<!-- #region DashboardHub [C:4] [TYPE Page] [SEMANTICS sveltekit, dashboard, hub, git, bulk-action] -->
<!-- @ingroup Routes -->
<!-- @BRIEF Dashboard hub page migrated to DashboardDataGrid with header snippet, render-based cells, and selection via bridge. -->
<!-- @LAYER Page -->
<!-- @RELATION DEPENDS_ON -> [Dashboard.DataGrid] -->
<!-- @RELATION BINDS_TO -> [DashboardHubModel] -->
<!-- @RATIONALE MIGRATED from inline CSS Grid to DashboardDataGrid. Svelte 5 limitation: only one snippet per component. Strategy: header snippet for sort+filter headers, Column.render() for cells, internal selectable for checkboxes. -->
<script lang="ts">
  import { onMount } from "svelte";
  import { tick } from "svelte";
  import { goto } from "$app/navigation";
  import { ROUTES } from "$lib/routes.js";
  import { t } from "$lib/i18n/index.svelte.js";
  import SearchableMultiSelect from "$lib/components/ui/SearchableMultiSelect.svelte";
  import DashboardMaintenanceBadge from "$lib/components/DashboardMaintenanceBadge.svelte";
  import type { Column } from "$lib/components/dashboard/DashboardDataGrid.svelte";
  import { environmentContextStore, initializeEnvironmentContext, setSelectedEnvironment } from "$lib/stores/environmentContext.svelte.js";
  import { Button, EmptyState } from "$lib/ui";
  import ColumnFilterPopover from "./ColumnFilterPopover.svelte";
  import { formatDate, getSortIndicator } from "./dashboard-helpers.js";
  import { DashboardHubModel } from "$lib/models/DashboardHubModel.svelte.ts";
  import MigrateDashboardModal from "./MigrateDashboardModal.svelte";
  import BackupDashboardModal from "./BackupDashboardModal.svelte";
  import DashboardDataGrid from "$lib/components/dashboard/DashboardDataGrid.svelte";
  import StartupEnvironmentWizard from "$lib/components/ui/StartupEnvironmentWizard.svelte";
  import { maintenanceStore } from "$lib/stores/maintenance.svelte.js";

  const m = new DashboardHubModel();

  onMount(async () => {
    await initializeEnvironmentContext();
    if (!m.selectedEnv && $environmentContextStore?.selectedEnvId) m.selectedEnv = $environmentContextStore.selectedEnvId;
  });

  function handleDocumentClick(event: MouseEvent) {
    if (!(event.target as HTMLElement).closest('.action-dropdown')) m.closeActionDropdown();
    if (!(event.target as HTMLElement).closest('.column-filter')) m.openFilterColumn = null;
  }
  if (typeof document !== 'undefined') document.addEventListener('click', handleDocumentClick);

  async function handleEnvironmentCreated(envId: string) { if (!envId) return; m.error = null; m.lastLoadedEnvId = null; m.selectedEnv = envId; setSelectedEnvironment(envId); }

  // ── Pagination bridge ──
  let pageZero = $state(m.currentPage - 1);
  $effect(() => { if (pageZero + 1 !== m.currentPage) m.setPage(pageZero + 1); });
  $effect(() => { if (m.currentPage - 1 !== pageZero) pageZero = m.currentPage - 1; });
  function handlePageSizeChange(size: number) { m.setPageSize({ target: { value: String(size) } } as any); }

  // ── Validation version (local reactive mirror, triggers grid {#key}) ──
  let localValidationVersion = $state(0);
  $effect(() => { localValidationVersion = m.validationVersion; });

  // ── Selection bridge (array ↔ model Set) ──
  let selectedIdsArray = $state([] as number[]);
  $effect(() => { selectedIdsArray = Array.from(m.selectedIds); });
  $effect(() => {
    const arrSet = new Set(selectedIdsArray);
    const modelSet = m.selectedIds;
    const same = arrSet.size === modelSet.size && selectedIdsArray.every((id: number) => modelSet.has(id));
    if (!same) m.replaceSelectedIds(arrSet);
  });

  let environments = $derived($environmentContextStore?.environments || []);
  let shouldShowEnvironmentWizard = $derived(Boolean($environmentContextStore?.isLoaded) && !$environmentContextStore?.isLoading && !$environmentContextStore?.error && environments.length === 0);
  let shouldShowEnvironmentZeroState = $derived(shouldShowEnvironmentWizard && !m.isLoading && !m.error);

  $effect(() => { const ctxEnvId = $environmentContextStore?.selectedEnvId; if (ctxEnvId && m.selectedEnv !== ctxEnvId) m.selectedEnv = ctxEnvId; });
  $effect(() => { const ctx = $environmentContextStore; if (!ctx || ctx.isLoading) return; if (ctx.error && !m.selectedEnv) { m.error = ctx.error; m.isLoading = false; return; } if (ctx.isLoaded && ctx.environments.length === 0) { m.isLoading = false; m.error = null; m.allDashboards = []; m.dashboards = []; m.filteredDashboards = []; m.total = 0; m.totalPages = 1; m.serverTotal = 0; m.serverTotalPages = 1; m.lastLoadedEnvId = null; } });
  $effect(() => { m.filterDashboardIds; queueMicrotask(() => { if (m.allDashboards.length > 0) m.applyGridTransforms(); }); });
  $effect(() => { const envId = m.selectedEnv; if (!envId || envId === m.lastLoadedEnvId) return; m.lastLoadedEnvId = envId; m.currentPage = 1; m.profileFilterOverrideShowAll = false; m.effectiveProfileFilter = null;     m.clearSelectedIds(); m.filterDashboardIds = [];
    void m.loadDashboards(); void m.loadDashboardSearchOptions('');
    void maintenanceStore.loadDashboardBanners().catch(() => {});
  });

  // ── Maintenance badge mounting (Svelte components into raw HTML placeholders) ──
  let maintenanceInstances: Array<{ $destroy?: () => void }> = [];
  async function mountMaintenanceBadges() {
    maintenanceInstances.forEach(c => c.$destroy?.());
    maintenanceInstances = [];
    await tick();
    const spans = document.querySelectorAll('[data-maintenance-dashboard]');
    spans.forEach(span => {
      const dashId = span.getAttribute('data-maintenance-dashboard');
      if (dashId && !span.hasChildNodes()) {
        const instance = new DashboardMaintenanceBadge({
          target: span as HTMLElement,
          props: { dashboardId: Number(dashId) }
        }) as { $destroy?: () => void };
        maintenanceInstances.push(instance);
      }
    });
  }
  $effect(() => {
    m.dashboards;
    mountMaintenanceBadges().catch(() => {}); // silently handle async errors
  });

  // ── Grid columns with render functions for cells ──
  let gridColumns = $derived.by(() => {
    // Force re-derive when validation statuses or dashboards change
    void m.validationVersion; void m.dashboards;
    return [
    { key: 'title', label: $t.dashboard?.title || 'Title', sortable: true, class: 'min-w-[250px]',
      render: (d: any) => `<a class="text-primary hover:text-primary-hover hover:underline text-sm font-medium" href="${ROUTES.dashboards.detail(d.id, m.selectedEnv)}">${d.title ?? ''}</a>`, raw: true },
    { key: 'git_status', label: 'GIT', sortable: true, class: 'min-w-[118px]',
      render: (d: any) => renderGitStatus(d), raw: true },
    { key: 'validation_status', label: $t.dashboard?.validation || 'Validation', sortable: true, class: 'min-w-[70px]',
      render: (d: any) => renderValidationDot(d), raw: true },
    { key: 'maintenance', label: $t.maintenance?.maintenance_badge || 'Maint.', sortable: false, class: 'min-w-[110px]',
      render: (d: any) => `<span data-maintenance-dashboard="${d.id}"></span>`, raw: true },
    { key: 'actions', label: $t.dashboard?.actions || 'Actions', sortable: false, class: 'min-w-[124px]',
      render: (d: any) => renderActions(d), raw: true },
    { key: 'changed_on', label: $t.dashboard?.changed_on || 'Changed', sortable: true, class: 'min-w-[150px]',
      render: (d: any) => `<span class="text-xs text-text-muted">${d.changedOnLabel ?? '-'}</span>`, raw: true },
    { key: 'actor', label: $t.dashboard?.owners || 'Owners', sortable: true, class: 'min-w-[150px]',
      render: (d: any) => renderOwners(d), raw: true },
    ] as Column[];
  });

  function renderGitStatus(d: any): string {
    const loading = $t.common?.loading || "Loading";
    const repo = $t.dashboard?.status_repo || "Repo";
    const noRepo = $t.dashboard?.status_no_repo || "No Repo";
    const diff = $t.dashboard?.status_changes || "Diff";
    const synced = $t.dashboard?.status_no_changes || "Synced";
    const hasRepo = d.git?.hasRepo;
    const repoClass = hasRepo === null ? 'bg-surface-muted text-text-muted' : hasRepo ? 'bg-primary-light text-primary' : 'bg-surface-muted text-text';
    const repoText = hasRepo === null ? loading : hasRepo ? repo : noRepo;
    let html = `<div class="flex flex-col gap-1"><span class="inline-flex w-fit items-center rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${repoClass}">${repoText}</span>`;
    if (hasRepo) {
      const changeClass = d.git?.hasChangesForCommit ? 'bg-warning-light text-warning' : 'bg-success-light text-success';
      const changeText = d.git?.hasChangesForCommit ? diff : synced;
      html += `<span class="inline-flex w-fit items-center rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${changeClass}">${changeText}</span>`;
    }
    html += '</div>';
    return html;
  }

  function renderValidationDot(d: any): string {
    const vs = m.validationStatuses[d.id];
    if (!vs) return m.validationStatusLoading ? '<span class="inline-flex h-5 w-5 items-center justify-center"><span class="block h-3 w-3 animate-pulse rounded-full bg-surface-muted"></span></span>' : '<span class="text-text-subtle text-xs">-</span>';
    const dotClass = vs.status === "PASS" ? "bg-success" : vs.status === "WARN" ? "bg-warning" : vs.status === "FAIL" ? "bg-destructive" : "bg-border";
    return `<span class="inline-flex h-5 w-5 items-center justify-center rounded-full border border-border-strong cursor-pointer hover:ring-2 hover:ring-border-strong" data-validation-popover="${d.id}" title="${vs.status || 'UNKNOWN'}"><span class="block h-3 w-3 rounded-full ${dotClass}"></span></span>`;
  }

  function renderActions(d: any): string {
    return `<div class="flex items-center gap-1.5">
      <button class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-border bg-surface-card text-primary hover:bg-primary-light hover:border-primary-ring transition-colors" data-action="migrate" data-dashboard-id="${d.id}" title="${$t.dashboard?.action_migrate || 'Migrate'}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg></button>
      <button class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-border bg-surface-card text-warning hover:bg-warning-light hover:border-warning transition-colors" data-action="backup" data-dashboard-id="${d.id}" title="${$t.dashboard?.action_backup || 'Backup'}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg></button>
    </div>`;
  }

  function renderOwners(d: any): string {
    if (!d.owners?.length) return '<span class="text-text-subtle">-</span>';
    return `<div class="flex flex-wrap items-center gap-1">${d.owners.map((o: string) => `<span class="rounded bg-surface-muted px-2 py-0.5 text-xs text-text truncate max-w-[180px]" title="${o}">${o}</span>`).join('')}</div>`;
  }

  // Delegate click handlers for rendered HTML buttons (validation popover + actions)
  function handleGridClick(event: MouseEvent) {
    const target = event.target as HTMLElement;
    const btn = target.closest('[data-action]') as HTMLElement | null;
    if (btn) {
      const action = btn.getAttribute('data-action');
      const dashId = Number(btn.getAttribute('data-dashboard-id'));
      const dashboard = m.dashboards.find((d: any) => d.id === dashId);
      if (dashboard) m.handleAction(dashboard, action!);
      return;
    }
    const dot = target.closest('[data-validation-popover]') as HTMLElement | null;
    if (dot) {
      const dashId = Number(dot.getAttribute('data-validation-popover'));
      m.toggleValidationPopover(dashId, dot);
      return;
    }
  }
  if (typeof document !== 'undefined') document.addEventListener('click', handleGridClick);
