issue-flow 0.17.0 → 0.19.1
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/README.md +34 -100
- package/dist/agent-EWKA7357.js +32 -0
- package/dist/{analyze-6M3KEOX7.js → analyze-Z7ODC27F.js} +42 -41
- package/dist/analyze-Z7ODC27F.js.map +1 -0
- package/dist/{apply-ADB3ZK7W.js → apply-VNSWQS6O.js} +22 -24
- package/dist/apply-VNSWQS6O.js.map +1 -0
- package/dist/artifacts-AMJQJFFV.js +82 -0
- package/dist/artifacts-AMJQJFFV.js.map +1 -0
- package/dist/{bench-FM7I7EIV.js → bench-ZJEIACUK.js} +42 -42
- package/dist/{chunk-RYOUZ6EK.js → chunk-23VHVVKW.js} +3 -3
- package/dist/chunk-2EYLOS5I.js +99 -0
- package/dist/chunk-2EYLOS5I.js.map +1 -0
- package/dist/{chunk-ANI2NTR4.js → chunk-2EZW4X57.js} +28 -25
- package/dist/chunk-2EZW4X57.js.map +1 -0
- package/dist/{chunk-E6KXCV3T.js → chunk-2Q6PG3MT.js} +15 -15
- package/dist/chunk-2Q6PG3MT.js.map +1 -0
- package/dist/{chunk-C5TL7T4V.js → chunk-2YM2VH6R.js} +2 -2
- package/dist/{chunk-LPWPFIIB.js → chunk-2YYP7AE6.js} +14 -16
- package/dist/{chunk-LPWPFIIB.js.map → chunk-2YYP7AE6.js.map} +1 -1
- package/dist/chunk-3JD25HHJ.js +278 -0
- package/dist/chunk-3JD25HHJ.js.map +1 -0
- package/dist/{chunk-ISZ54SVE.js → chunk-46S7APLQ.js} +41 -17
- package/dist/chunk-46S7APLQ.js.map +1 -0
- package/dist/chunk-6AOZCWN2.js +21 -0
- package/dist/chunk-6AOZCWN2.js.map +1 -0
- package/dist/{chunk-QPGEFLMY.js → chunk-7CMVL4X6.js} +7 -7
- package/dist/chunk-7MJGYPHF.js +1571 -0
- package/dist/chunk-7MJGYPHF.js.map +1 -0
- package/dist/chunk-7ZZUSMBJ.js +35 -0
- package/dist/chunk-7ZZUSMBJ.js.map +1 -0
- package/dist/{chunk-DUS5ZNKL.js → chunk-DIMDAYZS.js} +7 -6
- package/dist/{chunk-DGRTJD5R.js → chunk-DXH4TQRB.js} +7 -7
- package/dist/{chunk-CRGIQFUQ.js → chunk-E2H3JV2E.js} +3 -1
- package/dist/chunk-E2H3JV2E.js.map +1 -0
- package/dist/{chunk-YUNBOUQH.js → chunk-ERAG5IQB.js} +8 -8
- package/dist/{chunk-D6ZBGJ3L.js → chunk-F2NFJ27Z.js} +130 -36
- package/dist/chunk-F2NFJ27Z.js.map +1 -0
- package/dist/{chunk-SOM4QA6C.js → chunk-GD7JZMSJ.js} +93 -18
- package/dist/chunk-GD7JZMSJ.js.map +1 -0
- package/dist/{chunk-YII4U7ZD.js → chunk-HEHHQU4G.js} +70 -81
- package/dist/chunk-HEHHQU4G.js.map +1 -0
- package/dist/{chunk-KZ5EY5XX.js → chunk-HHXWAWU6.js} +159 -114
- package/dist/chunk-HHXWAWU6.js.map +1 -0
- package/dist/{chunk-SGTTR4XW.js → chunk-HWWB22HC.js} +152 -100
- package/dist/chunk-HWWB22HC.js.map +1 -0
- package/dist/chunk-IMB6FA2F.js +673 -0
- package/dist/chunk-IMB6FA2F.js.map +1 -0
- package/dist/{chunk-6YQT3SSM.js → chunk-IQYXXFRC.js} +22 -6
- package/dist/chunk-IQYXXFRC.js.map +1 -0
- package/dist/chunk-J6VJ5M2U.js +1201 -0
- package/dist/chunk-J6VJ5M2U.js.map +1 -0
- package/dist/{chunk-B3TSS7BT.js → chunk-KDQRNVBZ.js} +3 -3
- package/dist/{chunk-A4H7P7T7.js → chunk-LRWIP7QS.js} +41 -26
- package/dist/chunk-LRWIP7QS.js.map +1 -0
- package/dist/{chunk-DV7HUC4Z.js → chunk-LYC57OXF.js} +20 -117
- package/dist/chunk-LYC57OXF.js.map +1 -0
- package/dist/{chunk-HAKDLR6N.js → chunk-MAJ6QDRL.js} +65 -78
- package/dist/chunk-MAJ6QDRL.js.map +1 -0
- package/dist/{chunk-HJBF4WD2.js → chunk-MHA7Q3KU.js} +288 -108
- package/dist/chunk-MHA7Q3KU.js.map +1 -0
- package/dist/{chunk-UK4E2FUA.js → chunk-MQTVMB7W.js} +103 -76
- package/dist/chunk-MQTVMB7W.js.map +1 -0
- package/dist/chunk-NP47HRX2.js +136 -0
- package/dist/chunk-NP47HRX2.js.map +1 -0
- package/dist/{chunk-MI5TREN3.js → chunk-NYDAHE3Y.js} +52 -46
- package/dist/chunk-NYDAHE3Y.js.map +1 -0
- package/dist/{chunk-UWDCIFTP.js → chunk-P7ZYOC23.js} +41 -57
- package/dist/chunk-P7ZYOC23.js.map +1 -0
- package/dist/{chunk-3R27MJ5R.js → chunk-PFWHHYEO.js} +3 -3
- package/dist/{chunk-2SUFAE6X.js → chunk-PHPAK2L6.js} +44 -33
- package/dist/{chunk-2SUFAE6X.js.map → chunk-PHPAK2L6.js.map} +1 -1
- package/dist/{chunk-PPZ3K4YM.js → chunk-PRGPSNGA.js} +2 -2
- package/dist/{chunk-32JSYXEK.js → chunk-RGFMQFX7.js} +9 -23
- package/dist/{chunk-32JSYXEK.js.map → chunk-RGFMQFX7.js.map} +1 -1
- package/dist/{chunk-YVPZUN6X.js → chunk-TJVX42KY.js} +335 -38
- package/dist/chunk-TJVX42KY.js.map +1 -0
- package/dist/{chunk-RCZJQUTC.js → chunk-UDRAD7MF.js} +60 -27
- package/dist/chunk-UDRAD7MF.js.map +1 -0
- package/dist/{chunk-HY5HLFDU.js → chunk-UHDXXLYH.js} +148 -135
- package/dist/chunk-UHDXXLYH.js.map +1 -0
- package/dist/{chunk-RNFUVKC3.js → chunk-UPAAZU4W.js} +30 -13
- package/dist/chunk-UPAAZU4W.js.map +1 -0
- package/dist/{chunk-L6HSKTQE.js → chunk-W7LZLJAF.js} +4 -4
- package/dist/{chunk-HLAPOUEM.js → chunk-WB2FVIUS.js} +44 -12
- package/dist/chunk-WB2FVIUS.js.map +1 -0
- package/dist/{chunk-VE3IYNDN.js → chunk-WPHC6JBR.js} +9 -9
- package/dist/chunk-WPHC6JBR.js.map +1 -0
- package/dist/{chunk-KZXQWC7U.js → chunk-XHCP6ZNO.js} +201 -64
- package/dist/chunk-XHCP6ZNO.js.map +1 -0
- package/dist/{chunk-7F7LGMVO.js → chunk-XKR3VD2Z.js} +12 -12
- package/dist/{chunk-7F7LGMVO.js.map → chunk-XKR3VD2Z.js.map} +1 -1
- package/dist/{chunk-ICM463EO.js → chunk-XNK5UN2D.js} +5 -649
- package/dist/chunk-XNK5UN2D.js.map +1 -0
- package/dist/{chunk-Z5O2XBWA.js → chunk-XQLNZW7I.js} +40 -30
- package/dist/chunk-XQLNZW7I.js.map +1 -0
- package/dist/{chunk-QMIQR7EE.js → chunk-ZAYATXJD.js} +32 -316
- package/dist/chunk-ZAYATXJD.js.map +1 -0
- package/dist/cli.js +128 -60
- package/dist/cli.js.map +1 -1
- package/dist/compat-YPDZB34U.js +24 -0
- package/dist/{config-KGZUPAUP.js → config-56B4OHZE.js} +20 -17
- package/dist/{contract-JH2WBIO6.js → contract-HWLFFL3F.js} +2 -2
- package/dist/{conventions-RYN7OXJN.js → conventions-DW2CM5I3.js} +20 -16
- package/dist/{conventions-RYN7OXJN.js.map → conventions-DW2CM5I3.js.map} +1 -1
- package/dist/db-5NU77PJA.js +264 -0
- package/dist/db-5NU77PJA.js.map +1 -0
- package/dist/diagnostics-UCTS2VUF.js +25 -0
- package/dist/execute-R7AVOLNG.js +38 -0
- package/dist/{generate-CX6KNHZS.js → generate-O7GUXLN4.js} +46 -43
- package/dist/generate-O7GUXLN4.js.map +1 -0
- package/dist/{git-A2IP32PQ.js → git-LCG45YXD.js} +2 -3
- package/dist/history-6GSURU3H.js +101 -0
- package/dist/history-6GSURU3H.js.map +1 -0
- package/dist/init-DD6DBRC5.js +28 -0
- package/dist/{operations-CAKIT2BO.js → operations-HU5ZFEBA.js} +78 -52
- package/dist/operations-HU5ZFEBA.js.map +1 -0
- package/dist/{permissions-G4XCAESF.js → permissions-NNNSRC4P.js} +5 -6
- package/dist/plan-XR6B7EM3.js +38 -0
- package/dist/{policy-WX6O2JID.js → policy-RFJOJQL6.js} +17 -15
- package/dist/{policy-WX6O2JID.js.map → policy-RFJOJQL6.js.map} +1 -1
- package/dist/pr-OSCNJCOO.js +42 -0
- package/dist/pr-review-IKIYHFBO.js +33 -0
- package/dist/prd-HFDZJJQR.js +37 -0
- package/dist/{ps-76L6ZAZL.js → ps-AKRHGPSR.js} +26 -15
- package/dist/ps-AKRHGPSR.js.map +1 -0
- package/dist/{recorder-N7U66HCO.js → recorder-T7426CKV.js} +11 -7
- package/dist/registry-DWRKD65A.js +17 -0
- package/dist/repository-TABBRW2C.js +80 -0
- package/dist/resume-GDXKK5XP.js +278 -0
- package/dist/resume-GDXKK5XP.js.map +1 -0
- package/dist/review-JJHMQA2O.js +38 -0
- package/dist/routing-FPNUVE7T.js +35 -0
- package/dist/run-RLCURLLL.js +61 -0
- package/dist/{session-publisher-KAJQRHNZ.js → session-publisher-Q5TNEZPX.js} +3 -3
- package/dist/session-publisher-Q5TNEZPX.js.map +1 -0
- package/dist/{state-manager-UPHO2EA2.js → state-manager-IGNTD4AB.js} +9 -4
- package/dist/state-manager-IGNTD4AB.js.map +1 -0
- package/dist/{usage-U7QNGCPK.js → usage-XW27AL5K.js} +69 -63
- package/dist/usage-XW27AL5K.js.map +1 -0
- package/dist/web-NCDBVZ5Y.js +205 -0
- package/dist/web-NCDBVZ5Y.js.map +1 -0
- package/package.json +20 -7
- package/prompts/analyze.md +16 -19
- package/prompts/execute.md +73 -144
- package/prompts/generate.md +23 -10
- package/prompts/plan.md +32 -60
- package/prompts/pr-review.md +58 -40
- package/prompts/pr.md +50 -16
- package/prompts/prd.md +26 -19
- package/prompts/review.md +41 -18
- package/web/AGENTS.md +115 -7
- package/web/public/app.css +393 -245
- package/web/public/app.js +212 -143
- package/web/public/index.html +106 -78
- package/dist/agent-TB6W7PT7.js +0 -30
- package/dist/analyze-6M3KEOX7.js.map +0 -1
- package/dist/apply-ADB3ZK7W.js.map +0 -1
- package/dist/chunk-6YQT3SSM.js.map +0 -1
- package/dist/chunk-A4H7P7T7.js.map +0 -1
- package/dist/chunk-ANI2NTR4.js.map +0 -1
- package/dist/chunk-CRGIQFUQ.js.map +0 -1
- package/dist/chunk-D6ZBGJ3L.js.map +0 -1
- package/dist/chunk-DV7HUC4Z.js.map +0 -1
- package/dist/chunk-E6KXCV3T.js.map +0 -1
- package/dist/chunk-HAKDLR6N.js.map +0 -1
- package/dist/chunk-HJBF4WD2.js.map +0 -1
- package/dist/chunk-HLAPOUEM.js.map +0 -1
- package/dist/chunk-HY5HLFDU.js.map +0 -1
- package/dist/chunk-ICM463EO.js.map +0 -1
- package/dist/chunk-ISZ54SVE.js.map +0 -1
- package/dist/chunk-KZ5EY5XX.js.map +0 -1
- package/dist/chunk-KZXQWC7U.js.map +0 -1
- package/dist/chunk-MI5TREN3.js.map +0 -1
- package/dist/chunk-QMIQR7EE.js.map +0 -1
- package/dist/chunk-RCZJQUTC.js.map +0 -1
- package/dist/chunk-RNFUVKC3.js.map +0 -1
- package/dist/chunk-SGTTR4XW.js.map +0 -1
- package/dist/chunk-SOM4QA6C.js.map +0 -1
- package/dist/chunk-UK4E2FUA.js.map +0 -1
- package/dist/chunk-UWDCIFTP.js.map +0 -1
- package/dist/chunk-VE3IYNDN.js.map +0 -1
- package/dist/chunk-WCCZJL7W.js +0 -141
- package/dist/chunk-WCCZJL7W.js.map +0 -1
- package/dist/chunk-XEYH6ZZI.js +0 -289
- package/dist/chunk-XEYH6ZZI.js.map +0 -1
- package/dist/chunk-XZUEFTUP.js +0 -243
- package/dist/chunk-XZUEFTUP.js.map +0 -1
- package/dist/chunk-YII4U7ZD.js.map +0 -1
- package/dist/chunk-YVPZUN6X.js.map +0 -1
- package/dist/chunk-Z5O2XBWA.js.map +0 -1
- package/dist/diagnostics-HTCWF7F4.js +0 -23
- package/dist/execute-QL3ZQIAP.js +0 -37
- package/dist/generate-CX6KNHZS.js.map +0 -1
- package/dist/init-PDVQDRS6.js +0 -24
- package/dist/operations-CAKIT2BO.js.map +0 -1
- package/dist/plan-PRORGS3L.js +0 -34
- package/dist/pr-27RMQI5F.js +0 -39
- package/dist/pr-review-VNESMAMT.js +0 -32
- package/dist/prd-ZNC4T5BS.js +0 -33
- package/dist/ps-76L6ZAZL.js.map +0 -1
- package/dist/registry-XLHHYLTR.js +0 -15
- package/dist/resume-VLWIE264.js +0 -244
- package/dist/resume-VLWIE264.js.map +0 -1
- package/dist/review-7CREJDKB.js +0 -35
- package/dist/routing-VCDYHSXA.js +0 -33
- package/dist/run-3MPORLF5.js +0 -56
- package/dist/usage-U7QNGCPK.js.map +0 -1
- package/dist/web-HUNDHCJ4.js +0 -172
- package/dist/web-HUNDHCJ4.js.map +0 -1
- /package/dist/{agent-TB6W7PT7.js.map → agent-EWKA7357.js.map} +0 -0
- /package/dist/{bench-FM7I7EIV.js.map → bench-ZJEIACUK.js.map} +0 -0
- /package/dist/{chunk-RYOUZ6EK.js.map → chunk-23VHVVKW.js.map} +0 -0
- /package/dist/{chunk-C5TL7T4V.js.map → chunk-2YM2VH6R.js.map} +0 -0
- /package/dist/{chunk-QPGEFLMY.js.map → chunk-7CMVL4X6.js.map} +0 -0
- /package/dist/{chunk-DUS5ZNKL.js.map → chunk-DIMDAYZS.js.map} +0 -0
- /package/dist/{chunk-DGRTJD5R.js.map → chunk-DXH4TQRB.js.map} +0 -0
- /package/dist/{chunk-YUNBOUQH.js.map → chunk-ERAG5IQB.js.map} +0 -0
- /package/dist/{chunk-B3TSS7BT.js.map → chunk-KDQRNVBZ.js.map} +0 -0
- /package/dist/{chunk-3R27MJ5R.js.map → chunk-PFWHHYEO.js.map} +0 -0
- /package/dist/{chunk-PPZ3K4YM.js.map → chunk-PRGPSNGA.js.map} +0 -0
- /package/dist/{chunk-L6HSKTQE.js.map → chunk-W7LZLJAF.js.map} +0 -0
- /package/dist/{config-KGZUPAUP.js.map → compat-YPDZB34U.js.map} +0 -0
- /package/dist/{contract-JH2WBIO6.js.map → config-56B4OHZE.js.map} +0 -0
- /package/dist/{diagnostics-HTCWF7F4.js.map → contract-HWLFFL3F.js.map} +0 -0
- /package/dist/{execute-QL3ZQIAP.js.map → diagnostics-UCTS2VUF.js.map} +0 -0
- /package/dist/{git-A2IP32PQ.js.map → execute-R7AVOLNG.js.map} +0 -0
- /package/dist/{init-PDVQDRS6.js.map → git-LCG45YXD.js.map} +0 -0
- /package/dist/{permissions-G4XCAESF.js.map → init-DD6DBRC5.js.map} +0 -0
- /package/dist/{plan-PRORGS3L.js.map → permissions-NNNSRC4P.js.map} +0 -0
- /package/dist/{pr-27RMQI5F.js.map → plan-XR6B7EM3.js.map} +0 -0
- /package/dist/{pr-review-VNESMAMT.js.map → pr-OSCNJCOO.js.map} +0 -0
- /package/dist/{prd-ZNC4T5BS.js.map → pr-review-IKIYHFBO.js.map} +0 -0
- /package/dist/{recorder-N7U66HCO.js.map → prd-HFDZJJQR.js.map} +0 -0
- /package/dist/{registry-XLHHYLTR.js.map → recorder-T7426CKV.js.map} +0 -0
- /package/dist/{review-7CREJDKB.js.map → registry-DWRKD65A.js.map} +0 -0
- /package/dist/{routing-VCDYHSXA.js.map → repository-TABBRW2C.js.map} +0 -0
- /package/dist/{run-3MPORLF5.js.map → review-JJHMQA2O.js.map} +0 -0
- /package/dist/{session-publisher-KAJQRHNZ.js.map → routing-FPNUVE7T.js.map} +0 -0
- /package/dist/{state-manager-UPHO2EA2.js.map → run-RLCURLLL.js.map} +0 -0
package/web/public/app.js
CHANGED
|
@@ -24,6 +24,16 @@
|
|
|
24
24
|
failed: 'falhou',
|
|
25
25
|
};
|
|
26
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
|
+
|
|
27
37
|
const PHASE_ICONS = {
|
|
28
38
|
pending: '○',
|
|
29
39
|
running: '●',
|
|
@@ -69,6 +79,7 @@
|
|
|
69
79
|
dashboardMeta: document.getElementById('dashboard-meta'),
|
|
70
80
|
backToDashboard: document.getElementById('back-to-dashboard'),
|
|
71
81
|
issueLink: document.getElementById('issue-link'),
|
|
82
|
+
issueHeadline: document.getElementById('issue-headline'),
|
|
72
83
|
branchLine: document.getElementById('branch-line'),
|
|
73
84
|
statusBadge: document.getElementById('status-badge'),
|
|
74
85
|
elapsed: document.getElementById('elapsed'),
|
|
@@ -89,6 +100,7 @@
|
|
|
89
100
|
configuration: document.getElementById('configuration'),
|
|
90
101
|
appVersion: document.getElementById('app-version'),
|
|
91
102
|
appVersionDashboard: document.getElementById('app-version-dashboard'),
|
|
103
|
+
dashboardSummary: document.getElementById('dashboard-summary'),
|
|
92
104
|
phases: document.getElementById('phases'),
|
|
93
105
|
nextSteps: document.getElementById('next-steps'),
|
|
94
106
|
stories: document.getElementById('stories'),
|
|
@@ -155,6 +167,21 @@
|
|
|
155
167
|
return node;
|
|
156
168
|
}
|
|
157
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
|
+
|
|
158
185
|
function link(href, text, className) {
|
|
159
186
|
const node = el('a', className, text);
|
|
160
187
|
node.href = href;
|
|
@@ -176,17 +203,17 @@
|
|
|
176
203
|
function formatDuration(totalSeconds) {
|
|
177
204
|
if (totalSeconds === null || totalSeconds === undefined || totalSeconds < 0) return '—';
|
|
178
205
|
const seconds = Math.round(totalSeconds);
|
|
179
|
-
if (seconds < 60) return seconds
|
|
206
|
+
if (seconds < 60) return `${seconds}s`;
|
|
180
207
|
const minutes = Math.floor(seconds / 60);
|
|
181
|
-
if (minutes < 60) return minutes
|
|
208
|
+
if (minutes < 60) return `${minutes}min ${String(seconds % 60).padStart(2, '0')}s`;
|
|
182
209
|
const hours = Math.floor(minutes / 60);
|
|
183
|
-
return hours
|
|
210
|
+
return `${hours}h ${String(minutes % 60).padStart(2, '0')}min`;
|
|
184
211
|
}
|
|
185
212
|
|
|
186
213
|
function formatAgo(iso) {
|
|
187
214
|
const ms = parseIso(iso);
|
|
188
215
|
if (ms === null) return '';
|
|
189
|
-
return
|
|
216
|
+
return `há ${formatDuration((Date.now() - ms) / 1000)}`;
|
|
190
217
|
}
|
|
191
218
|
|
|
192
219
|
function formatClock(iso) {
|
|
@@ -206,8 +233,8 @@
|
|
|
206
233
|
// 1523 → 1.5k, 2400000 → 2.4M (mesma regra do resumo no terminal).
|
|
207
234
|
function compactTokens(value) {
|
|
208
235
|
const abs = Math.abs(value);
|
|
209
|
-
if (abs >= 1000000) return (value / 1000000).toFixed(1)
|
|
210
|
-
if (abs >= 1000) return (value / 1000).toFixed(1)
|
|
236
|
+
if (abs >= 1000000) return `${(value / 1000000).toFixed(1)}M`;
|
|
237
|
+
if (abs >= 1000) return `${(value / 1000).toFixed(1)}k`;
|
|
211
238
|
return String(Math.round(value));
|
|
212
239
|
}
|
|
213
240
|
|
|
@@ -226,14 +253,14 @@
|
|
|
226
253
|
const input = metric(usage.inputTokens);
|
|
227
254
|
const output = metric(usage.outputTokens);
|
|
228
255
|
const io = [];
|
|
229
|
-
if (input !== null) io.push(compactTokens(input)
|
|
230
|
-
if (output !== null) io.push(compactTokens(output)
|
|
256
|
+
if (input !== null) io.push(`${compactTokens(input)} in`);
|
|
257
|
+
if (output !== null) io.push(`${compactTokens(output)} out`);
|
|
231
258
|
if (io.length > 0) segments.push(io.join(' / '));
|
|
232
259
|
|
|
233
260
|
const cacheRead = metric(usage.cacheReadTokens);
|
|
234
261
|
const cacheCreation = metric(usage.cacheCreationTokens);
|
|
235
262
|
if (cacheRead !== null || cacheCreation !== null) {
|
|
236
|
-
segments.push(compactTokens((cacheRead || 0) + (cacheCreation || 0))
|
|
263
|
+
segments.push(`${compactTokens((cacheRead || 0) + (cacheCreation || 0))} cache`);
|
|
237
264
|
}
|
|
238
265
|
|
|
239
266
|
const cost = metric(usage.costUsd);
|
|
@@ -262,7 +289,7 @@
|
|
|
262
289
|
|
|
263
290
|
// URL do repositório derivada da URL da issue (…/issues/N → raiz do repo).
|
|
264
291
|
function repoUrl(snapshot) {
|
|
265
|
-
const url = snapshot.issue
|
|
292
|
+
const url = snapshot.issue?.url;
|
|
266
293
|
if (!url) return null;
|
|
267
294
|
const match = url.match(/^(https?:\/\/\S+?)\/issues\/\d+\/?$/);
|
|
268
295
|
return match ? match[1] : null;
|
|
@@ -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?.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 ------------------------------------------------------------------
|
|
@@ -340,7 +386,7 @@
|
|
|
340
386
|
let raw = null;
|
|
341
387
|
try {
|
|
342
388
|
raw = window.localStorage.getItem(THEME_STORAGE_KEY);
|
|
343
|
-
} catch (
|
|
389
|
+
} catch (_err) {
|
|
344
390
|
// localStorage indisponível (ex.: bloqueado) — segue no modo sistema.
|
|
345
391
|
}
|
|
346
392
|
return raw === 'light' || raw === 'dark' ? raw : 'system';
|
|
@@ -349,7 +395,7 @@
|
|
|
349
395
|
function storeTheme(value) {
|
|
350
396
|
try {
|
|
351
397
|
window.localStorage.setItem(THEME_STORAGE_KEY, value);
|
|
352
|
-
} catch (
|
|
398
|
+
} catch (_err) {
|
|
353
399
|
// Persistência é conveniência; falha é ignorada. Sem gravar, a escolha
|
|
354
400
|
// continua valendo nesta aba — só não sobrevive ao reload.
|
|
355
401
|
}
|
|
@@ -417,7 +463,7 @@
|
|
|
417
463
|
let raw = null;
|
|
418
464
|
try {
|
|
419
465
|
raw = window.localStorage.getItem(STORAGE_KEY);
|
|
420
|
-
} catch (
|
|
466
|
+
} catch (_err) {
|
|
421
467
|
// localStorage indisponível (ex.: bloqueado) — segue com o default.
|
|
422
468
|
}
|
|
423
469
|
if (raw === null) return null;
|
|
@@ -428,7 +474,7 @@
|
|
|
428
474
|
function storeRefresh(value) {
|
|
429
475
|
try {
|
|
430
476
|
window.localStorage.setItem(STORAGE_KEY, String(value));
|
|
431
|
-
} catch (
|
|
477
|
+
} catch (_err) {
|
|
432
478
|
// Persistência é conveniência; falha é ignorada.
|
|
433
479
|
}
|
|
434
480
|
}
|
|
@@ -442,7 +488,7 @@
|
|
|
442
488
|
}
|
|
443
489
|
clear(select);
|
|
444
490
|
for (const value of values) {
|
|
445
|
-
const option = el('option', null, value
|
|
491
|
+
const option = el('option', null, `${value}s`);
|
|
446
492
|
option.value = String(value);
|
|
447
493
|
select.appendChild(option);
|
|
448
494
|
}
|
|
@@ -515,14 +561,14 @@
|
|
|
515
561
|
}
|
|
516
562
|
|
|
517
563
|
function statusUrlFor(session) {
|
|
518
|
-
if (!session
|
|
564
|
+
if (!session?.statusUrl) return 'api/status';
|
|
519
565
|
// statusUrl vem com barra inicial (/api/status?session=…); o fetch relativo
|
|
520
566
|
// ao path do painel precisa da forma sem a barra absoluta do host.
|
|
521
567
|
return session.statusUrl.replace(/^\//, '');
|
|
522
568
|
}
|
|
523
569
|
|
|
524
570
|
function eventsUrlFor(session) {
|
|
525
|
-
if (!session
|
|
571
|
+
if (!session?.eventsUrl) return null;
|
|
526
572
|
return session.eventsUrl.replace(/^\//, '');
|
|
527
573
|
}
|
|
528
574
|
|
|
@@ -540,7 +586,7 @@
|
|
|
540
586
|
// então é ela que explica o que está na tela. As duas aparecem juntas no card
|
|
541
587
|
// de configuração quando divergem.
|
|
542
588
|
function renderMonitorVersion(version) {
|
|
543
|
-
const label = typeof version === 'string' && version !== '' ?
|
|
589
|
+
const label = typeof version === 'string' && version !== '' ? `v${version}` : null;
|
|
544
590
|
state.monitorVersion = label === null ? null : version;
|
|
545
591
|
for (const node of [els.appVersion, els.appVersionDashboard]) {
|
|
546
592
|
if (!node) continue;
|
|
@@ -597,7 +643,7 @@
|
|
|
597
643
|
try {
|
|
598
644
|
const sessionsRes = await fetch('api/sessions', { cache: 'no-store' });
|
|
599
645
|
if (serverInstanceChanged(sessionsRes)) return;
|
|
600
|
-
if (!sessionsRes.ok) throw new Error(
|
|
646
|
+
if (!sessionsRes.ok) throw new Error(`HTTP ${sessionsRes.status}`);
|
|
601
647
|
const sessions = await sessionsRes.json();
|
|
602
648
|
if (!Array.isArray(sessions)) throw new Error('sessions payload invalid');
|
|
603
649
|
|
|
@@ -655,12 +701,12 @@
|
|
|
655
701
|
return;
|
|
656
702
|
}
|
|
657
703
|
if (res.status !== 304) {
|
|
658
|
-
if (!res.ok) throw new Error(
|
|
704
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
659
705
|
state.etag = res.headers.get('ETag');
|
|
660
706
|
state.snapshot = await res.json();
|
|
661
707
|
render();
|
|
662
708
|
}
|
|
663
|
-
const configUrl =
|
|
709
|
+
const configUrl = `api/config?session=${encodeURIComponent(state.detailSessionId || '')}`;
|
|
664
710
|
const configRes = await fetch(configUrl, { cache: 'no-store' });
|
|
665
711
|
if (configRes.ok) {
|
|
666
712
|
state.configData = await configRes.json();
|
|
@@ -668,11 +714,10 @@
|
|
|
668
714
|
}
|
|
669
715
|
if (state.eventsUrl) {
|
|
670
716
|
const eventsRes = await fetch(state.eventsUrl, { cache: 'no-store' });
|
|
671
|
-
if (!eventsRes.ok) throw new Error(
|
|
717
|
+
if (!eventsRes.ok) throw new Error(`HTTP ${eventsRes.status}`);
|
|
672
718
|
const entries = await eventsRes.json();
|
|
673
719
|
state.events = Array.isArray(entries) ? entries : [];
|
|
674
|
-
const diagnosticUrl =
|
|
675
|
-
'api/diagnostics?session=' + encodeURIComponent(state.detailSessionId || '');
|
|
720
|
+
const diagnosticUrl = `api/diagnostics?session=${encodeURIComponent(state.detailSessionId || '')}`;
|
|
676
721
|
const diagnosticsRes = await fetch(diagnosticUrl, { cache: 'no-store' });
|
|
677
722
|
if (diagnosticsRes.ok) {
|
|
678
723
|
const diagnostics = await diagnosticsRes.json();
|
|
@@ -684,7 +729,7 @@
|
|
|
684
729
|
setViewMode('detail');
|
|
685
730
|
state.failures = 0;
|
|
686
731
|
els.banner.hidden = true;
|
|
687
|
-
} catch (
|
|
732
|
+
} catch (_err) {
|
|
688
733
|
state.failures += 1;
|
|
689
734
|
els.banner.hidden = false;
|
|
690
735
|
} finally {
|
|
@@ -701,6 +746,9 @@
|
|
|
701
746
|
function renderEmptyDetail() {
|
|
702
747
|
if (state.snapshot) return;
|
|
703
748
|
document.title = 'issue-flow';
|
|
749
|
+
els.issueLink.hidden = true;
|
|
750
|
+
els.issueHeadline.textContent = 'Nenhuma execução ativa';
|
|
751
|
+
els.branchLine.textContent = '';
|
|
704
752
|
els.statusBadge.textContent = STATUS_LABELS.idle;
|
|
705
753
|
els.statusBadge.className = 'badge status-idle';
|
|
706
754
|
els.elapsed.textContent = '—';
|
|
@@ -712,13 +760,32 @@
|
|
|
712
760
|
if (!text) return '';
|
|
713
761
|
const normalized = String(text).replace(/\s+/g, ' ').trim();
|
|
714
762
|
if (normalized.length <= max) return normalized;
|
|
715
|
-
return normalized.slice(0, max - 1).trimEnd()
|
|
763
|
+
return `${normalized.slice(0, max - 1).trimEnd()}…`;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Linha secundária do header do dashboard: quantas execuções e em que
|
|
767
|
+
// estado. Substitui a legenda estática que só repetia o h1.
|
|
768
|
+
function renderDashboardSummary(sessions) {
|
|
769
|
+
const counts = {};
|
|
770
|
+
for (const session of sessions) {
|
|
771
|
+
const status = session.status || 'idle';
|
|
772
|
+
counts[status] = (counts[status] || 0) + 1;
|
|
773
|
+
}
|
|
774
|
+
const parts = [];
|
|
775
|
+
for (const status of SUMMARY_STATUS_ORDER) {
|
|
776
|
+
const count = counts[status];
|
|
777
|
+
if (!count) continue;
|
|
778
|
+
const label = SUMMARY_STATUS_LABELS[status];
|
|
779
|
+
parts.push(`${count} ${count === 1 ? label[0] : label[1]}`);
|
|
780
|
+
}
|
|
781
|
+
const total = sessions.length + (sessions.length === 1 ? ' execução' : ' execuções');
|
|
782
|
+
els.dashboardSummary.textContent = parts.length > 0 ? `${total} · ${parts.join(' · ')}` : total;
|
|
716
783
|
}
|
|
717
784
|
|
|
718
785
|
function renderDashboard(sessions) {
|
|
719
786
|
const active = document.activeElement;
|
|
720
787
|
const focusedId =
|
|
721
|
-
active
|
|
788
|
+
active?.dataset && els.dashboard.contains(active) && active.dataset.sessionId
|
|
722
789
|
? active.dataset.sessionId
|
|
723
790
|
: null;
|
|
724
791
|
|
|
@@ -727,10 +794,13 @@
|
|
|
727
794
|
if (sessions.length === 0) {
|
|
728
795
|
els.dashboard.appendChild(el('p', 'empty', 'Nenhuma execução ativa.'));
|
|
729
796
|
els.dashboardMeta.textContent = '0 execuções';
|
|
797
|
+
els.dashboardSummary.textContent = 'Nenhuma execução ativa';
|
|
730
798
|
document.title = 'issue-flow';
|
|
731
799
|
return;
|
|
732
800
|
}
|
|
733
801
|
|
|
802
|
+
renderDashboardSummary(sessions);
|
|
803
|
+
|
|
734
804
|
for (const session of sessions) {
|
|
735
805
|
// <button> só aceita phrasing content — mesmos spans do Kanban.
|
|
736
806
|
const card = el('button', 'dashboard-card');
|
|
@@ -744,7 +814,7 @@
|
|
|
744
814
|
'dashboard-project',
|
|
745
815
|
session.repositoryName || 'Projeto desconhecido',
|
|
746
816
|
);
|
|
747
|
-
const badge = el('span',
|
|
817
|
+
const badge = el('span', `badge status-${session.status || 'idle'}`);
|
|
748
818
|
badge.textContent = STATUS_LABELS[session.status] || session.status || '—';
|
|
749
819
|
head.appendChild(project);
|
|
750
820
|
head.appendChild(badge);
|
|
@@ -752,7 +822,7 @@
|
|
|
752
822
|
|
|
753
823
|
const titleRow = el('span', 'dashboard-title-row');
|
|
754
824
|
if (session.issueNumber !== null && session.issueNumber !== undefined) {
|
|
755
|
-
titleRow.appendChild(el('span', 'dashboard-issue',
|
|
825
|
+
titleRow.appendChild(el('span', 'dashboard-issue', `#${session.issueNumber}`));
|
|
756
826
|
}
|
|
757
827
|
titleRow.appendChild(el('span', 'dashboard-title', session.issueTitle || 'Sem título'));
|
|
758
828
|
card.appendChild(titleRow);
|
|
@@ -761,9 +831,9 @@
|
|
|
761
831
|
card.appendChild(el('span', 'dashboard-summary muted', summary || 'Sem descrição'));
|
|
762
832
|
|
|
763
833
|
const meta = el('span', 'dashboard-meta-row');
|
|
764
|
-
meta.appendChild(el('span', null,
|
|
834
|
+
meta.appendChild(el('span', null, `Fase: ${session.currentPhase || '—'}`));
|
|
765
835
|
const percent = typeof session.progressPercent === 'number' ? session.progressPercent : 0;
|
|
766
|
-
meta.appendChild(el('span', null, percent
|
|
836
|
+
meta.appendChild(el('span', null, `${percent}%`));
|
|
767
837
|
const elapsed =
|
|
768
838
|
typeof session.elapsedSeconds === 'number'
|
|
769
839
|
? formatDuration(session.elapsedSeconds)
|
|
@@ -775,30 +845,33 @@
|
|
|
775
845
|
// A card that only shows a percentage cannot tell a run that is
|
|
776
846
|
// progressing from one that has been retrying for twenty minutes.
|
|
777
847
|
if (typeof session.retries === 'number' && session.retries > 0) {
|
|
778
|
-
meta.appendChild(el('span', null, session.retries
|
|
848
|
+
meta.appendChild(el('span', null, `${session.retries} retry(s)`));
|
|
779
849
|
}
|
|
780
850
|
if (typeof session.correctionCycle === 'number' && session.correctionCycle > 0) {
|
|
781
|
-
meta.appendChild(el('span', null,
|
|
851
|
+
meta.appendChild(el('span', null, `correção ${session.correctionCycle}`));
|
|
782
852
|
}
|
|
783
853
|
if (session.updatedAt) {
|
|
784
854
|
meta.appendChild(
|
|
785
|
-
el('span', null,
|
|
855
|
+
el('span', null, `atividade ${formatAgo(session.lastActivityAt || session.updatedAt)}`),
|
|
786
856
|
);
|
|
787
857
|
}
|
|
788
|
-
if (session.provider) meta.appendChild(el('span', null,
|
|
858
|
+
if (session.provider) meta.appendChild(el('span', null, `provider ${session.provider}`));
|
|
789
859
|
if (typeof session.attempt === 'number' && session.attempt > 0) {
|
|
790
|
-
meta.appendChild(el('span', null,
|
|
860
|
+
meta.appendChild(el('span', null, `tentativa ${session.attempt}`));
|
|
791
861
|
}
|
|
792
862
|
card.appendChild(meta);
|
|
793
863
|
|
|
794
864
|
const progress = el('span', 'dashboard-progress');
|
|
795
865
|
const bar = el('span', 'dashboard-progress-bar');
|
|
796
|
-
bar.style.width = Math.max(0, Math.min(100, percent))
|
|
866
|
+
bar.style.width = `${Math.max(0, Math.min(100, percent))}%`;
|
|
797
867
|
progress.appendChild(bar);
|
|
798
868
|
card.appendChild(progress);
|
|
799
869
|
|
|
800
870
|
if (session.status === 'running') {
|
|
801
|
-
|
|
871
|
+
const live = el('span', 'live-label');
|
|
872
|
+
live.appendChild(liveDot());
|
|
873
|
+
live.appendChild(document.createTextNode('ao vivo'));
|
|
874
|
+
card.appendChild(live);
|
|
802
875
|
}
|
|
803
876
|
|
|
804
877
|
card.addEventListener('click', () => {
|
|
@@ -810,11 +883,11 @@
|
|
|
810
883
|
|
|
811
884
|
els.dashboardMeta.textContent =
|
|
812
885
|
sessions.length + (sessions.length === 1 ? ' execução' : ' execuções');
|
|
813
|
-
document.title = sessions.length
|
|
886
|
+
document.title = `${sessions.length} execuções · issue-flow`;
|
|
814
887
|
|
|
815
888
|
if (focusedId) {
|
|
816
889
|
const card = els.dashboard.querySelector(
|
|
817
|
-
|
|
890
|
+
`[data-session-id="${focusedId.replace(/"/g, '')}"]`,
|
|
818
891
|
);
|
|
819
892
|
if (card) card.focus();
|
|
820
893
|
}
|
|
@@ -848,24 +921,31 @@
|
|
|
848
921
|
}
|
|
849
922
|
|
|
850
923
|
function renderTitle(snapshot) {
|
|
851
|
-
const issue = snapshot.issue.number !== null ?
|
|
924
|
+
const issue = snapshot.issue.number !== null ? `#${snapshot.issue.number}` : '';
|
|
852
925
|
let prefix = '';
|
|
853
|
-
if (snapshot.status === 'running') prefix = snapshot.progress.percent
|
|
926
|
+
if (snapshot.status === 'running') prefix = `${snapshot.progress.percent}% · `;
|
|
854
927
|
else if (snapshot.status === 'completed') prefix = '✓ ';
|
|
855
928
|
else if (snapshot.status === 'failed') prefix = '✗ ';
|
|
856
|
-
document.title =
|
|
929
|
+
document.title = `${prefix + issue} · issue-flow`.replace(/^ · /, '');
|
|
857
930
|
}
|
|
858
931
|
|
|
932
|
+
// O h1 é a execução — número, título, e ao lado status, tempo decorrido e
|
|
933
|
+
// branch. A marca do produto vive só no <title> do documento.
|
|
859
934
|
function renderHeader(snapshot) {
|
|
860
|
-
|
|
935
|
+
const hasIssue = snapshot.issue.number !== null && snapshot.issue.number !== undefined;
|
|
936
|
+
if (hasIssue) {
|
|
861
937
|
els.issueLink.hidden = false;
|
|
862
|
-
els.issueLink.textContent =
|
|
938
|
+
els.issueLink.textContent = `#${snapshot.issue.number}`;
|
|
863
939
|
if (snapshot.issue.url) els.issueLink.href = snapshot.issue.url;
|
|
864
940
|
else els.issueLink.removeAttribute('href');
|
|
865
941
|
} else {
|
|
866
942
|
els.issueLink.hidden = true;
|
|
867
943
|
}
|
|
868
944
|
|
|
945
|
+
// session.json antigo pode não ter título; sem issue vinculada, nem número.
|
|
946
|
+
els.issueHeadline.textContent =
|
|
947
|
+
snapshot.issue.title || (hasIssue ? 'Sem título' : 'Execução sem issue vinculada');
|
|
948
|
+
|
|
869
949
|
const branch = snapshot.git.branch;
|
|
870
950
|
const base = snapshot.git.baseBranch;
|
|
871
951
|
const branchMode =
|
|
@@ -875,11 +955,11 @@
|
|
|
875
955
|
? 'criada pelo Issue Flow'
|
|
876
956
|
: 'origem da branch não informada';
|
|
877
957
|
els.branchLine.textContent = branch
|
|
878
|
-
?
|
|
958
|
+
? `${base ? `${branch} ← ${base}` : branch} · ${branchMode}`
|
|
879
959
|
: '';
|
|
880
960
|
|
|
881
961
|
els.statusBadge.textContent = STATUS_LABELS[snapshot.status] || snapshot.status;
|
|
882
|
-
els.statusBadge.className =
|
|
962
|
+
els.statusBadge.className = `badge status-${snapshot.status}`;
|
|
883
963
|
}
|
|
884
964
|
|
|
885
965
|
function renderAlerts(snapshot) {
|
|
@@ -892,8 +972,8 @@
|
|
|
892
972
|
if (!any) return;
|
|
893
973
|
|
|
894
974
|
const counts = [];
|
|
895
|
-
if (errors.length > 0) counts.push(errors.length
|
|
896
|
-
if (warnings.length > 0) counts.push(warnings.length
|
|
975
|
+
if (errors.length > 0) counts.push(`${errors.length} erro(s)`);
|
|
976
|
+
if (warnings.length > 0) counts.push(`${warnings.length} aviso(s)`);
|
|
897
977
|
if (counts.length > 0) {
|
|
898
978
|
els.alertsBody.appendChild(el('p', 'alert-count', counts.join(' · ')));
|
|
899
979
|
}
|
|
@@ -906,13 +986,13 @@
|
|
|
906
986
|
}
|
|
907
987
|
for (const log of errors.slice(-ALERT_PREVIEW)) {
|
|
908
988
|
const entry = el('div', 'alert-entry level-error');
|
|
909
|
-
entry.appendChild(el('span', 'mono', formatClock(log.at)
|
|
989
|
+
entry.appendChild(el('span', 'mono', `${formatClock(log.at)} `));
|
|
910
990
|
entry.appendChild(document.createTextNode(log.message));
|
|
911
991
|
els.alertsBody.appendChild(entry);
|
|
912
992
|
}
|
|
913
993
|
for (const log of warnings.slice(-ALERT_PREVIEW)) {
|
|
914
994
|
const entry = el('div', 'alert-entry level-warn');
|
|
915
|
-
entry.appendChild(el('span', 'mono', formatClock(log.at)
|
|
995
|
+
entry.appendChild(el('span', 'mono', `${formatClock(log.at)} `));
|
|
916
996
|
entry.appendChild(document.createTextNode(log.message));
|
|
917
997
|
els.alertsBody.appendChild(entry);
|
|
918
998
|
}
|
|
@@ -925,23 +1005,13 @@
|
|
|
925
1005
|
clear(els.issueSummary);
|
|
926
1006
|
const issue = snapshot.issue || {};
|
|
927
1007
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
el(
|
|
931
|
-
'span',
|
|
932
|
-
'mono',
|
|
933
|
-
issue.number !== null && issue.number !== undefined ? '#' + issue.number : '—',
|
|
934
|
-
),
|
|
935
|
-
);
|
|
936
|
-
heading.appendChild(document.createTextNode(' ' + (issue.title || 'Sem título')));
|
|
937
|
-
els.issueSummary.appendChild(heading);
|
|
938
|
-
|
|
1008
|
+
// Número e título saem no h1 do header (renderHeader); aqui fica só o que
|
|
1009
|
+
// o header não carrega — estado, labels e descrição.
|
|
939
1010
|
const meta = el('div', 'issue-summary-meta');
|
|
940
1011
|
const state = issue.state || null;
|
|
941
1012
|
const stateClass =
|
|
942
1013
|
state === 'open' ? 'state-open' : state === 'closed' ? 'state-closed' : 'state-unknown';
|
|
943
|
-
meta.appendChild(el('span',
|
|
944
|
-
meta.appendChild(el('span', 'muted', 'Prioridade: Não definida'));
|
|
1014
|
+
meta.appendChild(el('span', `badge ${stateClass}`, state || 'estado desconhecido'));
|
|
945
1015
|
els.issueSummary.appendChild(meta);
|
|
946
1016
|
|
|
947
1017
|
const labels = issue.labels || [];
|
|
@@ -983,7 +1053,7 @@
|
|
|
983
1053
|
function renderProgress(snapshot) {
|
|
984
1054
|
const progress = snapshot.progress;
|
|
985
1055
|
els.progressBar.value = progress.percent;
|
|
986
|
-
els.progressPercent.textContent = progress.percent
|
|
1056
|
+
els.progressPercent.textContent = `${progress.percent}%`;
|
|
987
1057
|
const counters =
|
|
988
1058
|
'Fases ' +
|
|
989
1059
|
progress.phasesCompleted +
|
|
@@ -994,7 +1064,7 @@
|
|
|
994
1064
|
'/' +
|
|
995
1065
|
progress.storiesTotal;
|
|
996
1066
|
const totals = formatTotals(snapshot.metrics);
|
|
997
|
-
els.progressCounters.textContent = totals ? counters
|
|
1067
|
+
els.progressCounters.textContent = totals ? `${counters} · ${totals}` : counters;
|
|
998
1068
|
}
|
|
999
1069
|
|
|
1000
1070
|
function nowRow(grid, label, value) {
|
|
@@ -1008,16 +1078,16 @@
|
|
|
1008
1078
|
const messages = {
|
|
1009
1079
|
idle: 'Nenhuma execução em andamento.',
|
|
1010
1080
|
completed: 'Execução concluída.',
|
|
1011
|
-
failed: 'Execução falhou
|
|
1081
|
+
failed: 'Execução falhou. Veja os erros acima.',
|
|
1012
1082
|
};
|
|
1013
1083
|
els.now.appendChild(el('p', 'empty', messages[snapshot.status] || '—'));
|
|
1014
1084
|
return;
|
|
1015
1085
|
}
|
|
1016
1086
|
|
|
1017
1087
|
const grid = el('dl', 'now-grid');
|
|
1018
|
-
const phase = el('dd');
|
|
1088
|
+
const phase = el('dd', 'now-phase');
|
|
1019
1089
|
if (snapshot.currentPhase) {
|
|
1020
|
-
phase.appendChild(
|
|
1090
|
+
phase.appendChild(liveDot());
|
|
1021
1091
|
phase.appendChild(document.createTextNode(snapshot.currentPhase));
|
|
1022
1092
|
} else {
|
|
1023
1093
|
phase.textContent = '—';
|
|
@@ -1027,7 +1097,7 @@
|
|
|
1027
1097
|
|
|
1028
1098
|
const activity = snapshot.currentActivity;
|
|
1029
1099
|
if (activity) {
|
|
1030
|
-
if (activity.story) nowRow(grid, '
|
|
1100
|
+
if (activity.story) nowRow(grid, 'User story', activity.story);
|
|
1031
1101
|
if (activity.tool) nowRow(grid, 'Ferramenta', activity.tool);
|
|
1032
1102
|
if (activity.detail) nowRow(grid, 'Detalhe', activity.detail);
|
|
1033
1103
|
const since = el('dd', null, formatAgo(activity.since));
|
|
@@ -1052,7 +1122,7 @@
|
|
|
1052
1122
|
resilience.cooldownUntil ? formatClock(resilience.cooldownUntil) : '—',
|
|
1053
1123
|
);
|
|
1054
1124
|
const activity = resilience.lastActivityAt
|
|
1055
|
-
? formatClock(resilience.lastActivityAt)
|
|
1125
|
+
? `${formatClock(resilience.lastActivityAt)} (${formatAgo(resilience.lastActivityAt)})`
|
|
1056
1126
|
: '—';
|
|
1057
1127
|
nowRow(grid, 'Última atividade', activity);
|
|
1058
1128
|
els.resilience.appendChild(grid);
|
|
@@ -1078,16 +1148,16 @@
|
|
|
1078
1148
|
// estar em versões diferentes; este é o único lugar onde as duas aparecem
|
|
1079
1149
|
// lado a lado, então a divergência é dita aqui.
|
|
1080
1150
|
const env = snapshot.environment;
|
|
1081
|
-
const runVersion = env
|
|
1151
|
+
const runVersion = env?.cliVersion ? env.cliVersion : null;
|
|
1082
1152
|
const runtime = el('dl', 'now-grid');
|
|
1083
|
-
const runtimeBits = [runVersion ?
|
|
1084
|
-
if (env
|
|
1085
|
-
if (env
|
|
1153
|
+
const runtimeBits = [runVersion ? `v${runVersion}` : 'versão não registrada'];
|
|
1154
|
+
if (env?.node) runtimeBits.push(env.node);
|
|
1155
|
+
if (env?.platform) runtimeBits.push(env.platform);
|
|
1086
1156
|
nowRow(runtime, 'Issue Flow (execução)', runtimeBits.join(' · '));
|
|
1087
1157
|
nowRow(
|
|
1088
1158
|
runtime,
|
|
1089
1159
|
'Monitor (este painel)',
|
|
1090
|
-
state.monitorVersion ?
|
|
1160
|
+
state.monitorVersion ? `v${state.monitorVersion}` : '—',
|
|
1091
1161
|
);
|
|
1092
1162
|
els.configuration.appendChild(runtime);
|
|
1093
1163
|
if (runVersion !== null && state.monitorVersion && runVersion !== state.monitorVersion) {
|
|
@@ -1126,10 +1196,10 @@
|
|
|
1126
1196
|
nowRow(summary, 'Precedência', list(config.precedence).join(' → '));
|
|
1127
1197
|
els.configuration.appendChild(summary);
|
|
1128
1198
|
|
|
1129
|
-
const liveRouting = state.configData
|
|
1199
|
+
const liveRouting = state.configData?.routing;
|
|
1130
1200
|
if (liveRouting) {
|
|
1131
1201
|
const routingSummary = el('dl', 'now-grid config-routing-summary');
|
|
1132
|
-
nowRow(routingSummary, 'Routing', liveRouting.mode
|
|
1202
|
+
nowRow(routingSummary, 'Routing', `${liveRouting.mode} · perfil ${liveRouting.profile}`);
|
|
1133
1203
|
nowRow(
|
|
1134
1204
|
routingSummary,
|
|
1135
1205
|
'Política',
|
|
@@ -1195,10 +1265,10 @@
|
|
|
1195
1265
|
phaseButton.addEventListener('click', () => openDrawer('phase', phase.phase));
|
|
1196
1266
|
row.appendChild(phaseButton);
|
|
1197
1267
|
const provider = el('select');
|
|
1198
|
-
provider.setAttribute('aria-label',
|
|
1268
|
+
provider.setAttribute('aria-label', `Harness para ${phase.phase}`);
|
|
1199
1269
|
fillProviderSelect(provider, phase.provider.value || 'claude');
|
|
1200
1270
|
const model = el('select');
|
|
1201
|
-
model.setAttribute('aria-label',
|
|
1271
|
+
model.setAttribute('aria-label', `Tier e modelo para ${phase.phase}`);
|
|
1202
1272
|
fillModelSelect(model, provider.value, phase.model.value || '');
|
|
1203
1273
|
provider.addEventListener('change', () => fillModelSelect(model, provider.value, ''));
|
|
1204
1274
|
const save = el('button', 'config-save', 'Salvar');
|
|
@@ -1294,12 +1364,19 @@
|
|
|
1294
1364
|
}
|
|
1295
1365
|
|
|
1296
1366
|
function catalog() {
|
|
1297
|
-
return list(state.configData
|
|
1367
|
+
return list(state.configData?.catalog);
|
|
1298
1368
|
}
|
|
1299
1369
|
|
|
1300
1370
|
function fillProviderSelect(select, selected) {
|
|
1301
1371
|
clear(select);
|
|
1302
|
-
const entries = catalog().filter((entry) =>
|
|
1372
|
+
const entries = catalog().filter((entry) => {
|
|
1373
|
+
if (!entry.installed) return false;
|
|
1374
|
+
if (entry.state === 'unavailable') return false;
|
|
1375
|
+
if (entry.authentication === 'failed') return false;
|
|
1376
|
+
return (
|
|
1377
|
+
entry.authenticated !== false || entry.state === 'conditional' || entry.state === 'ready'
|
|
1378
|
+
);
|
|
1379
|
+
});
|
|
1303
1380
|
const providers = entries.length
|
|
1304
1381
|
? entries.map((entry) => entry.provider)
|
|
1305
1382
|
: ['claude', 'codex', 'cursor', 'antigravity'];
|
|
@@ -1318,9 +1395,9 @@
|
|
|
1318
1395
|
defaultOption.value = '';
|
|
1319
1396
|
select.appendChild(defaultOption);
|
|
1320
1397
|
const entry = catalog().find((candidate) => candidate.provider === provider);
|
|
1321
|
-
for (const model of list(entry
|
|
1398
|
+
for (const model of list(entry?.models)) {
|
|
1322
1399
|
if (!model.id) continue;
|
|
1323
|
-
const option = el('option', null, model.tier
|
|
1400
|
+
const option = el('option', null, `${model.tier} · ${model.id}`);
|
|
1324
1401
|
option.value = model.id;
|
|
1325
1402
|
select.appendChild(option);
|
|
1326
1403
|
}
|
|
@@ -1328,7 +1405,7 @@
|
|
|
1328
1405
|
selected &&
|
|
1329
1406
|
!Array.prototype.some.call(select.options, (option) => option.value === selected)
|
|
1330
1407
|
) {
|
|
1331
|
-
const current = el('option', null,
|
|
1408
|
+
const current = el('option', null, `configurado · ${selected}`);
|
|
1332
1409
|
current.value = selected;
|
|
1333
1410
|
select.appendChild(current);
|
|
1334
1411
|
}
|
|
@@ -1364,9 +1441,7 @@
|
|
|
1364
1441
|
for (const phase of snapshot.phases) {
|
|
1365
1442
|
const item = el('li');
|
|
1366
1443
|
item.className = 'detail-row';
|
|
1367
|
-
item.appendChild(
|
|
1368
|
-
el('span', 'item-icon icon-' + phase.status, PHASE_ICONS[phase.status] || '○'),
|
|
1369
|
-
);
|
|
1444
|
+
item.appendChild(statusIcon(phase.status));
|
|
1370
1445
|
const main = el('div', 'item-main');
|
|
1371
1446
|
main.appendChild(el('div', 'item-title', phase.name));
|
|
1372
1447
|
if (phase.error) main.appendChild(el('div', 'item-error', phase.error));
|
|
@@ -1387,19 +1462,17 @@
|
|
|
1387
1462
|
}
|
|
1388
1463
|
}
|
|
1389
1464
|
|
|
1465
|
+
// Uma linha dentro do bloco de estado, não uma lista num cartão próprio: os
|
|
1466
|
+
// passos são poucos e curtos, e o rótulo já vem do HTML (.next-steps-label).
|
|
1390
1467
|
function renderNextSteps(snapshot) {
|
|
1391
1468
|
clear(els.nextSteps);
|
|
1392
1469
|
if (snapshot.nextSteps.length === 0) {
|
|
1393
1470
|
const message =
|
|
1394
1471
|
snapshot.status === 'completed' ? 'Pipeline concluído.' : 'Nenhum passo pendente.';
|
|
1395
|
-
els.nextSteps.appendChild(el('
|
|
1472
|
+
els.nextSteps.appendChild(el('span', 'empty', message));
|
|
1396
1473
|
return;
|
|
1397
1474
|
}
|
|
1398
|
-
|
|
1399
|
-
for (const step of snapshot.nextSteps) {
|
|
1400
|
-
list.appendChild(el('li', null, step));
|
|
1401
|
-
}
|
|
1402
|
-
els.nextSteps.appendChild(list);
|
|
1475
|
+
els.nextSteps.appendChild(el('span', null, snapshot.nextSteps.join(' · ')));
|
|
1403
1476
|
}
|
|
1404
1477
|
|
|
1405
1478
|
function renderStories(snapshot) {
|
|
@@ -1416,7 +1489,7 @@
|
|
|
1416
1489
|
const item = el('li', storyStage === 'executing' ? 'story-executing' : null);
|
|
1417
1490
|
item.classList.add('detail-row');
|
|
1418
1491
|
const status = story.passes ? 'completed' : 'pending';
|
|
1419
|
-
item.appendChild(
|
|
1492
|
+
item.appendChild(statusIcon(status, story.passes ? '✓' : '○'));
|
|
1420
1493
|
const main = el('div', 'item-main');
|
|
1421
1494
|
const title = el('div', 'item-title');
|
|
1422
1495
|
title.appendChild(el('span', 'story-id', story.id));
|
|
@@ -1431,13 +1504,13 @@
|
|
|
1431
1504
|
meta.appendChild(
|
|
1432
1505
|
el(
|
|
1433
1506
|
'span',
|
|
1434
|
-
|
|
1507
|
+
`badge story-status-${storyStatus}`,
|
|
1435
1508
|
STORY_STATUS_LABELS[storyStatus] || storyStatus,
|
|
1436
1509
|
),
|
|
1437
1510
|
);
|
|
1438
1511
|
const dependencies = story.dependencies || [];
|
|
1439
1512
|
if (dependencies.length > 0) {
|
|
1440
|
-
meta.appendChild(el('span', 'muted story-deps',
|
|
1513
|
+
meta.appendChild(el('span', 'muted story-deps', `depende de: ${dependencies.join(', ')}`));
|
|
1441
1514
|
}
|
|
1442
1515
|
main.appendChild(meta);
|
|
1443
1516
|
|
|
@@ -1445,11 +1518,11 @@
|
|
|
1445
1518
|
const duration = metric(story.durationSeconds);
|
|
1446
1519
|
const stageLabel = STORY_STAGE_LABELS[storyStage] || storyStage;
|
|
1447
1520
|
const stageText = story.stageSince
|
|
1448
|
-
? stageLabel
|
|
1521
|
+
? `${stageLabel} ${formatAgo(story.stageSince)}`
|
|
1449
1522
|
: stageLabel;
|
|
1450
1523
|
const side = itemSideText([
|
|
1451
1524
|
stageText,
|
|
1452
|
-
story.completedAt ?
|
|
1525
|
+
story.completedAt ? `concluída ${formatClock(story.completedAt)}` : '',
|
|
1453
1526
|
duration !== null ? formatDuration(duration) : '',
|
|
1454
1527
|
formatUsage(story),
|
|
1455
1528
|
]);
|
|
@@ -1475,20 +1548,14 @@
|
|
|
1475
1548
|
card.dataset.storyId = story.id;
|
|
1476
1549
|
|
|
1477
1550
|
const head = el('span', 'kanban-card-head');
|
|
1478
|
-
head.appendChild(
|
|
1479
|
-
el(
|
|
1480
|
-
'span',
|
|
1481
|
-
'item-icon icon-' + (story.passes ? 'completed' : 'pending'),
|
|
1482
|
-
story.passes ? '✓' : '○',
|
|
1483
|
-
),
|
|
1484
|
-
);
|
|
1551
|
+
head.appendChild(statusIcon(story.passes ? 'completed' : 'pending', story.passes ? '✓' : '○'));
|
|
1485
1552
|
head.appendChild(el('span', 'story-id', story.id));
|
|
1486
1553
|
card.appendChild(head);
|
|
1487
1554
|
|
|
1488
1555
|
card.appendChild(el('span', 'kanban-card-title', story.title));
|
|
1489
1556
|
if (story.description) card.appendChild(el('span', 'kanban-card-desc', story.description));
|
|
1490
1557
|
card.appendChild(
|
|
1491
|
-
el('span',
|
|
1558
|
+
el('span', `badge story-status-${story.status}`, STORY_STATUS_LABELS[story.status]),
|
|
1492
1559
|
);
|
|
1493
1560
|
card.addEventListener('click', () => openDrawer('story', story.id));
|
|
1494
1561
|
return card;
|
|
@@ -1533,10 +1600,10 @@
|
|
|
1533
1600
|
if (cost.status === 'reported' || cost.status === 'estimated') {
|
|
1534
1601
|
return formatCost(cost.amount, cost.status === 'estimated');
|
|
1535
1602
|
}
|
|
1536
|
-
return cost.reason ?
|
|
1603
|
+
return cost.reason ? `não informado (${cost.reason})` : 'não informado';
|
|
1537
1604
|
}
|
|
1538
1605
|
|
|
1539
|
-
function renderExecutionHistory(
|
|
1606
|
+
function renderExecutionHistory(_snapshot, executions) {
|
|
1540
1607
|
drawerSection('Tentativas, revisões e correções', (body) => {
|
|
1541
1608
|
if (executions.length === 0) {
|
|
1542
1609
|
body.appendChild(el('p', 'empty', 'Nenhuma invocação associada.'));
|
|
@@ -1549,7 +1616,7 @@
|
|
|
1549
1616
|
title.appendChild(
|
|
1550
1617
|
el(
|
|
1551
1618
|
'span',
|
|
1552
|
-
|
|
1619
|
+
`badge status-${execution.status === 'completed' ? 'completed' : execution.status}`,
|
|
1553
1620
|
execution.status,
|
|
1554
1621
|
),
|
|
1555
1622
|
);
|
|
@@ -1557,7 +1624,7 @@
|
|
|
1557
1624
|
el(
|
|
1558
1625
|
'strong',
|
|
1559
1626
|
null,
|
|
1560
|
-
execution.purpose
|
|
1627
|
+
`${execution.purpose} · tentativa ${execution.attempt} · ${execution.trigger}`,
|
|
1561
1628
|
),
|
|
1562
1629
|
);
|
|
1563
1630
|
item.appendChild(title);
|
|
@@ -1582,7 +1649,7 @@
|
|
|
1582
1649
|
nowRow(grid, 'Tokens', formatUsage(execution.usage || {}) || '—');
|
|
1583
1650
|
nowRow(grid, 'Custo', executionCost(execution));
|
|
1584
1651
|
if (execution.correctionCycle)
|
|
1585
|
-
nowRow(grid, 'Correção',
|
|
1652
|
+
nowRow(grid, 'Correção', `ciclo ${execution.correctionCycle}`);
|
|
1586
1653
|
if (execution.verdict?.status) nowRow(grid, 'Veredito', execution.verdict.status);
|
|
1587
1654
|
item.appendChild(grid);
|
|
1588
1655
|
if (execution.failure?.message)
|
|
@@ -1600,7 +1667,7 @@
|
|
|
1600
1667
|
);
|
|
1601
1668
|
drawerSection('Saída do processo', (body) => {
|
|
1602
1669
|
const details = el('details', 'process-output');
|
|
1603
|
-
const summary = el('summary', null, logs.length
|
|
1670
|
+
const summary = el('summary', null, `${logs.length} linha(s) sanitizada(s)`);
|
|
1604
1671
|
details.appendChild(summary);
|
|
1605
1672
|
if (logs.length === 0) {
|
|
1606
1673
|
details.appendChild(el('p', 'empty', 'Nenhuma saída capturada.'));
|
|
@@ -1608,7 +1675,7 @@
|
|
|
1608
1675
|
const output = el('pre', 'process-output-body');
|
|
1609
1676
|
output.textContent = logs
|
|
1610
1677
|
.slice(-200)
|
|
1611
|
-
.map((entry) => formatClock(entry.at)
|
|
1678
|
+
.map((entry) => `${formatClock(entry.at)} ${entry.message}`)
|
|
1612
1679
|
.join('\n');
|
|
1613
1680
|
details.appendChild(output);
|
|
1614
1681
|
}
|
|
@@ -1627,12 +1694,12 @@
|
|
|
1627
1694
|
drawerSection('Diagnóstico global persistente', (body) => {
|
|
1628
1695
|
const details = el('details', 'process-output');
|
|
1629
1696
|
details.appendChild(
|
|
1630
|
-
el('summary', null, entries.length
|
|
1697
|
+
el('summary', null, `${entries.length} registro(s) em ~/.issue-flow/logs`),
|
|
1631
1698
|
);
|
|
1632
1699
|
const output = el('pre', 'process-output-body');
|
|
1633
1700
|
output.textContent = entries
|
|
1634
1701
|
.slice(0, 200)
|
|
1635
|
-
.map((entry) => formatClock(entry.timestamp)
|
|
1702
|
+
.map((entry) => `${formatClock(entry.timestamp)} ${entry.level} ${entry.message}`)
|
|
1636
1703
|
.join('\n');
|
|
1637
1704
|
details.appendChild(output);
|
|
1638
1705
|
body.appendChild(details);
|
|
@@ -1654,8 +1721,8 @@
|
|
|
1654
1721
|
|
|
1655
1722
|
clear(els.drawerBody);
|
|
1656
1723
|
if (kind === 'phase') {
|
|
1657
|
-
els.drawerTitle.textContent =
|
|
1658
|
-
els.drawerBody.appendChild(el('span',
|
|
1724
|
+
els.drawerTitle.textContent = `Fase · ${phase.name}`;
|
|
1725
|
+
els.drawerBody.appendChild(el('span', `badge status-${phase.status}`, phase.status));
|
|
1659
1726
|
const grid = el('dl', 'now-grid drawer-summary-grid');
|
|
1660
1727
|
nowRow(grid, 'Início', phase.startedAt ? formatClock(phase.startedAt) : '—');
|
|
1661
1728
|
nowRow(grid, 'Fim', phase.endedAt ? formatClock(phase.endedAt) : '—');
|
|
@@ -1682,13 +1749,13 @@
|
|
|
1682
1749
|
return;
|
|
1683
1750
|
}
|
|
1684
1751
|
|
|
1685
|
-
els.drawerTitle.textContent = story.id
|
|
1752
|
+
els.drawerTitle.textContent = `${story.id} · ${story.title}`;
|
|
1686
1753
|
els.drawerBody.appendChild(
|
|
1687
|
-
el('span',
|
|
1754
|
+
el('span', `badge story-status-${story.status}`, STORY_STATUS_LABELS[story.status]),
|
|
1688
1755
|
);
|
|
1689
1756
|
|
|
1690
1757
|
const timing = itemSideText([
|
|
1691
|
-
story.completedAt ?
|
|
1758
|
+
story.completedAt ? `concluída ${formatClock(story.completedAt)}` : '',
|
|
1692
1759
|
metric(story.durationSeconds) !== null ? formatDuration(story.durationSeconds) : '',
|
|
1693
1760
|
]);
|
|
1694
1761
|
if (timing) els.drawerBody.appendChild(el('p', 'muted drawer-timing', timing));
|
|
@@ -1736,7 +1803,7 @@
|
|
|
1736
1803
|
item.appendChild(
|
|
1737
1804
|
document.createTextNode(
|
|
1738
1805
|
(STORY_STAGE_LABELS[entry.stage] || entry.stage || '') +
|
|
1739
|
-
(entry.detail ?
|
|
1806
|
+
(entry.detail ? ` · ${entry.detail}` : ''),
|
|
1740
1807
|
),
|
|
1741
1808
|
);
|
|
1742
1809
|
entries.appendChild(item);
|
|
@@ -1777,7 +1844,7 @@
|
|
|
1777
1844
|
// referência guardada na abertura.
|
|
1778
1845
|
const card =
|
|
1779
1846
|
selected && selected.kind === 'story'
|
|
1780
|
-
? els.kanban.querySelector(
|
|
1847
|
+
? els.kanban.querySelector(`[data-story-id="${selected.id}"]`)
|
|
1781
1848
|
: null;
|
|
1782
1849
|
if (card) card.focus();
|
|
1783
1850
|
}
|
|
@@ -1792,7 +1859,7 @@
|
|
|
1792
1859
|
for (const commit of snapshot.git.commits) {
|
|
1793
1860
|
const item = el('li');
|
|
1794
1861
|
const hash = repo
|
|
1795
|
-
? link(repo
|
|
1862
|
+
? link(`${repo}/commit/${commit.hash}`, commit.hash, 'mono commit-hash')
|
|
1796
1863
|
: el('span', 'mono commit-hash', commit.hash);
|
|
1797
1864
|
item.appendChild(hash);
|
|
1798
1865
|
const subject = el('div', 'item-main item-title', commit.subject);
|
|
@@ -1812,7 +1879,7 @@
|
|
|
1812
1879
|
} else {
|
|
1813
1880
|
for (const pr of snapshot.pullRequests) {
|
|
1814
1881
|
const item = el('li');
|
|
1815
|
-
item.appendChild(link(pr.url,
|
|
1882
|
+
item.appendChild(link(pr.url, `#${pr.number}`));
|
|
1816
1883
|
item.appendChild(el('div', 'item-main item-title', pr.title));
|
|
1817
1884
|
els.pullRequests.appendChild(item);
|
|
1818
1885
|
}
|
|
@@ -1829,7 +1896,7 @@
|
|
|
1829
1896
|
}
|
|
1830
1897
|
// Mais recentes primeiro: monitoramento lê o topo, sem gerenciar scroll.
|
|
1831
1898
|
for (const entry of entries.slice().reverse()) {
|
|
1832
|
-
const item = el('li',
|
|
1899
|
+
const item = el('li', `level-${entry.level}`);
|
|
1833
1900
|
item.appendChild(el('span', 'log-time', formatClock(entry.at)));
|
|
1834
1901
|
item.appendChild(el('span', 'log-level', entry.level));
|
|
1835
1902
|
item.appendChild(el('span', 'log-message', entry.message));
|
|
@@ -1850,27 +1917,27 @@
|
|
|
1850
1917
|
case 'session:start':
|
|
1851
1918
|
return 'Sessão iniciada';
|
|
1852
1919
|
case 'session:end':
|
|
1853
|
-
return
|
|
1920
|
+
return `Sessão encerrada: ${event.status}`;
|
|
1854
1921
|
case 'phase:start':
|
|
1855
|
-
return
|
|
1922
|
+
return `Fase iniciada: ${event.phase}`;
|
|
1856
1923
|
case 'phase:end':
|
|
1857
|
-
return
|
|
1924
|
+
return `Fase encerrada: ${event.phase}${event.success ? ' (ok)' : ' (falhou)'}`;
|
|
1858
1925
|
case 'iteration:start':
|
|
1859
|
-
return
|
|
1926
|
+
return `Iteração ${event.iteration} iniciada`;
|
|
1860
1927
|
case 'iteration:end':
|
|
1861
|
-
return
|
|
1928
|
+
return `Iteração ${event.iteration} encerrada`;
|
|
1862
1929
|
case 'retry':
|
|
1863
|
-
return
|
|
1930
|
+
return `Retry ${event.attempt}${event.kind ? `: ${event.kind}` : ''}`;
|
|
1864
1931
|
case 'agent:attempt':
|
|
1865
|
-
return
|
|
1932
|
+
return `Tentativa ${event.attempt} com ${event.provider}`;
|
|
1866
1933
|
case 'agent:activity':
|
|
1867
|
-
return
|
|
1934
|
+
return `Atividade recebida de ${event.provider}`;
|
|
1868
1935
|
case 'agent:result':
|
|
1869
1936
|
return (
|
|
1870
1937
|
event.provider +
|
|
1871
1938
|
(event.success
|
|
1872
1939
|
? ' concluiu a tentativa'
|
|
1873
|
-
:
|
|
1940
|
+
: ` falhou: ${event.failureKind || 'desconhecida'}`)
|
|
1874
1941
|
);
|
|
1875
1942
|
case 'failover':
|
|
1876
1943
|
return (
|
|
@@ -1878,10 +1945,10 @@
|
|
|
1878
1945
|
event.from +
|
|
1879
1946
|
' para ' +
|
|
1880
1947
|
event.to +
|
|
1881
|
-
(event.reason ?
|
|
1948
|
+
(event.reason ? `: ${event.reason}` : '')
|
|
1882
1949
|
);
|
|
1883
1950
|
case 'correction:cycle':
|
|
1884
|
-
return
|
|
1951
|
+
return `Ciclo de correção ${event.cycle}/${event.maxCycles}`;
|
|
1885
1952
|
case 'log':
|
|
1886
1953
|
return event.message;
|
|
1887
1954
|
default:
|
|
@@ -1892,7 +1959,7 @@
|
|
|
1892
1959
|
function renderHistory() {
|
|
1893
1960
|
clear(els.history);
|
|
1894
1961
|
const entries = state.events.filter((entry) => {
|
|
1895
|
-
const type = entry
|
|
1962
|
+
const type = entry?.event?.type;
|
|
1896
1963
|
if (!type) return false;
|
|
1897
1964
|
if (state.historyFilter === 'resilience') return RESILIENCE_EVENTS.has(type);
|
|
1898
1965
|
if (state.historyFilter === 'pipeline') return !RESILIENCE_EVENTS.has(type);
|
|
@@ -1914,8 +1981,8 @@
|
|
|
1914
1981
|
|
|
1915
1982
|
function renderMeta(snapshot) {
|
|
1916
1983
|
const parts = [];
|
|
1917
|
-
if (snapshot.sessionId) parts.push(
|
|
1918
|
-
if (snapshot.updatedAt) parts.push(
|
|
1984
|
+
if (snapshot.sessionId) parts.push(`execução ${snapshot.sessionId}`);
|
|
1985
|
+
if (snapshot.updatedAt) parts.push(`atualizado ${formatClock(snapshot.updatedAt)}`);
|
|
1919
1986
|
parts.push('somente leitura');
|
|
1920
1987
|
els.sessionMeta.textContent = parts.join(' · ');
|
|
1921
1988
|
}
|
|
@@ -1938,7 +2005,7 @@
|
|
|
1938
2005
|
const estimate = snapshot.estimatedRemainingSeconds;
|
|
1939
2006
|
if (snapshot.status === 'running' && estimate !== null) {
|
|
1940
2007
|
els.estimate.hidden = false;
|
|
1941
|
-
els.estimate.textContent =
|
|
2008
|
+
els.estimate.textContent = `~${formatDuration(estimate)} restantes (estimativa)`;
|
|
1942
2009
|
} else {
|
|
1943
2010
|
els.estimate.hidden = true;
|
|
1944
2011
|
}
|
|
@@ -1963,6 +2030,8 @@
|
|
|
1963
2030
|
}
|
|
1964
2031
|
|
|
1965
2032
|
async function init() {
|
|
2033
|
+
const tablist = document.querySelector('[role="tablist"]');
|
|
2034
|
+
if (tablist) tablist.addEventListener('keydown', onTabListKeydown);
|
|
1966
2035
|
for (const tab of els.tabs) {
|
|
1967
2036
|
tab.addEventListener('click', () => setActiveTab(tab.id));
|
|
1968
2037
|
}
|
|
@@ -2017,7 +2086,7 @@
|
|
|
2017
2086
|
if (stored === null && Number.isFinite(suggested) && suggested > 0) {
|
|
2018
2087
|
state.refreshSeconds = suggested;
|
|
2019
2088
|
}
|
|
2020
|
-
} catch (
|
|
2089
|
+
} catch (_err) {
|
|
2021
2090
|
// Sem /api/health segue o default local (5s).
|
|
2022
2091
|
}
|
|
2023
2092
|
if (stored !== null) state.refreshSeconds = stored;
|