@autocode-cli/autocode 0.1.32 → 0.1.34

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 (51) hide show
  1. package/README.md +3 -3
  2. package/dist/cli/commands/init.js +1 -1
  3. package/dist/server/api.js +2 -2
  4. package/dist/server/api.js.map +1 -1
  5. package/dist/server/dashboard/pages/changelog.js +2 -2
  6. package/dist/server/dashboard/pages/changelog.js.map +1 -1
  7. package/dist/server/dashboard/pages/index.d.ts +3 -0
  8. package/dist/server/dashboard/pages/index.d.ts.map +1 -1
  9. package/dist/server/dashboard/pages/index.js +3 -0
  10. package/dist/server/dashboard/pages/index.js.map +1 -1
  11. package/dist/server/dashboard/pages/issue-comments.d.ts +8 -0
  12. package/dist/server/dashboard/pages/issue-comments.d.ts.map +1 -0
  13. package/dist/server/dashboard/pages/issue-comments.js +283 -0
  14. package/dist/server/dashboard/pages/issue-comments.js.map +1 -0
  15. package/dist/server/dashboard/pages/issue-history.d.ts +8 -0
  16. package/dist/server/dashboard/pages/issue-history.d.ts.map +1 -0
  17. package/dist/server/dashboard/pages/issue-history.js +149 -0
  18. package/dist/server/dashboard/pages/issue-history.js.map +1 -0
  19. package/dist/server/dashboard/pages/issue-shared.d.ts +27 -0
  20. package/dist/server/dashboard/pages/issue-shared.d.ts.map +1 -0
  21. package/dist/server/dashboard/pages/issue-shared.js +365 -0
  22. package/dist/server/dashboard/pages/issue-shared.js.map +1 -0
  23. package/dist/server/dashboard/pages/issue-terminal.d.ts +8 -0
  24. package/dist/server/dashboard/pages/issue-terminal.d.ts.map +1 -0
  25. package/dist/server/dashboard/pages/issue-terminal.js +363 -0
  26. package/dist/server/dashboard/pages/issue-terminal.js.map +1 -0
  27. package/dist/server/dashboard/pages/issue-view.d.ts +2 -2
  28. package/dist/server/dashboard/pages/issue-view.d.ts.map +1 -1
  29. package/dist/server/dashboard/pages/issue-view.js +49 -1027
  30. package/dist/server/dashboard/pages/issue-view.js.map +1 -1
  31. package/dist/server/dashboard/pages/main-dashboard.js +1 -1
  32. package/dist/server/dashboard/pages/main-dashboard.js.map +1 -1
  33. package/dist/server/dashboard/pages/new-issue.d.ts.map +1 -1
  34. package/dist/server/dashboard/pages/new-issue.js +66 -2
  35. package/dist/server/dashboard/pages/new-issue.js.map +1 -1
  36. package/dist/server/dashboard/pages/new-issue.test.js +208 -3
  37. package/dist/server/dashboard/pages/new-issue.test.js.map +1 -1
  38. package/dist/server/dashboard.d.ts +1 -1
  39. package/dist/server/dashboard.d.ts.map +1 -1
  40. package/dist/server/dashboard.js +1 -1
  41. package/dist/server/dashboard.js.map +1 -1
  42. package/dist/server/index.d.ts.map +1 -1
  43. package/dist/server/index.js +35 -5
  44. package/dist/server/index.js.map +1 -1
  45. package/dist/server/watcher.d.ts +1 -1
  46. package/dist/server/watcher.js +1 -1
  47. package/dist/services/issue-io.d.ts +1 -1
  48. package/dist/services/issue-io.js +1 -1
  49. package/dist/utils/config.js +2 -2
  50. package/dist/utils/config.js.map +1 -1
  51. package/package.json +4 -1
@@ -1,13 +1,14 @@
1
1
  /**
2
- * Issue view page generator
2
+ * Issue view page generator (Details tab)
3
3
  */
4
4
  import { getConfig } from '../../../utils/config.js';
5
5
  import { getIssue } from '../../../core/issue.js';
6
6
  import { getColumns } from '../../../core/column.js';
7
7
  import { escapeHtml } from '../utils.js';
8
8
  import { generate404Page } from './shared.js';
9
+ import { generateIssueBaseStyles, generateTabsNav, generateIssueBaseScript, } from './issue-shared.js';
9
10
  /**
10
- * Generate the issue view page
11
+ * Generate the issue view page (Details tab)
11
12
  */
