{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "spark-job-stage-visualizer",
  "title": "Spark Job Stage Visualizer",
  "type": "registry:page",
  "files": [
    {
      "path": "packages/registry-angular/blocks/spark-job-stage-visualizer/spark-job-stage-visualizer.component.ts",
      "content": "// Databricks and Apache Spark distributed job execution DAG visualizer with stage\n// performance metrics, task skew distribution quantiles, and executor JVM GC telemetry.\n// Port of the Vue block 1:1 (multi-file Vue block consolidated into one standalone component).\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport {\n  ArrowRightLeft,\n  Check,\n  CheckCircle2,\n  Clock,\n  Copy,\n  Cpu,\n  Download,\n  Flame,\n  Gauge,\n  LucideAngularModule,\n  Server,\n  ShieldAlert,\n  X,\n  Zap,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiProgressComponent } from '@/ui/progress/progress.component'\nimport {\n  UiTableBodyComponent,\n  UiTableCellComponent,\n  UiTableComponent,\n  UiTableHeadComponent,\n  UiTableHeaderComponent,\n  UiTableRowComponent,\n} from '@/ui/table/table.component'\nimport { UiTabsComponent, UiTabsListComponent, UiTabsTriggerComponent } from '@/ui/tabs/tabs.component'\n\nexport type SparkTab = 'dag' | 'skew' | 'executors' | 'aqe'\n\ninterface SparkStageSummary {\n  id: number\n  name: string\n  status: 'succeeded' | 'running' | 'pending'\n  tasksTotal: number\n  tasksSucceeded: number\n  duration: string\n  skewRatio: number\n  quantiles: { min: number; p25: number; median: number; p75: number; p95: number; max: number }\n}\n\ninterface SparkExecutorSummary {\n  id: string\n  host: string\n  role: 'driver' | 'executor'\n  status: string\n  cores: number\n  memoryUsedGB: number\n  memoryTotalGB: number\n  memoryPercent: number\n  gcTimeSec: number\n  gcPercent: number\n  tasksCompleted: number\n  spillGB: number\n}\n\ninterface AqeRuleSummary {\n  id: string\n  name: string\n  status: 'applied' | 'skipped'\n  description: string\n  impact: string\n}\n\nconst defaultStages: SparkStageSummary[] = [\n  {\n    id: 0,\n    name: 'FileScan parquet',\n    status: 'succeeded',\n    tasksTotal: 540,\n    tasksSucceeded: 540,\n    duration: '18s',\n    skewRatio: 1.12,\n    quantiles: { min: 0.8, p25: 1.2, median: 1.6, p75: 1.8, p95: 2.0, max: 2.1 },\n  },\n  {\n    id: 1,\n    name: 'Exchange hashpartitioning',\n    status: 'succeeded',\n    tasksTotal: 200,\n    tasksSucceeded: 200,\n    duration: '42s',\n    skewRatio: 1.15,\n    quantiles: { min: 1.8, p25: 2.4, median: 2.9, p75: 3.2, p95: 3.4, max: 3.6 },\n  },\n  {\n    id: 2,\n    name: 'HashAggregate & Filter',\n    status: 'succeeded',\n    tasksTotal: 200,\n    tasksSucceeded: 200,\n    duration: '1m 12s',\n    skewRatio: 1.18,\n    quantiles: { min: 2.1, p25: 2.8, median: 3.5, p75: 3.9, p95: 4.1, max: 4.2 },\n  },\n  {\n    id: 3,\n    name: 'SortMergeJoin',\n    status: 'succeeded',\n    tasksTotal: 200,\n    tasksSucceeded: 200,\n    duration: '56s',\n    skewRatio: 1.09,\n    quantiles: { min: 1.9, p25: 2.5, median: 3.1, p75: 3.4, p95: 3.7, max: 3.8 },\n  },\n]\n\nconst defaultExecutors: SparkExecutorSummary[] = [\n  {\n    id: 'driver',\n    host: '10.0.12.4',\n    role: 'driver',\n    status: 'active',\n    cores: 16,\n    memoryUsedGB: 24.2,\n    memoryTotalGB: 32,\n    memoryPercent: 75.6,\n    gcTimeSec: 4.2,\n    gcPercent: 1.8,\n    tasksCompleted: 285,\n    spillGB: 0.36,\n  },\n  {\n    id: 'exec-1',\n    host: '10.0.12.18',\n    role: 'executor',\n    status: 'active',\n    cores: 32,\n    memoryUsedGB: 28.8,\n    memoryTotalGB: 32,\n    memoryPercent: 90.0,\n    gcTimeSec: 5.1,\n    gcPercent: 2.2,\n    tasksCompleted: 284,\n    spillGB: 0.42,\n  },\n  {\n    id: 'exec-2',\n    host: '10.0.12.29',\n    role: 'executor',\n    status: 'active',\n    cores: 32,\n    memoryUsedGB: 21.4,\n    memoryTotalGB: 32,\n    memoryPercent: 66.8,\n    gcTimeSec: 4.8,\n    gcPercent: 2.3,\n    tasksCompleted: 286,\n    spillGB: 0.34,\n  },\n  {\n    id: 'exec-3',\n    host: '10.0.12.44',\n    role: 'executor',\n    status: 'active',\n    cores: 32,\n    memoryUsedGB: 18.6,\n    memoryTotalGB: 32,\n    memoryPercent: 58.1,\n    gcTimeSec: 4.3,\n    gcPercent: 2.1,\n    tasksCompleted: 285,\n    spillGB: 0.36,\n  },\n]\n\nconst defaultAqeRules: AqeRuleSummary[] = [\n  {\n    id: 'aqe-1',\n    name: 'CoalesceShufflePartitions',\n    status: 'applied',\n    description:\n      'Coalesced 540 initial map partitions down to 200 target shuffle partitions based on target partition size (64 MB).',\n    impact: 'Reduced network RPC overhead by 63% and avoided 340 small shuffle partition files.',\n  },\n  {\n    id: 'aqe-2',\n    name: 'OptimizeSkewedJoin',\n    status: 'applied',\n    description: 'Dynamic skew detection evaluated all 200 partition sizes against the 5x median threshold.',\n    impact: 'Max partition skew ratio remained well balanced at 1.18x. No partition splitting required.',\n  },\n  {\n    id: 'aqe-3',\n    name: 'DemoteBroadcastHashJoin',\n    status: 'skipped',\n    description: 'Evaluated right dimension relation size (3.1 GB) against auto-broadcast threshold (10 MB).',\n    impact: 'Relation exceeded broadcast threshold; preserved robust SortMergeJoin strategy.',\n  },\n  {\n    id: 'aqe-4',\n    name: 'EliminateSubqueryAliases',\n    status: 'applied',\n    description: 'Inlined and pruned unused redundant subquery attribute references from execution DAG.',\n    impact: 'Streamlined Catalyst physical plan tree depth from 14 nodes down to 8 nodes.',\n  },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-spark-job-stage-visualizer, [ui-spark-job-stage-visualizer]',\n  standalone: true,\n  // Vue renders the root <div> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiProgressComponent,\n    UiTableBodyComponent,\n    UiTableCellComponent,\n    UiTableComponent,\n    UiTableHeadComponent,\n    UiTableHeaderComponent,\n    UiTableRowComponent,\n    UiTabsComponent,\n    UiTabsListComponent,\n    UiTabsTriggerComponent,\n  ],\n  template: `\n    <div data-slot=\"spark-job-stage-visualizer\" [class]=\"rootClass\">\n      <!-- Header Section -->\n      <div class=\"border-border bg-card rounded-xl border p-5 shadow-xs transition-colors\">\n        <div class=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n          <!-- Title & Identifiers -->\n          <div class=\"space-y-1.5\">\n            <div class=\"flex flex-wrap items-center gap-2.5\">\n              <div\n                class=\"flex size-9 items-center justify-center rounded-lg bg-orange-500/10 text-orange-600 dark:text-orange-400\"\n              >\n                <lucide-icon [img]=\"Flame\" class=\"size-5\" />\n              </div>\n              <div>\n                <div class=\"flex flex-wrap items-center gap-2\">\n                  <h1 class=\"text-base font-semibold tracking-tight sm:text-lg\">AggregatedCustomerLTVJob</h1>\n                  <span ui-badge wrap variant=\"outline\" class=\"text-muted-foreground font-mono text-xs\">\n                    app-20260821-142850-0042\n                  </span>\n                  <button\n                    ui-button\n                    variant=\"ghost\"\n                    size=\"xs\"\n                    class=\"text-muted-foreground hover:text-foreground h-6 px-1.5 text-xs\"\n                    aria-label=\"Copy Spark Application ID\"\n                    (click)=\"copyAppId()\"\n                  >\n                    @if (copiedAppId()) {\n                      <lucide-icon [img]=\"Check\" class=\"text-success size-3\" />\n                    } @else {\n                      <lucide-icon [img]=\"Copy\" class=\"size-3\" />\n                    }\n                    <span class=\"sr-only\">Copy Application ID</span>\n                  </button>\n                </div>\n                <p class=\"text-muted-foreground text-xs\">Apache Spark 3.5.1 · Adaptive Query Execution (AQE) Enabled</p>\n              </div>\n            </div>\n\n            <!-- Metadata Tags -->\n            <div class=\"text-muted-foreground flex flex-wrap items-center gap-x-4 gap-y-1 pt-1 text-xs\">\n              <div class=\"flex items-center gap-1.5\">\n                <span class=\"bg-success size-2 rounded-full\"></span>\n                <span class=\"text-foreground font-medium\">Job Succeeded</span>\n                <span>·</span>\n                <span class=\"font-mono tabular-nums\">03m:42s</span>\n              </div>\n              <div class=\"flex items-center gap-1\">\n                <lucide-icon [img]=\"Server\" class=\"size-3.5\" />\n                <span>\n                  Cluster: <strong class=\"text-foreground font-medium\">c5d.4xlarge (4 nodes · 112 vCPUs)</strong>\n                </span>\n              </div>\n              <div class=\"flex items-center gap-1\">\n                <lucide-icon [img]=\"Clock\" class=\"size-3.5\" />\n                <span>Started: <strong class=\"text-foreground font-medium\">14:28:50 UTC</strong></span>\n              </div>\n            </div>\n          </div>\n\n          <!-- Action Buttons -->\n          <div class=\"flex flex-wrap items-center gap-2\">\n            <button\n              ui-button\n              aria-label=\"Download attachment\"\n              variant=\"outline\"\n              size=\"sm\"\n              class=\"h-8 gap-1.5 text-xs\"\n              [disabled]=\"downloadStarted()\"\n              (click)=\"triggerDownload()\"\n            >\n              @if (downloadStarted()) {\n                <lucide-icon [img]=\"CheckCircle2\" class=\"text-success size-3.5\" />\n              } @else {\n                <lucide-icon [img]=\"Download\" class=\"size-3.5\" />\n              }\n              <span>{{ downloadStarted() ? 'Log Exported' : 'Download EventLog' }}</span>\n            </button>\n\n            <button\n              ui-button\n              variant=\"ghost\"\n              size=\"sm\"\n              class=\"text-destructive hover:bg-destructive/10 hover:text-destructive h-8 gap-1.5 text-xs\"\n              (click)=\"showKillConfirm.set(!showKillConfirm())\"\n            >\n              <lucide-icon [img]=\"X\" class=\"size-3.5\" />\n              <span>Kill Application</span>\n            </button>\n          </div>\n        </div>\n\n        <!-- Kill Confirmation Alert (Collapsible) -->\n        @if (showKillConfirm()) {\n          <div\n            class=\"border-destructive/30 bg-destructive/5 text-destructive mt-4 flex items-center justify-between rounded-lg border p-3 text-xs\"\n          >\n            <div class=\"flex flex-wrap items-center gap-2\">\n              <lucide-icon [img]=\"ShieldAlert\" class=\"size-4 shrink-0\" />\n              <span>\n                This application is in finished state (SUCCEEDED). Terminating an inactive application will release log\n                retention locks.\n              </span>\n            </div>\n            <div class=\"flex flex-wrap items-center gap-2\">\n              <button ui-button size=\"xs\" variant=\"destructive\" (click)=\"showKillConfirm.set(false)\">\n                Confirm Release\n              </button>\n              <button ui-button size=\"xs\" variant=\"ghost\" (click)=\"showKillConfirm.set(false)\">Cancel</button>\n            </div>\n          </div>\n        }\n      </div>\n\n      <!-- 4 Spark Job Execution KPI Cards -->\n      <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n        <div ui-card class=\"border-border shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-xs font-medium\">Total Stages Completed</h3>\n            <div class=\"bg-success/10 text-success flex size-7 items-center justify-center rounded-md\">\n              <lucide-icon [img]=\"CheckCircle2\" class=\"size-4\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-2\">\n            <div class=\"text-foreground font-mono text-2xl font-bold tracking-tight\">6 / 6</div>\n            <div class=\"flex flex-wrap items-center gap-2\">\n              <span ui-badge wrap variant=\"success\" class=\"text-xs\">6 / 6 Stages Succeeded</span>\n            </div>\n            <p class=\"text-muted-foreground text-xs\">0 failed · 0 skipped · 4 stages active in DAG</p>\n          </div>\n        </div>\n\n        <div ui-card class=\"border-border shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-xs font-medium\">Shuffle Read / Write Volume</h3>\n            <div class=\"bg-info/10 text-info flex size-7 items-center justify-center rounded-md\">\n              <lucide-icon [img]=\"ArrowRightLeft\" class=\"size-4\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-2\">\n            <div class=\"text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums\">42.8 GB</div>\n            <div class=\"text-muted-foreground flex items-center gap-1.5 text-xs\">\n              <span class=\"text-foreground font-medium\">42.8 GB Shuffle Write</span>\n              <span>·</span>\n              <span class=\"text-warning font-mono\">1.48 GB Spill (Memory)</span>\n            </div>\n            <p class=\"text-muted-foreground text-xs\">38.6 GB shuffle read · 0 B disk spill</p>\n          </div>\n        </div>\n\n        <div ui-card class=\"border-border shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-xs font-medium\">Task Skew Max/Median Ratio</h3>\n            <div class=\"bg-success/10 text-success flex size-7 items-center justify-center rounded-md\">\n              <lucide-icon [img]=\"Gauge\" class=\"size-4\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-2\">\n            <div class=\"text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums\">1.18x</div>\n            <div class=\"flex flex-wrap items-center gap-2\">\n              <span ui-badge wrap variant=\"success\" class=\"text-xs\">1.18x Ratio · Well Balanced</span>\n            </div>\n            <p class=\"text-muted-foreground text-xs\">\n              Max task: <span class=\"text-foreground font-mono font-medium\">4.2s</span> · Median:\n              <span class=\"text-foreground font-mono font-medium\">3.5s</span>\n            </p>\n          </div>\n        </div>\n\n        <div ui-card class=\"border-border shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-xs font-medium\">Executor JVM GC Time</h3>\n            <div class=\"bg-chart-1/10 text-chart-1 flex size-7 items-center justify-center rounded-md\">\n              <lucide-icon [img]=\"Cpu\" class=\"size-4\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-2\">\n            <div class=\"text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums\">2.1%</div>\n            <div class=\"flex flex-wrap items-center gap-2\">\n              <span ui-badge wrap variant=\"success\" class=\"text-xs\">2.1% of Total CPU Time · Healthy</span>\n            </div>\n            <p class=\"text-muted-foreground text-xs\">18.4s GC time across 880s CPU total</p>\n          </div>\n        </div>\n      </div>\n\n      <!-- Main Content Tabs -->\n      <ui-tabs [value]=\"activeTab()\" (valueChange)=\"setActiveTab($event)\" class=\"space-y-4\">\n        <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n          <ui-tabs-list class=\"grid w-full grid-cols-2 sm:w-auto sm:grid-cols-4\">\n            <button ui-tabs-trigger value=\"dag\" class=\"text-xs\">Stages DAG Flow</button>\n            <button ui-tabs-trigger value=\"skew\" class=\"text-xs\">Task Skew & Quantiles</button>\n            <button ui-tabs-trigger value=\"executors\" class=\"text-xs\">Executors (4)</button>\n            <button ui-tabs-trigger value=\"aqe\" class=\"text-xs\">AQE Optimizer</button>\n          </ui-tabs-list>\n\n          <div class=\"text-muted-foreground flex flex-wrap items-center gap-2 text-xs\">\n            <span class=\"bg-primary inline-block size-2 rounded-full\"></span>\n            <span>Active Selection:</span>\n            <span class=\"text-foreground font-medium\">Stage {{ selectedStage().id }} ({{ selectedStage().name }})</span>\n          </div>\n        </div>\n\n        <!-- TAB 1: STAGE DAG FLOW -->\n        @if (activeTab() === 'dag') {\n          <div class=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n            @for (stage of allStages(); track stage.id) {\n              <button type=\"button\" [class]=\"stageCardClass(stage.id)\" (click)=\"selectStage(stage.id)\">\n                <div class=\"flex items-center justify-between\">\n                  <span class=\"font-mono text-xs font-semibold\">Stage {{ stage.id }}</span>\n                  <span ui-badge [variant]=\"stage.status === 'succeeded' ? 'success' : 'secondary'\" class=\"text-xs\">\n                    {{ stage.status }}\n                  </span>\n                </div>\n                <p class=\"text-foreground mt-1.5 text-left text-sm font-semibold\">{{ stage.name }}</p>\n                <p class=\"text-muted-foreground mt-1 font-mono text-xs tabular-nums\">\n                  {{ stage.tasksSucceeded }}/{{ stage.tasksTotal }} tasks · {{ stage.duration }}\n                </p>\n                <div ui-progress [value]=\"(stage.tasksSucceeded / stage.tasksTotal) * 100\" class=\"mt-2 h-1.5\"></div>\n              </button>\n            }\n          </div>\n        }\n\n        <!-- TAB 2: TASK SKEW & QUANTILES -->\n        @if (activeTab() === 'skew') {\n          <div ui-card class=\"border-border shadow-xs\">\n            <div ui-card-header class=\"pb-2\">\n              <div class=\"flex flex-wrap items-center justify-between gap-2\">\n                <h3 ui-card-title class=\"text-sm font-semibold\">\n                  Task Duration Quantiles — Stage {{ selectedStage().id }} ({{ selectedStage().name }})\n                </h3>\n                <span ui-badge variant=\"outline\" class=\"font-mono text-xs\">\n                  Skew {{ selectedStage().skewRatio.toFixed(2) }}x\n                </span>\n              </div>\n            </div>\n            <div ui-card-content class=\"space-y-3\">\n              @for (q of quantileRows(); track q.label) {\n                <div class=\"flex items-center gap-3 text-xs\">\n                  <span class=\"text-muted-foreground w-14 shrink-0 font-mono\">{{ q.label }}</span>\n                  <div class=\"bg-muted/60 relative h-2.5 flex-1 overflow-hidden rounded-full\">\n                    <div class=\"bg-primary absolute top-0 bottom-0 left-0 rounded-full\" [style.width.%]=\"q.pct\"></div>\n                  </div>\n                  <span class=\"text-foreground w-12 shrink-0 text-right font-mono tabular-nums\">{{ q.value }}s</span>\n                </div>\n              }\n              <div class=\"flex flex-wrap gap-2 pt-1\">\n                @for (stage of allStages(); track stage.id) {\n                  <button\n                    ui-button\n                    [variant]=\"selectedStageId() === stage.id ? 'default' : 'outline'\"\n                    size=\"xs\"\n                    class=\"font-mono text-xs\"\n                    (click)=\"selectStage(stage.id)\"\n                  >\n                    Stage {{ stage.id }}\n                  </button>\n                }\n              </div>\n            </div>\n          </div>\n        }\n\n        <!-- TAB 3: EXECUTORS -->\n        @if (activeTab() === 'executors') {\n          <div ui-card class=\"border-border shadow-xs\">\n            <div ui-card-header class=\"pb-2\">\n              <div class=\"flex flex-wrap items-center justify-between gap-2\">\n                <h3 ui-card-title class=\"text-sm font-semibold\">\n                  Active Cluster Executors (4 Nodes · 112 Cores Total)\n                </h3>\n                <span ui-badge variant=\"success\" class=\"font-mono text-xs\">All Healthy</span>\n              </div>\n            </div>\n            <div ui-card-content class=\"overflow-x-auto p-0\">\n              <table ui-table>\n                <thead ui-table-header>\n                  <tr ui-table-row class=\"bg-muted/40 hover:bg-muted/40\">\n                    <th ui-table-head class=\"text-xs font-semibold\">Executor</th>\n                    <th ui-table-head class=\"text-xs font-semibold\">Host</th>\n                    <th ui-table-head class=\"text-right text-xs font-semibold\">Cores</th>\n                    <th ui-table-head class=\"text-right text-xs font-semibold\">Memory</th>\n                    <th ui-table-head class=\"text-right text-xs font-semibold\">GC Pause Time</th>\n                    <th ui-table-head class=\"text-right text-xs font-semibold\">Tasks</th>\n                  </tr>\n                </thead>\n                <tbody ui-table-body>\n                  @for (exec of executors(); track exec.id) {\n                    <tr ui-table-row class=\"text-xs\">\n                      <td ui-table-cell class=\"font-mono font-semibold\">{{ exec.id }}</td>\n                      <td ui-table-cell class=\"font-mono\">{{ exec.host }}</td>\n                      <td ui-table-cell class=\"text-right font-mono tabular-nums\">{{ exec.cores }}</td>\n                      <td ui-table-cell class=\"text-right font-mono tabular-nums\">\n                        {{ exec.memoryUsedGB.toFixed(1) }}/{{ exec.memoryTotalGB }} GB\n                      </td>\n                      <td ui-table-cell class=\"text-right font-mono tabular-nums\">\n                        {{ exec.gcTimeSec.toFixed(1) }}s ({{ exec.gcPercent.toFixed(1) }}%)\n                      </td>\n                      <td ui-table-cell class=\"text-right font-mono tabular-nums\">{{ exec.tasksCompleted }}</td>\n                    </tr>\n                  }\n                </tbody>\n              </table>\n            </div>\n          </div>\n        }\n\n        <!-- TAB 4: AQE OPTIMIZER -->\n        @if (activeTab() === 'aqe') {\n          <div class=\"space-y-3\">\n            <div ui-card class=\"border-border shadow-xs\">\n              <div ui-card-header class=\"pb-2\">\n                <div class=\"flex flex-wrap items-center justify-between gap-2\">\n                  <h3 ui-card-title class=\"text-base font-semibold\">Adaptive Query Execution (AQE) Rules Engine</h3>\n                  <span ui-badge variant=\"success\" class=\"font-mono text-xs\">AQE Status: Active (3 Rules Applied)</span>\n                </div>\n                <p class=\"text-muted-foreground text-xs\">\n                  Catalyst optimizer rewrites evaluated at runtime against live shuffle statistics.\n                </p>\n              </div>\n            </div>\n            @for (rule of aqeRules(); track rule.id) {\n              <div class=\"border-border bg-card space-y-3 rounded-xl border p-4\">\n                <div class=\"flex flex-wrap items-center justify-between gap-2\">\n                  <span class=\"text-foreground font-mono text-sm font-semibold\">{{ rule.name }}</span>\n                  <span\n                    ui-badge\n                    [variant]=\"rule.status === 'applied' ? 'success' : 'outline'\"\n                    class=\"font-mono text-xs\"\n                  >\n                    {{ rule.status }}\n                  </span>\n                </div>\n                <p class=\"text-muted-foreground text-xs\">{{ rule.description }}</p>\n                <p class=\"text-xs\">\n                  <lucide-icon [img]=\"Zap\" class=\"text-warning mr-1 inline size-3.5\" />\n                  <span class=\"text-muted-foreground\">{{ rule.impact }}</span>\n                </p>\n              </div>\n            }\n          </div>\n        }\n      </ui-tabs>\n    </div>\n  `,\n})\nexport class UiSparkJobStageVisualizerComponent {\n  protected readonly ArrowRightLeft = ArrowRightLeft\n  protected readonly Check = Check\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly Clock = Clock\n  protected readonly Copy = Copy\n  protected readonly Cpu = Cpu\n  protected readonly Download = Download\n  protected readonly Flame = Flame\n  protected readonly Gauge = Gauge\n  protected readonly Server = Server\n  protected readonly ShieldAlert = ShieldAlert\n  protected readonly X = X\n  protected readonly Zap = Zap\n\n  @Input() set initialStageId(v: number) {\n    if (v !== undefined && v !== null) this.selectedStageId.set(v)\n  }\n  @Input() set stages(v: SparkStageSummary[] | undefined) {\n    if (v) this.stageList.set(v)\n  }\n  @Input('class') className?: string\n\n  private readonly stageList = signal<SparkStageSummary[]>(defaultStages)\n  readonly executors = signal<SparkExecutorSummary[]>(defaultExecutors)\n  readonly aqeRules = signal<AqeRuleSummary[]>(defaultAqeRules)\n\n  readonly selectedStageId = signal(2)\n  readonly activeTab = signal<SparkTab>('dag')\n  readonly copiedAppId = signal(false)\n  readonly showKillConfirm = signal(false)\n  readonly downloadStarted = signal(false)\n\n  readonly allStages = computed(() => this.stageList())\n\n  readonly selectedStage = computed<SparkStageSummary>(() => {\n    const list = this.stageList()\n    return list.find((s) => s.id === this.selectedStageId()) ?? list[2] ?? list[0]!\n  })\n\n  readonly quantileRows = computed(() => {\n    const q = this.selectedStage().quantiles\n    const max = q.max || 1\n    const rows = [\n      { label: 'min', value: q.min },\n      { label: 'p25', value: q.p25 },\n      { label: 'median', value: q.median },\n      { label: 'p75', value: q.p75 },\n      { label: 'p95', value: q.p95 },\n      { label: 'max', value: q.max },\n    ]\n    return rows.map((r) => ({ ...r, value: r.value.toFixed(1), pct: Math.min(100, (r.value / max) * 100) }))\n  })\n\n  get rootClass(): string {\n    return cn('text-foreground w-full space-y-6 font-sans antialiased', this.className)\n  }\n\n  stageCardClass(id: number): string {\n    return cn(\n      'rounded-xl border p-4 text-left transition-colors cursor-pointer',\n      this.selectedStageId() === id\n        ? 'border-primary bg-primary/5 ring-primary shadow-xs ring-1'\n        : 'border-border bg-card hover:border-primary/40',\n    )\n  }\n\n  setActiveTab(value: string): void {\n    this.activeTab.set(value as SparkTab)\n  }\n\n  selectStage(id: number): void {\n    this.selectedStageId.set(id)\n  }\n\n  async copyAppId(): Promise<void> {\n    try {\n      await navigator.clipboard.writeText('app-20260821-142850-0042')\n      this.copiedAppId.set(true)\n      setTimeout(() => this.copiedAppId.set(false), 2000)\n    } catch {\n      // Clipboard unavailable\n    }\n  }\n\n  triggerDownload(): void {\n    this.downloadStarted.set(true)\n    setTimeout(() => this.downloadStarted.set(false), 2500)\n  }\n}\n",
      "type": "registry:page",
      "target": "~/src/app/components/blocks/spark-job-stage-visualizer/spark-job-stage-visualizer.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/spark-job-stage-visualizer/index.ts",
      "content": "export { UiSparkJobStageVisualizerComponent, type SparkTab } from './spark-job-stage-visualizer.component'\n",
      "type": "registry:page",
      "target": "~/src/app/components/blocks/spark-job-stage-visualizer/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/progress.json",
    "https://uipkge.dev/r/angular/table.json",
    "https://uipkge.dev/r/angular/tabs.json"
  ],
  "description": "Databricks and Apache Spark distributed job execution DAG visualizer with stage performance metrics, task skew distribution quantiles, and executor JVM GC telemetry.",
  "categories": [
    "devops",
    "dashboard",
    "analytics",
    "data",
    "app"
  ]
}