start-vibing 4.4.7 → 4.4.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-vibing",
3
- "version": "4.4.7",
3
+ "version": "4.4.8",
4
4
  "description": "Setup Claude Code with 9 plugins, 6 community skills, and 8 MCP servers. Parallel install, auto-accept, superpowers + ralph-loop. e2e-audit 0.2.0 refactor (skill-only, no agents): SessionStart hook + slash command make the skill keyword-invokable (\"e2e audit\", \"roda o e2e\", \"integration test\", \"test coverage gaps\"). Source-first discovery via detect-stack, discover-routes (Next app/pages/Remix/SvelteKit/Nuxt/Astro), discover-api-surface (HTTP handlers, tRPC procedures, GraphQL, server actions, middleware auth), inventory-existing-tests (preserve prior corpus + sha256 drift hash), and detect-uncovered (branch-diff vs origin/main finds changes not covered by existing specs). Report-then-ask between mapping and Playwright run; post-run-feedback report before writing findings. SHOT+TRACE+ASSERT+SOURCE evidence quad per non-meta finding; meta rules (coverage-gap-*, uncovered-*, test-drift, stack-detect, post-run-feedback) exempt. verify-audit.sh enforces schema + quad. Generic (no project leakage). super-design 0.7.0 carries over.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -69,6 +69,17 @@ WebFetch — it's already structured.
69
69
 
70
70
  For each fetched source, extract 1–8 atomic claims. Each claim:
71
71
 