12
13
  export function generateIssueViewPage(issueKey, lang) {
13
14
  const config = getConfig();
@@ -17,8 +18,13 @@ export function generateIssueViewPage(issueKey, lang) {
17
18
  return generate404Page(issueKey, lang);
18
19
  }
19
20
  const currentColumn = columns.find(c => c.slug === issue.column_slug);
20
- const issueData = JSON.stringify(issue);
21
- const columnsData = JSON.stringify(columns);
21
+ const pageData = {
22
+ issueKey,
23
+ title: issue.title,
24
+ commentsCount: issue.comments?.length || 0,
25
+ historyCount: issue.history?.length || 0,
26
+ lang,
27
+ };
22
28
  return `<!DOCTYPE html>
23
29
  <html lang="${lang}">
24
30
  <head>
@@ -26,83 +32,8 @@ export function generateIssueViewPage(issueKey, lang) {
26
32
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
27
33
  <title>${escapeHtml(issue.title)} - ${issueKey} - AutoCode</title>
28
34
  <style>
29
- :root {
30
- --bg: #0a0a0f;
31
- --bg-secondary: #12121a;
32
- --bg-tertiary: #1a1a24;
33
- --text: #f1f5f9;
34
- --muted: #94a3b8;
35
- --border: #2a2a3a;
36
- --accent: #6366f1;
37
- --blue: #4dabf7;
38
- --green: #4ade80;
39
- --yellow: #facc15;
40
- --orange: #fb923c;
41
- --red: #f87171;
42
- --purple: #a78bfa;
43
- }
44
- * { margin: 0; padding: 0; box-sizing: border-box; }
45
- body {
46
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
47
- background: var(--bg);
48
- color: var(--text);
49
- min-height: 100vh;
50
- }
51
- .header {
52
- display: flex;
53
- align-items: center;
54
- gap: 24px;
55
- padding: 16px 24px;
56
- background: var(--bg-secondary);
57
- border-bottom: 1px solid var(--border);
58
- position: sticky;
59
- top: 0;
60
- z-index: 100;
61
- }
62
- .back-btn {
63
- color: var(--muted);
64
- text-decoration: none;
65
- font-size: 14px;
66
- display: flex;
67
- align-items: center;
68
- gap: 8px;
69
- }
70
- .back-btn:hover { color: var(--text); }
71
- .issue-header-info {
72
- flex: 1;
73
- display: flex;
74
- align-items: center;
75
- gap: 16px;
76
- }
77
- .issue-key {
78
- font-family: 'SF Mono', Monaco, monospace;
79
- font-size: 12px;
80
- color: var(--accent);
81
- background: rgba(99, 102, 241, 0.15);
82
- padding: 4px 10px;
83
- border-radius: 4px;
84
- font-weight: 600;
85
- }
86
- .issue-title {
87
- font-size: 18px;
88
- font-weight: 600;
89
- }
90
- .lang-selector {
91
- display: flex;
92
- gap: 4px;
93
- }
94
- .lang-btn {
95
- background: transparent;
96
- border: 1px solid var(--border);
97
- color: var(--muted);
98
- padding: 6px 12px;
99
- border-radius: 4px;
100
- cursor: pointer;
101
- font-size: 12px;
102
- font-weight: 500;
103
- }
104
- .lang-btn:hover { border-color: var(--accent); color: var(--text); }
105
- .lang-btn.active { background: var(--accent); border-color: var(--accent); color: white; }
35
+ ${generateIssueBaseStyles()}
36
+
106
37
  .issue-title-input {
107
38
  flex: 1;
108
39
  background: transparent;
@@ -120,10 +51,38 @@ export function generateIssueViewPage(issueKey, lang) {
120
51
  border-color: var(--accent);
121
52
  background: var(--bg-tertiary);
122
53
  }
123
- .section-title {
54
+ .issue-details { display: flex; flex-direction: column; gap: 24px; }
55
+ .issue-meta {
124
56
  display: flex;
125
- align-items: center;
126
- justify-content: space-between;
57
+ flex-wrap: wrap;
58
+ gap: 12px;
59
+ }
60
+ .meta-badge {
61
+ font-size: 11px;
62
+ font-weight: 600;
63
+ text-transform: uppercase;
64
+ letter-spacing: 0.5px;
65
+ padding: 5px 12px;
66
+ border-radius: 6px;
67
+ }
68
+ .priority-P0 { background: rgba(248, 113, 113, 0.2); color: var(--red); }
69
+ .priority-P1 { background: rgba(251, 146, 60, 0.2); color: var(--orange); }
70
+ .priority-P2 { background: rgba(250, 204, 21, 0.2); color: var(--yellow); }
71
+ .priority-P3 { background: rgba(148, 163, 184, 0.2); color: var(--muted); }
72
+ .column-badge { background: rgba(77, 171, 247, 0.15); color: var(--blue); }
73
+ .semver-badge { background: rgba(167, 139, 250, 0.15); color: var(--purple); }
74
+ .labels-list {
75
+ display: flex;
76
+ flex-wrap: wrap;
77
+ gap: 8px;
78
+ }
79
+ .label-tag {
80
+ font-size: 11px;
81
+ padding: 4px 10px;
82
+ border-radius: 12px;
83
+ background: var(--bg-tertiary);
84
+ color: var(--text);
85
+ border: 1px solid var(--border);
127
86
  }
128
87
  .btn-edit-toggle {
129
88
  background: transparent;
@@ -179,76 +138,6 @@ export function generateIssueViewPage(issueKey, lang) {
179
138
  outline: none;
180
139
  border-color: var(--accent);
181
140
  }
182
- .main-content {
183
- display: flex;
184
- flex-direction: column;
185
- gap: 24px;
186
- padding: 24px 48px;
187
- }
188
- .issue-details { display: flex; flex-direction: column; gap: 24px; }
189
- .issue-bottom {
190
- display: flex;
191
- flex-direction: column;
192
- gap: 24px;
193
- }
194
- .section {
195
- background: var(--bg-secondary);
196
- border: 1px solid var(--border);
197
- border-radius: 12px;
198
- padding: 20px;
199
- }
200
- .section-title {
201
- font-size: 12px;
202
- font-weight: 600;
203
- text-transform: uppercase;
204
- letter-spacing: 0.5px;
205
- color: var(--muted);
206
- margin-bottom: 16px;
207
- }
208
- .issue-meta {
209
- display: flex;
210
- flex-wrap: wrap;
211
- gap: 12px;
212
- }
213
- .meta-badge {
214
- font-size: 11px;
215
- font-weight: 600;
216
- text-transform: uppercase;
217
- letter-spacing: 0.5px;
218
- padding: 5px 12px;
219
- border-radius: 6px;
220
- }
221
- .priority-P0 { background: rgba(248, 113, 113, 0.2); color: var(--red); }
222
- .priority-P1 { background: rgba(251, 146, 60, 0.2); color: var(--orange); }
223
- .priority-P2 { background: rgba(250, 204, 21, 0.2); color: var(--yellow); }
224
- .priority-P3 { background: rgba(148, 163, 184, 0.2); color: var(--muted); }
225
- .column-badge { background: rgba(77, 171, 247, 0.15); color: var(--blue); }
226
- .semver-badge { background: rgba(167, 139, 250, 0.15); color: var(--purple); }
227
- .labels-list {
228
- display: flex;
229
- flex-wrap: wrap;
230
- gap: 8px;
231
- }
232
- .label-tag {
233
- font-size: 11px;
234
- padding: 4px 10px;
235
- border-radius: 12px;
236
- background: var(--bg-tertiary);
237
- color: var(--text);
238
- border: 1px solid var(--border);
239
- }
240
- .description-content {
241
- line-height: 1.7;
242
- color: var(--text);
243
- }
244
- .description-content p { margin-bottom: 12px; }
245
- .description-content code {
246
- background: var(--bg-tertiary);
247
- padding: 2px 6px;
248
- border-radius: 4px;
249
- font-family: 'SF Mono', Monaco, monospace;
250
- font-size: 13px;
251
- }
252
141
  .criteria-list { list-style: none; }
253
142
  .criteria-item {
254
143
  padding: 12px 16px;
@@ -263,99 +152,6 @@ export function generateIssueViewPage(issueKey, lang) {
263
152
  content: '☐';
264
153
  color: var(--muted);
265
154
  }
266
- .history-list { list-style: none; }
267
- .history-item {
268
- padding: 12px 0;
269
- border-bottom: 1px solid var(--border);
270
- display: flex;
271
- align-items: center;
272
- gap: 12px;
273
- font-size: 13px;
274
- }
275
- .history-item:last-child { border-bottom: none; }
276
- .history-action {
277
- font-weight: 600;
278
- text-transform: capitalize;
279
- }
280
- .history-from, .history-to {
281
- padding: 2px 8px;
282
- background: var(--bg-tertiary);
283
- border-radius: 4px;
284
- font-size: 11px;
285
- }
286
- .history-date { color: var(--muted); margin-left: auto; font-size: 12px; }
287
- .btn-prompt {
288
- background: none;
289
- border: none;
290
- cursor: pointer;
291
- font-size: 14px;
292
- padding: 2px 6px;
293
- opacity: 0.6;
294
- transition: opacity 0.2s;
295
- }
296
- .btn-prompt:hover { opacity: 1; }
297
- .prompt-modal {
298
- display: none;
299
- position: fixed;
300
- top: 0;
301
- left: 0;
302
- right: 0;
303
- bottom: 0;
304
- background: rgba(0, 0, 0, 0.7);
305
- z-index: 1000;
306
- align-items: center;
307
- justify-content: center;
308
- }
309
- .prompt-modal.visible { display: flex; }
310
- .prompt-modal-content {
311
- background: var(--bg-primary);
312
- border-radius: 12px;
313
- max-width: 900px;
314
- max-height: 80vh;
315
- width: 90%;
316
- display: flex;
317
- flex-direction: column;
318
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
319
- }
320
- .prompt-modal-header {
321
- display: flex;
322
- justify-content: space-between;
323
- align-items: center;
324
- padding: 16px 20px;
325
- border-bottom: 1px solid var(--border);
326
- }
327
- .prompt-modal-header h3 { margin: 0; }
328
- .prompt-modal-close {
329
- background: none;
330
- border: none;
331
- font-size: 24px;
332
- cursor: pointer;
333
- color: var(--muted);
334
- }
335
- .prompt-modal-close:hover { color: var(--text); }
336
- .prompt-modal-body {
337
- padding: 20px;
338
- overflow-y: auto;
339
- flex: 1;
340
- }
341
- .prompt-modal-body pre {
342
- white-space: pre-wrap;
343
- word-wrap: break-word;
344
- font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
345
- font-size: 13px;
346
- line-height: 1.5;
347
- margin: 0;
348
- background: var(--bg-secondary);
349
- padding: 16px;
350
- border-radius: 8px;
351
- }
352
- .log-container {
353
- max-height: 70vh;
354
- overflow-y: auto;
355
- display: flex;
356
- flex-direction: column;
357
- gap: 8px;
358
- }
359
155
  .actions-bar {
360
156
  display: flex;
361
157
  gap: 12px;
@@ -380,222 +176,6 @@ export function generateIssueViewPage(issueKey, lang) {
380
176
  .btn-secondary:hover { border-color: var(--accent); }
381
177
  .btn-danger { background: rgba(248, 113, 113, 0.15); color: var(--red); border: 1px solid transparent; }
382
178
  .btn-danger:hover { border-color: var(--red); }
383
- .comments-list {
384
- display: flex;
385
- flex-direction: column;
386
- gap: 12px;
387
- margin-bottom: 16px;
388
- }
389
- .comment {
390
- padding: 16px;
391
- background: var(--bg-tertiary);
392
- border-radius: 8px;
393
- border-left: 3px solid var(--border);
394
- transition: all 0.2s ease;
395
- }
396
- .comment:hover {
397
- border-left-color: var(--blue);
398
- }
399
- .comment-meta {
400
- display: flex;
401
- align-items: center;
402
- flex-wrap: wrap;
403
- gap: 8px;
404
- cursor: pointer;
405
- user-select: none;
406
- }
407
- .comment-meta::before {
408
- content: '▶';
409
- font-size: 10px;
410
- color: var(--muted);
411
- transition: transform 0.2s ease;
412
- }
413
- .comment.expanded .comment-meta::before {
414
- transform: rotate(90deg);
415
- }
416
- .comment-source {
417
- font-size: 10px;
418
- padding: 3px 8px;
419
- border-radius: 4px;
420
- text-transform: uppercase;
421
- font-weight: 600;
422
- letter-spacing: 0.5px;
423
- }
424
- .comment-source.user { background: #3b82f6; color: white; }
425
- .comment-source.claude { background: #8b5cf6; color: white; }
426
- .comment-column {
427
- font-size: 10px;
428
- font-weight: 600;
429
- text-transform: uppercase;
430
- letter-spacing: 0.5px;
431
- color: var(--blue);
432
- padding: 3px 8px;
433
- background: rgba(77,171,247,0.15);
434
- border-radius: 4px;
435
- }
436
- .comment-date {
437
- font-size: 11px;
438
- color: var(--muted);
439
- }
440
- .comment-text {
441
- font-size: 14px;
442
- line-height: 1.6;
443
- color: var(--text);
444
- max-height: 0;
445
- overflow: hidden;
446
- transition: max-height 0.3s ease, margin-top 0.3s ease, padding-top 0.3s ease;
447
- margin-top: 0;
448
- padding-top: 0;
449
- }
450
- .comment.expanded .comment-text {
451
- max-height: 500px;
452
- margin-top: 12px;
453
- padding-top: 12px;
454
- border-top: 1px solid var(--border);
455
- }
456
- .comment-text code {
457
- background: var(--bg);
458
- padding: 2px 6px;
459
- border-radius: 4px;
460
- font-family: 'SF Mono', Monaco, monospace;
461
- font-size: 12px;
462
- }
463
- .add-comment {
464
- display: flex;
465
- flex-direction: column;
466
- gap: 8px;
467
- }
468
- .add-comment textarea {
469
- width: 100%;
470
- min-height: 80px;
471
- padding: 12px;
472
- background: var(--bg-tertiary);
473
- border: 1px solid var(--border);
474
- border-radius: 8px;
475
- color: var(--text);
476
- font-family: inherit;
477
- font-size: 14px;
478
- resize: vertical;
479
- }
480
- .add-comment textarea:focus {
481
- outline: none;
482
- border-color: var(--accent);
483
- }
484
- .btn-comment {
485
- align-self: flex-end;
486
- padding: 8px 16px;
487
- background: var(--accent);
488
- color: white;
489
- border: none;
490
- border-radius: 6px;
491
- cursor: pointer;
492
- font-size: 13px;
493
- font-weight: 500;
494
- }
495
- .btn-comment:hover { opacity: 0.9; }
496
- .btn-comment:disabled { opacity: 0.5; cursor: not-allowed; }
497
- .no-comments {
498
- text-align: center;
499
- color: var(--muted);
500
- padding: 24px;
501
- font-size: 14px;
502
- }
503
- .notification {
504
- position: fixed;
505
- bottom: 24px;
506
- right: 24px;
507
- padding: 12px 20px;
508
- background: var(--green);
509
- color: #000;
510
- border-radius: 8px;
511
- font-weight: 500;
512
- transform: translateY(100px);
513
- opacity: 0;
514
- transition: all 0.3s ease;
515
- z-index: 1000;
516
- }
517
- .notification.show { transform: translateY(0); opacity: 1; }
518
- .notification.error { background: var(--red); color: white; }
519
-
520
- /* Claude Terminal */
521
- .claude-section .section-title {
522
- display: flex;
523
- align-items: center;
524
- justify-content: space-between;
525
- }
526
- .claude-status {
527
- font-size: 11px;
528
- padding: 3px 10px;
529
- border-radius: 12px;
530
- background: var(--bg-tertiary);
531
- color: var(--muted);
532
- }
533
- .claude-status.processing {
534
- color: var(--yellow);
535
- animation: pulse 1s infinite;
536
- }
537
- .claude-status.success { color: var(--green); }
538
- .claude-status.error { color: var(--red); }
539
- .claude-log {
540
- background: #0d1117;
541
- border: 1px solid var(--border);
542
- border-radius: 8px;
543
- padding: 16px;
544
- max-height: 400px;
545
- overflow-y: auto;
546
- font-family: 'SF Mono', Monaco, 'Consolas', monospace;
547
- font-size: 12px;
548
- line-height: 1.6;
549
- white-space: pre-wrap;
550
- word-break: break-word;
551
- color: var(--text);
552
- margin: 0;
553
- margin-top: 12px;
554
- }
555
- /* Formatted log entries */
556
- .log-entry { margin-bottom: 8px; padding: 8px 12px; border-radius: 4px; border-left: 3px solid transparent; flex-shrink: 0; }
557
- .log-entry.timestamp { color: #8b949e; font-size: 11px; border-left-color: #484f58; background: transparent; padding: 4px 12px; }
558
- .log-entry.system { color: #8b949e; border-left-color: #484f58; background: rgba(139,148,158,0.1); }
559
- .log-entry.user { color: #58a6ff; border-left-color: #58a6ff; background: rgba(88,166,255,0.1); }
560
- .log-entry.assistant { color: #7ee787; border-left-color: #7ee787; background: rgba(126,231,135,0.1); }
561
- .log-entry.tool-call { color: #d2a8ff; border-left-color: #d2a8ff; background: rgba(210,168,255,0.1); padding: 12px; }
562
- .log-entry.tool-result { color: #ffa657; border-left-color: #ffa657; background: rgba(255,166,87,0.1); }
563
- .log-entry.error { color: #f85149; border-left-color: #f85149; background: rgba(248,81,73,0.1); }
564
- .log-entry.success { color: #7ee787; border-left-color: #7ee787; background: rgba(126,231,135,0.1); }
565
- .log-label { font-weight: 600; font-size: 11px; text-transform: uppercase; margin-bottom: 4px; display: block; opacity: 0.8; }
566
- .log-content { white-space: pre-wrap; word-break: break-word; }
567
-
568
- /* Code blocks with line numbers */
569
- .log-code-block { background: #161b22; border-radius: 6px; overflow: hidden; margin: 8px 0; border: 1px solid #30363d; }
570
- .log-code-header { background: #21262d; padding: 8px 12px; font-size: 12px; color: #8b949e; border-bottom: 1px solid #30363d; display: flex; align-items: center; gap: 8px; }
571
- .log-code-header::before { content: ''; display: inline-block; width: 12px; height: 12px; background: #ffa657; border-radius: 50%; }
572
- .log-code-content { padding: 12px; overflow-x: auto; font-family: 'Fira Code', 'SF Mono', Monaco, monospace; font-size: 12px; line-height: 1.5; max-height: 400px; overflow-y: auto; }
573
- .log-code-line { display: flex; min-height: 20px; }
574
- .log-line-number { color: #484f58; text-align: right; padding-right: 16px; user-select: none; min-width: 40px; flex-shrink: 0; }
575
- .log-line-content { color: #c9d1d9; white-space: pre; }
576
-
577
- /* Message cards */
578
- .log-message-card { background: #161b22; border-radius: 8px; margin: 12px 0; overflow: hidden; border: 1px solid #30363d; flex-shrink: 0; }
579
- .log-message-header { padding: 10px 14px; display: flex; align-items: center; gap: 8px; font-weight: 500; font-size: 13px; }
580
- .log-message-header.assistant-header { background: linear-gradient(135deg, #238636 0%, #2ea043 100%); color: white; }
581
- .log-message-header.user-header { background: linear-gradient(135deg, #1f6feb 0%, #388bfd 100%); color: white; }
582
- .log-message-header.tool-header { background: linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%); color: white; }
583
- .log-message-header.result-header { background: linear-gradient(135deg, #f97316 0%, #fb923c 100%); color: white; }
584
- .log-message-body { padding: 14px; border-top: 1px solid #30363d; color: #c9d1d9; white-space: pre-wrap; word-break: break-word; }
585
-
586
- /* Tool badges */
587
- .log-tool-badge { display: inline-flex; align-items: center; gap: 6px; background: rgba(139,92,246,0.2); color: #a78bfa; padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; }
588
- .log-tool-input { background: #0d1117; border-radius: 4px; padding: 8px 12px; margin-top: 8px; font-family: 'Fira Code', monospace; font-size: 11px; color: #8b949e; max-height: 150px; overflow: auto; white-space: pre-wrap; }
589
-
590
- /* Collapsible raw JSON */
591
- .log-raw-details { margin: 8px 0; }
592
- .log-raw-summary { cursor: pointer; color: #8b949e; font-size: 12px; padding: 8px; background: rgba(139,148,158,0.1); border-radius: 4px; }
593
- .log-raw-summary:hover { background: rgba(139,148,158,0.2); }
594
- .log-raw-json { background: #0d1117; border-radius: 4px; padding: 12px; margin-top: 8px; font-size: 11px; color: #8b949e; max-height: 200px; overflow: auto; white-space: pre-wrap; }
595
- @keyframes pulse {
596
- 0%, 100% { opacity: 1; }
597
- 50% { opacity: 0.5; }
598
- }
599
179
  </style>
600
180
  </head>
601
181
  <body>
@@ -611,6 +191,8 @@ export function generateIssueViewPage(issueKey, lang) {
611
191
  </div>
612
192
  </header>
613
193
 
194
+ ${generateTabsNav(issueKey, 'details', pageData)}
195
+
614
196
  <main class="main-content">
615
197
  <div class="issue-details">
616
198
  <!-- Meta info -->
@@ -653,29 +235,6 @@ export function generateIssueViewPage(issueKey, lang) {
653
235
  </div>
654
236
  ` : ''}
