@autocode-cli/autocode 0.1.5 → 0.1.6

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 (118) hide show
  1. package/dist/cli/commands/serve.d.ts.map +1 -1
  2. package/dist/cli/commands/serve.js +27 -0
  3. package/dist/cli/commands/serve.js.map +1 -1
  4. package/dist/cli/commands/sync.d.ts +9 -0
  5. package/dist/cli/commands/sync.d.ts.map +1 -0
  6. package/dist/cli/commands/sync.js +91 -0
  7. package/dist/cli/commands/sync.js.map +1 -0
  8. package/dist/cli/parser.d.ts.map +1 -1
  9. package/dist/cli/parser.js +2 -0
  10. package/dist/cli/parser.js.map +1 -1
  11. package/dist/core/catalog.d.ts +52 -0
  12. package/dist/core/catalog.d.ts.map +1 -0
  13. package/dist/core/catalog.js +101 -0
  14. package/dist/core/catalog.js.map +1 -0
  15. package/dist/core/column.d.ts +2 -1
  16. package/dist/core/column.d.ts.map +1 -1
  17. package/dist/core/column.js +23 -11
  18. package/dist/core/column.js.map +1 -1
  19. package/dist/core/pipeline.d.ts +70 -0
  20. package/dist/core/pipeline.d.ts.map +1 -0
  21. package/dist/core/pipeline.js +199 -0
  22. package/dist/core/pipeline.js.map +1 -0
  23. package/dist/core/sync.d.ts +41 -0
  24. package/dist/core/sync.d.ts.map +1 -0
  25. package/dist/core/sync.js +269 -0
  26. package/dist/core/sync.js.map +1 -0
  27. package/dist/server/api.d.ts.map +1 -1
  28. package/dist/server/api.js +300 -0
  29. package/dist/server/api.js.map +1 -1
  30. package/dist/server/dashboard/pages/index.d.ts +1 -0
  31. package/dist/server/dashboard/pages/index.d.ts.map +1 -1
  32. package/dist/server/dashboard/pages/index.js +1 -0
  33. package/dist/server/dashboard/pages/index.js.map +1 -1
  34. package/dist/server/dashboard/pages/main-dashboard.d.ts.map +1 -1
  35. package/dist/server/dashboard/pages/main-dashboard.js +2 -1
  36. package/dist/server/dashboard/pages/main-dashboard.js.map +1 -1
  37. package/dist/server/dashboard/pages/pipeline-configurator.d.ts +8 -0
  38. package/dist/server/dashboard/pages/pipeline-configurator.d.ts.map +1 -0
  39. package/dist/server/dashboard/pages/pipeline-configurator.js +1531 -0
  40. package/dist/server/dashboard/pages/pipeline-configurator.js.map +1 -0
  41. package/dist/server/dashboard/pages/water-quality-form.d.ts +10 -0
  42. package/dist/server/dashboard/pages/water-quality-form.d.ts.map +1 -0
  43. package/dist/server/dashboard/pages/water-quality-form.js +910 -0
  44. package/dist/server/dashboard/pages/water-quality-form.js.map +1 -0
  45. package/dist/server/dashboard/styles/base.d.ts.map +1 -1
  46. package/dist/server/dashboard/styles/base.js +11 -0
  47. package/dist/server/dashboard/styles/base.js.map +1 -1
  48. package/dist/server/dashboard.d.ts +1 -1
  49. package/dist/server/dashboard.d.ts.map +1 -1
  50. package/dist/server/dashboard.js +1 -1
  51. package/dist/server/dashboard.js.map +1 -1
  52. package/dist/server/index.d.ts.map +1 -1
  53. package/dist/server/index.js +16 -1
  54. package/dist/server/index.js.map +1 -1
  55. package/dist/services/claude.d.ts +2 -1
  56. package/dist/services/claude.d.ts.map +1 -1
  57. package/dist/services/claude.js +8 -1
  58. package/dist/services/claude.js.map +1 -1
  59. package/dist/types/index.d.ts +67 -0
  60. package/dist/types/index.d.ts.map +1 -1
  61. package/package.json +2 -2
  62. package/templates/catalog.yaml +100 -0
  63. package/templates/prompts/changelog.en.md +31 -0
  64. package/templates/prompts/changelog.fr.md +31 -0
  65. package/templates/prompts/deploy-prod.en.md +32 -0
  66. package/templates/prompts/deploy-prod.fr.md +32 -0
  67. package/templates/prompts/design.en.md +30 -0
  68. package/templates/prompts/design.fr.md +30 -0
  69. package/templates/prompts/dev.en.md +31 -0
  70. package/templates/prompts/dev.fr.md +31 -0
  71. package/templates/prompts/git-commit.en.md +35 -0
  72. package/templates/prompts/git-commit.fr.md +35 -0
  73. package/templates/prompts/git-push.en.md +31 -0
  74. package/templates/prompts/git-push.fr.md +31 -0
  75. package/templates/prompts/git-tag.en.md +32 -0
  76. package/templates/prompts/git-tag.fr.md +32 -0
  77. package/templates/prompts/qualification.en.md +30 -0
  78. package/templates/prompts/qualification.fr.md +30 -0
  79. package/templates/prompts/retest.en.md +31 -0
  80. package/templates/prompts/retest.fr.md +31 -0
  81. package/templates/prompts/review-code.en.md +31 -0
  82. package/templates/prompts/review-code.fr.md +31 -0
  83. package/templates/prompts/specification.en.md +30 -0
  84. package/templates/prompts/specification.fr.md +30 -0
  85. package/templates/prompts/testing-integration.en.md +32 -0
  86. package/templates/prompts/testing-integration.fr.md +32 -0
  87. package/templates/prompts/testing-unit.en.md +32 -0
  88. package/templates/prompts/testing-unit.fr.md +32 -0
  89. /package/templates/{columns/00_backlog.en.md → prompts/backlog.en.md} +0 -0
  90. /package/templates/{columns/00_backlog.fr.md → prompts/backlog.fr.md} +0 -0
  91. /package/templates/{columns/12_deploy-staging.en.md → prompts/deploy-staging.en.md} +0 -0
  92. /package/templates/{columns/12_deploy-staging.fr.md → prompts/deploy-staging.fr.md} +0 -0
  93. /package/templates/{columns/14_done.en.md → prompts/done.en.md} +0 -0
  94. /package/templates/{columns/14_done.fr.md → prompts/done.fr.md} +0 -0
  95. /package/templates/{columns/02_in-progress.en.md → prompts/in-progress.en.md} +0 -0
  96. /package/templates/{columns/02_in-progress.fr.md → prompts/in-progress.fr.md} +0 -0
  97. /package/templates/{columns/01_ready.en.md → prompts/ready.en.md} +0 -0
  98. /package/templates/{columns/01_ready.fr.md → prompts/ready.fr.md} +0 -0
  99. /package/templates/{columns/10_retest-cypress.en.md → prompts/retest-cypress.en.md} +0 -0
  100. /package/templates/{columns/10_retest-cypress.fr.md → prompts/retest-cypress.fr.md} +0 -0
  101. /package/templates/{columns/09_retest-playwright.en.md → prompts/retest-playwright.en.md} +0 -0
  102. /package/templates/{columns/09_retest-playwright.fr.md → prompts/retest-playwright.fr.md} +0 -0
  103. /package/templates/{columns/05_review-best-practices.en.md → prompts/review-best-practices.en.md} +0 -0
  104. /package/templates/{columns/05_review-best-practices.fr.md → prompts/review-best-practices.fr.md} +0 -0
  105. /package/templates/{columns/07_review-consistency.en.md → prompts/review-consistency.en.md} +0 -0
  106. /package/templates/{columns/07_review-consistency.fr.md → prompts/review-consistency.fr.md} +0 -0
  107. /package/templates/{columns/06_review-no-duplication.en.md → prompts/review-no-duplication.en.md} +0 -0
  108. /package/templates/{columns/06_review-no-duplication.fr.md → prompts/review-no-duplication.fr.md} +0 -0
  109. /package/templates/{columns/08_review-security.en.md → prompts/review-security.en.md} +0 -0
  110. /package/templates/{columns/08_review-security.fr.md → prompts/review-security.fr.md} +0 -0
  111. /package/templates/{columns/04_testing-cypress.en.md → prompts/testing-cypress.en.md} +0 -0
  112. /package/templates/{columns/04_testing-cypress.fr.md → prompts/testing-cypress.fr.md} +0 -0
  113. /package/templates/{columns/03_testing-playwright.en.md → prompts/testing-playwright.en.md} +0 -0
  114. /package/templates/{columns/03_testing-playwright.fr.md → prompts/testing-playwright.fr.md} +0 -0
  115. /package/templates/{columns/11_update-docs.en.md → prompts/update-docs.en.md} +0 -0
  116. /package/templates/{columns/11_update-docs.fr.md → prompts/update-docs.fr.md} +0 -0
  117. /package/templates/{columns/13_validate-staging.en.md → prompts/validate-staging.en.md} +0 -0
  118. /package/templates/{columns/13_validate-staging.fr.md → prompts/validate-staging.fr.md} +0 -0
