issue-flow 0.13.0 → 0.14.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-OX3KPHE4.js +30 -0
- package/dist/{analyze-WS47UYGN.js → analyze-WIND2SEB.js} +21 -18
- package/dist/{analyze-WS47UYGN.js.map → analyze-WIND2SEB.js.map} +1 -1
- package/dist/{apply-OS62UWRN.js → apply-JNOI2LR4.js} +14 -11
- package/dist/{apply-OS62UWRN.js.map → apply-JNOI2LR4.js.map} +1 -1
- package/dist/{bench-SA3BOME5.js → bench-B4VZON7E.js} +30 -26
- package/dist/{bench-SA3BOME5.js.map → bench-B4VZON7E.js.map} +1 -1
- package/dist/{chunk-PIVGJM6K.js → chunk-2IQ6M4ZE.js} +11 -11
- package/dist/{chunk-QCXPNSEL.js → chunk-3BWZO4YI.js} +2 -2
- package/dist/chunk-3WFZETIM.js +163 -0
- package/dist/chunk-3WFZETIM.js.map +1 -0
- package/dist/{chunk-6FDFRNXZ.js → chunk-67YIU2VB.js} +25 -20
- package/dist/chunk-67YIU2VB.js.map +1 -0
- package/dist/{chunk-TM67EHSV.js → chunk-6BENHR2I.js} +19 -19
- package/dist/chunk-6BENHR2I.js.map +1 -0
- package/dist/{chunk-IYV6KNVF.js → chunk-6QYMSNVA.js} +66 -240
- package/dist/chunk-6QYMSNVA.js.map +1 -0
- package/dist/{chunk-IV5SS4JN.js → chunk-76MMPJ53.js} +9 -7
- package/dist/{chunk-IV5SS4JN.js.map → chunk-76MMPJ53.js.map} +1 -1
- package/dist/{chunk-LZ6GFQAG.js → chunk-B72RQZBV.js} +35 -25
- package/dist/chunk-B72RQZBV.js.map +1 -0
- package/dist/{chunk-KQHB7ZT3.js → chunk-BEZ5BA5U.js} +82 -16
- package/dist/chunk-BEZ5BA5U.js.map +1 -0
- package/dist/{chunk-BQDQTIM7.js → chunk-BRCNQXYQ.js} +4 -4
- package/dist/{chunk-ARFVHC2F.js → chunk-CGKLE6WR.js} +115 -23
- package/dist/chunk-CGKLE6WR.js.map +1 -0
- package/dist/{chunk-BHJCGWQX.js → chunk-F6TOYSYY.js} +9 -9
- package/dist/chunk-G5EK2XRN.js +25 -0
- package/dist/chunk-G5EK2XRN.js.map +1 -0
- package/dist/{chunk-TX5WIVYP.js → chunk-HAKDLR6N.js} +6 -1
- package/dist/chunk-HAKDLR6N.js.map +1 -0
- package/dist/{chunk-DIG5XKQQ.js → chunk-J3SOTUVI.js} +2 -2
- package/dist/{chunk-SNTTOQAD.js → chunk-JN6D3MY6.js} +2 -2
- package/dist/{chunk-MIVHK5YI.js → chunk-KMRFRXIT.js} +3 -3
- package/dist/{chunk-5TB6KLC2.js → chunk-KT7JKOXB.js} +8 -8
- package/dist/{chunk-NEZZ6KDR.js → chunk-LBGHI4S2.js} +49 -2
- package/dist/chunk-LBGHI4S2.js.map +1 -0
- package/dist/{chunk-3XLCNVMD.js → chunk-LEW4QM74.js} +2 -2
- package/dist/{chunk-IHIEXUTC.js → chunk-LFEQIZLB.js} +85 -10
- package/dist/chunk-LFEQIZLB.js.map +1 -0
- package/dist/{chunk-TVN2JVOF.js → chunk-OOVG2KBY.js} +3 -3
- package/dist/{chunk-7KD66LIM.js → chunk-OR43364P.js} +3 -3
- package/dist/{chunk-J62QSVP6.js → chunk-PBTA3QQX.js} +8 -7
- package/dist/{chunk-J62QSVP6.js.map → chunk-PBTA3QQX.js.map} +1 -1
- package/dist/{chunk-664Y3NDX.js → chunk-PHW2YV6N.js} +8 -8
- package/dist/{chunk-PV4OX5TC.js → chunk-PZTWU277.js} +11 -11
- package/dist/{chunk-HJGGNLWI.js → chunk-QD4VOILZ.js} +9 -9
- package/dist/{chunk-CNBYCMSU.js → chunk-QJ5UZAH5.js} +27 -18
- package/dist/{chunk-CNBYCMSU.js.map → chunk-QJ5UZAH5.js.map} +1 -1
- package/dist/{chunk-HQAHNIAR.js → chunk-QK7WUO7A.js} +14 -14
- package/dist/{chunk-UJ7XWK7J.js → chunk-QZVKRDJY.js} +16 -14
- package/dist/{chunk-UJ7XWK7J.js.map → chunk-QZVKRDJY.js.map} +1 -1
- package/dist/{chunk-FZDR7CGH.js → chunk-REO3PXXO.js} +3 -3
- package/dist/{chunk-UOCPC2Y2.js → chunk-RVO7L4FX.js} +4 -4
- package/dist/{chunk-QXDNBYQF.js → chunk-SZMHCEYV.js} +21 -10
- package/dist/chunk-SZMHCEYV.js.map +1 -0
- package/dist/chunk-TYRNTSPJ.js +243 -0
- package/dist/chunk-TYRNTSPJ.js.map +1 -0
- package/dist/{agent-BXRIEKVM.js → chunk-UJ3L2MS7.js} +22 -22
- package/dist/chunk-UJ3L2MS7.js.map +1 -0
- package/dist/{chunk-37N2RJW2.js → chunk-V7MO5EDS.js} +3 -3
- package/dist/{chunk-HEF2RMKN.js → chunk-WB2X37MO.js} +18 -16
- package/dist/{chunk-HEF2RMKN.js.map → chunk-WB2X37MO.js.map} +1 -1
- package/dist/{chunk-OPNGPUKJ.js → chunk-X44FKNKP.js} +133 -42
- package/dist/chunk-X44FKNKP.js.map +1 -0
- package/dist/cli.js +43 -40
- package/dist/cli.js.map +1 -1
- package/dist/{config-MEM2IWWN.js → config-HMFMCY2M.js} +12 -9
- package/dist/{conventions-WV4NDXGH.js → conventions-NJF2OPVQ.js} +14 -11
- package/dist/{conventions-WV4NDXGH.js.map → conventions-NJF2OPVQ.js.map} +1 -1
- package/dist/diagnostics-THH42ZMP.js +23 -0
- package/dist/execute-KFNNZZHI.js +35 -0
- package/dist/{generate-MVQI7A45.js → generate-FW5C6NP7.js} +23 -20
- package/dist/{generate-MVQI7A45.js.map → generate-FW5C6NP7.js.map} +1 -1
- package/dist/{git-YRVRUN3C.js → git-W72QQ6GT.js} +4 -2
- package/dist/init-HTN4V67Q.js +24 -0
- package/dist/{operations-LU6JA3BX.js → operations-BCFOBH42.js} +17 -14
- package/dist/{operations-LU6JA3BX.js.map → operations-BCFOBH42.js.map} +1 -1
- package/dist/{permissions-7NMDFU27.js → permissions-4AKNQZHK.js} +4 -4
- package/dist/plan-SIZLGA4V.js +34 -0
- package/dist/{policy-JSYEK5B2.js → policy-DT6Q6ZVM.js} +13 -10
- package/dist/{policy-JSYEK5B2.js.map → policy-DT6Q6ZVM.js.map} +1 -1
- package/dist/pr-OMWCGUOW.js +39 -0
- package/dist/pr-review-N24CZPKR.js +32 -0
- package/dist/prd-5OFTZAHJ.js +33 -0
- package/dist/{ps-TNE44R5V.js → ps-RFORTCUD.js} +11 -8
- package/dist/{ps-TNE44R5V.js.map → ps-RFORTCUD.js.map} +1 -1
- package/dist/{recorder-XGF55WXI.js → recorder-KCCPMKQG.js} +5 -4
- package/dist/registry-GLPXOMFG.js +15 -0
- package/dist/{resume-QMEPMSZW.js → resume-M5RLKP4I.js} +44 -40
- package/dist/{resume-QMEPMSZW.js.map → resume-M5RLKP4I.js.map} +1 -1
- package/dist/review-YM4JAABY.js +35 -0
- package/dist/{routing-2ZUMZPJB.js → routing-NNT5WKGX.js} +13 -10
- package/dist/{routing-2ZUMZPJB.js.map → routing-NNT5WKGX.js.map} +1 -1
- package/dist/run-PZUKHPQK.js +54 -0
- package/dist/run-PZUKHPQK.js.map +1 -0
- package/dist/session-publisher-T7EI3CIP.js +11 -0
- package/dist/session-publisher-T7EI3CIP.js.map +1 -0
- package/dist/{state-manager-X3PT3FGT.js → state-manager-GQH32C44.js} +3 -3
- package/dist/state-manager-GQH32C44.js.map +1 -0
- package/dist/{usage-VCFLDA5Y.js → usage-V2CHE5PY.js} +13 -10
- package/dist/{usage-VCFLDA5Y.js.map → usage-V2CHE5PY.js.map} +1 -1
- package/dist/{web-XVHLX62B.js → web-EA3KGNFN.js} +18 -14
- package/dist/{web-XVHLX62B.js.map → web-EA3KGNFN.js.map} +1 -1
- package/package.json +1 -1
- package/web/AGENTS.md +150 -11
- package/web/public/app.css +343 -110
- package/web/public/app.js +408 -18
- package/web/public/index.html +44 -1
- package/dist/agent-BXRIEKVM.js.map +0 -1
- package/dist/chunk-6FDFRNXZ.js.map +0 -1
- package/dist/chunk-ARFVHC2F.js.map +0 -1
- package/dist/chunk-IHIEXUTC.js.map +0 -1
- package/dist/chunk-IYV6KNVF.js.map +0 -1
- package/dist/chunk-KQHB7ZT3.js.map +0 -1
- package/dist/chunk-LZ6GFQAG.js.map +0 -1
- package/dist/chunk-NEZZ6KDR.js.map +0 -1
- package/dist/chunk-OPNGPUKJ.js.map +0 -1
- package/dist/chunk-QXDNBYQF.js.map +0 -1
- package/dist/chunk-TM67EHSV.js.map +0 -1
- package/dist/chunk-TX5WIVYP.js.map +0 -1
- package/dist/execute-NITIHHCK.js +0 -31
- package/dist/init-OKWEC3EV.js +0 -21
- package/dist/plan-LEJJF3DE.js +0 -31
- package/dist/pr-HPVUZ7S3.js +0 -36
- package/dist/pr-review-R2QZBT3F.js +0 -29
- package/dist/prd-5O6GYCC6.js +0 -30
- package/dist/registry-LGPE53QD.js +0 -15
- package/dist/review-YKWD6SNN.js +0 -32
- package/dist/run-YU4TR62R.js +0 -50
- /package/dist/{config-MEM2IWWN.js.map → agent-OX3KPHE4.js.map} +0 -0
- /package/dist/{chunk-PIVGJM6K.js.map → chunk-2IQ6M4ZE.js.map} +0 -0
- /package/dist/{chunk-QCXPNSEL.js.map → chunk-3BWZO4YI.js.map} +0 -0
- /package/dist/{chunk-BQDQTIM7.js.map → chunk-BRCNQXYQ.js.map} +0 -0
- /package/dist/{chunk-BHJCGWQX.js.map → chunk-F6TOYSYY.js.map} +0 -0
- /package/dist/{chunk-DIG5XKQQ.js.map → chunk-J3SOTUVI.js.map} +0 -0
- /package/dist/{chunk-SNTTOQAD.js.map → chunk-JN6D3MY6.js.map} +0 -0
- /package/dist/{chunk-MIVHK5YI.js.map → chunk-KMRFRXIT.js.map} +0 -0
- /package/dist/{chunk-5TB6KLC2.js.map → chunk-KT7JKOXB.js.map} +0 -0
- /package/dist/{chunk-3XLCNVMD.js.map → chunk-LEW4QM74.js.map} +0 -0
- /package/dist/{chunk-TVN2JVOF.js.map → chunk-OOVG2KBY.js.map} +0 -0
- /package/dist/{chunk-7KD66LIM.js.map → chunk-OR43364P.js.map} +0 -0
- /package/dist/{chunk-664Y3NDX.js.map → chunk-PHW2YV6N.js.map} +0 -0
- /package/dist/{chunk-PV4OX5TC.js.map → chunk-PZTWU277.js.map} +0 -0
- /package/dist/{chunk-HJGGNLWI.js.map → chunk-QD4VOILZ.js.map} +0 -0
- /package/dist/{chunk-HQAHNIAR.js.map → chunk-QK7WUO7A.js.map} +0 -0
- /package/dist/{chunk-FZDR7CGH.js.map → chunk-REO3PXXO.js.map} +0 -0
- /package/dist/{chunk-UOCPC2Y2.js.map → chunk-RVO7L4FX.js.map} +0 -0
- /package/dist/{chunk-37N2RJW2.js.map → chunk-V7MO5EDS.js.map} +0 -0
- /package/dist/{execute-NITIHHCK.js.map → config-HMFMCY2M.js.map} +0 -0
- /package/dist/{git-YRVRUN3C.js.map → diagnostics-THH42ZMP.js.map} +0 -0
- /package/dist/{init-OKWEC3EV.js.map → execute-KFNNZZHI.js.map} +0 -0
- /package/dist/{permissions-7NMDFU27.js.map → git-W72QQ6GT.js.map} +0 -0
- /package/dist/{plan-LEJJF3DE.js.map → init-HTN4V67Q.js.map} +0 -0
- /package/dist/{pr-HPVUZ7S3.js.map → permissions-4AKNQZHK.js.map} +0 -0
- /package/dist/{pr-review-R2QZBT3F.js.map → plan-SIZLGA4V.js.map} +0 -0
- /package/dist/{prd-5O6GYCC6.js.map → pr-OMWCGUOW.js.map} +0 -0
- /package/dist/{recorder-XGF55WXI.js.map → pr-review-N24CZPKR.js.map} +0 -0
- /package/dist/{registry-LGPE53QD.js.map → prd-5OFTZAHJ.js.map} +0 -0
- /package/dist/{review-YKWD6SNN.js.map → recorder-KCCPMKQG.js.map} +0 -0
- /package/dist/{run-YU4TR62R.js.map → registry-GLPXOMFG.js.map} +0 -0
- /package/dist/{state-manager-X3PT3FGT.js.map → review-YM4JAABY.js.map} +0 -0
package/web/public/app.js
CHANGED
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
const REFRESH_OPTIONS = [3, 5, 10, 30];
|
|
10
10
|
const PAUSED = 0;
|
|
11
11
|
const STORAGE_KEY = 'issue-flow:refresh-seconds';
|
|
12
|
+
const THEME_STORAGE_KEY = 'issue-flow:theme';
|
|
13
|
+
// Esta chave também é lida pelo <script> inline do <head>
|
|
14
|
+
// do index.html, que aplica o tema antes do primeiro paint. A duplicação
|
|
15
|
+
// é deliberada: aquele script roda antes deste arquivo existir na página e
|
|
16
|
+
// não pode depender de nenhum símbolo daqui. Mudou o formato do valor,
|
|
17
|
+
// mude nos dois lugares.
|
|
12
18
|
const MAX_BACKOFF_MS = 60000;
|
|
13
19
|
const ALERT_PREVIEW = 3;
|
|
14
20
|
const DESCRIPTION_PREVIEW = 140;
|
|
@@ -71,6 +77,8 @@
|
|
|
71
77
|
estimate: document.getElementById('estimate'),
|
|
72
78
|
refreshSelect: document.getElementById('refresh-select'),
|
|
73
79
|
refreshSelectDashboard: document.getElementById('refresh-select-dashboard'),
|
|
80
|
+
themeSelect: document.getElementById('theme-select'),
|
|
81
|
+
themeSelectDashboard: document.getElementById('theme-select-dashboard'),
|
|
74
82
|
alerts: document.getElementById('alerts'),
|
|
75
83
|
alertsBody: document.getElementById('alerts-body'),
|
|
76
84
|
issueSummary: document.getElementById('issue-summary'),
|
|
@@ -80,6 +88,7 @@
|
|
|
80
88
|
progressCounters: document.getElementById('progress-counters'),
|
|
81
89
|
now: document.getElementById('now'),
|
|
82
90
|
resilience: document.getElementById('resilience'),
|
|
91
|
+
configuration: document.getElementById('configuration'),
|
|
83
92
|
phases: document.getElementById('phases'),
|
|
84
93
|
nextSteps: document.getElementById('next-steps'),
|
|
85
94
|
stories: document.getElementById('stories'),
|
|
@@ -108,11 +117,16 @@
|
|
|
108
117
|
polling: false,
|
|
109
118
|
logFilter: 'all',
|
|
110
119
|
activeTab: 'tab-execution',
|
|
120
|
+
// 'system' | 'light' | 'dark'. Valor inicial em initTheme(), lido da
|
|
121
|
+
// preferência guardada em localStorage por readStoredTheme().
|
|
122
|
+
theme: 'system',
|
|
111
123
|
historyFilter: 'all',
|
|
112
124
|
events: [],
|
|
125
|
+
diagnostics: [],
|
|
113
126
|
// Só o id: o card que abriu o drawer é destruído no próximo render, então
|
|
114
127
|
// guardar o nó levaria a uma referência morta.
|
|
115
|
-
|
|
128
|
+
selectedDetail: null,
|
|
129
|
+
configWritable: false,
|
|
116
130
|
// Multi-sessão (#35): lista de /api/sessions e seleção explícita do usuário.
|
|
117
131
|
// selectedSessionId null = modo automático (1 sessão → detalhe; 2+ → dashboard).
|
|
118
132
|
sessions: [],
|
|
@@ -192,8 +206,9 @@
|
|
|
192
206
|
}
|
|
193
207
|
|
|
194
208
|
// Custos abaixo de um centavo perderiam todo o significado com 2 casas.
|
|
195
|
-
function formatCost(value) {
|
|
196
|
-
|
|
209
|
+
function formatCost(value, approximate) {
|
|
210
|
+
const prefix = approximate === false ? '$' : '~$';
|
|
211
|
+
return prefix + (Math.abs(value) < 0.01 ? value.toFixed(4) : value.toFixed(2));
|
|
197
212
|
}
|
|
198
213
|
|
|
199
214
|
// Ex.: '12.4k in / 3.1k out · 88.0k cache · ~$0.42'. Segmentos sem dado são
|
|
@@ -307,6 +322,88 @@
|
|
|
307
322
|
}
|
|
308
323
|
}
|
|
309
324
|
|
|
325
|
+
// ---- Tema ------------------------------------------------------------------
|
|
326
|
+
// Três estados: 'system' segue o SO (o @media decide), 'light'/'dark' forçam.
|
|
327
|
+
// Quem pinta é o CSS; aqui só se define (ou remove) o data-theme da raiz.
|
|
328
|
+
|
|
329
|
+
// Par ler/gravar tolerante a exceção, na mesma forma de readStoredRefresh()
|
|
330
|
+
// e storeRefresh(): armazenamento bloqueado (janela privada, cookies de
|
|
331
|
+
// terceiros desligados) não pode derrubar o painel.
|
|
332
|
+
function readStoredTheme() {
|
|
333
|
+
let raw = null;
|
|
334
|
+
try {
|
|
335
|
+
raw = window.localStorage.getItem(THEME_STORAGE_KEY);
|
|
336
|
+
} catch (err) {
|
|
337
|
+
// localStorage indisponível (ex.: bloqueado) — segue no modo sistema.
|
|
338
|
+
}
|
|
339
|
+
return raw === 'light' || raw === 'dark' ? raw : 'system';
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function storeTheme(value) {
|
|
343
|
+
try {
|
|
344
|
+
window.localStorage.setItem(THEME_STORAGE_KEY, value);
|
|
345
|
+
} catch (err) {
|
|
346
|
+
// Persistência é conveniência; falha é ignorada. Sem gravar, a escolha
|
|
347
|
+
// continua valendo nesta aba — só não sobrevive ao reload.
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function applyTheme(theme) {
|
|
352
|
+
// 'system' **remove** o atributo em vez de gravar 'system': é a ausência
|
|
353
|
+
// do data-theme que devolve a decisão ao @media (prefers-color-scheme).
|
|
354
|
+
if (theme === 'light' || theme === 'dark') {
|
|
355
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
356
|
+
} else {
|
|
357
|
+
document.documentElement.removeAttribute('data-theme');
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Os dois headers têm o seu seletor; mudar num precisa refletir no outro.
|
|
362
|
+
// As três opções são estáticas no HTML — aqui só se sincroniza o .value.
|
|
363
|
+
function syncThemeSelects() {
|
|
364
|
+
if (els.themeSelect) els.themeSelect.value = state.theme;
|
|
365
|
+
if (els.themeSelectDashboard) els.themeSelectDashboard.value = state.theme;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Preferência do SO. Só se observa no modo 'system': é ali que a troca de
|
|
369
|
+
// claro para escuro no SO precisa chegar ao painel com a página aberta.
|
|
370
|
+
const systemThemeQuery =
|
|
371
|
+
typeof window.matchMedia === 'function'
|
|
372
|
+
? window.matchMedia('(prefers-color-scheme: dark)')
|
|
373
|
+
: null;
|
|
374
|
+
|
|
375
|
+
function onSystemThemeChange() {
|
|
376
|
+
// Sincroniza o lado JS do tema com a preferência nova do SO: a raiz (que
|
|
377
|
+
// no modo sistema segue sem data-theme, para o @media decidir) e o .value
|
|
378
|
+
// dos dois seletores. O repaint das cores é do próprio @media, que o
|
|
379
|
+
// navegador reavalia sozinho — por isso não há nada a pintar aqui.
|
|
380
|
+
applyTheme(state.theme);
|
|
381
|
+
syncThemeSelects();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Anexa no modo 'system' e desanexa nos modos forçados: com tema forçado a
|
|
385
|
+
// preferência do SO não vale, e continuar ouvindo seria ruído.
|
|
386
|
+
function watchSystemTheme(enabled) {
|
|
387
|
+
if (!systemThemeQuery) return;
|
|
388
|
+
if (enabled) systemThemeQuery.addEventListener('change', onSystemThemeChange);
|
|
389
|
+
else systemThemeQuery.removeEventListener('change', onSystemThemeChange);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function setTheme(theme) {
|
|
393
|
+
state.theme = theme === 'light' || theme === 'dark' ? theme : 'system';
|
|
394
|
+
applyTheme(state.theme);
|
|
395
|
+
syncThemeSelects();
|
|
396
|
+
watchSystemTheme(state.theme === 'system');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function initTheme() {
|
|
400
|
+
// O <script> inline do <head> já leu a mesma chave e aplicou o tema antes
|
|
401
|
+
// do primeiro paint; esta é a segunda metade da duplicação deliberada
|
|
402
|
+
// documentada em THEME_STORAGE_KEY — traz a preferência para o state e,
|
|
403
|
+
// com ela, para os dois seletores.
|
|
404
|
+
setTheme(readStoredTheme());
|
|
405
|
+
}
|
|
406
|
+
|
|
310
407
|
// ---- Polling: intervalo configurável, aba oculta e backoff ----------------
|
|
311
408
|
|
|
312
409
|
function readStoredRefresh() {
|
|
@@ -390,6 +487,10 @@
|
|
|
390
487
|
return { mode: 'detail', session: null };
|
|
391
488
|
}
|
|
392
489
|
|
|
490
|
+
if (sessions.length === 1) {
|
|
491
|
+
return { mode: 'detail', session: sessions[0] };
|
|
492
|
+
}
|
|
493
|
+
|
|
393
494
|
return { mode: 'dashboard', session: null };
|
|
394
495
|
}
|
|
395
496
|
|
|
@@ -401,7 +502,7 @@
|
|
|
401
502
|
const showBack =
|
|
402
503
|
mode === 'detail' && state.selectedSessionId !== null && state.sessions.length >= 1;
|
|
403
504
|
els.backToDashboard.hidden = !showBack;
|
|
404
|
-
if (mode === 'dashboard' && state.
|
|
505
|
+
if (mode === 'dashboard' && state.selectedDetail !== null) {
|
|
405
506
|
closeDrawer();
|
|
406
507
|
}
|
|
407
508
|
}
|
|
@@ -423,6 +524,7 @@
|
|
|
423
524
|
state.snapshot = null;
|
|
424
525
|
state.detailSessionId = null;
|
|
425
526
|
state.events = [];
|
|
527
|
+
state.diagnostics = [];
|
|
426
528
|
state.eventsUrl = null;
|
|
427
529
|
}
|
|
428
530
|
|
|
@@ -528,7 +630,15 @@
|
|
|
528
630
|
if (!eventsRes.ok) throw new Error('HTTP ' + eventsRes.status);
|
|
529
631
|
const entries = await eventsRes.json();
|
|
530
632
|
state.events = Array.isArray(entries) ? entries : [];
|
|
633
|
+
const diagnosticUrl =
|
|
634
|
+
'api/diagnostics?session=' + encodeURIComponent(state.detailSessionId || '');
|
|
635
|
+
const diagnosticsRes = await fetch(diagnosticUrl, { cache: 'no-store' });
|
|
636
|
+
if (diagnosticsRes.ok) {
|
|
637
|
+
const diagnostics = await diagnosticsRes.json();
|
|
638
|
+
state.diagnostics = Array.isArray(diagnostics) ? diagnostics : [];
|
|
639
|
+
}
|
|
531
640
|
renderHistory();
|
|
641
|
+
renderDrawer(state.snapshot);
|
|
532
642
|
}
|
|
533
643
|
setViewMode('detail');
|
|
534
644
|
state.failures = 0;
|
|
@@ -689,6 +799,7 @@
|
|
|
689
799
|
renderProgress(snapshot);
|
|
690
800
|
renderNow(snapshot);
|
|
691
801
|
renderResilience(snapshot);
|
|
802
|
+
renderConfiguration(snapshot);
|
|
692
803
|
renderPhases(snapshot);
|
|
693
804
|
renderNextSteps(snapshot);
|
|
694
805
|
renderStories(snapshot);
|
|
@@ -724,7 +835,15 @@
|
|
|
724
835
|
|
|
725
836
|
const branch = snapshot.git.branch;
|
|
726
837
|
const base = snapshot.git.baseBranch;
|
|
727
|
-
|
|
838
|
+
const branchMode =
|
|
839
|
+
snapshot.git.branchCreated === false
|
|
840
|
+
? 'branch atual · não criada pelo Issue Flow'
|
|
841
|
+
: snapshot.git.branchCreated === true
|
|
842
|
+
? 'criada pelo Issue Flow'
|
|
843
|
+
: 'origem da branch não informada';
|
|
844
|
+
els.branchLine.textContent = branch
|
|
845
|
+
? (base ? branch + ' ← ' + base : branch) + ' · ' + branchMode
|
|
846
|
+
: '';
|
|
728
847
|
|
|
729
848
|
els.statusBadge.textContent = STATUS_LABELS[snapshot.status] || snapshot.status;
|
|
730
849
|
els.statusBadge.className = 'badge status-' + snapshot.status;
|
|
@@ -899,6 +1018,105 @@
|
|
|
899
1018
|
els.resilience.appendChild(grid);
|
|
900
1019
|
}
|
|
901
1020
|
|
|
1021
|
+
function configSourceLabel(source) {
|
|
1022
|
+
const labels = {
|
|
1023
|
+
default: 'default do Issue Flow',
|
|
1024
|
+
global: 'configuração global',
|
|
1025
|
+
project: 'configuração do projeto',
|
|
1026
|
+
env: 'variável de ambiente',
|
|
1027
|
+
cli: 'override da execução',
|
|
1028
|
+
fallback: 'fallback',
|
|
1029
|
+
};
|
|
1030
|
+
return labels[source] || source || 'não informado';
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function renderConfiguration(snapshot) {
|
|
1034
|
+
clear(els.configuration);
|
|
1035
|
+
const config = snapshot.configuration;
|
|
1036
|
+
if (!config) {
|
|
1037
|
+
els.configuration.appendChild(el('p', 'empty', 'Configuração não capturada nesta execução.'));
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const summary = el('dl', 'now-grid');
|
|
1042
|
+
nowRow(
|
|
1043
|
+
summary,
|
|
1044
|
+
'Harness padrão',
|
|
1045
|
+
(config.defaultProvider.value || '—') + ' · ' + configSourceLabel(config.defaultProvider.source),
|
|
1046
|
+
);
|
|
1047
|
+
nowRow(
|
|
1048
|
+
summary,
|
|
1049
|
+
'Modelo padrão',
|
|
1050
|
+
(config.defaultModel.value || 'default do provider') +
|
|
1051
|
+
' · ' +
|
|
1052
|
+
configSourceLabel(config.defaultModel.source),
|
|
1053
|
+
);
|
|
1054
|
+
nowRow(summary, 'Fallbacks', list(config.fallbacks).join(' → ') || 'nenhum configurado');
|
|
1055
|
+
nowRow(summary, 'Precedência', list(config.precedence).join(' → '));
|
|
1056
|
+
els.configuration.appendChild(summary);
|
|
1057
|
+
|
|
1058
|
+
const phases = el('div', 'config-phase-grid');
|
|
1059
|
+
for (const phase of list(config.phases)) {
|
|
1060
|
+
const row = el('button', 'config-phase-row');
|
|
1061
|
+
row.type = 'button';
|
|
1062
|
+
row.appendChild(el('span', 'mono', phase.phase));
|
|
1063
|
+
row.appendChild(el('span', null, phase.provider.value || '—'));
|
|
1064
|
+
row.appendChild(el('span', null, phase.model.value || 'default do provider'));
|
|
1065
|
+
row.appendChild(
|
|
1066
|
+
el(
|
|
1067
|
+
'span',
|
|
1068
|
+
'muted',
|
|
1069
|
+
configSourceLabel(phase.provider.source) + ' / ' + configSourceLabel(phase.model.source),
|
|
1070
|
+
),
|
|
1071
|
+
);
|
|
1072
|
+
row.addEventListener('click', () => openDrawer('phase', phase.phase));
|
|
1073
|
+
phases.appendChild(row);
|
|
1074
|
+
}
|
|
1075
|
+
els.configuration.appendChild(phases);
|
|
1076
|
+
|
|
1077
|
+
const form = el('form', 'config-form');
|
|
1078
|
+
const provider = el('select');
|
|
1079
|
+
provider.setAttribute('aria-label', 'Harness padrão para execuções futuras');
|
|
1080
|
+
for (const id of ['claude', 'codex', 'cursor', 'antigravity']) {
|
|
1081
|
+
const option = el('option', null, id);
|
|
1082
|
+
option.value = id;
|
|
1083
|
+
provider.appendChild(option);
|
|
1084
|
+
}
|
|
1085
|
+
provider.value = config.defaultProvider.value || 'claude';
|
|
1086
|
+
const model = el('input');
|
|
1087
|
+
model.type = 'text';
|
|
1088
|
+
model.placeholder = 'modelo (vazio = default)';
|
|
1089
|
+
model.value = config.defaultModel.value || '';
|
|
1090
|
+
const save = el('button', 'config-save', 'Salvar preferência global');
|
|
1091
|
+
save.type = 'submit';
|
|
1092
|
+
const feedback = el('span', 'muted');
|
|
1093
|
+
form.appendChild(provider);
|
|
1094
|
+
form.appendChild(model);
|
|
1095
|
+
form.appendChild(save);
|
|
1096
|
+
form.appendChild(feedback);
|
|
1097
|
+
form.addEventListener('submit', async (event) => {
|
|
1098
|
+
event.preventDefault();
|
|
1099
|
+
save.disabled = true;
|
|
1100
|
+
feedback.textContent = 'salvando…';
|
|
1101
|
+
try {
|
|
1102
|
+
const response = await fetch('api/config/agent', {
|
|
1103
|
+
method: 'POST',
|
|
1104
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1105
|
+
body: JSON.stringify({ provider: provider.value, model: model.value }),
|
|
1106
|
+
});
|
|
1107
|
+
const result = await response.json();
|
|
1108
|
+
if (!response.ok) throw new Error(result.error || 'falha ao salvar');
|
|
1109
|
+
feedback.textContent = 'salvo para execuções futuras';
|
|
1110
|
+
} catch (error) {
|
|
1111
|
+
feedback.textContent = error.message || 'falha ao salvar';
|
|
1112
|
+
} finally {
|
|
1113
|
+
save.disabled = false;
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
form.hidden = !state.configWritable;
|
|
1117
|
+
els.configuration.appendChild(form);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
902
1120
|
function renderPhases(snapshot) {
|
|
903
1121
|
clear(els.phases);
|
|
904
1122
|
if (snapshot.phases.length === 0) {
|
|
@@ -907,6 +1125,7 @@
|
|
|
907
1125
|
}
|
|
908
1126
|
for (const phase of snapshot.phases) {
|
|
909
1127
|
const item = el('li');
|
|
1128
|
+
item.className = 'detail-row';
|
|
910
1129
|
item.appendChild(el('span', 'item-icon icon-' + phase.status, PHASE_ICONS[phase.status] || '○'));
|
|
911
1130
|
const main = el('div', 'item-main');
|
|
912
1131
|
main.appendChild(el('div', 'item-title', phase.name));
|
|
@@ -918,6 +1137,12 @@
|
|
|
918
1137
|
formatUsage(phase),
|
|
919
1138
|
]);
|
|
920
1139
|
if (side) item.appendChild(el('span', 'item-side', side));
|
|
1140
|
+
item.tabIndex = 0;
|
|
1141
|
+
item.setAttribute('role', 'button');
|
|
1142
|
+
item.addEventListener('click', () => openDrawer('phase', phase.name));
|
|
1143
|
+
item.addEventListener('keydown', (event) => {
|
|
1144
|
+
if (event.key === 'Enter' || event.key === ' ') openDrawer('phase', phase.name);
|
|
1145
|
+
});
|
|
921
1146
|
els.phases.appendChild(item);
|
|
922
1147
|
}
|
|
923
1148
|
}
|
|
@@ -948,6 +1173,7 @@
|
|
|
948
1173
|
// antes da issue 38 pode chegar sem ele.
|
|
949
1174
|
const storyStage = story.stage !== null && story.stage !== undefined ? story.stage : 'pending';
|
|
950
1175
|
const item = el('li', storyStage === 'executing' ? 'story-executing' : null);
|
|
1176
|
+
item.classList.add('detail-row');
|
|
951
1177
|
const status = story.passes ? 'completed' : 'pending';
|
|
952
1178
|
item.appendChild(el('span', 'item-icon icon-' + status, story.passes ? '✓' : '○'));
|
|
953
1179
|
const main = el('div', 'item-main');
|
|
@@ -982,6 +1208,12 @@
|
|
|
982
1208
|
formatUsage(story),
|
|
983
1209
|
]);
|
|
984
1210
|
if (side) item.appendChild(el('span', 'item-side', side));
|
|
1211
|
+
item.tabIndex = 0;
|
|
1212
|
+
item.setAttribute('role', 'button');
|
|
1213
|
+
item.addEventListener('click', () => openDrawer('story', story.id));
|
|
1214
|
+
item.addEventListener('keydown', (event) => {
|
|
1215
|
+
if (event.key === 'Enter' || event.key === ' ') openDrawer('story', story.id);
|
|
1216
|
+
});
|
|
985
1217
|
els.stories.appendChild(item);
|
|
986
1218
|
}
|
|
987
1219
|
}
|
|
@@ -1008,7 +1240,7 @@
|
|
|
1008
1240
|
card.appendChild(
|
|
1009
1241
|
el('span', 'badge story-status-' + story.status, STORY_STATUS_LABELS[story.status]),
|
|
1010
1242
|
);
|
|
1011
|
-
card.addEventListener('click', () => openDrawer(story.id));
|
|
1243
|
+
card.addEventListener('click', () => openDrawer('story', story.id));
|
|
1012
1244
|
return card;
|
|
1013
1245
|
}
|
|
1014
1246
|
|
|
@@ -1039,18 +1271,145 @@
|
|
|
1039
1271
|
if (event.key === 'Escape') closeDrawer();
|
|
1040
1272
|
}
|
|
1041
1273
|
|
|
1042
|
-
|
|
1274
|
+
function executionsFor(snapshot, kind, id) {
|
|
1275
|
+
return list(snapshot.executions).filter((execution) => {
|
|
1276
|
+
if (kind === 'phase') return execution.purpose === id;
|
|
1277
|
+
return list(execution.storyIds).includes(id);
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function executionCost(execution) {
|
|
1282
|
+
const cost = execution.cost || {};
|
|
1283
|
+
if (cost.status === 'reported' || cost.status === 'estimated') {
|
|
1284
|
+
return formatCost(cost.amount, cost.status === 'estimated');
|
|
1285
|
+
}
|
|
1286
|
+
return cost.reason ? 'não informado (' + cost.reason + ')' : 'não informado';
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
function renderExecutionHistory(snapshot, executions) {
|
|
1290
|
+
drawerSection('Tentativas, revisões e correções', (body) => {
|
|
1291
|
+
if (executions.length === 0) {
|
|
1292
|
+
body.appendChild(el('p', 'empty', 'Nenhuma invocação associada.'));
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const timeline = el('ol', 'execution-timeline');
|
|
1296
|
+
for (const execution of executions.slice().reverse()) {
|
|
1297
|
+
const item = el('li', 'execution-entry');
|
|
1298
|
+
const title = el('div', 'execution-entry-head');
|
|
1299
|
+
title.appendChild(
|
|
1300
|
+
el(
|
|
1301
|
+
'span',
|
|
1302
|
+
'badge status-' + (execution.status === 'completed' ? 'completed' : execution.status),
|
|
1303
|
+
execution.status,
|
|
1304
|
+
),
|
|
1305
|
+
);
|
|
1306
|
+
title.appendChild(
|
|
1307
|
+
el(
|
|
1308
|
+
'strong',
|
|
1309
|
+
null,
|
|
1310
|
+
execution.purpose + ' · tentativa ' + execution.attempt + ' · ' + execution.trigger,
|
|
1311
|
+
),
|
|
1312
|
+
);
|
|
1313
|
+
item.appendChild(title);
|
|
1314
|
+
const grid = el('dl', 'now-grid execution-grid');
|
|
1315
|
+
nowRow(grid, 'Harness', execution.agent?.harness || '—');
|
|
1316
|
+
nowRow(grid, 'Modelo', execution.agent?.model?.resolved || execution.agent?.model?.requested || '—');
|
|
1317
|
+
nowRow(grid, 'Início', formatClock(execution.startedAt));
|
|
1318
|
+
nowRow(grid, 'Fim', execution.finishedAt ? formatClock(execution.finishedAt) : 'em andamento');
|
|
1319
|
+
nowRow(grid, 'Duração', metric(execution.durationMs) !== null ? formatDuration(execution.durationMs / 1000) : '—');
|
|
1320
|
+
nowRow(grid, 'Tokens', formatUsage(execution.usage || {}) || '—');
|
|
1321
|
+
nowRow(grid, 'Custo', executionCost(execution));
|
|
1322
|
+
if (execution.correctionCycle) nowRow(grid, 'Correção', 'ciclo ' + execution.correctionCycle);
|
|
1323
|
+
if (execution.verdict?.status) nowRow(grid, 'Veredito', execution.verdict.status);
|
|
1324
|
+
item.appendChild(grid);
|
|
1325
|
+
if (execution.failure?.message) item.appendChild(el('p', 'item-error', execution.failure.message));
|
|
1326
|
+
timeline.appendChild(item);
|
|
1327
|
+
}
|
|
1328
|
+
body.appendChild(timeline);
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
function renderProcessLogs(snapshot, executions, phase) {
|
|
1333
|
+
const ids = new Set(executions.map((execution) => execution.id));
|
|
1334
|
+
const logs = list(snapshot.processLogs).filter(
|
|
1335
|
+
(entry) => ids.has(entry.executionId) || (ids.size === 0 && entry.phase === phase),
|
|
1336
|
+
);
|
|
1337
|
+
drawerSection('Saída do processo', (body) => {
|
|
1338
|
+
const details = el('details', 'process-output');
|
|
1339
|
+
const summary = el('summary', null, logs.length + ' linha(s) sanitizada(s)');
|
|
1340
|
+
details.appendChild(summary);
|
|
1341
|
+
if (logs.length === 0) {
|
|
1342
|
+
details.appendChild(el('p', 'empty', 'Nenhuma saída capturada.'));
|
|
1343
|
+
} else {
|
|
1344
|
+
const output = el('pre', 'process-output-body');
|
|
1345
|
+
output.textContent = logs
|
|
1346
|
+
.slice(-200)
|
|
1347
|
+
.map((entry) => formatClock(entry.at) + ' ' + entry.message)
|
|
1348
|
+
.join('\n');
|
|
1349
|
+
details.appendChild(output);
|
|
1350
|
+
}
|
|
1351
|
+
body.appendChild(details);
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
function renderGlobalDiagnostics(kind, id, executions) {
|
|
1356
|
+
const ids = new Set(executions.map((execution) => execution.id));
|
|
1357
|
+
const entries = state.diagnostics.filter((entry) => {
|
|
1358
|
+
if (ids.has(entry.executionId)) return true;
|
|
1359
|
+
if (kind === 'phase') return entry.phase === id;
|
|
1360
|
+
return typeof entry.story === 'string' && entry.story.split(',').includes(id);
|
|
1361
|
+
});
|
|
1362
|
+
if (entries.length === 0) return;
|
|
1363
|
+
drawerSection('Diagnóstico global persistente', (body) => {
|
|
1364
|
+
const details = el('details', 'process-output');
|
|
1365
|
+
details.appendChild(el('summary', null, entries.length + ' registro(s) em ~/.issue-flow/logs'));
|
|
1366
|
+
const output = el('pre', 'process-output-body');
|
|
1367
|
+
output.textContent = entries
|
|
1368
|
+
.slice(0, 200)
|
|
1369
|
+
.map((entry) => formatClock(entry.timestamp) + ' ' + entry.level + ' ' + entry.message)
|
|
1370
|
+
.join('\n');
|
|
1371
|
+
details.appendChild(output);
|
|
1372
|
+
body.appendChild(details);
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// Reidrata a partir de kind/id a cada render: o drawer sobrevive ao poll.
|
|
1043
1377
|
function renderDrawer(snapshot) {
|
|
1044
|
-
if (state.
|
|
1045
|
-
const
|
|
1046
|
-
|
|
1047
|
-
|
|
1378
|
+
if (state.selectedDetail === null) return;
|
|
1379
|
+
const kind = state.selectedDetail.kind;
|
|
1380
|
+
const id = state.selectedDetail.id;
|
|
1381
|
+
const story = kind === 'story' ? getStoryById(snapshot, id) : null;
|
|
1382
|
+
const phase = kind === 'phase' ? list(snapshot.phases).find((entry) => entry.name === id) : null;
|
|
1383
|
+
if ((kind === 'story' && story === null) || (kind === 'phase' && !phase)) {
|
|
1048
1384
|
closeDrawer();
|
|
1049
1385
|
return;
|
|
1050
1386
|
}
|
|
1051
|
-
els.drawerTitle.textContent = story.id + ' · ' + story.title;
|
|
1052
1387
|
|
|
1053
1388
|
clear(els.drawerBody);
|
|
1389
|
+
if (kind === 'phase') {
|
|
1390
|
+
els.drawerTitle.textContent = 'Fase · ' + phase.name;
|
|
1391
|
+
els.drawerBody.appendChild(el('span', 'badge status-' + phase.status, phase.status));
|
|
1392
|
+
const grid = el('dl', 'now-grid drawer-summary-grid');
|
|
1393
|
+
nowRow(grid, 'Início', phase.startedAt ? formatClock(phase.startedAt) : '—');
|
|
1394
|
+
nowRow(grid, 'Fim', phase.endedAt ? formatClock(phase.endedAt) : '—');
|
|
1395
|
+
nowRow(grid, 'Duração', metric(phase.durationSeconds) !== null ? formatDuration(phase.durationSeconds) : '—');
|
|
1396
|
+
nowRow(grid, 'Uso total', formatUsage(phase) || '—');
|
|
1397
|
+
const configured = list(snapshot.configuration?.phases).find((entry) => entry.phase === phase.name);
|
|
1398
|
+
if (configured) {
|
|
1399
|
+
nowRow(grid, 'Harness efetivo', configured.provider.value || '—');
|
|
1400
|
+
nowRow(grid, 'Origem', configSourceLabel(configured.provider.source));
|
|
1401
|
+
nowRow(grid, 'Modelo efetivo', configured.model.value || 'default do provider');
|
|
1402
|
+
}
|
|
1403
|
+
els.drawerBody.appendChild(grid);
|
|
1404
|
+
if (phase.error) els.drawerBody.appendChild(el('p', 'item-error', phase.error));
|
|
1405
|
+
const executions = executionsFor(snapshot, kind, id);
|
|
1406
|
+
renderExecutionHistory(snapshot, executions);
|
|
1407
|
+
renderProcessLogs(snapshot, executions, phase.name);
|
|
1408
|
+
renderGlobalDiagnostics(kind, id, executions);
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
els.drawerTitle.textContent = story.id + ' · ' + story.title;
|
|
1054
1413
|
els.drawerBody.appendChild(
|
|
1055
1414
|
el('span', 'badge story-status-' + story.status, STORY_STATUS_LABELS[story.status]),
|
|
1056
1415
|
);
|
|
@@ -1100,12 +1459,21 @@
|
|
|
1100
1459
|
for (const entry of history) {
|
|
1101
1460
|
const item = el('li');
|
|
1102
1461
|
if (entry.at) item.appendChild(el('span', 'log-time', formatClock(entry.at)));
|
|
1103
|
-
item.appendChild(
|
|
1462
|
+
item.appendChild(
|
|
1463
|
+
document.createTextNode(
|
|
1464
|
+
(STORY_STAGE_LABELS[entry.stage] || entry.stage || '') +
|
|
1465
|
+
(entry.detail ? ' · ' + entry.detail : ''),
|
|
1466
|
+
),
|
|
1467
|
+
);
|
|
1104
1468
|
entries.appendChild(item);
|
|
1105
1469
|
}
|
|
1106
1470
|
body.appendChild(entries);
|
|
1107
1471
|
});
|
|
1108
1472
|
}
|
|
1473
|
+
const executions = executionsFor(snapshot, kind, id);
|
|
1474
|
+
renderExecutionHistory(snapshot, executions);
|
|
1475
|
+
renderProcessLogs(snapshot, executions, 'execute');
|
|
1476
|
+
renderGlobalDiagnostics(kind, id, executions);
|
|
1109
1477
|
}
|
|
1110
1478
|
|
|
1111
1479
|
function drawerSection(title, fill) {
|
|
@@ -1115,8 +1483,8 @@
|
|
|
1115
1483
|
els.drawerBody.appendChild(section);
|
|
1116
1484
|
}
|
|
1117
1485
|
|
|
1118
|
-
function openDrawer(id) {
|
|
1119
|
-
state.
|
|
1486
|
+
function openDrawer(kind, id) {
|
|
1487
|
+
state.selectedDetail = { kind, id };
|
|
1120
1488
|
els.drawer.hidden = false;
|
|
1121
1489
|
els.drawerOverlay.hidden = false;
|
|
1122
1490
|
document.addEventListener('keydown', onDrawerKeydown);
|
|
@@ -1125,15 +1493,18 @@
|
|
|
1125
1493
|
}
|
|
1126
1494
|
|
|
1127
1495
|
function closeDrawer() {
|
|
1128
|
-
const
|
|
1129
|
-
state.
|
|
1496
|
+
const selected = state.selectedDetail;
|
|
1497
|
+
state.selectedDetail = null;
|
|
1130
1498
|
els.drawer.hidden = true;
|
|
1131
1499
|
els.drawerOverlay.hidden = true;
|
|
1132
1500
|
document.removeEventListener('keydown', onDrawerKeydown);
|
|
1133
1501
|
clear(els.drawerBody);
|
|
1134
1502
|
// O card é recriado a cada render, então o foco volta pelo id, não por uma
|
|
1135
1503
|
// referência guardada na abertura.
|
|
1136
|
-
const card =
|
|
1504
|
+
const card =
|
|
1505
|
+
selected && selected.kind === 'story'
|
|
1506
|
+
? els.kanban.querySelector('[data-story-id="' + selected.id + '"]')
|
|
1507
|
+
: null;
|
|
1137
1508
|
if (card) card.focus();
|
|
1138
1509
|
}
|
|
1139
1510
|
|
|
@@ -1151,6 +1522,11 @@
|
|
|
1151
1522
|
: el('span', 'mono commit-hash', commit.hash);
|
|
1152
1523
|
item.appendChild(hash);
|
|
1153
1524
|
const subject = el('div', 'item-main item-title', commit.subject);
|
|
1525
|
+
const meta = itemSideText([
|
|
1526
|
+
commit.storyId || '',
|
|
1527
|
+
commit.committedAt ? formatClock(commit.committedAt) : '',
|
|
1528
|
+
]);
|
|
1529
|
+
if (meta) subject.appendChild(el('span', 'muted commit-meta', meta));
|
|
1154
1530
|
item.appendChild(subject);
|
|
1155
1531
|
els.commits.appendChild(item);
|
|
1156
1532
|
}
|
|
@@ -1290,6 +1666,11 @@
|
|
|
1290
1666
|
|
|
1291
1667
|
// ---- Inicialização --------------------------------------------------------
|
|
1292
1668
|
|
|
1669
|
+
function onThemeChange(select) {
|
|
1670
|
+
setTheme(select.value);
|
|
1671
|
+
storeTheme(state.theme);
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1293
1674
|
function onRefreshChange(select) {
|
|
1294
1675
|
state.refreshSeconds = Number(select.value);
|
|
1295
1676
|
storeRefresh(state.refreshSeconds);
|
|
@@ -1315,6 +1696,14 @@
|
|
|
1315
1696
|
);
|
|
1316
1697
|
}
|
|
1317
1698
|
|
|
1699
|
+
els.themeSelect.addEventListener('change', () => onThemeChange(els.themeSelect));
|
|
1700
|
+
if (els.themeSelectDashboard) {
|
|
1701
|
+
els.themeSelectDashboard.addEventListener('change', () =>
|
|
1702
|
+
onThemeChange(els.themeSelectDashboard),
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
initTheme();
|
|
1706
|
+
|
|
1318
1707
|
els.logFilter.addEventListener('change', () => {
|
|
1319
1708
|
state.logFilter = els.logFilter.value;
|
|
1320
1709
|
if (state.snapshot) renderLogs(state.snapshot);
|
|
@@ -1337,6 +1726,7 @@
|
|
|
1337
1726
|
} else {
|
|
1338
1727
|
try {
|
|
1339
1728
|
const health = await fetch('api/health', { cache: 'no-store' }).then((r) => r.json());
|
|
1729
|
+
state.configWritable = list(health.capabilities).includes('config:agent:write');
|
|
1340
1730
|
const suggested = Number(health.refreshSeconds);
|
|
1341
1731
|
if (Number.isFinite(suggested) && suggested > 0) state.refreshSeconds = suggested;
|
|
1342
1732
|
} catch (err) {
|