CallMeDaniel Claude Opus 4.6 (1M context) commited on
Commit
2eba4bf
·
1 Parent(s): 1fdf00c

docs: add frontend gap cards optimization plan

Browse files

4-task plan: add severity to backend model, rewrite renderQuestionCards
with generic rendering and severity support, simplify gapSubmitAll.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

docs/superpowers/plans/2026-04-13-frontend-gap-cards.md ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Frontend Gap Cards Optimization Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Make the frontend question cards a generic severity-aware renderer with no hardcoded category knowledge, letting the backend own all state mapping.
6
+
7
+ **Architecture:** Add `severity` field to `GeneratedQuestionCard` backend model. Rewrite frontend `renderQuestionCards()` to render all categories generically with severity indicators. Simplify `gapSubmitAll()` to just compose and send a message with no local state updates. Delete `gapDimChanged()`.
8
+
9
+ **Tech Stack:** Vanilla JS (no build step), Pydantic models, pytest
10
+
11
+ ---
12
+
13
+ ### File Structure
14
+
15
+ | File | Action | Responsibility |
16
+ |------|--------|----------------|
17
+ | `agents/gap_analyzer.py` | Modify | Add `severity` field to `GeneratedQuestionCard`, update system prompt |
18
+ | `tests/test_gap_analyzer.py` | Modify | Update model tests for new severity field |
19
+ | `web/index.html` | Modify | Rewrite card rendering, simplify submit, add severity CSS, delete dimension handler |
20
+
21
+ ---
22
+
23
+ ### Task 1: Add severity field to GeneratedQuestionCard
24
+
25
+ **Files:**
26
+ - Modify: `agents/gap_analyzer.py:31-39`
27
+ - Modify: `agents/gap_analyzer.py:55-74` (system prompt)
28
+ - Test: `tests/test_gap_analyzer.py`
29
+
30
+ - [ ] **Step 1: Write failing test for severity on question card**
31
+
32
+ Add to `tests/test_gap_analyzer.py` inside `TestModels`, after the existing `test_generated_question_card_defaults` test:
33
+
34
+ ```python
35
+ def test_question_card_severity_default(self):
36
+ card = GeneratedQuestionCard(
37
+ category="material",
38
+ question="What material?",
39
+ responsible_agent="engineering",
40
+ )
41
+ assert card.severity == "recommended"
42
+
43
+ def test_question_card_severity_blocking(self):
44
+ card = GeneratedQuestionCard(
45
+ category="material",
46
+ question="What material?",
47
+ responsible_agent="engineering",
48
+ severity="blocking",
49
+ )
50
+ assert card.severity == "blocking"
51
+
52
+ def test_question_card_severity_validation(self):
53
+ import pytest
54
+ with pytest.raises(Exception):
55
+ GeneratedQuestionCard(
56
+ category="x",
57
+ question="q",
58
+ responsible_agent="design",
59
+ severity="invalid",
60
+ )
61
+ ```
62
+
63
+ - [ ] **Step 2: Run tests to verify they fail**
64
+
65
+ Run: `uv run pytest tests/test_gap_analyzer.py::TestModels::test_question_card_severity_default tests/test_gap_analyzer.py::TestModels::test_question_card_severity_blocking tests/test_gap_analyzer.py::TestModels::test_question_card_severity_validation -v`
66
+ Expected: FAIL — `severity` field doesn't exist on `GeneratedQuestionCard`.
67
+
68
+ - [ ] **Step 3: Add severity field to GeneratedQuestionCard**
69
+
70
+ In `agents/gap_analyzer.py`, add the `severity` field to `GeneratedQuestionCard` (line 31-39). The field goes after `agent_color`:
71
+
72
+ Old:
73
+ ```python
74
+ class GeneratedQuestionCard(BaseModel):
75
+ """A contextual question card for the UI, generated by the LLM."""
76
+ category: str
77
+ question: str
78
+ responsible_agent: str
79
+ agent_name: str = ""
80
+ agent_color: str = ""
81
+ suggestions: list[str] = Field(default_factory=list)
82
+ allow_custom: bool = True
83
+ ```
84
+
85
+ New:
86
+ ```python
87
+ class GeneratedQuestionCard(BaseModel):
88
+ """A contextual question card for the UI, generated by the LLM."""
89
+ category: str
90
+ question: str
91
+ responsible_agent: str
92
+ agent_name: str = ""
93
+ agent_color: str = ""
94
+ severity: Literal["blocking", "recommended", "nice_to_have"] = "recommended"
95
+ suggestions: list[str] = Field(default_factory=list)
96
+ allow_custom: bool = True
97
+ ```
98
+
99
+ - [ ] **Step 4: Update system prompt to set severity on cards**
100
+
101
+ In `agents/gap_analyzer.py`, in the `_SYSTEM_PROMPT` string, after the line `- If no gaps exist, return has_gaps: false with empty lists.`, add:
102
+
103
+ ```
104
+ - Set severity on each question card matching the gap it addresses.
105
+ ```
106
+
107
+ - [ ] **Step 5: Run tests to verify they pass**
108
+
109
+ Run: `uv run pytest tests/test_gap_analyzer.py -v`
110
+ Expected: PASS (all 13 tests — 8 existing + 3 new)
111
+
112
+ - [ ] **Step 6: Commit**
113
+
114
+ ```bash
115
+ git add agents/gap_analyzer.py tests/test_gap_analyzer.py
116
+ git commit -m "feat: add severity field to GeneratedQuestionCard"
117
+ ```
118
+
119
+ ---
120
+
121
+ ### Task 2: Rewrite frontend renderQuestionCards with severity support
122
+
123
+ **Files:**
124
+ - Modify: `web/index.html:1361-1408` (CSS)
125
+ - Modify: `web/index.html:2169-2201` (renderQuestionCards function)
126
+
127
+ - [ ] **Step 1: Add severity CSS rules**
128
+
129
+ In `web/index.html`, after the existing `.gap-card-submit` rule (line 1408), add these new rules before the `/* ---- RESPONSIVE ---- */` comment:
130
+
131
+ ```css
132
+ .gap-card[data-severity="blocking"] {
133
+ border-left-color: var(--error);
134
+ }
135
+ .gap-card[data-severity="nice_to_have"] {
136
+ opacity: 0.7;
137
+ }
138
+ .gap-card-badge {
139
+ font-family: var(--font-mono);
140
+ font-size: 9px;
141
+ font-weight: 700;
142
+ color: var(--error);
143
+ margin-left: 6px;
144
+ text-transform: uppercase;
145
+ }
146
+ ```
147
+
148
+ - [ ] **Step 2: Add buildGapTitle helper function**
149
+
150
+ In `web/index.html`, replace line 2169 (`function renderQuestionCards(cards) {`) and the entire function body through line 2201 with the new implementation. First, add the helper right before `renderQuestionCards` (after line 2167 `let gapSelections = {};`):
151
+
152
+ ```javascript
153
+ function buildGapTitle(cards) {
154
+ const counts = { blocking: 0, recommended: 0, nice_to_have: 0 };
155
+ for (const c of cards) counts[c.severity || 'recommended']++;
156
+ const parts = [];
157
+ if (counts.blocking) parts.push(counts.blocking + ' REQUIRED');
158
+ if (counts.recommended) parts.push(counts.recommended + ' RECOMMENDED');
159
+ if (counts.nice_to_have) parts.push(counts.nice_to_have + ' OPTIONAL');
160
+ return parts.join(', ');
161
+ }
162
+ ```
163
+
164
+ - [ ] **Step 3: Rewrite renderQuestionCards function**
165
+
166
+ Replace the entire `renderQuestionCards` function (lines 2169-2201) with:
167
+
168
+ ```javascript
169
+ function renderQuestionCards(cards) {
170
+ if (!cards || cards.length === 0) return '';
171
+ gapSelections = {};
172
+
173
+ // Sort by severity: blocking first, recommended second, nice_to_have last
174
+ const order = { blocking: 0, recommended: 1, nice_to_have: 2 };
175
+ const sorted = [...cards].sort((a, b) => (order[a.severity] || 1) - (order[b.severity] || 1));
176
+
177
+ let html = '<div class="gap-cards" id="active-gap-cards">';
178
+ html += '<div class="gap-cards-title">' + escapeHtml(buildGapTitle(sorted)) + '</div>';
179
+
180
+ for (const card of sorted) {
181
+ const sev = card.severity || 'recommended';
182
+ html += '<div class="gap-card" style="border-left-color:' + (sev === 'blocking' ? 'var(--error)' : sev === 'nice_to_have' ? 'var(--border)' : escapeHtml(card.agent_color)) + ';" data-category="' + escapeHtml(card.category) + '" data-severity="' + escapeHtml(sev) + '">';
183
+
184
+ // Header
185
+ html += '<div class="gap-card-header">';
186
+ html += '<div class="gap-card-dot" style="background:' + escapeHtml(card.agent_color) + ';"></div>';
187
+ html += '<span class="gap-card-agent">' + escapeHtml(card.agent_name) + '</span>';
188
+ if (sev === 'blocking') html += '<span class="gap-card-badge">REQUIRED</span>';
189
+ html += '</div>';
190
+
191
+ // Question
192
+ html += '<div class="gap-card-question">' + escapeHtml(card.question) + '</div>';
193
+
194
+ // Generic input: chips + optional custom
195
+ if (card.suggestions && card.suggestions.length > 0) {
196
+ html += '<div class="wizard-chips">';
197
+ for (const s of card.suggestions) {
198
+ html += '<button class="wizard-chip" onclick="gapToggleChip(this,\'' + escapeHtml(s) + '\',\'' + escapeHtml(card.category) + '\')">' + escapeHtml(s) + '</button>';
199
+ }
200
+ html += '</div>';
201
+ }
202
+ if (card.allow_custom) {
203
+ html += '<input class="wizard-input" placeholder="Type your answer..." onchange="gapSetCustom(this.value,\'' + escapeHtml(card.category) + '\')">';
204
+ }
205
+
206
+ html += '</div>';
207
+ }
208
+
209
+ html += '<button class="gap-card-submit" onclick="gapSubmitAll()" style="width:100%;margin-top:4px;">Submit</button>';
210
+ html += '</div>';
211
+ return html;
212
+ }
213
+ ```
214
+
215
+ Key changes from old version:
216
+ - No `category === 'dimension'` special case
217
+ - Sorts cards by severity
218
+ - Sets `data-severity` attribute on each card
219
+ - Blocking cards get red border via inline style + "REQUIRED" badge
220
+ - Nice-to-have cards get dim border color
221
+ - Custom input shown whenever `allow_custom` is true (independent of suggestions)
222
+ - Chips and custom input are not mutually exclusive — both can appear
223
+
224
+ - [ ] **Step 4: Verify by inspecting the code**
225
+
226
+ Read the rewritten function to confirm:
227
+ - No reference to `'dimension'` category
228
+ - `data-severity` attribute is set
229
+ - `buildGapTitle()` is called
230
+ - Severity-based border color logic is correct
231
+ - `escapeHtml()` used on all dynamic content
232
+
233
+ - [ ] **Step 5: Commit**
234
+
235
+ ```bash
236
+ git add web/index.html
237
+ git commit -m "feat: rewrite renderQuestionCards with severity support and generic rendering"
238
+ ```
239
+
240
+ ---
241
+
242
+ ### Task 3: Simplify gapSubmitAll and delete gapDimChanged
243
+
244
+ **Files:**
245
+ - Modify: `web/index.html:2227-2274` (gapDimChanged + gapSubmitAll)
246
+
247
+ - [ ] **Step 1: Delete gapDimChanged function**
248
+
249
+ Delete the entire `gapDimChanged` function (lines 2227-2237):
250
+
251
+ ```javascript
252
+ function gapDimChanged() {
253
+ const w = document.getElementById('gap-dim-width')?.value;
254
+ const h = document.getElementById('gap-dim-height')?.value;
255
+ const d = document.getElementById('gap-dim-depth')?.value;
256
+ const parts = [];
257
+ if (w) parts.push(w + 'mm wide');
258
+ if (h) parts.push(h + 'mm high');
259
+ if (d) parts.push(d + 'mm deep');
260
+ if (parts.length > 0) gapSelections['dimension'] = parts.join(', ');
261
+ else delete gapSelections['dimension'];
262
+ }
263
+ ```
264
+
265
+ - [ ] **Step 2: Replace gapSubmitAll function**
266
+
267
+ Replace the entire `gapSubmitAll` function (lines 2239-2274) with:
268
+
269
+ ```javascript
270
+ function gapSubmitAll() {
271
+ const parts = [];
272
+ for (const [category, value] of Object.entries(gapSelections)) {
273
+ if (value) {
274
+ const label = category.replace(/_/g, ' ');
275
+ parts.push(label + ': ' + value);
276
+ }
277
+ }
278
+ if (parts.length === 0) return;
279
+ removeGapCards();
280
+ sendMessage(parts.join(', '));
281
+ }
282
+ ```
283
+
284
+ Key changes:
285
+ - No `gapDimChanged()` call
286
+ - No `designState` mutations (no `designState.material = ...`, no dimension parsing, etc.)
287
+ - No `saveState()` call
288
+ - Formats as `"category label: value"` with snake_case converted to spaces
289
+ - Just sends the message — backend handles state updates
290
+
291
+ - [ ] **Step 3: Remove stale CSS for dimension inputs in gap cards**
292
+
293
+ In `web/index.html`, delete line 1401:
294
+
295
+ ```css
296
+ .gap-card .wizard-dim-row { margin-top: 4px; }
297
+ ```
298
+
299
+ The `.wizard-dim-row` class is still used by the Guided wizard tab, so only remove the `.gap-card .wizard-dim-row` scoped rule.
300
+
301
+ - [ ] **Step 4: Verify no remaining references to gapDimChanged**
302
+
303
+ Search the file for `gapDimChanged` — should find zero results. Also search for `gap-dim-width`, `gap-dim-height`, `gap-dim-depth` — should find zero results (they were only in the deleted dimension rendering and `gapDimChanged`).
304
+
305
+ - [ ] **Step 5: Commit**
306
+
307
+ ```bash
308
+ git add web/index.html
309
+ git commit -m "refactor: simplify gapSubmitAll, remove category-specific state mappings"
310
+ ```
311
+
312
+ ---
313
+
314
+ ### Task 4: Final verification
315
+
316
+ **Files:**
317
+ - All modified files
318
+
319
+ - [ ] **Step 1: Run full backend test suite**
320
+
321
+ Run: `uv run pytest tests/ -v`
322
+ Expected: All tests PASS (275+ tests)
323
+
324
+ - [ ] **Step 2: Verify frontend file is valid**
325
+
326
+ Run: `grep -c 'gapDimChanged' web/index.html` — expected: `0`
327
+ Run: `grep -c 'gap-dim-width' web/index.html` — expected: `0`
328
+ Run: `grep -c "category === 'dimension'" web/index.html` — expected: `0`
329
+ Run: `grep -c 'designState.material' web/index.html` — should NOT appear inside `gapSubmitAll` (may appear elsewhere in file for other features)
330
+ Run: `grep -c 'buildGapTitle' web/index.html` — expected: `2` (definition + call)
331
+ Run: `grep -c 'data-severity' web/index.html` — expected: at least `3` (JS + CSS)
332
+
333
+ - [ ] **Step 3: Commit final state if any cleanup needed**
334
+
335
+ If all checks pass and no cleanup needed, skip this step. Otherwise:
336
+
337
+ ```bash
338
+ git add -A
339
+ git commit -m "fix: cleanup from frontend gap cards verification"
340
+ ```