@autocode-cli/autocode 0.1.5 → 0.1.7

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 +330 -6
  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 +1570 -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 +99 -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,1570 @@
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 id="labelPipelineName">Pipeline Name</label>
546
+ <input type="text" id="pipelineName" placeholder="e.g., Default Pipeline">
547
+ </div>
548
+ <div class="form-group">
549
+ <label id="labelVersion">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 id="labelDescription">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" id="segmentTitleDefinition">Definition</span>
565
+ </div>
566
+ <div class="segment-desc" id="segmentDescDefinition">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" id="segmentTitleAction">Action</span>
575
+ </div>
576
+ <div class="segment-desc" id="segmentDescAction">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" id="segmentTitleFinish">Finish</span>
585
+ </div>
586
+ <div class="segment-desc" id="segmentDescFinish">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 id="addColumnError" class="form-error" style="display: none; color: #e74c3c; background: #fdf2f2; padding: 8px 12px; border-radius: 4px; margin-top: 12px; font-size: 13px;"></div>
655
+ <div class="modal-actions">
656
+ <button class="btn btn-secondary" onclick="closeAddColumnModal()">Cancel</button>
657
+ <button class="btn btn-primary" id="generateColumnBtn" onclick="generateColumn()">
658
+ <span id="generateColumnText">Generate with Claude</span>
659
+ <span id="generateColumnSpinner" style="display:none;">Generating...</span>
660
+ </button>
661
+ </div>
662
+ </div>
663
+ </div>
664
+
665
+ <!-- Prompt Preview Modal -->
666
+ <div class="modal-overlay" id="promptPreviewModal">
667
+ <div class="modal" style="max-width: 700px; width: 90%;">
668
+ <div class="modal-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
669
+ <h3 id="promptPreviewTitle" style="margin: 0;">Column Prompt</h3>
670
+ <div style="display: flex; gap: 8px; align-items: center;">
671
+ <div class="lang-toggle">
672
+ <button class="btn btn-secondary lang-btn active" data-lang="en" onclick="switchPromptLang('en')">EN</button>
673
+ <button class="btn btn-secondary lang-btn" data-lang="fr" onclick="switchPromptLang('fr')">FR</button>
674
+ </div>
675
+ <button class="btn btn-secondary" onclick="closePromptPreview()" style="padding: 4px 10px;">✕</button>
676
+ </div>
677
+ </div>
678
+ <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>
679
+ </div>
680
+ </div>
681
+
682
+ <script>
683
+ // Translations
684
+ const translations = {
685
+ en: {
686
+ backBtn: '← Dashboard',
687
+ title: 'Configurator',
688
+ titleSpan: 'Pipeline',
689
+ savePipeline: 'Save Pipeline',
690
+ activateSync: 'Activate & Sync',
691
+ pipelines: 'Pipelines',
692
+ newPipeline: '+ New Pipeline',
693
+ noSelected: 'No Pipeline Selected',
694
+ noSelectedDesc: 'Select a pipeline from the list or create a new one.',
695
+ createFirst: 'Create First Pipeline',
696
+ pipelineName: 'Pipeline Name',
697
+ version: 'Version',
698
+ delete: 'Delete',
699
+ description: 'Description (optional)',
700
+ descPlaceholder: 'Short description of this pipeline',
701
+ definition: 'DEFINITION',
702
+ definitionDesc: 'Qualification and preparation columns',
703
+ action: 'ACTION',
704
+ actionDesc: 'Implementation and validation columns',
705
+ finish: 'FINISH',
706
+ finishDesc: 'Finalization and deployment columns',
707
+ catalog: 'Column Catalog',
708
+ catalogHint: '(drag columns to segments above)',
709
+ addColumn: '+ Add Column',
710
+ syncPreview: 'Sync Preview',
711
+ createNewPipeline: 'Create New Pipeline',
712
+ create: 'Create',
713
+ cancel: 'Cancel',
714
+ addColumnTitle: 'Add New Column to Catalog',
715
+ addColumnDesc: 'Describe your idea and Claude will generate the prompt in EN/FR.',
716
+ columnName: 'Column Name',
717
+ segment: 'Segment',
718
+ segmentDefinition: 'Definition (preparation)',
719
+ segmentAction: 'Action (implementation)',
720
+ segmentFinish: 'Finish (deployment)',
721
+ describeColumn: 'Describe what this column should do',
722
+ generateClaude: 'Generate with Claude',
723
+ generating: 'Generating...',
724
+ promptPreview: 'Column Prompt',
725
+ loading: 'Loading...',
726
+ noPrompt: '(No prompt available for this column)',
727
+ columnBelongsTo: 'This column belongs to the',
728
+ segmentLabel: 'segment',
729
+ cannotMoveBetween: 'Columns cannot be moved between segments',
730
+ columnExists: 'Column already exists in this segment',
731
+ pipelineSaved: 'Pipeline saved!',
732
+ pipelineCreated: 'Pipeline created!',
733
+ pipelineDeleted: 'Pipeline deleted',
734
+ pipelineActivated: 'Pipeline activated and synced!',
735
+ fillAllFields: 'Please fill in all fields',
736
+ enterPipelineName: 'Please enter a pipeline name',
737
+ keyExists: 'A pipeline with this key already exists',
738
+ confirmDelete: 'Are you sure you want to delete this pipeline?',
739
+ columnAdded: 'Column added to catalog!',
740
+ errorFillFields: 'Please fill in all fields',
741
+ errorColumnExists: 'A column with this name already exists in the catalog. Please choose a different name.',
742
+ errorGeneric: 'An error occurred. Please try again.',
743
+ },
744
+ fr: {
745
+ backBtn: '← Tableau de bord',
746
+ title: 'Configurateur',
747
+ titleSpan: 'Pipeline',
748
+ savePipeline: 'Sauvegarder',
749
+ activateSync: 'Activer & Sync',
750
+ pipelines: 'Pipelines',
751
+ newPipeline: '+ Nouveau Pipeline',
752
+ noSelected: 'Aucun Pipeline Sélectionné',
753
+ noSelectedDesc: 'Sélectionnez un pipeline dans la liste ou créez-en un nouveau.',
754
+ createFirst: 'Créer le Premier Pipeline',
755
+ pipelineName: 'Nom du Pipeline',
756
+ version: 'Version',
757
+ delete: 'Supprimer',
758
+ description: 'Description (optionnel)',
759
+ descPlaceholder: 'Brève description de ce pipeline',
760
+ definition: 'DÉFINITION',
761
+ definitionDesc: 'Colonnes de qualification et préparation',
762
+ action: 'ACTION',
763
+ actionDesc: "Colonnes d'implémentation et validation",
764
+ finish: 'FINALISATION',
765
+ finishDesc: 'Colonnes de finalisation et déploiement',
766
+ catalog: 'Catalogue de Colonnes',
767
+ catalogHint: '(glissez les colonnes vers les segments ci-dessus)',
768
+ addColumn: '+ Ajouter Colonne',
769
+ syncPreview: 'Aperçu Sync',
770
+ createNewPipeline: 'Créer un Nouveau Pipeline',
771
+ create: 'Créer',
772
+ cancel: 'Annuler',
773
+ addColumnTitle: 'Ajouter une Nouvelle Colonne au Catalogue',
774
+ addColumnDesc: 'Décrivez votre idée et Claude générera le prompt en EN/FR.',
775
+ columnName: 'Nom de la Colonne',
776
+ segment: 'Segment',
777
+ segmentDefinition: 'Définition (préparation)',
778
+ segmentAction: 'Action (implémentation)',
779
+ segmentFinish: 'Finalisation (déploiement)',
780
+ describeColumn: 'Décrivez ce que cette colonne doit faire',
781
+ generateClaude: 'Générer avec Claude',
782
+ generating: 'Génération...',
783
+ promptPreview: 'Prompt de la Colonne',
784
+ loading: 'Chargement...',
785
+ noPrompt: '(Aucun prompt disponible pour cette colonne)',
786
+ columnBelongsTo: 'Cette colonne appartient au segment',
787
+ segmentLabel: '',
788
+ cannotMoveBetween: 'Les colonnes ne peuvent pas être déplacées entre segments',
789
+ columnExists: 'La colonne existe déjà dans ce segment',
790
+ pipelineSaved: 'Pipeline sauvegardé !',
791
+ pipelineCreated: 'Pipeline créé !',
792
+ pipelineDeleted: 'Pipeline supprimé',
793
+ pipelineActivated: 'Pipeline activé et synchronisé !',
794
+ fillAllFields: 'Veuillez remplir tous les champs',
795
+ enterPipelineName: 'Veuillez entrer un nom de pipeline',
796
+ keyExists: 'Un pipeline avec cette clé existe déjà',
797
+ confirmDelete: 'Êtes-vous sûr de vouloir supprimer ce pipeline ?',
798
+ columnAdded: 'Colonne ajoutée au catalogue !',
799
+ errorFillFields: 'Veuillez remplir tous les champs',
800
+ errorColumnExists: 'Une colonne avec ce nom existe déjà dans le catalogue. Veuillez choisir un autre nom.',
801
+ errorGeneric: 'Une erreur est survenue. Veuillez réessayer.',
802
+ }
803
+ };
804
+
805
+ function t(key) {
806
+ return translations[currentLang]?.[key] || translations['en'][key] || key;
807
+ }
808
+
809
+ function updateUILanguage() {
810
+ const setText = (el, text) => { if (el) el.textContent = text; };
811
+ const setPlaceholder = (el, text) => { if (el) el.placeholder = text; };
812
+ const $ = (id) => document.getElementById(id);
813
+ const $q = (sel) => document.querySelector(sel);
814
+
815
+ // Header
816
+ setText($q('.back-btn'), t('backBtn'));
817
+ setText($q('.title span'), t('titleSpan'));
818
+ const titleEl = $q('.title');
819
+ if (titleEl && titleEl.childNodes[2]) titleEl.childNodes[2].textContent = ' ' + t('title');
820
+ setText($('savePipelineBtn'), t('savePipeline'));
821
+ setText($('activateBtn'), t('activateSync'));
822
+
823
+ // Sidebar
824
+ setText($q('.sidebar h2'), t('pipelines'));
825
+ setText($('newPipelineBtn'), t('newPipeline'));
826
+
827
+ // Empty state
828
+ setText($q('#emptyState h3'), t('noSelected'));
829
+ setText($q('#emptyState p'), t('noSelectedDesc'));
830
+ setText($('createFirstBtn'), t('createFirst'));
831
+
832
+ // Pipeline form labels
833
+ setText($('labelPipelineName'), t('pipelineName'));
834
+ setText($('labelVersion'), t('version'));
835
+ setText($('labelDescription'), t('description'));
836
+ setText($('deletePipelineBtn'), t('delete'));
837
+ setPlaceholder($('pipelineDescription'), t('descPlaceholder'));
838
+
839
+ // Segments
840
+ setText($('segmentTitleDefinition'), t('definition'));
841
+ setText($('segmentDescDefinition'), t('definitionDesc'));
842
+ setText($('segmentTitleAction'), t('action'));
843
+ setText($('segmentDescAction'), t('actionDesc'));
844
+ setText($('segmentTitleFinish'), t('finish'));
845
+ setText($('segmentDescFinish'), t('finishDesc'));
846
+
847
+ // Catalog
848
+ const catalogH2 = $q('.catalog h2');
849
+ if (catalogH2 && catalogH2.childNodes[0]) {
850
+ catalogH2.childNodes[0].textContent = t('catalog') + ' ';
851
+ setText(catalogH2.querySelector('span'), t('catalogHint'));
852
+ setText(catalogH2.querySelector('button'), t('addColumn'));
853
+ }
854
+
855
+ // Sync preview
856
+ setText($q('#syncPreview h3'), t('syncPreview'));
857
+
858
+ // New pipeline modal
859
+ setText($q('#newPipelineModal h3'), t('createNewPipeline'));
860
+ setText($q('#newPipelineModal label'), t('pipelineName'));
861
+ setText($q('#newPipelineModal .btn-secondary'), t('cancel'));
862
+ setText($q('#newPipelineModal .btn-primary'), t('create'));
863
+
864
+ // Add column modal
865
+ setText($q('#addColumnModal h3'), t('addColumnTitle'));
866
+ setText($q('#addColumnModal .modal > p'), t('addColumnDesc'));
867
+ const addColLabels = document.querySelectorAll('#addColumnModal label');
868
+ setText(addColLabels[0], t('columnName'));
869
+ setText(addColLabels[1], t('segment'));
870
+ setText(addColLabels[2], t('describeColumn'));
871
+ const segOpts = document.querySelectorAll('#newColumnSegment option');
872
+ setText(segOpts[0], t('segmentDefinition'));
873
+ setText(segOpts[1], t('segmentAction'));
874
+ setText(segOpts[2], t('segmentFinish'));
875
+ setText($q('#addColumnModal .btn-secondary'), t('cancel'));
876
+ setText($('generateColumnText'), t('generateClaude'));
877
+ setText($('generateColumnSpinner'), t('generating'));
878
+
879
+ // Prompt preview modal
880
+ setText($('promptPreviewTitle'), t('promptPreview'));
881
+ }
882
+
883
+ // State
884
+ let catalog = null;
885
+ let pipelines = [];
886
+ let currentPipelineKey = null;
887
+ let currentPipeline = null;
888
+ let hasChanges = false;
889
+ let currentPromptSlug = null;
890
+ let currentPromptLang = 'en';
891
+ let currentLang = localStorage.getItem('autocode-lang') || 'en';
892
+
893
+ // Elements
894
+ const pipelineList = document.getElementById('pipelineList');
895
+ const mainContent = document.getElementById('mainContent');
896
+ const emptyState = document.getElementById('emptyState');
897
+ const pipelineEditor = document.getElementById('pipelineEditor');
898
+ const savePipelineBtn = document.getElementById('savePipelineBtn');
899
+ const activateBtn = document.getElementById('activateBtn');
900
+ const notification = document.getElementById('notification');
901
+
902
+ // Load initial data
903
+ async function init() {
904
+ await Promise.all([loadCatalog(), loadPipelines()]);
905
+ renderCatalog();
906
+ renderPipelineList();
907
+ setupDragAndDrop();
908
+ }
909
+
910
+ // Load catalog
911
+ async function loadCatalog() {
912
+ try {
913
+ const res = await fetch('/api/catalog');
914
+ const data = await res.json();
915
+ if (data.success) {
916
+ catalog = data.data;
917
+ }
918
+ } catch (e) {
919
+ showNotification('Failed to load catalog: ' + e.message, 'error');
920
+ }
921
+ }
922
+
923
+ // Load pipelines
924
+ async function loadPipelines() {
925
+ try {
926
+ const res = await fetch('/api/pipelines');
927
+ const data = await res.json();
928
+ if (data.success) {
929
+ pipelines = data.data;
930
+ }
931
+ } catch (e) {
932
+ showNotification('Failed to load pipelines: ' + e.message, 'error');
933
+ }
934
+ }
935
+
936
+ // Render catalog
937
+ function renderCatalog() {
938
+ if (!catalog) return;
939
+ const container = document.getElementById('catalogContent');
940
+ container.innerHTML = '<div class="catalog-grid" id="catalogGrid"></div>';
941
+ const grid = document.getElementById('catalogGrid');
942
+
943
+ for (const [segment, data] of Object.entries(catalog.segments)) {
944
+ const segmentDiv = document.createElement('div');
945
+ segmentDiv.className = 'catalog-segment ' + segment;
946
+
947
+ // Header with arrow and title
948
+ const headerDiv = document.createElement('div');
949
+ headerDiv.className = 'catalog-segment-header';
950
+ headerDiv.innerHTML = '<span class="catalog-segment-arrow">↑</span><span class="catalog-segment-title">' + segment.toUpperCase() + '</span>';
951
+ segmentDiv.appendChild(headerDiv);
952
+
953
+ const columnsDiv = document.createElement('div');
954
+ columnsDiv.className = 'catalog-columns';
955
+
956
+ for (const col of data.columns) {
957
+ const item = document.createElement('div');
958
+ item.className = 'catalog-item';
959
+ item.draggable = true;
960
+ item.dataset.slug = col.slug;
961
+ item.dataset.name = col.name;
962
+ item.dataset.segment = segment;
963
+ item.title = col.description + ' (click to preview prompt)';
964
+ item.innerHTML = '<span class="catalog-item-name">' + escapeHtml(col.name) + '</span><span class="catalog-item-eye">👁</span>';
965
+ item.onclick = (e) => {
966
+ if (!e.target.classList.contains('dragging')) {
967
+ openPromptPreview(col.slug, col.name);
968
+ }
969
+ };
970
+ columnsDiv.appendChild(item);
971
+ }
972
+
973
+ segmentDiv.appendChild(columnsDiv);
974
+ grid.appendChild(segmentDiv);
975
+ }
976
+ }
977
+
978
+ // Render pipeline list
979
+ function renderPipelineList() {
980
+ pipelineList.innerHTML = '';
981
+ for (const p of pipelines) {
982
+ const item = document.createElement('div');
983
+ item.className = 'pipeline-item' + (p.name === currentPipelineKey ? ' active' : '');
984
+ item.innerHTML = '<div class="name">' + escapeHtml(p.pipeline.name) +
985
+ (p.isActive ? '<span class="badge">Active</span>' : '') +
986
+ '</div><div class="meta">v' + escapeHtml(p.pipeline.version) + '</div>';
987
+ item.onclick = () => selectPipeline(p.name);
988
+ pipelineList.appendChild(item);
989
+ }
990
+
991
+ if (pipelines.length === 0) {
992
+ emptyState.style.display = '';
993
+ pipelineEditor.style.display = 'none';
994
+ }
995
+ }
996
+
997
+ // Select a pipeline
998
+ function selectPipeline(key) {
999
+ const p = pipelines.find(p => p.name === key);
1000
+ if (!p) return;
1001
+
1002
+ currentPipelineKey = key;
1003
+ currentPipeline = JSON.parse(JSON.stringify(p.pipeline)); // Deep copy
1004
+
1005
+ emptyState.style.display = 'none';
1006
+ pipelineEditor.style.display = '';
1007
+
1008
+ document.getElementById('pipelineName').value = currentPipeline.name;
1009
+ document.getElementById('pipelineVersion').value = currentPipeline.version;
1010
+ document.getElementById('pipelineDescription').value = currentPipeline.description || '';
1011
+
1012
+ renderSegmentColumns();
1013
+ renderPipelineList();
1014
+
1015
+ hasChanges = false;
1016
+ updateButtons();
1017
+ }
1018
+
1019
+ // Render segment columns
1020
+ function renderSegmentColumns() {
1021
+ ['definition', 'action', 'finish'].forEach(segment => {
1022
+ const container = document.getElementById(segment + 'Columns');
1023
+ container.innerHTML = '';
1024
+
1025
+ const columns = currentPipeline[segment] || [];
1026
+ columns.forEach((col, index) => {
1027
+ const item = createColumnItem(col, segment, index);
1028
+ container.appendChild(item);
1029
+ });
1030
+ });
1031
+ }
1032
+
1033
+ // Create a column item element
1034
+ function createColumnItem(col, segment, index) {
1035
+ const item = document.createElement('div');
1036
+ item.className = 'column-item';
1037
+ item.draggable = true;
1038
+ item.dataset.segment = segment;
1039
+ item.dataset.index = index;
1040
+ item.innerHTML =
1041
+ '<span class="drag-handle">⋮⋮</span>' +
1042
+ '<span class="name">' + escapeHtml(col.name) + '</span>' +
1043
+ '<button class="remove-btn" title="Remove">✕</button>';
1044
+
1045
+ item.querySelector('.remove-btn').onclick = (e) => {
1046
+ e.stopPropagation();
1047
+ removeColumn(segment, index);
1048
+ };
1049
+
1050
+ return item;
1051
+ }
1052
+
1053
+ // Add column to segment
1054
+ function addColumn(segment, slug, name) {
1055
+ if (!currentPipeline) return;
1056
+ if (!currentPipeline[segment]) currentPipeline[segment] = [];
1057
+
1058
+ // Check if already exists
1059
+ if (currentPipeline[segment].some(c => c.slug.includes(slug))) {
1060
+ showNotification('Column already exists in this segment', 'warning');
1061
+ return;
1062
+ }
1063
+
1064
+ currentPipeline[segment].push({ slug, name });
1065
+ renderSegmentColumns();
1066
+ hasChanges = true;
1067
+ updateButtons();
1068
+ }
1069
+
1070
+ // Remove column from segment
1071
+ function removeColumn(segment, index) {
1072
+ if (!currentPipeline || !currentPipeline[segment]) return;
1073
+ currentPipeline[segment].splice(index, 1);
1074
+ renderSegmentColumns();
1075
+ hasChanges = true;
1076
+ updateButtons();
1077
+ }
1078
+
1079
+ // Setup drag and drop
1080
+ function setupDragAndDrop() {
1081
+ document.addEventListener('dragstart', (e) => {
1082
+ if (e.target.classList.contains('catalog-item') || e.target.classList.contains('column-item')) {
1083
+ e.target.classList.add('dragging');
1084
+ window.currentDragSegment = e.target.dataset.segment;
1085
+ e.dataTransfer.setData('text/plain', JSON.stringify({
1086
+ slug: e.target.dataset.slug,
1087
+ name: e.target.dataset.name,
1088
+ segment: e.target.dataset.segment,
1089
+ index: e.target.dataset.index
1090
+ }));
1091
+ }
1092
+ });
1093
+
1094
+ document.addEventListener('dragend', (e) => {
1095
+ e.target.classList.remove('dragging');
1096
+ window.currentDragSegment = null;
1097
+ document.querySelectorAll('.dragover').forEach(el => el.classList.remove('dragover'));
1098
+ document.querySelectorAll('.dragover-invalid').forEach(el => el.classList.remove('dragover-invalid'));
1099
+ });
1100
+
1101
+ document.querySelectorAll('.segment-columns').forEach(container => {
1102
+ container.addEventListener('dragover', (e) => {
1103
+ e.preventDefault();
1104
+ // Only show dragover if segment is compatible
1105
+ const draggedSegment = window.currentDragSegment;
1106
+ const targetSegment = container.dataset.segment;
1107
+ if (!draggedSegment || draggedSegment === targetSegment) {
1108
+ container.classList.add('dragover');
1109
+ } else {
1110
+ container.classList.add('dragover-invalid');
1111
+ }
1112
+ });
1113
+
1114
+ container.addEventListener('dragleave', () => {
1115
+ container.classList.remove('dragover');
1116
+ container.classList.remove('dragover-invalid');
1117
+ });
1118
+
1119
+ container.addEventListener('drop', (e) => {
1120
+ e.preventDefault();
1121
+ container.classList.remove('dragover');
1122
+ container.classList.remove('dragover-invalid');
1123
+
1124
+ const data = JSON.parse(e.dataTransfer.getData('text/plain'));
1125
+ const targetSegment = container.dataset.segment;
1126
+
1127
+ // Check if it's from catalog or from another segment
1128
+ if (data.index === undefined) {
1129
+ // From catalog - check segment compatibility
1130
+ if (data.segment !== targetSegment) {
1131
+ showNotification('This column belongs to the "' + data.segment + '" segment', 'warning');
1132
+ return;
1133
+ }
1134
+ addColumn(targetSegment, data.slug, data.name);
1135
+ } else {
1136
+ // Reordering within same segment only
1137
+ const fromSegment = data.segment;
1138
+ const fromIndex = parseInt(data.index);
1139
+
1140
+ if (fromSegment !== targetSegment) {
1141
+ showNotification('Columns cannot be moved between segments', 'warning');
1142
+ return;
1143
+ }
1144
+
1145
+ // Reorder within same segment
1146
+ const items = currentPipeline[fromSegment];
1147
+ const [removed] = items.splice(fromIndex, 1);
1148
+ // Find drop position
1149
+ const dropIndex = getDropIndex(e, container);
1150
+ items.splice(dropIndex, 0, removed);
1151
+
1152
+ renderSegmentColumns();
1153
+ hasChanges = true;
1154
+ updateButtons();
1155
+ }
1156
+ });
1157
+ });
1158
+ }
1159
+
1160
+ function getDropIndex(e, container) {
1161
+ const items = [...container.querySelectorAll('.column-item:not(.dragging)')];
1162
+ const y = e.clientY;
1163
+ let insertIndex = items.length;
1164
+
1165
+ for (let i = 0; i < items.length; i++) {
1166
+ const rect = items[i].getBoundingClientRect();
1167
+ if (y < rect.top + rect.height / 2) {
1168
+ insertIndex = i;
1169
+ break;
1170
+ }
1171
+ }
1172
+
1173
+ return insertIndex;
1174
+ }
1175
+
1176
+ // Update buttons state
1177
+ function updateButtons() {
1178
+ savePipelineBtn.disabled = !hasChanges || !currentPipeline;
1179
+ activateBtn.disabled = !currentPipeline || hasChanges;
1180
+
1181
+ // Validate pipeline
1182
+ if (currentPipeline) {
1183
+ const valid = validatePipeline();
1184
+ savePipelineBtn.disabled = savePipelineBtn.disabled || !valid;
1185
+ }
1186
+ }
1187
+
1188
+ // Validate pipeline
1189
+ function validatePipeline() {
1190
+ if (!currentPipeline) return false;
1191
+ if (!currentPipeline.definition || currentPipeline.definition.length === 0) return false;
1192
+ if (!currentPipeline.action || currentPipeline.action.length === 0) return false;
1193
+ if (!currentPipeline.finish || currentPipeline.finish.length === 0) return false;
1194
+ return true;
1195
+ }
1196
+
1197
+ // Save pipeline
1198
+ async function savePipeline() {
1199
+ if (!currentPipelineKey || !currentPipeline) return;
1200
+
1201
+ // Update from form
1202
+ currentPipeline.name = document.getElementById('pipelineName').value;
1203
+ currentPipeline.version = document.getElementById('pipelineVersion').value;
1204
+ currentPipeline.description = document.getElementById('pipelineDescription').value;
1205
+
1206
+ // Renumber columns
1207
+ let index = 0;
1208
+ ['definition', 'action', 'finish'].forEach(segment => {
1209
+ currentPipeline[segment] = currentPipeline[segment].map(col => ({
1210
+ slug: String(index++).padStart(2, '0') + '_' + col.slug.replace(/^\\d{2}_/, ''),
1211
+ name: col.name
1212
+ }));
1213
+ });
1214
+
1215
+ try {
1216
+ const res = await fetch('/api/pipelines', {
1217
+ method: 'POST',
1218
+ headers: { 'Content-Type': 'application/json' },
1219
+ body: JSON.stringify({
1220
+ name: currentPipelineKey,
1221
+ pipeline: currentPipeline,
1222
+ setActive: false
1223
+ })
1224
+ });
1225
+ const data = await res.json();
1226
+ if (data.success) {
1227
+ showNotification('Pipeline saved!');
1228
+ hasChanges = false;
1229
+ await loadPipelines();
1230
+ renderPipelineList();
1231
+ updateButtons();
1232
+ } else {
1233
+ showNotification('Error: ' + data.error, 'error');
1234
+ }
1235
+ } catch (e) {
1236
+ showNotification('Error: ' + e.message, 'error');
1237
+ }
1238
+ }
1239
+
1240
+ // Activate pipeline
1241
+ async function activatePipeline() {
1242
+ if (!currentPipelineKey) return;
1243
+
1244
+ try {
1245
+ const res = await fetch('/api/pipelines/' + encodeURIComponent(currentPipelineKey) + '/activate', {
1246
+ method: 'PUT'
1247
+ });
1248
+ const data = await res.json();
1249
+ if (data.success) {
1250
+ showNotification('Pipeline activated and synced!');
1251
+ await loadPipelines();
1252
+ renderPipelineList();
1253
+
1254
+ // Show sync results if any
1255
+ if (data.data && (data.data.added.length || data.data.removed.length || data.data.transitioned.length)) {
1256
+ showSyncResults(data.data);
1257
+ }
1258
+ } else {
1259
+ showNotification('Error: ' + data.error, 'error');
1260
+ }
1261
+ } catch (e) {
1262
+ showNotification('Error: ' + e.message, 'error');
1263
+ }
1264
+ }
1265
+
1266
+ // Show sync results
1267
+ function showSyncResults(result) {
1268
+ const preview = document.getElementById('syncPreview');
1269
+ const changes = document.getElementById('syncChanges');
1270
+
1271
+ changes.innerHTML = '';
1272
+
1273
+ if (result.added.length) {
1274
+ changes.innerHTML += '<div class="sync-group added"><h4>Added</h4><ul>' +
1275
+ result.added.map(s => '<li>+ ' + escapeHtml(s) + '</li>').join('') + '</ul></div>';
1276
+ }
1277
+ if (result.removed.length) {
1278
+ changes.innerHTML += '<div class="sync-group removed"><h4>Removed</h4><ul>' +
1279
+ result.removed.map(s => '<li>- ' + escapeHtml(s) + '</li>').join('') + '</ul></div>';
1280
+ }
1281
+ if (result.transitioned.length) {
1282
+ changes.innerHTML += '<div class="sync-group renamed"><h4>Tickets in Transition</h4><ul>' +
1283
+ result.transitioned.map(s => '<li>' + escapeHtml(s) + '</li>').join('') + '</ul></div>';
1284
+ }
1285
+
1286
+ preview.style.display = changes.innerHTML ? '' : 'none';
1287
+ }
1288
+
1289
+ // Delete pipeline
1290
+ async function deletePipeline() {
1291
+ if (!currentPipelineKey) return;
1292
+ if (!confirm('Are you sure you want to delete this pipeline?')) return;
1293
+
1294
+ try {
1295
+ const res = await fetch('/api/pipelines/' + encodeURIComponent(currentPipelineKey), {
1296
+ method: 'DELETE'
1297
+ });
1298
+ const data = await res.json();
1299
+ if (data.success) {
1300
+ showNotification('Pipeline deleted');
1301
+ currentPipelineKey = null;
1302
+ currentPipeline = null;
1303
+ await loadPipelines();
1304
+ renderPipelineList();
1305
+ emptyState.style.display = '';
1306
+ pipelineEditor.style.display = 'none';
1307
+ } else {
1308
+ showNotification('Error: ' + data.error, 'error');
1309
+ }
1310
+ } catch (e) {
1311
+ showNotification('Error: ' + e.message, 'error');
1312
+ }
1313
+ }
1314
+
1315
+ // Convert to kebab-case
1316
+ function toKebabCase(str) {
1317
+ return str
1318
+ .toLowerCase()
1319
+ .normalize('NFD').replace(/[\u0300-\u036f]/g, '') // Remove accents
1320
+ .replace(/[^a-z0-9]+/g, '-') // Replace non-alphanumeric with dashes
1321
+ .replace(/^-+|-+$/g, '') // Trim dashes
1322
+ .replace(/-+/g, '-'); // Collapse multiple dashes
1323
+ }
1324
+
1325
+ // New pipeline modal
1326
+ function openNewPipelineModal() {
1327
+ document.getElementById('newPipelineModal').classList.add('show');
1328
+ document.getElementById('newPipelineName').value = '';
1329
+ document.getElementById('newPipelineKeyPreview').textContent = '-';
1330
+ }
1331
+
1332
+ function closeNewPipelineModal() {
1333
+ document.getElementById('newPipelineModal').classList.remove('show');
1334
+ }
1335
+
1336
+ async function confirmNewPipeline() {
1337
+ const name = document.getElementById('newPipelineName').value.trim();
1338
+ const key = toKebabCase(name);
1339
+
1340
+ if (!name || !key) {
1341
+ showNotification('Please enter a pipeline name', 'warning');
1342
+ return;
1343
+ }
1344
+
1345
+ // Check if key already exists
1346
+ if (pipelines.some(p => p.name === key)) {
1347
+ showNotification('A pipeline with this key already exists', 'warning');
1348
+ return;
1349
+ }
1350
+
1351
+ // Create empty pipeline
1352
+ const newPipeline = {
1353
+ name: name,
1354
+ version: '1.0.0',
1355
+ description: '',
1356
+ definition: [{ slug: 'backlog', name: 'Backlog' }],
1357
+ action: [{ slug: 'in-progress', name: 'In Progress' }],
1358
+ finish: [{ slug: 'done', name: 'Done' }]
1359
+ };
1360
+
1361
+ try {
1362
+ const res = await fetch('/api/pipelines', {
1363
+ method: 'POST',
1364
+ headers: { 'Content-Type': 'application/json' },
1365
+ body: JSON.stringify({
1366
+ name: key,
1367
+ pipeline: newPipeline,
1368
+ setActive: pipelines.length === 0
1369
+ })
1370
+ });
1371
+ const data = await res.json();
1372
+ if (data.success) {
1373
+ closeNewPipelineModal();
1374
+ await loadPipelines();
1375
+ renderPipelineList();
1376
+ selectPipeline(key);
1377
+ showNotification('Pipeline created!');
1378
+ } else {
1379
+ showNotification('Error: ' + data.error, 'error');
1380
+ }
1381
+ } catch (e) {
1382
+ showNotification('Error: ' + e.message, 'error');
1383
+ }
1384
+ }
1385
+
1386
+ // Show notification
1387
+ function showNotification(msg, type = 'success') {
1388
+ notification.textContent = msg;
1389
+ notification.className = 'notification show' + (type !== 'success' ? ' ' + type : '');
1390
+ setTimeout(() => notification.className = 'notification', 3000);
1391
+ }
1392
+
1393
+ // Prompt preview
1394
+ async function openPromptPreview(slug, name) {
1395
+ currentPromptSlug = slug;
1396
+ currentPromptLang = currentLang || 'en';
1397
+ document.getElementById('promptPreviewTitle').textContent = name;
1398
+ document.getElementById('promptPreviewModal').classList.add('show');
1399
+ updatePromptLangButtons();
1400
+ await loadPromptContent();
1401
+ }
1402
+
1403
+ function closePromptPreview() {
1404
+ document.getElementById('promptPreviewModal').classList.remove('show');
1405
+ currentPromptSlug = null;
1406
+ }
1407
+
1408
+ async function switchPromptLang(lang) {
1409
+ currentPromptLang = lang;
1410
+ updatePromptLangButtons();
1411
+ await loadPromptContent();
1412
+ }
1413
+
1414
+ function updatePromptLangButtons() {
1415
+ document.querySelectorAll('#promptPreviewModal .lang-btn').forEach(btn => {
1416
+ btn.classList.toggle('active', btn.dataset.lang === currentPromptLang);
1417
+ });
1418
+ }
1419
+
1420
+ async function loadPromptContent() {
1421
+ const content = document.getElementById('promptPreviewContent');
1422
+ content.textContent = 'Loading...';
1423
+
1424
+ try {
1425
+ const res = await fetch('/api/prompts/' + encodeURIComponent(currentPromptSlug) + '?lang=' + currentPromptLang);
1426
+ const data = await res.json();
1427
+ if (data.success && data.data) {
1428
+ content.textContent = data.data;
1429
+ } else {
1430
+ content.textContent = '(No prompt available for this column in ' + currentPromptLang.toUpperCase() + ')';
1431
+ }
1432
+ } catch (e) {
1433
+ content.textContent = 'Error loading prompt: ' + e.message;
1434
+ }
1435
+ }
1436
+
1437
+ // Add Column Modal
1438
+ function openAddColumnModal() {
1439
+ document.getElementById('addColumnModal').classList.add('show');
1440
+ document.getElementById('newColumnName').value = '';
1441
+ document.getElementById('newColumnDescription').value = '';
1442
+ document.getElementById('newColumnSegment').value = 'action';
1443
+ hideAddColumnError();
1444
+ }
1445
+
1446
+ function closeAddColumnModal() {
1447
+ document.getElementById('addColumnModal').classList.remove('show');
1448
+ hideAddColumnError();
1449
+ }
1450
+
1451
+ function showAddColumnError(message) {
1452
+ const errorEl = document.getElementById('addColumnError');
1453
+ errorEl.textContent = message;
1454
+ errorEl.style.display = 'block';
1455
+ }
1456
+
1457
+ function hideAddColumnError() {
1458
+ const errorEl = document.getElementById('addColumnError');
1459
+ errorEl.style.display = 'none';
1460
+ errorEl.textContent = '';
1461
+ }
1462
+
1463
+ async function generateColumn() {
1464
+ const name = document.getElementById('newColumnName').value.trim();
1465
+ const segment = document.getElementById('newColumnSegment').value;
1466
+ const description = document.getElementById('newColumnDescription').value.trim();
1467
+
1468
+ hideAddColumnError();
1469
+
1470
+ if (!name || !description) {
1471
+ showAddColumnError(t('errorFillFields'));
1472
+ return;
1473
+ }
1474
+
1475
+ // Show loading state
1476
+ document.getElementById('generateColumnText').style.display = 'none';
1477
+ document.getElementById('generateColumnSpinner').style.display = 'inline';
1478
+ document.getElementById('generateColumnBtn').disabled = true;
1479
+
1480
+ try {
1481
+ const res = await fetch('/api/catalog/generate', {
1482
+ method: 'POST',
1483
+ headers: { 'Content-Type': 'application/json' },
1484
+ body: JSON.stringify({ name, segment, description })
1485
+ });
1486
+ const data = await res.json();
1487
+
1488
+ if (data.success) {
1489
+ showNotification('Column "' + name + '" added to catalog!');
1490
+ closeAddColumnModal();
1491
+ // Reload catalog
1492
+ await loadCatalog();
1493
+ renderCatalog();
1494
+ } else {
1495
+ // Check for duplicate slug error
1496
+ if (data.error && data.error.includes('already exists')) {
1497
+ showAddColumnError(t('errorColumnExists'));
1498
+ } else {
1499
+ showAddColumnError(data.error || t('errorGeneric'));
1500
+ }
1501
+ }
1502
+ } catch (e) {
1503
+ showAddColumnError(e.message);
1504
+ } finally {
1505
+ document.getElementById('generateColumnText').style.display = 'inline';
1506
+ document.getElementById('generateColumnSpinner').style.display = 'none';
1507
+ document.getElementById('generateColumnBtn').disabled = false;
1508
+ }
1509
+ }
1510
+
1511
+ // Escape HTML
1512
+ function escapeHtml(str) {
1513
+ const div = document.createElement('div');
1514
+ div.textContent = str || '';
1515
+ return div.innerHTML;
1516
+ }
1517
+
1518
+ // Event listeners
1519
+ document.getElementById('newPipelineBtn').onclick = openNewPipelineModal;
1520
+ document.getElementById('createFirstBtn').onclick = openNewPipelineModal;
1521
+ document.getElementById('savePipelineBtn').onclick = savePipeline;
1522
+ document.getElementById('activateBtn').onclick = activatePipeline;
1523
+ document.getElementById('deletePipelineBtn').onclick = deletePipeline;
1524
+
1525
+ // Track form changes
1526
+ ['pipelineName', 'pipelineVersion', 'pipelineDescription'].forEach(id => {
1527
+ document.getElementById(id).addEventListener('input', () => {
1528
+ hasChanges = true;
1529
+ updateButtons();
1530
+ });
1531
+ });
1532
+
1533
+ // Live update key preview in new pipeline modal
1534
+ document.getElementById('newPipelineName').addEventListener('input', (e) => {
1535
+ const name = e.target.value.trim();
1536
+ const key = toKebabCase(name);
1537
+ document.getElementById('newPipelineKeyPreview').textContent = key || '-';
1538
+ });
1539
+
1540
+ // Language switcher
1541
+ function initLangSwitcher() {
1542
+ const buttons = document.querySelectorAll('#lang-switcher .lang-btn');
1543
+ buttons.forEach(btn => {
1544
+ if (btn.dataset.lang === currentLang) {
1545
+ btn.classList.add('active');
1546
+ }
1547
+ btn.addEventListener('click', () => {
1548
+ currentLang = btn.dataset.lang;
1549
+ localStorage.setItem('autocode-lang', currentLang);
1550
+ buttons.forEach(b => b.classList.remove('active'));
1551
+ btn.classList.add('active');
1552
+ // Update default prompt language
1553
+ currentPromptLang = currentLang;
1554
+ // Update all UI text
1555
+ updateUILanguage();
1556
+ });
1557
+ });
1558
+ }
1559
+
1560
+ // Initialize
1561
+ init().then(() => {
1562
+ updateUILanguage();
1563
+ });
1564
+ initLangSwitcher();
1565
+ updateUILanguage();
1566
+ </script>
1567
+ </body>
1568
+ </html>`;
1569
+ }
1570
+ //# sourceMappingURL=pipeline-configurator.js.map