655
237
 
656
- <!-- History -->
657
- ${issue.history && issue.history.length > 0 ? `
658
- <div class="section">
659
- <div class="section-title" data-i18n="issueView.history">History</div>
660
- <ul class="history-list">
661
- ${issue.history.map((h) => {
662
- const date = new Date(h.at);
663
- const dateStr = date.toLocaleDateString('en-US', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
664
- const showPromptBtn = h.to && h.action !== 'created' ? `<a href="/issue/${issueKey}/${escapeHtml(h.to)}/prompt" class="btn-prompt" title="View prompt">📜</a>` : '';
665
- const showLogBtn = h.to && h.action !== 'created' ? `<a href="/issue/${issueKey}/${escapeHtml(h.to)}/terminal" class="btn-prompt" title="View terminal log">🖥️</a>` : '';
666
- return `<li class="history-item">
667
- <span class="history-action">${h.action}</span>
668
- ${h.from ? `<span class="history-from">${escapeHtml(h.from)}</span> →` : ''}
669
- <span class="history-to">${escapeHtml(h.to)}</span>
670
- ${showPromptBtn}
671
- ${showLogBtn}
672
- <span class="history-date">${dateStr}</span>
673
- </li>`;
674
- }).join('')}
675
- </ul>
676
- </div>
677
- ` : ''}
678
-
679
238
  <!-- Actions bar -->