</script>

<div class="mx-auto w-full px-4 lg:px-8 space-y-6">
  <div class="flex items-center justify-between">
    <h1 class="text-2xl font-bold text-text">{$t.nav?.dashboards}</h1>
    <div class="flex items-center space-x-4">
      <button class="inline-flex items-center justify-center rounded-lg border border-primary-ring bg-primary-light px-4 py-2 text-sm font-medium text-primary transition-colors hover:bg-primary-light" onclick={() => { const ids = Array.from(m.selectedIds); goto(ROUTES.validationTasks.new(ids.length > 0 ? ids.map(Number) : undefined)); }}>
        + {$t.dashboard?.create_validation_task || "Create Validation Task"}</button>
      <button class="inline-flex items-center justify-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary-hover" onclick={() => m.loadDashboards()}>{$t.common?.refresh}</button>
    </div>
  </div>

  {#if m.effectiveProfileFilter?.applied}
    <div class="rounded-lg border border-primary-ring bg-primary-light px-4 py-2 text-sm text-primary">
      <span class="font-medium">{$t.profile?.filter_badge_active || "Profile filters active"}</span>
      <button type="button" class="ml-3 rounded border border-primary bg-surface-card px-2.5 py-1 text-xs font-medium text-primary hover:bg-primary-light" onclick={() => m.handleTemporaryShowAll()}>{$t.profile?.filter_show_all_temporarily || "Show all"}</button>
    </div>
  {:else if m.effectiveProfileFilter?.override_show_all}
    <div class="rounded-lg border border-warning bg-warning-light px-4 py-2 text-sm text-warning">
      <span class="font-medium">{$t.profile?.filter_badge_override || "Showing all"}</span>
      <button type="button" class="ml-3 rounded border border-warning bg-surface-card px-2.5 py-1 text-xs font-medium text-warning hover:bg-warning-light" onclick={() => m.handleRestoreProfileFilter()}>{$t.profile?.filter_restore_default || "Restore filter"}</button>
    </div>
  {/if}

  {#if m.error}
    <div class="bg-destructive-light border border-destructive-ring text-destructive px-4 py-3 rounded flex items-center justify-between"><span>{m.error}</span><Button variant="destructive" onclick={() => m.loadDashboards()}>{$t.common?.retry}</Button></div>
  {/if}

  {#if shouldShowEnvironmentZeroState}
    <div class="relative overflow-hidden rounded-3xl border border-border bg-surface-page p-8 shadow-sm"><div class="max-w-2xl"><p class="text-xs font-semibold uppercase tracking-[0.24em] text-primary">{$t.dashboard?.setup_badge || "Initial setup"}</p><h2 class="mt-3 text-2xl font-semibold text-text">{$t.dashboard?.setup_empty_title || "No environments"}</h2><p class="mt-3 text-sm leading-6 text-text-muted">{$t.dashboard?.setup_empty_body || "Add environment to begin."}</p><div class="mt-6 flex flex-wrap gap-3"><button type="button" class="rounded-xl bg-primary px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-primary-hover">{$t.dashboard?.setup_start || "Start setup"}</button><button type="button" class="rounded-xl border border-border-strong px-4 py-2.5 text-sm font-medium text-text transition hover:border-border-strong hover:text-text" onclick={() => goto(ROUTES.settings.environmentsTab())}>{$t.dashboard?.setup_open_advanced || "Advanced settings"}</button></div></div></div>
  {:else if !m.isLoading && m.dashboards.length === 0}
    {#if m.effectiveProfileFilter?.applied}
      <EmptyState title={$t.profile?.filter_empty_state || "No dashboards found"} actionLabel={$t.profile?.filter_show_all_temporarily || "Show all"} onAction={() => m.handleTemporaryShowAll()} />
    {:else}
      <EmptyState title={$t.dashboard?.empty} />
    {/if}
  {:else}
    <div class="flex items-center justify-between mb-4 gap-4">
      <div class="flex items-center gap-2">
        <Button variant="secondary" size="sm" onclick={() => m.handleSelectAll()} disabled={m.total === 0}>{m.isAllSelected ? $t.dashboard?.deselect_all : $t.dashboard?.select_all}</Button>
        <Button variant="secondary" size="sm" onclick={() => m.handleSelectVisible()} disabled={m.dashboards.length === 0}>{m.isAllVisibleSelected ? $t.dashboard?.deselect_visible : $t.dashboard?.select_visible}</Button>
        {#if m.selectedIds.size > 0}<span class="text-sm text-text-muted">{($t.dashboard?.selected_count ?? '{count} selected').replace("{count}", String(m.selectedIds.size))}</span>{/if}
      </div>
      <div class="flex items-center gap-3">
        <select class="rounded border border-border-strong bg-surface-card px-2 py-1.5 text-xs text-text" value={m.pageSize} onchange={(e) => handlePageSizeChange(Number((e.target as HTMLSelectElement).value))}>
          {#each [5, 10, 25, 50, 100] as size}<option value={size}>{size}</option>{/each}
        </select>
        <div class="w-80"><SearchableMultiSelect options={m.searchableDashboardOptions} bind:selected={m.filterDashboardIds} placeholder={$t.dashboard?.search} selectAllLabel={$t.dashboard?.select_all} clearLabel={$t.common?.clear || "Clear"} emptyMessage="No dashboards match" loading={m.searchableDashboardLoading} onSearch={m.loadDashboardSearchOptions} debounceMs={300} /></div>
      </div>
    </div>

    <div class="bg-surface-card border border-border rounded-xl shadow-sm">
      {#key localValidationVersion}
      <DashboardDataGrid
        data={m.dashboards}
        keyField="id"
        columns={gridColumns}
        selectable={true}
        bind:selectedIds={selectedIdsArray}
        hideFilter={true}
        paginated={true}
        pageSize={m.pageSize}
        bind:page={pageZero}
        serverTotal={m.total}
        serverTotalPages={m.totalPages}
        loading={m.isLoading}
        loadingRows={Math.min(m.pageSize, 10)}
        emptyText={$t.dashboard?.no_dashboards || "No dashboards found"}
      >
        {#snippet header(col)}
          <div class="flex items-center gap-2">
            {#if col.sortable}
              <button class="hover:text-text transition-colors" onclick={() => m.handleSort(col.key)}>
                {col.label} {getSortIndicator(m.sortColumn, col.key, m.sortDirection)}
              </button>
              {#if col.key === 'title'}
                <div class="relative column-filter"><button class="rounded border px-1.5 py-0.5 text-xs transition-colors {m.hasColumnFilter('title') ? 'border-primary-ring text-primary bg-primary-light' : 'border-border-strong text-text-muted hover:text-text'}" onclick={(event) => m.toggleFilterDropdown("title", event, 256)} title={$t.dashboard?.column_filter}>▾</button>
                  {#if m.openFilterColumn === "title"}<ColumnFilterPopover column="title" bind:filterValues={m.columnFilters.title} bind:searchText={m.columnFilterSearch.title} options={m.getVisibleFilterOptions("title")} position={m.filterDropdownPosition} widthPx={256} onToggleFilter={(v: string, c: boolean) => m.toggleFilterValue("title", v, c)} onClear={() => m.clearColumnFilter("title")} />{/if}</div>
              {:else if col.key === 'git_status'}
                <div class="relative column-filter"><button class="rounded border px-1.5 py-0.5 text-xs transition-colors {m.hasColumnFilter('git_status') ? 'border-primary-ring text-primary bg-primary-light' : 'border-border-strong text-text-muted hover:text-text'}" onclick={(event) => m.toggleFilterDropdown("git_status", event, 256)} title={$t.dashboard?.column_filter}>▾</button>
                  {#if m.openFilterColumn === "git_status"}<ColumnFilterPopover column="git_status" bind:filterValues={m.columnFilters.git_status} bind:searchText={m.columnFilterSearch.git_status} options={m.getVisibleFilterOptions("git_status")} position={m.filterDropdownPosition} widthPx={256} getLabel={(v: string) => m.getFilterOptionLabel("git_status", v)} onToggleFilter={(v: string, c: boolean) => m.toggleFilterValue("git_status", v, c)} onClear={() => m.clearColumnFilter("git_status")} />{/if}</div>
              {:else if col.key === 'validation_status'}
                <div class="relative column-filter"><button class="rounded border px-1.5 py-0.5 text-xs transition-colors {m.hasColumnFilter('validation_status') ? 'border-primary-ring text-primary bg-primary-light' : 'border-border-strong text-text-muted hover:text-text'}" onclick={(event) => m.toggleFilterDropdown("validation_status", event, 200)} title={$t.dashboard?.column_filter}>▾</button>
                  {#if m.openFilterColumn === "validation_status"}<ColumnFilterPopover column="validation_status" bind:filterValues={m.columnFilters.validation_status} bind:searchText={m.columnFilterSearch.validation_status} options={[]} staticOptions={["pass", "warn", "fail", "unknown"]} showSearch={false} position={m.filterDropdownPosition} widthPx={200} onToggleFilter={(v: string, c: boolean) => m.toggleFilterValue("validation_status", v, c)} onClear={() => m.clearColumnFilter("validation_status")} />{/if}</div>
              {:else if col.key === 'changed_on'}
                <div class="relative column-filter"><button class="rounded border px-1.5 py-0.5 text-xs transition-colors {m.hasColumnFilter('changed_on') ? 'border-primary-ring text-primary bg-primary-light' : 'border-border-strong text-text-muted hover:text-text'}" onclick={(event) => m.toggleFilterDropdown("changed_on", event, 256)} title={$t.dashboard?.column_filter}>▾</button>
                  {#if m.openFilterColumn === "changed_on"}<ColumnFilterPopover column="changed_on" bind:filterValues={m.columnFilters.changed_on} bind:searchText={m.columnFilterSearch.changed_on} options={m.getVisibleFilterOptions("changed_on")} position={m.filterDropdownPosition} widthPx={256} onToggleFilter={(v: string, c: boolean) => m.toggleFilterValue("changed_on", v, c)} onClear={() => m.clearColumnFilter("changed_on")} />{/if}</div>
              {:else if col.key === 'actor'}
                <div class="relative column-filter"><button class="rounded border px-1.5 py-0.5 text-xs transition-colors {m.hasColumnFilter('actor') ? 'border-primary-ring text-primary bg-primary-light' : 'border-border-strong text-text-muted hover:text-text'}" onclick={(event) => m.toggleFilterDropdown("actor", event, 288)} title={$t.dashboard?.column_filter}>▾</button>
                  {#if m.openFilterColumn === "actor"}<ColumnFilterPopover column="actor" bind:filterValues={m.columnFilters.actor} bind:searchText={m.columnFilterSearch.actor} options={m.getVisibleFilterOptions("actor")} position={m.filterDropdownPosition} widthPx={288} onToggleFilter={(v: string, c: boolean) => m.toggleFilterValue("actor", v, c)} onClear={() => m.clearColumnFilter("actor")} />{/if}</div>
              {/if}
            {:else}
              {col.label}
            {/if}
          </div>
        {/snippet}
      </DashboardDataGrid>
      {/key}
    </div>

    {#if m.selectedIds.size > 0}
      <div class="fixed bottom-0 left-0 right-0 bg-surface-card border-t border-border shadow-lg p-4"><div class="flex items-center justify-between w-full px-4 lg:px-8 mx-auto"><span class="font-medium">✓ {($t.dashboard?.selected_count ?? '{count} selected').replace("{count}", String(m.selectedIds.size))}</span><div class="flex gap-3">
        <button class="px-3 py-1 text-sm bg-primary text-white border-primary rounded hover:bg-primary-hover transition-colors" onclick={() => { m.showMigrateModal = true; m.isEditingMappings = false; m.bulkMigrateModel.sourceEnvId = m.selectedEnv; m.bulkMigrateModel.selectedDashboardIds = Array.from(m.selectedIds); m.bulkMigrateModel.targetEnvId = ""; m.bulkMigrateModel.dryRunResult = null; m.bulkMigrateModel.error = ""; m.bulkMigrateModel.sourceDatabases = []; m.bulkMigrateModel.targetDatabases = []; m.bulkMigrateModel.mappings = []; m.bulkMigrateModel.suggestions = []; m.bulkMigrateModel.currentStep = 1; }}>{$t.dashboard?.bulk_migrate}</button>
        <button class="px-3 py-1 text-sm bg-primary text-white border-primary rounded hover:bg-primary-hover transition-colors" onclick={() => (m.showBackupModal = true)}>{$t.dashboard?.bulk_backup}</button>
        <button class="px-3 py-1 text-sm border border-border-strong rounded hover:bg-surface-muted transition-colors" onclick={() => m.clearSelectedIds()}>{$t.common?.cancel}</button>
      </div></div></div>
    {/if}
  {/if}

  <MigrateDashboardModal model={m} {environments} />
  <BackupDashboardModal model={m} {environments} />
  <StartupEnvironmentWizard open={shouldShowEnvironmentWizard} onCreated={handleEnvironmentCreated} />

  <!-- Validation Popover -->
  {#if m.openValidationPopover}
    <!-- svelte-ignore a11y_click_events_have_key_events -->
    <div class="fixed inset-0 z-40" role="presentation" onclick={() => m.closeValidationPopover()}></div>
    <div class="fixed z-50 w-80 rounded-lg border border-border bg-surface-card shadow-lg" style="left: {m.validationPopoverPosition.left}px; top: {m.validationPopoverPosition.top}px;" onclick={(e) => e.stopPropagation()}>
      <div class="flex items-center justify-between border-b border-border px-3 py-2">
        <span class="text-xs font-semibold uppercase tracking-wide text-text-muted">{$t.dashboard?.validation_history || "Validation History"}</span>
        <button type="button" class="text-text-muted hover:text-text" onclick={() => m.closeValidationPopover()} aria-label={$t.common?.close || "Close"}>✕</button>
      </div>
      {#if m.validationStatuses[m.openValidationPopover]?.history?.length > 0}
        <div class="max-h-48 overflow-y-auto">
          {#each m.validationStatuses[m.openValidationPopover].history.slice(0, 3) as run}
            <div class="flex items-center justify-between px-3 py-2 text-xs border-b border-border last:border-b-0">
              <span class="block h-2.5 w-2.5 rounded-full {run.status === 'PASS' ? 'bg-success' : run.status === 'WARN' ? 'bg-warning' : run.status === 'FAIL' ? 'bg-destructive' : 'bg-border'}"></span>
              <span class="text-text">{run.status || "UNKNOWN"}</span>
              <span class="text-text-subtle">{run.task_name || "-"}</span>
              <span class="text-text-subtle">{formatDate(run.last_run_at)}</span>
            </div>
          {/each}
        </div>
      {:else}
        <div class="px-3 py-3 text-xs text-text-subtle text-center">{$t.dashboard?.no_validation_runs || "No validation runs yet"}</div>
      {/if}
      <div class="border-t border-border px-3 py-2"><a href={ROUTES.validationTasks.list(String(m.openValidationPopover))} class="text-xs text-primary hover:text-primary-hover hover:underline">{$t.dashboard?.view_all_runs || "View all runs →"}</a></div>
    </div>
  {/if}
</div>

<!-- #endregion DashboardHub -->
