{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "synthetic-data-generator",
  "title": "Synthetic Data Generator",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/synthetic-data-generator/synthetic-data-generator.component.ts",
      "content": "import {\n  Component,\n  DestroyRef,\n  Input,\n  type OnInit,\n  computed,\n  inject,\n  signal,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport {\n  Check,\n  Code2,\n  Cpu,\n  Database,\n  Download,\n  FileSpreadsheet,\n  Info,\n  Layers,\n  Lock,\n  LucideAngularModule,\n  Plus,\n  RefreshCw,\n  ShieldCheck,\n  Sliders,\n  Sparkles,\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 {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiInputComponent } from '@/ui/input/input.component'\nimport {\n  UiSelectComponent,\n  UiSelectContentComponent,\n  UiSelectItemComponent,\n  UiSelectTriggerComponent,\n  UiSelectValueComponent,\n} from '@/ui/select/select.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\nimport { UiSliderComponent } from '@/ui/slider/slider.component'\nimport { UiSwitchComponent } from '@/ui/switch/switch.component'\nimport type {\n  FieldDataType,\n  FidelityMetrics,\n  GeneratedRecord,\n  SchemaField,\n  SyntheticDataGeneratorProps,\n  TemplatePreset,\n} from './synthetic-data-types'\nimport {\n  boxMullerRandom as boxMullerRandomHelper,\n  CARD_BRANDS,\n  FIRST_NAMES,\n  LAST_NAMES,\n  TEMPLATES,\n} from './synthetic-data-templates'\nimport { UiSyntheticDataPreviewComponent, type SyntheticDataPreviewTab } from './synthetic-data-preview.component'\n\nexport type {\n  FieldDataType,\n  FidelityMetrics,\n  GeneratedRecord,\n  SchemaField,\n  SyntheticDataGeneratorProps,\n  TemplatePreset,\n}\n\nconst ROW_PRESETS = [100, 1000, 5000, 10000, 50000]\nconst EPSILON_PRESETS = [\n  { eps: 0.5, label: 'Healthcare' },\n  { eps: 1.2, label: 'Standard' },\n  { eps: 3.0, label: 'Analytics' },\n]\nconst FIELD_TYPE_BADGE: Record<FieldDataType, string> = {\n  gaussian_float: 'border-info/20 bg-info/10 text-info',\n  name: 'border-chart-1/20 bg-chart-1/10 text-chart-1',\n  email: 'border-chart-2/20 bg-chart-2/10 text-chart-2',\n  credit_card: 'border-warning/20 bg-warning/10 text-warning',\n  category_weights: 'border-success/20 bg-success/10 text-success',\n  date_range: 'border-destructive/20 bg-destructive/10 text-destructive',\n}\nconst FORMAT_BUTTON = 'flex cursor-pointer items-center gap-1.5 rounded-md px-2.5 py-1.5 font-medium transition-colors'\n\nfunction cloneFields(key: string): SchemaField[] {\n  const tpl = TEMPLATES[key]\n  return tpl?.fields ? (JSON.parse(JSON.stringify(tpl.fields)) as SchemaField[]) : []\n}\n\n/**\n * Synthetic dataset generator & privacy engine (Gretel / Tonic style). Port of the React\n * `SyntheticDataGenerator` 1:1: schema template picker, CSV / JSON Lines format toggle, row\n * volume slider + presets, differential-privacy epsilon budget, a custom schema field builder,\n * PII anonymization switches, and the live preview column (`ui-synthetic-data-preview`).\n * `initialTemplate`, `initialRowCount` and `initialEpsilon` seed the state once.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-synthetic-data-generator, [ui-synthetic-data-generator]',\n  standalone: true,\n  // React 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    UiCardDescriptionComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiInputComponent,\n    UiSelectComponent,\n    UiSelectContentComponent,\n    UiSelectItemComponent,\n    UiSelectTriggerComponent,\n    UiSelectValueComponent,\n    UiSeparatorComponent,\n    UiSliderComponent,\n    UiSwitchComponent,\n    UiSyntheticDataPreviewComponent,\n  ],\n  template: `\n    <div data-slot=\"synthetic-data-generator\" [class]=\"rootClass\">\n      <!-- TOP HEADER HERO -->\n      <div ui-card class=\"border-border overflow-hidden shadow-xs\">\n        <div ui-card-header class=\"border-border/60 bg-muted/20 border-b 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\">\n              <div class=\"flex flex-wrap items-center gap-2\">\n                <div class=\"bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg\">\n                  <lucide-icon [img]=\"Cpu\" class=\"size-4.5\" />\n                </div>\n                <h3 ui-card-title class=\"text-lg font-bold tracking-tight md:text-xl\">\n                  Synthetic Dataset Generator &amp; Privacy Engine\n                </h3>\n                <span ui-badge variant=\"outline\" class=\"gap-1 text-xs font-normal\">\n                  <span class=\"bg-success size-1.5 animate-pulse rounded-full\"></span>\n                  Differential Privacy Enabled\n                </span>\n              </div>\n              <p ui-card-description class=\"text-xs md:text-sm\">\n                Mathematical &amp; LLM-guided synthetic tabular dataset synthesizer with configurable (ε, δ)-DP noise\n                injection.\n              </p>\n            </div>\n\n            <!-- Header Right Quick Actions -->\n            <div class=\"flex flex-wrap items-center gap-2.5\">\n              <!-- Format Selector Segmented Control -->\n              <div class=\"bg-muted/60 border-border inline-flex items-center rounded-lg border p-0.5 text-xs\">\n                <button type=\"button\" [class]=\"formatButtonClass('csv')\" (click)=\"outputFormat.set('csv')\">\n                  <lucide-icon [img]=\"FileSpreadsheet\" class=\"size-3.5\" />\n                  <span>Tabular CSV</span>\n                </button>\n                <button type=\"button\" [class]=\"formatButtonClass('jsonl')\" (click)=\"outputFormat.set('jsonl')\">\n                  <lucide-icon [img]=\"Code2\" class=\"size-3.5\" />\n                  <span>JSON Lines</span>\n                </button>\n              </div>\n\n              <!-- Download Button -->\n              <button\n                ui-button\n                aria-label=\"Download attachment\"\n                variant=\"outline\"\n                size=\"sm\"\n                class=\"h-9 gap-1.5 text-xs font-medium\"\n                (click)=\"handleDownloadDataset()\"\n              >\n                <lucide-icon [img]=\"Download\" class=\"size-3.5\" />\n                <span>Download Dataset</span>\n              </button>\n\n              <!-- Primary Generate Batch Button -->\n              <button\n                ui-button\n                variant=\"default\"\n                size=\"sm\"\n                class=\"h-9 gap-1.5 px-4 text-xs font-semibold shadow-xs transition-[color,background-color,border-color,box-shadow,opacity,transform,scale,translate,rotate] active:scale-95\"\n                [disabled]=\"isGenerating()\"\n                (click)=\"triggerRegeneration()\"\n              >\n                @if (isGenerating()) {\n                  <!-- [class] (not class=) feeds only the svg, so the host does not spin as well. -->\n                  <lucide-icon [img]=\"RefreshCw\" [class]=\"'size-3.5 animate-spin'\" />\n                } @else {\n                  <lucide-icon [img]=\"Sparkles\" class=\"size-3.5\" />\n                }\n                <span>{{ isGenerating() ? 'Synthesizing...' : 'Generate Synthetic Batch' }}</span>\n              </button>\n            </div>\n          </div>\n\n          <!-- Template Selector Bar -->\n          <div\n            class=\"border-border/50 bg-background/50 mt-3 flex flex-wrap items-center justify-between gap-3 rounded-lg border px-3.5 py-2\"\n          >\n            <div class=\"flex items-center gap-2\">\n              <lucide-icon [img]=\"Layers\" class=\"text-muted-foreground size-4\" />\n              <span class=\"text-foreground text-xs font-semibold\">Schema Template:</span>\n            </div>\n\n            <div class=\"w-full min-w-0 sm:max-w-md sm:flex-1\">\n              <ui-select [value]=\"selectedTemplateKey()\" (valueChange)=\"handleTemplateChange($event)\">\n                <button\n                  ui-select-trigger\n                  class=\"bg-background h-8.5 w-full text-xs font-medium [&>span]:truncate [&>svg]:shrink-0\"\n                >\n                  <span ui-select-value placeholder=\"Select Schema Template\"></span>\n                </button>\n                <ui-select-content>\n                  @for (tpl of templates; track tpl[0]) {\n                    <ui-select-item [value]=\"tpl[0]\" class=\"text-xs\">{{ tpl[1].name }}</ui-select-item>\n                  }\n                </ui-select-content>\n              </ui-select>\n            </div>\n\n            <div class=\"text-muted-foreground hidden items-center gap-2 text-xs sm:flex\">\n              <span ui-badge variant=\"secondary\" class=\"px-2 py-0.5 text-xs font-normal\">\n                {{ templateCategory() }}\n              </span>\n              <span>•</span>\n              <span class=\"font-mono tabular-nums\">{{ activeFields().length }} fields configured</span>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- DOWNLOAD SUCCESS NOTIFICATION -->\n      @if (downloadToast(); as toast) {\n        <div\n          class=\"border-success/30 bg-success/10 text-success text-success flex items-center justify-between rounded-lg border px-4 py-2.5 text-xs font-medium shadow-xs\"\n        >\n          <div class=\"flex items-center gap-2\">\n            <lucide-icon [img]=\"Check\" class=\"size-4 stroke-[2.5]\" />\n            <span>{{ toast }}</span>\n          </div>\n          <span ui-badge variant=\"outline\" class=\"border-success/40 font-mono text-xs\">\n            {{ estimatedFileSize() }}\n          </span>\n        </div>\n      }\n\n      <!-- 2-COLUMN STUDIO GENERATOR -->\n      <div class=\"grid grid-cols-1 gap-6 lg:grid-cols-12\">\n        <!-- LEFT COLUMN: SCHEMA & PRIVACY CONFIGURATION (40% / 5 Cols) -->\n        <div class=\"space-y-6 lg:col-span-5\">\n          <!-- CARD 1: ROW COUNT & BATCH SCALE -->\n          <div ui-card class=\"border-border shadow-xs\">\n            <div ui-card-header class=\"border-border/50 border-b pb-3\">\n              <div class=\"flex items-center justify-between\">\n                <div class=\"flex items-center gap-2\">\n                  <div class=\"bg-muted text-muted-foreground flex size-7 items-center justify-center rounded-md\">\n                    <lucide-icon [img]=\"Database\" class=\"size-3.5\" />\n                  </div>\n                  <h3 ui-card-title class=\"text-sm font-semibold\">Dataset Generation Volume</h3>\n                </div>\n                <span ui-badge variant=\"secondary\" class=\"font-mono text-xs font-semibold tabular-nums\">\n                  {{ rowCount().toLocaleString() }} rows\n                </span>\n              </div>\n            </div>\n\n            <div ui-card-content class=\"space-y-4 pt-4\">\n              <!-- Row Slider -->\n              <div class=\"space-y-2\">\n                <div class=\"text-muted-foreground flex items-center justify-between text-xs\">\n                  <span>100 rows</span>\n                  <span class=\"text-foreground font-mono font-medium\">Estimated: {{ estimatedFileSize() }}</span>\n                  <span>50,000 rows</span>\n                </div>\n\n                <ui-slider\n                  [value]=\"[rowCount()]\"\n                  [min]=\"100\"\n                  [max]=\"50000\"\n                  [step]=\"100\"\n                  class=\"w-full py-1\"\n                  (valueChange)=\"onRowCountChange($event)\"\n                />\n              </div>\n\n              <!-- Quick Preset Chips -->\n              <div class=\"flex flex-wrap items-center gap-1.5 pt-1\">\n                <span class=\"text-muted-foreground mr-1 text-xs\">Presets:</span>\n                @for (preset of rowPresets; track preset) {\n                  <button type=\"button\" [class]=\"rowPresetClass(preset)\" (click)=\"rowCount.set(preset)\">\n                    {{ presetLabel(preset) }}\n                  </button>\n                }\n              </div>\n            </div>\n          </div>\n\n          <!-- CARD 2: DIFFERENTIAL PRIVACY EPSILON (ε) CONTROLS -->\n          <div ui-card class=\"border-border shadow-xs\">\n            <div ui-card-header class=\"border-border/50 border-b pb-3\">\n              <div class=\"flex items-center justify-between\">\n                <div class=\"flex items-center gap-2\">\n                  <div class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n                    <lucide-icon [img]=\"ShieldCheck\" class=\"size-3.5\" />\n                  </div>\n                  <div>\n                    <h3 ui-card-title class=\"text-sm font-semibold\">Differential Privacy Budget</h3>\n                    <p ui-card-description class=\"text-xs\">Laplace (ε, δ)-DP noise calibration</p>\n                  </div>\n                </div>\n                <span ui-badge variant=\"outline\" class=\"font-mono text-xs font-bold tabular-nums\">\n                  ε = {{ epsilon().toFixed(1) }}\n                </span>\n              </div>\n            </div>\n\n            <div ui-card-content class=\"space-y-4 pt-4\">\n              <!-- Epsilon Slider -->\n              <div class=\"space-y-2\">\n                <div class=\"flex items-center justify-between text-xs\">\n                  <span class=\"text-success font-medium\">ε=0.1 (High Privacy)</span>\n                  <span class=\"text-warning font-medium\">ε=5.0 (High Fidelity)</span>\n                </div>\n\n                <ui-slider\n                  [value]=\"[epsilon()]\"\n                  [min]=\"0.1\"\n                  [max]=\"5.0\"\n                  [step]=\"0.1\"\n                  class=\"w-full py-1\"\n                  (valueChange)=\"onEpsilonChange($event)\"\n                />\n              </div>\n\n              <!-- Epsilon Interpretation Banner -->\n              <div [class]=\"privacyBannerClass()\">\n                <!-- [class] (not class=) feeds only the svg, so mt-0.5 is not applied twice (host + svg). -->\n                <lucide-icon [img]=\"Info\" [class]=\"'mt-0.5 size-4 shrink-0'\" />\n                <div class=\"space-y-1\">\n                  <div class=\"font-semibold\">{{ fidelityMetrics().privacyTier }}</div>\n                  <p class=\"text-muted-foreground text-xs leading-relaxed\">\n                    Laplace noise scale:\n                    <span class=\"text-foreground font-mono font-medium\"\n                      >σ=±{{ fidelityMetrics().laplaceNoiseStd }}</span\n                    >\n                    • Delta: <span class=\"text-foreground font-mono font-medium\">δ=10⁻⁵</span> • Zero memorization\n                    guarantee.\n                  </p>\n                </div>\n              </div>\n\n              <!-- Quick Epsilon Presets -->\n              <div class=\"grid grid-cols-3 gap-2 pt-1\">\n                @for (p of epsilonPresets; track p.eps) {\n                  <button type=\"button\" [class]=\"epsilonPresetClass(p.eps)\" (click)=\"epsilon.set(p.eps)\">\n                    <div class=\"font-bold\">ε = {{ p.eps.toFixed(1) }}</div>\n                    <div class=\"text-muted-foreground text-xs\">{{ p.label }}</div>\n                  </button>\n                }\n              </div>\n            </div>\n          </div>\n\n          <!-- CARD 3: FIELD SCHEMAS GENERATOR -->\n          <div ui-card class=\"border-border shadow-xs\">\n            <div ui-card-header class=\"border-border/50 border-b pb-3\">\n              <div class=\"flex items-center justify-between\">\n                <div class=\"flex items-center gap-2\">\n                  <div class=\"bg-muted text-muted-foreground flex size-7 items-center justify-center rounded-md\">\n                    <lucide-icon [img]=\"Sliders\" class=\"size-3.5\" />\n                  </div>\n                  <div>\n                    <h3 ui-card-title class=\"text-sm font-semibold\">Schema Fields Generator</h3>\n                    <p ui-card-description class=\"text-xs\">\n                      {{ activeFields().length }} active synthesizer distributions\n                    </p>\n                  </div>\n                </div>\n                <button\n                  ui-button\n                  variant=\"outline\"\n                  size=\"sm\"\n                  class=\"h-7.5 gap-1 px-2.5 text-xs\"\n                  (click)=\"showAddField.set(!showAddField())\"\n                >\n                  <lucide-icon [img]=\"Plus\" class=\"size-3.5\" />\n                  <span>{{ showAddField() ? 'Cancel' : 'Add Field' }}</span>\n                </button>\n              </div>\n            </div>\n\n            <div ui-card-content class=\"space-y-3 pt-4\">\n              <!-- Add Field Form -->\n              @if (showAddField()) {\n                <div class=\"border-border/80 bg-muted/30 space-y-3 rounded-lg border p-3.5 text-xs\">\n                  <div class=\"text-foreground font-semibold\">Configure New Synthetic Field</div>\n                  <div class=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n                    <div class=\"space-y-1\">\n                      <label class=\"text-muted-foreground text-xs\">Field Identifier</label>\n                      <ui-input\n                        [value]=\"newFieldName()\"\n                        (valueChange)=\"newFieldName.set($event)\"\n                        placeholder=\"e.g. account_balance\"\n                        class=\"h-8 text-xs\"\n                      />\n                    </div>\n                    <div class=\"space-y-1\">\n                      <label class=\"text-muted-foreground text-xs\">Data Distribution Type</label>\n                      <ui-select [value]=\"newFieldType()\" (valueChange)=\"setNewFieldType($event)\">\n                        <button ui-select-trigger class=\"bg-background h-8 w-full min-w-0 text-xs\">\n                          <span ui-select-value placeholder=\"Type\"></span>\n                        </button>\n                        <ui-select-content>\n                          <ui-select-item value=\"gaussian_float\" class=\"text-xs\"\n                            >Float Gaussian Distribution</ui-select-item\n                          >\n                          <ui-select-item value=\"name\" class=\"text-xs\">Full Name Faker</ui-select-item>\n                          <ui-select-item value=\"email\" class=\"text-xs\">Email Faker</ui-select-item>\n                          <ui-select-item value=\"credit_card\" class=\"text-xs\">Credit Card Faker</ui-select-item>\n                          <ui-select-item value=\"category_weights\" class=\"text-xs\">Category Weights</ui-select-item>\n                          <ui-select-item value=\"date_range\" class=\"text-xs\">Date Range</ui-select-item>\n                        </ui-select-content>\n                      </ui-select>\n                    </div>\n                  </div>\n\n                  <div class=\"flex justify-end gap-2 pt-1\">\n                    <button ui-button variant=\"ghost\" size=\"sm\" class=\"h-7.5 text-xs\" (click)=\"showAddField.set(false)\">\n                      Cancel\n                    </button>\n                    <button\n                      ui-button\n                      variant=\"default\"\n                      size=\"sm\"\n                      class=\"h-7.5 text-xs\"\n                      [disabled]=\"!newFieldName().trim()\"\n                      (click)=\"handleAddCustomField()\"\n                    >\n                      Add to Schema\n                    </button>\n                  </div>\n                </div>\n              }\n\n              <!-- Fields List -->\n              <div class=\"divide-border/60 max-h-[320px] space-y-2 overflow-y-auto pr-1\">\n                @for (field of activeFields(); track field.id) {\n                  <div\n                    class=\"group border-border/70 bg-card hover:bg-muted/20 flex items-center justify-between rounded-lg border p-2.5 transition-colors\"\n                  >\n                    <div class=\"min-w-0 space-y-0.5\">\n                      <div class=\"flex items-center gap-2\">\n                        <span class=\"text-foreground font-mono text-xs font-semibold\">{{ field.name }}</span>\n                        <span ui-badge variant=\"outline\" [class]=\"fieldTypeBadgeClass(field.type)\">\n                          {{ field.type.replace('_', ' ') }}\n                        </span>\n                      </div>\n                      <p class=\"text-muted-foreground truncate text-xs\">\n                        {{ field.description || 'Configured synthesizer parameter' }}\n                      </p>\n                    </div>\n\n                    <button\n                      ui-button\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      class=\"text-muted-foreground hover:text-destructive h-7 w-7 p-0 opacity-80 group-hover:opacity-100\"\n                      aria-label=\"Delete field\"\n                      [disabled]=\"activeFields().length <= 2\"\n                      (click)=\"removeField(field.id)\"\n                    >\n                      <lucide-icon [img]=\"Trash2\" class=\"size-3.5\" />\n                    </button>\n                  </div>\n                }\n              </div>\n            </div>\n          </div>\n\n          <!-- CARD 4: PII ANONYMIZATION POLICY SWITCHES -->\n          <div ui-card class=\"border-border shadow-xs\">\n            <div ui-card-header class=\"border-border/50 border-b pb-3\">\n              <div class=\"flex items-center gap-2\">\n                <div class=\"bg-muted text-muted-foreground flex size-7 items-center justify-center rounded-md\">\n                  <lucide-icon [img]=\"Lock\" class=\"size-3.5\" />\n                </div>\n                <div>\n                  <h3 ui-card-title class=\"text-sm font-semibold\">PII Anonymization Policy</h3>\n                  <p ui-card-description class=\"text-xs\">Zero linkage &amp; re-identification guarantees</p>\n                </div>\n              </div>\n            </div>\n\n            <div ui-card-content class=\"space-y-4 pt-4\">\n              <!-- K-Anonymity Switch -->\n              <div class=\"flex items-start justify-between gap-3\">\n                <div class=\"space-y-0.5\">\n                  <div class=\"text-foreground text-xs font-semibold\">K-Anonymity (k = 5 Cohort Enforcement)</div>\n                  <p class=\"text-muted-foreground text-xs leading-relaxed\">\n                    Generalizes quasi-identifiers so each demographic bucket contains ≥ 5 records.\n                  </p>\n                </div>\n                <button ui-switch [checked]=\"kAnonymity()\" (checkedChange)=\"kAnonymity.set($event)\"></button>\n              </div>\n\n              <div ui-separator></div>\n\n              <!-- Salted Hash Switch -->\n              <div class=\"flex items-start justify-between gap-3\">\n                <div class=\"space-y-0.5\">\n                  <div class=\"text-foreground text-xs font-semibold\">Salted HMAC-SHA256 for Primary Keys</div>\n                  <p class=\"text-muted-foreground text-xs leading-relaxed\">\n                    Deterministically pseudonymizes primary keys with ephemeral cryptographic salt.\n                  </p>\n                </div>\n                <button ui-switch [checked]=\"saltedHash()\" (checkedChange)=\"saltedHash.set($event)\"></button>\n              </div>\n\n              <div ui-separator></div>\n\n              <!-- Noise Injection Switch -->\n              <div class=\"flex items-start justify-between gap-3\">\n                <div class=\"space-y-0.5\">\n                  <div class=\"text-foreground text-xs font-semibold\">Differential Privacy Gaussian Noise</div>\n                  <p class=\"text-muted-foreground text-xs leading-relaxed\">\n                    Injects calibrated noise calibrated to global sensitivity (Δf / ε).\n                  </p>\n                </div>\n                <button ui-switch [checked]=\"noiseMechanism()\" (checkedChange)=\"noiseMechanism.set($event)\"></button>\n              </div>\n            </div>\n          </div>\n        </div>\n\n        <!-- RIGHT COLUMN: LIVE GENERATED SYNTHETIC DATA PREVIEW (60% / 7 Cols) -->\n        <div class=\"space-y-6 lg:col-span-7\">\n          <ui-synthetic-data-preview\n            [previewTab]=\"previewTab()\"\n            (previewTabChange)=\"previewTab.set($event)\"\n            [previewRecords]=\"previewRecords()\"\n            [activeFields]=\"activeFields()\"\n            [fidelityMetrics]=\"fidelityMetrics()\"\n            [epsilon]=\"epsilon()\"\n            [rowCount]=\"rowCount()\"\n            [outputFormat]=\"outputFormat()\"\n            [generationSeed]=\"generationSeed()\"\n            [copied]=\"copied()\"\n            (copyJsonLines)=\"copyJsonLines()\"\n          />\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiSyntheticDataGeneratorComponent implements OnInit {\n  @Input('class') className?: string\n  @Input() initialTemplate = 'customer-transactions'\n  @Input() initialRowCount = 1000\n  @Input() initialEpsilon = 1.2\n\n  // --- STATE ---\n  readonly selectedTemplateKey = signal<string>('customer-transactions')\n  readonly outputFormat = signal<'csv' | 'jsonl'>('csv')\n  readonly rowCount = signal<number>(1000)\n  readonly epsilon = signal<number>(1.2)\n  readonly kAnonymity = signal<boolean>(true)\n  readonly saltedHash = signal<boolean>(true)\n  readonly noiseMechanism = signal<boolean>(true)\n  readonly activeFields = signal<SchemaField[]>(cloneFields('customer-transactions'))\n\n  readonly isGenerating = signal<boolean>(false)\n  readonly copied = signal<boolean>(false)\n  readonly downloadToast = signal<string | null>(null)\n  readonly previewTab = signal<SyntheticDataPreviewTab>('table')\n  readonly generationSeed = signal<number>(48291)\n\n  // Field addition state\n  readonly showAddField = signal<boolean>(false)\n  readonly newFieldName = signal<string>('')\n  readonly newFieldType = signal<FieldDataType>('gaussian_float')\n\n  protected readonly templates = Object.entries(TEMPLATES)\n  protected readonly rowPresets = ROW_PRESETS\n  protected readonly epsilonPresets = EPSILON_PRESETS\n\n  protected readonly Check = Check\n  protected readonly Code2 = Code2\n  protected readonly Cpu = Cpu\n  protected readonly Database = Database\n  protected readonly Download = Download\n  protected readonly FileSpreadsheet = FileSpreadsheet\n  protected readonly Info = Info\n  protected readonly Layers = Layers\n  protected readonly Lock = Lock\n  protected readonly Plus = Plus\n  protected readonly RefreshCw = RefreshCw\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly Sliders = Sliders\n  protected readonly Sparkles = Sparkles\n  protected readonly Trash2 = Trash2\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  ngOnInit(): void {\n    this.selectedTemplateKey.set(this.initialTemplate)\n    this.rowCount.set(this.initialRowCount)\n    this.epsilon.set(this.initialEpsilon)\n    this.activeFields.set(cloneFields(this.initialTemplate))\n  }\n\n  get rootClass(): string {\n    return cn('mx-auto w-full max-w-7xl space-y-6', this.className)\n  }\n\n  protected readonly templateCategory = computed(() => TEMPLATES[this.selectedTemplateKey()]?.category)\n\n  // Dynamic preview records computation\n  readonly previewRecords = computed<GeneratedRecord[]>(() => {\n    const generationSeed = this.generationSeed()\n    const saltedHash = this.saltedHash()\n    const noiseMechanism = this.noiseMechanism()\n    const epsilon = this.epsilon()\n    const rows: GeneratedRecord[] = []\n    const count = 6\n\n    // Box-Muller Gaussian random generator with differential privacy noise\n    const boxMullerRandom = (mean: number, stdDev: number, seedOffset: number): number =>\n      boxMullerRandomHelper(mean, stdDev, seedOffset, generationSeed, noiseMechanism, epsilon)\n\n    for (let i = 0; i < count; i++) {\n      const fn = FIRST_NAMES[(i * 3 + generationSeed) % FIRST_NAMES.length]!\n      const ln = LAST_NAMES[(i * 5 + generationSeed + 2) % LAST_NAMES.length]!\n      const fullName = `${fn} ${ln}`\n      const baseSlug = `${fn.charAt(0).toLowerCase()}.${ln.toLowerCase().replace(/[^a-z]/g, '')}`\n\n      const record: GeneratedRecord = {\n        id: saltedHash\n          ? `syn_${((generationSeed * 9301 + i * 49297) % 233280).toString(16).padStart(6, '0')}`\n          : `usr_${1000 + i}`,\n      }\n\n      for (const field of this.activeFields()) {\n        if (field.name.includes('id') || field.name.includes('uuid') || field.name.includes('token')) {\n          record[field.name] = record.id\n          continue\n        }\n\n        switch (field.type) {\n          case 'name':\n            record[field.name] = fullName\n            break\n\n          case 'email': {\n            const dom = field.params.domain || 'synthetic-vault.io'\n            record[field.name] = `${baseSlug}@${dom}`\n            break\n          }\n\n          case 'credit_card': {\n            const last4 = (((generationSeed * 7 + i * 1337 + 1000) % 9000) + 1000).toString()\n            const brand = CARD_BRANDS[i % CARD_BRANDS.length]\n            record[field.name] = `${brand} •••• ${last4}`\n            break\n          }\n\n          case 'gaussian_float': {\n            const m = field.params.mean ?? 100\n            const s = field.params.stdDev ?? 20\n            const rawVal = boxMullerRandom(m, s, i)\n            const val = Math.max(m * 0.1, rawVal)\n\n            if (field.params.currency) {\n              record[field.name] = `${field.params.currency}${val.toFixed(2)}`\n            } else if (field.params.unit) {\n              record[field.name] = `${val.toFixed(1)} ${field.params.unit}`\n            } else {\n              record[field.name] = parseFloat(val.toFixed(2))\n            }\n            break\n          }\n\n          case 'category_weights': {\n            const cats = field.params.categories || ['Standard', 'Premium']\n            const catIndex = (i + Math.floor(generationSeed / 100)) % cats.length\n            record[field.name] = cats[catIndex]!\n            break\n          }\n\n          case 'date_range': {\n            const day = ((10 + i * 4) % 28) + 1\n            const month = (i % 8) + 1\n            const dayStr = day.toString().padStart(2, '0')\n            const monthStr = month.toString().padStart(2, '0')\n            const hour = (8 + i * 3) % 24\n            const hourStr = hour.toString().padStart(2, '0')\n            record[field.name] = `2025-${monthStr}-${dayStr} ${hourStr}:24:18 UTC`\n            break\n          }\n\n          default:\n            record[field.name] = `value_${i}`\n        }\n      }\n\n      rows.push(record)\n    }\n\n    return rows\n  })\n\n  // Statistical fidelity metrics\n  readonly fidelityMetrics = computed<FidelityMetrics>(() => {\n    const epsilon = this.epsilon()\n    const rawSimilarity = Math.min(99.4, Math.max(82.0, 99.2 - ((5.0 - epsilon) / 4.9) * 9.6))\n    const similarity = parseFloat(rawSimilarity.toFixed(1))\n    const leakageRisk =\n      this.kAnonymity() && this.saltedHash() && epsilon <= 3.5 ? 0.0 : Math.max(0, (epsilon - 3.5) * 0.04)\n    const correlationRetention = parseFloat(Math.min(99.1, 98.6 - ((5.0 - epsilon) / 4.9) * 6.2).toFixed(1))\n    const wassersteinDistance = parseFloat((0.032 + ((5.0 - epsilon) / 4.9) * 0.024).toFixed(3))\n    const laplaceNoiseStd = (0.12 / Math.max(0.1, epsilon)).toFixed(3)\n\n    let privacyTier = 'Balanced Privacy & Utility'\n    let privacyBadgeColor = 'bg-info/10 text-info border-info/30'\n\n    if (epsilon <= 0.5) {\n      privacyTier = 'Maximum Privacy / High DP Noise'\n      privacyBadgeColor = 'bg-success/10 text-success border-success/30'\n    } else if (epsilon > 2.5) {\n      privacyTier = 'High Statistical Utility / Minimal Noise'\n      privacyBadgeColor = 'bg-warning/10 text-warning border-warning/30'\n    }\n\n    return {\n      similarity,\n      leakageRisk: leakageRisk.toFixed(2),\n      correlationRetention,\n      wassersteinDistance,\n      laplaceNoiseStd,\n      privacyTier,\n      privacyBadgeColor,\n    }\n  })\n\n  // Estimated file size calculation\n  readonly estimatedFileSize = computed(() => {\n    const bytesPerRow = this.outputFormat() === 'csv' ? 180 : 380\n    const totalKb = (this.rowCount() * bytesPerRow) / 1024\n    if (totalKb < 1024) {\n      return `~${totalKb.toFixed(1)} KB`\n    }\n    return `~${(totalKb / 1024).toFixed(2)} MB`\n  })\n\n  protected formatButtonClass(format: 'csv' | 'jsonl'): string {\n    return cn(\n      FORMAT_BUTTON,\n      this.outputFormat() === format\n        ? 'bg-background text-foreground shadow-xs'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  protected rowPresetClass(preset: number): string {\n    return cn(\n      'cursor-pointer rounded-md border px-2.5 py-1 text-xs font-medium transition-colors',\n      this.rowCount() === preset\n        ? 'bg-primary text-primary-foreground border-primary shadow-xs'\n        : 'bg-muted/40 text-muted-foreground border-border hover:bg-muted hover:text-foreground',\n    )\n  }\n\n  protected presetLabel(preset: number): string {\n    return preset >= 1000 ? `${preset / 1000}k` : String(preset)\n  }\n\n  protected epsilonPresetClass(eps: number): string {\n    return cn(\n      'cursor-pointer rounded-lg border p-2 text-center text-xs transition-colors',\n      this.epsilon() === eps\n        ? 'border-primary bg-primary/10 text-primary font-semibold'\n        : 'border-border bg-background text-muted-foreground hover:bg-muted/40',\n    )\n  }\n\n  protected privacyBannerClass(): string {\n    return cn('flex items-start gap-2.5 rounded-lg border p-3 text-xs', this.fidelityMetrics().privacyBadgeColor)\n  }\n\n  protected fieldTypeBadgeClass(type: FieldDataType): string {\n    return cn('px-1.5 py-0 text-xs font-semibold tracking-wider uppercase', FIELD_TYPE_BADGE[type])\n  }\n\n  protected onRowCountChange(value: number[]): void {\n    if (value[0] !== undefined) this.rowCount.set(value[0])\n  }\n\n  protected onEpsilonChange(value: number[]): void {\n    if (value[0] !== undefined) this.epsilon.set(value[0])\n  }\n\n  protected setNewFieldType(value: string): void {\n    this.newFieldType.set(value as FieldDataType)\n  }\n\n  // Handle template selection switch\n  handleTemplateChange(key: string): void {\n    this.selectedTemplateKey.set(key)\n    if (TEMPLATES[key]) {\n      this.activeFields.set(cloneFields(key))\n      this.triggerRegeneration()\n    }\n  }\n\n  // Generation trigger\n  triggerRegeneration(): void {\n    this.isGenerating.set(true)\n    this.generationSeed.set(Math.floor(Math.random() * 90000) + 10000)\n    this.later(() => this.isGenerating.set(false), 450)\n  }\n\n  // Add field handler\n  handleAddCustomField(): void {\n    const name = this.newFieldName()\n    if (!name.trim()) return\n\n    const type = this.newFieldType()\n    const cleanName = name.trim().toLowerCase().replace(/\\s+/g, '_')\n    const newField: SchemaField = {\n      id: `f-custom-${Date.now()}`,\n      name: cleanName,\n      label: name.trim(),\n      type,\n      description: `Custom ${type} schema column`,\n      params:\n        type === 'gaussian_float'\n          ? { mean: 500, stdDev: 120, currency: '$' }\n          : type === 'category_weights'\n            ? { categories: ['Alpha', 'Beta', 'Gamma'], weights: [0.5, 0.3, 0.2] }\n            : {},\n    }\n\n    this.activeFields.update((prev) => [...prev, newField])\n    this.newFieldName.set('')\n    this.showAddField.set(false)\n    this.triggerRegeneration()\n  }\n\n  // Remove field handler\n  removeField(fieldId: string): void {\n    if (this.activeFields().length <= 2) return\n    this.activeFields.update((prev) => prev.filter((f) => f.id !== fieldId))\n    this.triggerRegeneration()\n  }\n\n  // Copy JSON lines\n  async copyJsonLines(): Promise<void> {\n    try {\n      const jsonLinesText = this.previewRecords()\n        .map((r) => JSON.stringify(r))\n        .join('\\n')\n      if (navigator?.clipboard?.writeText) {\n        await navigator.clipboard.writeText(jsonLinesText)\n      }\n      this.copied.set(true)\n      this.later(() => this.copied.set(false), 2000)\n    } catch (err) {\n      console.error('Failed to copy to clipboard', err)\n    }\n  }\n\n  // Download simulation\n  handleDownloadDataset(): void {\n    const rowCount = this.rowCount()\n    const filename = `synthetic_${this.selectedTemplateKey()}_${rowCount}_rows.${this.outputFormat() === 'csv' ? 'csv' : 'jsonl'}`\n    this.downloadToast.set(`Downloaded ${rowCount.toLocaleString()} synthetic records (${filename})`)\n    this.later(() => this.downloadToast.set(null), 3500)\n  }\n\n  private later(fn: () => void, ms: number): 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/synthetic-data-generator/synthetic-data-generator.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/synthetic-data-generator/synthetic-data-preview.component.ts",
      "content": "import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from '@angular/core'\nimport {\n  BarChart3,\n  Binary,\n  Check,\n  Code2,\n  Copy,\n  LucideAngularModule,\n  Shield,\n  ShieldCheck,\n  Table,\n  Zap,\n} from 'lucide-angular'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardFooterComponent,\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'\nimport type { FidelityMetrics, GeneratedRecord, SchemaField } from './synthetic-data-types'\n\nexport type SyntheticDataPreviewTab = 'table' | 'json' | 'metrics'\n\n/** How a preview cell renders its value (React's nested ternary in SyntheticDataPreview). */\ntype CellKind = 'key' | 'category' | 'float' | 'card' | 'email' | 'text'\n\n/**\n * Right-hand column of the synthetic data generator: the fidelity / privacy gauge banner and\n * the live preview card with Table / JSONL / Marginals views. Port of the React\n * `SyntheticDataPreview` 1:1; the parent owns all state and passes it in.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-synthetic-data-preview, [ui-synthetic-data-preview]',\n  standalone: true,\n  host: { class: 'contents' },\n  imports: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardFooterComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiProgressComponent,\n    UiTableComponent,\n    UiTableHeaderComponent,\n    UiTableBodyComponent,\n    UiTableRowComponent,\n    UiTableHeadComponent,\n    UiTableCellComponent,\n    UiTabsComponent,\n    UiTabsListComponent,\n    UiTabsTriggerComponent,\n  ],\n  template: `\n    <div class=\"space-y-6\">\n      <!-- TOP GAUGE BANNER: STATISTICAL FIDELITY VS PRIVACY -->\n      <div ui-card class=\"border-border overflow-hidden shadow-xs\">\n        <div class=\"border-border/60 bg-success/10 dark:bg-success/10 border-b px-4 py-3\">\n          <div class=\"flex flex-wrap items-center justify-between gap-2\">\n            <div class=\"flex items-center gap-2\">\n              <lucide-icon [img]=\"ShieldCheck\" class=\"text-success size-4.5\" />\n              <span class=\"text-foreground font-mono text-sm font-bold tracking-tight\">\n                {{ fidelityMetrics.similarity }}% Distribution Similarity &bull; {{ fidelityMetrics.leakageRisk }}% PII\n                Leakage Risk\n              </span>\n            </div>\n            <span ui-badge variant=\"outline\" class=\"bg-background border-success/40 text-success text-xs font-semibold\">\n              &epsilon;-DP Bound: &le; {{ epsilon.toFixed(1) }}\n            </span>\n          </div>\n        </div>\n\n        <div ui-card-content class=\"grid grid-cols-1 gap-4 pt-4 sm:grid-cols-3\">\n          <!-- Metric 1: Fidelity Score -->\n          <div class=\"border-border/60 bg-muted/10 space-y-1.5 rounded-lg border p-3\">\n            <div class=\"text-muted-foreground flex items-center justify-between text-xs\">\n              <span>Fidelity Score</span>\n              <lucide-icon [img]=\"Zap\" class=\"text-warning size-3.5\" />\n            </div>\n            <div class=\"text-foreground font-mono text-lg font-bold tabular-nums\">\n              {{ fidelityMetrics.similarity }}%\n            </div>\n            <div ui-progress [value]=\"fidelityMetrics.similarity\" class=\"h-1.5\"></div>\n          </div>\n\n          <!-- Metric 2: Correlation Preservation -->\n          <div class=\"border-border/60 bg-muted/10 space-y-1.5 rounded-lg border p-3\">\n            <div class=\"text-muted-foreground flex items-center justify-between text-xs\">\n              <span>Covariance Retention</span>\n              <lucide-icon [img]=\"BarChart3\" class=\"text-info size-3.5\" />\n            </div>\n            <div class=\"text-foreground font-mono text-lg font-bold tabular-nums\">\n              {{ fidelityMetrics.correlationRetention }}%\n            </div>\n            <div ui-progress [value]=\"fidelityMetrics.correlationRetention\" class=\"h-1.5\"></div>\n          </div>\n\n          <!-- Metric 3: Wasserstein Distance -->\n          <div class=\"border-border/60 bg-muted/10 space-y-1.5 rounded-lg border p-3\">\n            <div class=\"text-muted-foreground flex items-center justify-between text-xs\">\n              <span>Wasserstein Distance</span>\n              <lucide-icon [img]=\"Binary\" class=\"text-chart-1 size-3.5\" />\n            </div>\n            <div class=\"text-foreground font-mono text-lg font-bold tabular-nums\">\n              {{ fidelityMetrics.wassersteinDistance }}\n            </div>\n            <div class=\"text-muted-foreground text-xs\">Low error bound (&lt; 0.05)</div>\n          </div>\n        </div>\n      </div>\n\n      <!-- PREVIEW TABS CARD -->\n      <div ui-card class=\"border-border shadow-xs\">\n        <div ui-card-header class=\"border-border/50 border-b pb-3\">\n          <div class=\"flex flex-wrap items-center justify-between gap-2\">\n            <div class=\"flex items-center gap-2\">\n              <div class=\"bg-muted text-muted-foreground flex size-7 items-center justify-center rounded-md\">\n                <lucide-icon [img]=\"TableIcon\" class=\"size-3.5\" />\n              </div>\n              <div>\n                <h3 ui-card-title class=\"text-sm font-semibold\">Live Generated Synthetic Data Preview</h3>\n                <p ui-card-description class=\"text-xs\">\n                  Real-time sampler showing first {{ previewRecords.length }} synthesized records\n                </p>\n              </div>\n            </div>\n\n            <!-- View Switcher -->\n            <div ui-tabs [value]=\"previewTab\" (valueChange)=\"onTabChange($event)\" class=\"w-auto\">\n              <div ui-tabs-list class=\"h-8\">\n                <!-- [class] (not class=) feeds only the svg, so mr-1 is not applied twice (host + svg). -->\n                <button ui-tabs-trigger value=\"table\" class=\"h-7 px-2.5 text-xs\">\n                  <lucide-icon [img]=\"TableIcon\" [class]=\"'mr-1 size-3'\" />\n                  <span>Table</span>\n                </button>\n                <button ui-tabs-trigger value=\"json\" class=\"h-7 px-2.5 text-xs\">\n                  <lucide-icon [img]=\"Code2\" [class]=\"'mr-1 size-3'\" />\n                  <span>JSONL</span>\n                </button>\n                <button ui-tabs-trigger value=\"metrics\" class=\"h-7 px-2.5 text-xs\">\n                  <lucide-icon [img]=\"BarChart3\" [class]=\"'mr-1 size-3'\" />\n                  <span>Marginals</span>\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n\n        <div ui-card-content class=\"pt-4\">\n          <!-- TAB 1: TABULAR PREVIEW -->\n          @if (previewTab === 'table') {\n            <div class=\"space-y-3\">\n              <div class=\"border-border overflow-hidden rounded-lg border\">\n                <div class=\"overflow-x-auto\">\n                  <ui-table class=\"min-w-full text-xs\">\n                    <thead ui-table-header class=\"bg-muted/40\">\n                      <tr ui-table-row>\n                        @for (f of activeFields; track f.id) {\n                          <th ui-table-head class=\"text-foreground font-mono text-xs font-semibold whitespace-nowrap\">\n                            {{ f.name }}\n                          </th>\n                        }\n                      </tr>\n                    </thead>\n                    <tbody ui-table-body>\n                      @for (row of previewRecords; track $index) {\n                        <tr ui-table-row class=\"hover:bg-muted/30 transition-colors\">\n                          @for (f of activeFields; track f.id) {\n                            <td ui-table-cell class=\"py-2.5 whitespace-nowrap\">\n                              @switch (cellKind(f)) {\n                                @case ('key') {\n                                  <span class=\"text-muted-foreground font-mono font-medium\">{{ row[f.name] }}</span>\n                                }\n                                @case ('category') {\n                                  <span ui-badge variant=\"secondary\" class=\"font-mono text-xs font-medium\">\n                                    {{ row[f.name] }}\n                                  </span>\n                                }\n                                @case ('float') {\n                                  <span class=\"text-foreground font-mono font-semibold tabular-nums\">\n                                    {{ row[f.name] }}\n                                  </span>\n                                }\n                                @case ('card') {\n                                  <span class=\"text-foreground font-mono text-xs font-medium\">{{ row[f.name] }}</span>\n                                }\n                                @case ('email') {\n                                  <span class=\"text-primary font-mono text-xs font-normal\">{{ row[f.name] }}</span>\n                                }\n                                @default {\n                                  <span class=\"text-foreground font-medium\">{{ row[f.name] }}</span>\n                                }\n                              }\n                            </td>\n                          }\n                        </tr>\n                      }\n                    </tbody>\n                  </ui-table>\n                </div>\n              </div>\n\n              <div class=\"text-muted-foreground flex flex-wrap items-center justify-between gap-2 px-1 text-xs\">\n                <span class=\"flex items-center gap-1.5 font-mono tabular-nums\">\n                  <span class=\"bg-success size-1.5 rounded-full\"></span>\n                  Showing {{ previewRecords.length }} sample records of {{ rowCount.toLocaleString() }} generated rows\n                </span>\n                <span class=\"text-muted-foreground font-mono text-xs\">Seed: #{{ generationSeed }}</span>\n              </div>\n            </div>\n          }\n\n          <!-- TAB 2: JSON LINES PREVIEW -->\n          @if (previewTab === 'json') {\n            <div class=\"space-y-3\">\n              <div class=\"flex items-center justify-between\">\n                <span class=\"text-muted-foreground text-xs\">Streaming JSON Lines formatted output:</span>\n                <button\n                  ui-button\n                  variant=\"outline\"\n                  size=\"sm\"\n                  class=\"h-7.5 gap-1.5 px-2.5 text-xs font-medium\"\n                  (click)=\"copyJsonLines.emit()\"\n                >\n                  @if (copied) {\n                    <lucide-icon [img]=\"Check\" class=\"text-success size-3 stroke-[2.5]\" />\n                  } @else {\n                    <lucide-icon [img]=\"Copy\" class=\"size-3\" />\n                  }\n                  <span>{{ copied ? 'Copied JSONL' : 'Copy JSON Lines' }}</span>\n                </button>\n              </div>\n\n              <div\n                class=\"border-border/80 bg-muted/40 max-h-[380px] overflow-x-auto overflow-y-auto rounded-lg border p-4 font-mono text-xs leading-relaxed\"\n              >\n                @for (rec of previewRecords; track $index; let idx = $index) {\n                  <div class=\"hover:bg-muted/60 rounded px-1.5 py-1 whitespace-pre transition-colors\">\n                    <span class=\"text-muted-foreground select-none\">{{ idx + 1 }}&nbsp;</span>\n                    <span class=\"text-foreground\">{{ '{ ' }}</span>\n                    @for (entry of entries(rec); track entry[0]; let last = $last) {\n                      <span class=\"text-info font-semibold\">\"{{ entry[0] }}\"</span>\n                      <span class=\"text-muted-foreground\">: </span>\n                      <span [class]=\"isNumber(entry[1]) ? 'text-warning font-medium' : 'text-success'\">{{\n                        jsonValue(entry[1])\n                      }}</span>\n                      @if (!last) {\n                        <span class=\"text-muted-foreground\">, </span>\n                      }\n                    }\n                    <span class=\"text-foreground\">{{ ' }' }}</span>\n                  </div>\n                }\n              </div>\n            </div>\n          }\n\n          <!-- TAB 3: MARGINALS & FIDELITY DENSITY -->\n          @if (previewTab === 'metrics') {\n            <div class=\"space-y-4\">\n              <div class=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n                <!-- Density Gauge 1 -->\n                <div class=\"border-border/60 bg-muted/20 space-y-2 rounded-lg border p-3.5\">\n                  <div class=\"flex items-center justify-between text-xs\">\n                    <span class=\"text-foreground font-semibold\">Gaussian Distribution Overlap</span>\n                    <span ui-badge variant=\"outline\" class=\"font-mono text-xs\">96.2%</span>\n                  </div>\n                  <p class=\"text-muted-foreground text-xs\">\n                    Kolmogorov-Smirnov distance between synthetic batch and continuous Gaussian ground truth.\n                  </p>\n                  <div ui-progress [value]=\"96.2\" class=\"h-2\"></div>\n                </div>\n\n                <!-- Density Gauge 2 -->\n                <div class=\"border-border/60 bg-muted/20 space-y-2 rounded-lg border p-3.5\">\n                  <div class=\"flex items-center justify-between text-xs\">\n                    <span class=\"text-foreground font-semibold\">Categorical Frequency Prior</span>\n                    <span ui-badge variant=\"outline\" class=\"font-mono text-xs\">98.4%</span>\n                  </div>\n                  <p class=\"text-muted-foreground text-xs\">\n                    Total variation distance across categorical weight assignments.\n                  </p>\n                  <div ui-progress [value]=\"98.4\" class=\"h-2\"></div>\n                </div>\n              </div>\n\n              <!-- Mathematical Guarantees Breakdown -->\n              <div class=\"border-border/60 bg-muted/10 space-y-2.5 rounded-lg border p-4 text-xs\">\n                <div class=\"text-foreground flex items-center gap-1.5 font-semibold\">\n                  <lucide-icon [img]=\"Shield\" class=\"text-primary size-4\" />\n                  <span>Mathematical Privacy Guarantees &bull; Formal Specs</span>\n                </div>\n                <div class=\"text-muted-foreground grid grid-cols-1 gap-2 sm:grid-cols-2\">\n                  <div class=\"flex items-center gap-1.5\">\n                    <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                    <span>Differential Privacy (ε = {{ epsilon.toFixed(1) }}, δ = 10⁻⁵)</span>\n                  </div>\n                  <div class=\"flex items-center gap-1.5\">\n                    <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                    <span>K-Anonymity (k = 5) Cohort Clamping</span>\n                  </div>\n                  <div class=\"flex items-center gap-1.5\">\n                    <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                    <span>Zero Direct Row Memorization</span>\n                  </div>\n                  <div class=\"flex items-center gap-1.5\">\n                    <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                    <span>Ephemeral In-Memory Compute</span>\n                  </div>\n                </div>\n              </div>\n            </div>\n          }\n        </div>\n\n        <div\n          ui-card-footer\n          class=\"border-border/40 text-muted-foreground flex flex-wrap items-center justify-between border-t py-3 text-xs\"\n        >\n          <span class=\"flex items-center gap-1.5\">\n            <lucide-icon [img]=\"Zap\" class=\"text-warning size-3.5\" />\n            Powered by LLM Semantic Synthesis &amp; Laplace Noise Engine\n          </span>\n          <span class=\"font-mono tabular-nums\">\n            Output: {{ outputFormat.toUpperCase() }} &bull; {{ activeFields.length }} fields &bull;\n            {{ rowCount.toLocaleString() }} records\n          </span>\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiSyntheticDataPreviewComponent {\n  @Input() previewTab: SyntheticDataPreviewTab = 'table'\n  @Output() previewTabChange = new EventEmitter<SyntheticDataPreviewTab>()\n  @Input() previewRecords: GeneratedRecord[] = []\n  @Input() activeFields: SchemaField[] = []\n  @Input() fidelityMetrics: FidelityMetrics = {\n    similarity: 0,\n    leakageRisk: '0.00',\n    correlationRetention: 0,\n    wassersteinDistance: 0,\n    laplaceNoiseStd: '0.000',\n    privacyTier: '',\n    privacyBadgeColor: '',\n  }\n  @Input() epsilon = 0\n  @Input() rowCount = 0\n  @Input() outputFormat: 'csv' | 'jsonl' = 'csv'\n  @Input() generationSeed = 0\n  @Input() copied = false\n  @Output() copyJsonLines = new EventEmitter<void>()\n\n  protected readonly BarChart3 = BarChart3\n  protected readonly Binary = Binary\n  protected readonly Check = Check\n  protected readonly Code2 = Code2\n  protected readonly Copy = Copy\n  protected readonly Shield = Shield\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly TableIcon = Table\n  protected readonly Zap = Zap\n\n  protected onTabChange(value: string): void {\n    this.previewTabChange.emit(value as SyntheticDataPreviewTab)\n  }\n\n  /** Keys (id / uuid / token columns) first, then by field type, exactly like React's ternary chain. */\n  protected cellKind(f: SchemaField): CellKind {\n    if (f.name === 'id' || f.name.includes('id') || f.name.includes('uuid') || f.name.includes('token')) return 'key'\n    if (f.type === 'category_weights') return 'category'\n    if (f.type === 'gaussian_float') return 'float'\n    if (f.type === 'credit_card') return 'card'\n    if (f.type === 'email') return 'email'\n    return 'text'\n  }\n\n  protected entries(rec: GeneratedRecord): [string, string | number][] {\n    return Object.entries(rec)\n  }\n\n  protected isNumber(value: string | number): boolean {\n    return typeof value === 'number'\n  }\n\n  protected jsonValue(value: string | number): string {\n    return typeof value === 'number' ? String(value) : `\"${value}\"`\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/synthetic-data-generator/synthetic-data-preview.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/synthetic-data-generator/synthetic-data-types.ts",
      "content": "export type FieldDataType = 'name' | 'email' | 'credit_card' | 'gaussian_float' | 'date_range' | 'category_weights'\n\nexport interface SchemaField {\n  id: string\n  name: string\n  label: string\n  type: FieldDataType\n  description?: string\n  params: {\n    mean?: number\n    stdDev?: number\n    currency?: string\n    unit?: string\n    categories?: string[]\n    weights?: number[]\n    domain?: string\n    dateStart?: string\n    dateEnd?: string\n  }\n}\n\nexport interface TemplatePreset {\n  id: string\n  name: string\n  description: string\n  category: string\n  fields: SchemaField[]\n}\n\nexport interface GeneratedRecord {\n  id: string\n  [key: string]: string | number\n}\n\nexport interface FidelityMetrics {\n  similarity: number\n  leakageRisk: string\n  correlationRetention: number\n  wassersteinDistance: number\n  laplaceNoiseStd: string\n  privacyTier: string\n  privacyBadgeColor: string\n}\n\n/** Inputs of `<ui-synthetic-data-generator>` (`class` is merged onto the root). */\nexport interface SyntheticDataGeneratorProps {\n  class?: string\n  initialTemplate?: string\n  initialRowCount?: number\n  initialEpsilon?: number\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/synthetic-data-generator/synthetic-data-types.ts"
    },
    {
      "path": "packages/registry-angular/blocks/synthetic-data-generator/synthetic-data-templates.ts",
      "content": "import type { TemplatePreset } from './synthetic-data-types'\n\nexport const TEMPLATES: Record<string, TemplatePreset> = {\n  'customer-transactions': {\n    id: 'customer-transactions',\n    name: 'Customer Transactions & Demographics Dataset',\n    description:\n      'High-dimensional e-commerce customer cohort, synthetic PII, spending distribution, and country codes.',\n    category: 'E-Commerce & Retail',\n    fields: [\n      {\n        id: 'f-id',\n        name: 'cust_id',\n        label: 'Customer ID',\n        type: 'name',\n        description: 'Salted pseudonymized primary key',\n        params: {},\n      },\n      {\n        id: 'f-name',\n        name: 'full_name',\n        label: 'Full Name',\n        type: 'name',\n        description: 'Synthetic person name generator',\n        params: {},\n      },\n      {\n        id: 'f-email',\n        name: 'email_address',\n        label: 'Synthetic Email',\n        type: 'email',\n        description: 'Faker email with domain isolation',\n        params: { domain: 'syn-corp.io' },\n      },\n      {\n        id: 'f-card',\n        name: 'masked_card',\n        label: 'Card PAN',\n        type: 'credit_card',\n        description: 'Luhn-compliant masked payment token',\n        params: {},\n      },\n      {\n        id: 'f-spend',\n        name: 'spending_amount',\n        label: 'Spending Amount',\n        type: 'gaussian_float',\n        description: 'Gaussian spending curve (μ=$342.50, σ=$85.20)',\n        params: { mean: 342.5, stdDev: 85.2, currency: '$' },\n      },\n      {\n        id: 'f-country',\n        name: 'country_code',\n        label: 'Country Region',\n        type: 'category_weights',\n        description: 'Categorical distribution [US, DE, GB, FR, JP]',\n        params: {\n          categories: ['US', 'DE', 'GB', 'FR', 'JP'],\n          weights: [0.45, 0.2, 0.15, 0.1, 0.1],\n        },\n      },\n      {\n        id: 'f-created',\n        name: 'created_at',\n        label: 'Registration Date',\n        type: 'date_range',\n        description: 'ISO-8601 timestamp range (2024-2025)',\n        params: { dateStart: '2024-01-01', dateEnd: '2025-08-20' },\n      },\n    ],\n  },\n  'healthcare-ehr': {\n    id: 'healthcare-ehr',\n    name: 'Healthcare Patient Clinical Records (EHR)',\n    description: 'HIPAA-compliant patient cohort with biomarker telemetry, blood pressure, and triage severity tiers.',\n    category: 'Healthcare & Life Sciences',\n    fields: [\n      {\n        id: 'f-h-id',\n        name: 'patient_id',\n        label: 'Patient ID',\n        type: 'name',\n        description: 'Deterministic HIPAA salted identifier',\n        params: {},\n      },\n      {\n        id: 'f-h-name',\n        name: 'patient_name',\n        label: 'Patient Name',\n        type: 'name',\n        description: 'Synthetic medical record moniker',\n        params: {},\n      },\n      {\n        id: 'f-h-email',\n        name: 'contact_email',\n        label: 'Contact Email',\n        type: 'email',\n        description: 'Synthetic provider contact',\n        params: { domain: 'med-synthetic.org' },\n      },\n      {\n        id: 'f-h-glucose',\n        name: 'blood_glucose',\n        label: 'Glucose (mg/dL)',\n        type: 'gaussian_float',\n        description: 'Fast fasting blood glucose (μ=108.4, σ=22.1)',\n        params: { mean: 108.4, stdDev: 22.1, unit: 'mg/dL' },\n      },\n      {\n        id: 'f-h-bp',\n        name: 'systolic_bp',\n        label: 'Systolic BP',\n        type: 'gaussian_float',\n        description: 'Arterial blood pressure (μ=124.0, σ=15.5)',\n        params: { mean: 124.0, stdDev: 15.5, unit: 'mmHg' },\n      },\n      {\n        id: 'f-h-tier',\n        name: 'triage_tier',\n        label: 'Triage Severity',\n        type: 'category_weights',\n        description: 'Clinical priority classification',\n        params: {\n          categories: ['Routine', 'Elevated', 'Urgent', 'Critical'],\n          weights: [0.52, 0.28, 0.14, 0.06],\n        },\n      },\n      {\n        id: 'f-h-date',\n        name: 'admission_date',\n        label: 'Admission Date',\n        type: 'date_range',\n        description: 'Encounter timestamps',\n        params: { dateStart: '2024-06-01', dateEnd: '2025-08-20' },\n      },\n    ],\n  },\n  'fintech-fraud': {\n    id: 'fintech-fraud',\n    name: 'Fintech Fraud Detection & Risk Scoring',\n    description: 'Transaction monitoring telemetry for anti-fraud classifiers with skewed anomalous patterns.',\n    category: 'Fintech & Risk',\n    fields: [\n      {\n        id: 'f-ff-id',\n        name: 'tx_token',\n        label: 'Transaction ID',\n        type: 'name',\n        description: 'Cryptographic ledger token',\n        params: {},\n      },\n      {\n        id: 'f-ff-name',\n        name: 'merchant_name',\n        label: 'Merchant Name',\n        type: 'name',\n        description: 'Synthetic counterparty vendor',\n        params: {},\n      },\n      {\n        id: 'f-ff-email',\n        name: 'auth_email',\n        label: 'Auth Email',\n        type: 'email',\n        description: 'Tokenized transaction origin email',\n        params: { domain: 'syn-pay.net' },\n      },\n      {\n        id: 'f-ff-card',\n        name: 'card_pan',\n        label: 'Card PAN',\n        type: 'credit_card',\n        description: 'Tokenized 16-digit card surrogate',\n        params: {},\n      },\n      {\n        id: 'f-ff-amt',\n        name: 'settlement_usd',\n        label: 'Settlement ($)',\n        type: 'gaussian_float',\n        description: 'Transaction gross volume (μ=$1420.00, σ=$480.00)',\n        params: { mean: 1420.0, stdDev: 480.0, currency: '$' },\n      },\n      {\n        id: 'f-ff-verdict',\n        name: 'risk_verdict',\n        label: 'Risk Verdict',\n        type: 'category_weights',\n        description: 'Ground truth classifier target',\n        params: {\n          categories: ['Legitimate', 'Suspicious', 'Flagged_Fraud'],\n          weights: [0.89, 0.08, 0.03],\n        },\n      },\n      {\n        id: 'f-ff-ts',\n        name: 'settled_at',\n        label: 'Settlement Time',\n        type: 'date_range',\n        description: 'High-precision event timestamp',\n        params: { dateStart: '2025-01-01', dateEnd: '2025-08-20' },\n      },\n    ],\n  },\n  'saas-telemetry': {\n    id: 'saas-telemetry',\n    name: 'SaaS Product Telemetry & Usage Metrics',\n    description: 'B2B subscription telemetry with active seat distribution, recurring revenue, and subscription tiers.',\n    category: 'Cloud Software & B2B',\n    fields: [\n      {\n        id: 'f-st-id',\n        name: 'org_uuid',\n        label: 'Organization UUID',\n        type: 'name',\n        description: 'Tenant identifier',\n        params: {},\n      },\n      {\n        id: 'f-st-name',\n        name: 'tenant_name',\n        label: 'Tenant Name',\n        type: 'name',\n        description: 'Synthetic enterprise tenant moniker',\n        params: {},\n      },\n      {\n        id: 'f-st-email',\n        name: 'admin_email',\n        label: 'Admin Email',\n        type: 'email',\n        description: 'Primary owner email',\n        params: { domain: 'syn-saas.io' },\n      },\n      {\n        id: 'f-st-seats',\n        name: 'active_seats',\n        label: 'Active Seats',\n        type: 'gaussian_float',\n        description: 'Provisioned enterprise seats (μ=48.0, σ=18.0)',\n        params: { mean: 48.0, stdDev: 18.0, unit: 'seats' },\n      },\n      {\n        id: 'f-st-mrr',\n        name: 'mrr_usd',\n        label: 'Monthly Revenue',\n        type: 'gaussian_float',\n        description: 'MRR ledger (μ=$2850.00, σ=$950.00)',\n        params: { mean: 2850.0, stdDev: 950.0, currency: '$' },\n      },\n      {\n        id: 'f-st-plan',\n        name: 'plan_tier',\n        label: 'Plan Tier',\n        type: 'category_weights',\n        description: 'Subscription entitlement level',\n        params: {\n          categories: ['Starter', 'Growth', 'Enterprise'],\n          weights: [0.48, 0.36, 0.16],\n        },\n      },\n      {\n        id: 'f-st-date',\n        name: 'provisioned_at',\n        label: 'Provisioned Date',\n        type: 'date_range',\n        description: 'Cohort subscription activation',\n        params: { dateStart: '2024-01-01', dateEnd: '2025-08-20' },\n      },\n    ],\n  },\n}\n\nexport const FIRST_NAMES = [\n  'Elena',\n  'Malik',\n  'Siddharth',\n  'Claire',\n  'Liam',\n  'Amara',\n  'Kenji',\n  'Sofia',\n  'Devon',\n  'Maya',\n  'Tobias',\n  'Fatima',\n  'Mateo',\n  'Astrid',\n  'Zara',\n]\n\nexport const LAST_NAMES = [\n  'Rostova',\n  'Al-Mansoor',\n  'Patel',\n  'Chen',\n  \"O'Connor\",\n  'Okafor',\n  'Sato',\n  'Alvarez',\n  'Vance',\n  'Lin',\n  'Richter',\n  'Zahra',\n  'Silva',\n  'Lindqvist',\n  'Novak',\n]\n\nexport const CARD_BRANDS = ['Visa', 'Mastercard', 'Amex']\n\nexport function boxMullerRandom(\n  mean: number,\n  stdDev: number,\n  seedOffset: number,\n  seed: number,\n  noiseMechanism: boolean,\n  epsilon: number,\n): number {\n  const u1 = Math.max(1e-6, Math.abs(Math.sin(seed + seedOffset * 17.3)))\n  const u2 = Math.max(1e-6, Math.abs(Math.cos(seed + seedOffset * 31.7)))\n  const z0 = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2)\n\n  const dpNoiseScale = noiseMechanism ? stdDev / (Math.max(0.1, epsilon) * 12) : 0\n  const dpNoise = Math.sin(seed * 3.7 + seedOffset * 5.1) * dpNoiseScale\n\n  return mean + z0 * stdDev + dpNoise\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/synthetic-data-generator/synthetic-data-templates.ts"
    },
    {
      "path": "packages/registry-angular/blocks/synthetic-data-generator/index.ts",
      "content": "export {\n  UiSyntheticDataGeneratorComponent,\n  type FieldDataType,\n  type FidelityMetrics,\n  type GeneratedRecord,\n  type SchemaField,\n  type SyntheticDataGeneratorProps,\n  type TemplatePreset,\n} from './synthetic-data-generator.component'\nexport { UiSyntheticDataPreviewComponent, type SyntheticDataPreviewTab } from './synthetic-data-preview.component'\nexport { TEMPLATES, FIRST_NAMES, LAST_NAMES, CARD_BRANDS, boxMullerRandom } from './synthetic-data-templates'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/synthetic-data-generator/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/input.json",
    "https://uipkge.dev/r/angular/progress.json",
    "https://uipkge.dev/r/angular/select.json",
    "https://uipkge.dev/r/angular/separator.json",
    "https://uipkge.dev/r/angular/slider.json",
    "https://uipkge.dev/r/angular/switch.json",
    "https://uipkge.dev/r/angular/table.json",
    "https://uipkge.dev/r/angular/tabs.json"
  ],
  "description": "Gretel and Tonic-style LLM and mathematical synthetic dataset generator with differential privacy epsilon controls, custom schema field builders, K-anonymity policy toggles, live tabular CSV preview, and streaming JSON Lines export.",
  "categories": [
    "ai",
    "data",
    "app",
    "security"
  ]
}