admindb 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +368 -364
  2. package/dist/app.js +6 -9
  3. package/dist/auth/config.d.ts +1 -1
  4. package/dist/auth/config.js +18 -30
  5. package/dist/auth/crypto.js +11 -21
  6. package/dist/auth/middleware.js +9 -16
  7. package/dist/auth/routes.js +9 -15
  8. package/dist/cli/args.d.ts +1 -1
  9. package/dist/cli/args.js +15 -26
  10. package/dist/cli/config.js +6 -16
  11. package/dist/cli/runner.js +4 -7
  12. package/dist/data/chain.d.ts +1 -1
  13. package/dist/data/chain.js +40 -617
  14. package/dist/data/datasets.d.ts +1 -1
  15. package/dist/data/datasets.js +43 -1
  16. package/dist/data/detector.js +45 -21
  17. package/dist/data/engine.d.ts +1 -1
  18. package/dist/data/engine.js +483 -142
  19. package/dist/data/index.d.ts +1 -1
  20. package/dist/data/index.js +8 -0
  21. package/dist/data/prng.d.ts +1 -0
  22. package/dist/data/prng.js +73 -0
  23. package/dist/data/registry.d.ts +1 -0
  24. package/dist/data/registry.js +805 -0
  25. package/dist/data/schema-graph.d.ts +1 -0
  26. package/dist/data/schema-graph.js +210 -0
  27. package/dist/data/strategies.d.ts +1 -1
  28. package/dist/data/strategies.js +44 -283
  29. package/dist/data/templates.d.ts +1 -0
  30. package/dist/data/templates.js +134 -0
  31. package/dist/data/types.d.ts +1 -1
  32. package/dist/data/validator.d.ts +1 -0
  33. package/dist/data/validator.js +160 -0
  34. package/dist/db/database.js +9 -14
  35. package/dist/db/export.js +32 -55
  36. package/dist/db/filters.d.ts +1 -1
  37. package/dist/db/filters.js +15 -38
  38. package/dist/db/introspection.js +4 -7
  39. package/dist/db/manager.js +27 -51
  40. package/dist/db/migrations.js +7 -14
  41. package/dist/db/postgres.js +30 -78
  42. package/dist/index.d.ts +1 -1
  43. package/dist/public/css/app.css +1 -1
  44. package/dist/public/js/browse.js +1 -1
  45. package/dist/public/js/inspector.js +1 -1
  46. package/dist/public/js/query.js +1 -1
  47. package/dist/public/js/seed.js +1 -1
  48. package/dist/routes/api/erd.js +3 -8
  49. package/dist/routes/api/helpers.d.ts +1 -1
  50. package/dist/routes/api/helpers.js +75 -25
  51. package/dist/routes/api/import-export.js +1 -2
  52. package/dist/routes/api/query.js +2 -6
  53. package/dist/routes/api/rows.js +4 -8
  54. package/dist/routes/api/seed.js +35 -21
  55. package/dist/routes/api/tables.js +15 -30
  56. package/dist/routes/databases.js +8 -26
  57. package/dist/routes/pages.js +67 -18
  58. package/dist/serverless.js +15 -40
  59. package/dist/sql/generator.d.ts +1 -1
  60. package/dist/sql/generator.js +35 -109
  61. package/dist/types/api.d.ts +1 -1
  62. package/dist/utils/common.d.ts +1 -1
  63. package/dist/utils/common.js +45 -85
  64. package/dist/utils/csv.d.ts +1 -1
  65. package/dist/utils/csv.js +11 -17
  66. package/dist/utils/datatype.js +46 -60
  67. package/dist/utils/icons.js +8 -8
  68. package/dist/views/layouts/main.hbs +1 -87
  69. package/dist/views/pages/databases.hbs +1 -233
  70. package/dist/views/pages/designer.hbs +1 -89
  71. package/dist/views/pages/erd.hbs +1 -1241
  72. package/dist/views/pages/error.hbs +1 -10
  73. package/dist/views/pages/form.hbs +1 -59
  74. package/dist/views/pages/home.hbs +1 -220
  75. package/dist/views/pages/info.hbs +1 -107
  76. package/dist/views/pages/login.hbs +1 -98
  77. package/dist/views/pages/query.hbs +1 -129
  78. package/dist/views/pages/schema.hbs +1 -298
  79. package/dist/views/pages/seed-select.hbs +251 -0
  80. package/dist/views/pages/seed.hbs +256 -414
  81. package/dist/views/pages/table.hbs +1 -327
  82. package/dist/views/partials/navbar.hbs +1 -83
  83. package/dist/views/partials/sidebar.hbs +1 -129
  84. package/docs/API.md +149 -149
  85. package/docs/EXAMPLES.md +579 -579
  86. package/docs/SECURITY.md +265 -265
  87. package/package.json +79 -79