680
239
  <div class="actions-bar">
681
240
  <button class="btn btn-primary" id="btn-save" onclick="saveIssue()" data-i18n="issueView.save">Save</button>
@@ -683,227 +242,12 @@ export function generateIssueViewPage(issueKey, lang) {
683
242
  <button class="btn btn-danger" onclick="archiveIssue()" data-i18n="issueView.archive">Archive</button>
684
243
  </div>
685
244
  </div>
686
-
687
- <!-- Bottom section: Comments & Claude Terminal -->
688
- <div class="issue-bottom">
689
- <!-- Comments -->
690
- <div class="section comments-section">
691
- <div class="section-title"><span data-i18n="issueView.comments">Comments</span> (<span id="comments-count">${issue.comments?.length || 0}</span>)</div>
692
- <div class="comments-list" id="comments-list"></div>
693
- <div class="add-comment">
694
- <textarea id="new-comment" placeholder="Add a comment..." data-i18n-placeholder="issueView.addComment"></textarea>
695
- <button class="btn-comment" onclick="addComment()" data-i18n="btn.add">Add</button>
696
- </div>
697
- </div>
698
-
699
- <!-- Claude Terminal -->
700
- <div class="section claude-section" id="claude-section">
701
- <div class="section-title">
702
- <span data-i18n="issueView.claudeTerminal">Claude Terminal</span>
703
- <span class="claude-status" id="claude-status" data-i18n="status.waiting">Waiting</span>
704
- </div>
705
- <pre class="claude-log" id="claude-log"></pre>
706
- </div>
707
- </div>
708
245
  </main>
709
246
 
710
247
  <div class="notification" id="notification"></div>
711
248
 
712
- <!-- Prompt Modal -->
713
- <div class="prompt-modal" id="prompt-modal" onclick="closePromptModal(event)">
714
- <div class="prompt-modal-content" onclick="event.stopPropagation()">
715
- <div class="prompt-modal-header">
716
- <h3 id="prompt-modal-title">Prompt</h3>
717
- <button class="prompt-modal-close" onclick="closePromptModal()">&times;</button>
718
- </div>
719
- <div class="prompt-modal-body">
720
- <div id="prompt-modal-content" class="log-container"></div>
721
- </div>
722
- </div>
723
- </div>
724
-
725
249
  <script>
726
- const ISSUE_KEY = '${issueKey}';
727
- const ISSUE = ${issueData};
728
- const COLUMNS = ${columnsData};
729
- const STORAGE_KEY = 'autocode-lang';
730
-
731
- let currentLang = localStorage.getItem(STORAGE_KEY) || 'fr';
732
- let currentComments = ISSUE.comments || [];
733
-
734
- const translations = {
735
- en: {
736
- 'issueView.meta': 'Meta',
737
- 'issueView.labels': 'Labels',
738
- 'issueView.description': 'Description',
739
- 'issueView.criteria': 'Acceptance Criteria',
740
- 'issueView.history': 'History',
741
- 'issueView.actions': 'Actions',
742
- 'issueView.save': 'Save',
743
- 'issueView.moveNext': 'Move to next column',
744
- 'issueView.archive': 'Archive',
745
- 'issueView.confirmMove': 'Move this issue to the next column?',
746
- 'issueView.confirmArchive': 'Archive this issue?',
747
- 'issueView.comments': 'Comments',
748
- 'issueView.addComment': 'Add a comment...',
749
- 'issueView.noComments': 'No comments yet',
750
- 'issueView.claudeTerminal': 'Claude Terminal',
751
- 'issueView.noDescription': 'No description',
752
- 'issueView.noLog': 'No log yet. Waiting for Claude processing...',
753
- 'issueView.loadingPrompt': 'Loading prompt...',
754
- 'issueView.promptError': 'Error',
755
- 'btn.add': 'Add',
756
- 'btn.sending': 'Sending...',
757
- 'btn.saving': 'Saving...',
758
- 'status.waiting': 'Waiting',
759
- 'status.processing': 'Processing...',
760
- 'status.completed': 'Completed',
761
- 'status.failed': 'Failed',
762
- 'notify.commentAdded': 'Comment added',
763
- 'notify.issueAdvanced': 'Issue advanced',
764
- 'notify.issueArchived': 'Issue archived',
765
- 'notify.issueSaved': 'Issue saved',
766
- 'notify.error': 'Error'
767
- },
768
- fr: {
769
- 'issueView.meta': 'Méta',
770
- 'issueView.labels': 'Labels',
771
- 'issueView.description': 'Description',
772
- 'issueView.criteria': 'Critères d\\'acceptation',
773
- 'issueView.history': 'Historique',
774
- 'issueView.actions': 'Actions',
775
- 'issueView.save': 'Sauvegarder',
776
- 'issueView.moveNext': 'Déplacer vers la colonne suivante',
777
- 'issueView.archive': 'Archiver',
778
- 'issueView.confirmMove': 'Déplacer ce ticket vers la colonne suivante ?',
779
- 'issueView.confirmArchive': 'Archiver ce ticket ?',
780
- 'issueView.comments': 'Commentaires',
781
- 'issueView.addComment': 'Ajouter un commentaire...',
782
- 'issueView.noComments': 'Aucun commentaire',
783
- 'issueView.claudeTerminal': 'Terminal Claude',
784
- 'issueView.noDescription': 'Aucune description',
785
- 'issueView.noLog': 'Aucun log. En attente du traitement Claude...',
786
- 'issueView.loadingPrompt': 'Chargement du prompt...',
787
- 'issueView.promptError': 'Erreur',
788
- 'btn.add': 'Ajouter',
789
- 'btn.sending': 'Envoi...',
790
- 'btn.saving': 'Sauvegarde...',
791
- 'status.waiting': 'En attente',
792
- 'status.processing': 'En cours...',
793
- 'status.completed': 'Terminé',
794
- 'status.failed': 'Échec',
795
- 'notify.commentAdded': 'Commentaire ajouté',
796
- 'notify.issueAdvanced': 'Ticket avancé',
797
- 'notify.issueArchived': 'Ticket archivé',
798
- 'notify.issueSaved': 'Ticket sauvegardé',
799
- 'notify.error': 'Erreur'
800
- }
801
- };
802
-
803
- function t(key) {
804
- return translations[currentLang]?.[key] || translations['en'][key] || key;
805
- }
806
-
807
- function escapeHtml(text) {
808
- if (!text) return '';
809
- const div = document.createElement('div');
810
- div.textContent = text;
811
- return div.innerHTML;
812
- }
813
-
814
- function renderMarkdown(text) {
815
- if (!text) return '';
816
- let html = escapeHtml(text);
817
- html = html.replace(/^### (.+)$/gm, '<h4>$1</h4>');
818
- html = html.replace(/^## (.+)$/gm, '<h3>$1</h3>');
819
- html = html.replace(/^# (.+)$/gm, '<h2>$1</h2>');
820
- html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');
821
- html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');
822
- html = html.replace(/\`([^\`]+)\`/g, '<code>$1</code>');
823
- html = html.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2" target="_blank">$1</a>');
824
- html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
825
- html = html.replace(/(<li>.*<\\/li>\\n?)+/g, '<ul>$&</ul>');
826
- html = html.replace(/\\n/g, '<br>');
827
- return html;
828
- }
829
-
830
- function updateLangUI() {
831
- document.querySelectorAll('.lang-btn').forEach(btn => {
832
- btn.classList.toggle('active', btn.dataset.lang === currentLang);
833
- });
834
- document.querySelectorAll('[data-i18n]').forEach(el => {
835
- el.textContent = t(el.dataset.i18n);
836
- });
837
- document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
838
- el.placeholder = t(el.dataset.i18nPlaceholder);
839
- });
840
- }
841
-
842
- function renderComments() {
843
- const list = document.getElementById('comments-list');
844
- const count = document.getElementById('comments-count');
845
- count.textContent = currentComments.length;
846
-
847
- if (currentComments.length === 0) {
848
- list.innerHTML = '<div class="no-comments">' + t('issueView.noComments') + '</div>';
849
- return;
850
- }
851
-
852
- const sorted = [...currentComments].sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
853
- list.innerHTML = sorted.map((comment, index) => {
854
- const date = new Date(comment.created_at);
855
- const dateStr = date.toLocaleDateString('en-US', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
856
- const source = comment.source || 'user';
857
- const sourceBadge = source === 'claude'
858
- ? '<span class="comment-source claude">Claude</span>'
859
- : '<span class="comment-source user">User</span>';
860
- return '<div class="comment" id="comment-' + index + '">' +
861
- '<div class="comment-meta" onclick="toggleComment(' + index + ')">' + sourceBadge + '<span class="comment-column">' + (comment.column || 'N/A') + '</span>' +
862
- '<span class="comment-date">' + dateStr + '</span></div>' +
863
- '<div class="comment-text">' + renderMarkdown(comment.text) + '</div></div>';
864
- }).join('');
865
- }
866
-
867
- function toggleComment(index) {
868
- const comments = document.querySelectorAll('.comment');
869
- comments.forEach((comment, i) => {
870
- if (i === index) {
871
- comment.classList.toggle('expanded');
872
- } else {
873
- comment.classList.remove('expanded');
874
- }
875
- });
876
- }
877
-
878
- async function addComment() {
879
- const textarea = document.getElementById('new-comment');
880
- const text = textarea.value.trim();
881
- if (!text) return;
882
-
883
- const btn = document.querySelector('.btn-comment');
884
- btn.disabled = true;
885
- btn.textContent = t('btn.sending');
886
-
887
- try {
888
- const res = await fetch('/api/issues/' + ISSUE_KEY + '/comments', {
889
- method: 'POST',
890
- headers: { 'Content-Type': 'application/json' },
891
- body: JSON.stringify({ text, source: 'user' })
892
- });
893
- const result = await res.json();
894
- textarea.value = '';
895
- if (result.success && result.data && result.data.comments) {
896
- currentComments = result.data.comments;
897
- renderComments();
898
- }
899
- showNotification(t('notify.commentAdded'));
900
- } catch (e) {
901
- showNotification(t('notify.error') + ': ' + e.message, true);
902
- } finally {
903
- btn.disabled = false;
904
- btn.textContent = t('btn.add');
905
- }
906
- }
250
+ ${generateIssueBaseScript(issueKey)}
907
251
 
908
252
  let isEditingDescription = false;
909
253
 
@@ -1010,32 +354,8 @@ export function generateIssueViewPage(issueKey, lang) {
1010
354
  }
1011
355
  }
1012
356
 
1013
- function showNotification(msg, isError) {
1014
- const notification = document.getElementById('notification');
1015
- notification.textContent = msg;
1016
- notification.className = 'notification show' + (isError ? ' error' : '');
1017
- setTimeout(() => notification.className = 'notification', 3000);
1018
- }
1019
-
1020
- // Language switcher
1021
- document.querySelectorAll('.lang-btn').forEach(btn => {
1022
- btn.addEventListener('click', () => {
1023
- const newLang = btn.dataset.lang;
1024
- if (newLang !== currentLang) {
1025
- currentLang = newLang;
1026
- localStorage.setItem(STORAGE_KEY, newLang);
1027
- updateLangUI();
1028
- renderComments();
1029
- }
1030
- });
1031
- });
1032
-
1033
- // ========================================
1034
- // WEBSOCKET & CLAUDE LOG
1035
- // ========================================
357
+ // WebSocket for real-time updates
1036
358
  let ws;
1037
- let logPollingInterval = null;
1038
-
1039
359
  function connectWebSocket() {
1040
360
  const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
1041
361
  ws = new WebSocket(protocol + '//' + location.host + '/ws');
@@ -1043,27 +363,8 @@ export function generateIssueViewPage(issueKey, lang) {
1043
363
  ws.onmessage = (event) => {
1044
364
  try {
1045
365
  const data = JSON.parse(event.data);
1046
- switch (data.type) {
1047
- case 'issue_updated':
1048
- if (data.key === ISSUE_KEY) {
1049
- location.reload();
1050
- }
1051
- break;
1052
- case 'claude_start':
1053
- if (data.issue === ISSUE_KEY) {
1054
- onClaudeStart();
1055
- }
1056
- break;
1057
- case 'claude_stream':
1058
- if (data.issue === ISSUE_KEY) {
1059
- fetchLog();
1060
- }
1061
- break;
1062
- case 'claude_end':
1063
- if (data.issue === ISSUE_KEY) {
1064
- onClaudeEnd(data.success, data.duration);
1065
- }
1066
- break;
366
+ if (data.type === 'issue_updated' && data.key === ISSUE_KEY) {
367
+ location.reload();
1067
368
  }
1068
369
  } catch (e) {
1069
370
  console.error('WebSocket message error:', e);
@@ -1075,288 +376,9 @@ export function generateIssueViewPage(issueKey, lang) {
1075
376
  };
1076
377
  }
1077
378
 
1078
- function escapeHtml(str) {
1079
- if (typeof str !== 'string') return '';
1080
- return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
1081
- }
1082
-
1083
- function formatCodeBlock(content, filename) {
1084
- const lines = content.split(/\\\\n|\\n/);
1085
- // Détecter le langage depuis le nom de fichier
1086
- let lang = 'plaintext';
1087
- if (filename) {
1088
- const ext = filename.split('.').pop().toLowerCase();
1089
- const langMap = {
1090
- 'js': 'javascript', 'ts': 'typescript', 'vue': 'html', 'jsx': 'javascript',
1091
- 'tsx': 'typescript', 'py': 'python', 'rb': 'ruby', 'java': 'java',
1092
- 'go': 'go', 'rs': 'rust', 'cpp': 'cpp', 'c': 'c', 'h': 'c',
1093
- 'css': 'css', 'scss': 'scss', 'html': 'html', 'json': 'json',
1094
- 'md': 'markdown', 'sh': 'bash', 'yml': 'yaml', 'yaml': 'yaml'
1095
- };
1096
- lang = langMap[ext] || 'plaintext';
1097
- }
1098
- // Extraire le code sans les numéros de ligne pour highlight.js
1099
- let codeLines = [];
1100
- for (const line of lines) {
1101
- const match = line.match(/^\\s*\\d+[→|](.*)$/);
1102
- if (match) {
1103
- codeLines.push(match[1]);
1104
- } else if (line.trim()) {
1105
- codeLines.push(line);
1106
- }
1107
- }
1108
- const codeContent = codeLines.join('\\n');
1109
- const lineCount = codeLines.length;
1110
- const preview = filename || (lineCount + ' lignes');
1111
-
1112
- let html = '<details class="log-code-block">';
1113
- html += '<summary class="log-code-header"><span class="code-lang">' + lang + '</span> ' + escapeHtml(preview) + '</summary>';
1114
- html += '<pre><code class="language-' + lang + '">' + escapeHtml(codeContent) + '</code></pre>';
1115
- html += '</details>';
1116
- return html;
1117
- }
1118
-
1119
- function formatLogContent(rawContent) {
1120
- if (!rawContent) return '';
1121
- // Supprimer les balises <system-reminder> et leur contenu
1122
- let cleanedContent = rawContent.replace(/<system-reminder>[\\s\\S]*?<\\/system-reminder>/gi, '');
1123
- const lines = cleanedContent.split('\\n');
1124
- const entries = [];
1125
- let textBuffer = [];
1126
-
1127
- function flushTextBuffer() {
1128
- if (textBuffer.length > 0) {
1129
- const text = textBuffer.join('\\n');
1130
- entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + renderMarkdown(text) + '</div></div>');
1131
- textBuffer = [];
1132
- }
1133
- }
1134
-
1135
- for (const line of lines) {
1136
- // Garder les lignes vides dans le buffer pour les paragraphes markdown
1137
- if (!line.trim()) {
1138
- if (textBuffer.length > 0) textBuffer.push('');
1139
- continue;
1140
- }
1141
-
1142
- const timestampMatch = line.match(/^\\[(\\d{4}-\\d{2}-\\d{2}T[^\\]]+)\\]\\s*(.*)$/);
1143
- if (timestampMatch) {
1144
- flushTextBuffer();
1145
- const date = new Date(timestampMatch[1]);
1146
- const timeStr = date.toLocaleTimeString();
1147
- entries.push('<div class="log-entry timestamp">' + timeStr + ' - ' + escapeHtml(timestampMatch[2]) + '</div>');
1148
- continue;
1149
- }
1150
-
1151
- // [RAW] - Parse avec regex (pas JSON.parse car les lignes sont coupées)
1152
- if (line.startsWith('[RAW] ')) {
1153
- const raw = line.slice(6);
1154
-
1155
- // Ignorer les lignes de continuation (ne commencent pas par {)
1156
- if (!raw.startsWith('{')) {
1157
- continue;
1158
- }
1159
-
1160
- // Extraire tool_result content
1161
- if (raw.includes('"type":"tool_result"')) {
1162
- const contentStart = raw.indexOf('"content":"');
1163
- if (contentStart !== -1) {
1164
- // Extraire le contenu après "content":"
1165
- let content = raw.slice(contentStart + 11);
1166
- // Enlever le reste du JSON (approximatif car tronqué)
1167
- const endQuote = content.lastIndexOf('"');
1168
- if (endQuote > 0) content = content.slice(0, endQuote);
1169
- // Décoder les échappements
1170
- const decoded = content.replace(/\\\\n/g, '\\n').replace(/\\\\"/g, '"').replace(/\\\\t/g, '\\t');
1171
- // Vérifier si c'est du code avec numéros de ligne
1172
- if (/^\\s*\\d+[→|]/.test(decoded)) {
1173
- entries.push('<div class="log-message-card"><div class="log-message-header result-header">Tool Result</div><div class="log-message-body">' + formatCodeBlock(decoded) + '</div></div>');
1174
- } else {
1175
- // Résultat texte simple (liste de fichiers, etc.)
1176
- const lines = decoded.split('\\n').slice(0, 20); // Limiter à 20 lignes
1177
- const truncated = decoded.split('\\n').length > 20 ? '<div style="color:#8b949e;font-style:italic">... (truncated)</div>' : '';
1178
- entries.push('<div class="log-message-card"><div class="log-message-header result-header">Tool Result</div><div class="log-message-body" style="font-family:monospace;font-size:12px;white-space:pre-wrap;max-height:200px;overflow-y:auto">' + escapeHtml(lines.join('\\n')) + truncated + '</div></div>');
1179
- }
1180
- continue;
1181
- }
1182
- }
1183
-
1184
- // Extraire message assistant texte
1185
- const textMatch = raw.match(/"type":"text","text":"([^"]+)"/);
1186
- if (textMatch) {
1187
- const decoded = textMatch[1].replace(/\\\\n/g, '\\n').replace(/\\\\"/g, '"');
1188
- entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + renderMarkdown(decoded) + '</div></div>');
1189
- continue;
1190
- }
1191
-
1192
- // Extraire tool_use
1193
- const toolMatch = raw.match(/"type":"tool_use"[^}]*"name":"([^"]+)"/);
1194
- if (toolMatch) {
1195
- entries.push('<div class="log-entry tool-call"><span class="log-tool-badge">' + escapeHtml(toolMatch[1]) + '</span></div>');
1196
- continue;
1197
- }
1198
-
1199
- // Ignorer les autres RAW
1200
- continue;
1201
- }
1202
-
1203
- if (line.startsWith('[SYSTEM]')) {
1204
- flushTextBuffer();
1205
- entries.push('<div class="log-entry system"><span class="log-label">System</span><div class="log-content">' + escapeHtml(line.slice(9)) + '</div></div>');
1206
- continue;
1207
- }
1208
- if (line.startsWith('[ASSISTANT]')) {
1209
- flushTextBuffer();
1210
- entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + renderMarkdown(line.slice(12)) + '</div></div>');
1211
- continue;
1212
- }
1213
- if (line.startsWith('[TOOL]')) {
1214
- flushTextBuffer();
1215
- entries.push('<div class="log-entry tool-call"><span class="log-tool-badge">' + escapeHtml(line.slice(7)) + '</span></div>');
1216
- continue;
1217
- }
1218
- if (line.startsWith('[RESULT]')) {
1219
- flushTextBuffer();
1220
- const content = line.slice(9);
1221
- if (/^\\s*\\d+[→|]/.test(content)) {
1222
- entries.push('<div class="log-message-card"><div class="log-message-header result-header">Result</div><div class="log-message-body">' + formatCodeBlock(content) + '</div></div>');
1223
- } else {
1224
- entries.push('<div class="log-message-card"><div class="log-message-header result-header">Result</div><div class="log-message-body">' + escapeHtml(content.slice(0, 1000)) + (content.length > 1000 ? '...' : '') + '</div></div>');
1225
- }
1226
- continue;
1227
- }
1228
- if (line.startsWith('[ERROR]')) {
1229
- flushTextBuffer();
1230
- entries.push('<div class="log-entry error"><span class="log-label">Error</span><div class="log-content">' + escapeHtml(line.slice(8)) + '</div></div>');
1231
- continue;
1232
- }
1233
-
1234
- // Ligne de texte brut - ajouter au buffer pour regrouper
1235
- textBuffer.push(line);
1236
- }
1237
-
1238
- // Flush le buffer restant
1239
- flushTextBuffer();
1240
- return entries.join('');
1241
- }
1242
-
1243
- function startLogPolling() {
1244
- stopLogPolling();
1245
- logPollingInterval = setInterval(fetchLog, 1000);
1246
- fetchLog();
1247
- }
1248
-
1249
- function stopLogPolling() {
1250
- if (logPollingInterval) {
1251
- clearInterval(logPollingInterval);
1252
- logPollingInterval = null;
1253
- }
1254
- }
1255
-
1256
- async function fetchLog() {
1257
- try {
1258
- const res = await fetch('/api/issues/' + ISSUE_KEY + '/log');
1259
- const json = await res.json();
1260
- const log = document.getElementById('claude-log');
1261
-
1262
- if (json.success && json.data && (json.data.exists || json.data.content)) {
1263
- log.innerHTML = formatLogContent(json.data.content || '');
1264
- log.scrollTop = log.scrollHeight;
1265
- } else {
1266
- log.innerHTML = '<div class="log-entry system">' + t('issueView.noLog') + '</div>';
1267
- }
1268
- } catch (e) {
1269
- console.error('Log fetch error:', e);
1270
- }
1271
- }
1272
-
1273
- function onClaudeStart() {
1274
- const status = document.getElementById('claude-status');
1275
- status.className = 'claude-status processing';
1276
- status.textContent = t('status.processing');
1277
- startLogPolling();
1278
- }
1279
-
1280
- function onClaudeEnd(success, duration) {
1281
- stopLogPolling();
1282
- fetchLog();
1283
- const status = document.getElementById('claude-status');
1284
- if (success) {
1285
- status.className = 'claude-status success';
1286
- status.textContent = t('status.completed') + ' (' + (duration / 1000).toFixed(1) + 's)';
1287
- } else {
1288
- status.className = 'claude-status error';
1289
- status.textContent = t('status.failed');
1290
- }
1291
- }
1292
-
1293
- // Prompt Modal
1294
- async function showPrompt(columnSlug) {
1295
- const modal = document.getElementById('prompt-modal');
1296
- const title = document.getElementById('prompt-modal-title');
1297
- const content = document.getElementById('prompt-modal-content');
1298
-
1299
- title.textContent = t('issueView.loadingPrompt');
1300
- content.textContent = '';
1301
- modal.classList.add('visible');
1302
-
1303
- try {
1304
- const res = await fetch('/api/issues/' + ISSUE_KEY + '/prompt/' + columnSlug);
1305
- const json = await res.json();
1306
- if (json.success) {
1307
- title.textContent = 'Prompt → ' + json.data.column;
1308
- content.textContent = json.data.prompt;
1309
- } else {
1310
- title.textContent = t('issueView.promptError');
1311
- content.textContent = json.error || 'Error loading prompt';
1312
- }
1313
- } catch (e) {
1314
- title.textContent = t('issueView.promptError');
1315
- content.textContent = e.message;
1316
- }
1317
- }
1318
-
1319
- function closePromptModal(event) {
1320
- if (event && event.target !== event.currentTarget) return;
1321
- document.getElementById('prompt-modal').classList.remove('visible');
1322
- }
1323
-
1324
- // Log Modal (reuses prompt modal)
1325
- async function showLog(columnSlug) {
1326
- const modal = document.getElementById('prompt-modal');
1327
- const title = document.getElementById('prompt-modal-title');
1328
- const content = document.getElementById('prompt-modal-content');
1329
-
1330
- title.textContent = t('issueView.loadingLog') || 'Loading log...';
1331
- content.textContent = '';
1332
- modal.classList.add('visible');
1333
-
1334
- try {
1335
- const res = await fetch('/api/issues/' + ISSUE_KEY + '/log/' + columnSlug);
1336
- const json = await res.json();
1337
- if (json.success) {
1338
- title.textContent = 'Terminal → ' + columnSlug;
1339
- if (json.data.content) {
1340
- content.innerHTML = formatLogContent(json.data.content);
1341
- } else {
1342
- content.textContent = t('issueView.noLog') || 'No log available';
1343
- }
1344
- } else {
1345
- title.textContent = t('issueView.logError') || 'Log Error';
1346
- content.textContent = json.error || 'Error loading log';
1347
- }
1348
- } catch (e) {
1349
- title.textContent = t('issueView.logError') || 'Log Error';
1350
- content.textContent = e.message;
1351
- }
1352
- }
1353
-
1354
379
  // Init
1355
- updateLangUI();
1356
380
  renderDescription();
1357
- renderComments();
1358
381
  connectWebSocket();
1359
- fetchLog();
1360
382
  </script>
1361
383
  </body>
1362
384
  </html>`;