trackfw 2.5.4 → 2.7.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.
@@ -0,0 +1,199 @@
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>trackfw — dashboard</title>
7
+ <!-- Tailwind CSS CDN -->
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <!-- HTMX -->
10
+ <script src="https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js"></script>
11
+ <!-- marked.js -->
12
+ <script src="https://cdn.jsdelivr.net/npm/marked@12.0.0/marked.min.js"></script>
13
+ <!-- Chart.js -->
14
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js"></script>
15
+ <!-- D3.js -->
16
+ <script src="https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js"></script>
17
+ <link rel="stylesheet" href="/static/style.css" />
18
+ </head>
19
+ <body class="bg-gray-50 text-gray-900 min-h-screen flex flex-col">
20
+
21
+ <!-- Header / Nav -->
22
+ <header class="bg-white border-b border-gray-200 px-6 py-3 flex items-center gap-6 shadow-sm">
23
+ <span class="font-bold text-lg tracking-tight text-blue-600">trackfw</span>
24
+ <nav class="flex gap-2" role="navigation" aria-label="Navegação principal">
25
+ <button
26
+ id="tab-board"
27
+ onclick="switchView('board')"
28
+ class="tab-btn px-4 py-1.5 rounded text-sm font-medium transition-colors"
29
+ aria-pressed="true"
30
+ >Board</button>
31
+ <button
32
+ id="tab-chain"
33
+ onclick="switchView('chain')"
34
+ class="tab-btn px-4 py-1.5 rounded text-sm font-medium transition-colors"
35
+ aria-pressed="false"
36
+ >Chain</button>
37
+ <button
38
+ id="tab-metrics"
39
+ onclick="switchView('metrics')"
40
+ class="tab-btn px-4 py-1.5 rounded text-sm font-medium transition-colors"
41
+ aria-pressed="false"
42
+ >Metrics</button>
43
+ </nav>
44
+ </header>
45
+
46
+ <!-- Conteúdo principal -->
47
+ <main class="flex-1 relative overflow-hidden flex">
48
+
49
+ <!-- View: Board -->
50
+ <section id="view-board" class="view-section w-full p-6 overflow-auto" aria-label="Board kanban">
51
+
52
+ <!-- Filtro por agente -->
53
+ <div id="agent-filter-container" class="mb-4 hidden">
54
+ <label for="agent-filter" class="text-sm font-medium text-gray-600 mr-2">Filtrar por agente:</label>
55
+ <select
56
+ id="agent-filter"
57
+ onchange="filterByAgent(this.value)"
58
+ class="text-sm border border-gray-300 rounded px-2 py-1 bg-white"
59
+ aria-label="Filtrar roadmaps por agente"
60
+ >
61
+ <option value="">Todos</option>
62
+ </select>
63
+ </div>
64
+
65
+ <!-- Kanban columns -->
66
+ <div id="board-container" class="flex gap-4 min-w-max">
67
+ <!-- Spinner de loading inicial -->
68
+ <div id="board-loading" class="flex items-center gap-2 text-gray-500 text-sm">
69
+ <svg class="animate-spin h-4 w-4 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true">
70
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
71
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"></path>
72
+ </svg>
73
+ Carregando board...
74
+ </div>
75
+ </div>
76
+
77
+ <!-- Mensagem de erro Board -->
78
+ <div id="board-error" class="hidden text-red-600 text-sm mt-4" role="alert"></div>
79
+ </section>
80
+
81
+ <!-- View: Chain -->
82
+ <section id="view-chain" class="view-section w-full hidden" aria-label="Grafo de cadeia ADR-REQ-ROADMAP">
83
+ <div id="chain-loading" class="flex items-center gap-2 text-gray-500 text-sm p-6">
84
+ <svg class="animate-spin h-4 w-4 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true">
85
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
86
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"></path>
87
+ </svg>
88
+ Carregando cadeia...
89
+ </div>
90
+ <div id="chain-error" class="hidden text-red-600 text-sm p-6" role="alert"></div>
91
+ <svg id="chain-svg" class="w-full" style="height: calc(100vh - 60px);"></svg>
92
+ </section>
93
+
94
+ <!-- View: Metrics -->
95
+ <section id="view-metrics" class="view-section w-full hidden p-6 overflow-auto" aria-label="Métricas do projeto">
96
+ <div id="metrics-loading" class="flex items-center gap-2 text-gray-500 text-sm">
97
+ <svg class="animate-spin h-4 w-4 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true">
98
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
99
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"></path>
100
+ </svg>
101
+ Carregando métricas...
102
+ </div>
103
+ <div id="metrics-error" class="hidden text-red-600 text-sm" role="alert"></div>
104
+
105
+ <!-- KPI cards -->
106
+ <div id="kpi-cards" class="hidden grid grid-cols-1 sm:grid-cols-3 gap-4 mb-8">
107
+ <div class="bg-white rounded-lg border border-gray-200 p-5 shadow-sm">
108
+ <p class="text-xs font-medium text-gray-500 uppercase tracking-wider mb-1">Lead Time Médio</p>
109
+ <p id="kpi-lead-time" class="text-2xl font-bold text-gray-900">—</p>
110
+ </div>
111
+ <div class="bg-white rounded-lg border border-gray-200 p-5 shadow-sm">
112
+ <p class="text-xs font-medium text-gray-500 uppercase tracking-wider mb-1">Cycle Time Médio</p>
113
+ <p id="kpi-cycle-time" class="text-2xl font-bold text-gray-900">—</p>
114
+ </div>
115
+ <div class="bg-white rounded-lg border border-gray-200 p-5 shadow-sm">
116
+ <p class="text-xs font-medium text-gray-500 uppercase tracking-wider mb-1">Taxa de Abandono</p>
117
+ <p id="kpi-abandonment" class="text-2xl font-bold text-gray-900">—</p>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- Gráficos -->
122
+ <div id="charts-container" class="hidden grid grid-cols-1 lg:grid-cols-2 gap-6">
123
+ <div class="bg-white rounded-lg border border-gray-200 p-5 shadow-sm">
124
+ <h2 class="text-sm font-semibold text-gray-700 mb-4">Distribuição por Estado</h2>
125
+ <div class="flex justify-center" style="height: 260px;">
126
+ <canvas id="donut-chart" aria-label="Gráfico donut: distribuição por estado"></canvas>
127
+ </div>
128
+ </div>
129
+ <div class="bg-white rounded-lg border border-gray-200 p-5 shadow-sm">
130
+ <h2 class="text-sm font-semibold text-gray-700 mb-4">Burndown</h2>
131
+ <div id="burndown-empty" class="hidden text-sm text-gray-500 flex items-center justify-center h-48">
132
+ Sem dados suficientes no .trackfw-log
133
+ </div>
134
+ <canvas id="burndown-chart" style="height: 240px;" aria-label="Gráfico de burndown: abertos e fechados ao longo do tempo"></canvas>
135
+ </div>
136
+ </div>
137
+ </section>
138
+
139
+ </main>
140
+
141
+ <!-- Overlay do drawer -->
142
+ <div
143
+ id="drawer-overlay"
144
+ class="fixed inset-0 bg-black bg-opacity-30 hidden z-40"
145
+ onclick="closeDrawer()"
146
+ aria-hidden="true"
147
+ ></div>
148
+
149
+ <!-- Drawer lateral -->
150
+ <aside
151
+ id="drawer"
152
+ class="fixed top-0 right-0 h-full bg-white shadow-2xl z-50 flex flex-col drawer-slide"
153
+ style="width: 40%; display: none;"
154
+ role="complementary"
155
+ aria-label="Detalhes do arquivo"
156
+ >
157
+ <!-- Header do drawer -->
158
+ <div class="flex items-center justify-between px-5 py-4 border-b border-gray-200 bg-gray-50">
159
+ <span id="drawer-filename" class="text-sm font-medium text-gray-700 truncate max-w-xs"></span>
160
+ <button
161
+ onclick="closeDrawer()"
162
+ class="text-gray-400 hover:text-gray-700 transition-colors p-1 rounded"
163
+ aria-label="Fechar drawer"
164
+ >
165
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
166
+ <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
167
+ </svg>
168
+ </button>
169
+ </div>
170
+
171
+ <!-- Loading do drawer -->
172
+ <div id="drawer-loading" class="flex items-center justify-center gap-2 text-gray-500 text-sm p-8 hidden">
173
+ <svg class="animate-spin h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true">
174
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
175
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"></path>
176
+ </svg>
177
+ Carregando arquivo...
178
+ </div>
179
+
180
+ <!-- Erro do drawer -->
181
+ <div id="drawer-error" class="hidden text-red-600 text-sm p-5" role="alert"></div>
182
+
183
+ <!-- Conteúdo do drawer -->
184
+ <div id="drawer-content" class="flex-1 overflow-y-auto p-5 hidden">
185
+ <!-- Frontmatter como tabela -->
186
+ <div id="drawer-frontmatter" class="mb-5 hidden">
187
+ <h3 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Metadados</h3>
188
+ <table id="frontmatter-table" class="w-full text-sm border border-gray-200 rounded overflow-hidden">
189
+ <tbody></tbody>
190
+ </table>
191
+ </div>
192
+ <!-- Markdown renderizado -->
193
+ <div id="drawer-markdown" class="prose prose-sm max-w-none"></div>
194
+ </div>
195
+ </aside>
196
+
197
+ <script src="/static/app.js"></script>
198
+ </body>
199
+ </html>
@@ -0,0 +1,153 @@
1
+ /* trackfw dashboard — estilos complementares ao Tailwind CDN */
2
+
3
+ /* Slide-in do drawer */
4
+ @keyframes slideIn {
5
+ from {
6
+ transform: translateX(100%);
7
+ opacity: 0;
8
+ }
9
+ to {
10
+ transform: translateX(0);
11
+ opacity: 1;
12
+ }
13
+ }
14
+
15
+ .drawer-slide {
16
+ animation: slideIn 0.22s cubic-bezier(0.16, 1, 0.3, 1) both;
17
+ }
18
+
19
+ /* Tabs ativas */
20
+ .tab-btn {
21
+ background-color: transparent;
22
+ color: #6b7280;
23
+ }
24
+
25
+ .tab-btn:hover {
26
+ background-color: #f3f4f6;
27
+ color: #111827;
28
+ }
29
+
30
+ .tab-btn.active {
31
+ background-color: #eff6ff;
32
+ color: #2563eb;
33
+ font-weight: 600;
34
+ }
35
+
36
+ /* Cards kanban */
37
+ .kanban-card {
38
+ cursor: pointer;
39
+ transition: box-shadow 0.15s ease, transform 0.1s ease;
40
+ }
41
+
42
+ .kanban-card:hover {
43
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
44
+ transform: translateY(-1px);
45
+ }
46
+
47
+ .kanban-card:focus-visible {
48
+ outline: 2px solid #3b82f6;
49
+ outline-offset: 2px;
50
+ }
51
+
52
+ /* Badge de estado por coluna */
53
+ .badge-wip {
54
+ background-color: #dbeafe;
55
+ color: #1d4ed8;
56
+ }
57
+
58
+ .badge-backlog {
59
+ background-color: #f3f4f6;
60
+ color: #374151;
61
+ }
62
+
63
+ .badge-blocked {
64
+ background-color: #fee2e2;
65
+ color: #991b1b;
66
+ }
67
+
68
+ .badge-done {
69
+ background-color: #dcfce7;
70
+ color: #15803d;
71
+ }
72
+
73
+ .badge-abandoned {
74
+ background-color: #f5f0eb;
75
+ color: #57534e;
76
+ }
77
+
78
+ /* Coluna de kanban */
79
+ .kanban-column {
80
+ min-width: 220px;
81
+ max-width: 280px;
82
+ flex: 1;
83
+ }
84
+
85
+ /* Markdown renderizado no drawer */
86
+ .prose h1 { font-size: 1.35rem; font-weight: 700; margin-bottom: 0.6rem; color: #111827; }
87
+ .prose h2 { font-size: 1.1rem; font-weight: 600; margin-top: 1.2rem; margin-bottom: 0.4rem; color: #1f2937; }
88
+ .prose h3 { font-size: 0.95rem; font-weight: 600; margin-top: 1rem; margin-bottom: 0.3rem; color: #374151; }
89
+ .prose p { margin-bottom: 0.75rem; line-height: 1.6; color: #374151; }
90
+ .prose ul { list-style: disc; padding-left: 1.25rem; margin-bottom: 0.75rem; }
91
+ .prose ol { list-style: decimal; padding-left: 1.25rem; margin-bottom: 0.75rem; }
92
+ .prose li { margin-bottom: 0.2rem; line-height: 1.5; color: #374151; }
93
+ .prose code {
94
+ background-color: #f3f4f6;
95
+ border-radius: 3px;
96
+ padding: 0.1em 0.3em;
97
+ font-size: 0.85em;
98
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
99
+ color: #be185d;
100
+ }
101
+ .prose pre {
102
+ background-color: #1f2937;
103
+ color: #f9fafb;
104
+ border-radius: 6px;
105
+ padding: 0.85rem 1rem;
106
+ overflow-x: auto;
107
+ margin-bottom: 0.75rem;
108
+ font-size: 0.82rem;
109
+ }
110
+ .prose pre code {
111
+ background: none;
112
+ color: inherit;
113
+ padding: 0;
114
+ }
115
+ .prose a { color: #2563eb; text-decoration: underline; }
116
+ .prose a:hover { color: #1d4ed8; }
117
+ .prose blockquote {
118
+ border-left: 3px solid #d1d5db;
119
+ padding-left: 0.9rem;
120
+ margin-left: 0;
121
+ color: #6b7280;
122
+ font-style: italic;
123
+ }
124
+ .prose table { width: 100%; border-collapse: collapse; margin-bottom: 0.75rem; font-size: 0.85rem; }
125
+ .prose th { background-color: #f9fafb; border: 1px solid #e5e7eb; padding: 0.4rem 0.6rem; text-align: left; font-weight: 600; }
126
+ .prose td { border: 1px solid #e5e7eb; padding: 0.35rem 0.6rem; }
127
+ .prose hr { border: none; border-top: 1px solid #e5e7eb; margin: 1rem 0; }
128
+
129
+ /* Tabela de frontmatter */
130
+ #frontmatter-table tr:nth-child(even) td { background-color: #f9fafb; }
131
+ #frontmatter-table td { border: 1px solid #e5e7eb; padding: 0.35rem 0.6rem; }
132
+ #frontmatter-table td:first-child { font-weight: 600; color: #374151; white-space: nowrap; width: 40%; }
133
+ #frontmatter-table td:last-child { color: #4b5563; word-break: break-word; }
134
+
135
+ /* D3 chain graph */
136
+ #chain-svg .node-label {
137
+ font-size: 11px;
138
+ fill: #1f2937;
139
+ pointer-events: none;
140
+ user-select: none;
141
+ }
142
+
143
+ #chain-svg .link-line {
144
+ stroke: #d1d5db;
145
+ stroke-width: 1.5;
146
+ }
147
+
148
+ /* Responsivo: drawer 100% em mobile */
149
+ @media (max-width: 767px) {
150
+ #drawer {
151
+ width: 100% !important;
152
+ }
153
+ }
@@ -818,11 +818,12 @@ async function validateUnfiltered() {
818
818
  applyRule('filename_uniqueness', validateFilenameUniqueness(), violations, warnings)
819
819
  applyRule('blocked_by_draft_adr', validateREQsNotBlockedByDraftADRs(), violations, warnings)
820
820
 
821
- // Regras diretas (sem configuração de severidade): violations sempre
822
- // Popular _itemMeta manualmente para manter rastreabilidade no JSON
823
- for (const msg of validateREQsHaveADR()) { _setMeta(msg, 'req_has_adr'); violations.push(msg) }
824
- for (const msg of validateBlockedHasREQ()) { _setMeta(msg, 'blocked_has_req'); violations.push(msg) }
825
- for (const msg of validateREQsHaveRoadmap()) { _setMeta(msg, 'req_has_roadmap'); violations.push(msg) }
821
+ // Regras configuráveis via applyRule (popula _itemMeta automaticamente)
822
+ applyRule('req_has_adr', validateREQsHaveADR(), violations, warnings)
823
+ applyRule('blocked_has_req', validateBlockedHasREQ(), violations, warnings)
824
+ applyRule('req_has_roadmap', validateREQsHaveRoadmap(), violations, warnings)
825
+
826
+ // Regra direta (sem configuração de severidade): violation sempre
826
827
  for (const msg of validateFrontmatterPresence()) { _setMeta(msg, 'frontmatter_presence'); violations.push(msg) }
827
828
 
828
829
  // warnings diretos do WIP limit (não configuráveis)