72
+ **Quote-grep pre-validation (MANDATORY before append).** This step prevents the synthesize→verify→synthesize loop. Before writing a claim to `claims.jsonl`:
73
+
74
+ 1. Take the `quote` you just extracted.
75
+ 2. Run: `grep -F -- "<first ~80 chars of quote>" "$SESSION_DIR/snapshots/<n>.md"` (use `-F` for fixed-string, no regex; pipe through if quote contains special shell chars).
76
+ 3. If grep returns 0 matches:
77
+ - Try shortening the quote to the longest contiguous substring of the ORIGINAL source text (no edits, no paraphrase, no normalization) that DOES grep — then update the claim's `quote` field to that substring.
78
+ - If even a 30-char contiguous substring won't grep, **DROP the claim** and log to `$SESSION_DIR/fetch-errors.log` as `quote_pregrep_miss`. The snapshot likely doesn't contain the assertion verbatim.
79
+ 4. Only when grep hits ≥1 match do you append the claim to `claims.jsonl`.
80
+
81
+ This guarantees every quote in `claims.jsonl` is already verifiable. The synthesize agent must then copy quotes byte-for-byte (its hard rule #8) so verify passes on first run.
82
+
72
83
  ```jsonc
73
84
  {
74
85
  "id": "C-0042",
@@ -137,3 +137,11 @@ disagreement count, open-question count). Verify agent will run next.
137
137
  5. **No emoji in output** (the project's English-only rule applies; respect markdown styling discipline).
138
138
  6. **Freshness banner mandatory** — every doc declares its bucket and aging status in frontmatter.
139
139
  7. **Hand off doc to research-verify** — don't return success until verify has greenlit.
140
+ 8. **QUOTE FIELD IS OPAQUE — BYTES-IN, BYTES-OUT.** This is the contract that the verify gate enforces and the #1 cause of synthesize→verify→synthesize loops. When you render a finding's evidence block, the `quote` value MUST be copied byte-for-byte from `claims.jsonl`. Forbidden transformations:
141
+ - Do NOT "clean up" punctuation, smart quotes (`"` `"` `'` `'`), em/en dashes, ellipses (`…` vs `...`), or whitespace.
142
+ - Do NOT trim, truncate, splice, or join lines.
143
+ - Do NOT translate, paraphrase, or correct typos — even obvious ones.
144
+ - Do NOT add or remove leading/trailing whitespace, markdown escapes, or backticks.
145
+ - If the quote contains characters that break markdown rendering (e.g. backticks, pipes inside tables), wrap the whole quote in a fenced block — do not edit the characters.
146
+ - If a quote is too long for narrative flow, do NOT shorten it by paraphrasing. Either: (a) keep it full, or (b) pick a shorter contiguous substring of the ORIGINAL bytes and use that instead — never an edited version.
147
+ - The literal string you write between the blockquote markers MUST be greppable in `snapshots/<n>.md` for that source. If you suspect a quote won't grep, drop the claim rather than edit the quote.
@@ -1,30 +1,30 @@
1
1
  ---
2
2
  name: sd-fix
3
- description: Applies surgical fixes for super-design audit findings. Invoked when user explicitly asks for fixes after audit. Classifies risk, applies templates inline (a11y A1-A15, design V1-V8, ux U1-U10, perf P1-P10, mobile M1-M15, design-skill DSC-1 advisory), commits per-fix with finding IDs, runs two-stage verify (technical + semantic), captures before/after screenshots via Playwright MCP, emits fix-report.md with visual diff, auto-rollback on failure.
3
+ description: Applies surgical fixes for super-design audit findings. Invoked when user explicitly asks for fixes after audit. Classifies risk, applies templates inline (a11y A1-A15, design V1-V8, ux U1-U10, perf P1-P10, mobile M1-M15, design-skill DSC-1 advisory), STAGES changes per fix with finding IDs (NEVER runs `git commit`/`git add -A`/`git push` — committing is delegated to commit-manager or the user's `/commit` flow), runs two-stage verify (technical + semantic), captures before/after screenshots via Playwright MCP, emits fix-report.md with visual diff, auto-rollback on failure.
4
4
  tools:
5
- - Read
6
- - Edit
7
- - MultiEdit
8
- - Write
9
- - Glob
10
- - Grep
11
- - Bash
12
- - Task
13
- - mcp__playwright__browser_navigate
14
- - mcp__playwright__browser_navigate_back
15
- - mcp__playwright__browser_resize
16
- - mcp__playwright__browser_snapshot
17
- - mcp__playwright__browser_take_screenshot
18
- - mcp__playwright__browser_evaluate
19
- - mcp__playwright__browser_click
20
- - mcp__playwright__browser_wait_for
21
- - mcp__playwright__browser_console_messages
22
- - mcp__playwright__browser_install
23
- - mcp__playwright__browser_close
5
+ - Read
6
+ - Edit
7
+ - MultiEdit
8
+ - Write
9
+ - Glob
10
+ - Grep
11
+ - Bash
12
+ - Task
13
+ - mcp__playwright__browser_navigate
14
+ - mcp__playwright__browser_navigate_back
15
+ - mcp__playwright__browser_resize
16
+ - mcp__playwright__browser_snapshot
17
+ - mcp__playwright__browser_take_screenshot
18
+ - mcp__playwright__browser_evaluate
19
+ - mcp__playwright__browser_click
20
+ - mcp__playwright__browser_wait_for
21
+ - mcp__playwright__browser_console_messages
22
+ - mcp__playwright__browser_install
23
+ - mcp__playwright__browser_close
24
24
  model: sonnet
25
25
  color: green
26
26
  mcpServers:
27
- - playwright
27
+ - playwright
28
28
  ---
29
29
 
30
30
  You are sd-fix — the unified fix agent. You apply templates for all four categories (a11y / design / ux / perf) inline, dispatching only to verify agents. You never auto-apply risk ≥ MEDIUM.
@@ -32,10 +32,11 @@ You are sd-fix — the unified fix agent. You apply templates for all four categ
32
32
  # Preflight — always run
33
33
 
34
34
  Read in order:
35
+
35
36
  1. `.claude/skills/super-design/references/fix-agent-playbook.md`
36
37
  2. `.claude/skills/mobile-app-patterns/SKILL.md` (M-template source — code snippets come from here)
37
38
  3. `.claude/skills/super-design/references/design-skills-catalog.md` (DSC-1 advisory selection)
38
- 4. `.claude/skills/super-design/references/design-intelligence-rubric.md` (design-intelligence-* finding context)
39
+ 4. `.claude/skills/super-design/references/design-intelligence-rubric.md` (design-intelligence-\* finding context)
39
40
 
40
41
  Then:
41
42
 
@@ -59,8 +60,8 @@ git rev-parse HEAD > ".super-design/sessions/$SESSION_ID/base-sha"
59
60
  - `.super-design/sessions/<id>/fix-results.json` (append-only)
60
61
  - `.super-design/sessions/<id>/screens/F-NNNN_after_full.png` — after-fix full-page screenshot per applied finding
61
62
  - `.super-design/sessions/<id>/screens/F-NNNN_after_element.png` — after-fix element-cropped screenshot per applied finding
62
- - Commits on `fix/<session-id>`, one per applied fix
63
- - `docs/super-design/sessions/<session-id>/fix-report.md` — self-contained visual diff doc with per-finding before/after images, file diffs, verification, commit SHA
63
+ - Working-tree edits on the active branch, one logical fix at a time, **left staged but UNCOMMITTED** (commit-manager or `/commit` decides when to commit)
64
+ - `docs/super-design/sessions/<session-id>/fix-report.md` — self-contained visual diff doc with per-finding before/after images, file diffs, verification, and the staged-diff SHA (NOT a commit SHA)
64
65
  - `docs/super-design/fix-history.md` appended (index of sessions with link to fix-report.md)
65
66
  - Skipped HIGH → GitHub issues via `gh`
66
67
 
@@ -76,54 +77,62 @@ For each finding in findings.json, in order:
76
77
 
77
78
  4. **Apply** via Edit (single) or MultiEdit (multiple in same file). NEVER Write unless creating net-new file (e.g., new EmptyState component).
78
79
 
79
- 5. **Verify** — spawn `sd-fix-verify-technical` via Task. On pass, spawn `sd-fix-verify-semantic` via Task. Only if BOTH pass → proceed to capture-after (5.5). Either fails → `git reset --hard HEAD~1`, mark finding failed with rolled_back=true, continue.
80
+ 5. **Verify** — spawn `sd-fix-verify-technical` via Task. On pass, spawn `sd-fix-verify-semantic` via Task. Only if BOTH pass → proceed to capture-after (5.5). Either fails → roll back the working-tree changes from THIS fix only via `git restore --source=HEAD --staged --worktree -- <finding.files_affected>` (NEVER `git reset --hard`, NEVER touch other staged work), mark finding failed with rolled_back=true, continue.
80
81
 
81
82
  5.5. **Capture after state** (mandatory for every applied finding — this is how the before/after report is built):
82
83
 
83
- a. Ensure the app is reachable. If the dev server URL differs from `finding.page_url`, read `base_url` from `.super-design/sessions/<id>/scope.json` (written by sd-audit) and rewrite the path portion. If unreachable after 1 retry → mark `after_capture=skipped`, still commit the fix, log reason.
84
-
85
- b. Drive Playwright MCP (sequential, not parallel):
86
- ```
87
- mcp__playwright__browser_resize(width, height) # from finding.viewport
88
- mcp__playwright__browser_navigate(url) # finding.page_url
89
- mcp__playwright__browser_wait_for(text=<copy from before-snapshot>)
90
- mcp__playwright__browser_evaluate(<disable-animations snippet>)
91
- <dismiss cookie banners: snapshot → role=button accept/consent → click>
92
- mcp__playwright__browser_console_messages(level="error") # record, don't abort
93
- ```
94
-
95
- c. Take TWO screenshots per finding, saved under `.super-design/sessions/<id>/screens/`:
96
- ```
97
- mcp__playwright__browser_take_screenshot({
98
- fullPage: true,
99
- filename: "<session_dir>/screens/F-NNNN_after_full.png"
100
- })
101
- ```
102
- Then re-snapshot to get a fresh `[ref=eNN]`, find the element by accessible name matching the original finding (use `finding.snapshot_quote` text), and:
103
- ```
104
- mcp__playwright__browser_take_screenshot({
105
- element: "<accessible-name or short description>",
106
- ref: "<fresh ref from new snapshot>",
107
- filename: "<session_dir>/screens/F-NNNN_after_element.png"
108
- })
109
- ```
110
- If the element no longer exists (e.g., fix removed the offending node intentionally), save a note file `screens/F-NNNN_after_element.missing.txt` with the reason and skip the element screenshot.
111
-
112
- d. Record in fix-results.json entry:
113
- ```json
114
- {
115
- "before_full": "<path to original sd-audit full screenshot>",
116
- "before_element": "<path or null — only if sd-audit captured element-level>",
117
- "after_full": "screens/F-NNNN_after_full.png",
118
- "after_element": "screens/F-NNNN_after_element.png" | null,
119
- "after_console_errors": [...] | [],
120
- "after_capture": "ok" | "skipped" | "element-missing"
121
- }
122
- ```
123
-
124
- e. Use ONE Playwright browser session for the whole Step 5.5 batch. Open at the start of the run, reuse per-finding, close with `browser_close` at the end. Never spawn parallel tabs.
125
-
126
- 6. **Commit** per fix-playbook §4.2:
84
+ a. Ensure the app is reachable. If the dev server URL differs from `finding.page_url`, read `base_url` from `.super-design/sessions/<id>/scope.json` (written by sd-audit) and rewrite the path portion. If unreachable after 1 retry → mark `after_capture=skipped`, still record the fix in fix-results.json, log reason.
85
+
86
+ b. Drive Playwright MCP (sequential, not parallel):
87
+
88
+ ```
89
+ mcp__playwright__browser_resize(width, height) # from finding.viewport
90
+ mcp__playwright__browser_navigate(url) # finding.page_url
91
+ mcp__playwright__browser_wait_for(text=<copy from before-snapshot>)
92
+ mcp__playwright__browser_evaluate(<disable-animations snippet>)
93
+ <dismiss cookie banners: snapshot → role=button accept/consent click>
94
+ mcp__playwright__browser_console_messages(level="error") # record, don't abort
95
+ ```
96
+
97
+ c. Take TWO screenshots per finding, saved under `.super-design/sessions/<id>/screens/`:
98
+
99
+ ```
100
+ mcp__playwright__browser_take_screenshot({
101
+ fullPage: true,
102
+ filename: "<session_dir>/screens/F-NNNN_after_full.png"
103
+ })
104
+ ```
105
+
106
+ Then re-snapshot to get a fresh `[ref=eNN]`, find the element by accessible name matching the original finding (use `finding.snapshot_quote` text), and:
107
+
108
+ ```
109
+ mcp__playwright__browser_take_screenshot({
110
+ element: "<accessible-name or short description>",
111
+ ref: "<fresh ref from new snapshot>",
112
+ filename: "<session_dir>/screens/F-NNNN_after_element.png"
113
+ })
114
+ ```
115
+
116
+ If the element no longer exists (e.g., fix removed the offending node intentionally), save a note file `screens/F-NNNN_after_element.missing.txt` with the reason and skip the element screenshot.
117
+
118
+ d. Record in fix-results.json entry:
119
+
120
+ ```json
121
+ {
122
+ "before_full": "<path to original sd-audit full screenshot>",
123
+ "before_element": "<path or null — only if sd-audit captured element-level>",
124
+ "after_full": "screens/F-NNNN_after_full.png",
125
+ "after_element": "screens/F-NNNN_after_element.png" | null,
126
+ "after_console_errors": [...] | [],
127
+ "after_capture": "ok" | "skipped" | "element-missing"
128
+ }
129
+ ```
130
+
131
+ e. Use ONE Playwright browser session for the whole Step 5.5 batch. Open at the start of the run, reuse per-finding, close with `browser_close` at the end. Never spawn parallel tabs.
132
+
133
+ 6. **Record (do NOT commit)** — append a fix-results.json entry citing the finding ID, files changed, risk, verification status, and the diff hash (`git diff --no-color | sha256sum`). NEVER run `git commit`, `git add -A`, or `git push`. Committing is owned by the commit-manager agent or the user's `/commit` flow — sd-fix only stages.
134
+
135
+ Suggested commit message that commit-manager (or the user) can copy when they decide to commit:
127
136
 
128
137
  ```
129
138
  fix(<cat>): [F-NNNN] <short desc>
@@ -134,18 +143,18 @@ Risk: <TRIVIAL|LOW|MEDIUM|HIGH>
134
143
  Verification: technical passed, semantic passed
135
144
 
136
145
  Applied by: super-design sd-fix (<model>)
137
- Undo: git revert <sha> (session: <SESSION_ID>)
146
+ Session: <SESSION_ID>
138
147
  ```
139
148
 
140
149
  7. **Report** — append to fix-results.json incrementally. After the full batch:
141
150
 
142
- a. Render `docs/super-design/sessions/<session-id>/fix-report.md` using `.claude/skills/super-design/templates/fix-report.md.tpl`. For every applied finding, embed before+after images using paths relative to the report file (copy or symlink screenshots from `.super-design/sessions/<id>/screens/` into `docs/super-design/sessions/<session-id>/screens/` so the doc is portable in git). Proposed and skipped findings list their before screenshot only.
151
+ a. Render `docs/super-design/sessions/<session-id>/fix-report.md` using `.claude/skills/super-design/templates/fix-report.md.tpl`. For every applied finding, embed before+after images using paths relative to the report file (copy or symlink screenshots from `.super-design/sessions/<id>/screens/` into `docs/super-design/sessions/<session-id>/screens/` so the doc is portable in git). Proposed and skipped findings list their before screenshot only.
143
152
 
144
- b. Append a row to `docs/super-design/fix-history.md` using `fix-history.md.tpl`, including a link to the per-session `fix-report.md`.
153
+ b. Append a row to `docs/super-design/fix-history.md` using `fix-history.md.tpl`, including a link to the per-session `fix-report.md`.
145
154
 
146
- c. Close the Playwright browser: `mcp__playwright__browser_close`.
155
+ c. Close the Playwright browser: `mcp__playwright__browser_close`.
147
156
 
148
- d. If `--ci`, create PR via `gh` and include the fix-report.md path in the PR body.
157
+ d. If `--ci`, hand off to commit-manager to commit + push, THEN create PR via `gh` with the fix-report.md path in the body. sd-fix itself never runs `git commit`/`git push`/`gh pr create` directly.
149
158
 
150
159
  # Template library (apply inline, don't dispatch to specialists)
151
160
 
@@ -153,25 +162,26 @@ Source of truth: `references/fix-agent-playbook.md` §7.
153
162
 
154
163
  ## a11y templates (A1–A15)
155
164
 
156
- | ID | Fix | Framework notes |
157
- |---|---|---|
158
- | A1 | alt="<meaningful>" or alt="" for decorative + role="presentation" | React: `alt=""` ; Vue/Svelte/Astro/HTML: `alt=""` |
159
- | A2 | `<label htmlFor/for>` wrap, or aria-label for icon-only | React: `htmlFor`, Vue/Svelte/Astro/HTML: `for` |
160
- | A3 | `aria-label` OR `<span class="sr-only">` + svg aria-hidden focusable=false | Same across frameworks |
161
- | A4 | Correct heading level; preserve visual via class | Same |
162
- | A5 | Nearest palette token meeting 4.5:1 text / 3:1 large/UI | Read design_tokens cache |
163
- | A6 | `:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px }` | CSS file or styled-components |
164
- | A7 | `<html lang="en">` in layout | React: `app/layout.tsx`, Nuxt: `app.vue`, Astro: `Layout.astro` |
165
- | A8 | Anchor accessible name via text, aria-label, or child alt | Same |
166
- | A9 | `aria-expanded={open} aria-controls="id"` on toggle | React camelCase, others kebab |
167
- | A10 | Swap to `<button type="button">`; preserve handler + classes | All frameworks |
168
- | A11 | `role="status" aria-live="polite"`; `role="alert"` for urgent | Same |
169
- | A12 | Skip-link first focusable; `<main id="main-content" tabindex="-1">` | Layout file |
170
- | A13 | `aria-current="page"` on active NavLink | React: router hook, Vue: v-bind :aria-current |
171
- | A14 | aria-label + aria-hidden on SVG | Same |
172
- | A15 | `<caption>`, `<thead><th scope="col">`, `<th scope="row">` | Same |
165
+ | ID | Fix | Framework notes |
166
+ | --- | -------------------------------------------------------------------------- | --------------------------------------------------------------- |
167
+ | A1 | alt="<meaningful>" or alt="" for decorative + role="presentation" | React: `alt=""` ; Vue/Svelte/Astro/HTML: `alt=""` |
168
+ | A2 | `<label htmlFor/for>` wrap, or aria-label for icon-only | React: `htmlFor`, Vue/Svelte/Astro/HTML: `for` |
169
+ | A3 | `aria-label` OR `<span class="sr-only">` + svg aria-hidden focusable=false | Same across frameworks |
170
+ | A4 | Correct heading level; preserve visual via class | Same |
171
+ | A5 | Nearest palette token meeting 4.5:1 text / 3:1 large/UI | Read design_tokens cache |
172
+ | A6 | `:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px }` | CSS file or styled-components |
173
+ | A7 | `<html lang="en">` in layout | React: `app/layout.tsx`, Nuxt: `app.vue`, Astro: `Layout.astro` |
174
+ | A8 | Anchor accessible name via text, aria-label, or child alt | Same |
175
+ | A9 | `aria-expanded={open} aria-controls="id"` on toggle | React camelCase, others kebab |
176
+ | A10 | Swap to `<button type="button">`; preserve handler + classes | All frameworks |
177
+ | A11 | `role="status" aria-live="polite"`; `role="alert"` for urgent | Same |
178
+ | A12 | Skip-link first focusable; `<main id="main-content" tabindex="-1">` | Layout file |
179
+ | A13 | `aria-current="page"` on active NavLink | React: router hook, Vue: v-bind :aria-current |
180
+ | A14 | aria-label + aria-hidden on SVG | Same |
181
+ | A15 | `<caption>`, `<thead><th scope="col">`, `<th scope="row">` | Same |
173
182
 
174
183
  **Never auto-apply:**
184
+
175
185
  - Invent alt text for ambiguous subjects → needs_human
176
186
  - Swap contrast token across brand-primary → needs_human
177
187
  - Replace `<div onClick>` with `<button>` if ancestor click handlers unknown → needs_human
@@ -179,18 +189,19 @@ Source of truth: `references/fix-agent-playbook.md` §7.
179
189
 
180
190
  ## design templates (V1–V8)
181
191
 
182
- | ID | Fix |
183
- |---|---|
184
- | V1 | `p-[13px]` → `p-3` (nearest 4/8 scale token) |
185
- | V2 | `text-[15px]` → `text-sm` or `text-base` (type-scale) |
186
- | V3 | `rounded-[5px]` → `rounded-md` (radius token) |
187
- | V4 | Off-palette color → nearest token by ΔE |
188
- | V5 | Competing CTA → demote to `variant="secondary"` or `"ghost"` |
189
- | V6 | Arbitrary `z-[9999]` → CSS-var token scale |
190
- | V7 | Arbitrary box-shadow → `shadow-md` token |
191
- | V8 | Custom `@media` → framework breakpoint token |
192
+ | ID | Fix |
193
+ | --- | ------------------------------------------------------------ |
194
+ | V1 | `p-[13px]` → `p-3` (nearest 4/8 scale token) |
195
+ | V2 | `text-[15px]` → `text-sm` or `text-base` (type-scale) |
196
+ | V3 | `rounded-[5px]` → `rounded-md` (radius token) |
197
+ | V4 | Off-palette color → nearest token by ΔE |
198
+ | V5 | Competing CTA → demote to `variant="secondary"` or `"ghost"` |
199
+ | V6 | Arbitrary `z-[9999]` → CSS-var token scale |
200
+ | V7 | Arbitrary box-shadow → `shadow-md` token |
201
+ | V8 | Custom `@media` → framework breakpoint token |
192
202
 
193
203
  **Never auto-apply:**
204
+
194
205
  - Change brand-primary → needs_human (broader audit required)
195
206
  - Swap color used in >5 files → needs_human (too broad for single fix)
196
207
  - Convert design token itself → MEDIUM, escalate
@@ -208,20 +219,21 @@ hue 0-360).
208
219
 
209
220
  ## ux templates (U1–U10)
210
221
 
211
- | ID | Fix |
212
- |---|---|
213
- | U1 | Placeholder-as-label → add visible `<label>`; keep placeholder as format hint |
214
- | U2 | Loading state → `disabled + aria-busy + Spinner + label change + role="status"` |
215
- | U3 | Empty state → ternary render `EmptyState` component |
216
- | U4 | Error state → `{error && <div role="alert">...<button onClick={retry}>Try again</button></div>}` |
217
- | U5 | Destructive without confirm → native `<dialog>`; typed confirm for high-risk |
218
- | U6 | No undo → optimistic UI + 6s setTimeout + toast Undo action |
219
- | U7 | Paste blocked on password → remove onPaste / autocomplete="off" |
220
- | U8 | Missing autocomplete on login → `username`, `current-password`, `new-password`, `one-time-code` |
221
- | U9 | Errors not announced → summary `role="alert"` + per-field `aria-invalid` + `aria-describedby` |
222
- | U10 | No retry on network fail → backoff for idempotent GET; explicit retry button for POST |
222
+ | ID | Fix |
223
+ | --- | ------------------------------------------------------------------------------------------------ |
224
+ | U1 | Placeholder-as-label → add visible `<label>`; keep placeholder as format hint |
225
+ | U2 | Loading state → `disabled + aria-busy + Spinner + label change + role="status"` |
226
+ | U3 | Empty state → ternary render `EmptyState` component |
227
+ | U4 | Error state → `{error && <div role="alert">...<button onClick={retry}>Try again</button></div>}` |
228
+ | U5 | Destructive without confirm → native `<dialog>`; typed confirm for high-risk |
229
+ | U6 | No undo → optimistic UI + 6s setTimeout + toast Undo action |
230
+ | U7 | Paste blocked on password → remove onPaste / autocomplete="off" |
231
+ | U8 | Missing autocomplete on login → `username`, `current-password`, `new-password`, `one-time-code` |
232
+ | U9 | Errors not announced → summary `role="alert"` + per-field `aria-invalid` + `aria-describedby` |
233
+ | U10 | No retry on network fail → backoff for idempotent GET; explicit retry button for POST |
223
234
 
224
235
  **Never auto-apply:**
236
+
225
237
  - Add `<dialog>` if >1 existing modal implementation → needs_human (inconsistency)
226
238
  - Change form submission semantics → needs_human
227
239
  - Introduce new dependency for Undo toast lib → needs_human
@@ -232,25 +244,26 @@ Source: `.claude/skills/mobile-app-patterns/SKILL.md` — copy snippets verbatim
232
244
  Apply ONLY when `finding.viewport` is `mobile` (≤768px). Desktop/tablet
233
245
  mobile-pattern findings → needs_human (UI architecture decision).
234
246
 
235
- | ID | Fix | Risk | Pattern |
236
- |---|---|---|---|
237
- | M1 | Hamburger-only nav → `<nav class="fixed inset-x-0 bottom-0 ...">` bottom tab bar (3–5 destinations, fill-icon + label, safe-area-inset-bottom padding) | MEDIUM | needs_human for tab selection |
238
- | M2 | Metric cards in `flex-col` → `<ul class="divide-y">` compact list rows (py-3 px-4, icon + label on left, tabular-nums value on right). For the hero metric extract into `<section>` with 4xl tabular-nums number + delta chip | LOW | auto when only one metric-card block |
239
- | M3 | `<table>` at ≤768px → card-per-row (`<article>` with primary text + metadata chips + trailing `[⋯]` menu) OR compact list (avatar + two lines + trailing meta). Preserve sort/filter controls above | MEDIUM | needs_human if >3 columns carry semantic meaning |
240
- | M4 | Input `font-size < 16px` → `font-size: max(16px, 1rem)` (Tailwind: `text-base` or `text-[max(16px,1rem)]`). Prevents iOS Safari zoom-on-focus | TRIVIAL | auto |
241
- | M5 | Touch target <44×44 → wrap interactive node in `<button class="size-11 flex items-center justify-center">` keeping inner glyph. Add 8px+ gap to adjacent targets | LOW | auto for isolated buttons |
242
- | M6 | Centered modal on mobile → migrate to bottom sheet via Vaul/Radix Drawer (`<Drawer.Root>` + `Drawer.Content className="fixed inset-x-0 bottom-0 rounded-t-2xl"`). Full-screen variant for flows | MEDIUM | needs_human — swap affects all call sites |
243
- | M7 | Hover-only affordance → gate with `@media (hover: hover)`; add tap equivalent (visible button, long-press menu, or always-on chip) | LOW | auto for tooltip-only hovers |
244
- | M8 | Async action without loading state → apply U2 template (disabled + aria-busy + Spinner + label change + role="status") | TRIVIAL | auto |
245
- | M9 | Zero-data view without empty state → apply U3 template with mobile-specific illustration+CTA (full-width button) | LOW | auto |
246
- | M10 | Server failure without error state → apply U4 template; ensure retry button is 44px tall and sticky above safe-area | LOW | auto |
247
- | M11 | Missing safe-area insets → `padding-top: env(safe-area-inset-top)` on header, `padding-bottom: env(safe-area-inset-bottom)` on bottom nav/CTA. `viewport-fit=cover` in meta viewport | TRIVIAL | auto |
248
- | M12 | `100vh` anywhere → `100svh` primary, `100dvh` fallback, `-webkit-fill-available` legacy. Replace all occurrences in one file | TRIVIAL | auto |
249
- | M13 | Inner scroll conflicting with browser pull → `overscroll-behavior: contain` on the inner scroll container | TRIVIAL | auto |
250
- | M14 | Primary list without pull-to-refresh → propose integration of `react-pull-to-refresh` or Framer gesture; register refresh handler with existing query-key invalidation | MEDIUM | needs_human — new dependency |
251
- | M15 | Swipe-action row without peek → add `transform: translateX(-8px)` reveal on first render, animate back after 600ms (framer keyframes). Ensure long-press fallback + trailing `[⋯]` menu button | MEDIUM | needs_human if no existing swipe-action library |
247
+ | ID | Fix | Risk | Pattern |
248
+ | --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------ |
249
+ | M1 | Hamburger-only nav → `<nav class="fixed inset-x-0 bottom-0 ...">` bottom tab bar (3–5 destinations, fill-icon + label, safe-area-inset-bottom padding) | MEDIUM | needs_human for tab selection |
250
+ | M2 | Metric cards in `flex-col` → `<ul class="divide-y">` compact list rows (py-3 px-4, icon + label on left, tabular-nums value on right). For the hero metric extract into `<section>` with 4xl tabular-nums number + delta chip | LOW | auto when only one metric-card block |
251
+ | M3 | `<table>` at ≤768px → card-per-row (`<article>` with primary text + metadata chips + trailing `[⋯]` menu) OR compact list (avatar + two lines + trailing meta). Preserve sort/filter controls above | MEDIUM | needs_human if >3 columns carry semantic meaning |
252
+ | M4 | Input `font-size < 16px` → `font-size: max(16px, 1rem)` (Tailwind: `text-base` or `text-[max(16px,1rem)]`). Prevents iOS Safari zoom-on-focus | TRIVIAL | auto |
253
+ | M5 | Touch target <44×44 → wrap interactive node in `<button class="size-11 flex items-center justify-center">` keeping inner glyph. Add 8px+ gap to adjacent targets | LOW | auto for isolated buttons |
254
+ | M6 | Centered modal on mobile → migrate to bottom sheet via Vaul/Radix Drawer (`<Drawer.Root>` + `Drawer.Content className="fixed inset-x-0 bottom-0 rounded-t-2xl"`). Full-screen variant for flows | MEDIUM | needs_human — swap affects all call sites |
255
+ | M7 | Hover-only affordance → gate with `@media (hover: hover)`; add tap equivalent (visible button, long-press menu, or always-on chip) | LOW | auto for tooltip-only hovers |
256
+ | M8 | Async action without loading state → apply U2 template (disabled + aria-busy + Spinner + label change + role="status") | TRIVIAL | auto |
257
+ | M9 | Zero-data view without empty state → apply U3 template with mobile-specific illustration+CTA (full-width button) | LOW | auto |
258
+ | M10 | Server failure without error state → apply U4 template; ensure retry button is 44px tall and sticky above safe-area | LOW | auto |
259
+ | M11 | Missing safe-area insets → `padding-top: env(safe-area-inset-top)` on header, `padding-bottom: env(safe-area-inset-bottom)` on bottom nav/CTA. `viewport-fit=cover` in meta viewport | TRIVIAL | auto |
260
+ | M12 | `100vh` anywhere → `100svh` primary, `100dvh` fallback, `-webkit-fill-available` legacy. Replace all occurrences in one file | TRIVIAL | auto |
261
+ | M13 | Inner scroll conflicting with browser pull → `overscroll-behavior: contain` on the inner scroll container | TRIVIAL | auto |
262
+ | M14 | Primary list without pull-to-refresh → propose integration of `react-pull-to-refresh` or Framer gesture; register refresh handler with existing query-key invalidation | MEDIUM | needs_human — new dependency |
263
+ | M15 | Swipe-action row without peek → add `transform: translateX(-8px)` reveal on first render, animate back after 600ms (framer keyframes). Ensure long-press fallback + trailing `[⋯]` menu button | MEDIUM | needs_human if no existing swipe-action library |
252
264
 
253
265
  **Never auto-apply:**
266
+
254
267
  - Any `M*` fix when finding affects >1 layout component — UI architecture decision
255
268
  - Adding a drawer/sheet dependency (Vaul, vaul-drawer) without user confirmation
256
269
  - Converting a table to cards when columns drive business logic (sortable compound filters)
@@ -268,32 +281,36 @@ Source: `.claude/skills/super-design/references/design-skills-catalog.md`.
268
281
  2. Write `.super-design/sessions/<id>/proposals/F-NNNN_design-skill-advisory.md`
269
282
  using this structure:
270
283
 
271
- ```markdown
272
- # F-NNNN — Design-skill advisory (NON-FIX)
284
+ ```markdown
285
+ # F-NNNN — Design-skill advisory (NON-FIX)
286
+
287
+ **Rule:** design-intelligence-design-system-coherence
288
+ **Risk:** HIGH (aesthetic change requires human sign-off)
289
+
290
+ ## Current state
273
291
 
274
- **Rule:** design-intelligence-design-system-coherence
275
- **Risk:** HIGH (aesthetic change requires human sign-off)
292
+ <embedded finding.screenshot_path + finding.finding one-liner>
276
293
 
277
- ## Current state
278
- <embedded finding.screenshot_path + finding.finding one-liner>
294
+ ## Recommended skills
279
295
 
280
- ## Recommended skills
281
- <for each id in finding.recommended_skills:>
282
- - **<id>** <description from design-skills-catalog selection matrix>
283
- - Visual signature: <catalog signature column>
284
- - When to recommend: <catalog "When to recommend" column>
296
+ <for each id in finding.recommended_skills:>
297
+ - **<id>** — <description from design-skills-catalog selection matrix>
298
+ - Visual signature: <catalog signature column>
299
+ - When to recommend: <catalog "When to recommend" column>
285
300
 
286
- ## Competitor evidence
287
- <best-matching competitor screenshots from
288
- .cache/evidence/<slug>/<viewport>/components/ cited as reference — pick
289
- the 2 closest to the recommended aesthetic>
301
+ ## Competitor evidence
290
302
 
291
- ## Next step for the user
292
- Run `/frontend-design` (or re-run super-design with the chosen skill
293
- active) to apply this direction. sd-fix cannot auto-apply aesthetic
294
- realignment because every subsequent fix depends on the chosen
295
- tokens.
296
- ```
303
+ <best-matching competitor screenshots from
304
+ .cache/evidence/<slug>/<viewport>/components/ cited as reference pick
305
+ the 2 closest to the recommended aesthetic>
306
+
307
+ ## Next step for the user
308
+
309
+ Run `/frontend-design` (or re-run super-design with the chosen skill
310
+ active) to apply this direction. sd-fix cannot auto-apply aesthetic
311
+ realignment because every subsequent fix depends on the chosen
312
+ tokens.
313
+ ```
297
314
 
298
315
  3. Append to fix-report.md under a "Proposed aesthetic direction" section,
299
316
  NOT under "Applied fixes" — the image diff format is
@@ -307,20 +324,21 @@ without writing code.**
307
324
 
308
325
  ## perf templates (P1–P10)
309
326
 
310
- | ID | Fix |
311
- |---|---|
312
- | P1 | `<img>` without w/h → add width + height; CSS `max-width:100%; height:auto` |
313
- | P2 | Missing `loading="lazy"` → add to below-fold ONLY (NEVER LCP) |
314
- | P3 | Missing `fetchpriority="high"` on LCP → add (ONE per route, confirm LCP first) |
315
- | P4 | Embed CLS → wrap in aspect-ratio div or `aspect-video` |
316
- | P5 | Render-blocking CSS → inline critical; `rel="preload" as="style" onload="..."` (MEDIUM) |
317
- | P6 | Font FOIT → `font-display: swap`; preload critical weight `crossorigin` |
318
- | P7 | Large JS bundle → `dynamic(() => import, {ssr:false})` / `React.lazy` (MEDIUM — propose) |
319
- | P8 | No preconnect 3P → `<link rel="preconnect" crossorigin>` (≤4 origins) |
320
- | P9 | `<img>` not `next/image` → swap; whitelist hostname in `remotePatterns` |
321
- | P10 | No Cache-Control on static → `public, max-age=31536000, immutable` (hashed only) |
327
+ | ID | Fix |
328
+ | --- | ---------------------------------------------------------------------------------------- |
329
+ | P1 | `<img>` without w/h → add width + height; CSS `max-width:100%; height:auto` |
330
+ | P2 | Missing `loading="lazy"` → add to below-fold ONLY (NEVER LCP) |
331
+ | P3 | Missing `fetchpriority="high"` on LCP → add (ONE per route, confirm LCP first) |
332
+ | P4 | Embed CLS → wrap in aspect-ratio div or `aspect-video` |
333
+ | P5 | Render-blocking CSS → inline critical; `rel="preload" as="style" onload="..."` (MEDIUM) |
334
+ | P6 | Font FOIT → `font-display: swap`; preload critical weight `crossorigin` |
335
+ | P7 | Large JS bundle → `dynamic(() => import, {ssr:false})` / `React.lazy` (MEDIUM — propose) |
336
+ | P8 | No preconnect 3P → `<link rel="preconnect" crossorigin>` (≤4 origins) |
337
+ | P9 | `<img>` not `next/image` → swap; whitelist hostname in `remotePatterns` |
338
+ | P10 | No Cache-Control on static → `public, max-age=31536000, immutable` (hashed only) |
322
339
 
323
340
  **Hard blockers (stop and ask):**
341
+
324
342
  - `loading="lazy"` on image that might be LCP → verify via Playwright first
325
343
  - `fetchpriority="high"` on >1 image per route → only one allowed
326
344
  - Code-splitting boundary → MEDIUM propose, never auto
@@ -332,6 +350,7 @@ without writing code.**
332
350
  - Never `Write` existing file unless fully replacing.
333
351
  - Never auto-apply risk ≥ MEDIUM.
334
352
  - Never touch `.git`, `node_modules`, `dist`, `build`, `.next`, lockfiles.
353
+ - **Never run `git commit`, `git add -A`, `git push`, `git stash`, `git reset --hard`, `git rebase`, `git merge`, or `gh pr create`.** Stage edits surgically (`git add -- <file>` for the specific files in `finding.files_affected` is OK). All commit/push/PR work is delegated to commit-manager or the user's `/commit` flow.
335
354
  - Never invent alt text for ambiguous subjects → needs_human.
336
355
  - Never add `loading="lazy"` to potential LCP image.
337
356
  - Never set `fetchpriority="high"` on >1 image per route.
@@ -342,12 +361,12 @@ without writing code.**
342
361
  - Never fabricate after-screenshots. No real browser call → no after image.
343
362
  - Never run Step 5.5 in parallel against the same browser tab.
344
363
  - Never auto-apply DSC-1 (design-skill advisory) — write the proposal, then stop.
345
- - Never auto-apply any M* fix for desktop/tablet viewports — mobile patterns do not generalize upward.
364
+ - Never auto-apply any M\* fix for desktop/tablet viewports — mobile patterns do not generalize upward.
346
365
  - Never introduce a mobile-pattern dependency (Vaul, vaul-drawer, react-pull-to-refresh, react-swipeable-list) without user confirmation.
347
366
 
348
367
  # Evidence rule
349
368
 
350
- Every applied fix MUST cite finding ID in commit message AND fix-results.json. No finding ID → no commit.
369
+ Every applied fix MUST cite finding ID in fix-results.json AND in the suggested commit message stored alongside (for commit-manager to pick up). No finding ID → no fix is recorded as applied.
351
370
 
352
371
  # Preserve these when editing
353
372
 
@@ -362,7 +381,7 @@ Every applied fix MUST cite finding ID in commit message AND fix-results.json. N
362
381
  # Self-check before completing
363
382
 
364
383
  - [ ] fix-results.json has entry for every finding in findings.json
365
- - [ ] Applied count matches commit count on session branch
384
+ - [ ] Applied count matches the number of fix-results entries with `applied=true` (commits are NOT created by sd-fix — handing off to commit-manager is the user's call)
366
385
  - [ ] Tests and types passing on tip
367
386
  - [ ] Every applied finding has `after_full` screenshot on disk (or `after_capture=skipped` with reason)
368
387
  - [ ] Every applied finding has `after_element` screenshot on disk OR a `.missing.txt` note (or `after_capture=skipped`)