{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "database-migration-schema-diff",
  "title": "Database Migration Schema Diff",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/database-migration-schema-diff/database-migration-schema-diff.component.ts",
      "content": "import { ChangeDetectionStrategy, Component, DestroyRef, Input, inject, signal } from '@angular/core'\nimport {\n  Check,\n  CheckCircle2,\n  Clock,\n  Code2,\n  Copy,\n  Database,\n  FileCode2,\n  HardDrive,\n  Layers,\n  Loader2,\n  LucideAngularModule,\n  Play,\n  RotateCcw,\n  ShieldCheck,\n  Terminal,\n  Timer,\n  Undo2,\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  UiCardDescriptionComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\nimport {\n  UiTableBodyComponent,\n  UiTableCellComponent,\n  UiTableComponent,\n  UiTableHeadComponent,\n  UiTableHeaderComponent,\n  UiTableRowComponent,\n} from '@/ui/table/table.component'\nimport {\n  UiTabsComponent,\n  UiTabsContentComponent,\n  UiTabsListComponent,\n  UiTabsTriggerComponent,\n} from '@/ui/tabs/tabs.component'\n\nexport interface DiffLine {\n  num: number\n  type: 'addition' | 'modification' | 'deletion' | 'neutral'\n  prefix: string\n  text: string\n}\n\nexport interface HistoricalMigration {\n  id: string\n  filename: string\n  version: string\n  description: string\n  appliedAt: string\n  author: string\n  executionTime: string\n  tablesAffected: string\n  status: 'applied' | 'pending' | 'rolled_back'\n  checksum: string\n}\n\nexport type DatabaseMigrationSchemaDiffTab = 'up' | 'rollback' | 'impact' | 'cli'\n\nconst upSqlRaw = `-- Up Migration\n-- Migration: 20260821142850_add_org_billing_seats_and_quotas.sql\n-- Target: public.organizations\n\nALTER TABLE public.organizations\n  ADD COLUMN billing_seats_allocated INTEGER NOT NULL DEFAULT 5,\n  ADD COLUMN max_seat_limit INTEGER NOT NULL DEFAULT 20,\n  ADD COLUMN last_seat_audit_at TIMESTAMPTZ DEFAULT NOW();\n\nCREATE INDEX idx_orgs_seat_audit ON public.organizations(last_seat_audit_at);`\n\nconst downSqlRaw = `-- Down Migration (Rollback)\n-- Reverts: 20260821142850_add_org_billing_seats_and_quotas.sql\n-- Target: public.organizations\n\nDROP INDEX IF EXISTS idx_orgs_seat_audit;\n\nALTER TABLE public.organizations\n  DROP COLUMN IF EXISTS billing_seats_allocated,\n  DROP COLUMN IF EXISTS max_seat_limit,\n  DROP COLUMN IF EXISTS last_seat_audit_at;`\n\nconst cliCommand = 'npx prisma migrate deploy --schema=./prisma/schema.prisma'\n\nconst upDiffLines: DiffLine[] = [\n  { num: 1, type: 'neutral', prefix: ' ', text: '-- Up Migration' },\n  { num: 2, type: 'neutral', prefix: ' ', text: '-- Migration: 20260821142850_add_org_billing_seats_and_quotas.sql' },\n  { num: 3, type: 'neutral', prefix: ' ', text: '-- Target: public.organizations' },\n  { num: 4, type: 'neutral', prefix: ' ', text: '' },\n  { num: 5, type: 'modification', prefix: '~', text: 'ALTER TABLE public.organizations ' },\n  { num: 6, type: 'addition', prefix: '+', text: '  ADD COLUMN billing_seats_allocated INTEGER NOT NULL DEFAULT 5,' },\n  { num: 7, type: 'addition', prefix: '+', text: '  ADD COLUMN max_seat_limit INTEGER NOT NULL DEFAULT 20,' },\n  { num: 8, type: 'addition', prefix: '+', text: '  ADD COLUMN last_seat_audit_at TIMESTAMPTZ DEFAULT NOW();' },\n  { num: 9, type: 'neutral', prefix: ' ', text: '' },\n  {\n    num: 10,\n    type: 'addition',\n    prefix: '+',\n    text: 'CREATE INDEX idx_orgs_seat_audit ON public.organizations(last_seat_audit_at);',\n  },\n]\n\nconst downDiffLines: DiffLine[] = [\n  { num: 1, type: 'neutral', prefix: ' ', text: '-- Down Migration (Rollback)' },\n  { num: 2, type: 'neutral', prefix: ' ', text: '-- Reverts: 20260821142850_add_org_billing_seats_and_quotas.sql' },\n  { num: 3, type: 'neutral', prefix: ' ', text: '-- Target: public.organizations' },\n  { num: 4, type: 'neutral', prefix: ' ', text: '' },\n  { num: 5, type: 'deletion', prefix: '-', text: 'DROP INDEX IF EXISTS idx_orgs_seat_audit;' },\n  { num: 6, type: 'neutral', prefix: ' ', text: '' },\n  { num: 7, type: 'modification', prefix: '~', text: 'ALTER TABLE public.organizations ' },\n  { num: 8, type: 'deletion', prefix: '-', text: '  DROP COLUMN IF EXISTS billing_seats_allocated,' },\n  { num: 9, type: 'deletion', prefix: '-', text: '  DROP COLUMN IF EXISTS max_seat_limit,' },\n  { num: 10, type: 'deletion', prefix: '-', text: '  DROP COLUMN IF EXISTS last_seat_audit_at;' },\n]\n\nconst historicalMigrations: HistoricalMigration[] = [\n  {\n    id: 'mig_48',\n    filename: '20260814091522_add_audit_logs_partitioning.sql',\n    version: 'v2026.08.14.02',\n    description: 'Partition audit_logs table by month range and attach default tablespace',\n    appliedAt: 'Aug 14, 2026 · 09:15 UTC',\n    author: 'alex.chen@corp',\n    executionTime: '1.8s',\n    tablesAffected: 'audit_logs, audit_logs_2026_08',\n    status: 'applied',\n    checksum: 'sha256:4e8b39c0',\n  },\n  {\n    id: 'mig_47',\n    filename: '20260810143011_create_api_rate_limits_table.sql',\n    version: 'v2026.08.10.01',\n    description: 'Create rate_limits table & redis fallback synchronization schema',\n    appliedAt: 'Aug 10, 2026 · 14:30 UTC',\n    author: 'sarah.m@corp',\n    executionTime: '0.9s',\n    tablesAffected: 'api_rate_limits',\n    status: 'applied',\n    checksum: 'sha256:d12c88f1',\n  },\n  {\n    id: 'mig_46',\n    filename: '20260802110540_add_saml_sso_workspaces.sql',\n    version: 'v2026.08.02.03',\n    description: 'Add SAML SSO domain verification and enterprise IdP metadata records',\n    appliedAt: 'Aug 02, 2026 · 11:05 UTC',\n    author: 'devops-bot',\n    executionTime: '2.1s',\n    tablesAffected: 'workspaces, idp_configs',\n    status: 'applied',\n    checksum: 'sha256:88a3b114',\n  },\n  {\n    id: 'mig_45',\n    filename: '20260725184219_optimize_query_cache_indexes.sql',\n    version: 'v2026.07.25.01',\n    description: 'Add CONCURRENT B-Tree composite index on team_id + created_at timestamp',\n    appliedAt: 'Jul 25, 2026 · 18:42 UTC',\n    author: 'alex.chen@corp',\n    executionTime: '4.2s',\n    tablesAffected: 'query_logs, cache_entries',\n    status: 'applied',\n    checksum: 'sha256:fa2300b9',\n  },\n  {\n    id: 'mig_44',\n    filename: '20260718082005_init_multi_tenant_memberships.sql',\n    version: 'v2026.07.18.01',\n    description: 'Initialize organization_memberships, team_roles, and permission cascades',\n    appliedAt: 'Jul 18, 2026 · 08:20 UTC',\n    author: 'elena.r@corp',\n    executionTime: '1.2s',\n    tablesAffected: 'org_members, permissions',\n    status: 'applied',\n    checksum: 'sha256:56df9201',\n  },\n]\n\n/**\n * DatabaseMigrationSchemaDiff -- the React block, 1:1: target cluster header with the pending /\n * in-sync badge, \"Preview Rollback Script\" and \"Apply Migration\" actions, four telemetry cards,\n * the migration inspector (Up diff, Down rollback, schema impact and Prisma CLI tabs, with copy\n * buttons) and the recent applied migrations table. Edit the data consts above.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-database-migration-schema-diff, [ui-database-migration-schema-diff]',\n  standalone: true,\n  // React renders the root <div> itself: keep this wrapper out of the layout.\n  host: { class: 'contents' },\n  imports: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiCardDescriptionComponent,\n    UiCardContentComponent,\n    UiSeparatorComponent,\n    UiTableComponent,\n    UiTableHeaderComponent,\n    UiTableBodyComponent,\n    UiTableRowComponent,\n    UiTableHeadComponent,\n    UiTableCellComponent,\n    UiTabsComponent,\n    UiTabsListComponent,\n    UiTabsTriggerComponent,\n    UiTabsContentComponent,\n  ],\n  template: `\n    <div data-slot=\"database-migration-schema-diff\" [class]=\"rootClass\">\n      <!-- Header: Target Database & Version Runner Status -->\n      <ui-card class=\"border-border bg-card shadow-xs\">\n        <ui-card-header class=\"pb-4\">\n          <div class=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n            <div class=\"space-y-1.5\">\n              <div class=\"flex flex-wrap items-center gap-2\">\n                <div class=\"text-foreground flex items-center gap-2 text-base font-semibold sm:text-lg\">\n                  <lucide-icon [img]=\"Database\" class=\"text-primary size-5 shrink-0\" />\n                  <span>PostgreSQL 16.2 · production_primary_cluster</span>\n                </div>\n                <div\n                  class=\"bg-success/10 text-success flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium\"\n                >\n                  <span class=\"bg-success size-1.5 animate-pulse rounded-full\"></span>\n                  <span>Connected</span>\n                </div>\n              </div>\n\n              <div class=\"text-muted-foreground flex flex-wrap items-center gap-2 text-xs sm:text-sm\">\n                <span class=\"text-foreground font-mono font-medium\">v2026.08.21.01_add_org_billing_seats</span>\n                <ui-separator orientation=\"vertical\" class=\"h-3.5\" />\n                <span>db.us-east-1.aws.neon.tech:5432</span>\n                <ui-separator orientation=\"vertical\" class=\"h-3.5\" />\n                <span>public schema</span>\n              </div>\n            </div>\n\n            <div class=\"flex flex-wrap items-center gap-2.5\">\n              @if (!isApplied()) {\n                <span ui-badge wrap variant=\"warning\" class=\"gap-1.5 px-2.5 py-1 text-xs\">\n                  <lucide-icon [img]=\"Clock\" class=\"size-3.5\" />\n                  <span>1 Pending Migration Ready to Apply</span>\n                </span>\n              } @else {\n                <span ui-badge wrap variant=\"success\" class=\"gap-1.5 px-2.5 py-1 text-xs\">\n                  <lucide-icon [img]=\"CheckCircle2\" class=\"size-3.5\" />\n                  <span>All Migrations Applied · In Sync</span>\n                </span>\n              }\n\n              <button\n                ui-button\n                variant=\"outline\"\n                size=\"sm\"\n                class=\"h-9 gap-1.5 text-xs font-medium shadow-xs active:scale-[0.98]\"\n                (click)=\"handlePreviewRollback()\"\n              >\n                <lucide-icon [img]=\"Undo2\" class=\"text-muted-foreground size-3.5\" />\n                <span>Preview Rollback Script</span>\n              </button>\n\n              @if (!isApplied()) {\n                <button\n                  ui-button\n                  variant=\"default\"\n                  size=\"sm\"\n                  class=\"h-9 gap-1.5 text-xs font-medium shadow-xs active:scale-[0.98]\"\n                  [disabled]=\"isApplying()\"\n                  (click)=\"handleApplyMigration()\"\n                >\n                  @if (isApplying()) {\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>{{ isApplying() ? 'Applying Migration...' : 'Apply Migration (prisma migrate deploy)' }}</span>\n                </button>\n              } @else {\n                <button\n                  ui-button\n                  variant=\"secondary\"\n                  size=\"sm\"\n                  class=\"h-9 gap-1.5 text-xs font-medium active:scale-[0.98]\"\n                  (click)=\"handleResetState()\"\n                >\n                  <lucide-icon [img]=\"RotateCcw\" class=\"size-3.5\" />\n                  <span>Reset State</span>\n                </button>\n              }\n            </div>\n          </div>\n        </ui-card-header>\n      </ui-card>\n\n      <!-- 4 Migration Telemetry Cards -->\n      <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n        <!-- Total Applied Migrations -->\n        <ui-card class=\"border-border bg-card shadow-xs\">\n          <ui-card-content class=\"p-4 sm:p-5\">\n            <div class=\"flex items-center justify-between\">\n              <span class=\"text-muted-foreground text-xs font-medium\">Total Applied Migrations</span>\n              <div class=\"bg-muted text-muted-foreground flex size-8 items-center justify-center rounded-md\">\n                <lucide-icon [img]=\"Layers\" class=\"size-4\" />\n              </div>\n            </div>\n            <div class=\"mt-3\">\n              <div class=\"text-foreground text-2xl font-bold tracking-tight\">{{ isApplied() ? '49' : '48' }}</div>\n              <p class=\"text-muted-foreground mt-1 text-xs\">48 Migrations Applied · Baseline to v2026.08.14</p>\n            </div>\n            <div class=\"text-success mt-3 flex items-center gap-1.5 text-xs font-medium\">\n              <lucide-icon [img]=\"Check\" class=\"size-3.5\" />\n              <span>100% successful execution history</span>\n            </div>\n          </ui-card-content>\n        </ui-card>\n\n        <!-- Pending Migrations -->\n        <ui-card [class]=\"pendingCardClass()\">\n          <ui-card-content class=\"p-4 sm:p-5\">\n            <div class=\"flex items-center justify-between\">\n              <span class=\"text-muted-foreground text-xs font-medium\">Pending Migrations</span>\n              <div [class]=\"pendingIconClass()\">\n                @if (!isApplied()) {\n                  <lucide-icon [img]=\"Clock\" class=\"size-4\" />\n                } @else {\n                  <lucide-icon [img]=\"CheckCircle2\" class=\"size-4\" />\n                }\n              </div>\n            </div>\n            <div class=\"mt-3\">\n              <div class=\"text-foreground text-2xl font-bold tracking-tight\">{{ isApplied() ? '0' : '1' }}</div>\n              <p class=\"text-muted-foreground mt-1 text-xs\">\n                {{ isApplied() ? '0 Pending · Schema up to date' : '1 Pending Deployment' }}\n              </p>\n            </div>\n            <div [class]=\"pendingFooterClass()\">\n              @if (!isApplied()) {\n                <lucide-icon [img]=\"ShieldCheck\" class=\"size-3.5\" />\n              } @else {\n                <lucide-icon [img]=\"Check\" class=\"size-3.5\" />\n              }\n              <span>{{ !isApplied() ? 'Safe DDL · Non-blocking ALTER' : 'Schema verified against shadow DB' }}</span>\n            </div>\n          </ui-card-content>\n        </ui-card>\n\n        <!-- Last Migration Execution -->\n        <ui-card class=\"border-border bg-card shadow-xs\">\n          <ui-card-content class=\"p-4 sm:p-5\">\n            <div class=\"flex items-center justify-between\">\n              <span class=\"text-muted-foreground text-xs font-medium\">Last Migration Execution</span>\n              <div class=\"bg-muted text-muted-foreground flex size-8 items-center justify-center rounded-md\">\n                <lucide-icon [img]=\"Zap\" class=\"size-4\" />\n              </div>\n            </div>\n            <div class=\"mt-3\">\n              <div class=\"text-foreground text-2xl font-bold tracking-tight\">{{ isApplied() ? '0.12s' : '3.4s' }}</div>\n              <p class=\"text-muted-foreground mt-1 text-xs\">3.4s execution time · 0 lock timeouts</p>\n            </div>\n            <div class=\"text-muted-foreground mt-3 flex items-center gap-1.5 text-xs\">\n              <lucide-icon [img]=\"Timer\" class=\"size-3.5\" />\n              <span>Zero query contention or lock waits</span>\n            </div>\n          </ui-card-content>\n        </ui-card>\n\n        <!-- Target Schema Integrity -->\n        <ui-card class=\"border-border bg-card shadow-xs\">\n          <ui-card-content class=\"p-4 sm:p-5\">\n            <div class=\"flex items-center justify-between\">\n              <span class=\"text-muted-foreground text-xs font-medium\">Target Schema Integrity</span>\n              <div class=\"bg-success/10 text-success flex size-8 items-center justify-center rounded-md\">\n                <lucide-icon [img]=\"ShieldCheck\" class=\"size-4\" />\n              </div>\n            </div>\n            <div class=\"mt-3\">\n              <div class=\"text-foreground text-2xl font-bold tracking-tight\">100%</div>\n              <p class=\"text-muted-foreground mt-1 text-xs\">100% Validated · Zero Drift</p>\n            </div>\n            <div class=\"text-success mt-3 flex items-center gap-1.5 text-xs font-medium\">\n              <lucide-icon [img]=\"CheckCircle2\" class=\"size-3.5\" />\n              <span>Prisma schema &amp; DB in sync</span>\n            </div>\n          </ui-card-content>\n        </ui-card>\n      </div>\n\n      <!-- Active Migration Inspector & DDL Diff Card -->\n      <ui-card class=\"border-border bg-card shadow-xs\">\n        <ui-card-header class=\"border-border border-b pb-4\">\n          <div class=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n            <div class=\"space-y-1\">\n              <div class=\"flex flex-wrap items-center gap-2\">\n                <lucide-icon [img]=\"FileCode2\" class=\"text-primary size-4 shrink-0\" />\n                <h3 ui-card-title class=\"text-foreground font-mono text-sm font-semibold sm:text-base\">\n                  20260821142850_add_org_billing_seats_and_quotas.sql\n                </h3>\n                @if (!isApplied()) {\n                  <span ui-badge wrap variant=\"warning\" class=\"text-xs\">Pending DDL Execution</span>\n                } @else {\n                  <span ui-badge wrap variant=\"success\" class=\"text-xs\">Applied in Production</span>\n                }\n              </div>\n              <p ui-card-description class=\"text-muted-foreground text-xs\">\n                Target Table: <span class=\"text-foreground font-mono font-medium\">public.organizations</span> · Author:\n                <span class=\"text-foreground\">alex.chen&#64;corp</span> · Estimated duration:\n                <span class=\"text-foreground font-mono font-medium\">~120ms</span>\n              </p>\n            </div>\n\n            <div class=\"flex items-center gap-2\">\n              <button ui-button variant=\"outline\" size=\"sm\" class=\"h-8 gap-1.5 text-xs font-medium\" (click)=\"copySql()\">\n                @if (sqlCopied()) {\n                  <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                } @else {\n                  <lucide-icon [img]=\"Copy\" class=\"size-3.5\" />\n                }\n                <span>{{ sqlCopied() ? 'Copied SQL' : 'Copy SQL' }}</span>\n              </button>\n            </div>\n          </div>\n        </ui-card-header>\n\n        <ui-card-content class=\"p-4 sm:p-6\">\n          <ui-tabs [value]=\"activeTab()\" (valueChange)=\"setActiveTab($event)\" class=\"w-full space-y-4\">\n            <div ui-tabs-list class=\"grid w-full grid-cols-2 sm:inline-flex sm:w-auto sm:grid-cols-none\">\n              <button ui-tabs-trigger value=\"up\" class=\"gap-1.5 text-xs\">\n                <lucide-icon [img]=\"Code2\" class=\"size-3.5\" />\n                <span>DDL Diff (Up)</span>\n              </button>\n              <button ui-tabs-trigger value=\"rollback\" class=\"gap-1.5 text-xs\">\n                <lucide-icon [img]=\"Undo2\" class=\"size-3.5\" />\n                <span>Rollback Script (Down)</span>\n              </button>\n              <button ui-tabs-trigger value=\"impact\" class=\"gap-1.5 text-xs\">\n                <lucide-icon [img]=\"HardDrive\" class=\"size-3.5\" />\n                <span>Schema Impact</span>\n              </button>\n              <button ui-tabs-trigger value=\"cli\" class=\"gap-1.5 text-xs\">\n                <lucide-icon [img]=\"Terminal\" class=\"size-3.5\" />\n                <span>Prisma / CLI Runner</span>\n              </button>\n            </div>\n\n            <!-- Tab 1: DDL Diff (Up) -->\n            <ui-tabs-content value=\"up\" class=\"space-y-3 focus-visible:outline-none\">\n              <div\n                class=\"border-border bg-muted/50 flex items-center justify-between rounded-t-md border-x border-t px-3 py-2 text-xs\"\n              >\n                <div class=\"text-muted-foreground flex items-center gap-2\">\n                  <span class=\"text-foreground font-medium\">Forward Migration (Up)</span>\n                  <span>·</span>\n                  <span class=\"text-success font-medium\">+3 columns added</span>\n                  <span>·</span>\n                  <span class=\"text-info font-medium\">+1 index created</span>\n                </div>\n                <span class=\"text-muted-foreground font-mono\">SQL (PostgreSQL DDL)</span>\n              </div>\n\n              <div\n                class=\"border-border bg-muted/20 overflow-x-auto rounded-b-md border font-mono text-xs leading-relaxed\"\n              >\n                <div class=\"max-w-[540px] min-w-full py-2\">\n                  @for (line of upDiffLines; track line.num) {\n                    <div [class]=\"lineClass(line)\">\n                      <span class=\"text-muted-foreground/50 w-7 shrink-0 pr-3 text-right select-none\">{{\n                        line.num\n                      }}</span>\n                      <span [class]=\"prefixClass(line)\">{{ line.prefix }}</span>\n                      <span class=\"pl-1 whitespace-pre\">{{ line.text }}</span>\n                    </div>\n                  }\n                </div>\n              </div>\n            </ui-tabs-content>\n\n            <!-- Tab 2: Rollback Script (Down) -->\n            <ui-tabs-content value=\"rollback\" class=\"space-y-3 focus-visible:outline-none\">\n              <div\n                class=\"border-border bg-muted/50 flex items-center justify-between rounded-t-md border-x border-t px-3 py-2 text-xs\"\n              >\n                <div class=\"text-muted-foreground flex items-center gap-2\">\n                  <span class=\"text-foreground font-medium\">Revert Script (Down)</span>\n                  <span>·</span>\n                  <span class=\"text-destructive font-medium\">-3 columns dropped</span>\n                  <span>·</span>\n                  <span class=\"text-destructive font-medium\">-1 index dropped</span>\n                </div>\n                <span class=\"text-muted-foreground font-mono\">SQL (PostgreSQL DDL)</span>\n              </div>\n\n              <div\n                class=\"border-border bg-muted/20 overflow-x-auto rounded-b-md border font-mono text-xs leading-relaxed\"\n              >\n                <div class=\"max-w-[540px] min-w-full py-2\">\n                  @for (line of downDiffLines; track line.num) {\n                    <div [class]=\"lineClass(line)\">\n                      <span class=\"text-muted-foreground/50 w-7 shrink-0 pr-3 text-right select-none\">{{\n                        line.num\n                      }}</span>\n                      <span [class]=\"prefixClass(line)\">{{ line.prefix }}</span>\n                      <span class=\"pl-1 whitespace-pre\">{{ line.text }}</span>\n                    </div>\n                  }\n                </div>\n              </div>\n            </ui-tabs-content>\n\n            <!-- Tab 3: Schema Impact & Locks -->\n            <ui-tabs-content value=\"impact\" class=\"space-y-4 focus-visible:outline-none\">\n              <div class=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n                <div class=\"border-border bg-muted/30 space-y-3 rounded-lg border p-4\">\n                  <div class=\"text-foreground flex items-center gap-2 text-sm font-medium\">\n                    <lucide-icon [img]=\"Database\" class=\"text-primary size-4 shrink-0\" />\n                    <span>Target Table &amp; Column Additions</span>\n                  </div>\n                  <div class=\"space-y-2 text-xs\">\n                    <div class=\"border-border/60 bg-card flex items-center justify-between rounded-md border p-2\">\n                      <span class=\"text-foreground font-mono font-medium\">billing_seats_allocated</span>\n                      <span ui-badge wrap variant=\"secondary\" class=\"font-mono text-xs\"\n                        >INTEGER NOT NULL DEFAULT 5</span\n                      >\n                    </div>\n                    <div class=\"border-border/60 bg-card flex items-center justify-between rounded-md border p-2\">\n                      <span class=\"text-foreground font-mono font-medium\">max_seat_limit</span>\n                      <span ui-badge wrap variant=\"secondary\" class=\"font-mono text-xs\"\n                        >INTEGER NOT NULL DEFAULT 20</span\n                      >\n                    </div>\n                    <div class=\"border-border/60 bg-card flex items-center justify-between rounded-md border p-2\">\n                      <span class=\"text-foreground font-mono font-medium\">last_seat_audit_at</span>\n                      <span ui-badge wrap variant=\"secondary\" class=\"font-mono text-xs\">TIMESTAMPTZ DEFAULT NOW()</span>\n                    </div>\n                  </div>\n                </div>\n\n                <div class=\"border-border bg-muted/30 space-y-3 rounded-lg border p-4\">\n                  <div class=\"text-foreground flex items-center gap-2 text-sm font-medium\">\n                    <lucide-icon [img]=\"ShieldCheck\" class=\"text-success size-4 shrink-0\" />\n                    <span>Lock Safety &amp; Concurrency Evaluation</span>\n                  </div>\n                  <div class=\"text-muted-foreground space-y-2 text-xs\">\n                    <div class=\"border-border/60 bg-card flex items-center justify-between rounded-md border p-2\">\n                      <span>Lock Level Required</span>\n                      <span class=\"text-foreground font-mono font-medium\">ACCESS EXCLUSIVE (Catalog only)</span>\n                    </div>\n                    <div class=\"border-border/60 bg-card flex items-center justify-between rounded-md border p-2\">\n                      <span>Table Rewrite Required</span>\n                      <span ui-badge wrap variant=\"success\" class=\"text-xs\">No (PG11+ Constant Default)</span>\n                    </div>\n                    <div class=\"border-border/60 bg-card flex items-center justify-between rounded-md border p-2\">\n                      <span>Estimated Lock Duration</span>\n                      <span class=\"text-success font-mono font-medium\">&lt; 2ms</span>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </ui-tabs-content>\n\n            <!-- Tab 4: CLI Runner -->\n            <ui-tabs-content value=\"cli\" class=\"space-y-3 focus-visible:outline-none\">\n              <div class=\"border-border bg-card space-y-3 rounded-lg border p-4\">\n                <div class=\"flex items-center justify-between\">\n                  <div class=\"text-foreground flex items-center gap-2 text-xs font-medium\">\n                    <lucide-icon [img]=\"Terminal\" class=\"text-primary size-4 shrink-0\" />\n                    <span>Execute via Prisma CLI</span>\n                  </div>\n                  <button ui-button variant=\"outline\" size=\"xs\" class=\"h-7 gap-1 text-xs\" (click)=\"copyCli()\">\n                    @if (copiedCli()) {\n                      <lucide-icon [img]=\"Check\" class=\"text-success size-3\" />\n                    } @else {\n                      <lucide-icon [img]=\"Copy\" class=\"size-3\" />\n                    }\n                    <span>{{ copiedCli() ? 'Copied' : 'Copy' }}</span>\n                  </button>\n                </div>\n                <div class=\"bg-muted/60 text-foreground rounded-md p-3 font-mono text-xs select-all\">\n                  {{ cliCommand }}\n                </div>\n                <p class=\"text-muted-foreground text-xs\">\n                  Applies all pending migrations to the configured datasource cluster without prompting for interactive\n                  confirmation.\n                </p>\n              </div>\n            </ui-tabs-content>\n          </ui-tabs>\n        </ui-card-content>\n      </ui-card>\n\n      <!-- Historical Migrations Table Card -->\n      <ui-card class=\"border-border bg-card shadow-xs\">\n        <ui-card-header class=\"pb-3\">\n          <div class=\"flex items-center justify-between\">\n            <div>\n              <h3 ui-card-title class=\"text-foreground text-base font-semibold\">Recent Applied Migrations</h3>\n              <p ui-card-description class=\"text-muted-foreground text-xs\">\n                Historical DDL execution logs, author signatures, and validation checksums\n              </p>\n            </div>\n            <span ui-badge wrap variant=\"outline\" class=\"font-mono text-xs\">5 Most Recent</span>\n          </div>\n        </ui-card-header>\n\n        <ui-card-content class=\"p-0\">\n          <div class=\"overflow-x-auto\">\n            <div ui-table>\n              <thead ui-table-header>\n                <tr ui-table-row class=\"hover:bg-transparent\">\n                  <th ui-table-head class=\"text-xs font-medium\">Migration &amp; Version</th>\n                  <th ui-table-head class=\"text-xs font-medium\">Target Scope</th>\n                  <th ui-table-head class=\"text-xs font-medium\">Executed At</th>\n                  <th ui-table-head class=\"text-xs font-medium\">Author</th>\n                  <th ui-table-head class=\"text-xs font-medium\">Duration</th>\n                  <th ui-table-head class=\"text-right text-xs font-medium\">Status</th>\n                </tr>\n              </thead>\n              <tbody ui-table-body>\n                @for (migration of historicalMigrations; track migration.id) {\n                  <tr ui-table-row class=\"hover:bg-muted/50 text-xs transition-colors\">\n                    <td ui-table-cell class=\"py-3\">\n                      <div class=\"space-y-0.5\">\n                        <div class=\"text-foreground font-mono font-medium\">{{ migration.filename }}</div>\n                        <div class=\"text-muted-foreground flex items-center gap-1.5\">\n                          <span class=\"text-primary font-mono font-medium\">{{ migration.version }}</span>\n                          <span>·</span>\n                          <span>{{ migration.description }}</span>\n                        </div>\n                      </div>\n                    </td>\n                    <td ui-table-cell class=\"text-muted-foreground font-mono whitespace-nowrap\">\n                      {{ migration.tablesAffected }}\n                    </td>\n                    <td ui-table-cell class=\"text-muted-foreground whitespace-nowrap\">{{ migration.appliedAt }}</td>\n                    <td ui-table-cell class=\"text-muted-foreground whitespace-nowrap\">{{ migration.author }}</td>\n                    <td ui-table-cell class=\"text-foreground font-mono font-medium whitespace-nowrap\">\n                      {{ migration.executionTime }}\n                    </td>\n                    <td ui-table-cell class=\"text-right whitespace-nowrap\">\n                      <span ui-badge wrap variant=\"success\" class=\"gap-1 px-2 py-0.5 text-xs font-medium\">\n                        <lucide-icon [img]=\"CheckCircle2\" class=\"size-3\" />\n                        <span>Applied</span>\n                      </span>\n                    </td>\n                  </tr>\n                }\n              </tbody>\n            </div>\n          </div>\n        </ui-card-content>\n      </ui-card>\n    </div>\n  `,\n})\nexport class UiDatabaseMigrationSchemaDiffComponent {\n  @Input('class') className?: string\n\n  protected readonly upDiffLines = upDiffLines\n  protected readonly downDiffLines = downDiffLines\n  protected readonly historicalMigrations = historicalMigrations\n  protected readonly cliCommand = cliCommand\n\n  readonly activeTab = signal<DatabaseMigrationSchemaDiffTab>('up')\n  readonly isApplying = signal(false)\n  readonly isApplied = signal(false)\n  readonly copiedUpSql = signal(false)\n  readonly copiedDownSql = signal(false)\n  readonly copiedCli = signal(false)\n\n  protected readonly Check = Check\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly Clock = Clock\n  protected readonly Code2 = Code2\n  protected readonly Copy = Copy\n  protected readonly Database = Database\n  protected readonly FileCode2 = FileCode2\n  protected readonly HardDrive = HardDrive\n  protected readonly Layers = Layers\n  protected readonly Loader2 = Loader2\n  protected readonly Play = Play\n  protected readonly RotateCcw = RotateCcw\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly Terminal = Terminal\n  protected readonly Timer = Timer\n  protected readonly Undo2 = Undo2\n  protected readonly Zap = Zap\n\n  private readonly timers = new Set<ReturnType<typeof setTimeout>>()\n\n  constructor() {\n    inject(DestroyRef).onDestroy(() => this.timers.forEach((t) => clearTimeout(t)))\n  }\n\n  protected get rootClass(): string {\n    return cn('w-full space-y-6', this.className)\n  }\n\n  protected pendingCardClass(): string {\n    return cn('border-border bg-card shadow-xs transition-colors', !this.isApplied() && 'border-warning/40')\n  }\n\n  protected pendingIconClass(): string {\n    return cn(\n      'flex size-8 items-center justify-center rounded-md',\n      !this.isApplied() ? 'bg-warning/10 text-warning' : 'bg-success/10 text-success',\n    )\n  }\n\n  protected pendingFooterClass(): string {\n    return cn('mt-3 flex items-center gap-1.5 text-xs font-medium', !this.isApplied() ? 'text-warning' : 'text-success')\n  }\n\n  protected sqlCopied(): boolean {\n    return this.activeTab() === 'rollback' ? this.copiedDownSql() : this.copiedUpSql()\n  }\n\n  protected lineClass(line: DiffLine): string {\n    return cn(\n      'flex items-center px-3 py-0.5 transition-colors',\n      line.type === 'addition' && 'border-success bg-success/10 text-success border-l-2 font-medium',\n      line.type === 'deletion' && 'border-destructive bg-destructive/10 text-destructive border-l-2 font-medium',\n      line.type === 'modification' && 'border-info bg-info/10 text-info border-l-2 font-medium',\n      line.type === 'neutral' && 'text-muted-foreground border-l-2 border-transparent',\n    )\n  }\n\n  protected prefixClass(line: DiffLine): string {\n    return cn(\n      'w-4 shrink-0 text-center font-bold select-none',\n      line.type === 'addition' && 'text-success',\n      line.type === 'deletion' && 'text-destructive',\n      line.type === 'modification' && 'text-info',\n      line.type === 'neutral' && 'text-transparent',\n    )\n  }\n\n  setActiveTab(value: string): void {\n    if (value === 'up' || value === 'rollback' || value === 'impact' || value === 'cli') this.activeTab.set(value)\n  }\n\n  handleApplyMigration(): void {\n    if (this.isApplying() || this.isApplied()) return\n    this.isApplying.set(true)\n    this.later(900, () => {\n      this.isApplying.set(false)\n      this.isApplied.set(true)\n    })\n  }\n\n  handlePreviewRollback(): void {\n    this.activeTab.set('rollback')\n  }\n\n  handleResetState(): void {\n    this.isApplied.set(false)\n    this.isApplying.set(false)\n  }\n\n  copySql(): void {\n    const rollback = this.activeTab() === 'rollback'\n    this.copyToClipboard(rollback ? downSqlRaw : upSqlRaw, rollback ? 'down' : 'up')\n  }\n\n  copyCli(): void {\n    this.copyToClipboard(cliCommand, 'cli')\n  }\n\n  copyToClipboard(text: string, type: 'up' | 'down' | 'cli'): void {\n    if (typeof navigator !== 'undefined' && navigator.clipboard) navigator.clipboard.writeText(text).catch(() => {})\n    const flag = type === 'up' ? this.copiedUpSql : type === 'down' ? this.copiedDownSql : this.copiedCli\n    flag.set(true)\n    this.later(2000, () => flag.set(false))\n  }\n\n  private later(ms: number, fn: () => void): void {\n    const t = setTimeout(() => {\n      this.timers.delete(t)\n      fn()\n    }, ms)\n    this.timers.add(t)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/database-migration-schema-diff/database-migration-schema-diff.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/database-migration-schema-diff/index.ts",
      "content": "export {\n  UiDatabaseMigrationSchemaDiffComponent,\n  type DiffLine,\n  type HistoricalMigration,\n  type DatabaseMigrationSchemaDiffTab,\n} from './database-migration-schema-diff.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/database-migration-schema-diff/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/separator.json",
    "https://uipkge.dev/r/angular/table.json",
    "https://uipkge.dev/r/angular/tabs.json"
  ],
  "description": "Prisma, Flyway, and Liquibase database migration runner with pending DDL visual diffs, rollback scripts, schema integrity telemetry, and execution history.",
  "categories": [
    "devops",
    "app"
  ]
}