oficina-opencode 3.0.5 → 3.1.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 +240 -208
- package/commands/oficina.md +7 -6
- package/dashboard/.dom-check.html +1257 -0
- package/dashboard/README-DASHBOARD.md +64 -0
- package/dashboard/assets/factory.css +1543 -0
- package/dashboard/assets/tokens.css +208 -0
- package/dashboard/build-kanban-data.mjs +279 -0
- package/dashboard/dashboard.css +753 -0
- package/dashboard/dashboard.html +121 -0
- package/dashboard/dashboard.js +610 -0
- package/dashboard/data/snapshot.json +883 -0
- package/dashboard/debate.html +134 -0
- package/dashboard/factory/build-snapshot.mjs +89 -0
- package/dashboard/factory/factory-app.js +1185 -0
- package/dashboard/factory/factory-core.js +541 -0
- package/dashboard/factory/snapshot.js +850 -0
- package/dashboard/factory.html +350 -0
- package/dashboard/kanban-data.json +671 -0
- package/dashboard/kanban-source.js +643 -0
- package/dashboard/kanban.check.mjs +454 -0
- package/dashboard/kanban.html +1257 -0
- package/dashboard/leadership.html +1008 -0
- package/dashboard/lib/debate-app.js +1149 -0
- package/dashboard/lib/debate-core.js +698 -0
- package/dashboard/lib/debate-data.js +163 -0
- package/dashboard/lib/debate-demo.js +110 -0
- package/dashboard/lib/debate.css +1155 -0
- package/dashboard/lib/leadership-model.js +946 -0
- package/dashboard/probe.mjs +35 -0
- package/dashboard/scripts/serve.mjs +122 -0
- package/dashboard/scripts/snapshot-data.mjs +93 -0
- package/dashboard/scripts/snapshot.mjs +28 -0
- package/dashboard/server.mjs +242 -0
- package/dashboard/shot.mjs +54 -0
- package/dashboard/shots/desktop-gates.png +0 -0
- package/dashboard/shots/desktop-light.png +0 -0
- package/dashboard/shots/desktop-real.png +0 -0
- package/dashboard/shots/mobile.png +0 -0
- package/dashboard/test/factory-core.test.mjs +244 -0
- package/dashboard/tests/debate-core.test.mjs +336 -0
- package/dashboard/tests/leadership-model.test.mjs +404 -0
- package/dist/cli.js +148 -26
- package/dist/cli.js.map +1 -1
- package/dist/core/bus.js +3 -3
- package/dist/core/bus.js.map +1 -1
- package/dist/core/catalog.d.ts +9 -1
- package/dist/core/catalog.d.ts.map +1 -1
- package/dist/core/catalog.js +77 -2
- package/dist/core/catalog.js.map +1 -1
- package/dist/core/dashboard.d.ts +33 -0
- package/dist/core/dashboard.d.ts.map +1 -0
- package/dist/core/dashboard.js +115 -0
- package/dist/core/dashboard.js.map +1 -0
- package/dist/core/dispatch.d.ts +1 -1
- package/dist/core/dispatch.d.ts.map +1 -1
- package/dist/core/dispatch.js +84 -12
- package/dist/core/dispatch.js.map +1 -1
- package/dist/core/docsync.d.ts +0 -9
- package/dist/core/docsync.d.ts.map +1 -1
- package/dist/core/docsync.js +12 -15
- package/dist/core/docsync.js.map +1 -1
- package/dist/core/environment.d.ts +28 -0
- package/dist/core/environment.d.ts.map +1 -0
- package/dist/core/environment.js +80 -0
- package/dist/core/environment.js.map +1 -0
- package/dist/core/factory.d.ts +16 -0
- package/dist/core/factory.d.ts.map +1 -1
- package/dist/core/factory.js +143 -0
- package/dist/core/factory.js.map +1 -1
- package/dist/core/fsx.d.ts +20 -2
- package/dist/core/fsx.d.ts.map +1 -1
- package/dist/core/fsx.js +10 -6
- package/dist/core/fsx.js.map +1 -1
- package/dist/core/gates.d.ts +23 -1
- package/dist/core/gates.d.ts.map +1 -1
- package/dist/core/gates.js +83 -8
- package/dist/core/gates.js.map +1 -1
- package/dist/core/leases.d.ts.map +1 -1
- package/dist/core/leases.js +4 -1
- package/dist/core/leases.js.map +1 -1
- package/dist/core/mesa.d.ts +1 -6
- package/dist/core/mesa.d.ts.map +1 -1
- package/dist/core/mesa.js +1 -6
- package/dist/core/mesa.js.map +1 -1
- package/dist/core/office.d.ts +3 -29
- package/dist/core/office.d.ts.map +1 -1
- package/dist/core/office.js +77 -90
- package/dist/core/office.js.map +1 -1
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/paths.js +6 -1
- package/dist/core/paths.js.map +1 -1
- package/dist/core/retry.d.ts +2 -8
- package/dist/core/retry.d.ts.map +1 -1
- package/dist/core/retry.js +3 -34
- package/dist/core/retry.js.map +1 -1
- package/dist/core/roster.js +3 -3
- package/dist/core/roster.js.map +1 -1
- package/dist/core/sandbox.d.ts +38 -1
- package/dist/core/sandbox.d.ts.map +1 -1
- package/dist/core/sandbox.js +119 -15
- package/dist/core/sandbox.js.map +1 -1
- package/dist/core/state.d.ts +2 -2
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/state.js +5 -33
- package/dist/core/state.js.map +1 -1
- package/dist/core/stores.d.ts +3 -13
- package/dist/core/stores.d.ts.map +1 -1
- package/dist/core/stores.js +8 -32
- package/dist/core/stores.js.map +1 -1
- package/dist/core/systemPrompt.d.ts.map +1 -1
- package/dist/core/systemPrompt.js +4 -1
- package/dist/core/systemPrompt.js.map +1 -1
- package/dist/core/tripwire.d.ts +22 -8
- package/dist/core/tripwire.d.ts.map +1 -1
- package/dist/core/tripwire.js +71 -26
- package/dist/core/tripwire.js.map +1 -1
- package/dist/core/types.d.ts +6 -14
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +11 -3
- package/dist/core/types.js.map +1 -1
- package/dist/hooks/scopeGuard.d.ts +15 -1
- package/dist/hooks/scopeGuard.d.ts.map +1 -1
- package/dist/hooks/scopeGuard.js +88 -73
- package/dist/hooks/scopeGuard.js.map +1 -1
- package/dist/tools/index.d.ts +9 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +29 -81
- package/dist/tools/index.js.bak +972 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/v1.d.ts.map +1 -1
- package/dist/v1.js +30 -36
- package/dist/v1.js.map +1 -1
- package/dist/v2.d.ts +5 -0
- package/dist/v2.d.ts.map +1 -1
- package/dist/v2.js +79 -46
- package/dist/v2.js.bak +517 -0
- package/dist/v2.js.map +1 -1
- package/docs/GARANTIAS.md +2 -2
- package/docs/PRUEBA-E2E.md +4 -4
- package/docs/QUE-ES.md +4 -5
- package/docs/SEGURIDAD-REPO.md +83 -0
- package/docs/assets/banner.svg +42 -0
- package/docs/catalog-facts.json +3 -3
- package/docs/superpowers/specs/2026-10-29-factory-skill-creator-and-remove-budget.md +182 -0
- package/package.json +14 -6
|
@@ -0,0 +1,1257 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="es" data-theme="dark"><head>
|
|
3
|
+
<meta charset="utf-8">
|
|
4
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
5
|
+
<title>Kanban ÔÇö Oficina de Agents</title>
|
|
6
|
+
<meta name="description" content="Tablero Kanban read-only de las cards de la Oficina: columnas por estado, criterios de aceptaci├│n, gates y evidencia.">
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #0B0D10;
|
|
10
|
+
--surface: #14181D;
|
|
11
|
+
--surface-2: #101419;
|
|
12
|
+
--border: #232A32;
|
|
13
|
+
--border-strong: #2F3945;
|
|
14
|
+
--text: #E6EAF0;
|
|
15
|
+
--muted: #8B97A6;
|
|
16
|
+
--accent: #4ADE80;
|
|
17
|
+
--warn: #FBBF24;
|
|
18
|
+
--danger: #F87171;
|
|
19
|
+
--radius: 4px;
|
|
20
|
+
--sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
21
|
+
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
22
|
+
--ease-in: cubic-bezier(0.16, 1, 0.3, 1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
* { box-sizing: border-box; }
|
|
26
|
+
|
|
27
|
+
html, body { height: 100%; }
|
|
28
|
+
|
|
29
|
+
body {
|
|
30
|
+
margin: 0;
|
|
31
|
+
background: var(--bg);
|
|
32
|
+
color: var(--text);
|
|
33
|
+
font-family: var(--sans);
|
|
34
|
+
font-size: 14px;
|
|
35
|
+
line-height: 1.5;
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.sr-only {
|
|
41
|
+
position: absolute;
|
|
42
|
+
width: 1px;
|
|
43
|
+
height: 1px;
|
|
44
|
+
padding: 0;
|
|
45
|
+
margin: -1px;
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
clip: rect(0 0 0 0);
|
|
48
|
+
white-space: nowrap;
|
|
49
|
+
border: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* ÔöÇÔöÇ Header ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇ */
|
|
53
|
+
|
|
54
|
+
.app-head {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-wrap: wrap;
|
|
57
|
+
align-items: baseline;
|
|
58
|
+
gap: 8px 20px;
|
|
59
|
+
padding: 16px 20px 12px;
|
|
60
|
+
border-bottom: 1px solid var(--border);
|
|
61
|
+
background: var(--surface-2);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.app-head h1 {
|
|
65
|
+
margin: 0;
|
|
66
|
+
font-size: 18px;
|
|
67
|
+
font-weight: 700;
|
|
68
|
+
letter-spacing: -0.02em;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.app-head .run-meta {
|
|
72
|
+
color: var(--muted);
|
|
73
|
+
font-family: var(--mono);
|
|
74
|
+
font-size: 11px;
|
|
75
|
+
letter-spacing: 0.02em;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.app-head .run-meta strong { color: var(--text); font-weight: 600; }
|
|
79
|
+
|
|
80
|
+
.counter {
|
|
81
|
+
display: inline-flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
gap: 6px;
|
|
84
|
+
font-family: var(--mono);
|
|
85
|
+
font-size: 11px;
|
|
86
|
+
color: var(--muted);
|
|
87
|
+
font-variant-numeric: tabular-nums;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.counter strong { color: var(--text); font-weight: 600; }
|
|
91
|
+
|
|
92
|
+
/* ÔöÇÔöÇ Toolbar ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇ */
|
|
93
|
+
|
|
94
|
+
.toolbar {
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-wrap: wrap;
|
|
97
|
+
align-items: end;
|
|
98
|
+
gap: 10px 14px;
|
|
99
|
+
padding: 10px 20px;
|
|
100
|
+
border-bottom: 1px solid var(--border);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.field { display: flex; flex-direction: column; gap: 4px; }
|
|
104
|
+
.field > label {
|
|
105
|
+
font-family: var(--mono);
|
|
106
|
+
font-size: 10px;
|
|
107
|
+
letter-spacing: 0.12em;
|
|
108
|
+
text-transform: uppercase;
|
|
109
|
+
color: var(--muted);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.field select,
|
|
113
|
+
.field input {
|
|
114
|
+
background: var(--surface);
|
|
115
|
+
color: var(--text);
|
|
116
|
+
border: 1px solid var(--border-strong);
|
|
117
|
+
border-radius: var(--radius);
|
|
118
|
+
padding: 6px 8px;
|
|
119
|
+
font-family: var(--mono);
|
|
120
|
+
font-size: 12px;
|
|
121
|
+
min-width: 150px;
|
|
122
|
+
transition: border-color 220ms var(--ease-in);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.field select:hover,
|
|
126
|
+
.field input:hover { border-color: var(--muted); }
|
|
127
|
+
|
|
128
|
+
.field select:focus-visible,
|
|
129
|
+
.field input:focus-visible,
|
|
130
|
+
.btn:focus-visible,
|
|
131
|
+
.card:focus-visible {
|
|
132
|
+
outline: 2px solid var(--accent);
|
|
133
|
+
outline-offset: 2px;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.btn {
|
|
137
|
+
background: var(--surface);
|
|
138
|
+
color: var(--text);
|
|
139
|
+
border: 1px solid var(--border-strong);
|
|
140
|
+
border-radius: var(--radius);
|
|
141
|
+
padding: 6px 10px;
|
|
142
|
+
font-family: var(--mono);
|
|
143
|
+
font-size: 11px;
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
transition: border-color 220ms var(--ease-in), transform 80ms var(--ease-in);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.btn:hover { border-color: var(--muted); }
|
|
149
|
+
.btn:active { transform: scale(0.97); }
|
|
150
|
+
.btn--ghost { color: var(--muted); }
|
|
151
|
+
|
|
152
|
+
.toolbar-note {
|
|
153
|
+
margin-left: auto;
|
|
154
|
+
font-family: var(--mono);
|
|
155
|
+
font-size: 11px;
|
|
156
|
+
color: var(--muted);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.toolbar-note .source { color: var(--text); }
|
|
160
|
+
|
|
161
|
+
/* ÔöÇÔöÇ Board ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇ */
|
|
162
|
+
|
|
163
|
+
.board {
|
|
164
|
+
flex: 1;
|
|
165
|
+
display: grid;
|
|
166
|
+
grid-auto-flow: column;
|
|
167
|
+
grid-auto-columns: minmax(250px, 1fr);
|
|
168
|
+
gap: 12px;
|
|
169
|
+
padding: 14px 20px 20px;
|
|
170
|
+
overflow-x: auto;
|
|
171
|
+
align-items: start;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.column {
|
|
175
|
+
background: var(--surface-2);
|
|
176
|
+
border: 1px solid var(--border);
|
|
177
|
+
border-radius: var(--radius);
|
|
178
|
+
display: flex;
|
|
179
|
+
flex-direction: column;
|
|
180
|
+
min-height: 160px;
|
|
181
|
+
max-height: calc(100vh - 210px);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.column-head {
|
|
185
|
+
display: flex;
|
|
186
|
+
align-items: center;
|
|
187
|
+
justify-content: space-between;
|
|
188
|
+
gap: 8px;
|
|
189
|
+
padding: 8px 10px;
|
|
190
|
+
border-bottom: 1px solid var(--border);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.column-title {
|
|
194
|
+
margin: 0;
|
|
195
|
+
font-family: var(--mono);
|
|
196
|
+
font-size: 11px;
|
|
197
|
+
font-weight: 600;
|
|
198
|
+
letter-spacing: 0.12em;
|
|
199
|
+
color: var(--text);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.column[data-state="done"] .column-title { color: var(--accent); }
|
|
203
|
+
.column[data-state="blocked"] .column-title,
|
|
204
|
+
.column[data-state="escalated"] .column-title,
|
|
205
|
+
.column[data-state="rework"] .column-title { color: var(--warn); }
|
|
206
|
+
|
|
207
|
+
.column-count {
|
|
208
|
+
font-family: var(--mono);
|
|
209
|
+
font-size: 11px;
|
|
210
|
+
color: var(--muted);
|
|
211
|
+
font-variant-numeric: tabular-nums;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.column-body {
|
|
215
|
+
display: flex;
|
|
216
|
+
flex-direction: column;
|
|
217
|
+
gap: 8px;
|
|
218
|
+
padding: 8px;
|
|
219
|
+
overflow-y: auto;
|
|
220
|
+
min-height: 88px;
|
|
221
|
+
transition: background-color 220ms var(--ease-in), box-shadow 220ms var(--ease-in);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.column-body.drop-target {
|
|
225
|
+
background: rgba(74, 222, 128, 0.06);
|
|
226
|
+
box-shadow: inset 0 0 0 1px var(--accent);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.column-empty {
|
|
230
|
+
margin: 6px 2px;
|
|
231
|
+
font-family: var(--mono);
|
|
232
|
+
font-size: 11px;
|
|
233
|
+
color: var(--muted);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.column-hint { color: var(--border-strong); }
|
|
237
|
+
|
|
238
|
+
/* ÔöÇÔöÇ Card ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇ */
|
|
239
|
+
|
|
240
|
+
.card {
|
|
241
|
+
background: var(--surface);
|
|
242
|
+
border: 1px solid var(--border);
|
|
243
|
+
border-radius: var(--radius);
|
|
244
|
+
padding: 9px 10px;
|
|
245
|
+
cursor: pointer;
|
|
246
|
+
transition: border-color 220ms var(--ease-in), transform 80ms var(--ease-in), opacity 220ms var(--ease-in);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.card:hover { border-color: var(--border-strong); }
|
|
250
|
+
.card:active { transform: scale(0.99); }
|
|
251
|
+
.card.dragging { opacity: 0.45; }
|
|
252
|
+
|
|
253
|
+
.card[data-moved="true"] { border-color: var(--warn); }
|
|
254
|
+
|
|
255
|
+
.card-top {
|
|
256
|
+
display: flex;
|
|
257
|
+
align-items: center;
|
|
258
|
+
justify-content: space-between;
|
|
259
|
+
gap: 8px;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.card-id {
|
|
263
|
+
font-family: var(--mono);
|
|
264
|
+
font-size: 11px;
|
|
265
|
+
font-weight: 600;
|
|
266
|
+
letter-spacing: 0.04em;
|
|
267
|
+
color: var(--text);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.badge {
|
|
271
|
+
font-family: var(--mono);
|
|
272
|
+
font-size: 10px;
|
|
273
|
+
letter-spacing: 0.06em;
|
|
274
|
+
padding: 1px 5px;
|
|
275
|
+
border: 1px solid var(--border-strong);
|
|
276
|
+
border-radius: var(--radius);
|
|
277
|
+
color: var(--muted);
|
|
278
|
+
text-transform: lowercase;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.badge--type { color: var(--muted); }
|
|
282
|
+
|
|
283
|
+
.badge--state { text-transform: uppercase; color: var(--text); }
|
|
284
|
+
.badge--state[data-state="done"] { color: var(--accent); border-color: var(--accent); }
|
|
285
|
+
.badge--state[data-state="blocked"],
|
|
286
|
+
.badge--state[data-state="rework"],
|
|
287
|
+
.badge--state[data-state="escalated"] { color: var(--warn); border-color: var(--warn); }
|
|
288
|
+
|
|
289
|
+
.card-title {
|
|
290
|
+
margin: 5px 0 7px;
|
|
291
|
+
font-size: 12.5px;
|
|
292
|
+
font-weight: 600;
|
|
293
|
+
line-height: 1.35;
|
|
294
|
+
letter-spacing: -0.01em;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.card-people {
|
|
298
|
+
display: flex;
|
|
299
|
+
align-items: center;
|
|
300
|
+
gap: 6px;
|
|
301
|
+
margin-bottom: 7px;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.person {
|
|
305
|
+
display: inline-flex;
|
|
306
|
+
align-items: center;
|
|
307
|
+
gap: 4px;
|
|
308
|
+
min-width: 0;
|
|
309
|
+
font-family: var(--mono);
|
|
310
|
+
font-size: 10px;
|
|
311
|
+
color: var(--muted);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.person--reviewer { opacity: 0.85; }
|
|
315
|
+
.person--reviewer .avatar { outline: 1px dashed var(--border-strong); outline-offset: 1px; }
|
|
316
|
+
|
|
317
|
+
.person-name {
|
|
318
|
+
max-width: 78px;
|
|
319
|
+
overflow: hidden;
|
|
320
|
+
text-overflow: ellipsis;
|
|
321
|
+
white-space: nowrap;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.person--unassigned .person-name { color: var(--border-strong); font-style: italic; }
|
|
325
|
+
|
|
326
|
+
.avatar {
|
|
327
|
+
display: inline-flex;
|
|
328
|
+
align-items: center;
|
|
329
|
+
justify-content: center;
|
|
330
|
+
width: 18px;
|
|
331
|
+
height: 18px;
|
|
332
|
+
border-radius: 50%;
|
|
333
|
+
background: hsl(var(--avatar-hue, 210) 26% 24%);
|
|
334
|
+
color: var(--text);
|
|
335
|
+
font-family: var(--mono);
|
|
336
|
+
font-size: 8.5px;
|
|
337
|
+
font-weight: 600;
|
|
338
|
+
letter-spacing: 0.02em;
|
|
339
|
+
flex: none;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.avatar--empty {
|
|
343
|
+
background: transparent;
|
|
344
|
+
border: 1px dashed var(--border-strong);
|
|
345
|
+
color: var(--border-strong);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.rejections {
|
|
349
|
+
margin-left: auto;
|
|
350
|
+
font-family: var(--mono);
|
|
351
|
+
font-size: 10px;
|
|
352
|
+
color: var(--muted);
|
|
353
|
+
font-variant-numeric: tabular-nums;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.rejections[data-rejections="0"] { color: var(--border-strong); }
|
|
357
|
+
|
|
358
|
+
.checklist {
|
|
359
|
+
list-style: none;
|
|
360
|
+
margin: 0 0 7px;
|
|
361
|
+
padding: 0;
|
|
362
|
+
display: flex;
|
|
363
|
+
flex-direction: column;
|
|
364
|
+
gap: 3px;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.check-item {
|
|
368
|
+
display: flex;
|
|
369
|
+
gap: 5px;
|
|
370
|
+
align-items: flex-start;
|
|
371
|
+
font-size: 11px;
|
|
372
|
+
line-height: 1.35;
|
|
373
|
+
color: var(--muted);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.check-box {
|
|
377
|
+
font-family: var(--mono);
|
|
378
|
+
font-size: 10px;
|
|
379
|
+
width: 11px;
|
|
380
|
+
flex: none;
|
|
381
|
+
text-align: center;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.check-item.is-checked .check-box { color: var(--accent); }
|
|
385
|
+
.check-item.is-checked .check-text { color: var(--text); }
|
|
386
|
+
.check-item.is-failed .check-box { color: var(--danger); }
|
|
387
|
+
.check-item.is-failed .check-text { color: var(--danger); }
|
|
388
|
+
|
|
389
|
+
.check-text {
|
|
390
|
+
display: -webkit-box;
|
|
391
|
+
-webkit-line-clamp: 2;
|
|
392
|
+
-webkit-box-orient: vertical;
|
|
393
|
+
overflow: hidden;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.check-more {
|
|
397
|
+
font-family: var(--mono);
|
|
398
|
+
font-size: 10px;
|
|
399
|
+
color: var(--border-strong);
|
|
400
|
+
padding-left: 16px;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.check-progress {
|
|
404
|
+
display: flex;
|
|
405
|
+
align-items: center;
|
|
406
|
+
gap: 6px;
|
|
407
|
+
padding-left: 16px;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.check-progress-label {
|
|
411
|
+
font-family: var(--mono);
|
|
412
|
+
font-size: 9.5px;
|
|
413
|
+
color: var(--muted);
|
|
414
|
+
white-space: nowrap;
|
|
415
|
+
font-variant-numeric: tabular-nums;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.check-progress-track {
|
|
419
|
+
flex: 1;
|
|
420
|
+
height: 3px;
|
|
421
|
+
background: var(--border);
|
|
422
|
+
border-radius: 2px;
|
|
423
|
+
overflow: hidden;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.check-progress-fill {
|
|
427
|
+
display: block;
|
|
428
|
+
height: 100%;
|
|
429
|
+
background: var(--accent);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.checklist-empty {
|
|
433
|
+
margin: 0 0 7px;
|
|
434
|
+
font-family: var(--mono);
|
|
435
|
+
font-size: 10px;
|
|
436
|
+
color: var(--border-strong);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.card-gates {
|
|
440
|
+
display: flex;
|
|
441
|
+
flex-wrap: wrap;
|
|
442
|
+
gap: 4px;
|
|
443
|
+
padding-top: 7px;
|
|
444
|
+
border-top: 1px solid var(--border);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.gate-badge {
|
|
448
|
+
display: inline-flex;
|
|
449
|
+
align-items: center;
|
|
450
|
+
gap: 4px;
|
|
451
|
+
font-family: var(--mono);
|
|
452
|
+
font-size: 9.5px;
|
|
453
|
+
letter-spacing: 0.04em;
|
|
454
|
+
padding: 1px 5px;
|
|
455
|
+
border-radius: var(--radius);
|
|
456
|
+
border: 1px solid currentColor;
|
|
457
|
+
white-space: nowrap;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.gate-badge .gate-key { opacity: 0.65; }
|
|
461
|
+
|
|
462
|
+
.gate-badge--pass { color: var(--accent); }
|
|
463
|
+
.gate-badge--fail { color: var(--danger); }
|
|
464
|
+
.gate-badge--not-run { color: var(--warn); }
|
|
465
|
+
.gate-badge--na { color: var(--muted); }
|
|
466
|
+
|
|
467
|
+
/* ÔöÇÔöÇ Modal ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇ */
|
|
468
|
+
|
|
469
|
+
dialog#card-modal {
|
|
470
|
+
width: min(920px, calc(100vw - 32px));
|
|
471
|
+
max-height: calc(100vh - 48px);
|
|
472
|
+
padding: 0;
|
|
473
|
+
background: var(--surface);
|
|
474
|
+
color: var(--text);
|
|
475
|
+
border: 1px solid var(--border-strong);
|
|
476
|
+
border-radius: var(--radius);
|
|
477
|
+
overflow: hidden;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
dialog#card-modal::backdrop { background: rgba(4, 6, 8, 0.72); }
|
|
481
|
+
|
|
482
|
+
.modal-shell { display: flex; flex-direction: column; max-height: inherit; }
|
|
483
|
+
|
|
484
|
+
.modal-head {
|
|
485
|
+
position: relative;
|
|
486
|
+
padding: 14px 18px 12px;
|
|
487
|
+
border-bottom: 1px solid var(--border);
|
|
488
|
+
background: var(--surface-2);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.modal-ident { display: flex; align-items: center; gap: 8px; }
|
|
492
|
+
|
|
493
|
+
.modal-title {
|
|
494
|
+
margin: 6px 0 4px;
|
|
495
|
+
font-size: 17px;
|
|
496
|
+
font-weight: 700;
|
|
497
|
+
letter-spacing: -0.02em;
|
|
498
|
+
line-height: 1.3;
|
|
499
|
+
max-width: 62ch;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.modal-meta {
|
|
503
|
+
margin: 0;
|
|
504
|
+
font-family: var(--mono);
|
|
505
|
+
font-size: 10.5px;
|
|
506
|
+
color: var(--muted);
|
|
507
|
+
font-variant-numeric: tabular-nums;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.modal-close {
|
|
511
|
+
position: absolute;
|
|
512
|
+
top: 12px;
|
|
513
|
+
right: 12px;
|
|
514
|
+
background: transparent;
|
|
515
|
+
border: 1px solid var(--border-strong);
|
|
516
|
+
border-radius: var(--radius);
|
|
517
|
+
color: var(--muted);
|
|
518
|
+
font-family: var(--mono);
|
|
519
|
+
font-size: 11px;
|
|
520
|
+
padding: 3px 7px;
|
|
521
|
+
cursor: pointer;
|
|
522
|
+
transition: color 220ms var(--ease-in), border-color 220ms var(--ease-in);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.modal-close:hover { color: var(--text); border-color: var(--muted); }
|
|
526
|
+
|
|
527
|
+
.modal-body {
|
|
528
|
+
padding: 14px 18px 20px;
|
|
529
|
+
overflow-y: auto;
|
|
530
|
+
display: flex;
|
|
531
|
+
flex-direction: column;
|
|
532
|
+
gap: 18px;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.detail-section h3 {
|
|
536
|
+
margin: 0 0 8px;
|
|
537
|
+
font-size: 12px;
|
|
538
|
+
font-weight: 600;
|
|
539
|
+
letter-spacing: 0.12em;
|
|
540
|
+
text-transform: uppercase;
|
|
541
|
+
color: var(--muted);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.detail-count {
|
|
545
|
+
font-family: var(--mono);
|
|
546
|
+
font-size: 10px;
|
|
547
|
+
color: var(--text);
|
|
548
|
+
letter-spacing: 0;
|
|
549
|
+
margin-left: 4px;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.scope-block { margin-bottom: 8px; }
|
|
553
|
+
|
|
554
|
+
.scope-block h4,
|
|
555
|
+
.gate-block-title {
|
|
556
|
+
margin: 0 0 4px;
|
|
557
|
+
font-family: var(--mono);
|
|
558
|
+
font-size: 10px;
|
|
559
|
+
font-weight: 600;
|
|
560
|
+
letter-spacing: 0.08em;
|
|
561
|
+
text-transform: uppercase;
|
|
562
|
+
color: var(--muted);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.scope-block--may h4 { color: var(--accent); }
|
|
566
|
+
.scope-block--forbidden h4 { color: var(--danger); }
|
|
567
|
+
|
|
568
|
+
.scope-list {
|
|
569
|
+
list-style: none;
|
|
570
|
+
margin: 0;
|
|
571
|
+
padding: 0;
|
|
572
|
+
display: flex;
|
|
573
|
+
flex-wrap: wrap;
|
|
574
|
+
gap: 4px;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.scope-list code,
|
|
578
|
+
.artifact-path,
|
|
579
|
+
.chip {
|
|
580
|
+
font-family: var(--mono);
|
|
581
|
+
font-size: 10.5px;
|
|
582
|
+
background: var(--bg);
|
|
583
|
+
border: 1px solid var(--border);
|
|
584
|
+
border-radius: var(--radius);
|
|
585
|
+
padding: 1px 5px;
|
|
586
|
+
color: var(--text);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
.scope-block--forbidden .scope-list code { color: var(--danger); }
|
|
590
|
+
.scope-block--may .scope-list code { color: var(--accent); }
|
|
591
|
+
|
|
592
|
+
.detail-meta {
|
|
593
|
+
font-family: var(--mono);
|
|
594
|
+
font-size: 10.5px;
|
|
595
|
+
color: var(--muted);
|
|
596
|
+
margin: 6px 0 0;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.detail-gate-text { margin: 0 0 10px; }
|
|
600
|
+
.detail-gate-text code {
|
|
601
|
+
font-family: var(--mono);
|
|
602
|
+
font-size: 11px;
|
|
603
|
+
color: var(--muted);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.gate-block { margin-bottom: 12px; }
|
|
607
|
+
|
|
608
|
+
.gate-table {
|
|
609
|
+
width: 100%;
|
|
610
|
+
border-collapse: collapse;
|
|
611
|
+
font-size: 11px;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.gate-table th {
|
|
615
|
+
text-align: left;
|
|
616
|
+
font-family: var(--mono);
|
|
617
|
+
font-size: 9.5px;
|
|
618
|
+
letter-spacing: 0.08em;
|
|
619
|
+
text-transform: uppercase;
|
|
620
|
+
color: var(--muted);
|
|
621
|
+
font-weight: 600;
|
|
622
|
+
padding: 4px 6px;
|
|
623
|
+
border-bottom: 1px solid var(--border);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.gate-table td {
|
|
627
|
+
padding: 5px 6px;
|
|
628
|
+
border-bottom: 1px solid var(--border);
|
|
629
|
+
vertical-align: top;
|
|
630
|
+
color: var(--muted);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.gate-table td code { font-family: var(--mono); font-size: 10px; color: var(--text); }
|
|
634
|
+
|
|
635
|
+
.phase-reason {
|
|
636
|
+
margin: 3px 0 0;
|
|
637
|
+
font-family: var(--mono);
|
|
638
|
+
font-size: 10px;
|
|
639
|
+
color: var(--warn);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
.detail-acceptance {
|
|
643
|
+
margin: 0;
|
|
644
|
+
padding: 0;
|
|
645
|
+
list-style: none;
|
|
646
|
+
display: flex;
|
|
647
|
+
flex-direction: column;
|
|
648
|
+
gap: 8px;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.acceptance-item {
|
|
652
|
+
border-left: 2px solid var(--border);
|
|
653
|
+
padding: 2px 0 2px 9px;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.acceptance-item[data-acceptance-state="pass"] { border-left-color: var(--accent); }
|
|
657
|
+
.acceptance-item[data-acceptance-state="fail"] { border-left-color: var(--danger); }
|
|
658
|
+
.acceptance-item[data-acceptance-state="not_run"] { border-left-color: var(--warn); }
|
|
659
|
+
|
|
660
|
+
.acceptance-text {
|
|
661
|
+
margin: 3px 0 4px;
|
|
662
|
+
font-size: 12px;
|
|
663
|
+
line-height: 1.45;
|
|
664
|
+
max-width: 78ch;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.acceptance-evidence-list {
|
|
668
|
+
margin: 0;
|
|
669
|
+
padding: 0 0 0 2px;
|
|
670
|
+
list-style: none;
|
|
671
|
+
display: flex;
|
|
672
|
+
flex-direction: column;
|
|
673
|
+
gap: 2px;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.acceptance-evidence {
|
|
677
|
+
display: flex;
|
|
678
|
+
flex-wrap: wrap;
|
|
679
|
+
gap: 6px;
|
|
680
|
+
align-items: baseline;
|
|
681
|
+
font-size: 10.5px;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.acceptance-evidence code { font-family: var(--mono); font-size: 10px; color: var(--muted); }
|
|
685
|
+
|
|
686
|
+
.detail-list {
|
|
687
|
+
margin: 0;
|
|
688
|
+
padding: 0;
|
|
689
|
+
list-style: none;
|
|
690
|
+
display: flex;
|
|
691
|
+
flex-direction: column;
|
|
692
|
+
gap: 9px;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
.evidence-item {
|
|
696
|
+
border-left: 2px solid var(--border);
|
|
697
|
+
padding-left: 9px;
|
|
698
|
+
font-size: 11px;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.evidence-item[data-evidence-passed="true"] { border-left-color: var(--accent); }
|
|
702
|
+
.evidence-item[data-evidence-passed="false"] { border-left-color: var(--danger); }
|
|
703
|
+
|
|
704
|
+
.evidence-ref {
|
|
705
|
+
font-family: var(--mono);
|
|
706
|
+
font-size: 10.5px;
|
|
707
|
+
color: var(--text);
|
|
708
|
+
word-break: break-word;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.evidence-meta {
|
|
712
|
+
display: block;
|
|
713
|
+
font-family: var(--mono);
|
|
714
|
+
font-size: 9.5px;
|
|
715
|
+
color: var(--muted);
|
|
716
|
+
margin-top: 2px;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.evidence-note {
|
|
720
|
+
margin: 3px 0 0;
|
|
721
|
+
color: var(--muted);
|
|
722
|
+
font-size: 11px;
|
|
723
|
+
max-width: 78ch;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
.evidence-req {
|
|
727
|
+
margin: 3px 0 0;
|
|
728
|
+
font-size: 10.5px;
|
|
729
|
+
color: var(--border-strong);
|
|
730
|
+
max-width: 78ch;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
.detail-empty {
|
|
734
|
+
font-family: var(--mono);
|
|
735
|
+
font-size: 10.5px;
|
|
736
|
+
color: var(--border-strong);
|
|
737
|
+
margin: 0;
|
|
738
|
+
padding: 4px 0;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.artifact-list {
|
|
742
|
+
margin: 0 0 8px;
|
|
743
|
+
padding: 0;
|
|
744
|
+
list-style: none;
|
|
745
|
+
display: flex;
|
|
746
|
+
flex-wrap: wrap;
|
|
747
|
+
gap: 8px;
|
|
748
|
+
align-items: center;
|
|
749
|
+
font-family: var(--mono);
|
|
750
|
+
font-size: 11px;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
.artifact-link {
|
|
754
|
+
color: var(--accent);
|
|
755
|
+
text-decoration: none;
|
|
756
|
+
border-bottom: 1px solid transparent;
|
|
757
|
+
transition: border-color 220ms var(--ease-in);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.artifact-link:hover { border-bottom-color: var(--accent); }
|
|
761
|
+
|
|
762
|
+
.detail-result {
|
|
763
|
+
margin: 8px 0 0;
|
|
764
|
+
font-family: var(--mono);
|
|
765
|
+
font-size: 10.5px;
|
|
766
|
+
color: var(--muted);
|
|
767
|
+
max-width: 78ch;
|
|
768
|
+
border-left: 2px solid var(--border);
|
|
769
|
+
padding-left: 9px;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/* ÔöÇÔöÇ Footer ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇ */
|
|
773
|
+
|
|
774
|
+
.app-foot {
|
|
775
|
+
display: flex;
|
|
776
|
+
flex-wrap: wrap;
|
|
777
|
+
gap: 16px;
|
|
778
|
+
align-items: center;
|
|
779
|
+
padding: 8px 20px;
|
|
780
|
+
border-top: 1px solid var(--border);
|
|
781
|
+
font-family: var(--mono);
|
|
782
|
+
font-size: 10px;
|
|
783
|
+
color: var(--muted);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
.app-foot .read-only { color: var(--warn); }
|
|
787
|
+
|
|
788
|
+
/* ÔöÇÔöÇ Responsive ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇ */
|
|
789
|
+
|
|
790
|
+
@media (max-width: 900px) {
|
|
791
|
+
.board {
|
|
792
|
+
grid-auto-columns: minmax(78vw, 1fr);
|
|
793
|
+
padding: 12px;
|
|
794
|
+
}
|
|
795
|
+
.toolbar-note { margin-left: 0; width: 100%; }
|
|
796
|
+
.column { max-height: none; }
|
|
797
|
+
.board { max-height: none; }
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
@media (prefers-reduced-motion: reduce) {
|
|
801
|
+
*, *::before, *::after {
|
|
802
|
+
transition-duration: 0.001ms !important;
|
|
803
|
+
animation-duration: 0.001ms !important;
|
|
804
|
+
}
|
|
805
|
+
.card, .btn, .column-body { transform: none !important; }
|
|
806
|
+
}
|
|
807
|
+
</style>
|
|
808
|
+
</head>
|
|
809
|
+
<body>
|
|
810
|
+
|
|
811
|
+
<header class="app-head">
|
|
812
|
+
<h1>Kanban de la Oficina</h1>
|
|
813
|
+
<p class="run-meta" id="run-meta">run <strong>run-87fc0537</strong> ┬À estado <strong>running</strong> ┬À (sin objetivo registrado)</p>
|
|
814
|
+
<span class="counter">cards <strong id="count-total">11</strong></span>
|
|
815
|
+
<span class="counter">visibles <strong id="count-visible">11</strong></span>
|
|
816
|
+
</header>
|
|
817
|
+
|
|
818
|
+
<div class="toolbar" role="search" aria-label="Filtros del tablero">
|
|
819
|
+
<div class="field">
|
|
820
|
+
<label for="filter-owner">owner</label>
|
|
821
|
+
<select id="filter-owner" name="owner"><option value="all">todos</option><option value="e-design-ui-finish-gate-reviewer">UI Finish-Gate Reviewer (e-design-ui-finish-gate-reviewer)</option><option value="e-testing-api-tester">API Tester (e-testing-api-tester)</option><option value="e-xr-immersive-developer">XR Immersive Developer (e-xr-immersive-developer)</option></select>
|
|
822
|
+
</div>
|
|
823
|
+
<div class="field">
|
|
824
|
+
<label for="filter-type">tipo</label>
|
|
825
|
+
<select id="filter-type" name="type"><option value="all">todos</option><option value="bugfix">bugfix</option><option value="design">design</option><option value="exploration">exploration</option><option value="implementation">implementation</option></select>
|
|
826
|
+
</div>
|
|
827
|
+
<div class="field">
|
|
828
|
+
<label for="filter-state">estado</label>
|
|
829
|
+
<select id="filter-state" name="state"><option value="all">todos</option><option value="todo">TODO</option><option value="in_progress">IN_PROGRESS</option><option value="review">REVIEW</option><option value="done">DONE</option><option value="rework">REWORK</option><option value="blocked">BLOCKED</option><option value="escalated">ESCALATED</option></select>
|
|
830
|
+
</div>
|
|
831
|
+
<div class="field">
|
|
832
|
+
<label for="filter-search">buscar</label>
|
|
833
|
+
<input id="filter-search" name="q" type="search" placeholder="id, título, tipo" autocomplete="off">
|
|
834
|
+
</div>
|
|
835
|
+
<button class="btn btn--ghost" id="reset-view" type="button">reset vista</button>
|
|
836
|
+
<button class="btn btn--ghost" id="refresh-data" type="button">recargar data</button>
|
|
837
|
+
<p class="toolbar-note">
|
|
838
|
+
fuente <span class="source" id="data-source">snapshot embebido</span>
|
|
839
|
+
</p>
|
|
840
|
+
</div>
|
|
841
|
+
|
|
842
|
+
<main class="board" id="board" aria-label="Tablero Kanban de cards">
|
|
843
|
+
|
|
844
|
+
<section class="column" data-state="todo" aria-labelledby="col-todo-title">
|
|
845
|
+
<div class="column-head">
|
|
846
|
+
<h2 class="column-title" id="col-todo-title">TODO</h2>
|
|
847
|
+
<span class="column-count" data-count="todo" aria-label="cards en TODO">9</span>
|
|
848
|
+
</div>
|
|
849
|
+
<div class="column-body" data-cards="todo" data-dropzone="true"><article class="card" data-card-id="T-01" data-state="todo" data-type="exploration" draggable="true" tabindex="0" role="button" aria-haspopup="dialog" aria-label="T-01 ┬À Explorar estructura .oficina/ y definir schema de datos para el dashboard"><header class="card-top"><span class="card-id">T-01</span><span class="badge badge--type">exploration</span></header><h3 class="card-title">Explorar estructura .oficina/ y definir schema de datos para el dashboard</h3><div class="card-people"><span class="person person--owner" data-owner="e-design-ui-finish-gate-reviewer"><span class="avatar" style="--avatar-hue:207" title="UI Finish-Gate Reviewer ┬À design-ui-finish-gate-reviewer" data-agent-id="e-design-ui-finish-gate-reviewer">UR</span><span class="person-name">UI Finish-Gate Reviewer</span></span><span class="person person--reviewer" data-reviewer="e-testing-api-tester"><span class="avatar" style="--avatar-hue:183" title="API Tester ┬À testing-api-tester" data-agent-id="e-testing-api-tester">AT</span><span class="person-name">API Tester</span></span><span class="rejections" data-rejections="0" title="Rechazos del revisor"><span class="rejections-mark" aria-hidden="true">Ôå║</span> 0</span></div><ul class="checklist" aria-label="Criterios de aceptaci├│n"><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Mapear todos los archivos en .oficina/ (state.json, roster.json, bus/*, evidence/*, leases/*, context.md, cost.json)</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Definir TypeScript interfaces para cada tipo de dato que el dashboard consumira</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Identificar que archivos cambian en tiempo real y frecuencia de actualizacion</span></li><li class="check-more">+1 criterio m├ís</li><li class="check-progress"><span class="check-progress-label">0/4 con evidencia</span><span class="check-progress-track" aria-hidden="true"><span class="check-progress-fill" style="width:0%"></span></span></li></ul><footer class="card-gates"><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="TEC: NOT_RUN"><span class="gate-key">TEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="SEC: NOT_RUN"><span class="gate-key">SEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="REQ: NOT_RUN"><span class="gate-key">REQ</span> NOT_RUN</span></footer></article><article class="card" data-card-id="T-02" data-state="todo" data-type="design" draggable="true" tabindex="0" role="button" aria-haspopup="dialog" aria-label="T-02 ┬À Arquitectura del dashboard: single-page app con polling/WebSocket sobre archivos"><header class="card-top"><span class="card-id">T-02</span><span class="badge badge--type">design</span></header><h3 class="card-title">Arquitectura del dashboard: single-page app con polling/WebSocket sobre archivos</h3><div class="card-people"><span class="person person--owner" data-owner="e-design-ui-finish-gate-reviewer"><span class="avatar" style="--avatar-hue:207" title="UI Finish-Gate Reviewer ┬À design-ui-finish-gate-reviewer" data-agent-id="e-design-ui-finish-gate-reviewer">UR</span><span class="person-name">UI Finish-Gate Reviewer</span></span><span class="person person--reviewer" data-reviewer="e-testing-api-tester"><span class="avatar" style="--avatar-hue:183" title="API Tester ┬À testing-api-tester" data-agent-id="e-testing-api-tester">AT</span><span class="person-name">API Tester</span></span><span class="rejections" data-rejections="0" title="Rechazos del revisor"><span class="rejections-mark" aria-hidden="true">Ôå║</span> 0</span></div><ul class="checklist" aria-label="Criterios de aceptaci├│n"><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Diseno de arquitectura: HTML unico + CSS + JS vanilla/Alpine.js sin build step</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Estrategia de polling: intervalos adaptativos por tipo de dato (state.json: 500ms, bus: 1s, roster: 2s)</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">WebSocket fallback si se detecta servidor (opcional, documentar)</span></li><li class="check-more">+3 criterios m├ís</li><li class="check-progress"><span class="check-progress-label">0/6 con evidencia</span><span class="check-progress-track" aria-hidden="true"><span class="check-progress-fill" style="width:0%"></span></span></li></ul><footer class="card-gates"><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="TEC: NOT_RUN"><span class="gate-key">TEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="SEC: NOT_RUN"><span class="gate-key">SEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="REQ: NOT_RUN"><span class="gate-key">REQ</span> NOT_RUN</span></footer></article><article class="card" data-card-id="T-03" data-state="todo" data-type="implementation" draggable="true" tabindex="0" role="button" aria-haspopup="dialog" aria-label="T-03 ┬À Implementar mesa visual en vivo (panel principal)"><header class="card-top"><span class="card-id">T-03</span><span class="badge badge--type">implementation</span></header><h3 class="card-title">Implementar mesa visual en vivo (panel principal)</h3><div class="card-people"><span class="person person--owner" data-owner="e-design-ui-finish-gate-reviewer"><span class="avatar" style="--avatar-hue:207" title="UI Finish-Gate Reviewer ┬À design-ui-finish-gate-reviewer" data-agent-id="e-design-ui-finish-gate-reviewer">UR</span><span class="person-name">UI Finish-Gate Reviewer</span></span><span class="person person--reviewer" data-reviewer="e-testing-api-tester"><span class="avatar" style="--avatar-hue:183" title="API Tester ┬À testing-api-tester" data-agent-id="e-testing-api-tester">AT</span><span class="person-name">API Tester</span></span><span class="rejections" data-rejections="0" title="Rechazos del revisor"><span class="rejections-mark" aria-hidden="true">Ôå║</span> 0</span></div><ul class="checklist" aria-label="Criterios de aceptaci├│n"><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Panel que lee roster.json y state.json y muestra agentes con estado visual (working/idle/no-heartbeat/dead)</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Cards sin owner visibles con badge de 'unassigned'</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Avatares de agentes con iniciales, color por rol, indicador de heartbeat (pulse animation)</span></li><li class="check-more">+3 criterios m├ís</li><li class="check-progress"><span class="check-progress-label">0/6 con evidencia</span><span class="check-progress-track" aria-hidden="true"><span class="check-progress-fill" style="width:0%"></span></span></li></ul><footer class="card-gates"><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="TEC: NOT_RUN"><span class="gate-key">TEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="SEC: NOT_RUN"><span class="gate-key">SEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="REQ: NOT_RUN"><span class="gate-key">REQ</span> NOT_RUN</span></footer></article><article class="card" data-card-id="T-04" data-state="todo" data-type="implementation" draggable="true" tabindex="0" role="button" aria-haspopup="dialog" aria-label="T-04 ┬À Implementar bus de mensajes visible con threading y prioridades"><header class="card-top"><span class="card-id">T-04</span><span class="badge badge--type">implementation</span></header><h3 class="card-title">Implementar bus de mensajes visible con threading y prioridades</h3><div class="card-people"><span class="person person--owner" data-owner="e-design-ui-finish-gate-reviewer"><span class="avatar" style="--avatar-hue:207" title="UI Finish-Gate Reviewer ┬À design-ui-finish-gate-reviewer" data-agent-id="e-design-ui-finish-gate-reviewer">UR</span><span class="person-name">UI Finish-Gate Reviewer</span></span><span class="person person--reviewer" data-reviewer="e-testing-api-tester"><span class="avatar" style="--avatar-hue:183" title="API Tester ┬À testing-api-tester" data-agent-id="e-testing-api-tester">AT</span><span class="person-name">API Tester</span></span><span class="rejections" data-rejections="0" title="Rechazos del revisor"><span class="rejections-mark" aria-hidden="true">Ôå║</span> 0</span></div><ul class="checklist" aria-label="Criterios de aceptaci├│n"><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Panel que lee bus/peer/ y canales de coordinadores (coord-command, coord-arch, coord-flow)</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Visualizacion tipo sequence diagram: flechas entre avatares, colores por prioridad (critical=red, high=orange, normal=blue, low=gray)</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Threading visual: inReplyTo agrupa mensajes en hilos colapsables/expandibles</span></li><li class="check-more">+3 criterios m├ís</li><li class="check-progress"><span class="check-progress-label">0/6 con evidencia</span><span class="check-progress-track" aria-hidden="true"><span class="check-progress-fill" style="width:0%"></span></span></li></ul><footer class="card-gates"><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="TEC: NOT_RUN"><span class="gate-key">TEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="SEC: NOT_RUN"><span class="gate-key">SEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="REQ: NOT_RUN"><span class="gate-key">REQ</span> NOT_RUN</span></footer></article><article class="card" data-card-id="T-05" data-state="todo" data-type="implementation" draggable="true" tabindex="0" role="button" aria-haspopup="dialog" aria-label="T-05 ┬À Implementar panel de debate/brainstorming trazable (HELP_REQUEST broadcast + respuestas)"><header class="card-top"><span class="card-id">T-05</span><span class="badge badge--type">implementation</span></header><h3 class="card-title">Implementar panel de debate/brainstorming trazable (HELP_REQUEST broadcast + respuestas)</h3><div class="card-people"><span class="person person--owner" data-owner="e-xr-immersive-developer"><span class="avatar" style="--avatar-hue:77" title="XR Immersive Developer ┬À xr-immersive-developer" data-agent-id="e-xr-immersive-developer">XD</span><span class="person-name">XR Immersive Developer</span></span><span class="person person--reviewer person--unassigned" data-reviewer=""><span class="avatar avatar--empty" aria-hidden="true">?</span><span class="person-name">sin revisor</span></span><span class="rejections" data-rejections="0" title="Rechazos del revisor"><span class="rejections-mark" aria-hidden="true">Ôå║</span> 0</span></div><ul class="checklist" aria-label="Criterios de aceptaci├│n"><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Deteccion automatica de HELP_REQUEST con to:['*'] (broadcast)</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Vista tipo chat: burbujas con avatar, timestamp, agente, expandible para ver body completo</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Respuestas (HELP_ACCEPT, HELP_DECLINE, HELP_PARTIAL, HELP_QUESTION) anidadas bajo el request</span></li><li class="check-more">+3 criterios m├ís</li><li class="check-progress"><span class="check-progress-label">0/6 con evidencia</span><span class="check-progress-track" aria-hidden="true"><span class="check-progress-fill" style="width:0%"></span></span></li></ul><footer class="card-gates"><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="TEC: NOT_RUN"><span class="gate-key">TEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="SEC: NOT_RUN"><span class="gate-key">SEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="REQ: NOT_RUN"><span class="gate-key">REQ</span> NOT_RUN</span></footer></article><article class="card" data-card-id="T-06" data-state="todo" data-type="implementation" draggable="true" tabindex="0" role="button" aria-haspopup="dialog" aria-label="T-06 ┬À Implementar panel de intervencion del jefe (Commander/Integrator/Architect decisions)"><header class="card-top"><span class="card-id">T-06</span><span class="badge badge--type">implementation</span></header><h3 class="card-title">Implementar panel de intervencion del jefe (Commander/Integrator/Architect decisions)</h3><div class="card-people"><span class="person person--owner" data-owner="e-xr-immersive-developer"><span class="avatar" style="--avatar-hue:77" title="XR Immersive Developer ┬À xr-immersive-developer" data-agent-id="e-xr-immersive-developer">XD</span><span class="person-name">XR Immersive Developer</span></span><span class="person person--reviewer person--unassigned" data-reviewer=""><span class="avatar avatar--empty" aria-hidden="true">?</span><span class="person-name">sin revisor</span></span><span class="rejections" data-rejections="0" title="Rechazos del revisor"><span class="rejections-mark" aria-hidden="true">Ôå║</span> 0</span></div><ul class="checklist" aria-label="Criterios de aceptaci├│n"><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Tres secciones: Commander (scope/prioridades/escalations), Integrator (routing/leases/gates/deadlocks), Architect (tech decisions/patterns/contracts)</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Lee decisions de state.json + mensajes en canales de coordinadores</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Cada decision: timestamp, actor, cardId, decision, razon, siguiente accion</span></li><li class="check-more">+2 criterios m├ís</li><li class="check-progress"><span class="check-progress-label">0/5 con evidencia</span><span class="check-progress-track" aria-hidden="true"><span class="check-progress-fill" style="width:0%"></span></span></li></ul><footer class="card-gates"><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="TEC: NOT_RUN"><span class="gate-key">TEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="SEC: NOT_RUN"><span class="gate-key">SEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="REQ: NOT_RUN"><span class="gate-key">REQ</span> NOT_RUN</span></footer></article><article class="card" data-card-id="T-08" data-state="todo" data-type="implementation" draggable="true" tabindex="0" role="button" aria-haspopup="dialog" aria-label="T-08 ┬À Implementar animacion de Factory creando agentes nuevos"><header class="card-top"><span class="card-id">T-08</span><span class="badge badge--type">implementation</span></header><h3 class="card-title">Implementar animacion de Factory creando agentes nuevos</h3><div class="card-people"><span class="person person--owner" data-owner="e-xr-immersive-developer"><span class="avatar" style="--avatar-hue:77" title="XR Immersive Developer ┬À xr-immersive-developer" data-agent-id="e-xr-immersive-developer">XD</span><span class="person-name">XR Immersive Developer</span></span><span class="person person--reviewer person--unassigned" data-reviewer=""><span class="avatar avatar--empty" aria-hidden="true">?</span><span class="person-name">sin revisor</span></span><span class="rejections" data-rejections="0" title="Rechazos del revisor"><span class="rejections-mark" aria-hidden="true">Ôå║</span> 0</span></div><ul class="checklist" aria-label="Criterios de aceptaci├│n"><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Deteccion: cuando hay card sin match en catalogo (cobertura < 60% en roster/state)</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Animacion secuencial: Factory creando agente... -> archivo .md apareciendo en .opencode/agents/ -> entrada al catalogo dinamico -> match en proxima card</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Visualizacion: progress steps con checkmarks, archivo escribiendose con cursor parpadeante</span></li><li class="check-more">+2 criterios m├ís</li><li class="check-progress"><span class="check-progress-label">0/5 con evidencia</span><span class="check-progress-track" aria-hidden="true"><span class="check-progress-fill" style="width:0%"></span></span></li></ul><footer class="card-gates"><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="TEC: NOT_RUN"><span class="gate-key">TEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="SEC: NOT_RUN"><span class="gate-key">SEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="REQ: NOT_RUN"><span class="gate-key">REQ</span> NOT_RUN</span></footer></article><article class="card" data-card-id="T-09" data-state="todo" data-type="implementation" draggable="true" tabindex="0" role="button" aria-haspopup="dialog" aria-label="T-09 ┬À Implementar panel lateral de Gates y Evidencia con spinners y resultados"><header class="card-top"><span class="card-id">T-09</span><span class="badge badge--type">implementation</span></header><h3 class="card-title">Implementar panel lateral de Gates y Evidencia con spinners y resultados</h3><div class="card-people"><span class="person person--owner" data-owner="e-testing-api-tester"><span class="avatar" style="--avatar-hue:183" title="API Tester ┬À testing-api-tester" data-agent-id="e-testing-api-tester">AT</span><span class="person-name">API Tester</span></span><span class="person person--reviewer" data-reviewer="e-design-ui-finish-gate-reviewer"><span class="avatar" style="--avatar-hue:207" title="UI Finish-Gate Reviewer ┬À design-ui-finish-gate-reviewer" data-agent-id="e-design-ui-finish-gate-reviewer">UR</span><span class="person-name">UI Finish-Gate Reviewer</span></span><span class="rejections" data-rejections="0" title="Rechazos del revisor"><span class="rejections-mark" aria-hidden="true">Ôå║</span> 0</span></div><ul class="checklist" aria-label="Criterios de aceptaci├│n"><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Panel lateral fijo (desktop) / bottom sheet (mobile) mostrando 7 gates</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Fases: Build, Type check, Lint, Tests+coverage, Security grep, Diff review, Requisitos</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Spinners animados mientras corren, iconos PASS/FAIL/NOT_RUN/N/A al terminar</span></li><li class="check-more">+3 criterios m├ís</li><li class="check-progress"><span class="check-progress-label">0/6 con evidencia</span><span class="check-progress-track" aria-hidden="true"><span class="check-progress-fill" style="width:0%"></span></span></li></ul><footer class="card-gates"><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="TEC: NOT_RUN"><span class="gate-key">TEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="SEC: NOT_RUN"><span class="gate-key">SEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="REQ: NOT_RUN"><span class="gate-key">REQ</span> NOT_RUN</span></footer></article><article class="card" data-card-id="T-10" data-state="todo" data-type="implementation" draggable="true" tabindex="0" role="button" aria-haspopup="dialog" aria-label="T-10 ┬À Integrar todo en dashboard.html unico + README-DASHBOARD.md"><header class="card-top"><span class="card-id">T-10</span><span class="badge badge--type">implementation</span></header><h3 class="card-title">Integrar todo en dashboard.html unico + README-DASHBOARD.md</h3><div class="card-people"><span class="person person--owner" data-owner="e-testing-api-tester"><span class="avatar" style="--avatar-hue:183" title="API Tester ┬À testing-api-tester" data-agent-id="e-testing-api-tester">AT</span><span class="person-name">API Tester</span></span><span class="person person--reviewer" data-reviewer="e-design-ui-finish-gate-reviewer"><span class="avatar" style="--avatar-hue:207" title="UI Finish-Gate Reviewer ┬À design-ui-finish-gate-reviewer" data-agent-id="e-design-ui-finish-gate-reviewer">UR</span><span class="person-name">UI Finish-Gate Reviewer</span></span><span class="rejections" data-rejections="0" title="Rechazos del revisor"><span class="rejections-mark" aria-hidden="true">Ôå║</span> 0</span></div><ul class="checklist" aria-label="Criterios de aceptaci├│n"><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">dashboard.html unico con todos los paneles (layout: header + sidebar tabs + main area)</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Navegacion por tabs: Mesa | Bus | Debate | Jefe | Kanban | Factory | Gates</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Persistencia de tab activo en localStorage</span></li><li class="check-more">+3 criterios m├ís</li><li class="check-progress"><span class="check-progress-label">0/6 con evidencia</span><span class="check-progress-track" aria-hidden="true"><span class="check-progress-fill" style="width:0%"></span></span></li></ul><footer class="card-gates"><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="TEC: NOT_RUN"><span class="gate-key">TEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="SEC: NOT_RUN"><span class="gate-key">SEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="REQ: NOT_RUN"><span class="gate-key">REQ</span> NOT_RUN</span></footer></article></div>
|
|
850
|
+
</section>
|
|
851
|
+
|
|
852
|
+
<section class="column" data-state="in_progress" aria-labelledby="col-in_progress-title">
|
|
853
|
+
<div class="column-head">
|
|
854
|
+
<h2 class="column-title" id="col-in_progress-title">IN_PROGRESS</h2>
|
|
855
|
+
<span class="column-count" data-count="in_progress" aria-label="cards en IN_PROGRESS">1</span>
|
|
856
|
+
</div>
|
|
857
|
+
<div class="column-body" data-cards="in_progress" data-dropzone="true"><article class="card" data-card-id="T-07" data-state="in_progress" data-type="implementation" draggable="true" tabindex="0" role="button" aria-haspopup="dialog" aria-label="T-07 ┬À Implementar Kanban visual de cards con drag-drop y acceptance criteria"><header class="card-top"><span class="card-id">T-07</span><span class="badge badge--type">implementation</span></header><h3 class="card-title">Implementar Kanban visual de cards con drag-drop y acceptance criteria</h3><div class="card-people"><span class="person person--owner" data-owner="e-xr-immersive-developer"><span class="avatar" style="--avatar-hue:77" title="XR Immersive Developer ┬À xr-immersive-developer" data-agent-id="e-xr-immersive-developer">XD</span><span class="person-name">XR Immersive Developer</span></span><span class="person person--reviewer person--unassigned" data-reviewer=""><span class="avatar avatar--empty" aria-hidden="true">?</span><span class="person-name">sin revisor</span></span><span class="rejections" data-rejections="0" title="Rechazos del revisor"><span class="rejections-mark" aria-hidden="true">Ôå║</span> 0</span></div><ul class="checklist" aria-label="Criterios de aceptaci├│n"><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Columnas: TODO, IN_PROGRESS, REVIEW, DONE, REWORK, BLOCKED, ESCALATED</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Cards muestran: id, titulo, owner avatar, reviewer avatar, rejection count, acceptance checklist</span></li><li class="check-item is-empty"><span class="check-box" aria-hidden="true">┬À</span><span class="check-text">Drag-drop opcional entre columnas (solo visual, no persiste - read-only dashboard)</span></li><li class="check-more">+3 criterios m├ís</li><li class="check-progress"><span class="check-progress-label">0/6 con evidencia</span><span class="check-progress-track" aria-hidden="true"><span class="check-progress-fill" style="width:0%"></span></span></li></ul><footer class="card-gates"><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="TEC: NOT_RUN"><span class="gate-key">TEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="SEC: NOT_RUN"><span class="gate-key">SEC</span> NOT_RUN</span><span class="gate-badge gate-badge--not-run" data-outcome="not_run" title="REQ: NOT_RUN"><span class="gate-key">REQ</span> NOT_RUN</span></footer></article></div>
|
|
858
|
+
</section>
|
|
859
|
+
|
|
860
|
+
<section class="column" data-state="review" aria-labelledby="col-review-title">
|
|
861
|
+
<div class="column-head">
|
|
862
|
+
<h2 class="column-title" id="col-review-title">REVIEW</h2>
|
|
863
|
+
<span class="column-count" data-count="review" aria-label="cards en REVIEW">0</span>
|
|
864
|
+
</div>
|
|
865
|
+
<div class="column-body" data-cards="review" data-dropzone="true"><p class="column-empty">nada en <strong>REVIEW</strong> <span class="column-hint">(esperando revisor)</span></p></div>
|
|
866
|
+
</section>
|
|
867
|
+
|
|
868
|
+
<section class="column" data-state="done" aria-labelledby="col-done-title">
|
|
869
|
+
<div class="column-head">
|
|
870
|
+
<h2 class="column-title" id="col-done-title">DONE</h2>
|
|
871
|
+
<span class="column-count" data-count="done" aria-label="cards en DONE">1</span>
|
|
872
|
+
</div>
|
|
873
|
+
<div class="column-body" data-cards="done" data-dropzone="true"><article class="card" data-card-id="T-00" data-state="done" data-type="bugfix" draggable="true" tabindex="0" role="button" aria-haspopup="dialog" aria-label="T-00 ┬À Cablear el guard anti-gate-verde-vac├¡o en el runner de gates"><header class="card-top"><span class="card-id">T-00</span><span class="badge badge--type">bugfix</span></header><h3 class="card-title">Cablear el guard anti-gate-verde-vac├¡o en el runner de gates</h3><div class="card-people"><span class="person person--owner" data-owner="e-testing-api-tester"><span class="avatar" style="--avatar-hue:183" title="API Tester ┬À testing-api-tester" data-agent-id="e-testing-api-tester">AT</span><span class="person-name">API Tester</span></span><span class="person person--reviewer person--unassigned" data-reviewer=""><span class="avatar avatar--empty" aria-hidden="true">?</span><span class="person-name">sin revisor</span></span><span class="rejections" data-rejections="1" title="Rechazos del revisor"><span class="rejections-mark" aria-hidden="true">Ôå║</span> 1</span></div><ul class="checklist" aria-label="Criterios de aceptaci├│n"><li class="check-item is-checked"><span class="check-box" aria-hidden="true">Ô£ô</span><span class="check-text">runTechnicalGates participa del guard: si la fase test sale con exit 0 pero testsActuallyRan es false, la fase test se reporta como not_run con reason expl├¡cita de gate verde vac├¡o (no PASS silencioso ni silencioso en otros campos)</span></li><li class="check-item is-checked"><span class="check-box" aria-hidden="true">Ô£ô</span><span class="check-text">si testsActuallyRan es true (p. ej. conteo legible '5 passed' o 'Tests: 5'), la fase test sigue en PASS sin regresi├│n</span></li><li class="check-item is-checked"><span class="check-box" aria-hidden="true">Ô£ô</span><span class="check-text">consolidate con ese resultado produce technicalOutcome fail/not_run (passed=false) y renderGate muestra el motivo de forma legible</span></li><li class="check-more">+3 criterios m├ís</li><li class="check-progress"><span class="check-progress-label">6/6 con evidencia</span><span class="check-progress-track" aria-hidden="true"><span class="check-progress-fill" style="width:100%"></span></span></li></ul><footer class="card-gates"><span class="gate-badge gate-badge--pass" data-outcome="pass" title="TEC: PASS"><span class="gate-key">TEC</span> PASS</span><span class="gate-badge gate-badge--pass" data-outcome="pass" title="SEC: PASS"><span class="gate-key">SEC</span> PASS</span><span class="gate-badge gate-badge--pass" data-outcome="pass" title="REQ: PASS"><span class="gate-key">REQ</span> PASS</span></footer></article></div>
|
|
874
|
+
</section>
|
|
875
|
+
|
|
876
|
+
<section class="column" data-state="rework" aria-labelledby="col-rework-title">
|
|
877
|
+
<div class="column-head">
|
|
878
|
+
<h2 class="column-title" id="col-rework-title">REWORK</h2>
|
|
879
|
+
<span class="column-count" data-count="rework" aria-label="cards en REWORK">0</span>
|
|
880
|
+
</div>
|
|
881
|
+
<div class="column-body" data-cards="rework" data-dropzone="true"><p class="column-empty">nada en <strong>REWORK</strong> <span class="column-hint">(rechazada, vuelve)</span></p></div>
|
|
882
|
+
</section>
|
|
883
|
+
|
|
884
|
+
<section class="column" data-state="blocked" aria-labelledby="col-blocked-title">
|
|
885
|
+
<div class="column-head">
|
|
886
|
+
<h2 class="column-title" id="col-blocked-title">BLOCKED</h2>
|
|
887
|
+
<span class="column-count" data-count="blocked" aria-label="cards en BLOCKED">0</span>
|
|
888
|
+
</div>
|
|
889
|
+
<div class="column-body" data-cards="blocked" data-dropzone="true"><p class="column-empty">nada en <strong>BLOCKED</strong> <span class="column-hint">(dependencia externa)</span></p></div>
|
|
890
|
+
</section>
|
|
891
|
+
|
|
892
|
+
<section class="column" data-state="escalated" aria-labelledby="col-escalated-title">
|
|
893
|
+
<div class="column-head">
|
|
894
|
+
<h2 class="column-title" id="col-escalated-title">ESCALATED</h2>
|
|
895
|
+
<span class="column-count" data-count="escalated" aria-label="cards en ESCALATED">0</span>
|
|
896
|
+
</div>
|
|
897
|
+
<div class="column-body" data-cards="escalated" data-dropzone="true"><p class="column-empty">nada en <strong>ESCALATED</strong> <span class="column-hint">(esperando decisi├│n)</span></p></div>
|
|
898
|
+
</section>
|
|
899
|
+
|
|
900
|
+
</main>
|
|
901
|
+
|
|
902
|
+
<footer class="app-foot">
|
|
903
|
+
<span class="read-only">read-only: drag-drop es visual, no persiste</span>
|
|
904
|
+
<span>legend</span>
|
|
905
|
+
<span class="gate-badge gate-badge--pass">PASS</span>
|
|
906
|
+
<span class="gate-badge gate-badge--fail">FAIL</span>
|
|
907
|
+
<span class="gate-badge gate-badge--not-run">NOT_RUN</span>
|
|
908
|
+
<span class="gate-badge gate-badge--na">N/A</span>
|
|
909
|
+
<span>click en card abre el detalle completo</span>
|
|
910
|
+
</footer>
|
|
911
|
+
|
|
912
|
+
<p class="sr-only" id="live-region" role="status" aria-live="polite">11 de 11 cards visibles</p>
|
|
913
|
+
|
|
914
|
+
<dialog id="card-modal" aria-labelledby="card-modal-title">
|
|
915
|
+
<div class="modal-shell">
|
|
916
|
+
<div id="card-modal-content"></div>
|
|
917
|
+
<button class="modal-close" id="card-modal-close" type="button">cerrar (esc)</button>
|
|
918
|
+
</div>
|
|
919
|
+
</dialog>
|
|
920
|
+
|
|
921
|
+
<!-- Dataset embebido: lo regenera `node dashboard/build-kanban-data.mjs` desde .oficina/.
|
|
922
|
+
Se embebe para que el tablero funcione abriendo el archivo con file:// (sin CORS). -->
|
|
923
|
+
<!--BEGIN_DATASET-->
|
|
924
|
+
<script type="application/json" id="kanban-dataset">{"meta":{"runId":"run-87fc0537","objective":"","status":"running","iteration":0,"updatedAt":"2026-10-29T17:39:24.040Z","generatedAt":"2026-10-29T17:42:05.317Z","generator":"dashboard/build-kanban-data.mjs"},"agents":{"e-testing-api-tester":{"id":"e-testing-api-tester","displayName":"API Tester","role":"testing-api-tester","state":"idle_available","currentCard":null},"e-design-ui-finish-gate-reviewer":{"id":"e-design-ui-finish-gate-reviewer","displayName":"UI Finish-Gate Reviewer","role":"design-ui-finish-gate-reviewer","state":"idle_available","currentCard":null},"e-xr-immersive-developer":{"id":"e-xr-immersive-developer","displayName":"XR Immersive Developer","role":"xr-immersive-developer","state":"working","currentCard":"T-07"}},"cards":[{"id":"T-00","title":"Cablear el guard anti-gate-verde-vac├¡o en el runner de gates","type":"bugfix","state":"done","owner":"e-testing-api-tester","reviewer":null,"requiredCapabilities":["typescript","testing","vitest","node"],"scope":{"mayTouch":["src/core/gates.ts","src/core/office.ts","tests/gates.test.ts"],"forbidden":["src/hooks/**","src/cli.ts","dist/**","node_modules/**",".oficina/**"]},"acceptance":["runTechnicalGates participa del guard: si la fase test sale con exit 0 pero testsActuallyRan es false, la fase test se reporta como not_run con reason expl├¡cita de gate verde vac├¡o (no PASS silencioso ni silencioso en otros campos)","si testsActuallyRan es true (p. ej. conteo legible '5 passed' o 'Tests: 5'), la fase test sigue en PASS sin regresi├│n","consolidate con ese resultado produce technicalOutcome fail/not_run (passed=false) y renderGate muestra el motivo de forma legible","pmOfficeOffice.runGates / flujo Office.runGates queda cableado al guard v├¡a runTechnicalGates (sin duplicar l├│gica)","regresi├│n cubierta por tests vitest nuevos en tests/gates.test.ts (existen ya tests de testsActuallyRan; se agregan los del cableado)","npm run typecheck sin errores y npm test todo en verde (las 456 existentes + nuevas)"],"gate":"npm run typecheck && npm test (exit 0, 0 tests fallidos)","dependsOn":[],"attempts":1,"rejections":1,"artifacts":".oficina\\artifacts\\T-00","result":"APROBADO: REWORK 2 verificado (secuencia post-gate FAIL de secrecy): fixtures literales de secretos en tests/gates.test.ts neutralizados por concatenaci├│n ÔÇö runtime byte-a-byte id├®ntico (evaluado directo: FAKE_SK_LIVE Ôëí sk_live_abcdef123456, FAKE_AKIA Ôëí AKIAIOSFODNN7EXAMPLE), los tests de comportamiento de runSecurityGate siguen detectando secretos reales (29/29 en el archivo). Scanner sin cambios (decisi├│n correcta: no excluir tests/** ÔÇö no oculta secretos reales). Escaneo real del revisor: tests/gates.test.ts ÔåÆ [], src/core/gates.ts ÔåÆ [], src/core/office.ts ÔåÆ [], repositorio entero ÔåÆ []. C-001 sigue resuelto (sin deltas en gates.ts/office.ts desde mi aprobaci├│n previa). Findings ÔëÑ80: ninguno. Verificaci├│n independiente: typecheck exit 0; npm test 27 archivos / 466 passed, exit 0. Review completo en .oficina/artifacts/T-00/review-rework2.md.","helpRequests":[],"helpHops":0},{"id":"T-01","title":"Explorar estructura .oficina/ y definir schema de datos para el dashboard","type":"exploration","state":"todo","owner":"e-design-ui-finish-gate-reviewer","reviewer":"e-testing-api-tester","requiredCapabilities":["typescript","node","filesystem"],"scope":{"mayTouch":[".oficina/**"],"forbidden":["src/**","dist/**","node_modules/**"]},"acceptance":["Mapear todos los archivos en .oficina/ (state.json, roster.json, bus/*, evidence/*, leases/*, context.md, cost.json)","Definir TypeScript interfaces para cada tipo de dato que el dashboard consumira","Identificar que archivos cambian en tiempo real y frecuencia de actualizacion","Documentar estructura de mensajes en bus/ y formato de envelopes"],"gate":"TypeScript interfaces validadas y documentadas en artifacts/T-01/handoff.md","dependsOn":[],"attempts":0,"rejections":0,"artifacts":".oficina\\artifacts\\T-01","result":null,"helpRequests":[],"helpHops":0},{"id":"T-02","title":"Arquitectura del dashboard: single-page app con polling/WebSocket sobre archivos","type":"design","state":"todo","owner":"e-design-ui-finish-gate-reviewer","reviewer":"e-testing-api-tester","requiredCapabilities":["frontend-architecture","typescript","real-time-systems"],"scope":{"mayTouch":["dashboard/**"],"forbidden":["src/**",".oficina/**","dist/**","node_modules/**"]},"acceptance":["Diseno de arquitectura: HTML unico + CSS + JS vanilla/Alpine.js sin build step","Estrategia de polling: intervalos adaptativos por tipo de dato (state.json: 500ms, bus: 1s, roster: 2s)","WebSocket fallback si se detecta servidor (opcional, documentar)","Tema oscuro/claro con design tokens consistentes (OKLCH)","Responsive: desktop paneles lado a lado, mobile tabs/accordion","Accesibilidad: ARIA live regions, keyboard navigation, WCAG AA contrast"],"gate":"Architecture decision document en artifacts/T-02/handoff.md con decisiones y trade-offs","dependsOn":["T-01"],"attempts":0,"rejections":0,"artifacts":".oficina\\artifacts\\T-02","result":null,"helpRequests":[],"helpHops":0},{"id":"T-03","title":"Implementar mesa visual en vivo (panel principal)","type":"implementation","state":"todo","owner":"e-design-ui-finish-gate-reviewer","reviewer":"e-testing-api-tester","requiredCapabilities":["typescript","frontend","svg-canvas","real-time-ui"],"scope":{"mayTouch":["dashboard/**"],"forbidden":["src/**",".oficina/**","dist/**","node_modules/**"]},"acceptance":["Panel que lee roster.json y state.json y muestra agentes con estado visual (working/idle/no-heartbeat/dead)","Cards sin owner visibles con badge de 'unassigned'","Avatares de agentes con iniciales, color por rol, indicador de heartbeat (pulse animation)","Tooltip con capabilities, currentCard, idleSince, helpGiven/Received","Actualizacion en tiempo real via polling (500ms)","Keyboard navigation entre agentes, Enter para ver detalle"],"gate":"dashboard/mesa.html funcional + npm run typecheck + npm test pass","dependsOn":["T-02"],"attempts":0,"rejections":0,"artifacts":".oficina\\artifacts\\T-03","result":null,"helpRequests":[],"helpHops":0},{"id":"T-04","title":"Implementar bus de mensajes visible con threading y prioridades","type":"implementation","state":"todo","owner":"e-design-ui-finish-gate-reviewer","reviewer":"e-testing-api-tester","requiredCapabilities":["typescript","frontend","data-visualization","real-time-ui"],"scope":{"mayTouch":["dashboard/**"],"forbidden":["src/**",".oficina/**","dist/**","node_modules/**"]},"acceptance":["Panel que lee bus/peer/ y canales de coordinadores (coord-command, coord-arch, coord-flow)","Visualizacion tipo sequence diagram: flechas entre avatares, colores por prioridad (critical=red, high=orange, normal=blue, low=gray)","Threading visual: inReplyTo agrupa mensajes en hilos colapsables/expandibles","Filtros por kind (HELP_REQUEST, HELP_ACCEPT, CLAIM, RELEASE, SPAWN_REQUEST, etc.)","Timeline con timestamps relativos, auto-scroll opcional","Click en mensaje muestra detalle completo (body, ttl, hops, idempotency_key)"],"gate":"dashboard/bus.html funcional + typecheck + tests pass","dependsOn":["T-02"],"attempts":0,"rejections":0,"artifacts":".oficina\\artifacts\\T-04","result":null,"helpRequests":[],"helpHops":0},{"id":"T-05","title":"Implementar panel de debate/brainstorming trazable (HELP_REQUEST broadcast + respuestas)","type":"implementation","state":"todo","owner":"e-xr-immersive-developer","reviewer":null,"requiredCapabilities":["typescript","frontend","chat-ui","threading"],"scope":{"mayTouch":["dashboard/**"],"forbidden":["src/**",".oficina/**","dist/**","node_modules/**"]},"acceptance":["Deteccion automatica de HELP_REQUEST con to:['*'] (broadcast)","Vista tipo chat: burbujas con avatar, timestamp, agente, expandible para ver body completo","Respuestas (HELP_ACCEPT, HELP_DECLINE, HELP_PARTIAL, HELP_QUESTION) anidadas bajo el request","Indicador visual de estado: pending -> accepted/declined/partial -> completed","Colapso/expansion de hilos completos, contador de respuestas","ARIA live region para anuncios de nuevos mensajes en hilos abiertos"],"gate":"dashboard/debate.html funcional + typecheck + tests pass","dependsOn":["T-02"],"attempts":0,"rejections":0,"artifacts":".oficina\\artifacts\\T-05","result":null,"helpRequests":[],"helpHops":0},{"id":"T-06","title":"Implementar panel de intervencion del jefe (Commander/Integrator/Architect decisions)","type":"implementation","state":"todo","owner":"e-xr-immersive-developer","reviewer":null,"requiredCapabilities":["typescript","frontend","decision-tracking"],"scope":{"mayTouch":["dashboard/**"],"forbidden":["src/**",".oficina/**","dist/**","node_modules/**"]},"acceptance":["Tres secciones: Commander (scope/prioridades/escalations), Integrator (routing/leases/gates/deadlocks), Architect (tech decisions/patterns/contracts)","Lee decisions de state.json + mensajes en canales de coordinadores","Cada decision: timestamp, actor, cardId, decision, razon, siguiente accion","Filtro por coordinador, por card, por tipo de decision","Enlace a card afectada y a artifacts correspondientes"],"gate":"dashboard/leadership.html funcional + typecheck + tests pass","dependsOn":["T-02"],"attempts":0,"rejections":0,"artifacts":".oficina\\artifacts\\T-06","result":null,"helpRequests":[],"helpHops":0},{"id":"T-07","title":"Implementar Kanban visual de cards con drag-drop y acceptance criteria","type":"implementation","state":"in_progress","owner":"e-xr-immersive-developer","reviewer":null,"requiredCapabilities":["typescript","frontend","kanban","drag-drop"],"scope":{"mayTouch":["dashboard/**"],"forbidden":["src/**",".oficina/**","dist/**","node_modules/**"]},"acceptance":["Columnas: TODO, IN_PROGRESS, REVIEW, DONE, REWORK, BLOCKED, ESCALATED","Cards muestran: id, titulo, owner avatar, reviewer avatar, rejection count, acceptance checklist","Drag-drop opcional entre columnas (solo visual, no persiste - read-only dashboard)","Click en card abre modal con detalle completo: scope, acceptance, gate, artifacts, evidence links","Badges de estado gate: PASS/FAIL/NOT_RUN con colores","Filtro por owner, por tipo, por estado"],"gate":"dashboard/kanban.html funcional + typecheck + tests pass","dependsOn":["T-02"],"attempts":0,"rejections":0,"artifacts":".oficina\\artifacts\\T-07","result":null,"helpRequests":[],"helpHops":0},{"id":"T-08","title":"Implementar animacion de Factory creando agentes nuevos","type":"implementation","state":"todo","owner":"e-xr-immersive-developer","reviewer":null,"requiredCapabilities":["typescript","frontend","animation","svg-canvas"],"scope":{"mayTouch":["dashboard/**"],"forbidden":["src/**",".oficina/**","dist/**","node_modules/**"]},"acceptance":["Deteccion: cuando hay card sin match en catalogo (cobertura < 60% en roster/state)","Animacion secuencial: Factory creando agente... -> archivo .md apareciendo en .opencode/agents/ -> entrada al catalogo dinamico -> match en proxima card","Visualizacion: progress steps con checkmarks, archivo escribiendose con cursor parpadeante","Toast/notification cuando factory completa y agente aparece en roster","Click en notificacion abre ficha del agente fabricado"],"gate":"dashboard/factory.html funcional + typecheck + tests pass","dependsOn":["T-02"],"attempts":0,"rejections":0,"artifacts":".oficina\\artifacts\\T-08","result":null,"helpRequests":[],"helpHops":0},{"id":"T-09","title":"Implementar panel lateral de Gates y Evidencia con spinners y resultados","type":"implementation","state":"todo","owner":"e-testing-api-tester","reviewer":"e-design-ui-finish-gate-reviewer","requiredCapabilities":["typescript","frontend","real-time-ui","testing"],"scope":{"mayTouch":["dashboard/**"],"forbidden":["src/**",".oficina/**","dist/**","node_modules/**"]},"acceptance":["Panel lateral fijo (desktop) / bottom sheet (mobile) mostrando 7 gates","Fases: Build, Type check, Lint, Tests+coverage, Security grep, Diff review, Requisitos","Spinners animados mientras corren, iconos PASS/FAIL/NOT_RUN/N/A al terminar","Click en fase muestra output real (pega de evidence.md/gates.md)","Enlace a evidence.md y handoff.md de la card","Gate 7 (Requisitos): lista acceptance criteria con check/cross y link a evidencia registrada"],"gate":"dashboard/gates.html funcional + typecheck + tests pass","dependsOn":["T-02"],"attempts":0,"rejections":0,"artifacts":".oficina\\artifacts\\T-09","result":null,"helpRequests":[],"helpHops":0},{"id":"T-10","title":"Integrar todo en dashboard.html unico + README-DASHBOARD.md","type":"implementation","state":"todo","owner":"e-testing-api-tester","reviewer":"e-design-ui-finish-gate-reviewer","requiredCapabilities":["typescript","frontend","integration","documentation"],"scope":{"mayTouch":["dashboard/**"],"forbidden":["src/**",".oficina/**","dist/**","node_modules/**"]},"acceptance":["dashboard.html unico con todos los paneles (layout: header + sidebar tabs + main area)","Navegacion por tabs: Mesa | Bus | Debate | Jefe | Kanban | Factory | Gates","Persistencia de tab activo en localStorage","Tema oscuro/claro con toggle persistido","Atajos de teclado: 1-7 tabs, Espacio pause/resume polling, R refresh, ? ayuda","README-DASHBOARD.md con: como usarlo, que muestra cada panel, atajos, troubleshooting"],"gate":"dashboard.html abre en browser file:// + typecheck + tests pass + README completo","dependsOn":["T-03","T-04","T-05","T-06","T-07","T-08","T-09"],"attempts":0,"rejections":0,"artifacts":".oficina\\artifacts\\T-10","result":null,"helpRequests":[],"helpHops":0}],"gates":{"T-00":{"ranAt":"2026-10-29T03:28:55.630Z","technical":[{"phase":"build","command":"npm run build","outcome":"pass","detail":"exit 0 en 11254ms","reason":""},{"phase":"typecheck","command":"npm run typecheck","outcome":"pass","detail":"exit 0 en 10481ms","reason":""},{"phase":"lint","command":null,"outcome":"na","detail":"el proyecto no define script `lint`","reason":""},{"phase":"test","command":"npm run test","outcome":"pass","detail":"exit 0 en 38518ms","reason":""},{"phase":"secrecy","command":"scan:secretos","outcome":"pass","detail":"sin secretos en el scope","reason":""},{"phase":"diff","command":"git diff --stat HEAD","outcome":"pass","detail":"exit 0 en 448ms","reason":""}],"security":[{"phase":"secrecy","command":"scan:credenciales asignadas","outcome":"pass","detail":"credenciales asignadas ÔÇö limpio","reason":""},{"phase":"secrecy","command":"scan:claves de cloud","outcome":"pass","detail":"claves de cloud ÔÇö limpio","reason":""},{"phase":"secrecy","command":"scan:tokens de provider","outcome":"pass","detail":"tokens de provider ÔÇö limpio","reason":""}],"requirements":[],"technicalOutcome":"pass","securityOutcome":"pass","requirementsOutcome":"not_run","verdict":"pass"}},"evidence":{"T-00":[{"id":"ev-t-00-0-test-tests-gates-test-ts-guard-anti-gate-verde-vacio-","cardId":"T-00","requirement":"runTechnicalGates participa del guard: si la fase test sale con exit 0 pero testsActuallyRan es false, la fase test se reporta como not_run con reason expl├¡cita de gate verde vac├¡o (no PASS silencioso ni silencioso en otros campos)","requirementIndex":0,"mandatory":false,"kind":"test","level":2,"ref":"tests/gates.test.ts > Guard anti-gate-verde-vac├¡o > degrada a not_run la fase test que sale 0 sin tests","note":"runTechnicalGates marca la fase test como not_run con reason EMPTY_TEST_GATE_REASON cuando exit 0 y no hay conteo; conserva exitCode 0 y output real.","recordedAt":"1970-01-01T00:00:00.000Z","recordedBy":"e-testing-api-tester","passed":true},{"id":"ev-t-00-1-test-tests-gates-test-ts-guard-anti-gate-verde-vacio-","cardId":"T-00","requirement":"si testsActuallyRan es true (p. ej. conteo legible '5 passed' o 'Tests: 5'), la fase test sigue en PASS sin regresi├│n","requirementIndex":1,"mandatory":false,"kind":"test","level":2,"ref":"tests/gates.test.ts > Guard anti-gate-verde-vac├¡o > no degrada cuando el conteo es legible ('5 passed' / 'Tests: 5')","note":"Con conteo legible la fase test sigue en PASS; sin regresi├│n.","recordedAt":"1970-01-01T00:00:00.000Z","recordedBy":"e-testing-api-tester","passed":true},{"id":"ev-t-00-2-test-tests-gates-test-ts-guard-anti-gate-verde-vacio-","cardId":"T-00","requirement":"consolidate con ese resultado produce technicalOutcome fail/not_run (passed=false) y renderGate muestra el motivo de forma legible","requirementIndex":2,"mandatory":false,"kind":"test","level":2,"ref":"tests/gates.test.ts > Guard anti-gate-verde-vac├¡o > consolidate propaga not_run / renderGate muestra el motivo","note":"consolidate devuelve technicalOutcome=not_run y passed=false; renderGate incluye columna Motivo con el texto del gate verde vac├¡o.","recordedAt":"1970-01-01T00:00:00.000Z","recordedBy":"e-testing-api-tester","passed":true},{"id":"ev-t-00-3-test-tests-gates-test-ts-guard-anti-gate-verde-vacio-","cardId":"T-00","requirement":"pmOfficeOffice.runGates / flujo Office.runGates queda cableado al guard v├¡a runTechnicalGates (sin duplicar l├│gica)","requirementIndex":3,"mandatory":false,"kind":"test","level":2,"ref":"tests/gates.test.ts > Guard anti-gate-verde-vac├¡o > Office.runGates hereda el guard v├¡a runTechnicalGates","note":"Office.runGates no reimplementa el guard: delega en runTechnicalGates; gates.md en disco muestra el motivo.","recordedAt":"1970-01-01T00:00:00.000Z","recordedBy":"e-testing-api-tester","passed":true},{"id":"ev-t-00-4-artifact-tests-gates-test-ts","cardId":"T-00","requirement":"regresi├│n cubierta por tests vitest nuevos en tests/gates.test.ts (existen ya tests de testsActuallyRan; se agregan los del cableado)","requirementIndex":4,"mandatory":false,"kind":"artifact","level":2,"ref":"tests/gates.test.ts","note":"8 tests vitest nuevos en el describe 'Guard anti-gate-verde-vac├¡o' (27 en el archivo).","recordedAt":"1970-01-01T00:00:00.000Z","recordedBy":"e-testing-api-tester","passed":true},{"id":"ev-t-00-5-test-npm-run-typecheck-npm-test","cardId":"T-00","requirement":"npm run typecheck sin errores y npm test todo en verde (las 456 existentes + nuevas)","requirementIndex":5,"mandatory":false,"kind":"test","level":2,"ref":"npm run typecheck && npm test","note":"typecheck exit 0; vitest 27 archivos / 464 tests en verde, 0 fallidos.","recordedAt":"1970-01-01T00:00:00.000Z","recordedBy":"e-testing-api-tester","passed":true}]}}</script>
|
|
925
|
+
<!--END_DATASET-->
|
|
926
|
+
|
|
927
|
+
<script src="./kanban-source.js"></script>
|
|
928
|
+
<script>
|
|
929
|
+
(function () {
|
|
930
|
+
"use strict"
|
|
931
|
+
|
|
932
|
+
var Core = globalThis.KanbanSource
|
|
933
|
+
var board = document.getElementById("board")
|
|
934
|
+
var modal = document.getElementById("card-modal")
|
|
935
|
+
var modalContent = document.getElementById("card-modal-content")
|
|
936
|
+
var live = document.getElementById("live-region")
|
|
937
|
+
var ownerSelect = document.getElementById("filter-owner")
|
|
938
|
+
var typeSelect = document.getElementById("filter-type")
|
|
939
|
+
var stateSelect = document.getElementById("filter-state")
|
|
940
|
+
var searchInput = document.getElementById("filter-search")
|
|
941
|
+
var sourceLabel = document.getElementById("data-source")
|
|
942
|
+
|
|
943
|
+
var view = Core.normalizeDataset({})
|
|
944
|
+
var filters = { owner: "all", type: "all", state: "all", q: "" }
|
|
945
|
+
var lastFocused = null
|
|
946
|
+
|
|
947
|
+
// ÔöÇÔöÇ Dataset ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇ
|
|
948
|
+
|
|
949
|
+
function embeddedDataset() {
|
|
950
|
+
var el = document.getElementById("kanban-dataset")
|
|
951
|
+
if (!el) return { cards: [], agents: {} }
|
|
952
|
+
try {
|
|
953
|
+
return JSON.parse(el.textContent)
|
|
954
|
+
} catch (err) {
|
|
955
|
+
return { cards: [], agents: {} }
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function datasetUrl() {
|
|
960
|
+
var params = new URLSearchParams(location.search)
|
|
961
|
+
return params.get("data")
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function describe(dataset) {
|
|
965
|
+
var meta = dataset.meta || {}
|
|
966
|
+
var run = document.getElementById("run-meta")
|
|
967
|
+
var objective = meta.objective || "(sin objetivo registrado)"
|
|
968
|
+
run.innerHTML = "run <strong>" + Core.escapeHtml(meta.runId || "ÔÇö") + "</strong> ┬À estado <strong>" +
|
|
969
|
+
Core.escapeHtml(meta.status || "ÔÇö") + "</strong> ┬À " + Core.escapeHtml(objective)
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function load(dataset, label) {
|
|
973
|
+
view = Core.normalizeDataset(dataset)
|
|
974
|
+
describe(dataset)
|
|
975
|
+
sourceLabel.textContent = label
|
|
976
|
+
buildFilterOptions()
|
|
977
|
+
render()
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
function loadFrom(url, label) {
|
|
981
|
+
return fetch(url, { cache: "no-store" })
|
|
982
|
+
.then(function (res) {
|
|
983
|
+
if (!res.ok) throw new Error("HTTP " + res.status)
|
|
984
|
+
return res.json()
|
|
985
|
+
})
|
|
986
|
+
.then(function (dataset) {
|
|
987
|
+
load(dataset, label)
|
|
988
|
+
announce("Datos recargados desde " + label)
|
|
989
|
+
})
|
|
990
|
+
.catch(function (err) {
|
|
991
|
+
load(embeddedDataset(), "snapshot embebido (fetch fall├│: " + err.message + ")")
|
|
992
|
+
announce("No se pudo leer " + label + ". Se usa el snapshot embebido.")
|
|
993
|
+
})
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function bootstrap() {
|
|
997
|
+
var url = datasetUrl()
|
|
998
|
+
if (url) {
|
|
999
|
+
loadFrom(url, url)
|
|
1000
|
+
} else {
|
|
1001
|
+
load(embeddedDataset(), "snapshot embebido")
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// ÔöÇÔöÇ Filtros ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇ
|
|
1006
|
+
|
|
1007
|
+
function option(value, label) {
|
|
1008
|
+
var opt = document.createElement("option")
|
|
1009
|
+
opt.value = value
|
|
1010
|
+
opt.textContent = label
|
|
1011
|
+
return opt
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
function buildFilterOptions() {
|
|
1015
|
+
while (ownerSelect.options.length > 1) ownerSelect.remove(1)
|
|
1016
|
+
var owners = {}
|
|
1017
|
+
view.cards.forEach(function (card) {
|
|
1018
|
+
if (card.owner) owners[card.owner.id] = card.owner.name
|
|
1019
|
+
})
|
|
1020
|
+
Object.keys(owners).sort().forEach(function (id) {
|
|
1021
|
+
ownerSelect.appendChild(option(id, owners[id] + " (" + id + ")"))
|
|
1022
|
+
})
|
|
1023
|
+
if (view.cards.some(function (card) { return !card.owner })) {
|
|
1024
|
+
ownerSelect.appendChild(option("unassigned", "sin asignar"))
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
while (typeSelect.options.length > 1) typeSelect.remove(1)
|
|
1028
|
+
var types = {}
|
|
1029
|
+
view.cards.forEach(function (card) { types[card.type] = true })
|
|
1030
|
+
Object.keys(types).sort().forEach(function (type) {
|
|
1031
|
+
typeSelect.appendChild(option(type, type))
|
|
1032
|
+
})
|
|
1033
|
+
|
|
1034
|
+
while (stateSelect.options.length > 1) stateSelect.remove(1)
|
|
1035
|
+
Core.COLUMNS.forEach(function (column) {
|
|
1036
|
+
stateSelect.appendChild(option(column.id, column.label))
|
|
1037
|
+
})
|
|
1038
|
+
|
|
1039
|
+
ownerSelect.value = filters.owner
|
|
1040
|
+
typeSelect.value = filters.type
|
|
1041
|
+
stateSelect.value = filters.state
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
function readFilters() {
|
|
1045
|
+
filters.owner = ownerSelect.value
|
|
1046
|
+
filters.type = typeSelect.value
|
|
1047
|
+
filters.state = stateSelect.value
|
|
1048
|
+
filters.q = searchInput.value.trim()
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// ÔöÇÔöÇ Render ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇ
|
|
1052
|
+
|
|
1053
|
+
function render() {
|
|
1054
|
+
var groups = Core.groupByColumn(Core.filterCards(view.cards, filters))
|
|
1055
|
+
var active = filters.owner !== "all" || filters.type !== "all" || filters.state !== "all" || filters.q !== ""
|
|
1056
|
+
|
|
1057
|
+
Core.COLUMN_IDS.forEach(function (id) {
|
|
1058
|
+
var body = board.querySelector('[data-cards="' + id + '"]')
|
|
1059
|
+
var column = Core.COLUMNS.filter(function (c) { return c.id === id })[0]
|
|
1060
|
+
body.innerHTML = Core.columnBodyHtml(column, groups[id])
|
|
1061
|
+
var counter = board.querySelector('[data-count="' + id + '"]')
|
|
1062
|
+
var total = view.cards.filter(function (card) { return card.state === id }).length
|
|
1063
|
+
counter.textContent = active ? groups[id].length + "/" + total : String(total)
|
|
1064
|
+
})
|
|
1065
|
+
|
|
1066
|
+
var visible = Core.filterCards(view.cards, filters).length
|
|
1067
|
+
document.getElementById("count-total").textContent = String(view.cards.length)
|
|
1068
|
+
document.getElementById("count-visible").textContent = String(visible)
|
|
1069
|
+
|
|
1070
|
+
var msg = visible + " de " + view.cards.length + " cards visibles"
|
|
1071
|
+
if (active) msg += " con filtros activos"
|
|
1072
|
+
live.textContent = msg
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function announce(message) {
|
|
1076
|
+
live.textContent = message
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function cardById(id) {
|
|
1080
|
+
for (var i = 0; i < view.cards.length; i++) {
|
|
1081
|
+
if (view.cards[i].id === id) return view.cards[i]
|
|
1082
|
+
}
|
|
1083
|
+
return null
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// ÔöÇÔöÇ Modal ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇ
|
|
1087
|
+
|
|
1088
|
+
function openCard(id) {
|
|
1089
|
+
var card = cardById(id)
|
|
1090
|
+
if (!card) return
|
|
1091
|
+
lastFocused = document.activeElement
|
|
1092
|
+
modalContent.innerHTML = Core.modalHtml(card)
|
|
1093
|
+
if (typeof modal.showModal === "function") {
|
|
1094
|
+
modal.showModal()
|
|
1095
|
+
} else {
|
|
1096
|
+
modal.setAttribute("open", "open")
|
|
1097
|
+
}
|
|
1098
|
+
var closeBtn = document.getElementById("card-modal-close")
|
|
1099
|
+
if (closeBtn) closeBtn.focus()
|
|
1100
|
+
announce("Detalle de " + card.id + " abierto")
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function closeModal() {
|
|
1104
|
+
if (typeof modal.close === "function") modal.close()
|
|
1105
|
+
else modal.removeAttribute("open")
|
|
1106
|
+
if (lastFocused && typeof lastFocused.focus === "function") lastFocused.focus()
|
|
1107
|
+
announce("Detalle cerrado")
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
document.getElementById("card-modal-close").addEventListener("click", closeModal)
|
|
1111
|
+
modal.addEventListener("click", function (event) {
|
|
1112
|
+
if (event.target === modal) closeModal()
|
|
1113
|
+
})
|
|
1114
|
+
modal.addEventListener("close", function () {
|
|
1115
|
+
if (lastFocused && typeof lastFocused.focus === "function") lastFocused.focus()
|
|
1116
|
+
})
|
|
1117
|
+
|
|
1118
|
+
// ÔöÇÔöÇ Interacci├│n del tablero ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇ
|
|
1119
|
+
|
|
1120
|
+
board.addEventListener("click", function (event) {
|
|
1121
|
+
var card = event.target.closest(".card")
|
|
1122
|
+
if (card) openCard(card.getAttribute("data-card-id"))
|
|
1123
|
+
})
|
|
1124
|
+
|
|
1125
|
+
board.addEventListener("keydown", function (event) {
|
|
1126
|
+
var card = event.target.closest ? event.target.closest(".card") : null
|
|
1127
|
+
if (!card) return
|
|
1128
|
+
if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
|
|
1129
|
+
event.preventDefault()
|
|
1130
|
+
openCard(card.getAttribute("data-card-id"))
|
|
1131
|
+
}
|
|
1132
|
+
})
|
|
1133
|
+
|
|
1134
|
+
// ÔöÇÔöÇ Drag & drop (visual, no persiste) ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇ
|
|
1135
|
+
// DRAG-DROP:start
|
|
1136
|
+
var dragged = null
|
|
1137
|
+
|
|
1138
|
+
function onDragStart(event) {
|
|
1139
|
+
var card = event.target.closest ? event.target.closest(".card") : null
|
|
1140
|
+
if (!card) return
|
|
1141
|
+
dragged = card
|
|
1142
|
+
card.classList.add("dragging")
|
|
1143
|
+
if (event.dataTransfer) {
|
|
1144
|
+
event.dataTransfer.effectAllowed = "move"
|
|
1145
|
+
try { event.dataTransfer.setData("text/plain", card.getAttribute("data-card-id")) } catch (err) { /* noop */ }
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function onDragEnd() {
|
|
1150
|
+
if (dragged) dragged.classList.remove("dragging")
|
|
1151
|
+
dragged = null
|
|
1152
|
+
board.querySelectorAll(".column-body.drop-target").forEach(function (body) {
|
|
1153
|
+
body.classList.remove("drop-target")
|
|
1154
|
+
})
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function onDragOver(event) {
|
|
1158
|
+
var zone = event.target.closest ? event.target.closest("[data-dropzone]") : null
|
|
1159
|
+
if (!zone) return
|
|
1160
|
+
event.preventDefault()
|
|
1161
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = "move"
|
|
1162
|
+
board.querySelectorAll(".column-body.drop-target").forEach(function (body) {
|
|
1163
|
+
if (body !== zone) body.classList.remove("drop-target")
|
|
1164
|
+
})
|
|
1165
|
+
zone.classList.add("drop-target")
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function onDragLeave(event) {
|
|
1169
|
+
var zone = event.target.closest ? event.target.closest("[data-dropzone]") : null
|
|
1170
|
+
if (!zone) return
|
|
1171
|
+
if (event.relatedTarget && zone.contains(event.relatedTarget)) return
|
|
1172
|
+
zone.classList.remove("drop-target")
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// El drop solo mueve el nodo en el DOM. No hay red ni storage: el tablero
|
|
1176
|
+
// es read-only por contrato, y un reload descarta cualquier movimiento.
|
|
1177
|
+
function onDrop(event) {
|
|
1178
|
+
var zone = event.target.closest ? event.target.closest("[data-dropzone]") : null
|
|
1179
|
+
if (!zone || !dragged) return
|
|
1180
|
+
event.preventDefault()
|
|
1181
|
+
var card = dragged
|
|
1182
|
+
var targetState = zone.getAttribute("data-cards")
|
|
1183
|
+
var originState = card.getAttribute("data-state")
|
|
1184
|
+
var originBody = card.parentElement
|
|
1185
|
+
zone.appendChild(card)
|
|
1186
|
+
card.setAttribute("data-visual-state", targetState)
|
|
1187
|
+
card.setAttribute("data-moved", targetState !== originState ? "true" : "false")
|
|
1188
|
+
var note = card.querySelector(".card-moved-note")
|
|
1189
|
+
if (targetState !== originState) {
|
|
1190
|
+
if (!note) {
|
|
1191
|
+
note = document.createElement("p")
|
|
1192
|
+
note.className = "card-moved-note check-more"
|
|
1193
|
+
note.textContent = "movida visualmente desde " + originState + " (no persiste)"
|
|
1194
|
+
card.appendChild(note)
|
|
1195
|
+
} else {
|
|
1196
|
+
note.textContent = "movida visualmente desde " + originState + " (no persiste)"
|
|
1197
|
+
}
|
|
1198
|
+
} else if (note) {
|
|
1199
|
+
note.remove()
|
|
1200
|
+
}
|
|
1201
|
+
if (originBody) originBody.classList.remove("drop-target")
|
|
1202
|
+
zone.classList.remove("drop-target")
|
|
1203
|
+
dragged = null
|
|
1204
|
+
refreshCountsFromDom()
|
|
1205
|
+
announce("Card " + card.getAttribute("data-card-id") + " movida a " + targetState + " (cambio visual, no se guarda)")
|
|
1206
|
+
}
|
|
1207
|
+
// DRAG-DROP:end
|
|
1208
|
+
|
|
1209
|
+
function refreshCountsFromDom() {
|
|
1210
|
+
Core.COLUMN_IDS.forEach(function (id) {
|
|
1211
|
+
var body = board.querySelector('[data-cards="' + id + '"]')
|
|
1212
|
+
var count = body ? body.querySelectorAll(".card").length : 0
|
|
1213
|
+
var counter = board.querySelector('[data-count="' + id + '"]')
|
|
1214
|
+
if (counter) counter.textContent = String(count)
|
|
1215
|
+
})
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
board.addEventListener("dragstart", onDragStart)
|
|
1219
|
+
board.addEventListener("dragend", onDragEnd)
|
|
1220
|
+
board.addEventListener("dragover", onDragOver)
|
|
1221
|
+
board.addEventListener("dragleave", onDragLeave)
|
|
1222
|
+
board.addEventListener("drop", onDrop)
|
|
1223
|
+
|
|
1224
|
+
// ÔöÇÔöÇ Wiring de filtros ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇ
|
|
1225
|
+
|
|
1226
|
+
function onFilterChange() {
|
|
1227
|
+
readFilters()
|
|
1228
|
+
render()
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
ownerSelect.addEventListener("change", onFilterChange)
|
|
1232
|
+
typeSelect.addEventListener("change", onFilterChange)
|
|
1233
|
+
stateSelect.addEventListener("change", onFilterChange)
|
|
1234
|
+
searchInput.addEventListener("input", onFilterChange)
|
|
1235
|
+
|
|
1236
|
+
document.getElementById("reset-view").addEventListener("click", function () {
|
|
1237
|
+
filters = { owner: "all", type: "all", state: "all", q: "" }
|
|
1238
|
+
ownerSelect.value = "all"
|
|
1239
|
+
typeSelect.value = "all"
|
|
1240
|
+
stateSelect.value = "all"
|
|
1241
|
+
searchInput.value = ""
|
|
1242
|
+
render()
|
|
1243
|
+
announce("Vista restablecida: movimientos visuales descartados")
|
|
1244
|
+
})
|
|
1245
|
+
|
|
1246
|
+
document.getElementById("refresh-data").addEventListener("click", function () {
|
|
1247
|
+
var url = datasetUrl()
|
|
1248
|
+
if (url) loadFrom(url, url)
|
|
1249
|
+
else load(embeddedDataset(), "snapshot embebido")
|
|
1250
|
+
})
|
|
1251
|
+
|
|
1252
|
+
bootstrap()
|
|
1253
|
+
})()
|
|
1254
|
+
</script>
|
|
1255
|
+
|
|
1256
|
+
|
|
1257
|
+
</body></html>
|