issue-flow 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist/{agent-TB6W7PT7.js → agent-HMZTXCRT.js} +5 -5
  2. package/dist/{analyze-6M3KEOX7.js → analyze-Q4ONIWRZ.js} +10 -10
  3. package/dist/{apply-ADB3ZK7W.js → apply-EEFPY2W4.js} +5 -5
  4. package/dist/{bench-FM7I7EIV.js → bench-WWWXLTFH.js} +9 -9
  5. package/dist/{chunk-KZ5EY5XX.js → chunk-34OCSKOB.js} +7 -7
  6. package/dist/{chunk-UWDCIFTP.js → chunk-45WZ6GMI.js} +27 -43
  7. package/dist/chunk-45WZ6GMI.js.map +1 -0
  8. package/dist/{chunk-QMIQR7EE.js → chunk-62GVVZQO.js} +3 -3
  9. package/dist/{chunk-YII4U7ZD.js → chunk-6K2LCFG6.js} +2 -2
  10. package/dist/{chunk-A4H7P7T7.js → chunk-7UMMFOL2.js} +3 -3
  11. package/dist/chunk-7UMMFOL2.js.map +1 -0
  12. package/dist/{chunk-HJBF4WD2.js → chunk-CWSQVHVV.js} +59 -25
  13. package/dist/chunk-CWSQVHVV.js.map +1 -0
  14. package/dist/{chunk-SGTTR4XW.js → chunk-E4I6MMHD.js} +4 -4
  15. package/dist/{chunk-6YQT3SSM.js → chunk-ECU7YO7S.js} +2 -2
  16. package/dist/{chunk-MI5TREN3.js → chunk-EG2MGDP5.js} +6 -6
  17. package/dist/{chunk-RCZJQUTC.js → chunk-HOAHMERN.js} +43 -10
  18. package/dist/chunk-HOAHMERN.js.map +1 -0
  19. package/dist/{chunk-HLAPOUEM.js → chunk-KMCS76ZW.js} +3 -3
  20. package/dist/{chunk-KZXQWC7U.js → chunk-KRAGAUKD.js} +8 -8
  21. package/dist/{chunk-E6KXCV3T.js → chunk-LGHND47K.js} +2 -2
  22. package/dist/{chunk-DV7HUC4Z.js → chunk-LI35PIIS.js} +2 -2
  23. package/dist/{chunk-Z5O2XBWA.js → chunk-LTH6XYZ3.js} +5 -5
  24. package/dist/{chunk-2SUFAE6X.js → chunk-NEWNXZ65.js} +21 -12
  25. package/dist/{chunk-2SUFAE6X.js.map → chunk-NEWNXZ65.js.map} +1 -1
  26. package/dist/{chunk-LPWPFIIB.js → chunk-OODXFCQV.js} +7 -7
  27. package/dist/{chunk-ISZ54SVE.js → chunk-PMLE7KAQ.js} +17 -9
  28. package/dist/chunk-PMLE7KAQ.js.map +1 -0
  29. package/dist/{chunk-HY5HLFDU.js → chunk-QF7VEL3M.js} +115 -14
  30. package/dist/chunk-QF7VEL3M.js.map +1 -0
  31. package/dist/{chunk-B3TSS7BT.js → chunk-QJ44VG5G.js} +2 -2
  32. package/dist/{chunk-VE3IYNDN.js → chunk-SA6HXL4Y.js} +2 -2
  33. package/dist/{chunk-UK4E2FUA.js → chunk-TZYINKCC.js} +75 -53
  34. package/dist/chunk-TZYINKCC.js.map +1 -0
  35. package/dist/{chunk-32JSYXEK.js → chunk-V7QOIQYC.js} +2 -2
  36. package/dist/{chunk-ANI2NTR4.js → chunk-XUEMATVY.js} +6 -6
  37. package/dist/{chunk-D6ZBGJ3L.js → chunk-ZVZMWNCG.js} +129 -35
  38. package/dist/chunk-ZVZMWNCG.js.map +1 -0
  39. package/dist/cli.js +35 -35
  40. package/dist/cli.js.map +1 -1
  41. package/dist/{config-KGZUPAUP.js → config-LEBZZCJP.js} +4 -4
  42. package/dist/{conventions-RYN7OXJN.js → conventions-XOT6YW5L.js} +6 -6
  43. package/dist/{execute-QL3ZQIAP.js → execute-LELOSR5T.js} +14 -14
  44. package/dist/{generate-CX6KNHZS.js → generate-JGW5JSKB.js} +10 -10
  45. package/dist/{init-PDVQDRS6.js → init-TO6MUXO4.js} +7 -5
  46. package/dist/{operations-CAKIT2BO.js → operations-O4FORFIH.js} +4 -4
  47. package/dist/{plan-PRORGS3L.js → plan-OXAT344Q.js} +12 -12
  48. package/dist/{policy-WX6O2JID.js → policy-U5NBFAO5.js} +5 -5
  49. package/dist/{pr-27RMQI5F.js → pr-UZHNKKZC.js} +12 -12
  50. package/dist/{pr-review-VNESMAMT.js → pr-review-Q4ACAWDM.js} +9 -9
  51. package/dist/{prd-ZNC4T5BS.js → prd-6CYEMLDY.js} +12 -12
  52. package/dist/{recorder-N7U66HCO.js → recorder-2BAKPBRA.js} +2 -2
  53. package/dist/{resume-VLWIE264.js → resume-URCU6CIJ.js} +26 -26
  54. package/dist/{review-7CREJDKB.js → review-LVZT2TMW.js} +13 -13
  55. package/dist/{routing-VCDYHSXA.js → routing-EK6RBZM4.js} +6 -6
  56. package/dist/run-2RXJCJKT.js +56 -0
  57. package/dist/{usage-U7QNGCPK.js → usage-KTMYXAPF.js} +3 -3
  58. package/dist/{web-HUNDHCJ4.js → web-B5UPHHR5.js} +8 -8
  59. package/package.json +1 -1
  60. package/web/AGENTS.md +115 -7
  61. package/web/public/app.css +393 -245
  62. package/web/public/app.js +108 -38
  63. package/web/public/index.html +106 -78
  64. package/dist/chunk-A4H7P7T7.js.map +0 -1
  65. package/dist/chunk-D6ZBGJ3L.js.map +0 -1
  66. package/dist/chunk-HJBF4WD2.js.map +0 -1
  67. package/dist/chunk-HY5HLFDU.js.map +0 -1
  68. package/dist/chunk-ISZ54SVE.js.map +0 -1
  69. package/dist/chunk-RCZJQUTC.js.map +0 -1
  70. package/dist/chunk-UK4E2FUA.js.map +0 -1
  71. package/dist/chunk-UWDCIFTP.js.map +0 -1
  72. package/dist/run-3MPORLF5.js +0 -56
  73. /package/dist/{agent-TB6W7PT7.js.map → agent-HMZTXCRT.js.map} +0 -0
  74. /package/dist/{analyze-6M3KEOX7.js.map → analyze-Q4ONIWRZ.js.map} +0 -0
  75. /package/dist/{apply-ADB3ZK7W.js.map → apply-EEFPY2W4.js.map} +0 -0
  76. /package/dist/{bench-FM7I7EIV.js.map → bench-WWWXLTFH.js.map} +0 -0
  77. /package/dist/{chunk-KZ5EY5XX.js.map → chunk-34OCSKOB.js.map} +0 -0
  78. /package/dist/{chunk-QMIQR7EE.js.map → chunk-62GVVZQO.js.map} +0 -0
  79. /package/dist/{chunk-YII4U7ZD.js.map → chunk-6K2LCFG6.js.map} +0 -0
  80. /package/dist/{chunk-SGTTR4XW.js.map → chunk-E4I6MMHD.js.map} +0 -0
  81. /package/dist/{chunk-6YQT3SSM.js.map → chunk-ECU7YO7S.js.map} +0 -0
  82. /package/dist/{chunk-MI5TREN3.js.map → chunk-EG2MGDP5.js.map} +0 -0
  83. /package/dist/{chunk-HLAPOUEM.js.map → chunk-KMCS76ZW.js.map} +0 -0
  84. /package/dist/{chunk-KZXQWC7U.js.map → chunk-KRAGAUKD.js.map} +0 -0
  85. /package/dist/{chunk-E6KXCV3T.js.map → chunk-LGHND47K.js.map} +0 -0
  86. /package/dist/{chunk-DV7HUC4Z.js.map → chunk-LI35PIIS.js.map} +0 -0
  87. /package/dist/{chunk-Z5O2XBWA.js.map → chunk-LTH6XYZ3.js.map} +0 -0
  88. /package/dist/{chunk-LPWPFIIB.js.map → chunk-OODXFCQV.js.map} +0 -0
  89. /package/dist/{chunk-B3TSS7BT.js.map → chunk-QJ44VG5G.js.map} +0 -0
  90. /package/dist/{chunk-VE3IYNDN.js.map → chunk-SA6HXL4Y.js.map} +0 -0
  91. /package/dist/{chunk-32JSYXEK.js.map → chunk-V7QOIQYC.js.map} +0 -0
  92. /package/dist/{chunk-ANI2NTR4.js.map → chunk-XUEMATVY.js.map} +0 -0
  93. /package/dist/{config-KGZUPAUP.js.map → config-LEBZZCJP.js.map} +0 -0
  94. /package/dist/{conventions-RYN7OXJN.js.map → conventions-XOT6YW5L.js.map} +0 -0
  95. /package/dist/{execute-QL3ZQIAP.js.map → execute-LELOSR5T.js.map} +0 -0
  96. /package/dist/{generate-CX6KNHZS.js.map → generate-JGW5JSKB.js.map} +0 -0
  97. /package/dist/{init-PDVQDRS6.js.map → init-TO6MUXO4.js.map} +0 -0
  98. /package/dist/{operations-CAKIT2BO.js.map → operations-O4FORFIH.js.map} +0 -0
  99. /package/dist/{plan-PRORGS3L.js.map → plan-OXAT344Q.js.map} +0 -0
  100. /package/dist/{policy-WX6O2JID.js.map → policy-U5NBFAO5.js.map} +0 -0
  101. /package/dist/{pr-27RMQI5F.js.map → pr-UZHNKKZC.js.map} +0 -0
  102. /package/dist/{pr-review-VNESMAMT.js.map → pr-review-Q4ACAWDM.js.map} +0 -0
  103. /package/dist/{prd-ZNC4T5BS.js.map → prd-6CYEMLDY.js.map} +0 -0
  104. /package/dist/{recorder-N7U66HCO.js.map → recorder-2BAKPBRA.js.map} +0 -0
  105. /package/dist/{resume-VLWIE264.js.map → resume-URCU6CIJ.js.map} +0 -0
  106. /package/dist/{review-7CREJDKB.js.map → review-LVZT2TMW.js.map} +0 -0
  107. /package/dist/{routing-VCDYHSXA.js.map → routing-EK6RBZM4.js.map} +0 -0
  108. /package/dist/{run-3MPORLF5.js.map → run-2RXJCJKT.js.map} +0 -0
  109. /package/dist/{usage-U7QNGCPK.js.map → usage-KTMYXAPF.js.map} +0 -0
  110. /package/dist/{web-HUNDHCJ4.js.map → web-B5UPHHR5.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;
@@ -318,15 +345,34 @@
318
345
  // Troca apenas visibilidade e estado ARIA. Não toca no polling: render()
319
346
  // segue atualizando os dois painéis, então a aba inativa nunca fica defasada.
320
347
 
321
- function setActiveTab(tabId) {
348
+ function setActiveTab(tabId, options) {
322
349
  state.activeTab = tabId;
350
+ let focused = null;
323
351
  for (const tab of els.tabs) {
324
352
  const active = tab.id === tabId;
325
353
  tab.setAttribute('aria-selected', active ? 'true' : 'false');
354
+ tab.tabIndex = active ? 0 : -1;
326
355
  tab.classList.toggle('is-active', active);
327
356
  const panel = document.getElementById(tab.getAttribute('aria-controls'));
328
357
  if (panel) panel.hidden = !active;
358
+ if (active) focused = tab;
329
359
  }
360
+ if (options && options.focus && focused) focused.focus();
361
+ }
362
+
363
+ // Padrão ARIA de tablist: setas movem entre as abas, Home/End vão às pontas.
364
+ // Só a aba ativa fica no fluxo do Tab (roving tabindex).
365
+ function onTabListKeydown(event) {
366
+ const current = els.tabs.indexOf(event.target);
367
+ if (current === -1) return;
368
+ let next = -1;
369
+ if (event.key === 'ArrowRight') next = (current + 1) % els.tabs.length;
370
+ else if (event.key === 'ArrowLeft') next = (current - 1 + els.tabs.length) % els.tabs.length;
371
+ else if (event.key === 'Home') next = 0;
372
+ else if (event.key === 'End') next = els.tabs.length - 1;
373
+ else return;
374
+ event.preventDefault();
375
+ setActiveTab(els.tabs[next].id, { focus: true });
330
376
  }
331
377
 
332
378
  // ---- Tema ------------------------------------------------------------------
@@ -701,6 +747,9 @@
701
747
  function renderEmptyDetail() {
702
748
  if (state.snapshot) return;
703
749
  document.title = 'issue-flow';
750
+ els.issueLink.hidden = true;
751
+ els.issueHeadline.textContent = 'Nenhuma execução ativa';
752
+ els.branchLine.textContent = '';
704
753
  els.statusBadge.textContent = STATUS_LABELS.idle;
705
754
  els.statusBadge.className = 'badge status-idle';
706
755
  els.elapsed.textContent = '—';
@@ -715,6 +764,25 @@
715
764
  return normalized.slice(0, max - 1).trimEnd() + '…';
716
765
  }
717
766
 
767
+ // Linha secundária do header do dashboard: quantas execuções e em que
768
+ // estado. Substitui a legenda estática que só repetia o h1.
769
+ function renderDashboardSummary(sessions) {
770
+ const counts = {};
771
+ for (const session of sessions) {
772
+ const status = session.status || 'idle';
773
+ counts[status] = (counts[status] || 0) + 1;
774
+ }
775
+ const parts = [];
776
+ for (const status of SUMMARY_STATUS_ORDER) {
777
+ const count = counts[status];
778
+ if (!count) continue;
779
+ const label = SUMMARY_STATUS_LABELS[status];
780
+ parts.push(count + ' ' + (count === 1 ? label[0] : label[1]));
781
+ }
782
+ const total = sessions.length + (sessions.length === 1 ? ' execução' : ' execuções');
783
+ els.dashboardSummary.textContent = parts.length > 0 ? total + ' · ' + parts.join(' · ') : total;
784
+ }
785
+
718
786
  function renderDashboard(sessions) {
719
787
  const active = document.activeElement;
720
788
  const focusedId =
@@ -727,10 +795,13 @@
727
795
  if (sessions.length === 0) {
728
796
  els.dashboard.appendChild(el('p', 'empty', 'Nenhuma execução ativa.'));
729
797
  els.dashboardMeta.textContent = '0 execuções';
798
+ els.dashboardSummary.textContent = 'Nenhuma execução ativa';
730
799
  document.title = 'issue-flow';
731
800
  return;
732
801
  }
733
802
 
803
+ renderDashboardSummary(sessions);
804
+
734
805
  for (const session of sessions) {
735
806
  // <button> só aceita phrasing content — mesmos spans do Kanban.
736
807
  const card = el('button', 'dashboard-card');
@@ -798,7 +869,10 @@
798
869
  card.appendChild(progress);
799
870
 
800
871
  if (session.status === 'running') {
801
- card.appendChild(el('span', 'dashboard-live-dot', 'ao vivo'));
872
+ const live = el('span', 'live-label');
873
+ live.appendChild(liveDot());
874
+ live.appendChild(document.createTextNode('ao vivo'));
875
+ card.appendChild(live);
802
876
  }
803
877
 
804
878
  card.addEventListener('click', () => {
@@ -856,8 +930,11 @@
856
930
  document.title = (prefix + issue + ' · issue-flow').replace(/^ · /, '');
857
931
  }
858
932
 
933
+ // O h1 é a execução — número, título, e ao lado status, tempo decorrido e
934
+ // branch. A marca do produto vive só no <title> do documento.
859
935
  function renderHeader(snapshot) {
860
- if (snapshot.issue.number !== null) {
936
+ const hasIssue = snapshot.issue.number !== null && snapshot.issue.number !== undefined;
937
+ if (hasIssue) {
861
938
  els.issueLink.hidden = false;
862
939
  els.issueLink.textContent = '#' + snapshot.issue.number;
863
940
  if (snapshot.issue.url) els.issueLink.href = snapshot.issue.url;
@@ -866,6 +943,10 @@
866
943
  els.issueLink.hidden = true;
867
944
  }
868
945
 
946
+ // session.json antigo pode não ter título; sem issue vinculada, nem número.
947
+ els.issueHeadline.textContent =
948
+ snapshot.issue.title || (hasIssue ? 'Sem título' : 'Execução sem issue vinculada');
949
+
869
950
  const branch = snapshot.git.branch;
870
951
  const base = snapshot.git.baseBranch;
871
952
  const branchMode =
@@ -925,23 +1006,13 @@
925
1006
  clear(els.issueSummary);
926
1007
  const issue = snapshot.issue || {};
927
1008
 
928
- const heading = el('p', 'issue-summary-title');
929
- heading.appendChild(
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
-
1009
+ // Número e título saem no h1 do header (renderHeader); aqui fica só o que
1010
+ // o header não carrega — estado, labels e descrição.
939
1011
  const meta = el('div', 'issue-summary-meta');
940
1012
  const state = issue.state || null;
941
1013
  const stateClass =
942
1014
  state === 'open' ? 'state-open' : state === 'closed' ? 'state-closed' : 'state-unknown';
943
1015
  meta.appendChild(el('span', 'badge ' + stateClass, state || 'estado desconhecido'));
944
- meta.appendChild(el('span', 'muted', 'Prioridade: Não definida'));
945
1016
  els.issueSummary.appendChild(meta);
946
1017
 
947
1018
  const labels = issue.labels || [];
@@ -1008,16 +1079,16 @@
1008
1079
  const messages = {
1009
1080
  idle: 'Nenhuma execução em andamento.',
1010
1081
  completed: 'Execução concluída.',
1011
- failed: 'Execução falhou veja os erros acima.',
1082
+ failed: 'Execução falhou. Veja os erros acima.',
1012
1083
  };
1013
1084
  els.now.appendChild(el('p', 'empty', messages[snapshot.status] || '—'));
1014
1085
  return;
1015
1086
  }
1016
1087
 
1017
1088
  const grid = el('dl', 'now-grid');
1018
- const phase = el('dd');
1089
+ const phase = el('dd', 'now-phase');
1019
1090
  if (snapshot.currentPhase) {
1020
- phase.appendChild(el('span', 'pulse'));
1091
+ phase.appendChild(liveDot());
1021
1092
  phase.appendChild(document.createTextNode(snapshot.currentPhase));
1022
1093
  } else {
1023
1094
  phase.textContent = '—';
@@ -1027,7 +1098,7 @@
1027
1098
 
1028
1099
  const activity = snapshot.currentActivity;
1029
1100
  if (activity) {
1030
- if (activity.story) nowRow(grid, 'Story', activity.story);
1101
+ if (activity.story) nowRow(grid, 'User story', activity.story);
1031
1102
  if (activity.tool) nowRow(grid, 'Ferramenta', activity.tool);
1032
1103
  if (activity.detail) nowRow(grid, 'Detalhe', activity.detail);
1033
1104
  const since = el('dd', null, formatAgo(activity.since));
@@ -1299,7 +1370,14 @@
1299
1370
 
1300
1371
  function fillProviderSelect(select, selected) {
1301
1372
  clear(select);
1302
- const entries = catalog().filter((entry) => entry.installed && entry.authenticated);
1373
+ const entries = catalog().filter((entry) => {
1374
+ if (!entry.installed) return false;
1375
+ if (entry.state === 'unavailable') return false;
1376
+ if (entry.authentication === 'failed') return false;
1377
+ return (
1378
+ entry.authenticated !== false || entry.state === 'conditional' || entry.state === 'ready'
1379
+ );
1380
+ });
1303
1381
  const providers = entries.length
1304
1382
  ? entries.map((entry) => entry.provider)
1305
1383
  : ['claude', 'codex', 'cursor', 'antigravity'];
@@ -1364,9 +1442,7 @@
1364
1442
  for (const phase of snapshot.phases) {
1365
1443
  const item = el('li');
1366
1444
  item.className = 'detail-row';
1367
- item.appendChild(
1368
- el('span', 'item-icon icon-' + phase.status, PHASE_ICONS[phase.status] || '○'),
1369
- );
1445
+ item.appendChild(statusIcon(phase.status));
1370
1446
  const main = el('div', 'item-main');
1371
1447
  main.appendChild(el('div', 'item-title', phase.name));
1372
1448
  if (phase.error) main.appendChild(el('div', 'item-error', phase.error));
@@ -1387,19 +1463,17 @@
1387
1463
  }
1388
1464
  }
1389
1465
 
1466
+ // Uma linha dentro do bloco de estado, não uma lista num cartão próprio: os
1467
+ // passos são poucos e curtos, e o rótulo já vem do HTML (.next-steps-label).
1390
1468
  function renderNextSteps(snapshot) {
1391
1469
  clear(els.nextSteps);
1392
1470
  if (snapshot.nextSteps.length === 0) {
1393
1471
  const message =
1394
1472
  snapshot.status === 'completed' ? 'Pipeline concluído.' : 'Nenhum passo pendente.';
1395
- els.nextSteps.appendChild(el('p', 'empty', message));
1473
+ els.nextSteps.appendChild(el('span', 'empty', message));
1396
1474
  return;
1397
1475
  }
1398
- const list = el('ol');
1399
- for (const step of snapshot.nextSteps) {
1400
- list.appendChild(el('li', null, step));
1401
- }
1402
- els.nextSteps.appendChild(list);
1476
+ els.nextSteps.appendChild(el('span', null, snapshot.nextSteps.join(' · ')));
1403
1477
  }
1404
1478
 
1405
1479
  function renderStories(snapshot) {
@@ -1416,7 +1490,7 @@
1416
1490
  const item = el('li', storyStage === 'executing' ? 'story-executing' : null);
1417
1491
  item.classList.add('detail-row');
1418
1492
  const status = story.passes ? 'completed' : 'pending';
1419
- item.appendChild(el('span', 'item-icon icon-' + status, story.passes ? '✓' : '○'));
1493
+ item.appendChild(statusIcon(status, story.passes ? '✓' : '○'));
1420
1494
  const main = el('div', 'item-main');
1421
1495
  const title = el('div', 'item-title');
1422
1496
  title.appendChild(el('span', 'story-id', story.id));
@@ -1475,13 +1549,7 @@
1475
1549
  card.dataset.storyId = story.id;
1476
1550
 
1477
1551
  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
- );
1552
+ head.appendChild(statusIcon(story.passes ? 'completed' : 'pending', story.passes ? '✓' : '○'));
1485
1553
  head.appendChild(el('span', 'story-id', story.id));
1486
1554
  card.appendChild(head);
1487
1555
 
@@ -1914,7 +1982,7 @@
1914
1982
 
1915
1983
  function renderMeta(snapshot) {
1916
1984
  const parts = [];
1917
- if (snapshot.sessionId) parts.push('sessão ' + snapshot.sessionId);
1985
+ if (snapshot.sessionId) parts.push('execução ' + snapshot.sessionId);
1918
1986
  if (snapshot.updatedAt) parts.push('atualizado ' + formatClock(snapshot.updatedAt));
1919
1987
  parts.push('somente leitura');
1920
1988
  els.sessionMeta.textContent = parts.join(' · ');
@@ -1963,6 +2031,8 @@
1963
2031
  }
1964
2032
 
1965
2033
  async function init() {
2034
+ const tablist = document.querySelector('[role="tablist"]');
2035
+ if (tablist) tablist.addEventListener('keydown', onTabListKeydown);
1966
2036
  for (const tab of els.tabs) {
1967
2037
  tab.addEventListener('click', () => setActiveTab(tab.id));
1968
2038
  }
@@ -30,18 +30,18 @@
30
30
  </head>
31
31
  <body>
32
32
  <div id="banner-disconnected" class="banner" role="alert" hidden>
33
- Desconectado do servidor tentando reconectar…
33
+ Desconectado do servidor. Tentando reconectar…
34
34
  </div>
35
35
 
36
36
  <main>
37
37
  <div id="view-dashboard" class="view" hidden>
38
38
  <header class="card header">
39
39
  <div class="header-main">
40
- <h1>
41
- <span class="brand">issue-flow</span>
40
+ <h1>Execuções ativas</h1>
41
+ <p class="header-meta">
42
+ <span id="dashboard-summary" class="muted"></span>
42
43
  <span id="app-version-dashboard" class="app-version" hidden></span>
43
- </h1>
44
- <p class="muted">Execuções ativas</p>
44
+ </p>
45
45
  </div>
46
46
  <div class="header-side">
47
47
  <label class="refresh-control">
@@ -70,12 +70,16 @@
70
70
  <button type="button" id="back-to-dashboard" class="back-link" hidden>
71
71
  ← Todas as execuções
72
72
  </button>
73
+ <!-- A linha mais visível da tela é a execução, não a marca: o produto
74
+ identifica-se pelo <title> do documento. Ver web/AGENTS.md. -->
73
75
  <h1>
74
- <span class="brand">issue-flow</span>
75
- <span id="app-version" class="app-version" hidden></span>
76
76
  <a id="issue-link" class="issue-link" target="_blank" rel="noopener" hidden>#—</a>
77
+ <span id="issue-headline">Nenhuma execução ativa</span>
77
78
  </h1>
78
- <p id="branch-line" class="muted mono"></p>
79
+ <p class="header-meta">
80
+ <span id="branch-line" class="muted mono"></span>
81
+ <span id="app-version" class="app-version" hidden></span>
82
+ </p>
79
83
  </div>
80
84
  <div class="header-side">
81
85
  <span id="status-badge" class="badge status-idle">aguardando</span>
@@ -105,99 +109,124 @@
105
109
 
106
110
  <div class="tabs" role="tablist" aria-label="Visualizações do painel">
107
111
  <button type="button" class="tab" role="tab" id="tab-execution"
108
- aria-controls="panel-execution" aria-selected="true">Execução</button>
112
+ aria-controls="panel-execution" aria-selected="true" tabindex="0">Execução</button>
109
113
  <button type="button" class="tab" role="tab" id="tab-kanban"
110
- aria-controls="panel-kanban" aria-selected="false">Kanban</button>
114
+ aria-controls="panel-kanban" aria-selected="false" tabindex="-1">Kanban</button>
111
115
  <button type="button" class="tab" role="tab" id="tab-history"
112
- aria-controls="panel-history" aria-selected="false">Histórico</button>
116
+ aria-controls="panel-history" aria-selected="false" tabindex="-1">Histórico</button>
113
117
  </div>
114
118
 
119
+ <!-- Aba Execução em quatro blocos, na ordem em que a pergunta aparece:
120
+ o que está acontecendo agora, sobre o quê, quanto já andou e o que
121
+ saiu disso. Cada bloco é UM cartão; o que antes era um cartão por
122
+ assunto virou uma sub-seção com <h3> e sem borda própria. -->
115
123
  <div class="tab-panel" id="panel-execution" role="tabpanel" aria-labelledby="tab-execution" tabindex="0">
116
- <div class="columns">
117
- <section class="card">
118
- <h2>Resumo da issue</h2>
119
- <div id="issue-summary"></div>
120
- </section>
121
- <section class="card">
122
- <h2>Repositório</h2>
123
- <div id="repository"></div>
124
- </section>
125
- </div>
126
124
 
127
- <section class="card" aria-live="polite">
128
- <h2>Progresso</h2>
129
- <div class="progress-row">
130
- <progress id="progress-bar" max="100" value="0"></progress>
131
- <span id="progress-percent" class="progress-percent">0%</span>
125
+ <!-- Estado agora. Primeiro de propósito: é o único bloco que precisa ser
126
+ legível sem rolagem. Não acrescente conteúdo aqui sem checar isso. -->
127
+ <section class="card block block-now">
128
+ <h2>Estado agora</h2>
129
+
130
+ <div aria-live="polite">
131
+ <div class="progress-row">
132
+ <progress id="progress-bar" max="100" value="0"></progress>
133
+ <span id="progress-percent" class="progress-percent">0%</span>
134
+ </div>
135
+ <p id="progress-counters" class="muted"></p>
132
136
  </div>
133
- <p id="progress-counters" class="muted"></p>
134
- </section>
135
137
 
136
- <section class="card" aria-live="polite">
137
- <h2>Executando agora</h2>
138
- <div id="now"></div>
139
- </section>
138
+ <div class="columns">
139
+ <div class="block-part">
140
+ <h3>Executando agora</h3>
141
+ <div id="now" aria-live="polite"></div>
142
+ </div>
143
+ <div class="block-part">
144
+ <h3>Resiliência</h3>
145
+ <div id="resilience" aria-live="polite"></div>
146
+ </div>
147
+ </div>
140
148
 
141
- <section class="card" aria-live="polite">
142
- <h2>Resiliência</h2>
143
- <div id="resilience"></div>
149
+ <p class="next-steps-line">
150
+ <span class="next-steps-label">Próximos passos:</span>
151
+ <span id="next-steps"></span>
152
+ </p>
144
153
  </section>
145
154
 
146
- <section class="card">
147
- <div class="section-head">
148
- <h2>Harnesses e configuração efetiva</h2>
149
- <span class="muted">versões, preferências futuras e overrides desta execução</span>
155
+ <!-- Contexto. Estável durante a execução, então densidade menor: é
156
+ referência para quando o estado acima não bastar. -->
157
+ <section class="card block block-context">
158
+ <h2>Contexto</h2>
159
+
160
+ <div class="columns">
161
+ <div class="block-part">
162
+ <h3>Issue</h3>
163
+ <div id="issue-summary"></div>
164
+ </div>
165
+ <div class="block-part">
166
+ <h3>Repositório</h3>
167
+ <div id="repository"></div>
168
+ </div>
169
+ </div>
170
+
171
+ <div class="block-part">
172
+ <h3>
173
+ Harnesses e configuração efetiva
174
+ <span class="muted block-hint">versões, preferências futuras e overrides desta execução</span>
175
+ </h3>
176
+ <div id="configuration"></div>
150
177
  </div>
151
- <div id="configuration"></div>
152
178
  </section>
153
179
 
154
- <div class="columns">
155
- <section class="card">
156
- <h2>Fases</h2>
180
+ <!-- Andamento: o caminho já percorrido, fase a fase e story a story. -->
181
+ <section class="card block block-progress">
182
+ <h2>Andamento</h2>
183
+
184
+ <div class="block-part">
185
+ <h3>Fases</h3>
157
186
  <ol id="phases" class="plain-list"></ol>
158
- </section>
159
- <section class="card">
160
- <h2>Próximos passos</h2>
161
- <div id="next-steps"></div>
162
- </section>
163
- </div>
187
+ </div>
164
188
 
165
- <section class="card">
166
- <h2>User stories</h2>
167
- <ol id="stories" class="plain-list"></ol>
189
+ <div class="block-part">
190
+ <h3>User stories</h3>
191
+ <ol id="stories" class="plain-list"></ol>
192
+ </div>
168
193
  </section>
169
194
 
170
- <div class="columns">
171
- <section class="card">
172
- <h2>Commits</h2>
173
- <ul id="commits" class="plain-list"></ul>
174
- </section>
175
- <section class="card">
176
- <h2>Pull requests</h2>
177
- <ul id="pull-requests" class="plain-list"></ul>
178
- </section>
179
- </div>
195
+ <!-- Saída: o que a execução produziu no repositório e no log. -->
196
+ <section class="card block block-output">
197
+ <h2>Saída</h2>
180
198
 
181
- <section class="card">
182
- <div class="section-head">
183
- <h2>Logs recentes</h2>
184
- <label class="log-filter-control">
185
- Nível
186
- <select id="log-filter" aria-label="Filtro por nível de log">
187
- <option value="all">todos</option>
188
- <option value="info">info</option>
189
- <option value="warn">avisos</option>
190
- <option value="error">erros</option>
191
- </select>
192
- </label>
199
+ <div class="columns">
200
+ <div class="block-part">
201
+ <h3>Commits</h3>
202
+ <ul id="commits" class="plain-list"></ul>
203
+ </div>
204
+ <div class="block-part">
205
+ <h3>Pull requests</h3>
206
+ <ul id="pull-requests" class="plain-list"></ul>
207
+ </div>
208
+ </div>
209
+
210
+ <div class="block-part">
211
+ <div class="section-head">
212
+ <h3>Logs recentes</h3>
213
+ <label class="log-filter-control">
214
+ Nível
215
+ <select id="log-filter" aria-label="Filtro por nível de log">
216
+ <option value="all">todos</option>
217
+ <option value="info">info</option>
218
+ <option value="warn">avisos</option>
219
+ <option value="error">erros</option>
220
+ </select>
221
+ </label>
222
+ </div>
223
+ <ol id="logs" class="plain-list logs"></ol>
193
224
  </div>
194
- <ol id="logs" class="plain-list logs"></ol>
195
225
  </section>
196
226
  </div>
197
227
 
198
228
  <div class="tab-panel" id="panel-kanban" role="tabpanel" aria-labelledby="tab-kanban" tabindex="0" hidden>
199
229
  <section class="card">
200
- <h2>Kanban de user stories</h2>
201
230
  <div id="kanban" class="kanban"></div>
202
231
  </section>
203
232
  </div>
@@ -205,7 +234,6 @@
205
234
  <div class="tab-panel" id="panel-history" role="tabpanel" aria-labelledby="tab-history" tabindex="0" hidden>
206
235
  <section class="card">
207
236
  <div class="section-head">
208
- <h2>Histórico da execução</h2>
209
237
  <label class="log-filter-control">
210
238
  Evento
211
239
  <select id="history-filter" aria-label="Filtro do histórico">
@@ -229,7 +257,7 @@
229
257
  <aside id="drawer" class="drawer" role="dialog" aria-modal="true"
230
258
  aria-labelledby="drawer-title" tabindex="-1" hidden>
231
259
  <div class="drawer-head">
232
- <h2 id="drawer-title">Detalhes da user story</h2>
260
+ <h2 id="drawer-title"></h2>
233
261
  <button type="button" id="drawer-close" class="drawer-close" aria-label="Fechar detalhes">✕</button>
234
262
  </div>
235
263
  <div id="drawer-body" class="drawer-body"></div>