@@ -1,465 +1,307 @@
1
- <div class="max-w-5xl mx-auto space-y-8 pb-24">
2
- <div class="flex items-center gap-4">
3
- <a href="{{basePath}}/tables/{{table}}" class="btn btn-circle btn-ghost bg-base-200 hover:bg-base-300 transition-colors">
4
- {{{icon "arrowLeft" class="h-5 w-5"}}}
5
- </a>
6
- <div class="flex-1 min-w-0">
7
- <div class="flex items-center gap-3 flex-wrap">
8
- <div class="p-2 rounded-xl bg-gradient-to-br from-primary/20 to-secondary/20 text-primary shadow-soft">
9
- {{{icon "seed" class="h-6 w-6"}}}
10
- </div>
11
- <div>
12
- <h1 class="text-2xl font-bold font-mono tracking-tight">Seed Generator <span class="text-primary">{{table}}</span></h1>
13
- <p class="text-sm text-base-content/50 mt-0.5">Generate realistic mock data in three easy steps.</p>
1
+ <div class="space-y-4 pb-20">
2
+ <div class="app-card p-3.5 flex flex-wrap items-center justify-between gap-4">
3
+ <div class="flex items-center gap-3 min-w-0">
4
+ <a href="{{basePath}}/seed" class="btn btn-sm btn-ghost btn-square text-base-content/70 hover:text-base-content" title="Choose table & mode">
5
+ {{{icon "arrowLeft" class="h-4 w-4"}}}
6
+ </a>
7
+ <div class="h-4 w-px bg-base-300"></div>
8
+ <div class="flex items-center gap-2 flex-wrap">
9
+ <a href="{{basePath}}/seed" class="text-xs font-semibold text-base-content/50 hover:text-primary transition-colors">Seed Data</a>
10
+ <span class="text-xs text-base-content/30">/</span>
11
+ {{#if allTables.length}}
12
+ <div class="dropdown dropdown-bottom">
13
+ <label tabindex="0" class="btn btn-xs btn-ghost gap-1 font-mono font-bold text-xs" title="Switch table">
14
+ <span>{{table}}</span>
15
+ {{{icon "chevronDown" class="h-3 w-3 opacity-60"}}}
16
+ </label>
17
+ <ul tabindex="0" class="dropdown-content z-30 menu p-1 shadow-lg bg-base-100 border border-base-300 rounded-box w-52 max-h-60 overflow-y-auto">
18
+ {{#each allTables}}
19
+ <li>
20
+ <a href="{{../basePath}}/seed/{{this}}" class="{{#if (eq this ../table)}}active font-bold{{/if}} font-mono text-xs">
21
+ {{{icon "table" class="h-3.5 w-3.5"}}}
22
+ {{this}}
23
+ </a>
24
+ </li>
25
+ {{/each}}
26
+ </ul>
14
27
  </div>
28
+ {{else}}
29
+ <span class="font-mono font-bold text-sm text-base-content">{{table}}</span>
30
+ {{/if}}
31
+ <span class="badge badge-sm badge-ghost font-mono text-[11px] text-base-content/60">{{colCount}} cols</span>
32
+ {{#if dialect}}
33
+ <span class="badge badge-sm badge-outline font-mono text-[10px] uppercase text-base-content/50">{{dialect}}</span>
34
+ {{/if}}
15
35
  </div>
16
36
  </div>
17
- </div>
18
- <div class="app-card p-4">
19
- <div class="flex items-center justify-between relative">
20
- <div class="absolute left-0 right-0 top-1/2 -translate-y-1/2 h-0.5 bg-base-300 mx-16 z-0">
21
- <div id="wizard-progress-bar" class="h-full bg-gradient-to-r from-primary to-secondary transition-all duration-500 ease-out" style="width: 0%"></div>
22
- </div>
23
- <div id="step-nav-1" class="seed-step relative z-10 flex flex-col items-center gap-2 cursor-pointer group">
24
- <div class="seed-step-circle w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold border-2 transition-all duration-300 bg-primary border-primary text-primary-content shadow-[0_0_12px_rgba(109,93,246,0.4)]">
25
- <span class="seed-step-num">1</span>
26
- <span class="seed-step-check hidden">{{{icon "check" class="h-4 w-4"}}}</span>
27
- </div>
28
- <span class="text-xs font-semibold text-primary">Choose Mode</span>
37
+ <div class="flex items-center gap-2.5 flex-wrap">
38
+ <div class="seed-mode-segment">
39
+ <button type="button" id="btn-select-single" class="seed-mode-btn {{#unless isChainMode}}active{{/unless}}">
40
+ {{{icon "table" class="h-3.5 w-3.5"}}}
41
+ <span>Single Table</span>
42
+ </button>
43
+ <button type="button" id="btn-select-chain" class="seed-mode-btn {{#if isChainMode}}active{{/if}}">
44
+ {{{icon "diagram" class="h-3.5 w-3.5"}}}
45
+ <span>ER Chain</span>
46
+ </button>
29
47
  </div>
30
- <div id="step-nav-2" class="seed-step relative z-10 flex flex-col items-center gap-2 cursor-pointer group">
31
- <div class="seed-step-circle w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold border-2 transition-all duration-300 bg-base-200 border-base-300 text-base-content/40">
32
- <span class="seed-step-num">2</span>
33
- <span class="seed-step-check hidden">{{{icon "check" class="h-4 w-4"}}}</span>
48
+ <div class="h-4 w-px bg-base-300 hidden sm:block"></div>
49
+ <div class="flex items-center gap-1.5" title="Numeric seed for deterministic generation">
50
+ <div class="relative">
51
+ <input id="plan-seed-input" type="number" placeholder="Seed (auto)"
52
+ class="field-input w-28 font-mono text-xs !py-1 pl-2.5 pr-6" />
53
+ <span class="absolute right-2 top-1/2 -translate-y-1/2 text-[10px] font-mono text-base-content/40">#</span>
34
54
  </div>
35
- <span class="text-xs font-semibold text-base-content/40">Configure</span>
55
+ <button type="button" id="btn-random-seed" class="btn btn-sm btn-ghost btn-square text-base-content/70 hover:text-base-content" title="Roll random seed">
56
+ {{{icon "refresh" class="h-3.5 w-3.5"}}}
57
+ </button>
36
58
  </div>
37
- <div id="step-nav-3" class="seed-step relative z-10 flex flex-col items-center gap-2 cursor-pointer group">
38
- <div class="seed-step-circle w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold border-2 transition-all duration-300 bg-base-200 border-base-300 text-base-content/40">
39
- <span class="seed-step-num">3</span>
40
- <span class="seed-step-check hidden">{{{icon "check" class="h-4 w-4"}}}</span>
41
- </div>
42
- <span class="text-xs font-semibold text-base-content/40">Preview & Run</span>
59
+ <div class="h-4 w-px bg-base-300 hidden sm:block"></div>
60
+ <div class="flex items-center gap-1">
61
+ <button type="button" id="btn-profiles-modal" class="btn btn-sm btn-ghost gap-1.5 text-xs text-base-content/80 hover:text-base-content" title="Manage seed profiles">
62
+ {{{icon "book" class="h-3.5 w-3.5 text-primary"}}}
63
+ <span class="hidden md:inline">Profiles</span>
64
+ </button>
65
+ <button type="button" id="btn-export-plan-json" class="btn btn-sm btn-ghost btn-square text-base-content/70 hover:text-base-content" title="Export generation plan JSON">
66
+ {{{icon "download" class="h-3.5 w-3.5"}}}
67
+ </button>
68
+ <label for="input-import-plan-json" class="btn btn-sm btn-ghost btn-square text-base-content/70 hover:text-base-content cursor-pointer" title="Import generation plan JSON">
69
+ {{{icon "upload" class="h-3.5 w-3.5"}}}
70
+ <input type="file" id="input-import-plan-json" accept=".json" class="hidden" />
71
+ </label>
43
72
  </div>
73
+ <div class="h-4 w-px bg-base-300 hidden sm:block"></div>
74
+ <button id="seed-insert" type="button" class="btn btn-primary btn-sm gap-2 font-bold px-4 shadow-sm">
75
+ {{{icon "play" class="h-3.5 w-3.5"}}}
76
+ <span>Execute Seed</span>
77
+ </button>
44
78
  </div>
45
79
  </div>
46
- <div id="step-content-1" class="space-y-6">
47
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
48
- <button id="btn-select-single" class="seed-mode-card app-card p-6 text-left group transition-all duration-200 border-2 border-transparent hover:border-primary/40 hover:-translate-y-0.5">
49
- <div class="flex items-start gap-4 mb-5">
50
- <div class="relative shrink-0">
51
- <div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-primary/20 to-primary/5 flex items-center justify-center text-primary group-hover:scale-110 transition-transform duration-200 shadow-soft">
52
- {{{icon "table" class="h-7 w-7"}}}
80
+ <div id="step-content-1" class="hidden">
81
+ <button id="btn-next-1" class="hidden"></button>
82
+ </div>
83
+ <div id="step-content-2" class="contents">
84
+ <button id="btn-prev-2" class="hidden"></button>
85
+ <button id="btn-next-2" class="hidden"></button>
86
+ </div>
87
+ <div id="step-content-3" class="contents">
88
+ <button id="btn-prev-3" class="hidden"></button>
89
+ </div>
90
+ <div id="step-nav-1" class="hidden"><div class="seed-step-circle"><span class="seed-step-num"></span><span class="seed-step-check"></span></div></div>
91
+ <div id="step-nav-2" class="hidden"><div class="seed-step-circle"><span class="seed-step-num"></span><span class="seed-step-check"></span></div></div>
92
+ <div id="step-nav-3" class="hidden"><div class="seed-step-circle"><span class="seed-step-num"></span><span class="seed-step-check"></span></div></div>
93
+ <div id="wizard-progress-bar" class="hidden"></div>
94
+ <div class="grid grid-cols-1 xl:grid-cols-12 gap-4 items-start">
95
+ <div class="xl:col-span-7 space-y-4">
96
+ <div id="config-single" class="{{#if isChainMode}}hidden{{/if}} space-y-4">
97
+ <div class="app-card p-3.5 flex flex-wrap items-center justify-between gap-4">
98
+ <div class="flex items-center gap-3">
99
+ <div class="flex items-center">
100
+ <button type="button" id="seed-count-dec" class="btn btn-xs btn-outline rounded-r-none border-r-0 px-2.5 font-mono text-sm hover:bg-primary hover:text-primary-content hover:border-primary">−</button>
101
+ <input id="seed-count" type="number" value="10" min="1" max="{{maxRows}}"
102
+ class="field-input !rounded-none border-x-0 !py-0.5 font-mono w-20 font-bold text-xs text-center [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" />
103
+ <button type="button" id="seed-count-inc" class="btn btn-xs btn-outline rounded-l-none border-l-0 px-2.5 font-mono text-sm hover:bg-primary hover:text-primary-content hover:border-primary">+</button>
104
+ </div>
105
+ <span class="text-[11px] uppercase font-bold text-base-content/50">Rows</span>
106
+ <div class="h-4 w-px bg-base-200 hidden sm:block"></div>
107
+ <div class="flex gap-1 flex-wrap">
108
+ {{#each quickCounts}}
109
+ <button type="button" class="preset-pill btn btn-xs btn-ghost border border-base-300 font-mono text-[11px] px-2" data-count="{{this}}">{{this}}</button>
110
+ {{/each}}
53
111
  </div>
54
112
  </div>
55
- <div class="min-w-0">
56
- <h3 class="text-lg font-bold leading-tight">Single Table</h3>
57
- <p class="text-xs text-base-content/50 mt-1">Generate data for this table only</p>
58
- </div>
59
- <div class="ml-auto shrink-0 w-5 h-5 rounded-full border-2 border-base-300 transition-all duration-200 seed-mode-radio flex items-center justify-center">
60
- <div class="w-2.5 h-2.5 rounded-full bg-primary scale-0 transition-transform duration-200 seed-mode-dot"></div>
113
+ <div class="flex items-center gap-2 w-full sm:w-auto">
114
+ <input type="text" id="seed-col-search" placeholder="Filter columns…" class="field-input text-xs !py-1 w-full sm:w-48" />
115
+ <span id="col-count-badge" class="badge badge-sm badge-ghost font-mono text-[11px] shrink-0">{{colCount}} cols</span>
61
116
  </div>
62
117
  </div>
63
- <p class="text-sm text-base-content/60 leading-relaxed mb-5">
64
- Manually control mock values for each column with full strategy control.
65
- </p>
66
- <ul class="space-y-1.5">
67
- <li class="flex items-center gap-2 text-xs text-base-content/60">
68
- <span class="w-4 h-4 rounded-full bg-success/15 text-success flex items-center justify-center shrink-0">{{{icon "check" class="h-2.5 w-2.5"}}}</span>
69
- Manual column strategies (fixed, list, pattern, etc.)
70
- </li>
71
- <li class="flex items-center gap-2 text-xs text-base-content/60">
72
- <span class="w-4 h-4 rounded-full bg-success/15 text-success flex items-center justify-center shrink-0">{{{icon "check" class="h-2.5 w-2.5"}}}</span>
73
- Live sample value preview per column
74
- </li>
75
- <li class="flex items-center gap-2 text-xs text-base-content/60">
76
- <span class="w-4 h-4 rounded-full bg-success/15 text-success flex items-center justify-center shrink-0">{{{icon "check" class="h-2.5 w-2.5"}}}</span>
77
- Up to 5,000 rows in one pass
78
- </li>
79
- </ul>
80
- </button>
81
- <button id="btn-select-chain" class="seed-mode-card app-card p-6 text-left group transition-all duration-200 border-2 border-transparent hover:border-secondary/40 hover:-translate-y-0.5">
82
- <div class="flex items-start gap-4 mb-5">
83
- <div class="relative shrink-0">
84
- <div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-secondary/20 to-secondary/5 flex items-center justify-center text-secondary group-hover:scale-110 transition-transform duration-200 shadow-soft">
85
- {{{icon "diagram" class="h-7 w-7"}}}
86
- </div>
87
- </div>
88
- <div class="min-w-0">
89
- <h3 class="text-lg font-bold leading-tight">ER Relational Chain</h3>
90
- <p class="text-xs text-base-content/50 mt-1">Synchronized relational seeding</p>
118
+ <div class="app-card p-4 space-y-3">
119
+ <div class="flex items-center justify-between pb-2 border-b border-base-200">
120
+ <span class="text-xs font-bold uppercase tracking-wider text-base-content/70">Columns Specification</span>
121
+ <span class="text-[11px] text-base-content/40">Select generator strategy & customize parameters per column</span>
91
122
  </div>
92
- <div class="ml-auto shrink-0 w-5 h-5 rounded-full border-2 border-base-300 transition-all duration-200 seed-mode-radio flex items-center justify-center">
93
- <div class="w-2.5 h-2.5 rounded-full bg-secondary scale-0 transition-transform duration-200 seed-mode-dot"></div>
123
+ <div id="seed-columns" class="space-y-2.5">
94
124
  </div>
95
125
  </div>
96
- <p class="text-sm text-base-content/60 leading-relaxed mb-5">
97
- Builds a dependency graph and inserts parent records before children to prevent FK errors.
98
- </p>
99
- <ul class="space-y-1.5">
100
- <li class="flex items-center gap-2 text-xs text-base-content/60">
101
- <span class="w-4 h-4 rounded-full bg-success/15 text-success flex items-center justify-center shrink-0">{{{icon "check" class="h-2.5 w-2.5"}}}</span>
102
- Auto FK dependency resolution
103
- </li>
104
- <li class="flex items-center gap-2 text-xs text-base-content/60">
105
- <span class="w-4 h-4 rounded-full bg-success/15 text-success flex items-center justify-center shrink-0">{{{icon "check" class="h-2.5 w-2.5"}}}</span>
106
- Topologically-ordered execution pipeline
107
- </li>
108
- <li class="flex items-center gap-2 text-xs text-base-content/60">
109
- <span class="w-4 h-4 rounded-full bg-success/15 text-success flex items-center justify-center shrink-0">{{{icon "check" class="h-2.5 w-2.5"}}}</span>
110
- Configurable per-table row counts
111
- </li>
112
- </ul>
113
- </button>
114
- </div>
115
- <div class="flex justify-end pt-2">
116
- <button id="btn-next-1" class="btn btn-primary gap-2 px-8 transition-all duration-200" disabled>
117
- Next Step
118
- {{{icon "arrowRight" class="h-4 w-4"}}}
119
- </button>
120
- </div>
121
- </div>
122
- <div id="step-content-2" class="hidden space-y-6">
123
- <div id="config-single" class="hidden space-y-5">
124
- <div class="app-card p-5">
125
- <div class="flex flex-wrap gap-6 items-end justify-between">
126
- <div class="flex items-end gap-4">
127
- <div>
128
- <label class="block text-xs font-bold uppercase tracking-wider text-base-content/50 mb-2">Rows to Generate</label>
129
- <div class="flex items-center gap-0">
130
- <button type="button" id="seed-count-dec" class="btn btn-sm btn-outline rounded-r-none border-r-0 px-3 font-mono text-lg leading-none hover:bg-primary hover:text-primary-content hover:border-primary transition-colors">−</button>
131
- <input id="seed-count" type="number" value="10" min="1" max="5000"
132
- class="field-input !rounded-none border-x-0 !py-1.5 font-mono w-24 font-bold text-lg text-center [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" />
133
- <button type="button" id="seed-count-inc" class="btn btn-sm btn-outline rounded-l-none border-l-0 px-3 font-mono text-lg leading-none hover:bg-primary hover:text-primary-content hover:border-primary transition-colors">+</button>
134
- </div>
135
- </div>
126
+ </div>
127
+ <div id="config-chain" class="{{#unless isChainMode}}hidden{{/unless}} space-y-4">
128
+ <div class="app-card p-3.5 flex flex-wrap items-center justify-between gap-4">
129
+ <div class="flex items-center gap-3">
130
+ <label class="text-xs font-bold text-base-content/70">Graph Scope:</label>
131
+ <select id="chain-scope" class="field-select !py-1 text-xs font-semibold w-52">
132
+ <option value="chain">Connected Relational Chain</option>
133
+ <option value="ancestors">Upstream Parents Only</option>
134
+ <option value="descendants">Downstream Children Only</option>
135
+ <option value="all">Entire Database (All Tables)</option>
136
+ </select>
136
137
  </div>
137
- <div class="flex items-end gap-3">
138
- <div>
139
- <span class="block text-xs font-bold uppercase tracking-wider text-base-content/50 mb-2">Quick Presets</span>
140
- <div class="flex gap-2">
141
- <button type="button" class="preset-pill btn btn-sm btn-outline rounded-full gap-1.5" id="preset-realistic" data-count="50">
142
- <span class="w-1.5 h-1.5 rounded-full bg-success"></span>
143
- Realistic
144
- </button>
145
- <button type="button" class="preset-pill btn btn-sm btn-outline rounded-full gap-1.5" id="preset-minimal" data-count="5">
146
- <span class="w-1.5 h-1.5 rounded-full bg-info"></span>
147
- Minimal
148
- </button>
149
- </div>
138
+ <div class="flex items-center gap-2">
139
+ <span class="text-xs font-bold text-base-content/70">Scale:</span>
140
+ <div class="join">
141
+ <button type="button" class="chain-scale-btn btn btn-xs join-item font-mono" data-scale="0.5">0.5×</button>
142
+ <button type="button" class="chain-scale-btn btn btn-xs join-item btn-neutral font-mono" data-scale="1">1×</button>
143
+ <button type="button" class="chain-scale-btn btn btn-xs join-item font-mono" data-scale="2">2×</button>
144
+ <button type="button" class="chain-scale-btn btn btn-xs join-item font-mono" data-scale="5">5×</button>
150
145
  </div>
146
+ <span id="chain-summary-badge" class="badge badge-sm badge-primary font-mono font-bold ml-2">0 Tables</span>
151
147
  </div>
152
148
  </div>
153
- </div>
154
- <div class="app-card p-6">
155
- <div class="flex items-center justify-between mb-5">
156
- <h3 class="text-base font-bold">Column Strategies</h3>
157
- <span class="badge badge-ghost text-xs">Configure per column</span>
158
- </div>
159
- <div id="seed-columns" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
149
+ <div class="app-card p-4 space-y-3">
150
+ <div class="flex items-center justify-between pb-2 border-b border-base-200">
151
+ <span class="text-xs font-bold uppercase tracking-wider text-base-content/70">Topological Execution Pipeline</span>
152
+ <span class="text-[11px] text-base-content/40">Dependencies ordered upstream-first</span>
153
+ </div>
154
+ <div id="chain-pipeline-container" class="space-y-2.5">
155
+ </div>
160
156
  </div>
161
157
  </div>
158
+ <details class="app-card group">
159
+ <summary class="p-3.5 flex items-center justify-between cursor-pointer list-none select-none text-xs font-bold text-base-content/70 hover:text-base-content">
160
+ <div class="flex items-center gap-2">
161
+ {{{icon "settings" class="h-3.5 w-3.5"}}}
162
+ <span>Advanced Execution Options</span>
163
+ </div>
164
+ <span class="text-xs text-base-content/40 group-open:rotate-180 transition-transform">▼</span>
165
+ </summary>
166
+ <div class="p-3.5 pt-0 border-t border-base-200/80 mt-1 flex flex-wrap items-center gap-6 text-xs">
167
+ <label class="cursor-pointer flex items-center gap-2 select-none group">
168
+ <input id="plan-transaction-opt" type="checkbox" checked class="checkbox checkbox-primary checkbox-xs" />
169
+ <span class="font-medium text-base-content/80 group-hover:text-base-content">Wrap in Transaction (BEGIN ... COMMIT)</span>
170
+ </label>
171
+ <label class="cursor-pointer flex items-center gap-2 select-none group">
172
+ <input id="plan-rollback-opt" type="checkbox" checked class="checkbox checkbox-primary checkbox-xs" />
173
+ <span class="font-medium text-base-content/80 group-hover:text-base-content">Atomic Rollback on Error</span>
174
+ </label>
175
+ <label class="cursor-pointer flex items-center gap-2 select-none group">
176
+ <input id="seed-truncate" type="checkbox" class="checkbox checkbox-error checkbox-xs" />
177
+ <span class="font-medium text-error/90 group-hover:text-error">Clear tables before inserting (Truncate)</span>
178
+ </label>
179
+ </div>
180
+ </details>
162
181
  </div>
163
- <div id="config-chain" class="hidden space-y-5">
164
- <div class="app-card p-5">
165
- <div class="flex flex-wrap gap-6 items-end justify-between">
166
- <div>
167
- <label class="block text-xs font-bold uppercase tracking-wider text-base-content/50 mb-2">Scope</label>
168
- <select id="chain-scope" class="field-select !py-1.5 w-64 font-medium">
169
- <option value="chain">Full Connected Chain</option>
170
- <option value="ancestors">Parents Only</option>
171
- <option value="descendants">Children Only</option>
172
- </select>
182
+ <div class="xl:col-span-5 space-y-4 xl:sticky xl:top-4">
183
+ <div id="validation-report-card" class="app-card p-3.5 space-y-2">
184
+ <div class="flex items-center justify-between">
185
+ <div id="validation-summary-pill" class="flex items-center gap-2">
186
+ <span id="validation-status-dot" class="w-2 h-2 rounded-full bg-success"></span>
187
+ <span id="validation-status-text" class="text-xs font-mono font-bold text-base-content">Plan Ready</span>
173
188
  </div>
174
- <div>
175
- <span class="block text-xs font-bold uppercase tracking-wider text-base-content/50 mb-2">Scale Multiplier</span>
176
- <div class="join shadow-soft">
177
- <button type="button" class="chain-scale-btn btn btn-sm join-item font-mono" data-scale="0.5">0.5×</button>
178
- <button type="button" class="chain-scale-btn btn btn-sm join-item btn-neutral font-mono" data-scale="1">1×</button>
179
- <button type="button" class="chain-scale-btn btn btn-sm join-item font-mono" data-scale="2">2×</button>
180
- </div>
189
+ <span id="validation-summary-chip" class="badge badge-sm badge-success font-mono text-[10px] font-bold">All Checks Passed</span>
190
+ </div>
191
+ <div id="validation-issues-list" class="space-y-1.5 text-xs">
192
+ <div class="flex items-center gap-2 text-success font-medium text-[11px]">
193
+ {{{icon "check" class="h-3.5 w-3.5 shrink-0"}}}
194
+ <span>Primary key & constraint strategies valid</span>
181
195
  </div>
182
196
  </div>
183
197
  </div>
184
- <div class="app-card p-6">
185
- <div class="flex items-center justify-between mb-5 pb-4 border-b border-base-200">
186
- <div>
187
- <h3 class="text-base font-bold">Execution Pipeline</h3>
188
- <p class="text-xs text-base-content/50 mt-0.5">Tables are inserted in topological order</p>
198
+ <div class="app-card overflow-hidden">
199
+ <div class="flex items-center justify-between p-3 border-b border-base-200 bg-base-100 gap-2 flex-wrap">
200
+ <div class="join">
201
+ <button type="button" id="preview-tab-grid" class="btn btn-xs join-item btn-active font-bold">Table Preview</button>
202
+ <button type="button" id="preview-tab-sql" class="btn btn-xs join-item font-bold">SQL Script</button>
189
203
  </div>
190
- <div class="flex items-center gap-2">
191
- <div class="w-2 h-2 rounded-full bg-success animate-pulse"></div>
192
- <span id="chain-summary-badge" class="badge badge-primary font-bold">0 Tables</span>
204
+ <button id="seed-refresh-preview" type="button" class="btn btn-xs btn-ghost gap-1 text-xs text-base-content/70 hover:text-primary">
205
+ {{{icon "refresh" class="h-3 w-3"}}}
206
+ <span>Refresh</span>
207
+ </button>
208
+ </div>
209
+ <div id="seed-chain-table-tabs" class="tabs tabs-bordered flex-nowrap w-full overflow-x-auto hidden px-3 py-1 bg-base-200/40 border-b border-base-200 text-xs"></div>
210
+ <div id="preview-view-grid">
211
+ <div class="overflow-x-auto max-h-[440px]">
212
+ <table class="table table-zebra table-pin-rows table-xs w-full">
213
+ <thead id="seed-preview-thead" class="bg-base-200/90 text-[10px] uppercase font-mono tracking-wider text-base-content/70 backdrop-blur sticky top-0 z-10"></thead>
214
+ <tbody id="seed-preview-tbody" class="font-mono text-[11px]">
215
+ <tr>
216
+ <td colspan="100" class="text-center py-12 opacity-50">
217
+ <div class="flex flex-col items-center gap-2">
218
+ <span class="loading loading-spinner loading-sm text-primary"></span>
219
+ <span class="text-xs">Generating preview…</span>
220
+ </div>
221
+ </td>
222
+ </tr>
223
+ </tbody>
224
+ </table>
193
225
  </div>
194
226
  </div>
195
- <div id="chain-pipeline-container" class="space-y-3">
227
+ <div id="preview-view-sql" class="hidden p-3 bg-base-200/20 space-y-2">
228
+ <div class="flex items-center justify-between">
229
+ <span class="text-[11px] font-mono text-base-content/50 uppercase font-bold">Generated SQL (Preview)</span>
230
+ <button type="button" id="btn-copy-sql" class="btn btn-xs btn-outline gap-1 text-[11px]">
231
+ {{{icon "copy" class="h-3 w-3"}}}
232
+ <span>Copy SQL</span>
233
+ </button>
234
+ </div>
235
+ <pre id="preview-sql-code" class="bg-neutral text-neutral-content p-3.5 rounded-lg font-mono text-xs overflow-x-auto max-h-[380px] border border-base-300 leading-relaxed"></pre>
196
236
  </div>
197
237
  </div>
198
- </div>
199
- <div class="flex justify-between pt-2">
200
- <button id="btn-prev-2" type="button" class="btn btn-ghost gap-2 px-6">
201
- {{{icon "arrowLeft" class="h-4 w-4"}}}
202
- Back
203
- </button>
204
- <button id="btn-next-2" type="button" class="btn btn-primary gap-2 px-8">
205
- Preview Data
206
- {{{icon "arrowRight" class="h-4 w-4"}}}
207
- </button>
238
+ <div id="seed-truncate-banner" class="hidden p-3 rounded-lg bg-error/10 border border-error/20 flex items-center gap-2.5">
239
+ <span class="text-error shrink-0">{{{icon "warning" class="h-4 w-4"}}}</span>
240
+ <p class="text-xs font-bold text-error">
241
+ Destructive Action: Existing table rows will be truncated before seeding.
242
+ </p>
243
+ </div>
244
+ <div id="seed-execute-bar" class="hidden"></div>
245
+ <div id="seed-feedback" class="hidden"></div>
208
246
  </div>
209
247
  </div>
210
- <div id="step-content-3" class="hidden space-y-5">
211
- <div class="app-card overflow-hidden">
212
- <div class="flex items-center justify-between px-6 pt-5 pb-4 border-b border-base-200">
213
- <div>
214
- <h3 class="text-base font-bold">Data Preview</h3>
215
- <p class="text-xs text-base-content/50 mt-0.5">Sample of what will be inserted</p>
216
- </div>
217
- <button id="seed-refresh-preview" type="button" class="btn btn-sm btn-ghost gap-1.5 hover:bg-primary/10 hover:text-primary transition-colors">
218
- {{{icon "refresh" class="h-4 w-4"}}}
219
- Regenerate
220
- </button>
221
- </div>
222
- <div class="w-full overflow-x-auto border-b border-base-200 bg-base-200/30">
223
- <div id="seed-chain-table-tabs" class="tabs tabs-bordered flex-nowrap w-max min-w-full hidden px-4"></div>
224
- </div>
225
- <div class="px-4 pt-3 pb-1 flex items-center justify-between">
226
- <span id="seed-row-count-badge" class="text-xs text-base-content/40 font-mono"></span>
227
- </div>
228
- <div class="overflow-x-auto max-h-[460px] shadow-inner">
229
- <table class="table table-zebra table-pin-rows table-sm w-full">
230
- <thead id="seed-preview-thead" class="bg-base-200/80 text-[10px] uppercase tracking-widest font-bold text-base-content/60 backdrop-blur sticky top-0 z-10"></thead>
231
- <tbody id="seed-preview-tbody" class="font-mono text-[11px] leading-relaxed">
232
- <tr>
233
- <td colspan="100" class="text-center py-16 opacity-40">
234
- <div class="flex flex-col items-center gap-3">
235
- <span class="loading loading-spinner loading-md"></span>
236
- <span class="text-xs">Generating preview…</span>
237
- </div>
238
- </td>
239
- </tr>
240
- </tbody>
241
- </table>
248
+ </div>
249
+ <dialog id="seed-profiles-dialog" class="modal">
250
+ <div class="modal-box p-0 overflow-hidden max-w-lg rounded-2xl border border-base-200 shadow-xl">
251
+ <div class="px-5 py-4 bg-base-200/50 border-b border-base-200 flex items-center justify-between">
252
+ <div class="flex items-center gap-2.5">
253
+ {{{icon "book" class="h-4 w-4 text-primary"}}}
254
+ <h3 class="font-bold text-sm text-base-content">Seed Profiles & Presets</h3>
242
255
  </div>
256
+ <button type="button" class="btn btn-xs btn-circle btn-ghost" id="btn-close-profiles">✕</button>
243
257
  </div>
244
- <div id="seed-execute-bar" class="app-card overflow-hidden transition-all duration-300">
245
- <div id="seed-truncate-banner" class="hidden px-6 py-3 bg-error/10 border-b border-error/20 flex items-center gap-3">
246
- <span class="text-error shrink-0">{{{icon "warning" class="h-4 w-4"}}}</span>
247
- <p class="text-xs font-semibold text-error/80">
248
- <strong>Destructive action:</strong> Existing rows will be permanently deleted before new data is inserted.
249
- </p>
258
+ <div class="p-5 space-y-4 text-xs">
259
+ <div>
260
+ <h4 class="font-bold text-[10px] uppercase tracking-wider text-base-content/50 mb-2">Quick Blueprints</h4>
261
+ <div class="grid grid-cols-3 gap-2">
262
+ <button type="button" class="btn-preset-profile btn btn-xs btn-outline font-mono" data-preset="smoke">⚡ Smoke (10)</button>
263
+ <button type="button" class="btn-preset-profile btn btn-xs btn-outline font-mono" data-preset="realistic">🌱 Standard (100)</button>
264
+ <button type="button" class="btn-preset-profile btn btn-xs btn-outline font-mono" data-preset="stress">🚀 Stress (1000)</button>
265
+ </div>
250
266
  </div>
251
- <div class="flex flex-wrap items-center justify-between gap-4 p-5">
252
- <button id="btn-prev-3" type="button" class="btn btn-ghost gap-2 px-5">
253
- {{{icon "arrowLeft" class="h-4 w-4"}}}
254
- Back to Config
255
- </button>
256
- <div class="flex items-center gap-5">
257
- <label class="cursor-pointer flex items-center gap-2.5 group select-none" for="seed-truncate">
258
- <input id="seed-truncate" type="checkbox" class="checkbox checkbox-error checkbox-sm" />
259
- <span class="text-sm font-semibold text-base-content/70 group-hover:text-error transition-colors">
260
- Clear tables first
261
- </span>
262
- </label>
263
- <button id="seed-insert" type="button"
264
- class="btn btn-primary btn-lg gap-2 px-10 shadow-lg hover:shadow-primary/30 hover:shadow-xl transition-all duration-200 hover:scale-[1.02] active:scale-100">
265
- {{{icon "play" class="h-5 w-5"}}}
266
- Execute Seed
267
- </button>
267
+ <div class="p-3 rounded-lg bg-base-200/40 border border-base-300 space-y-2">
268
+ <h4 class="font-bold text-[10px] uppercase tracking-wider text-base-content/60">Save Current Configuration</h4>
269
+ <div class="flex gap-2">
270
+ <input id="profile-name-input" type="text" placeholder="Profile name (e.g. Test Dataset)"
271
+ class="field-input flex-1 text-xs !py-1 font-medium" />
272
+ <button type="button" id="btn-save-profile" class="btn btn-xs btn-primary px-3 font-bold">Save</button>
273
+ </div>
274
+ </div>
275
+ <div>
276
+ <h4 class="font-bold text-[10px] uppercase tracking-wider text-base-content/50 mb-1.5">Saved Profiles</h4>
277
+ <div id="profiles-list-container" class="space-y-1.5 max-h-48 overflow-y-auto pr-1">
278
+ <div class="py-4 text-center text-xs text-base-content/40">No saved profiles yet.</div>
268
279
  </div>
269
280
  </div>
270
281
  </div>
271
- <div id="seed-feedback" class="hidden"></div>
272
282
  </div>
273
- </div>
283
+ <form method="dialog" class="modal-backdrop bg-base-300/60 backdrop-blur-sm"><button>close</button></form>
284
+ </dialog>
274
285
  <dialog id="seed-truncate-dialog" class="modal">
275
- <div class="modal-box p-0 overflow-hidden max-w-md">
276
- <div class="px-6 pt-6 pb-4 bg-error/5 border-b border-error/15">
277
- <div class="flex items-center gap-3">
278
- <div class="w-10 h-10 rounded-xl bg-error/15 flex items-center justify-center text-error shrink-0">
279
- {{{icon "warning" class="h-5 w-5"}}}
280
- </div>
281
- <div>
282
- <h3 class="font-bold text-lg text-error leading-tight">Confirm Destructive Action</h3>
283
- <p class="text-xs text-base-content/50 mt-0.5">This operation cannot be undone</p>
284
- </div>
285
- </div>
286
+ <div class="modal-box p-0 overflow-hidden max-w-md rounded-2xl border border-error/20 shadow-xl">
287
+ <div class="px-5 py-4 bg-error/5 border-b border-error/15 flex items-center gap-2.5">
288
+ <span class="text-error">{{{icon "warning" class="h-5 w-5"}}}</span>
289
+ <h3 class="font-bold text-sm text-error">Confirm Truncate Action</h3>
286
290
  </div>
287
- <div class="px-6 py-5 space-y-4">
288
- <p class="text-sm text-base-content/80 leading-relaxed">
289
- You have selected to <strong class="text-error">clear the tables</strong> before generating mock data.
290
- This will <strong>permanently delete all existing rows</strong> in the affected tables.
291
+ <div class="p-5 space-y-3 text-xs text-base-content/80 leading-relaxed">
292
+ <p>
293
+ You have enabled <strong class="text-error">Clear tables before inserting</strong>. All existing records in affected tables will be permanently deleted in reverse topological order before new rows are inserted.
291
294
  </p>
292
- <div class="rounded-xl bg-base-200 border border-base-300 p-4">
293
- <p class="text-xs font-bold uppercase tracking-wider text-base-content/40 mb-2">What will happen</p>
294
- <ul class="space-y-1.5">
295
- <li class="flex items-center gap-2 text-xs text-base-content/70">
296
- <span class="text-error">{{{icon "warning" class="h-3.5 w-3.5"}}}</span>
297
- All existing rows will be truncated
298
- </li>
299
- <li class="flex items-center gap-2 text-xs text-base-content/70">
300
- <span class="text-primary">{{{icon "play" class="h-3.5 w-3.5"}}}</span>
301
- New mock data will be inserted
302
- </li>
303
- <li class="flex items-center gap-2 text-xs text-base-content/70">
304
- <span class="text-error">{{{icon "warning" class="h-3.5 w-3.5"}}}</span>
305
- This action is irreversible
306
- </li>
307
- </ul>
308
- </div>
309
295
  </div>
310
- <div class="px-6 pb-6 flex items-center justify-end gap-3">
311
- <button type="button" class="btn btn-ghost" id="seed-truncate-cancel">Cancel</button>
312
- <button type="button" class="btn btn-error gap-2" id="seed-truncate-confirm">
313
- {{{icon "warning" class="h-4 w-4"}}}
314
- Yes, Delete & Seed
296
+ <div class="px-5 pb-4 flex items-center justify-end gap-2">
297
+ <button type="button" class="btn btn-ghost btn-xs" id="seed-truncate-cancel">Cancel</button>
298
+ <button type="button" class="btn btn-error btn-xs font-bold gap-1.5" id="seed-truncate-confirm">
299
+ {{{icon "warning" class="h-3.5 w-3.5"}}}
300
+ <span>Yes, Truncate & Seed</span>
315
301
  </button>
316
302
  </div>
317
303
  </div>
318
- <form method="dialog" class="modal-backdrop bg-base-300/80 backdrop-blur-sm"><button>close</button></form>
304
+ <form method="dialog" class="modal-backdrop bg-base-300/60 backdrop-blur-sm"><button>close</button></form>
319
305
  </dialog>
320
306
  <script id="seed-config" type="application/json">{{{json seedConfig}}}</script>
321
307
  <script src="{{basePath}}/js/seed.js" defer></script>
322
- <script>
323
- (function () {
324
- document.addEventListener('DOMContentLoaded', function () {
325
- // +/- count buttons
326
- var countInput = document.getElementById('seed-count');
327
- var decBtn = document.getElementById('seed-count-dec');
328
- var incBtn = document.getElementById('seed-count-inc');
329
- if (decBtn && incBtn && countInput) {
330
- decBtn.addEventListener('click', function () {
331
- var v = Math.max(1, (parseInt(countInput.value, 10) || 10) - 1);
332
- countInput.value = v;
333
- countInput.dispatchEvent(new Event('input'));
334
- });
335
- incBtn.addEventListener('click', function () {
336
- var v = Math.min(5000, (parseInt(countInput.value, 10) || 10) + 1);
337
- countInput.value = v;
338
- countInput.dispatchEvent(new Event('input'));
339
- });
340
- }
341
- // Preset pills: sync count
342
- document.querySelectorAll('.preset-pill').forEach(function (pill) {
343
- pill.addEventListener('click', function () {
344
- document.querySelectorAll('.preset-pill').forEach(function (p) {
345
- p.classList.remove('btn-primary', 'btn-secondary');
346
- p.classList.add('btn-outline');
347
- });
348
- pill.classList.remove('btn-outline');
349
- pill.classList.add('btn-primary');
350
- if (countInput && pill.dataset.count) {
351
- countInput.value = pill.dataset.count;
352
- countInput.dispatchEvent(new Event('input'));
353
- }
354
- });
355
- });
356
- // Truncate checkbox → warning banner toggle
357
- var truncateChk = document.getElementById('seed-truncate');
358
- var truncateBanner = document.getElementById('seed-truncate-banner');
359
- if (truncateChk && truncateBanner) {
360
- truncateChk.addEventListener('change', function () {
361
- if (truncateChk.checked) {
362
- truncateBanner.classList.remove('hidden');
363
- truncateBanner.classList.add('flex');
364
- } else {
365
- truncateBanner.classList.add('hidden');
366
- truncateBanner.classList.remove('flex');
367
- }
368
- });
369
- }
370
- // Wizard progress bar + step label color sync (mirrors seed.js goToStep)
371
- var progressBar = document.getElementById('wizard-progress-bar');
372
- var stepNavEls = [
373
- document.getElementById('step-nav-1'),
374
- document.getElementById('step-nav-2'),
375
- document.getElementById('step-nav-3')
376
- ];
377
- function syncStepStyles(activeStep) {
378
- var pct = { 1: '0%', 2: '50%', 3: '100%' };
379
- if (progressBar) progressBar.style.width = pct[activeStep] || '0%';
380
- stepNavEls.forEach(function (el, i) {
381
- if (!el) return;
382
- var stepNum = i + 1;
383
- var circle = el.querySelector('.seed-step-circle');
384
- var numSpan = el.querySelector('.seed-step-num');
385
- var checkSpan = el.querySelector('.seed-step-check');
386
- var label = el.querySelector('span:last-child');
387
- if (stepNum < activeStep) {
388
- // Completed
389
- circle.className = 'seed-step-circle w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold border-2 transition-all duration-300 bg-success border-success text-success-content';
390
- if (numSpan) numSpan.classList.add('hidden');
391
- if (checkSpan) checkSpan.classList.remove('hidden');
392
- if (label) label.className = 'text-xs font-semibold text-success';
393
- } else if (stepNum === activeStep) {
394
- // Active
395
- circle.className = 'seed-step-circle w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold border-2 transition-all duration-300 bg-primary border-primary text-primary-content shadow-[0_0_12px_rgba(109,93,246,0.4)]';
396
- if (numSpan) numSpan.classList.remove('hidden');
397
- if (checkSpan) checkSpan.classList.add('hidden');
398
- if (label) label.className = 'text-xs font-semibold text-primary';
399
- } else {
400
- // Inactive
401
- circle.className = 'seed-step-circle w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold border-2 transition-all duration-300 bg-base-200 border-base-300 text-base-content/40';
402
- if (numSpan) numSpan.classList.remove('hidden');
403
- if (checkSpan) checkSpan.classList.add('hidden');
404
- if (label) label.className = 'text-xs font-semibold text-base-content/40';
405
- }
406
- });
407
- }
408
- // Patch seed.js goToStep to also sync our UI
409
- var origGoToStep = window._seedGoToStep;
410
- // We'll observe mutation / use a MutationObserver on step content visibility
411
- var observer = new MutationObserver(function () {
412
- var contents = [
413
- document.getElementById('step-content-1'),
414
- document.getElementById('step-content-2'),
415
- document.getElementById('step-content-3')
416
- ];
417
- for (var i = 0; i < contents.length; i++) {
418
- if (contents[i] && !contents[i].classList.contains('hidden')) {
419
- syncStepStyles(i + 1);
420
- return;
421
- }
422
- }
423
- });
424
- ['step-content-1', 'step-content-2', 'step-content-3'].forEach(function (id) {
425
- var el = document.getElementById(id);
426
- if (el) observer.observe(el, { attributes: true, attributeFilter: ['class'] });
427
- });
428
- // Initial sync
429
- syncStepStyles(1);
430
- // Mode card radio dot sync (complement seed.js selectMode)
431
- var btnSingle = document.getElementById('btn-select-single');
432
- var btnChain = document.getElementById('btn-select-chain');
433
- function syncModeCards(selected) {
434
- [btnSingle, btnChain].forEach(function (btn) {
435
- if (!btn) return;
436
- var radio = btn.querySelector('.seed-mode-radio');
437
- var dot = btn.querySelector('.seed-mode-dot');
438
- var isSelected = btn === selected;
439
- if (radio) radio.classList.toggle('border-primary', isSelected && btn === btnSingle);
440
- if (radio) radio.classList.toggle('border-secondary', isSelected && btn === btnChain);
441
- if (radio) radio.classList.toggle('border-base-300', !isSelected);
442
- if (dot) dot.classList.toggle('scale-100', isSelected);
443
- if (dot) dot.classList.toggle('scale-0', !isSelected);
444
- btn.classList.toggle('seed-mode-card--selected-single', isSelected && btn === btnSingle);
445
- btn.classList.toggle('seed-mode-card--selected-chain', isSelected && btn === btnChain);
446
- });
447
- }
448
- if (btnSingle) btnSingle.addEventListener('click', function () { syncModeCards(btnSingle); });
449
- if (btnChain) btnChain.addEventListener('click', function () { syncModeCards(btnChain); });
450
- // Row count badge updater for step 3
451
- var origTbody = document.getElementById('seed-preview-tbody');
452
- if (origTbody) {
453
- var rowObserver = new MutationObserver(function () {
454
- var badge = document.getElementById('seed-row-count-badge');
455
- if (!badge) return;
456
- var rows = origTbody.querySelectorAll('tr');
457
- var isPlaceholder = origTbody.querySelector('td[colspan]');
458
- if (isPlaceholder) { badge.textContent = ''; return; }
459
- badge.textContent = rows.length + ' row' + (rows.length !== 1 ? 's' : '') + ' generated';
460
- });
461
- rowObserver.observe(origTbody, { childList: true, subtree: true });
462
- }
463
- });
464
- })();
465
- </script>