@@ -0,0 +1,1531 @@
1
+ /**
2
+ * Pipeline configurator page generator
3
+ */
4
+ /**
5
+ * Generate the pipeline configurator page
6
+ */
7
+ export function generatePipelineConfiguratorPage(lang) {
8
+ return `<!DOCTYPE html>
9
+ <html lang="${lang}">
10
+ <head>
11
+ <meta charset="UTF-8">
12
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
13
+ <title>Pipeline Configurator - AutoCode</title>
14
+ <style>
15
+ :root {
16
+ --bg: #0d1117;
17
+ --bg-card: #161b22;
18
+ --bg-hover: #1f2937;
19
+ --border: #30363d;
20
+ --fg: #c9d1d9;
21
+ --muted: #8b949e;
22
+ --accent: #7c3aed;
23
+ --blue: #4dabf7;
24
+ --green: #22c55e;
25
+ --red: #ef4444;
26
+ --yellow: #eab308;
27
+ --segment-definition: #3b82f6;
28
+ --segment-action: #f97316;
29
+ --segment-finish: #22c55e;
30
+ }
31
+ * { margin: 0; padding: 0; box-sizing: border-box; }
32
+ body {
33
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
34
+ background: var(--bg);
35
+ color: var(--fg);
36
+ min-height: 100vh;
37
+ }
38
+ .header {
39
+ background: var(--bg-card);
40
+ border-bottom: 1px solid var(--border);
41
+ padding: 16px 24px;
42
+ display: flex;
43
+ align-items: center;
44
+ gap: 16px;
45
+ position: sticky;
46
+ top: 0;
47
+ z-index: 100;
48
+ }
49
+ .back-btn {
50
+ background: none;
51
+ border: 1px solid var(--border);
52
+ color: var(--fg);
53
+ padding: 8px 12px;
54
+ border-radius: 6px;
55
+ cursor: pointer;
56
+ font-size: 14px;
57
+ text-decoration: none;
58
+ }
59
+ .back-btn:hover { border-color: var(--accent); color: var(--accent); }
60
+ .title { flex: 1; font-size: 18px; font-weight: 600; }
61
+ .title span { color: var(--accent); }
62
+ .btn {
63
+ padding: 8px 16px;
64
+ border-radius: 6px;
65
+ font-size: 14px;
66
+ font-weight: 500;
67
+ cursor: pointer;
68
+ border: none;
69
+ display: inline-flex;
70
+ align-items: center;
71
+ gap: 6px;
72
+ }
73
+ .btn-primary { background: var(--accent); color: white; }
74
+ .btn-primary:hover { opacity: 0.9; }
75
+ .btn-success { background: var(--green); color: white; }
76
+ .btn-success:hover { opacity: 0.9; }
77
+ .btn-secondary { background: var(--bg); border: 1px solid var(--border); color: var(--fg); }
78
+ .btn-secondary:hover { border-color: var(--accent); }
79
+ .btn-danger { background: var(--red); color: white; }
80
+ .btn-danger:hover { opacity: 0.9; }
81
+ .btn:disabled { opacity: 0.5; cursor: not-allowed; }
82
+
83
+ .container {
84
+ display: grid;
85
+ grid-template-columns: 280px 1fr;
86
+ gap: 24px;
87
+ padding: 24px;
88
+ max-width: 1600px;
89
+ margin: 0 auto;
90
+ }
91
+
92
+ /* Sidebar - Pipeline List */
93
+ .sidebar {
94
+ background: var(--bg-card);
95
+ border: 1px solid var(--border);
96
+ border-radius: 8px;
97
+ padding: 16px;
98
+ }
99
+ .sidebar h2 {
100
+ font-size: 14px;
101
+ text-transform: uppercase;
102
+ color: var(--muted);
103
+ margin-bottom: 12px;
104
+ letter-spacing: 0.5px;
105
+ }
106
+ .pipeline-list {
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: 8px;
110
+ margin-bottom: 16px;
111
+ }
112
+ .pipeline-item {
113
+ padding: 12px;
114
+ background: var(--bg);
115
+ border: 1px solid var(--border);
116
+ border-radius: 6px;
117
+ cursor: pointer;
118
+ transition: all 0.2s;
119
+ }
120
+ .pipeline-item:hover { border-color: var(--accent); }
121
+ .pipeline-item.active { border-color: var(--accent); background: rgba(124, 58, 237, 0.1); }
122
+ .pipeline-item .name { font-weight: 500; margin-bottom: 4px; }
123
+ .pipeline-item .meta { font-size: 12px; color: var(--muted); }
124
+ .pipeline-item .badge {
125
+ display: inline-block;
126
+ background: var(--green);
127
+ color: white;
128
+ font-size: 10px;
129
+ padding: 2px 6px;
130
+ border-radius: 4px;
131
+ margin-left: 8px;
132
+ }
133
+ .new-pipeline-btn {
134
+ width: 100%;
135
+ padding: 12px;
136
+ background: var(--bg);
137
+ border: 1px dashed var(--border);
138
+ border-radius: 6px;
139
+ color: var(--muted);
140
+ cursor: pointer;
141
+ font-size: 14px;
142
+ }
143
+ .new-pipeline-btn:hover { border-color: var(--accent); color: var(--accent); }
144
+
145
+ /* Main area */
146
+ .main {
147
+ display: flex;
148
+ flex-direction: column;
149
+ gap: 24px;
150
+ }
151
+
152
+ /* Pipeline details */
153
+ .pipeline-header {
154
+ background: var(--bg-card);
155
+ border: 1px solid var(--border);
156
+ border-radius: 8px;
157
+ padding: 20px;
158
+ }
159
+ .pipeline-form {
160
+ display: grid;
161
+ grid-template-columns: 1fr 1fr auto;
162
+ gap: 16px;
163
+ align-items: end;
164
+ }
165
+ .form-group {
166
+ display: flex;
167
+ flex-direction: column;
168
+ gap: 6px;
169
+ }
170
+ .form-group label {
171
+ font-size: 12px;
172
+ color: var(--muted);
173
+ text-transform: uppercase;
174
+ }
175
+ .form-group input, .form-group textarea {
176
+ background: var(--bg);
177
+ border: 1px solid var(--border);
178
+ border-radius: 6px;
179
+ padding: 10px 12px;
180
+ color: var(--fg);
181
+ font-size: 14px;
182
+ }
183
+ .form-group input:focus, .form-group textarea:focus {
184
+ outline: none;
185
+ border-color: var(--accent);
186
+ }
187
+
188
+ /* Segments editor */
189
+ .segments-container {
190
+ display: grid;
191
+ grid-template-columns: repeat(3, 1fr);
192
+ gap: 16px;
193
+ }
194
+ .segment {
195
+ background: var(--bg-card);
196
+ border: 1px solid var(--border);
197
+ border-radius: 8px;
198
+ padding: 16px;
199
+ min-height: 300px;
200
+ }
201
+ .segment.definition { border-top: 3px solid var(--segment-definition); }
202
+ .segment.action { border-top: 3px solid var(--segment-action); }
203
+ .segment.finish { border-top: 3px solid var(--segment-finish); }
204
+ .segment-header {
205
+ display: flex;
206
+ align-items: center;
207
+ justify-content: space-between;
208
+ margin-bottom: 12px;
209
+ }
210
+ .segment-title {
211
+ font-weight: 600;
212
+ font-size: 14px;
213
+ text-transform: uppercase;
214
+ }
215
+ .segment.definition .segment-title { color: var(--segment-definition); }
216
+ .segment.action .segment-title { color: var(--segment-action); }
217
+ .segment.finish .segment-title { color: var(--segment-finish); }
218
+ .segment-desc {
219
+ font-size: 12px;
220
+ color: var(--muted);
221
+ margin-bottom: 16px;
222
+ }
223
+ .segment-columns {
224
+ display: flex;
225
+ flex-direction: column;
226
+ gap: 8px;
227
+ min-height: 200px;
228
+ padding: 8px;
229
+ background: var(--bg);
230
+ border-radius: 6px;
231
+ border: 2px dashed var(--border);
232
+ }
233
+ .segment-columns.dragover {
234
+ border-color: var(--accent);
235
+ background: rgba(124, 58, 237, 0.1);
236
+ }
237
+ .segment-columns.dragover-invalid {
238
+ border-color: var(--red);
239
+ background: rgba(239, 68, 68, 0.1);
240
+ cursor: not-allowed;
241
+ }
242
+
243
+ /* Column items */
244
+ .column-item {
245
+ padding: 10px 12px;
246
+ background: var(--bg-card);
247
+ border: 1px solid var(--border);
248
+ border-radius: 6px;
249
+ cursor: grab;
250
+ display: flex;
251
+ align-items: center;
252
+ gap: 8px;
253
+ transition: all 0.2s;
254
+ }
255
+ .column-item:hover { border-color: var(--accent); }
256
+ .column-item.dragging { opacity: 0.5; }
257
+ .column-item .drag-handle {
258
+ color: var(--muted);
259
+ font-size: 12px;
260
+ }
261
+ .column-item .name { flex: 1; font-size: 14px; }
262
+ .column-item .remove-btn {
263
+ background: none;
264
+ border: none;
265
+ color: var(--muted);
266
+ cursor: pointer;
267
+ padding: 4px;
268
+ border-radius: 4px;
269
+ }
270
+ .column-item .remove-btn:hover { color: var(--red); background: rgba(239, 68, 68, 0.1); }
271
+
272
+ /* Catalog */
273
+ .catalog {
274
+ background: var(--bg-card);
275
+ border: 1px solid var(--border);
276
+ border-radius: 8px;
277
+ padding: 16px;
278
+ }
279
+ .catalog h2 {
280
+ font-size: 14px;
281
+ text-transform: uppercase;
282
+ color: var(--muted);
283
+ margin-bottom: 16px;
284
+ display: flex;
285
+ align-items: center;
286
+ gap: 8px;
287
+ }
288
+ .catalog-grid {
289
+ display: grid;
290
+ grid-template-columns: repeat(3, 1fr);
291
+ gap: 16px;
292
+ }
293
+ .catalog-segment {
294
+ display: flex;
295
+ flex-direction: column;
296
+ }
297
+ .catalog-segment-header {
298
+ display: flex;
299
+ flex-direction: column;
300
+ align-items: center;
301
+ gap: 8px;
302
+ margin-bottom: 12px;
303
+ }
304
+ .catalog-segment-arrow {
305
+ font-size: 24px;
306
+ color: var(--muted);
307
+ opacity: 0.5;
308
+ animation: bounce 2s infinite;
309
+ }
310
+ @keyframes bounce {
311
+ 0%, 100% { transform: translateY(0); }
312
+ 50% { transform: translateY(-5px); }
313
+ }
314
+ .catalog-segment.definition .catalog-segment-arrow { color: var(--segment-definition); }
315
+ .catalog-segment.action .catalog-segment-arrow { color: var(--segment-action); }
316
+ .catalog-segment.finish .catalog-segment-arrow { color: var(--segment-finish); }
317
+ .catalog-segment-title {
318
+ font-size: 12px;
319
+ font-weight: 600;
320
+ padding: 4px 8px;
321
+ border-radius: 4px;
322
+ display: inline-block;
323
+ }
324
+ .catalog-segment.definition .catalog-segment-title { background: rgba(59, 130, 246, 0.2); color: var(--segment-definition); }
325
+ .catalog-segment.action .catalog-segment-title { background: rgba(249, 115, 22, 0.2); color: var(--segment-action); }
326
+ .catalog-segment.finish .catalog-segment-title { background: rgba(34, 197, 94, 0.2); color: var(--segment-finish); }
327
+ .catalog-columns {
328
+ display: flex;
329
+ flex-direction: column;
330
+ gap: 6px;
331
+ flex: 1;
332
+ }
333
+ .catalog-item {
334
+ padding: 8px 12px;
335
+ background: var(--bg);
336
+ border: 1px solid var(--border);
337
+ border-radius: 6px;
338
+ cursor: pointer;
339
+ font-size: 13px;
340
+ transition: all 0.2s;
341
+ display: flex;
342
+ align-items: center;
343
+ gap: 6px;
344
+ }
345
+ .catalog-item:hover { border-color: var(--accent); }
346
+ .catalog-item.dragging { opacity: 0.5; cursor: grabbing; }
347
+ .catalog-item-name { flex: 1; }
348
+ .catalog-item-eye {
349
+ opacity: 0.3;
350
+ font-size: 11px;
351
+ transition: opacity 0.2s;
352
+ }
353
+ .catalog-item:hover .catalog-item-eye { opacity: 0.8; }
354
+
355
+ /* Lang toggle */
356
+ .lang-toggle {
357
+ display: flex;
358
+ gap: 4px;
359
+ }
360
+ .lang-toggle .lang-btn {
361
+ padding: 4px 10px;
362
+ font-size: 12px;
363
+ }
364
+ .lang-toggle .lang-btn.active {
365
+ background: var(--accent);
366
+ border-color: var(--accent);
367
+ color: white;
368
+ }
369
+ .catalog-item[title]:hover::after {
370
+ content: attr(title);
371
+ position: absolute;
372
+ background: var(--bg-card);
373
+ border: 1px solid var(--border);
374
+ padding: 6px 10px;
375
+ border-radius: 4px;
376
+ font-size: 12px;
377
+ white-space: nowrap;
378
+ z-index: 100;
379
+ margin-top: 30px;
380
+ margin-left: -50px;
381
+ }
382
+
383
+ /* Sync preview */
384
+ .sync-preview {
385
+ background: var(--bg-card);
386
+ border: 1px solid var(--border);
387
+ border-radius: 8px;
388
+ padding: 16px;
389
+ }
390
+ .sync-preview h3 {
391
+ font-size: 14px;
392
+ margin-bottom: 12px;
393
+ display: flex;
394
+ align-items: center;
395
+ gap: 8px;
396
+ }
397
+ .sync-changes {
398
+ display: grid;
399
+ grid-template-columns: repeat(3, 1fr);
400
+ gap: 12px;
401
+ }
402
+ .sync-group {
403
+ padding: 12px;
404
+ border-radius: 6px;
405
+ background: var(--bg);
406
+ }
407
+ .sync-group.added { border-left: 3px solid var(--green); }
408
+ .sync-group.removed { border-left: 3px solid var(--red); }
409
+ .sync-group.renamed { border-left: 3px solid var(--yellow); }
410
+ .sync-group h4 {
411
+ font-size: 12px;
412
+ margin-bottom: 8px;
413
+ color: var(--muted);
414
+ }
415
+ .sync-group ul {
416
+ list-style: none;
417
+ font-size: 13px;
418
+ }
419
+ .sync-group li { padding: 4px 0; }
420
+
421
+ /* Notification */
422
+ .notification {
423
+ position: fixed;
424
+ bottom: 24px;
425
+ right: 24px;
426
+ background: var(--bg-card);
427
+ border: 1px solid var(--border);
428
+ border-left: 3px solid var(--green);
429
+ padding: 12px 16px;
430
+ border-radius: 6px;
431
+ font-size: 14px;
432
+ opacity: 0;
433
+ transform: translateY(10px);
434
+ transition: all 0.3s;
435
+ z-index: 1000;
436
+ }
437
+ .notification.show { opacity: 1; transform: translateY(0); }
438
+ .notification.error { border-left-color: var(--red); }
439
+ .notification.warning { border-left-color: var(--yellow); }
440
+
441
+ /* Modal */
442
+ .modal-overlay {
443
+ position: fixed;
444
+ inset: 0;
445
+ background: rgba(0,0,0,0.7);
446
+ display: none;
447
+ align-items: center;
448
+ justify-content: center;
449
+ z-index: 1000;
450
+ }
451
+ .modal-overlay.show { display: flex; }
452
+ .modal {
453
+ background: var(--bg-card);
454
+ border: 1px solid var(--border);
455
+ border-radius: 12px;
456
+ padding: 24px;
457
+ min-width: 400px;
458
+ max-width: 500px;
459
+ }
460
+ .modal h3 {
461
+ margin-bottom: 16px;
462
+ font-size: 18px;
463
+ }
464
+ .modal-actions {
465
+ display: flex;
466
+ justify-content: flex-end;
467
+ gap: 8px;
468
+ margin-top: 20px;
469
+ }
470
+
471
+ /* Empty state */
472
+ .empty-state {
473
+ text-align: center;
474
+ padding: 40px;
475
+ color: var(--muted);
476
+ }
477
+ .empty-state h3 { font-size: 18px; margin-bottom: 8px; color: var(--fg); }
478
+ .empty-state p { margin-bottom: 20px; }
479
+
480
+ /* Lang switcher */
481
+ .lang-switcher {
482
+ display: flex;
483
+ gap: 4px;
484
+ margin-left: auto;
485
+ margin-right: 16px;
486
+ }
487
+ .lang-switcher .lang-btn {
488
+ background: var(--bg);
489
+ border: 1px solid var(--border);
490
+ color: var(--muted);
491
+ padding: 6px 12px;
492
+ border-radius: 4px;
493
+ cursor: pointer;
494
+ font-size: 13px;
495
+ font-weight: 600;
496
+ transition: all 0.2s;
497
+ }
498
+ .lang-switcher .lang-btn:hover:not(.active) {
499
+ border-color: var(--accent);
500
+ color: var(--fg);
501
+ }
502
+ .lang-switcher .lang-btn.active {
503
+ background: var(--accent);
504
+ color: white;
505
+ border-color: var(--accent);
506
+ }
507
+ </style>
508
+ </head>
509
+ <body>
510
+ <header class="header">
511
+ <a href="/" class="back-btn">← Dashboard</a>
512
+ <h1 class="title"><span>Pipeline</span> Configurator</h1>
513
+ <div class="lang-switcher" id="lang-switcher">
514
+ <button class="lang-btn" data-lang="en">EN</button>
515
+ <button class="lang-btn" data-lang="fr">FR</button>
516
+ </div>
517
+ <button class="btn btn-success" id="savePipelineBtn" disabled>Save Pipeline</button>
518
+ <button class="btn btn-primary" id="activateBtn" disabled>Activate & Sync</button>
519
+ </header>
520
+
521
+ <div class="container">
522
+ <!-- Sidebar: Pipeline list -->
523
+ <aside class="sidebar">
524
+ <h2>Pipelines</h2>
525
+ <div class="pipeline-list" id="pipelineList">
526
+ <!-- Populated by JS -->
527
+ </div>
528
+ <button class="new-pipeline-btn" id="newPipelineBtn">+ New Pipeline</button>
529
+ </aside>
530
+
531
+ <!-- Main content -->
532
+ <main class="main" id="mainContent">
533
+ <div class="empty-state" id="emptyState">
534
+ <h3>No Pipeline Selected</h3>
535
+ <p>Select a pipeline from the list or create a new one.</p>
536
+ <button class="btn btn-primary" id="createFirstBtn">Create First Pipeline</button>
537
+ </div>
538
+
539
+ <!-- Pipeline editor (hidden initially) -->
540
+ <div id="pipelineEditor" style="display:none;">
541
+ <!-- Pipeline details -->
542
+ <div class="pipeline-header">
543
+ <div class="pipeline-form">
544
+ <div class="form-group">
545
+ <label>Pipeline Name</label>
546
+ <input type="text" id="pipelineName" placeholder="e.g., Default Pipeline">
547
+ </div>
548
+ <div class="form-group">
549
+ <label>Version</label>
550
+ <input type="text" id="pipelineVersion" placeholder="e.g., 1.0.0" value="1.0.0">
551
+ </div>
552
+ <button class="btn btn-danger" id="deletePipelineBtn">Delete</button>
553
+ </div>
554
+ <div class="form-group" style="margin-top: 12px;">
555
+ <label>Description (optional)</label>
556
+ <input type="text" id="pipelineDescription" placeholder="Short description of this pipeline">
557
+ </div>
558
+ </div>
559
+
560
+ <!-- Segments -->
561
+ <div class="segments-container">
562
+ <div class="segment definition">
563
+ <div class="segment-header">
564
+ <span class="segment-title">Definition</span>
565
+ </div>
566
+ <div class="segment-desc">Qualification and preparation columns</div>
567
+ <div class="segment-columns" id="definitionColumns" data-segment="definition">
568
+ <!-- Columns added here -->
569
+ </div>
570
+ </div>
571
+
572
+ <div class="segment action">
573
+ <div class="segment-header">
574
+ <span class="segment-title">Action</span>
575
+ </div>
576
+ <div class="segment-desc">Implementation and validation columns</div>
577
+ <div class="segment-columns" id="actionColumns" data-segment="action">
578
+ <!-- Columns added here -->
579
+ </div>
580
+ </div>
581
+
582
+ <div class="segment finish">
583
+ <div class="segment-header">
584
+ <span class="segment-title">Finish</span>
585
+ </div>
586
+ <div class="segment-desc">Finalization and deployment columns</div>
587
+ <div class="segment-columns" id="finishColumns" data-segment="finish">
588
+ <!-- Columns added here -->
589
+ </div>
590
+ </div>
591
+ </div>
592
+
593
+ <!-- Catalog -->
594
+ <div class="catalog">
595
+ <h2>
596
+ Column Catalog <span style="font-weight: normal; font-size: 12px;">(drag columns to segments above)</span>
597
+ <button class="btn btn-secondary" style="margin-left: auto; font-size: 12px;" onclick="openAddColumnModal()">+ Add Column</button>
598
+ </h2>
599
+ <div id="catalogContent">
600
+ <!-- Populated by JS -->
601
+ </div>
602
+ </div>
603
+
604
+ <!-- Sync preview -->
605
+ <div class="sync-preview" id="syncPreview" style="display:none;">
606
+ <h3>Sync Preview</h3>
607
+ <div class="sync-changes" id="syncChanges">
608
+ <!-- Populated by JS -->
609
+ </div>
610
+ </div>
611
+ </div>
612
+ </main>
613
+ </div>
614
+
615
+ <!-- New Pipeline Modal -->
616
+ <div class="modal-overlay" id="newPipelineModal">
617
+ <div class="modal">
618
+ <h3>Create New Pipeline</h3>
619
+ <div class="form-group">
620
+ <label>Pipeline Name</label>
621
+ <input type="text" id="newPipelineName" placeholder="e.g., Default Pipeline">
622
+ <small style="color: var(--muted); margin-top: 4px; display: block;">Key: <code id="newPipelineKeyPreview">-</code></small>
623
+ </div>
624
+ <div class="modal-actions">
625
+ <button class="btn btn-secondary" onclick="closeNewPipelineModal()">Cancel</button>
626
+ <button class="btn btn-primary" onclick="confirmNewPipeline()">Create</button>
627
+ </div>
628
+ </div>
629
+ </div>
630
+
631
+ <div class="notification" id="notification"></div>
632
+
633
+ <!-- Add Column Modal -->
634
+ <div class="modal-overlay" id="addColumnModal">
635
+ <div class="modal" style="max-width: 500px;">
636
+ <h3>Add New Column to Catalog</h3>
637
+ <p style="color: var(--muted); font-size: 13px; margin-bottom: 16px;">Describe your idea and Claude will generate the prompt in EN/FR.</p>
638
+ <div class="form-group">
639
+ <label>Column Name</label>
640
+ <input type="text" id="newColumnName" placeholder="e.g., Code Formatting">
641
+ </div>
642
+ <div class="form-group" style="margin-top: 12px;">
643
+ <label>Segment</label>
644
+ <select id="newColumnSegment">
645
+ <option value="definition">Definition (preparation)</option>
646
+ <option value="action" selected>Action (implementation)</option>
647
+ <option value="finish">Finish (deployment)</option>
648
+ </select>
649
+ </div>
650
+ <div class="form-group" style="margin-top: 12px;">
651
+ <label>Describe what this column should do</label>
652
+ <textarea id="newColumnDescription" rows="4" placeholder="e.g., Check code formatting with Prettier, fix any issues, ensure consistent style across the project..."></textarea>
653
+ </div>
654
+ <div class="modal-actions">
655
+ <button class="btn btn-secondary" onclick="closeAddColumnModal()">Cancel</button>
656
+ <button class="btn btn-primary" id="generateColumnBtn" onclick="generateColumn()">
657
+ <span id="generateColumnText">Generate with Claude</span>
658
+ <span id="generateColumnSpinner" style="display:none;">Generating...</span>
659
+ </button>
660
+ </div>
661
+ </div>
662
+ </div>
663
+
664
+ <!-- Prompt Preview Modal -->
665
+ <div class="modal-overlay" id="promptPreviewModal">
666
+ <div class="modal" style="max-width: 700px; width: 90%;">
667
+ <div class="modal-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
668
+ <h3 id="promptPreviewTitle" style="margin: 0;">Column Prompt</h3>
669
+ <div style="display: flex; gap: 8px; align-items: center;">
670
+ <div class="lang-toggle">
671
+ <button class="btn btn-secondary lang-btn active" data-lang="en" onclick="switchPromptLang('en')">EN</button>
672
+ <button class="btn btn-secondary lang-btn" data-lang="fr" onclick="switchPromptLang('fr')">FR</button>
673
+ </div>
674
+ <button class="btn btn-secondary" onclick="closePromptPreview()" style="padding: 4px 10px;">✕</button>
675
+ </div>
676
+ </div>
677
+ <div id="promptPreviewContent" style="background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 16px; max-height: 60vh; overflow-y: auto; white-space: pre-wrap; font-family: monospace; font-size: 13px; line-height: 1.5;"></div>
678
+ </div>
679
+ </div>
680
+
681
+ <script>
682
+ // Translations
683
+ const translations = {
684
+ en: {
685
+ backBtn: '← Dashboard',
686
+ title: 'Configurator',
687
+ titleSpan: 'Pipeline',
688
+ savePipeline: 'Save Pipeline',
689
+ activateSync: 'Activate & Sync',
690
+ pipelines: 'Pipelines',
691
+ newPipeline: '+ New Pipeline',
692
+ noSelected: 'No Pipeline Selected',
693
+ noSelectedDesc: 'Select a pipeline from the list or create a new one.',
694
+ createFirst: 'Create First Pipeline',
695
+ pipelineName: 'Pipeline Name',
696
+ version: 'Version',
697
+ delete: 'Delete',
698
+ description: 'Description (optional)',
699
+ descPlaceholder: 'Short description of this pipeline',
700
+ definition: 'DEFINITION',
701
+ definitionDesc: 'Qualification and preparation columns',
702
+ action: 'ACTION',
703
+ actionDesc: 'Implementation and validation columns',
704
+ finish: 'FINISH',
705
+ finishDesc: 'Finalization and deployment columns',
706
+ catalog: 'Column Catalog',
707
+ catalogHint: '(drag columns to segments above)',
708
+ addColumn: '+ Add Column',
709
+ syncPreview: 'Sync Preview',
710
+ createNewPipeline: 'Create New Pipeline',
711
+ create: 'Create',
712
+ cancel: 'Cancel',
713
+ addColumnTitle: 'Add New Column to Catalog',
714
+ addColumnDesc: 'Describe your idea and Claude will generate the prompt in EN/FR.',
715
+ columnName: 'Column Name',
716
+ segment: 'Segment',
717
+ segmentDefinition: 'Definition (preparation)',
718
+ segmentAction: 'Action (implementation)',
719
+ segmentFinish: 'Finish (deployment)',
720
+ describeColumn: 'Describe what this column should do',
721
+ generateClaude: 'Generate with Claude',
722
+ generating: 'Generating...',
723
+ promptPreview: 'Column Prompt',
724
+ loading: 'Loading...',
725
+ noPrompt: '(No prompt available for this column)',
726
+ columnBelongsTo: 'This column belongs to the',
727
+ segmentLabel: 'segment',
728
+ cannotMoveBetween: 'Columns cannot be moved between segments',
729
+ columnExists: 'Column already exists in this segment',
730
+ pipelineSaved: 'Pipeline saved!',
731
+ pipelineCreated: 'Pipeline created!',
732
+ pipelineDeleted: 'Pipeline deleted',
733
+ pipelineActivated: 'Pipeline activated and synced!',
734
+ fillAllFields: 'Please fill in all fields',
735
+ enterPipelineName: 'Please enter a pipeline name',
736
+ keyExists: 'A pipeline with this key already exists',
737
+ confirmDelete: 'Are you sure you want to delete this pipeline?',
738
+ columnAdded: 'Column added to catalog!',
739
+ },
740
+ fr: {
741
+ backBtn: '← Tableau de bord',
742
+ title: 'Configurateur',
743
+ titleSpan: 'Pipeline',
744
+ savePipeline: 'Sauvegarder',
745
+ activateSync: 'Activer & Sync',
746
+ pipelines: 'Pipelines',
747
+ newPipeline: '+ Nouveau Pipeline',
748
+ noSelected: 'Aucun Pipeline Sélectionné',
749
+ noSelectedDesc: 'Sélectionnez un pipeline dans la liste ou créez-en un nouveau.',
750
+ createFirst: 'Créer le Premier Pipeline',
751
+ pipelineName: 'Nom du Pipeline',
752
+ version: 'Version',
753
+ delete: 'Supprimer',
754
+ description: 'Description (optionnel)',
755
+ descPlaceholder: 'Brève description de ce pipeline',
756
+ definition: 'DÉFINITION',
757
+ definitionDesc: 'Colonnes de qualification et préparation',
758
+ action: 'ACTION',
759
+ actionDesc: "Colonnes d'implémentation et validation",
760
+ finish: 'FINALISATION',
761
+ finishDesc: 'Colonnes de finalisation et déploiement',
762
+ catalog: 'Catalogue de Colonnes',
763
+ catalogHint: '(glissez les colonnes vers les segments ci-dessus)',
764
+ addColumn: '+ Ajouter Colonne',
765
+ syncPreview: 'Aperçu Sync',
766
+ createNewPipeline: 'Créer un Nouveau Pipeline',
767
+ create: 'Créer',
768
+ cancel: 'Annuler',
769
+ addColumnTitle: 'Ajouter une Nouvelle Colonne au Catalogue',
770
+ addColumnDesc: 'Décrivez votre idée et Claude générera le prompt en EN/FR.',
771
+ columnName: 'Nom de la Colonne',
772
+ segment: 'Segment',
773
+ segmentDefinition: 'Définition (préparation)',
774
+ segmentAction: 'Action (implémentation)',
775
+ segmentFinish: 'Finalisation (déploiement)',
776
+ describeColumn: 'Décrivez ce que cette colonne doit faire',
777
+ generateClaude: 'Générer avec Claude',
778
+ generating: 'Génération...',
779
+ promptPreview: 'Prompt de la Colonne',
780
+ loading: 'Chargement...',
781
+ noPrompt: '(Aucun prompt disponible pour cette colonne)',
782
+ columnBelongsTo: 'Cette colonne appartient au segment',
783
+ segmentLabel: '',
784
+ cannotMoveBetween: 'Les colonnes ne peuvent pas être déplacées entre segments',
785
+ columnExists: 'La colonne existe déjà dans ce segment',
786
+ pipelineSaved: 'Pipeline sauvegardé !',
787
+ pipelineCreated: 'Pipeline créé !',
788
+ pipelineDeleted: 'Pipeline supprimé',
789
+ pipelineActivated: 'Pipeline activé et synchronisé !',
790
+ fillAllFields: 'Veuillez remplir tous les champs',
791
+ enterPipelineName: 'Veuillez entrer un nom de pipeline',
792
+ keyExists: 'Un pipeline avec cette clé existe déjà',
793
+ confirmDelete: 'Êtes-vous sûr de vouloir supprimer ce pipeline ?',
794
+ columnAdded: 'Colonne ajoutée au catalogue !',
795
+ }
796
+ };
797
+
798
+ function t(key) {
799
+ return translations[currentLang]?.[key] || translations['en'][key] || key;
800
+ }
801
+
802
+ function updateUILanguage() {
803
+ // Header
804
+ document.querySelector('.back-btn').textContent = t('backBtn');
805
+ document.querySelector('.title span').textContent = t('titleSpan');
806
+ document.querySelector('.title').childNodes[2].textContent = ' ' + t('title');
807
+ document.getElementById('savePipelineBtn').textContent = t('savePipeline');
808
+ document.getElementById('activateBtn').textContent = t('activateSync');
809
+
810
+ // Sidebar
811
+ document.querySelector('.sidebar h2').textContent = t('pipelines');
812
+ document.getElementById('newPipelineBtn').textContent = t('newPipeline');
813
+
814
+ // Empty state
815
+ document.querySelector('#emptyState h3').textContent = t('noSelected');
816
+ document.querySelector('#emptyState p').textContent = t('noSelectedDesc');
817
+ document.getElementById('createFirstBtn').textContent = t('createFirst');
818
+
819
+ // Pipeline form
820
+ document.querySelector('label[for="pipelineName"]')?.setAttribute('data-text', t('pipelineName'));
821
+ document.querySelectorAll('.pipeline-header .form-group label')[0].textContent = t('pipelineName');
822
+ document.querySelectorAll('.pipeline-header .form-group label')[1].textContent = t('version');
823
+ document.getElementById('deletePipelineBtn').textContent = t('delete');
824
+ document.querySelectorAll('.pipeline-header .form-group label')[2].textContent = t('description');
825
+ document.getElementById('pipelineDescription').placeholder = t('descPlaceholder');
826
+
827
+ // Segments
828
+ document.querySelectorAll('.segment.definition .segment-title')[0].textContent = t('definition');
829
+ document.querySelectorAll('.segment.definition .segment-desc')[0].textContent = t('definitionDesc');
830
+ document.querySelectorAll('.segment.action .segment-title')[0].textContent = t('action');
831
+ document.querySelectorAll('.segment.action .segment-desc')[0].textContent = t('actionDesc');
832
+ document.querySelectorAll('.segment.finish .segment-title')[0].textContent = t('finish');
833
+ document.querySelectorAll('.segment.finish .segment-desc')[0].textContent = t('finishDesc');
834
+
835
+ // Catalog
836
+ const catalogH2 = document.querySelector('.catalog h2');
837
+ catalogH2.childNodes[0].textContent = t('catalog') + ' ';
838
+ catalogH2.querySelector('span').textContent = t('catalogHint');
839
+ catalogH2.querySelector('button').textContent = t('addColumn');
840
+
841
+ // Sync preview
842
+ document.querySelector('#syncPreview h3').textContent = t('syncPreview');
843
+
844
+ // New pipeline modal
845
+ document.querySelector('#newPipelineModal h3').textContent = t('createNewPipeline');
846
+ document.querySelector('#newPipelineModal label').textContent = t('pipelineName');
847
+ document.querySelectorAll('#newPipelineModal .btn-secondary')[0].textContent = t('cancel');
848
+ document.querySelectorAll('#newPipelineModal .btn-primary')[0].textContent = t('create');
849
+
850
+ // Add column modal
851
+ document.querySelector('#addColumnModal h3').textContent = t('addColumnTitle');
852
+ document.querySelector('#addColumnModal p').textContent = t('addColumnDesc');
853
+ document.querySelectorAll('#addColumnModal label')[0].textContent = t('columnName');
854
+ document.querySelectorAll('#addColumnModal label')[1].textContent = t('segment');
855
+ document.querySelectorAll('#addColumnModal label')[2].textContent = t('describeColumn');
856
+ document.querySelectorAll('#addColumnModal select option')[0].textContent = t('segmentDefinition');
857
+ document.querySelectorAll('#addColumnModal select option')[1].textContent = t('segmentAction');
858
+ document.querySelectorAll('#addColumnModal select option')[2].textContent = t('segmentFinish');
859
+ document.querySelectorAll('#addColumnModal .btn-secondary')[0].textContent = t('cancel');
860
+ document.getElementById('generateColumnText').textContent = t('generateClaude');
861
+ document.getElementById('generateColumnSpinner').textContent = t('generating');
862
+
863
+ // Prompt preview modal
864
+ document.getElementById('promptPreviewTitle').textContent = t('promptPreview');
865
+ }
866
+
867
+ // State
868
+ let catalog = null;
869
+ let pipelines = [];
870
+ let currentPipelineKey = null;
871
+ let currentPipeline = null;
872
+ let hasChanges = false;
873
+ let currentPromptSlug = null;
874
+ let currentPromptLang = 'en';
875
+ let currentLang = localStorage.getItem('autocode-lang') || 'en';
876
+
877
+ // Elements
878
+ const pipelineList = document.getElementById('pipelineList');
879
+ const mainContent = document.getElementById('mainContent');
880
+ const emptyState = document.getElementById('emptyState');
881
+ const pipelineEditor = document.getElementById('pipelineEditor');
882
+ const savePipelineBtn = document.getElementById('savePipelineBtn');
883
+ const activateBtn = document.getElementById('activateBtn');
884
+ const notification = document.getElementById('notification');
885
+
886
+ // Load initial data
887
+ async function init() {
888
+ await Promise.all([loadCatalog(), loadPipelines()]);
889
+ renderCatalog();
890
+ renderPipelineList();
891
+ setupDragAndDrop();
892
+ }
893
+
894
+ // Load catalog
895
+ async function loadCatalog() {
896
+ try {
897
+ const res = await fetch('/api/catalog');
898
+ const data = await res.json();
899
+ if (data.success) {
900
+ catalog = data.data;
901
+ }
902
+ } catch (e) {
903
+ showNotification('Failed to load catalog: ' + e.message, 'error');
904
+ }
905
+ }
906
+
907
+ // Load pipelines
908
+ async function loadPipelines() {
909
+ try {
910
+ const res = await fetch('/api/pipelines');
911
+ const data = await res.json();
912
+ if (data.success) {
913
+ pipelines = data.data;
914
+ }
915
+ } catch (e) {
916
+ showNotification('Failed to load pipelines: ' + e.message, 'error');
917
+ }
918
+ }
919
+
920
+ // Render catalog
921
+ function renderCatalog() {
922
+ if (!catalog) return;
923
+ const container = document.getElementById('catalogContent');
924
+ container.innerHTML = '<div class="catalog-grid" id="catalogGrid"></div>';
925
+ const grid = document.getElementById('catalogGrid');
926
+
927
+ for (const [segment, data] of Object.entries(catalog.segments)) {
928
+ const segmentDiv = document.createElement('div');
929
+ segmentDiv.className = 'catalog-segment ' + segment;
930
+
931
+ // Header with arrow and title
932
+ const headerDiv = document.createElement('div');
933
+ headerDiv.className = 'catalog-segment-header';
934
+ headerDiv.innerHTML = '<span class="catalog-segment-arrow">↑</span><span class="catalog-segment-title">' + segment.toUpperCase() + '</span>';
935
+ segmentDiv.appendChild(headerDiv);
936
+
937
+ const columnsDiv = document.createElement('div');
938
+ columnsDiv.className = 'catalog-columns';
939
+
940
+ for (const col of data.columns) {
941
+ const item = document.createElement('div');
942
+ item.className = 'catalog-item';
943
+ item.draggable = true;
944
+ item.dataset.slug = col.slug;
945
+ item.dataset.name = col.name;
946
+ item.dataset.segment = segment;
947
+ item.title = col.description + ' (click to preview prompt)';
948
+ item.innerHTML = '<span class="catalog-item-name">' + escapeHtml(col.name) + '</span><span class="catalog-item-eye">👁</span>';
949
+ item.onclick = (e) => {
950
+ if (!e.target.classList.contains('dragging')) {
951
+ openPromptPreview(col.slug, col.name);
952
+ }
953
+ };
954
+ columnsDiv.appendChild(item);
955
+ }
956
+
957
+ segmentDiv.appendChild(columnsDiv);
958
+ grid.appendChild(segmentDiv);
959
+ }
960
+ }
961
+
962
+ // Render pipeline list
963
+ function renderPipelineList() {
964
+ pipelineList.innerHTML = '';
965
+ for (const p of pipelines) {
966
+ const item = document.createElement('div');
967
+ item.className = 'pipeline-item' + (p.name === currentPipelineKey ? ' active' : '');
968
+ item.innerHTML = '<div class="name">' + escapeHtml(p.pipeline.name) +
969
+ (p.isActive ? '<span class="badge">Active</span>' : '') +
970
+ '</div><div class="meta">v' + escapeHtml(p.pipeline.version) + '</div>';
971
+ item.onclick = () => selectPipeline(p.name);
972
+ pipelineList.appendChild(item);
973
+ }
974
+
975
+ if (pipelines.length === 0) {
976
+ emptyState.style.display = '';
977
+ pipelineEditor.style.display = 'none';
978
+ }
979
+ }
980
+
981
+ // Select a pipeline
982
+ function selectPipeline(key) {
983
+ const p = pipelines.find(p => p.name === key);
984
+ if (!p) return;
985
+
986
+ currentPipelineKey = key;
987
+ currentPipeline = JSON.parse(JSON.stringify(p.pipeline)); // Deep copy
988
+
989
+ emptyState.style.display = 'none';
990
+ pipelineEditor.style.display = '';
991
+
992
+ document.getElementById('pipelineName').value = currentPipeline.name;
993
+ document.getElementById('pipelineVersion').value = currentPipeline.version;
994
+ document.getElementById('pipelineDescription').value = currentPipeline.description || '';
995
+
996
+ renderSegmentColumns();
997
+ renderPipelineList();
998
+
999
+ hasChanges = false;
1000
+ updateButtons();
1001
+ }
1002
+
1003
+ // Render segment columns
1004
+ function renderSegmentColumns() {
1005
+ ['definition', 'action', 'finish'].forEach(segment => {
1006
+ const container = document.getElementById(segment + 'Columns');
1007
+ container.innerHTML = '';
1008
+
1009
+ const columns = currentPipeline[segment] || [];
1010
+ columns.forEach((col, index) => {
1011
+ const item = createColumnItem(col, segment, index);
1012
+ container.appendChild(item);
1013
+ });
1014
+ });
1015
+ }
1016
+
1017
+ // Create a column item element
1018
+ function createColumnItem(col, segment, index) {
1019
+ const item = document.createElement('div');
1020
+ item.className = 'column-item';
1021
+ item.draggable = true;
1022
+ item.dataset.segment = segment;
1023
+ item.dataset.index = index;
1024
+ item.innerHTML =
1025
+ '<span class="drag-handle">⋮⋮</span>' +
1026
+ '<span class="name">' + escapeHtml(col.name) + '</span>' +
1027
+ '<button class="remove-btn" title="Remove">✕</button>';
1028
+
1029
+ item.querySelector('.remove-btn').onclick = (e) => {
1030
+ e.stopPropagation();
1031
+ removeColumn(segment, index);
1032
+ };
1033
+
1034
+ return item;
1035
+ }
1036
+
1037
+ // Add column to segment
1038
+ function addColumn(segment, slug, name) {
1039
+ if (!currentPipeline) return;
1040
+ if (!currentPipeline[segment]) currentPipeline[segment] = [];
1041
+
1042
+ // Check if already exists
1043
+ if (currentPipeline[segment].some(c => c.slug.includes(slug))) {
1044
+ showNotification('Column already exists in this segment', 'warning');
1045
+ return;
1046
+ }
1047
+
1048
+ currentPipeline[segment].push({ slug, name });
1049
+ renderSegmentColumns();
1050
+ hasChanges = true;
1051
+ updateButtons();
1052
+ }
1053
+
1054
+ // Remove column from segment
1055
+ function removeColumn(segment, index) {
1056
+ if (!currentPipeline || !currentPipeline[segment]) return;
1057
+ currentPipeline[segment].splice(index, 1);
1058
+ renderSegmentColumns();
1059
+ hasChanges = true;
1060
+ updateButtons();
1061
+ }
1062
+
1063
+ // Setup drag and drop
1064
+ function setupDragAndDrop() {
1065
+ document.addEventListener('dragstart', (e) => {
1066
+ if (e.target.classList.contains('catalog-item') || e.target.classList.contains('column-item')) {
1067
+ e.target.classList.add('dragging');
1068
+ window.currentDragSegment = e.target.dataset.segment;
1069
+ e.dataTransfer.setData('text/plain', JSON.stringify({
1070
+ slug: e.target.dataset.slug,
1071
+ name: e.target.dataset.name,
1072
+ segment: e.target.dataset.segment,
1073
+ index: e.target.dataset.index
1074
+ }));
1075
+ }
1076
+ });
1077
+
1078
+ document.addEventListener('dragend', (e) => {
1079
+ e.target.classList.remove('dragging');
1080
+ window.currentDragSegment = null;
1081
+ document.querySelectorAll('.dragover').forEach(el => el.classList.remove('dragover'));
1082
+ document.querySelectorAll('.dragover-invalid').forEach(el => el.classList.remove('dragover-invalid'));
1083
+ });
1084
+
1085
+ document.querySelectorAll('.segment-columns').forEach(container => {
1086
+ container.addEventListener('dragover', (e) => {
1087
+ e.preventDefault();
1088
+ // Only show dragover if segment is compatible
1089
+ const draggedSegment = window.currentDragSegment;
1090
+ const targetSegment = container.dataset.segment;
1091
+ if (!draggedSegment || draggedSegment === targetSegment) {
1092
+ container.classList.add('dragover');
1093
+ } else {
1094
+ container.classList.add('dragover-invalid');
1095
+ }
1096
+ });
1097
+
1098
+ container.addEventListener('dragleave', () => {
1099
+ container.classList.remove('dragover');
1100
+ container.classList.remove('dragover-invalid');
1101
+ });
1102
+
1103
+ container.addEventListener('drop', (e) => {
1104
+ e.preventDefault();
1105
+ container.classList.remove('dragover');
1106
+ container.classList.remove('dragover-invalid');
1107
+
1108
+ const data = JSON.parse(e.dataTransfer.getData('text/plain'));
1109
+ const targetSegment = container.dataset.segment;
1110
+
1111
+ // Check if it's from catalog or from another segment
1112
+ if (data.index === undefined) {
1113
+ // From catalog - check segment compatibility
1114
+ if (data.segment !== targetSegment) {
1115
+ showNotification('This column belongs to the "' + data.segment + '" segment', 'warning');
1116
+ return;
1117
+ }
1118
+ addColumn(targetSegment, data.slug, data.name);
1119
+ } else {
1120
+ // Reordering within same segment only
1121
+ const fromSegment = data.segment;
1122
+ const fromIndex = parseInt(data.index);
1123
+
1124
+ if (fromSegment !== targetSegment) {
1125
+ showNotification('Columns cannot be moved between segments', 'warning');
1126
+ return;
1127
+ }
1128
+
1129
+ // Reorder within same segment
1130
+ const items = currentPipeline[fromSegment];
1131
+ const [removed] = items.splice(fromIndex, 1);
1132
+ // Find drop position
1133
+ const dropIndex = getDropIndex(e, container);
1134
+ items.splice(dropIndex, 0, removed);
1135
+
1136
+ renderSegmentColumns();
1137
+ hasChanges = true;
1138
+ updateButtons();
1139
+ }
1140
+ });
1141
+ });
1142
+ }
1143
+
1144
+ function getDropIndex(e, container) {
1145
+ const items = [...container.querySelectorAll('.column-item:not(.dragging)')];
1146
+ const y = e.clientY;
1147
+ let insertIndex = items.length;
1148
+
1149
+ for (let i = 0; i < items.length; i++) {
1150
+ const rect = items[i].getBoundingClientRect();
1151
+ if (y < rect.top + rect.height / 2) {
1152
+ insertIndex = i;
1153
+ break;
1154
+ }
1155
+ }
1156
+
1157
+ return insertIndex;
1158
+ }
1159
+
1160
+ // Update buttons state
1161
+ function updateButtons() {
1162
+ savePipelineBtn.disabled = !hasChanges || !currentPipeline;
1163
+ activateBtn.disabled = !currentPipeline || hasChanges;
1164
+
1165
+ // Validate pipeline
1166
+ if (currentPipeline) {
1167
+ const valid = validatePipeline();
1168
+ savePipelineBtn.disabled = savePipelineBtn.disabled || !valid;
1169
+ }
1170
+ }
1171
+
1172
+ // Validate pipeline
1173
+ function validatePipeline() {
1174
+ if (!currentPipeline) return false;
1175
+ if (!currentPipeline.definition || currentPipeline.definition.length === 0) return false;
1176
+ if (!currentPipeline.action || currentPipeline.action.length === 0) return false;
1177
+ if (!currentPipeline.finish || currentPipeline.finish.length === 0) return false;
1178
+ return true;
1179
+ }
1180
+
1181
+ // Save pipeline
1182
+ async function savePipeline() {
1183
+ if (!currentPipelineKey || !currentPipeline) return;
1184
+
1185
+ // Update from form
1186
+ currentPipeline.name = document.getElementById('pipelineName').value;
1187
+ currentPipeline.version = document.getElementById('pipelineVersion').value;
1188
+ currentPipeline.description = document.getElementById('pipelineDescription').value;
1189
+
1190
+ // Renumber columns
1191
+ let index = 0;
1192
+ ['definition', 'action', 'finish'].forEach(segment => {
1193
+ currentPipeline[segment] = currentPipeline[segment].map(col => ({
1194
+ slug: String(index++).padStart(2, '0') + '_' + col.slug.replace(/^\\d{2}_/, ''),
1195
+ name: col.name
1196
+ }));
1197
+ });
1198
+
1199
+ try {
1200
+ const res = await fetch('/api/pipelines', {
1201
+ method: 'POST',
1202
+ headers: { 'Content-Type': 'application/json' },
1203
+ body: JSON.stringify({
1204
+ name: currentPipelineKey,
1205
+ pipeline: currentPipeline,
1206
+ setActive: false
1207
+ })
1208
+ });
1209
+ const data = await res.json();
1210
+ if (data.success) {
1211
+ showNotification('Pipeline saved!');
1212
+ hasChanges = false;
1213
+ await loadPipelines();
1214
+ renderPipelineList();
1215
+ updateButtons();
1216
+ } else {
1217
+ showNotification('Error: ' + data.error, 'error');
1218
+ }
1219
+ } catch (e) {
1220
+ showNotification('Error: ' + e.message, 'error');
1221
+ }
1222
+ }
1223
+
1224
+ // Activate pipeline
1225
+ async function activatePipeline() {
1226
+ if (!currentPipelineKey) return;
1227
+
1228
+ try {
1229
+ const res = await fetch('/api/pipelines/' + encodeURIComponent(currentPipelineKey) + '/activate', {
1230
+ method: 'PUT'
1231
+ });
1232
+ const data = await res.json();
1233
+ if (data.success) {
1234
+ showNotification('Pipeline activated and synced!');
1235
+ await loadPipelines();
1236
+ renderPipelineList();
1237
+
1238
+ // Show sync results if any
1239
+ if (data.data && (data.data.added.length || data.data.removed.length || data.data.transitioned.length)) {
1240
+ showSyncResults(data.data);
1241
+ }
1242
+ } else {
1243
+ showNotification('Error: ' + data.error, 'error');
1244
+ }
1245
+ } catch (e) {
1246
+ showNotification('Error: ' + e.message, 'error');
1247
+ }
1248
+ }
1249
+
1250
+ // Show sync results
1251
+ function showSyncResults(result) {
1252
+ const preview = document.getElementById('syncPreview');
1253
+ const changes = document.getElementById('syncChanges');
1254
+
1255
+ changes.innerHTML = '';
1256
+
1257
+ if (result.added.length) {
1258
+ changes.innerHTML += '<div class="sync-group added"><h4>Added</h4><ul>' +
1259
+ result.added.map(s => '<li>+ ' + escapeHtml(s) + '</li>').join('') + '</ul></div>';
1260
+ }
1261
+ if (result.removed.length) {
1262
+ changes.innerHTML += '<div class="sync-group removed"><h4>Removed</h4><ul>' +
1263
+ result.removed.map(s => '<li>- ' + escapeHtml(s) + '</li>').join('') + '</ul></div>';
1264
+ }
1265
+ if (result.transitioned.length) {
1266
+ changes.innerHTML += '<div class="sync-group renamed"><h4>Tickets in Transition</h4><ul>' +
1267
+ result.transitioned.map(s => '<li>' + escapeHtml(s) + '</li>').join('') + '</ul></div>';
1268
+ }
1269
+
1270
+ preview.style.display = changes.innerHTML ? '' : 'none';
1271
+ }
1272
+
1273
+ // Delete pipeline
1274
+ async function deletePipeline() {
1275
+ if (!currentPipelineKey) return;
1276
+ if (!confirm('Are you sure you want to delete this pipeline?')) return;
1277
+
1278
+ try {
1279
+ const res = await fetch('/api/pipelines/' + encodeURIComponent(currentPipelineKey), {
1280
+ method: 'DELETE'
1281
+ });
1282
+ const data = await res.json();
1283
+ if (data.success) {
1284
+ showNotification('Pipeline deleted');
1285
+ currentPipelineKey = null;
1286
+ currentPipeline = null;
1287
+ await loadPipelines();
1288
+ renderPipelineList();
1289
+ emptyState.style.display = '';
1290
+ pipelineEditor.style.display = 'none';
1291
+ } else {
1292
+ showNotification('Error: ' + data.error, 'error');
1293
+ }
1294
+ } catch (e) {
1295
+ showNotification('Error: ' + e.message, 'error');
1296
+ }
1297
+ }
1298
+
1299
+ // Convert to kebab-case
1300
+ function toKebabCase(str) {
1301
+ return str
1302
+ .toLowerCase()
1303
+ .normalize('NFD').replace(/[\u0300-\u036f]/g, '') // Remove accents
1304
+ .replace(/[^a-z0-9]+/g, '-') // Replace non-alphanumeric with dashes
1305
+ .replace(/^-+|-+$/g, '') // Trim dashes
1306
+ .replace(/-+/g, '-'); // Collapse multiple dashes
1307
+ }
1308
+
1309
+ // New pipeline modal
1310
+ function openNewPipelineModal() {
1311
+ document.getElementById('newPipelineModal').classList.add('show');
1312
+ document.getElementById('newPipelineName').value = '';
1313
+ document.getElementById('newPipelineKeyPreview').textContent = '-';
1314
+ }
1315
+
1316
+ function closeNewPipelineModal() {
1317
+ document.getElementById('newPipelineModal').classList.remove('show');
1318
+ }
1319
+
1320
+ async function confirmNewPipeline() {
1321
+ const name = document.getElementById('newPipelineName').value.trim();
1322
+ const key = toKebabCase(name);
1323
+
1324
+ if (!name || !key) {
1325
+ showNotification('Please enter a pipeline name', 'warning');
1326
+ return;
1327
+ }
1328
+
1329
+ // Check if key already exists
1330
+ if (pipelines.some(p => p.name === key)) {
1331
+ showNotification('A pipeline with this key already exists', 'warning');
1332
+ return;
1333
+ }
1334
+
1335
+ // Create empty pipeline
1336
+ const newPipeline = {
1337
+ name: name,
1338
+ version: '1.0.0',
1339
+ description: '',
1340
+ definition: [{ slug: 'backlog', name: 'Backlog' }],
1341
+ action: [{ slug: 'in-progress', name: 'In Progress' }],
1342
+ finish: [{ slug: 'done', name: 'Done' }]
1343
+ };
1344
+
1345
+ try {
1346
+ const res = await fetch('/api/pipelines', {
1347
+ method: 'POST',
1348
+ headers: { 'Content-Type': 'application/json' },
1349
+ body: JSON.stringify({
1350
+ name: key,
1351
+ pipeline: newPipeline,
1352
+ setActive: pipelines.length === 0
1353
+ })
1354
+ });
1355
+ const data = await res.json();
1356
+ if (data.success) {
1357
+ closeNewPipelineModal();
1358
+ await loadPipelines();
1359
+ renderPipelineList();
1360
+ selectPipeline(key);
1361
+ showNotification('Pipeline created!');
1362
+ } else {
1363
+ showNotification('Error: ' + data.error, 'error');
1364
+ }
1365
+ } catch (e) {
1366
+ showNotification('Error: ' + e.message, 'error');
1367
+ }
1368
+ }
1369
+
1370
+ // Show notification
1371
+ function showNotification(msg, type = 'success') {
1372
+ notification.textContent = msg;
1373
+ notification.className = 'notification show' + (type !== 'success' ? ' ' + type : '');
1374
+ setTimeout(() => notification.className = 'notification', 3000);
1375
+ }
1376
+
1377
+ // Prompt preview
1378
+ async function openPromptPreview(slug, name) {
1379
+ currentPromptSlug = slug;
1380
+ currentPromptLang = currentLang || 'en';
1381
+ document.getElementById('promptPreviewTitle').textContent = name;
1382
+ document.getElementById('promptPreviewModal').classList.add('show');
1383
+ updatePromptLangButtons();
1384
+ await loadPromptContent();
1385
+ }
1386
+
1387
+ function closePromptPreview() {
1388
+ document.getElementById('promptPreviewModal').classList.remove('show');
1389
+ currentPromptSlug = null;
1390
+ }
1391
+
1392
+ async function switchPromptLang(lang) {
1393
+ currentPromptLang = lang;
1394
+ updatePromptLangButtons();
1395
+ await loadPromptContent();
1396
+ }
1397
+
1398
+ function updatePromptLangButtons() {
1399
+ document.querySelectorAll('#promptPreviewModal .lang-btn').forEach(btn => {
1400
+ btn.classList.toggle('active', btn.dataset.lang === currentPromptLang);
1401
+ });
1402
+ }
1403
+
1404
+ async function loadPromptContent() {
1405
+ const content = document.getElementById('promptPreviewContent');
1406
+ content.textContent = 'Loading...';
1407
+
1408
+ try {
1409
+ const res = await fetch('/api/prompts/' + encodeURIComponent(currentPromptSlug) + '?lang=' + currentPromptLang);
1410
+ const data = await res.json();
1411
+ if (data.success && data.data) {
1412
+ content.textContent = data.data;
1413
+ } else {
1414
+ content.textContent = '(No prompt available for this column in ' + currentPromptLang.toUpperCase() + ')';
1415
+ }
1416
+ } catch (e) {
1417
+ content.textContent = 'Error loading prompt: ' + e.message;
1418
+ }
1419
+ }
1420
+
1421
+ // Add Column Modal
1422
+ function openAddColumnModal() {
1423
+ document.getElementById('addColumnModal').classList.add('show');
1424
+ document.getElementById('newColumnName').value = '';
1425
+ document.getElementById('newColumnDescription').value = '';
1426
+ document.getElementById('newColumnSegment').value = 'action';
1427
+ }
1428
+
1429
+ function closeAddColumnModal() {
1430
+ document.getElementById('addColumnModal').classList.remove('show');
1431
+ }
1432
+
1433
+ async function generateColumn() {
1434
+ const name = document.getElementById('newColumnName').value.trim();
1435
+ const segment = document.getElementById('newColumnSegment').value;
1436
+ const description = document.getElementById('newColumnDescription').value.trim();
1437
+
1438
+ if (!name || !description) {
1439
+ showNotification('Please fill in all fields', 'warning');
1440
+ return;
1441
+ }
1442
+
1443
+ // Show loading state
1444
+ document.getElementById('generateColumnText').style.display = 'none';
1445
+ document.getElementById('generateColumnSpinner').style.display = 'inline';
1446
+ document.getElementById('generateColumnBtn').disabled = true;
1447
+
1448
+ try {
1449
+ const res = await fetch('/api/catalog/generate', {
1450
+ method: 'POST',
1451
+ headers: { 'Content-Type': 'application/json' },
1452
+ body: JSON.stringify({ name, segment, description })
1453
+ });
1454
+ const data = await res.json();
1455
+
1456
+ if (data.success) {
1457
+ showNotification('Column "' + name + '" added to catalog!');
1458
+ closeAddColumnModal();
1459
+ // Reload catalog
1460
+ await loadCatalog();
1461
+ renderCatalog();
1462
+ } else {
1463
+ showNotification('Error: ' + data.error, 'error');
1464
+ }
1465
+ } catch (e) {
1466
+ showNotification('Error: ' + e.message, 'error');
1467
+ } finally {
1468
+ document.getElementById('generateColumnText').style.display = 'inline';
1469
+ document.getElementById('generateColumnSpinner').style.display = 'none';
1470
+ document.getElementById('generateColumnBtn').disabled = false;
1471
+ }
1472
+ }
1473
+
1474
+ // Escape HTML
1475
+ function escapeHtml(str) {
1476
+ const div = document.createElement('div');
1477
+ div.textContent = str || '';
1478
+ return div.innerHTML;
1479
+ }
1480
+
1481
+ // Event listeners
1482
+ document.getElementById('newPipelineBtn').onclick = openNewPipelineModal;
1483
+ document.getElementById('createFirstBtn').onclick = openNewPipelineModal;
1484
+ document.getElementById('savePipelineBtn').onclick = savePipeline;
1485
+ document.getElementById('activateBtn').onclick = activatePipeline;
1486
+ document.getElementById('deletePipelineBtn').onclick = deletePipeline;
1487
+
1488
+ // Track form changes
1489
+ ['pipelineName', 'pipelineVersion', 'pipelineDescription'].forEach(id => {
1490
+ document.getElementById(id).addEventListener('input', () => {
1491
+ hasChanges = true;
1492
+ updateButtons();
1493
+ });
1494
+ });
1495
+
1496
+ // Live update key preview in new pipeline modal
1497
+ document.getElementById('newPipelineName').addEventListener('input', (e) => {
1498
+ const name = e.target.value.trim();
1499
+ const key = toKebabCase(name);
1500
+ document.getElementById('newPipelineKeyPreview').textContent = key || '-';
1501
+ });
1502
+
1503
+ // Language switcher
1504
+ function initLangSwitcher() {
1505
+ const buttons = document.querySelectorAll('#lang-switcher .lang-btn');
1506
+ buttons.forEach(btn => {
1507
+ if (btn.dataset.lang === currentLang) {
1508
+ btn.classList.add('active');
1509
+ }
1510
+ btn.addEventListener('click', () => {
1511
+ currentLang = btn.dataset.lang;
1512
+ localStorage.setItem('autocode-lang', currentLang);
1513
+ buttons.forEach(b => b.classList.remove('active'));
1514
+ btn.classList.add('active');
1515
+ // Update default prompt language
1516
+ currentPromptLang = currentLang;
1517
+ // Update all UI text
1518
+ updateUILanguage();
1519
+ });
1520
+ });
1521
+ }
1522
+
1523
+ // Initialize
1524
+ init();
1525
+ initLangSwitcher();
1526
+ updateUILanguage();
1527
+ </script>
1528
+ </body>
1529
+ </html>`;
1530
+ }
1531
+ //# sourceMappingURL=pipeline-configurator.js.map