issue-flow 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-HMZTXCRT.js +30 -0
- package/dist/{analyze-556KCJKZ.js → analyze-Q4ONIWRZ.js} +22 -22
- package/dist/{apply-WVV7G7WN.js → apply-EEFPY2W4.js} +14 -14
- package/dist/{bench-UBFGKUHR.js → bench-WWWXLTFH.js} +23 -23
- package/dist/{chunk-GV2DABNZ.js → chunk-34OCSKOB.js} +12 -12
- package/dist/chunk-3R27MJ5R.js +25 -0
- package/dist/chunk-3R27MJ5R.js.map +1 -0
- package/dist/{chunk-PGN2VX4Y.js → chunk-45WZ6GMI.js} +32 -48
- package/dist/chunk-45WZ6GMI.js.map +1 -0
- package/dist/{chunk-H3EYBJZU.js → chunk-62GVVZQO.js} +10 -10
- package/dist/{chunk-GPLP7EPL.js → chunk-6K2LCFG6.js} +10 -10
- package/dist/{chunk-GPLP7EPL.js.map → chunk-6K2LCFG6.js.map} +1 -1
- package/dist/{chunk-OGVWJO4S.js → chunk-7F7LGMVO.js} +3 -3
- package/dist/{chunk-X6GY3CM2.js → chunk-7UMMFOL2.js} +104 -104
- package/dist/chunk-7UMMFOL2.js.map +1 -0
- package/dist/{chunk-OLUOZLU5.js → chunk-CWSQVHVV.js} +1691 -1075
- package/dist/chunk-CWSQVHVV.js.map +1 -0
- package/dist/{chunk-MXCOSPLJ.js → chunk-DGRTJD5R.js} +3 -3
- package/dist/{chunk-ROIIEVZH.js → chunk-DUS5ZNKL.js} +474 -361
- package/dist/chunk-DUS5ZNKL.js.map +1 -0
- package/dist/{chunk-BSBJXPLC.js → chunk-E4I6MMHD.js} +10 -10
- package/dist/{chunk-QZOBR6YF.js → chunk-ECU7YO7S.js} +4 -4
- package/dist/chunk-ECU7YO7S.js.map +1 -0
- package/dist/{chunk-EPFC4LHT.js → chunk-EG2MGDP5.js} +9 -9
- package/dist/chunk-HOAHMERN.js +229 -0
- package/dist/chunk-HOAHMERN.js.map +1 -0
- package/dist/{chunk-QVLIIGBU.js → chunk-ICM463EO.js} +60 -13
- package/dist/chunk-ICM463EO.js.map +1 -0
- package/dist/{chunk-4FUWWBTP.js → chunk-KMCS76ZW.js} +8 -8
- package/dist/{chunk-SW2MF47D.js → chunk-KRAGAUKD.js} +19 -19
- package/dist/{chunk-OOX3RMD3.js → chunk-LGHND47K.js} +4 -4
- package/dist/{chunk-VTF4RWS5.js → chunk-LI35PIIS.js} +7 -7
- package/dist/{chunk-Q2TZA6AC.js → chunk-LTH6XYZ3.js} +14 -14
- package/dist/{chunk-YA6FWFYW.js → chunk-NEWNXZ65.js} +62 -11
- package/dist/chunk-NEWNXZ65.js.map +1 -0
- package/dist/{chunk-2H43OVTU.js → chunk-OODXFCQV.js} +11 -11
- package/dist/{chunk-KV2K7MK2.js → chunk-PMLE7KAQ.js} +20 -12
- package/dist/chunk-PMLE7KAQ.js.map +1 -0
- package/dist/{chunk-R42YHLDV.js → chunk-PPZ3K4YM.js} +2 -2
- package/dist/{chunk-J2LJB2OH.js → chunk-QF7VEL3M.js} +739 -579
- package/dist/chunk-QF7VEL3M.js.map +1 -0
- package/dist/{chunk-VK65RNFA.js → chunk-QJ44VG5G.js} +3 -3
- package/dist/{chunk-FKWYD44M.js → chunk-QPGEFLMY.js} +2 -2
- package/dist/{chunk-NQCR3HSU.js → chunk-RNFUVKC3.js} +3 -3
- package/dist/{chunk-G5LACAXO.js → chunk-RYOUZ6EK.js} +2 -2
- package/dist/{chunk-MCLPSL6B.js → chunk-SA6HXL4Y.js} +3 -3
- package/dist/{chunk-GVOZ6NDO.js → chunk-SOM4QA6C.js} +3 -3
- package/dist/{chunk-73TS2H42.js → chunk-TZYINKCC.js} +154 -34
- package/dist/chunk-TZYINKCC.js.map +1 -0
- package/dist/{chunk-DBEMD34J.js → chunk-V7QOIQYC.js} +8 -8
- package/dist/{chunk-IAMIBZRF.js → chunk-WCCZJL7W.js} +2 -2
- package/dist/{chunk-OGN2NX57.js → chunk-XUEMATVY.js} +9 -9
- package/dist/{chunk-SVDEWOUD.js → chunk-XZUEFTUP.js} +6 -6
- package/dist/{chunk-WLJVL25O.js → chunk-YUNBOUQH.js} +6 -6
- package/dist/{chunk-WNROH2YE.js → chunk-YVPZUN6X.js} +2 -2
- package/dist/chunk-ZVZMWNCG.js +415 -0
- package/dist/chunk-ZVZMWNCG.js.map +1 -0
- package/dist/cli.js +57 -49
- package/dist/cli.js.map +1 -1
- package/dist/{config-UE7IP36J.js → config-LEBZZCJP.js} +13 -13
- package/dist/{conventions-Z3QC2NSG.js → conventions-XOT6YW5L.js} +15 -15
- package/dist/{diagnostics-2Z5HFQOO.js → diagnostics-HTCWF7F4.js} +7 -7
- package/dist/execute-LELOSR5T.js +37 -0
- package/dist/{generate-PB4L7V5N.js → generate-JGW5JSKB.js} +22 -22
- package/dist/{git-7MUOGVRG.js → git-A2IP32PQ.js} +2 -2
- package/dist/init-TO6MUXO4.js +26 -0
- package/dist/{operations-QDJAFZUN.js → operations-O4FORFIH.js} +15 -15
- package/dist/{permissions-XVXXENVP.js → permissions-G4XCAESF.js} +4 -4
- package/dist/plan-OXAT344Q.js +34 -0
- package/dist/{policy-6WQZB6B2.js → policy-U5NBFAO5.js} +14 -14
- package/dist/pr-UZHNKKZC.js +39 -0
- package/dist/pr-review-Q4ACAWDM.js +32 -0
- package/dist/prd-6CYEMLDY.js +33 -0
- package/dist/{ps-CX7756QG.js → ps-76L6ZAZL.js} +12 -12
- package/dist/{recorder-RK6XZYF3.js → recorder-2BAKPBRA.js} +7 -7
- package/dist/{registry-POSB35Q4.js → registry-XLHHYLTR.js} +5 -5
- package/dist/{resume-OQJZV3JH.js → resume-URCU6CIJ.js} +39 -38
- package/dist/{resume-OQJZV3JH.js.map → resume-URCU6CIJ.js.map} +1 -1
- package/dist/review-LVZT2TMW.js +35 -0
- package/dist/routing-EK6RBZM4.js +33 -0
- package/dist/run-2RXJCJKT.js +56 -0
- package/dist/{session-publisher-VVXOCJ2S.js → session-publisher-KAJQRHNZ.js} +3 -2
- package/dist/{state-manager-TIC73GQY.js → state-manager-UPHO2EA2.js} +4 -4
- package/dist/state-manager-UPHO2EA2.js.map +1 -0
- package/dist/{usage-WDDNSIHP.js → usage-KTMYXAPF.js} +12 -12
- package/dist/{web-IPGY52AK.js → web-B5UPHHR5.js} +20 -18
- package/dist/{web-IPGY52AK.js.map → web-B5UPHHR5.js.map} +1 -1
- package/package.json +6 -5
- package/web/AGENTS.md +125 -18
- package/web/public/app.css +420 -234
- package/web/public/app.js +371 -80
- package/web/public/index.html +106 -78
- package/dist/agent-PMWCDWOW.js +0 -30
- package/dist/chunk-73TS2H42.js.map +0 -1
- package/dist/chunk-7Z4Y3VKM.js +0 -26
- package/dist/chunk-7Z4Y3VKM.js.map +0 -1
- package/dist/chunk-AOAQU7PD.js +0 -180
- package/dist/chunk-AOAQU7PD.js.map +0 -1
- package/dist/chunk-J2LJB2OH.js.map +0 -1
- package/dist/chunk-KV2K7MK2.js.map +0 -1
- package/dist/chunk-OLUOZLU5.js.map +0 -1
- package/dist/chunk-PGN2VX4Y.js.map +0 -1
- package/dist/chunk-QVLIIGBU.js.map +0 -1
- package/dist/chunk-QZOBR6YF.js.map +0 -1
- package/dist/chunk-ROIIEVZH.js.map +0 -1
- package/dist/chunk-X6GY3CM2.js.map +0 -1
- package/dist/chunk-YA6FWFYW.js.map +0 -1
- package/dist/execute-QYLJ5L4T.js +0 -36
- package/dist/init-XBERAZ4O.js +0 -24
- package/dist/plan-RXOW32KY.js +0 -34
- package/dist/pr-3WFYAVA7.js +0 -39
- package/dist/pr-review-RK63ZEMM.js +0 -32
- package/dist/prd-LRV6IW3E.js +0 -33
- package/dist/review-AEFISJZD.js +0 -35
- package/dist/routing-EA5NJGNU.js +0 -94
- package/dist/routing-EA5NJGNU.js.map +0 -1
- package/dist/run-HSC7R33Y.js +0 -55
- /package/dist/{agent-PMWCDWOW.js.map → agent-HMZTXCRT.js.map} +0 -0
- /package/dist/{analyze-556KCJKZ.js.map → analyze-Q4ONIWRZ.js.map} +0 -0
- /package/dist/{apply-WVV7G7WN.js.map → apply-EEFPY2W4.js.map} +0 -0
- /package/dist/{bench-UBFGKUHR.js.map → bench-WWWXLTFH.js.map} +0 -0
- /package/dist/{chunk-GV2DABNZ.js.map → chunk-34OCSKOB.js.map} +0 -0
- /package/dist/{chunk-H3EYBJZU.js.map → chunk-62GVVZQO.js.map} +0 -0
- /package/dist/{chunk-OGVWJO4S.js.map → chunk-7F7LGMVO.js.map} +0 -0
- /package/dist/{chunk-MXCOSPLJ.js.map → chunk-DGRTJD5R.js.map} +0 -0
- /package/dist/{chunk-BSBJXPLC.js.map → chunk-E4I6MMHD.js.map} +0 -0
- /package/dist/{chunk-EPFC4LHT.js.map → chunk-EG2MGDP5.js.map} +0 -0
- /package/dist/{chunk-4FUWWBTP.js.map → chunk-KMCS76ZW.js.map} +0 -0
- /package/dist/{chunk-SW2MF47D.js.map → chunk-KRAGAUKD.js.map} +0 -0
- /package/dist/{chunk-OOX3RMD3.js.map → chunk-LGHND47K.js.map} +0 -0
- /package/dist/{chunk-VTF4RWS5.js.map → chunk-LI35PIIS.js.map} +0 -0
- /package/dist/{chunk-Q2TZA6AC.js.map → chunk-LTH6XYZ3.js.map} +0 -0
- /package/dist/{chunk-2H43OVTU.js.map → chunk-OODXFCQV.js.map} +0 -0
- /package/dist/{chunk-R42YHLDV.js.map → chunk-PPZ3K4YM.js.map} +0 -0
- /package/dist/{chunk-VK65RNFA.js.map → chunk-QJ44VG5G.js.map} +0 -0
- /package/dist/{chunk-FKWYD44M.js.map → chunk-QPGEFLMY.js.map} +0 -0
- /package/dist/{chunk-NQCR3HSU.js.map → chunk-RNFUVKC3.js.map} +0 -0
- /package/dist/{chunk-G5LACAXO.js.map → chunk-RYOUZ6EK.js.map} +0 -0
- /package/dist/{chunk-MCLPSL6B.js.map → chunk-SA6HXL4Y.js.map} +0 -0
- /package/dist/{chunk-GVOZ6NDO.js.map → chunk-SOM4QA6C.js.map} +0 -0
- /package/dist/{chunk-DBEMD34J.js.map → chunk-V7QOIQYC.js.map} +0 -0
- /package/dist/{chunk-IAMIBZRF.js.map → chunk-WCCZJL7W.js.map} +0 -0
- /package/dist/{chunk-OGN2NX57.js.map → chunk-XUEMATVY.js.map} +0 -0
- /package/dist/{chunk-SVDEWOUD.js.map → chunk-XZUEFTUP.js.map} +0 -0
- /package/dist/{chunk-WLJVL25O.js.map → chunk-YUNBOUQH.js.map} +0 -0
- /package/dist/{chunk-WNROH2YE.js.map → chunk-YVPZUN6X.js.map} +0 -0
- /package/dist/{config-UE7IP36J.js.map → config-LEBZZCJP.js.map} +0 -0
- /package/dist/{conventions-Z3QC2NSG.js.map → conventions-XOT6YW5L.js.map} +0 -0
- /package/dist/{diagnostics-2Z5HFQOO.js.map → diagnostics-HTCWF7F4.js.map} +0 -0
- /package/dist/{execute-QYLJ5L4T.js.map → execute-LELOSR5T.js.map} +0 -0
- /package/dist/{generate-PB4L7V5N.js.map → generate-JGW5JSKB.js.map} +0 -0
- /package/dist/{git-7MUOGVRG.js.map → git-A2IP32PQ.js.map} +0 -0
- /package/dist/{init-XBERAZ4O.js.map → init-TO6MUXO4.js.map} +0 -0
- /package/dist/{operations-QDJAFZUN.js.map → operations-O4FORFIH.js.map} +0 -0
- /package/dist/{permissions-XVXXENVP.js.map → permissions-G4XCAESF.js.map} +0 -0
- /package/dist/{plan-RXOW32KY.js.map → plan-OXAT344Q.js.map} +0 -0
- /package/dist/{policy-6WQZB6B2.js.map → policy-U5NBFAO5.js.map} +0 -0
- /package/dist/{pr-3WFYAVA7.js.map → pr-UZHNKKZC.js.map} +0 -0
- /package/dist/{pr-review-RK63ZEMM.js.map → pr-review-Q4ACAWDM.js.map} +0 -0
- /package/dist/{prd-LRV6IW3E.js.map → prd-6CYEMLDY.js.map} +0 -0
- /package/dist/{ps-CX7756QG.js.map → ps-76L6ZAZL.js.map} +0 -0
- /package/dist/{recorder-RK6XZYF3.js.map → recorder-2BAKPBRA.js.map} +0 -0
- /package/dist/{registry-POSB35Q4.js.map → registry-XLHHYLTR.js.map} +0 -0
- /package/dist/{review-AEFISJZD.js.map → review-LVZT2TMW.js.map} +0 -0
- /package/dist/{run-HSC7R33Y.js.map → routing-EK6RBZM4.js.map} +0 -0
- /package/dist/{session-publisher-VVXOCJ2S.js.map → run-2RXJCJKT.js.map} +0 -0
- /package/dist/{state-manager-TIC73GQY.js.map → session-publisher-KAJQRHNZ.js.map} +0 -0
- /package/dist/{usage-WDDNSIHP.js.map → usage-KTMYXAPF.js.map} +0 -0
package/web/public/app.js
CHANGED
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
// todo texto dinâmico entra via textContent (nunca innerHTML com dados
|
|
5
5
|
// do snapshot).
|
|
6
6
|
(() => {
|
|
7
|
-
'use strict';
|
|
8
|
-
|
|
9
7
|
const REFRESH_OPTIONS = [3, 5, 10, 30];
|
|
10
8
|
const PAUSED = 0;
|
|
11
9
|
const STORAGE_KEY = 'issue-flow:refresh-seconds';
|
|
@@ -26,6 +24,16 @@
|
|
|
26
24
|
failed: 'falhou',
|
|
27
25
|
};
|
|
28
26
|
|
|
27
|
+
// O resumo do dashboard conta *execuções* (feminino), então não reaproveita
|
|
28
|
+
// STATUS_LABELS, que qualifica a execução no masculino usado pelo badge.
|
|
29
|
+
const SUMMARY_STATUS_ORDER = ['running', 'idle', 'completed', 'failed'];
|
|
30
|
+
const SUMMARY_STATUS_LABELS = {
|
|
31
|
+
running: ['em execução', 'em execução'],
|
|
32
|
+
idle: ['aguardando', 'aguardando'],
|
|
33
|
+
completed: ['concluída', 'concluídas'],
|
|
34
|
+
failed: ['com falha', 'com falha'],
|
|
35
|
+
};
|
|
36
|
+
|
|
29
37
|
const PHASE_ICONS = {
|
|
30
38
|
pending: '○',
|
|
31
39
|
running: '●',
|
|
@@ -71,6 +79,7 @@
|
|
|
71
79
|
dashboardMeta: document.getElementById('dashboard-meta'),
|
|
72
80
|
backToDashboard: document.getElementById('back-to-dashboard'),
|
|
73
81
|
issueLink: document.getElementById('issue-link'),
|
|
82
|
+
issueHeadline: document.getElementById('issue-headline'),
|
|
74
83
|
branchLine: document.getElementById('branch-line'),
|
|
75
84
|
statusBadge: document.getElementById('status-badge'),
|
|
76
85
|
elapsed: document.getElementById('elapsed'),
|
|
@@ -91,6 +100,7 @@
|
|
|
91
100
|
configuration: document.getElementById('configuration'),
|
|
92
101
|
appVersion: document.getElementById('app-version'),
|
|
93
102
|
appVersionDashboard: document.getElementById('app-version-dashboard'),
|
|
103
|
+
dashboardSummary: document.getElementById('dashboard-summary'),
|
|
94
104
|
phases: document.getElementById('phases'),
|
|
95
105
|
nextSteps: document.getElementById('next-steps'),
|
|
96
106
|
stories: document.getElementById('stories'),
|
|
@@ -129,6 +139,7 @@
|
|
|
129
139
|
// guardar o nó levaria a uma referência morta.
|
|
130
140
|
selectedDetail: null,
|
|
131
141
|
configWritable: false,
|
|
142
|
+
configData: null,
|
|
132
143
|
// Identifica o processo que serviu os assets atuais. Uma troca significa
|
|
133
144
|
// que --restart-web colocou código novo no mesmo origin e exige reload.
|
|
134
145
|
serverInstanceId: null,
|
|
@@ -156,6 +167,21 @@
|
|
|
156
167
|
return node;
|
|
157
168
|
}
|
|
158
169
|
|
|
170
|
+
// Ponto pulsante único do painel — o mesmo componente em "Executando agora"
|
|
171
|
+
// e no card do dashboard. Sem texto: o rótulo adjacente carrega o significado.
|
|
172
|
+
function liveDot() {
|
|
173
|
+
const dot = el('span', 'live');
|
|
174
|
+
dot.setAttribute('aria-hidden', 'true');
|
|
175
|
+
return dot;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Ícone textual (○ ● ✓ ✗): a cor e o rótulo ao lado já dizem o estado.
|
|
179
|
+
function statusIcon(status, symbol) {
|
|
180
|
+
const icon = el('span', 'item-icon icon-' + status, symbol || PHASE_ICONS[status] || '○');
|
|
181
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
182
|
+
return icon;
|
|
183
|
+
}
|
|
184
|
+
|
|
159
185
|
function link(href, text, className) {
|
|
160
186
|
const node = el('a', className, text);
|
|
161
187
|
node.href = href;
|
|
@@ -291,7 +317,8 @@
|
|
|
291
317
|
...story,
|
|
292
318
|
status: STORY_STATUS_LABELS[status] !== undefined ? status : 'backlog',
|
|
293
319
|
stage: STORY_STAGE_LABELS[stage] !== undefined ? stage : 'pending',
|
|
294
|
-
stageSince:
|
|
320
|
+
stageSince:
|
|
321
|
+
story.stageSince !== null && story.stageSince !== undefined ? story.stageSince : null,
|
|
295
322
|
stageDetail:
|
|
296
323
|
story.stageDetail !== null && story.stageDetail !== undefined ? story.stageDetail : null,
|
|
297
324
|
dependencies: list(story.dependencies),
|
|
@@ -318,15 +345,34 @@
|
|
|
318
345
|
// Troca apenas visibilidade e estado ARIA. Não toca no polling: render()
|
|
319
346
|
// segue atualizando os dois painéis, então a aba inativa nunca fica defasada.
|
|
320
347
|
|
|
321
|
-
function setActiveTab(tabId) {
|
|
348
|
+
function setActiveTab(tabId, options) {
|
|
322
349
|
state.activeTab = tabId;
|
|
350
|
+
let focused = null;
|
|
323
351
|
for (const tab of els.tabs) {
|
|
324
352
|
const active = tab.id === tabId;
|
|
325
353
|
tab.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
354
|
+
tab.tabIndex = active ? 0 : -1;
|
|
326
355
|
tab.classList.toggle('is-active', active);
|
|
327
356
|
const panel = document.getElementById(tab.getAttribute('aria-controls'));
|
|
328
357
|
if (panel) panel.hidden = !active;
|
|
358
|
+
if (active) focused = tab;
|
|
329
359
|
}
|
|
360
|
+
if (options && options.focus && focused) focused.focus();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Padrão ARIA de tablist: setas movem entre as abas, Home/End vão às pontas.
|
|
364
|
+
// Só a aba ativa fica no fluxo do Tab (roving tabindex).
|
|
365
|
+
function onTabListKeydown(event) {
|
|
366
|
+
const current = els.tabs.indexOf(event.target);
|
|
367
|
+
if (current === -1) return;
|
|
368
|
+
let next = -1;
|
|
369
|
+
if (event.key === 'ArrowRight') next = (current + 1) % els.tabs.length;
|
|
370
|
+
else if (event.key === 'ArrowLeft') next = (current - 1 + els.tabs.length) % els.tabs.length;
|
|
371
|
+
else if (event.key === 'Home') next = 0;
|
|
372
|
+
else if (event.key === 'End') next = els.tabs.length - 1;
|
|
373
|
+
else return;
|
|
374
|
+
event.preventDefault();
|
|
375
|
+
setActiveTab(els.tabs[next].id, { focus: true });
|
|
330
376
|
}
|
|
331
377
|
|
|
332
378
|
// ---- Tema ------------------------------------------------------------------
|
|
@@ -613,9 +659,7 @@
|
|
|
613
659
|
}
|
|
614
660
|
|
|
615
661
|
const nextId =
|
|
616
|
-
resolved.session && resolved.session.sessionId != null
|
|
617
|
-
? resolved.session.sessionId
|
|
618
|
-
: null;
|
|
662
|
+
resolved.session && resolved.session.sessionId != null ? resolved.session.sessionId : null;
|
|
619
663
|
const sessionChanged = state.detailSessionId !== nextId;
|
|
620
664
|
if (sessionChanged) {
|
|
621
665
|
clearDetailState();
|
|
@@ -662,6 +706,12 @@
|
|
|
662
706
|
state.snapshot = await res.json();
|
|
663
707
|
render();
|
|
664
708
|
}
|
|
709
|
+
const configUrl = 'api/config?session=' + encodeURIComponent(state.detailSessionId || '');
|
|
710
|
+
const configRes = await fetch(configUrl, { cache: 'no-store' });
|
|
711
|
+
if (configRes.ok) {
|
|
712
|
+
state.configData = await configRes.json();
|
|
713
|
+
if (state.snapshot) renderConfiguration(state.snapshot);
|
|
714
|
+
}
|
|
665
715
|
if (state.eventsUrl) {
|
|
666
716
|
const eventsRes = await fetch(state.eventsUrl, { cache: 'no-store' });
|
|
667
717
|
if (!eventsRes.ok) throw new Error('HTTP ' + eventsRes.status);
|
|
@@ -697,6 +747,9 @@
|
|
|
697
747
|
function renderEmptyDetail() {
|
|
698
748
|
if (state.snapshot) return;
|
|
699
749
|
document.title = 'issue-flow';
|
|
750
|
+
els.issueLink.hidden = true;
|
|
751
|
+
els.issueHeadline.textContent = 'Nenhuma execução ativa';
|
|
752
|
+
els.branchLine.textContent = '';
|
|
700
753
|
els.statusBadge.textContent = STATUS_LABELS.idle;
|
|
701
754
|
els.statusBadge.className = 'badge status-idle';
|
|
702
755
|
els.elapsed.textContent = '—';
|
|
@@ -711,13 +764,29 @@
|
|
|
711
764
|
return normalized.slice(0, max - 1).trimEnd() + '…';
|
|
712
765
|
}
|
|
713
766
|
|
|
767
|
+
// Linha secundária do header do dashboard: quantas execuções e em que
|
|
768
|
+
// estado. Substitui a legenda estática que só repetia o h1.
|
|
769
|
+
function renderDashboardSummary(sessions) {
|
|
770
|
+
const counts = {};
|
|
771
|
+
for (const session of sessions) {
|
|
772
|
+
const status = session.status || 'idle';
|
|
773
|
+
counts[status] = (counts[status] || 0) + 1;
|
|
774
|
+
}
|
|
775
|
+
const parts = [];
|
|
776
|
+
for (const status of SUMMARY_STATUS_ORDER) {
|
|
777
|
+
const count = counts[status];
|
|
778
|
+
if (!count) continue;
|
|
779
|
+
const label = SUMMARY_STATUS_LABELS[status];
|
|
780
|
+
parts.push(count + ' ' + (count === 1 ? label[0] : label[1]));
|
|
781
|
+
}
|
|
782
|
+
const total = sessions.length + (sessions.length === 1 ? ' execução' : ' execuções');
|
|
783
|
+
els.dashboardSummary.textContent = parts.length > 0 ? total + ' · ' + parts.join(' · ') : total;
|
|
784
|
+
}
|
|
785
|
+
|
|
714
786
|
function renderDashboard(sessions) {
|
|
715
787
|
const active = document.activeElement;
|
|
716
788
|
const focusedId =
|
|
717
|
-
active &&
|
|
718
|
-
active.dataset &&
|
|
719
|
-
els.dashboard.contains(active) &&
|
|
720
|
-
active.dataset.sessionId
|
|
789
|
+
active && active.dataset && els.dashboard.contains(active) && active.dataset.sessionId
|
|
721
790
|
? active.dataset.sessionId
|
|
722
791
|
: null;
|
|
723
792
|
|
|
@@ -726,10 +795,13 @@
|
|
|
726
795
|
if (sessions.length === 0) {
|
|
727
796
|
els.dashboard.appendChild(el('p', 'empty', 'Nenhuma execução ativa.'));
|
|
728
797
|
els.dashboardMeta.textContent = '0 execuções';
|
|
798
|
+
els.dashboardSummary.textContent = 'Nenhuma execução ativa';
|
|
729
799
|
document.title = 'issue-flow';
|
|
730
800
|
return;
|
|
731
801
|
}
|
|
732
802
|
|
|
803
|
+
renderDashboardSummary(sessions);
|
|
804
|
+
|
|
733
805
|
for (const session of sessions) {
|
|
734
806
|
// <button> só aceita phrasing content — mesmos spans do Kanban.
|
|
735
807
|
const card = el('button', 'dashboard-card');
|
|
@@ -753,20 +825,15 @@
|
|
|
753
825
|
if (session.issueNumber !== null && session.issueNumber !== undefined) {
|
|
754
826
|
titleRow.appendChild(el('span', 'dashboard-issue', '#' + session.issueNumber));
|
|
755
827
|
}
|
|
756
|
-
titleRow.appendChild(
|
|
757
|
-
el('span', 'dashboard-title', session.issueTitle || 'Sem título'),
|
|
758
|
-
);
|
|
828
|
+
titleRow.appendChild(el('span', 'dashboard-title', session.issueTitle || 'Sem título'));
|
|
759
829
|
card.appendChild(titleRow);
|
|
760
830
|
|
|
761
831
|
const summary = truncateText(session.issueDescription, DESCRIPTION_PREVIEW);
|
|
762
832
|
card.appendChild(el('span', 'dashboard-summary muted', summary || 'Sem descrição'));
|
|
763
833
|
|
|
764
834
|
const meta = el('span', 'dashboard-meta-row');
|
|
765
|
-
meta.appendChild(
|
|
766
|
-
|
|
767
|
-
);
|
|
768
|
-
const percent =
|
|
769
|
-
typeof session.progressPercent === 'number' ? session.progressPercent : 0;
|
|
835
|
+
meta.appendChild(el('span', null, 'Fase: ' + (session.currentPhase || '—')));
|
|
836
|
+
const percent = typeof session.progressPercent === 'number' ? session.progressPercent : 0;
|
|
770
837
|
meta.appendChild(el('span', null, percent + '%'));
|
|
771
838
|
const elapsed =
|
|
772
839
|
typeof session.elapsedSeconds === 'number'
|
|
@@ -802,7 +869,10 @@
|
|
|
802
869
|
card.appendChild(progress);
|
|
803
870
|
|
|
804
871
|
if (session.status === 'running') {
|
|
805
|
-
|
|
872
|
+
const live = el('span', 'live-label');
|
|
873
|
+
live.appendChild(liveDot());
|
|
874
|
+
live.appendChild(document.createTextNode('ao vivo'));
|
|
875
|
+
card.appendChild(live);
|
|
806
876
|
}
|
|
807
877
|
|
|
808
878
|
card.addEventListener('click', () => {
|
|
@@ -860,8 +930,11 @@
|
|
|
860
930
|
document.title = (prefix + issue + ' · issue-flow').replace(/^ · /, '');
|
|
861
931
|
}
|
|
862
932
|
|
|
933
|
+
// O h1 é a execução — número, título, e ao lado status, tempo decorrido e
|
|
934
|
+
// branch. A marca do produto vive só no <title> do documento.
|
|
863
935
|
function renderHeader(snapshot) {
|
|
864
|
-
|
|
936
|
+
const hasIssue = snapshot.issue.number !== null && snapshot.issue.number !== undefined;
|
|
937
|
+
if (hasIssue) {
|
|
865
938
|
els.issueLink.hidden = false;
|
|
866
939
|
els.issueLink.textContent = '#' + snapshot.issue.number;
|
|
867
940
|
if (snapshot.issue.url) els.issueLink.href = snapshot.issue.url;
|
|
@@ -870,6 +943,10 @@
|
|
|
870
943
|
els.issueLink.hidden = true;
|
|
871
944
|
}
|
|
872
945
|
|
|
946
|
+
// session.json antigo pode não ter título; sem issue vinculada, nem número.
|
|
947
|
+
els.issueHeadline.textContent =
|
|
948
|
+
snapshot.issue.title || (hasIssue ? 'Sem título' : 'Execução sem issue vinculada');
|
|
949
|
+
|
|
873
950
|
const branch = snapshot.git.branch;
|
|
874
951
|
const base = snapshot.git.baseBranch;
|
|
875
952
|
const branchMode =
|
|
@@ -929,18 +1006,13 @@
|
|
|
929
1006
|
clear(els.issueSummary);
|
|
930
1007
|
const issue = snapshot.issue || {};
|
|
931
1008
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
el('span', 'mono', issue.number !== null && issue.number !== undefined ? '#' + issue.number : '—'),
|
|
935
|
-
);
|
|
936
|
-
heading.appendChild(document.createTextNode(' ' + (issue.title || 'Sem título')));
|
|
937
|
-
els.issueSummary.appendChild(heading);
|
|
938
|
-
|
|
1009
|
+
// Número e título saem no h1 do header (renderHeader); aqui fica só o que
|
|
1010
|
+
// o header não carrega — estado, labels e descrição.
|
|
939
1011
|
const meta = el('div', 'issue-summary-meta');
|
|
940
1012
|
const state = issue.state || null;
|
|
941
|
-
const stateClass =
|
|
1013
|
+
const stateClass =
|
|
1014
|
+
state === 'open' ? 'state-open' : state === 'closed' ? 'state-closed' : 'state-unknown';
|
|
942
1015
|
meta.appendChild(el('span', 'badge ' + stateClass, state || 'estado desconhecido'));
|
|
943
|
-
meta.appendChild(el('span', 'muted', 'Prioridade: Não definida'));
|
|
944
1016
|
els.issueSummary.appendChild(meta);
|
|
945
1017
|
|
|
946
1018
|
const labels = issue.labels || [];
|
|
@@ -952,7 +1024,9 @@
|
|
|
952
1024
|
els.issueSummary.appendChild(labelRow);
|
|
953
1025
|
}
|
|
954
1026
|
|
|
955
|
-
els.issueSummary.appendChild(
|
|
1027
|
+
els.issueSummary.appendChild(
|
|
1028
|
+
el('p', 'issue-description', issue.description || 'Sem descrição.'),
|
|
1029
|
+
);
|
|
956
1030
|
}
|
|
957
1031
|
|
|
958
1032
|
function metaRow(grid, label, value, className) {
|
|
@@ -1005,16 +1079,16 @@
|
|
|
1005
1079
|
const messages = {
|
|
1006
1080
|
idle: 'Nenhuma execução em andamento.',
|
|
1007
1081
|
completed: 'Execução concluída.',
|
|
1008
|
-
failed: 'Execução falhou
|
|
1082
|
+
failed: 'Execução falhou. Veja os erros acima.',
|
|
1009
1083
|
};
|
|
1010
1084
|
els.now.appendChild(el('p', 'empty', messages[snapshot.status] || '—'));
|
|
1011
1085
|
return;
|
|
1012
1086
|
}
|
|
1013
1087
|
|
|
1014
1088
|
const grid = el('dl', 'now-grid');
|
|
1015
|
-
const phase = el('dd');
|
|
1089
|
+
const phase = el('dd', 'now-phase');
|
|
1016
1090
|
if (snapshot.currentPhase) {
|
|
1017
|
-
phase.appendChild(
|
|
1091
|
+
phase.appendChild(liveDot());
|
|
1018
1092
|
phase.appendChild(document.createTextNode(snapshot.currentPhase));
|
|
1019
1093
|
} else {
|
|
1020
1094
|
phase.textContent = '—';
|
|
@@ -1024,7 +1098,7 @@
|
|
|
1024
1098
|
|
|
1025
1099
|
const activity = snapshot.currentActivity;
|
|
1026
1100
|
if (activity) {
|
|
1027
|
-
if (activity.story) nowRow(grid, '
|
|
1101
|
+
if (activity.story) nowRow(grid, 'User story', activity.story);
|
|
1028
1102
|
if (activity.tool) nowRow(grid, 'Ferramenta', activity.tool);
|
|
1029
1103
|
if (activity.detail) nowRow(grid, 'Detalhe', activity.detail);
|
|
1030
1104
|
const since = el('dd', null, formatAgo(activity.since));
|
|
@@ -1063,6 +1137,7 @@
|
|
|
1063
1137
|
env: 'variável de ambiente',
|
|
1064
1138
|
cli: 'override da execução',
|
|
1065
1139
|
fallback: 'fallback',
|
|
1140
|
+
recommended: 'política recomendada',
|
|
1066
1141
|
};
|
|
1067
1142
|
return labels[source] || source || 'não informado';
|
|
1068
1143
|
}
|
|
@@ -1107,7 +1182,9 @@
|
|
|
1107
1182
|
nowRow(
|
|
1108
1183
|
summary,
|
|
1109
1184
|
'Harness padrão',
|
|
1110
|
-
(config.defaultProvider.value || '—') +
|
|
1185
|
+
(config.defaultProvider.value || '—') +
|
|
1186
|
+
' · ' +
|
|
1187
|
+
configSourceLabel(config.defaultProvider.source),
|
|
1111
1188
|
);
|
|
1112
1189
|
nowRow(
|
|
1113
1190
|
summary,
|
|
@@ -1120,21 +1197,126 @@
|
|
|
1120
1197
|
nowRow(summary, 'Precedência', list(config.precedence).join(' → '));
|
|
1121
1198
|
els.configuration.appendChild(summary);
|
|
1122
1199
|
|
|
1200
|
+
const liveRouting = state.configData && state.configData.routing;
|
|
1201
|
+
if (liveRouting) {
|
|
1202
|
+
const routingSummary = el('dl', 'now-grid config-routing-summary');
|
|
1203
|
+
nowRow(routingSummary, 'Routing', liveRouting.mode + ' · perfil ' + liveRouting.profile);
|
|
1204
|
+
nowRow(
|
|
1205
|
+
routingSummary,
|
|
1206
|
+
'Política',
|
|
1207
|
+
liveRouting.policy === 'recommended' ? 'recomendada (opt-in)' : 'score adaptativo',
|
|
1208
|
+
);
|
|
1209
|
+
els.configuration.appendChild(routingSummary);
|
|
1210
|
+
|
|
1211
|
+
if (state.configWritable) {
|
|
1212
|
+
const routingForm = el('form', 'config-form config-routing-form');
|
|
1213
|
+
const mode = el('select');
|
|
1214
|
+
mode.setAttribute('aria-label', 'Modo de routing');
|
|
1215
|
+
for (const value of ['off', 'shadow', 'recommend', 'active']) {
|
|
1216
|
+
const option = el('option', null, value);
|
|
1217
|
+
option.value = value;
|
|
1218
|
+
mode.appendChild(option);
|
|
1219
|
+
}
|
|
1220
|
+
mode.value = liveRouting.mode;
|
|
1221
|
+
const profile = el('select');
|
|
1222
|
+
profile.setAttribute('aria-label', 'Perfil de routing');
|
|
1223
|
+
for (const value of ['economy', 'balanced', 'quality', 'speed']) {
|
|
1224
|
+
const option = el('option', null, value);
|
|
1225
|
+
option.value = value;
|
|
1226
|
+
profile.appendChild(option);
|
|
1227
|
+
}
|
|
1228
|
+
profile.value = liveRouting.profile;
|
|
1229
|
+
const saveRouting = el('button', 'config-save', 'Salvar routing');
|
|
1230
|
+
saveRouting.type = 'submit';
|
|
1231
|
+
const applyPolicy = el(
|
|
1232
|
+
'button',
|
|
1233
|
+
'config-save config-policy',
|
|
1234
|
+
'Aplicar política recomendada',
|
|
1235
|
+
);
|
|
1236
|
+
applyPolicy.type = 'button';
|
|
1237
|
+
const routingFeedback = el('span', 'muted');
|
|
1238
|
+
routingForm.appendChild(mode);
|
|
1239
|
+
routingForm.appendChild(profile);
|
|
1240
|
+
routingForm.appendChild(saveRouting);
|
|
1241
|
+
routingForm.appendChild(applyPolicy);
|
|
1242
|
+
routingForm.appendChild(routingFeedback);
|
|
1243
|
+
routingForm.addEventListener('submit', async (event) => {
|
|
1244
|
+
event.preventDefault();
|
|
1245
|
+
await saveRoutingConfig(
|
|
1246
|
+
{ mode: mode.value, profile: profile.value },
|
|
1247
|
+
saveRouting,
|
|
1248
|
+
routingFeedback,
|
|
1249
|
+
);
|
|
1250
|
+
});
|
|
1251
|
+
applyPolicy.addEventListener('click', async () => {
|
|
1252
|
+
await saveRoutingConfig({ policy: 'recommended' }, applyPolicy, routingFeedback);
|
|
1253
|
+
});
|
|
1254
|
+
els.configuration.appendChild(routingForm);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1123
1258
|
const phases = el('div', 'config-phase-grid');
|
|
1124
1259
|
for (const phase of list(config.phases)) {
|
|
1125
|
-
const row = el('button', 'config-phase-row');
|
|
1126
|
-
row.type = 'button';
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
)
|
|
1136
|
-
|
|
1137
|
-
|
|
1260
|
+
const row = el(state.configWritable ? 'form' : 'button', 'config-phase-row');
|
|
1261
|
+
if (!state.configWritable) row.type = 'button';
|
|
1262
|
+
if (state.configWritable) {
|
|
1263
|
+
row.classList.add('is-editable');
|
|
1264
|
+
const phaseButton = el('button', 'config-phase-open mono', phase.phase);
|
|
1265
|
+
phaseButton.type = 'button';
|
|
1266
|
+
phaseButton.addEventListener('click', () => openDrawer('phase', phase.phase));
|
|
1267
|
+
row.appendChild(phaseButton);
|
|
1268
|
+
const provider = el('select');
|
|
1269
|
+
provider.setAttribute('aria-label', 'Harness para ' + phase.phase);
|
|
1270
|
+
fillProviderSelect(provider, phase.provider.value || 'claude');
|
|
1271
|
+
const model = el('select');
|
|
1272
|
+
model.setAttribute('aria-label', 'Tier e modelo para ' + phase.phase);
|
|
1273
|
+
fillModelSelect(model, provider.value, phase.model.value || '');
|
|
1274
|
+
provider.addEventListener('change', () => fillModelSelect(model, provider.value, ''));
|
|
1275
|
+
const save = el('button', 'config-save', 'Salvar');
|
|
1276
|
+
save.type = 'submit';
|
|
1277
|
+
const feedback = el('span', 'muted config-phase-feedback');
|
|
1278
|
+
row.appendChild(provider);
|
|
1279
|
+
row.appendChild(model);
|
|
1280
|
+
row.appendChild(save);
|
|
1281
|
+
row.appendChild(feedback);
|
|
1282
|
+
row.addEventListener('submit', async (event) => {
|
|
1283
|
+
event.preventDefault();
|
|
1284
|
+
save.disabled = true;
|
|
1285
|
+
feedback.textContent = 'salvando…';
|
|
1286
|
+
try {
|
|
1287
|
+
const response = await fetch('api/config/agent', {
|
|
1288
|
+
method: 'POST',
|
|
1289
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1290
|
+
body: JSON.stringify({
|
|
1291
|
+
provider: provider.value,
|
|
1292
|
+
model: model.value,
|
|
1293
|
+
phase: phase.phase,
|
|
1294
|
+
}),
|
|
1295
|
+
});
|
|
1296
|
+
const result = await response.json();
|
|
1297
|
+
if (!response.ok) throw new Error(result.error || 'falha ao salvar');
|
|
1298
|
+
feedback.textContent = 'salvo para a próxima execução';
|
|
1299
|
+
} catch (error) {
|
|
1300
|
+
feedback.textContent = error.message || 'falha ao salvar';
|
|
1301
|
+
} finally {
|
|
1302
|
+
save.disabled = false;
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
} else {
|
|
1306
|
+
row.appendChild(el('span', 'mono', phase.phase));
|
|
1307
|
+
row.appendChild(el('span', null, phase.provider.value || '—'));
|
|
1308
|
+
row.appendChild(el('span', null, phase.model.value || 'default do provider'));
|
|
1309
|
+
row.appendChild(
|
|
1310
|
+
el(
|
|
1311
|
+
'span',
|
|
1312
|
+
'muted',
|
|
1313
|
+
configSourceLabel(phase.provider.source) +
|
|
1314
|
+
' / ' +
|
|
1315
|
+
configSourceLabel(phase.model.source),
|
|
1316
|
+
),
|
|
1317
|
+
);
|
|
1318
|
+
row.addEventListener('click', () => openDrawer('phase', phase.phase));
|
|
1319
|
+
}
|
|
1138
1320
|
phases.appendChild(row);
|
|
1139
1321
|
}
|
|
1140
1322
|
els.configuration.appendChild(phases);
|
|
@@ -1182,6 +1364,75 @@
|
|
|
1182
1364
|
els.configuration.appendChild(form);
|
|
1183
1365
|
}
|
|
1184
1366
|
|
|
1367
|
+
function catalog() {
|
|
1368
|
+
return list(state.configData && state.configData.catalog);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
function fillProviderSelect(select, selected) {
|
|
1372
|
+
clear(select);
|
|
1373
|
+
const entries = catalog().filter((entry) => {
|
|
1374
|
+
if (!entry.installed) return false;
|
|
1375
|
+
if (entry.state === 'unavailable') return false;
|
|
1376
|
+
if (entry.authentication === 'failed') return false;
|
|
1377
|
+
return (
|
|
1378
|
+
entry.authenticated !== false || entry.state === 'conditional' || entry.state === 'ready'
|
|
1379
|
+
);
|
|
1380
|
+
});
|
|
1381
|
+
const providers = entries.length
|
|
1382
|
+
? entries.map((entry) => entry.provider)
|
|
1383
|
+
: ['claude', 'codex', 'cursor', 'antigravity'];
|
|
1384
|
+
if (!providers.includes(selected)) providers.push(selected);
|
|
1385
|
+
for (const provider of providers) {
|
|
1386
|
+
const option = el('option', null, provider);
|
|
1387
|
+
option.value = provider;
|
|
1388
|
+
select.appendChild(option);
|
|
1389
|
+
}
|
|
1390
|
+
select.value = selected;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
function fillModelSelect(select, provider, selected) {
|
|
1394
|
+
clear(select);
|
|
1395
|
+
const defaultOption = el('option', null, 'default do provider');
|
|
1396
|
+
defaultOption.value = '';
|
|
1397
|
+
select.appendChild(defaultOption);
|
|
1398
|
+
const entry = catalog().find((candidate) => candidate.provider === provider);
|
|
1399
|
+
for (const model of list(entry && entry.models)) {
|
|
1400
|
+
if (!model.id) continue;
|
|
1401
|
+
const option = el('option', null, model.tier + ' · ' + model.id);
|
|
1402
|
+
option.value = model.id;
|
|
1403
|
+
select.appendChild(option);
|
|
1404
|
+
}
|
|
1405
|
+
if (
|
|
1406
|
+
selected &&
|
|
1407
|
+
!Array.prototype.some.call(select.options, (option) => option.value === selected)
|
|
1408
|
+
) {
|
|
1409
|
+
const current = el('option', null, 'configurado · ' + selected);
|
|
1410
|
+
current.value = selected;
|
|
1411
|
+
select.appendChild(current);
|
|
1412
|
+
}
|
|
1413
|
+
select.value = selected || '';
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
async function saveRoutingConfig(values, button, feedback) {
|
|
1417
|
+
button.disabled = true;
|
|
1418
|
+
feedback.textContent = 'salvando…';
|
|
1419
|
+
try {
|
|
1420
|
+
const response = await fetch('api/config/routing', {
|
|
1421
|
+
method: 'POST',
|
|
1422
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1423
|
+
body: JSON.stringify(values),
|
|
1424
|
+
});
|
|
1425
|
+
const result = await response.json();
|
|
1426
|
+
if (!response.ok) throw new Error(result.error || 'falha ao salvar');
|
|
1427
|
+
state.configData.routing = Object.assign({}, state.configData.routing, values);
|
|
1428
|
+
feedback.textContent = 'salvo para execuções futuras';
|
|
1429
|
+
} catch (error) {
|
|
1430
|
+
feedback.textContent = error.message || 'falha ao salvar';
|
|
1431
|
+
} finally {
|
|
1432
|
+
button.disabled = false;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1185
1436
|
function renderPhases(snapshot) {
|
|
1186
1437
|
clear(els.phases);
|
|
1187
1438
|
if (snapshot.phases.length === 0) {
|
|
@@ -1191,7 +1442,7 @@
|
|
|
1191
1442
|
for (const phase of snapshot.phases) {
|
|
1192
1443
|
const item = el('li');
|
|
1193
1444
|
item.className = 'detail-row';
|
|
1194
|
-
item.appendChild(
|
|
1445
|
+
item.appendChild(statusIcon(phase.status));
|
|
1195
1446
|
const main = el('div', 'item-main');
|
|
1196
1447
|
main.appendChild(el('div', 'item-title', phase.name));
|
|
1197
1448
|
if (phase.error) main.appendChild(el('div', 'item-error', phase.error));
|
|
@@ -1212,19 +1463,17 @@
|
|
|
1212
1463
|
}
|
|
1213
1464
|
}
|
|
1214
1465
|
|
|
1466
|
+
// Uma linha dentro do bloco de estado, não uma lista num cartão próprio: os
|
|
1467
|
+
// passos são poucos e curtos, e o rótulo já vem do HTML (.next-steps-label).
|
|
1215
1468
|
function renderNextSteps(snapshot) {
|
|
1216
1469
|
clear(els.nextSteps);
|
|
1217
1470
|
if (snapshot.nextSteps.length === 0) {
|
|
1218
1471
|
const message =
|
|
1219
1472
|
snapshot.status === 'completed' ? 'Pipeline concluído.' : 'Nenhum passo pendente.';
|
|
1220
|
-
els.nextSteps.appendChild(el('
|
|
1473
|
+
els.nextSteps.appendChild(el('span', 'empty', message));
|
|
1221
1474
|
return;
|
|
1222
1475
|
}
|
|
1223
|
-
|
|
1224
|
-
for (const step of snapshot.nextSteps) {
|
|
1225
|
-
list.appendChild(el('li', null, step));
|
|
1226
|
-
}
|
|
1227
|
-
els.nextSteps.appendChild(list);
|
|
1476
|
+
els.nextSteps.appendChild(el('span', null, snapshot.nextSteps.join(' · ')));
|
|
1228
1477
|
}
|
|
1229
1478
|
|
|
1230
1479
|
function renderStories(snapshot) {
|
|
@@ -1236,11 +1485,12 @@
|
|
|
1236
1485
|
for (const story of snapshot.stories) {
|
|
1237
1486
|
// stage vem com default no schema ('pending'), mas session.json gravado
|
|
1238
1487
|
// antes da issue 38 pode chegar sem ele.
|
|
1239
|
-
const storyStage =
|
|
1488
|
+
const storyStage =
|
|
1489
|
+
story.stage !== null && story.stage !== undefined ? story.stage : 'pending';
|
|
1240
1490
|
const item = el('li', storyStage === 'executing' ? 'story-executing' : null);
|
|
1241
1491
|
item.classList.add('detail-row');
|
|
1242
1492
|
const status = story.passes ? 'completed' : 'pending';
|
|
1243
|
-
item.appendChild(
|
|
1493
|
+
item.appendChild(statusIcon(status, story.passes ? '✓' : '○'));
|
|
1244
1494
|
const main = el('div', 'item-main');
|
|
1245
1495
|
const title = el('div', 'item-title');
|
|
1246
1496
|
title.appendChild(el('span', 'story-id', story.id));
|
|
@@ -1249,10 +1499,15 @@
|
|
|
1249
1499
|
|
|
1250
1500
|
// status/dependencies vêm com default no schema (backlog/[]), mas
|
|
1251
1501
|
// session.json gravado antes de #29 pode chegar sem eles.
|
|
1252
|
-
const storyStatus =
|
|
1502
|
+
const storyStatus =
|
|
1503
|
+
story.status !== null && story.status !== undefined ? story.status : 'backlog';
|
|
1253
1504
|
const meta = el('div', 'story-meta');
|
|
1254
1505
|
meta.appendChild(
|
|
1255
|
-
el(
|
|
1506
|
+
el(
|
|
1507
|
+
'span',
|
|
1508
|
+
'badge story-status-' + storyStatus,
|
|
1509
|
+
STORY_STATUS_LABELS[storyStatus] || storyStatus,
|
|
1510
|
+
),
|
|
1256
1511
|
);
|
|
1257
1512
|
const dependencies = story.dependencies || [];
|
|
1258
1513
|
if (dependencies.length > 0) {
|
|
@@ -1294,9 +1549,7 @@
|
|
|
1294
1549
|
card.dataset.storyId = story.id;
|
|
1295
1550
|
|
|
1296
1551
|
const head = el('span', 'kanban-card-head');
|
|
1297
|
-
head.appendChild(
|
|
1298
|
-
el('span', 'item-icon icon-' + (story.passes ? 'completed' : 'pending'), story.passes ? '✓' : '○'),
|
|
1299
|
-
);
|
|
1552
|
+
head.appendChild(statusIcon(story.passes ? 'completed' : 'pending', story.passes ? '✓' : '○'));
|
|
1300
1553
|
head.appendChild(el('span', 'story-id', story.id));
|
|
1301
1554
|
card.appendChild(head);
|
|
1302
1555
|
|
|
@@ -1378,16 +1631,30 @@
|
|
|
1378
1631
|
item.appendChild(title);
|
|
1379
1632
|
const grid = el('dl', 'now-grid execution-grid');
|
|
1380
1633
|
nowRow(grid, 'Harness', execution.agent?.harness || '—');
|
|
1381
|
-
nowRow(
|
|
1634
|
+
nowRow(
|
|
1635
|
+
grid,
|
|
1636
|
+
'Modelo',
|
|
1637
|
+
execution.agent?.model?.resolved || execution.agent?.model?.requested || '—',
|
|
1638
|
+
);
|
|
1382
1639
|
nowRow(grid, 'Início', formatClock(execution.startedAt));
|
|
1383
|
-
nowRow(
|
|
1384
|
-
|
|
1640
|
+
nowRow(
|
|
1641
|
+
grid,
|
|
1642
|
+
'Fim',
|
|
1643
|
+
execution.finishedAt ? formatClock(execution.finishedAt) : 'em andamento',
|
|
1644
|
+
);
|
|
1645
|
+
nowRow(
|
|
1646
|
+
grid,
|
|
1647
|
+
'Duração',
|
|
1648
|
+
metric(execution.durationMs) !== null ? formatDuration(execution.durationMs / 1000) : '—',
|
|
1649
|
+
);
|
|
1385
1650
|
nowRow(grid, 'Tokens', formatUsage(execution.usage || {}) || '—');
|
|
1386
1651
|
nowRow(grid, 'Custo', executionCost(execution));
|
|
1387
|
-
if (execution.correctionCycle)
|
|
1652
|
+
if (execution.correctionCycle)
|
|
1653
|
+
nowRow(grid, 'Correção', 'ciclo ' + execution.correctionCycle);
|
|
1388
1654
|
if (execution.verdict?.status) nowRow(grid, 'Veredito', execution.verdict.status);
|
|
1389
1655
|
item.appendChild(grid);
|
|
1390
|
-
if (execution.failure?.message)
|
|
1656
|
+
if (execution.failure?.message)
|
|
1657
|
+
item.appendChild(el('p', 'item-error', execution.failure.message));
|
|
1391
1658
|
timeline.appendChild(item);
|
|
1392
1659
|
}
|
|
1393
1660
|
body.appendChild(timeline);
|
|
@@ -1427,7 +1694,9 @@
|
|
|
1427
1694
|
if (entries.length === 0) return;
|
|
1428
1695
|
drawerSection('Diagnóstico global persistente', (body) => {
|
|
1429
1696
|
const details = el('details', 'process-output');
|
|
1430
|
-
details.appendChild(
|
|
1697
|
+
details.appendChild(
|
|
1698
|
+
el('summary', null, entries.length + ' registro(s) em ~/.issue-flow/logs'),
|
|
1699
|
+
);
|
|
1431
1700
|
const output = el('pre', 'process-output-body');
|
|
1432
1701
|
output.textContent = entries
|
|
1433
1702
|
.slice(0, 200)
|
|
@@ -1444,7 +1713,8 @@
|
|
|
1444
1713
|
const kind = state.selectedDetail.kind;
|
|
1445
1714
|
const id = state.selectedDetail.id;
|
|
1446
1715
|
const story = kind === 'story' ? getStoryById(snapshot, id) : null;
|
|
1447
|
-
const phase =
|
|
1716
|
+
const phase =
|
|
1717
|
+
kind === 'phase' ? list(snapshot.phases).find((entry) => entry.name === id) : null;
|
|
1448
1718
|
if ((kind === 'story' && story === null) || (kind === 'phase' && !phase)) {
|
|
1449
1719
|
closeDrawer();
|
|
1450
1720
|
return;
|
|
@@ -1457,9 +1727,15 @@
|
|
|
1457
1727
|
const grid = el('dl', 'now-grid drawer-summary-grid');
|
|
1458
1728
|
nowRow(grid, 'Início', phase.startedAt ? formatClock(phase.startedAt) : '—');
|
|
1459
1729
|
nowRow(grid, 'Fim', phase.endedAt ? formatClock(phase.endedAt) : '—');
|
|
1460
|
-
nowRow(
|
|
1730
|
+
nowRow(
|
|
1731
|
+
grid,
|
|
1732
|
+
'Duração',
|
|
1733
|
+
metric(phase.durationSeconds) !== null ? formatDuration(phase.durationSeconds) : '—',
|
|
1734
|
+
);
|
|
1461
1735
|
nowRow(grid, 'Uso total', formatUsage(phase) || '—');
|
|
1462
|
-
const configured = list(snapshot.configuration?.phases).find(
|
|
1736
|
+
const configured = list(snapshot.configuration?.phases).find(
|
|
1737
|
+
(entry) => entry.phase === phase.name,
|
|
1738
|
+
);
|
|
1463
1739
|
if (configured) {
|
|
1464
1740
|
nowRow(grid, 'Harness efetivo', configured.provider.value || '—');
|
|
1465
1741
|
nowRow(grid, 'Origem', configSourceLabel(configured.provider.source));
|
|
@@ -1499,7 +1775,8 @@
|
|
|
1499
1775
|
return;
|
|
1500
1776
|
}
|
|
1501
1777
|
const items = el('ul', 'drawer-list');
|
|
1502
|
-
for (const criterion of story.acceptanceCriteria)
|
|
1778
|
+
for (const criterion of story.acceptanceCriteria)
|
|
1779
|
+
items.appendChild(el('li', null, criterion));
|
|
1503
1780
|
body.appendChild(items);
|
|
1504
1781
|
});
|
|
1505
1782
|
|
|
@@ -1613,9 +1890,7 @@
|
|
|
1613
1890
|
function renderLogs(snapshot) {
|
|
1614
1891
|
clear(els.logs);
|
|
1615
1892
|
const filter = state.logFilter;
|
|
1616
|
-
const entries = snapshot.logs.filter(
|
|
1617
|
-
(entry) => filter === 'all' || entry.level === filter,
|
|
1618
|
-
);
|
|
1893
|
+
const entries = snapshot.logs.filter((entry) => filter === 'all' || entry.level === filter);
|
|
1619
1894
|
if (entries.length === 0) {
|
|
1620
1895
|
els.logs.appendChild(el('p', 'empty', 'Nenhum log para exibir.'));
|
|
1621
1896
|
return;
|
|
@@ -1659,9 +1934,20 @@
|
|
|
1659
1934
|
case 'agent:activity':
|
|
1660
1935
|
return 'Atividade recebida de ' + event.provider;
|
|
1661
1936
|
case 'agent:result':
|
|
1662
|
-
return
|
|
1937
|
+
return (
|
|
1938
|
+
event.provider +
|
|
1939
|
+
(event.success
|
|
1940
|
+
? ' concluiu a tentativa'
|
|
1941
|
+
: ' falhou: ' + (event.failureKind || 'desconhecida'))
|
|
1942
|
+
);
|
|
1663
1943
|
case 'failover':
|
|
1664
|
-
return
|
|
1944
|
+
return (
|
|
1945
|
+
'Failover de ' +
|
|
1946
|
+
event.from +
|
|
1947
|
+
' para ' +
|
|
1948
|
+
event.to +
|
|
1949
|
+
(event.reason ? ': ' + event.reason : '')
|
|
1950
|
+
);
|
|
1665
1951
|
case 'correction:cycle':
|
|
1666
1952
|
return 'Ciclo de correção ' + event.cycle + '/' + event.maxCycles;
|
|
1667
1953
|
case 'log':
|
|
@@ -1696,7 +1982,7 @@
|
|
|
1696
1982
|
|
|
1697
1983
|
function renderMeta(snapshot) {
|
|
1698
1984
|
const parts = [];
|
|
1699
|
-
if (snapshot.sessionId) parts.push('
|
|
1985
|
+
if (snapshot.sessionId) parts.push('execução ' + snapshot.sessionId);
|
|
1700
1986
|
if (snapshot.updatedAt) parts.push('atualizado ' + formatClock(snapshot.updatedAt));
|
|
1701
1987
|
parts.push('somente leitura');
|
|
1702
1988
|
els.sessionMeta.textContent = parts.join(' · ');
|
|
@@ -1745,6 +2031,8 @@
|
|
|
1745
2031
|
}
|
|
1746
2032
|
|
|
1747
2033
|
async function init() {
|
|
2034
|
+
const tablist = document.querySelector('[role="tablist"]');
|
|
2035
|
+
if (tablist) tablist.addEventListener('keydown', onTabListKeydown);
|
|
1748
2036
|
for (const tab of els.tabs) {
|
|
1749
2037
|
tab.addEventListener('click', () => setActiveTab(tab.id));
|
|
1750
2038
|
}
|
|
@@ -1791,7 +2079,10 @@
|
|
|
1791
2079
|
serverInstanceChanged(healthRes);
|
|
1792
2080
|
const health = await healthRes.json();
|
|
1793
2081
|
renderMonitorVersion(health.version);
|
|
1794
|
-
|
|
2082
|
+
const capabilities = list(health.capabilities);
|
|
2083
|
+
state.configWritable =
|
|
2084
|
+
capabilities.includes('config:agent:write') &&
|
|
2085
|
+
capabilities.includes('config:routing:write');
|
|
1795
2086
|
const suggested = Number(health.refreshSeconds);
|
|
1796
2087
|
if (stored === null && Number.isFinite(suggested) && suggested > 0) {
|
|
1797
2088
|
state.refreshSeconds = suggested;
|