{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "sql-query-notebook",
  "title": "Sql Query Notebook",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/sql-query-notebook/sql-query-notebook.component.ts",
      "content": "// Hex, Deepnote, and Jupyter style SQL analytical query notebook with documentation\n// cells, executable Snowflake SQL editor with syntax coloring, live tabular results grid,\n// CSV export, and chart visualization. Port of the Vue block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport {\n  BarChart3,\n  Check,\n  Clock,\n  Copy,\n  Database,\n  Download,\n  FileCode2,\n  FileText,\n  Layers,\n  Loader2,\n  LucideAngularModule,\n  Play,\n  Plus,\n  Table2,\n  Target,\n  Terminal,\n  Trash2,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component'\nimport {\n  UiTableBodyComponent,\n  UiTableCellComponent,\n  UiTableComponent,\n  UiTableHeadComponent,\n  UiTableHeaderComponent,\n  UiTableRowComponent,\n} from '@/ui/table/table.component'\n\ninterface CohortRow {\n  signup_cohort: string\n  cohort_label: string\n  total_customers: number\n  avg_mrr: number\n  cohort_ltv: number\n  expansion_rate: string\n  retention_score: number\n}\n\nconst sqlQuery = `SELECT\n  DATE_TRUNC('month', created_at) AS signup_cohort,\n  COUNT(DISTINCT customer_id) AS total_customers,\n  ROUND(AVG(mrr), 2) AS avg_mrr,\n  ROUND(SUM(lifetime_value), 2) AS cohort_ltv\nFROM analytics.fct_subscriptions\nWHERE created_at >= '2026-01-01'\nGROUP BY 1 ORDER BY 1 DESC;`\n\nconst cohortResults: CohortRow[] = [\n  {\n    signup_cohort: '2026-06-01',\n    cohort_label: 'Jun 2026',\n    total_customers: 4820,\n    avg_mrr: 248.5,\n    cohort_ltv: 1428500.0,\n    expansion_rate: '+18.4%',\n    retention_score: 94,\n  },\n  {\n    signup_cohort: '2026-05-01',\n    cohort_label: 'May 2026',\n    total_customers: 3940,\n    avg_mrr: 232.1,\n    cohort_ltv: 1148200.0,\n    expansion_rate: '+14.1%',\n    retention_score: 89,\n  },\n  {\n    signup_cohort: '2026-04-01',\n    cohort_label: 'Apr 2026',\n    total_customers: 3110,\n    avg_mrr: 219.8,\n    cohort_ltv: 924100.0,\n    expansion_rate: '+11.8%',\n    retention_score: 85,\n  },\n  {\n    signup_cohort: '2026-03-01',\n    cohort_label: 'Mar 2026',\n    total_customers: 2420,\n    avg_mrr: 198.4,\n    cohort_ltv: 682900.0,\n    expansion_rate: '+8.6%',\n    retention_score: 81,\n  },\n]\n\n/** SQL syntax highlighter: tokenizes keywords, functions, strings, and numbers. */\nexport function highlightSqlLine(line: string): string {\n  if (!line.trim()) return '&nbsp;'\n  const escaped = line.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n  if (/^\\s*--/.test(escaped)) return `<span class=\"text-zinc-500 italic\">${escaped}</span>`\n  // Every emitted <span> is parked behind a letter-only placeholder so later\n  // passes cannot match inside the markup they already produced.\n  const parked: string[] = []\n  const park = (html: string) => {\n    const key = String(parked.length)\n      .split('')\n      .map((d) => String.fromCharCode(97 + Number(d)))\n      .join('')\n    parked.push(html)\n    return `\\u0000${key}\\u0000`\n  }\n  let out = escaped\n  out = out.replace(/(['\"])(?:(?=(\\\\?))\\2.)*?\\1/g, (m) => park(`<span class=\"text-success font-normal\">${m}</span>`))\n  out = out.replace(\n    /\\b(DATE_TRUNC|COUNT|DISTINCT|ROUND|AVG|SUM|MIN|MAX|COALESCE|CAST|CONCAT|NOW|CURRENT_TIMESTAMP)\\b/g,\n    (m) => park(`<span class=\"text-info font-semibold\">${m}</span>`),\n  )\n  out = out.replace(\n    /\\b(SELECT|FROM|WHERE|GROUP BY|ORDER BY|GROUP|ORDER|BY|DESC|ASC|AS|AND|OR|NOT|IN|ON|JOIN|LEFT|RIGHT|INNER|OUTER|LIMIT|HAVING|UNION|ALL|CASE|WHEN|THEN|ELSE|END|WITH)\\b/g,\n    (m) => park(`<span class=\"text-chart-1 font-semibold\">${m}</span>`),\n  )\n  out = out.replace(/\\b(\\d+(\\.\\d+)?)\\b/g, (m) => park(`<span class=\"text-warning font-mono\">${m}</span>`))\n  return out.replace(/\\u0000([a-j]+)\\u0000/g, (_, key: string) => {\n    const idx = Number(\n      key\n        .split('')\n        .map((c: string) => String(c.charCodeAt(0) - 97))\n        .join(''),\n    )\n    return parked[idx] ?? ''\n  })\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sql-query-notebook, [ui-sql-query-notebook]',\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    UiTableBodyComponent,\n    UiTableCellComponent,\n    UiTableComponent,\n    UiTableHeadComponent,\n    UiTableHeaderComponent,\n    UiTableRowComponent,\n  ],\n  template: `\n    <div data-slot=\"sql-query-notebook\" [class]=\"rootClass\">\n      <!-- Top Notebook Header -->\n      <header class=\"border-border bg-card/70 border-b px-4 py-3.5 sm:px-6\">\n        <div class=\"flex flex-col gap-3.5 lg:flex-row lg:items-center lg:justify-between\">\n          <!-- Notebook Identity & Metadata -->\n          <div class=\"flex items-start gap-3\">\n            <div class=\"bg-primary/10 text-primary mt-0.5 flex size-9 shrink-0 items-center justify-center rounded-lg\">\n              <lucide-icon [img]=\"FileCode2\" class=\"size-4.5\" />\n            </div>\n            <div class=\"space-y-1\">\n              <div class=\"flex flex-wrap items-center gap-2\">\n                <h1 class=\"text-foreground text-sm font-semibold tracking-tight sm:text-base\">\n                  Customer Churn &amp; Cohort Analysis · Q3 2026\n                </h1>\n                <span ui-badge variant=\"secondary\" class=\"font-mono text-xs\">v2.4</span>\n              </div>\n\n              <div class=\"text-muted-foreground flex flex-wrap items-center gap-x-3 gap-y-1 text-xs\">\n                <div class=\"flex items-center gap-1.5\">\n                  <span class=\"relative flex size-2\">\n                    <span class=\"bg-success absolute inline-flex h-full w-full rounded-full opacity-75\"></span>\n                    <span class=\"bg-success relative inline-flex size-2 rounded-full\"></span>\n                  </span>\n                  <span class=\"text-foreground/90 font-medium\">Snowflake Production Warehouse</span>\n                  <span class=\"text-muted-foreground\">· Large Cluster</span>\n                </div>\n                <div class=\"flex items-center gap-1\">\n                  <lucide-icon [img]=\"Clock\" class=\"size-3.5 opacity-70\" />\n                  <span>{{ lastSaved() }}</span>\n                </div>\n              </div>\n            </div>\n          </div>\n\n          <!-- Global Action Controls -->\n          <div class=\"flex flex-wrap items-center gap-2\">\n            <button\n              ui-button\n              variant=\"outline\"\n              size=\"sm\"\n              class=\"h-8 gap-1.5 text-xs shadow-none\"\n              title=\"Append SQL query cell\"\n              (click)=\"handleAddCell()\"\n            >\n              <lucide-icon [img]=\"Plus\" class=\"size-3.5\" />\n              <span>Add SQL Cell</span>\n            </button>\n\n            <button\n              ui-button\n              size=\"sm\"\n              class=\"h-8 gap-1.5 text-xs font-medium\"\n              [disabled]=\"isRunningAll()\"\n              (click)=\"handleRunAll()\"\n            >\n              @if (isRunningAll()) {\n                <lucide-icon [img]=\"Loader2\" class=\"size-3.5 animate-spin\" />\n              } @else {\n                <lucide-icon [img]=\"Play\" class=\"size-3.5 fill-current\" />\n              }\n              <span>{{ isRunningAll() ? 'Running Notebook...' : 'Run All Cells' }}</span>\n              <kbd\n                class=\"border-primary-foreground/30 bg-primary-foreground/10 hidden rounded border px-1 font-mono text-xs sm:inline\"\n              >\n                ⌘↵\n              </kbd>\n            </button>\n          </div>\n        </div>\n      </header>\n\n      <!-- Notebook Content Stream Container -->\n      <main class=\"space-y-4 p-4 sm:p-6\">\n        <!-- CELL 1: Markdown Documentation Cell -->\n        <div ui-card [class]=\"docCardClass\" (click)=\"activeCell.set('doc')\">\n          <div [class]=\"docStripClass\"></div>\n\n          <div class=\"border-border bg-muted/30 flex items-center justify-between border-b px-4 py-2\">\n            <div class=\"flex items-center gap-2\">\n              <span ui-badge variant=\"outline\" class=\"font-mono text-xs font-semibold uppercase\">[MD] Doc</span>\n              <span class=\"text-muted-foreground text-xs\">Methodology &amp; Hypothesis</span>\n            </div>\n\n            <button\n              ui-button\n              variant=\"ghost\"\n              size=\"sm\"\n              class=\"text-muted-foreground hover:text-foreground h-6 px-2 text-xs\"\n              (click)=\"$event.stopPropagation(); showMarkdownEditor.set(!showMarkdownEditor())\"\n            >\n              <lucide-icon [img]=\"FileText\" class=\"mr-1 size-3\" />\n              {{ showMarkdownEditor() ? 'Preview' : 'Source' }}\n            </button>\n          </div>\n\n          <div ui-card-content class=\"p-4 sm:p-5\">\n            @if (showMarkdownEditor()) {\n              <div class=\"space-y-2\">\n                <textarea\n                  rows=\"3\"\n                  class=\"border-input bg-muted/20 text-foreground focus-visible:ring-ring w-full rounded-md border p-3 font-mono text-xs leading-relaxed outline-none focus-visible:ring-2\"\n                  [value]=\"markdownContent()\"\n                  (input)=\"markdownContent.set($any($event.target).value)\"\n                ></textarea>\n              </div>\n            } @else {\n              <div class=\"space-y-3\">\n                <div class=\"border-border/60 border-b pb-2\">\n                  <h2 class=\"text-foreground text-base font-semibold tracking-tight\">\n                    Cohort Retention &amp; Expansion Query\n                  </h2>\n                  <p class=\"text-muted-foreground mt-1 text-xs leading-relaxed\">\n                    Grouping by monthly signup cohort to evaluate customer lifetime value (LTV) trajectory and expansion\n                    MRR across enterprise tiers. Filtered for 2026 activations from the primary subscriptions warehouse.\n                  </p>\n                </div>\n\n                <div class=\"flex flex-wrap items-center gap-2 pt-0.5\">\n                  <div\n                    class=\"border-border bg-muted/40 text-muted-foreground flex flex-wrap items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs\"\n                  >\n                    <lucide-icon [img]=\"Database\" class=\"text-foreground/70 size-3.5\" />\n                    <span>Source:</span>\n                    <span class=\"text-foreground font-mono font-medium\">analytics.fct_subscriptions</span>\n                  </div>\n                  <div\n                    class=\"border-border bg-muted/40 text-muted-foreground flex flex-wrap items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs\"\n                  >\n                    <lucide-icon [img]=\"Target\" class=\"text-warning size-3.5\" />\n                    <span>Target Retention:</span>\n                    <span class=\"text-foreground font-medium\">&gt; 118% Net Expansion</span>\n                  </div>\n                  <div\n                    class=\"border-border bg-muted/40 text-muted-foreground flex flex-wrap items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs\"\n                  >\n                    <lucide-icon [img]=\"Layers\" class=\"text-foreground/70 size-3.5\" />\n                    <span>Granularity:</span>\n                    <span class=\"text-foreground font-mono font-medium\">DATE_TRUNC('month')</span>\n                  </div>\n                </div>\n              </div>\n            }\n          </div>\n        </div>\n\n        <!-- CELL 2: SQL Query Execution Cell -->\n        <div ui-card [class]=\"sqlCardClass\" (click)=\"activeCell.set('sql')\">\n          <div [class]=\"sqlStripClass\"></div>\n\n          <div class=\"border-border bg-muted/30 flex flex-wrap items-center justify-between gap-2 border-b px-4 py-2\">\n            <div class=\"flex flex-wrap items-center gap-2\">\n              <span\n                ui-badge\n                variant=\"default\"\n                class=\"bg-primary text-primary-foreground font-mono text-xs font-semibold\"\n              >\n                [1] SQL\n              </span>\n              <span ui-badge variant=\"secondary\" class=\"font-mono text-xs\">Snowflake SQL</span>\n\n              <div\n                class=\"border-border/80 bg-background/80 flex items-center gap-1.5 rounded-md border px-2 py-0.5 text-xs\"\n              >\n                <span [class]=\"statusDotClass\"></span>\n                <span class=\"text-muted-foreground font-mono\">\n                  {{ isCellRunning() ? 'Executing query...' : executionTime() + ' · ' + rowCountText() + ' rows' }}\n                </span>\n              </div>\n            </div>\n\n            <div class=\"flex items-center gap-1.5\">\n              <button\n                ui-button\n                variant=\"outline\"\n                size=\"sm\"\n                class=\"h-7 gap-1 px-2.5 text-xs shadow-none\"\n                title=\"Copy SQL Query\"\n                (click)=\"$event.stopPropagation(); handleCopySql()\"\n              >\n                @if (copiedSql()) {\n                  <lucide-icon [img]=\"Check\" class=\"text-success size-3\" />\n                } @else {\n                  <lucide-icon [img]=\"Copy\" class=\"size-3\" />\n                }\n                <span>{{ copiedSql() ? 'Copied' : 'Copy SQL' }}</span>\n              </button>\n\n              <button\n                ui-button\n                size=\"sm\"\n                class=\"h-7 gap-1 px-2.5 text-xs font-medium\"\n                [disabled]=\"isCellRunning()\"\n                (click)=\"$event.stopPropagation(); handleRunCell()\"\n              >\n                @if (isCellRunning()) {\n                  <lucide-icon [img]=\"Loader2\" class=\"size-3 animate-spin\" />\n                } @else {\n                  <lucide-icon [img]=\"Play\" class=\"size-3 fill-current\" />\n                }\n                <span>Run Cell</span>\n              </button>\n            </div>\n          </div>\n\n          <!-- Code Editor Body (Dark Theme) -->\n          <div\n            class=\"relative flex overflow-x-auto bg-zinc-950 py-3 font-mono text-xs leading-relaxed text-zinc-100 select-text\"\n          >\n            <div class=\"flex flex-col border-r border-zinc-800/80 px-3 text-right text-zinc-600 select-none\">\n              @for (line of sqlLines(); track $index) {\n                <span [class]=\"lineNumberClass($index + 1)\">{{ $index + 1 }}</span>\n              }\n            </div>\n\n            <div class=\"flex-1 px-4 whitespace-pre\">\n              @for (line of sqlLines(); track $index) {\n                <div\n                  [class]=\"sqlLineClass($index + 1)\"\n                  (mouseenter)=\"hoveredSqlLine.set($index + 1)\"\n                  (mouseleave)=\"hoveredSqlLine.set(null)\"\n                >\n                  <span [innerHTML]=\"highlightLine(line)\"></span>\n                </div>\n              }\n            </div>\n          </div>\n\n          <!-- Tabular Query Results & Output Grid -->\n          <div class=\"border-border border-t\">\n            <div class=\"border-border bg-card flex flex-wrap items-center justify-between gap-2 border-b px-4 py-2\">\n              <div class=\"flex items-center gap-2\">\n                <div class=\"flex items-center gap-1.5\">\n                  <lucide-icon [img]=\"Table2\" class=\"text-primary size-4\" />\n                  <span class=\"text-foreground text-xs font-semibold\">Query Output</span>\n                </div>\n                <span ui-badge variant=\"secondary\" class=\"font-mono text-xs\">4 cohorts · 14,290 records</span>\n                <span class=\"text-muted-foreground hidden font-mono text-xs sm:inline\">\n                  Last run: {{ lastExecutionTimestamp() }}\n                </span>\n              </div>\n\n              <div class=\"flex flex-wrap items-center gap-2\">\n                <div class=\"border-border bg-muted/40 flex items-center rounded-lg border p-0.5\">\n                  <button type=\"button\" [class]=\"resultTabClass('table')\" (click)=\"activeResultTab.set('table')\">\n                    <lucide-icon [img]=\"Table2\" class=\"size-3.5\" />\n                    <span>Table</span>\n                  </button>\n                  <button type=\"button\" [class]=\"resultTabClass('chart')\" (click)=\"activeResultTab.set('chart')\">\n                    <lucide-icon [img]=\"BarChart3\" class=\"size-3.5\" />\n                    <span>Chart</span>\n                  </button>\n                </div>\n\n                <button\n                  ui-button\n                  aria-label=\"Download attachment\"\n                  variant=\"outline\"\n                  size=\"sm\"\n                  class=\"h-7 gap-1 px-2.5 text-xs shadow-none\"\n                  (click)=\"handleExportCsv()\"\n                >\n                  @if (copiedCsv()) {\n                    <lucide-icon [img]=\"Check\" class=\"text-success size-3\" />\n                  } @else {\n                    <lucide-icon [img]=\"Download\" class=\"size-3\" />\n                  }\n                  <span>{{ copiedCsv() ? 'Copied CSV!' : 'Export CSV' }}</span>\n                </button>\n\n                @if (activeResultTab() === 'table') {\n                  <button\n                    ui-button\n                    variant=\"secondary\"\n                    size=\"sm\"\n                    class=\"h-7 gap-1 px-2.5 text-xs\"\n                    (click)=\"activeResultTab.set('chart')\"\n                  >\n                    <lucide-icon [img]=\"BarChart3\" class=\"size-3\" />\n                    <span>Visualize as Chart</span>\n                  </button>\n                }\n              </div>\n            </div>\n\n            @if (activeResultTab() === 'table') {\n              <div class=\"overflow-x-auto\">\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\">signup_cohort</th>\n                      <th ui-table-head class=\"text-right text-xs font-semibold\">total_customers</th>\n                      <th ui-table-head class=\"text-right text-xs font-semibold\">avg_mrr</th>\n                      <th ui-table-head class=\"text-right text-xs font-semibold\">cohort_ltv</th>\n                      <th ui-table-head class=\"text-right text-xs font-semibold\">expansion_velocity</th>\n                    </tr>\n                  </thead>\n                  <tbody ui-table-body>\n                    @for (row of allCohorts; track row.signup_cohort; let idx = $index) {\n                      <tr\n                        ui-table-row\n                        [class]=\"idx % 2 === 1 ? 'bg-muted/15 text-xs transition-colors' : 'text-xs transition-colors'\"\n                      >\n                        <td ui-table-cell class=\"font-mono font-medium\">\n                          <div class=\"flex items-center gap-2\">\n                            <span class=\"text-muted-foreground font-mono text-xs\">{{ idx + 1 }}</span>\n                            <span class=\"text-foreground font-semibold\">{{ row.signup_cohort }}</span>\n                            <span ui-badge variant=\"secondary\" class=\"font-sans text-xs font-normal\">\n                              {{ row.cohort_label }}\n                            </span>\n                          </div>\n                        </td>\n                        <td ui-table-cell class=\"text-foreground text-right font-mono font-medium tabular-nums\">\n                          {{ row.total_customers.toLocaleString() }}\n                        </td>\n                        <td ui-table-cell class=\"text-foreground text-right font-mono tabular-nums\">\n                          &#36;{{ row.avg_mrr.toFixed(2) }}\n                        </td>\n                        <td ui-table-cell class=\"text-foreground text-right font-mono font-semibold tabular-nums\">\n                          &#36;{{\n                            row.cohort_ltv.toLocaleString('en-US', {\n                              minimumFractionDigits: 2,\n                              maximumFractionDigits: 2,\n                            })\n                          }}\n                        </td>\n                        <td ui-table-cell class=\"text-right\">\n                          <span\n                            class=\"bg-success/10 text-success rounded-md px-2 py-0.5 font-mono text-xs font-semibold\"\n                          >\n                            {{ row.expansion_rate }}\n                          </span>\n                        </td>\n                      </tr>\n                    }\n                  </tbody>\n                </table>\n\n                <div\n                  class=\"border-border bg-muted/20 flex flex-wrap items-center justify-between gap-3 border-t px-4 py-2.5 text-xs\"\n                >\n                  <span class=\"text-muted-foreground font-mono\">4 of 4 cohort partitions loaded · 0 errors</span>\n                  <div class=\"flex flex-wrap items-center gap-4\">\n                    <span class=\"text-muted-foreground\">\n                      Total Active Accounts:\n                      <strong class=\"text-foreground font-mono tabular-nums\">{{\n                        totalCustomersSum().toLocaleString()\n                      }}</strong>\n                    </span>\n                    <span class=\"text-muted-foreground\">\n                      Cumulative Cohort LTV:\n                      <strong class=\"text-foreground font-mono tabular-nums\">\n                        &#36;{{ totalLtvSum().toLocaleString('en-US', { minimumFractionDigits: 2 }) }}\n                      </strong>\n                    </span>\n                  </div>\n                </div>\n              </div>\n            } @else {\n              <div class=\"space-y-5 p-4 sm:p-6\">\n                <div class=\"flex flex-col justify-between gap-2 sm:flex-row sm:items-center\">\n                  <div>\n                    <h3 class=\"text-foreground text-sm font-semibold\">Cohort Lifetime Value Trajectory</h3>\n                    <p class=\"text-muted-foreground text-xs\">\n                      Progression of cumulative customer LTV and expansion momentum by activation month.\n                    </p>\n                  </div>\n                  <span ui-badge variant=\"outline\" class=\"font-mono text-xs\">Snowflake Snowpark Analytics</span>\n                </div>\n\n                <div class=\"space-y-3.5\">\n                  @for (item of allCohorts; track item.signup_cohort) {\n                    <div class=\"border-border/60 bg-card space-y-1.5 rounded-lg border p-3 shadow-none\">\n                      <div class=\"flex items-center justify-between text-xs\">\n                        <div class=\"flex items-center gap-2\">\n                          <span class=\"font-mono font-semibold\">{{ item.cohort_label }}</span>\n                          <span class=\"text-muted-foreground font-mono\">({{ item.signup_cohort }})</span>\n                          <span ui-badge variant=\"secondary\" class=\"font-mono text-xs\">\n                            {{ item.total_customers.toLocaleString() }} customers\n                          </span>\n                        </div>\n                        <div class=\"flex items-center gap-3\">\n                          <span class=\"text-muted-foreground font-mono\"\n                            >Avg MRR: &#36;{{ item.avg_mrr.toFixed(2) }}</span\n                          >\n                          <span class=\"text-foreground font-mono font-bold tabular-nums\">\n                            &#36;{{ item.cohort_ltv.toLocaleString('en-US', { minimumFractionDigits: 2 }) }}\n                          </span>\n                        </div>\n                      </div>\n\n                      <div class=\"bg-muted/60 relative h-3.5 w-full overflow-hidden rounded-full\">\n                        <div\n                          class=\"bg-primary absolute top-0 bottom-0 left-0 rounded-full transition-colors duration-500\"\n                          [style.width.%]=\"(item.cohort_ltv / maxCohortLtv()) * 100\"\n                        ></div>\n                      </div>\n                    </div>\n                  }\n                </div>\n              </div>\n            }\n          </div>\n        </div>\n\n        <!-- CELL 3: Downstream Python / Added Cell -->\n        @if (showExtraCell()) {\n          <div ui-card [class]=\"extraCardClass\" (click)=\"activeCell.set('extra')\">\n            <div [class]=\"extraStripClass\"></div>\n\n            <div class=\"border-border bg-muted/30 flex items-center justify-between border-b px-4 py-2\">\n              <div class=\"flex items-center gap-2\">\n                <span ui-badge variant=\"secondary\" class=\"font-mono text-xs font-semibold\">[2] Python</span>\n                <span class=\"text-muted-foreground text-xs\">Downstream Snowpark Dataframe</span>\n              </div>\n\n              <div class=\"flex items-center gap-1.5\">\n                <button\n                  ui-button\n                  size=\"sm\"\n                  class=\"h-7 gap-1 px-2.5 text-xs\"\n                  [disabled]=\"extraCellRunning()\"\n                  (click)=\"$event.stopPropagation(); handleRunExtraCell()\"\n                >\n                  @if (extraCellRunning()) {\n                    <lucide-icon [img]=\"Loader2\" class=\"size-3 animate-spin\" />\n                  } @else {\n                    <lucide-icon [img]=\"Play\" class=\"size-3 fill-current\" />\n                  }\n                  <span>Run</span>\n                </button>\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  class=\"text-muted-foreground hover:text-destructive h-7 px-2\"\n                  aria-label=\"Delete query cell\"\n                  (click)=\"$event.stopPropagation(); showExtraCell.set(false)\"\n                >\n                  <lucide-icon [img]=\"Trash2\" class=\"size-3\" />\n                </button>\n              </div>\n            </div>\n\n            <div class=\"bg-zinc-950 p-4 font-mono text-xs leading-relaxed text-zinc-300\">\n              <pre\n                class=\"text-zinc-400\"\n              ><code><span class=\"text-chart-1\">import</span> snowflake.snowpark <span class=\"text-chart-1\">as</span> snowpark\n<span class=\"text-chart-1\">import</span> polars <span class=\"text-chart-1\">as</span> pl\n\n<span class=\"text-zinc-500\"># Read Cell [1] tabular results and compute hazard rate</span>\ndf = cell_1_results.to_pandas()\ndf[<span class=\"text-success\">'expansion_velocity'</span>] = df[<span class=\"text-success\">'cohort_ltv'</span>] / df[<span class=\"text-success\">'total_customers'</span>]\n<span class=\"text-info\">print</span>(f<span class=\"text-success\">\"Average cohort customer value: &#36;&#123;df['expansion_velocity'].mean():.2f&#125;\"</span>)</code></pre>\n            </div>\n\n            <div class=\"border-border bg-muted/20 border-t p-3 font-mono text-xs\">\n              <div class=\"text-muted-foreground flex items-center gap-2\">\n                <lucide-icon [img]=\"Terminal\" class=\"text-success size-3.5\" />\n                <span>Output:</span>\n                <span class=\"text-success font-semibold\">Average cohort customer value: $292.77</span>\n              </div>\n            </div>\n          </div>\n        }\n      </main>\n    </div>\n  `,\n})\nexport class UiSqlQueryNotebookComponent {\n  protected readonly BarChart3 = BarChart3\n  protected readonly Check = Check\n  protected readonly Clock = Clock\n  protected readonly Copy = Copy\n  protected readonly Database = Database\n  protected readonly Download = Download\n  protected readonly FileCode2 = FileCode2\n  protected readonly FileText = FileText\n  protected readonly Layers = Layers\n  protected readonly Loader2 = Loader2\n  protected readonly Play = Play\n  protected readonly Plus = Plus\n  protected readonly Table2 = Table2\n  protected readonly Target = Target\n  protected readonly Terminal = Terminal\n  protected readonly Trash2 = Trash2\n  protected readonly allCohorts = cohortResults\n\n  @Input('class') className?: string\n\n  readonly isRunningAll = signal(false)\n  readonly isCellRunning = signal(false)\n  readonly lastSaved = signal('Autosaved 1m ago')\n  readonly activeCell = signal<'doc' | 'sql' | 'extra'>('sql')\n  readonly activeResultTab = signal<'table' | 'chart'>('table')\n  readonly copiedSql = signal(false)\n  readonly copiedCsv = signal(false)\n  readonly hoveredSqlLine = signal<number | null>(null)\n  readonly executionTime = signal('1.24s')\n  readonly rowCount = signal(14290)\n  readonly lastExecutionTimestamp = signal('14:28:40')\n  readonly showMarkdownEditor = signal(false)\n  readonly showExtraCell = signal(false)\n  readonly extraCellRunning = signal(false)\n  readonly markdownContent = signal(\n    '# Cohort Retention & Expansion Query: Grouping by monthly signup cohort to evaluate customer lifetime value (LTV) trajectory and expansion MRR across enterprise tiers. Filtered for 2026 activations from the primary subscriptions warehouse.',\n  )\n\n  readonly sqlLines = computed(() => sqlQuery.split('\\n'))\n  readonly totalCustomersSum = computed(() => cohortResults.reduce((acc, r) => acc + r.total_customers, 0))\n  readonly totalLtvSum = computed(() => cohortResults.reduce((acc, r) => acc + r.cohort_ltv, 0))\n  readonly maxCohortLtv = computed(() => Math.max(...cohortResults.map((r) => r.cohort_ltv)))\n  readonly rowCountText = computed(() => this.rowCount().toLocaleString())\n\n  get rootClass(): string {\n    return cn(\n      'bg-background text-foreground border-border w-full overflow-hidden rounded-xl border shadow-xs',\n      this.className,\n    )\n  }\n\n  get statusDotClass(): string {\n    return cn(\n      'size-1.5 rounded-full transition-colors',\n      this.isCellRunning() ? 'bg-warning animate-pulse' : 'bg-success',\n    )\n  }\n\n  get docCardClass(): string {\n    return cn(\n      'border-border relative overflow-hidden shadow-none transition-colors',\n      this.activeCell() === 'doc' ? 'ring-primary/20 ring-2' : '',\n    )\n  }\n\n  get docStripClass(): string {\n    return cn(\n      'absolute top-0 bottom-0 left-0 w-1 transition-colors',\n      this.activeCell() === 'doc' ? 'bg-primary' : 'bg-transparent',\n    )\n  }\n\n  get sqlCardClass(): string {\n    return cn(\n      'border-border relative overflow-hidden shadow-none transition-colors',\n      this.activeCell() === 'sql' ? 'ring-primary/20 ring-2' : '',\n    )\n  }\n\n  get sqlStripClass(): string {\n    return cn(\n      'absolute top-0 bottom-0 left-0 w-1 transition-colors',\n      this.activeCell() === 'sql' ? 'bg-primary' : 'bg-transparent',\n    )\n  }\n\n  get extraCardClass(): string {\n    return cn(\n      'border-border relative overflow-hidden shadow-none transition-colors',\n      this.activeCell() === 'extra' ? 'ring-primary/20 ring-2' : '',\n    )\n  }\n\n  get extraStripClass(): string {\n    return cn(\n      'absolute top-0 bottom-0 left-0 w-1 transition-colors',\n      this.activeCell() === 'extra' ? 'bg-primary' : 'bg-transparent',\n    )\n  }\n\n  lineNumberClass(idx: number): string {\n    return cn('h-5 leading-5 transition-colors', this.hoveredSqlLine() === idx ? 'font-semibold text-zinc-300' : '')\n  }\n\n  sqlLineClass(idx: number): string {\n    return cn(\n      'group flex h-5 items-center rounded-xs px-1 leading-5 transition-colors',\n      this.hoveredSqlLine() === idx ? 'bg-zinc-800/40' : '',\n    )\n  }\n\n  resultTabClass(tab: 'table' | 'chart'): string {\n    return cn(\n      'flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition-colors',\n      this.activeResultTab() === tab\n        ? 'bg-background text-foreground shadow-xs'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  highlightLine(line: string): string {\n    return highlightSqlLine(line)\n  }\n\n  handleRunAll(): void {\n    if (this.isRunningAll()) return\n    this.isRunningAll.set(true)\n    this.isCellRunning.set(true)\n    setTimeout(() => {\n      this.isCellRunning.set(false)\n      this.isRunningAll.set(false)\n      this.lastExecutionTimestamp.set(new Date().toTimeString().split(' ')[0])\n      this.lastSaved.set('Saved just now')\n    }, 620)\n  }\n\n  handleRunCell(): void {\n    if (this.isCellRunning()) return\n    this.isCellRunning.set(true)\n    setTimeout(() => {\n      this.isCellRunning.set(false)\n      this.lastExecutionTimestamp.set(new Date().toTimeString().split(' ')[0])\n    }, 480)\n  }\n\n  async handleCopySql(): Promise<void> {\n    try {\n      await navigator.clipboard.writeText(sqlQuery)\n      this.copiedSql.set(true)\n      setTimeout(() => this.copiedSql.set(false), 2000)\n    } catch {\n      // Clipboard unavailable\n    }\n  }\n\n  async handleExportCsv(): Promise<void> {\n    const csvHeaders = 'signup_cohort,total_customers,avg_mrr,cohort_ltv\\n'\n    const csvRows = cohortResults\n      .map((r) => `${r.signup_cohort},${r.total_customers},${r.avg_mrr},${r.cohort_ltv}`)\n      .join('\\n')\n    try {\n      await navigator.clipboard.writeText(csvHeaders + csvRows)\n      this.copiedCsv.set(true)\n      setTimeout(() => this.copiedCsv.set(false), 2000)\n    } catch {\n      // Clipboard unavailable\n    }\n  }\n\n  handleAddCell(): void {\n    this.showExtraCell.set(true)\n    this.activeCell.set('extra')\n  }\n\n  handleRunExtraCell(): void {\n    this.extraCellRunning.set(true)\n    setTimeout(() => this.extraCellRunning.set(false), 450)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/sql-query-notebook/sql-query-notebook.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/sql-query-notebook/index.ts",
      "content": "export { UiSqlQueryNotebookComponent, highlightSqlLine } from './sql-query-notebook.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/sql-query-notebook/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/table.json"
  ],
  "description": "Hex, Deepnote, and Jupyter style SQL analytical query notebook with documentation cells, executable Snowflake SQL editor with syntax coloring, live tabular results grid, CSV export, and chart visualization.",
  "categories": [
    "devops",
    "app"
  ]
}