docs(phase10a): static slice audit findings
Captures the agent-runnable portion of Phase 10a ahead of Jake's manual walkthrough and feedback-document pass: - a11y baseline confirmed clean (svelte-check 0/0; consistent aria-label + aria-hidden patterns across icon buttons; global :focus-visible ring set in design tokens; prefers-reduced-motion guards present where motion warrants them) - WCAG 2.1 AA contrast tables for both themes computed from the token list at design-system/colors_and_type.css. Nine pairs miss AA-normal; light-theme warning misses AA-large too. Severity ranked, suggested token shifts noted as starting points - CI matrix state: check.yml runs on every push, build.yml has never been end-to-end exercised - recommend manual workflow_ dispatch before tagging v0.1.0 - Clean-install test plan and the Phase 9d walkthrough checklist consolidated for the testing session
This commit is contained in:
261
docs/superpowers/audits/2026-04-25-phase10a-static-slice.md
Normal file
261
docs/superpowers/audits/2026-04-25-phase10a-static-slice.md
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
---
|
||||||
|
name: Phase 10a static slice audit
|
||||||
|
description: Static a11y + contrast + CI verification produced by Wren on 2026/04/25 as the agent-runnable portion of Phase 10a, ahead of Jake's manual walkthrough and feedback-document pass.
|
||||||
|
type: audit
|
||||||
|
tags: [phase10a, audit, a11y, contrast, ci, release]
|
||||||
|
created: 2026/04/25
|
||||||
|
status: findings
|
||||||
|
author: Wren (CORBEL's resident agent) on behalf of Jake Sames
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 10a — Static Slice Audit
|
||||||
|
|
||||||
|
> **Scope.** Everything in Phase 10a that an agent can verify without
|
||||||
|
> a running dev server. The dogfood walkthrough, RB-08 macOS power-
|
||||||
|
> assertion verification, and runtime keyboard / screen-reader
|
||||||
|
> traversal stay with Jake (or Rachmann for the Mac slot).
|
||||||
|
>
|
||||||
|
> **Baseline.** `main` at `0ca4e0e`. 277 cargo tests pass. clippy /
|
||||||
|
> fmt / svelte-check / npm build all clean.
|
||||||
|
|
||||||
|
## Summary of findings
|
||||||
|
|
||||||
|
- **A11y static rules: clean.** svelte-check reports 0/0 across 3957
|
||||||
|
files. Spot-checks confirm consistent `aria-label` on icon-only
|
||||||
|
buttons and `aria-hidden="true"` on inner SVGs. Global
|
||||||
|
`:focus-visible` ring is set in design tokens.
|
||||||
|
- **Contrast: real fails in light theme + small dim text in both
|
||||||
|
themes.** Nine token pairs miss WCAG AA-normal (4.5:1). One pair
|
||||||
|
(`warning` on `bg`, light) misses AA-large too.
|
||||||
|
- **CI: cross-platform `cargo check` matrix exists and runs on every
|
||||||
|
push to `main`.** The full Tauri installer build (`build.yml`) has
|
||||||
|
never been exercised end-to-end and should be triggered by manual
|
||||||
|
workflow_dispatch before tagging v0.1.0.
|
||||||
|
|
||||||
|
## A11y baseline
|
||||||
|
|
||||||
|
- `npm run check` (svelte-check 4): 0 errors / 0 warnings on 3957
|
||||||
|
files. Svelte's a11y ruleset enforces `<img>` alt text, form-input
|
||||||
|
labels, click-handler-on-non-interactive, autofocus restrictions,
|
||||||
|
noninteractive-tabindex, and roughly twenty other rules. All pass.
|
||||||
|
- Spot-checked icon-only buttons across `Sidebar.svelte`,
|
||||||
|
`ToastViewport.svelte`, `MicroSteps.svelte`, `HotkeyRecorder.svelte`,
|
||||||
|
`HistoryPage.svelte`, `TasksPage.svelte`. Every button carries
|
||||||
|
`aria-label`. Every inner `<svg>` carries `aria-hidden="true"`. The
|
||||||
|
pattern is consistent enough to assume it's the convention rather
|
||||||
|
than coincidence.
|
||||||
|
- Global focus ring at `src/design-system/colors_and_type.css:225`:
|
||||||
|
`:focus-visible { outline: 2px solid var(--accent); outline-offset:
|
||||||
|
3px; border-radius: var(--radius-md); }`. Covers every focusable
|
||||||
|
element by default.
|
||||||
|
- `prefers-reduced-motion` guards present in 8 files
|
||||||
|
(`ToastViewport`, `CompletionSparkline`, `SettingsGroup`,
|
||||||
|
`DictationPage`, `TasksPage`, `app.css`, `colors_and_type.css`,
|
||||||
|
plus the `preferences.svelte.ts` store that exposes the flag).
|
||||||
|
These cover the four scaled / staggered animations Phase 8 + 9
|
||||||
|
introduced. Hover-only colour transitions across the rest of the
|
||||||
|
app rely on the global `*` transition rule and don't pose a
|
||||||
|
vestibular risk; they're under the reduced-motion threshold.
|
||||||
|
|
||||||
|
## Contrast audit (WCAG 2.1 AA)
|
||||||
|
|
||||||
|
Computed via Python (sRGB → linear-RGB → relative luminance →
|
||||||
|
Web Content Accessibility Guidelines contrast ratio). Floors:
|
||||||
|
**4.5:1** for normal text, **3:1** for large text (≥18px regular
|
||||||
|
or ≥14px bold) and UI components.
|
||||||
|
|
||||||
|
### Dark theme
|
||||||
|
|
||||||
|
| Foreground | Background | Ratio | AA-normal | AA-large |
|
||||||
|
|---|---|---:|---|---|
|
||||||
|
| `text` | `bg` | 16.38 | PASS | PASS |
|
||||||
|
| `text` | `bg-elevated` | 15.35 | PASS | PASS |
|
||||||
|
| `text` | `bg-card` | 14.77 | PASS | PASS |
|
||||||
|
| `text` | `sidebar` | 15.90 | PASS | PASS |
|
||||||
|
| `text` | `nav-active` | 14.12 | PASS | PASS |
|
||||||
|
| `text` | `hover` | 14.44 | PASS | PASS |
|
||||||
|
| `text-secondary` | `bg` | 6.39 | PASS | PASS |
|
||||||
|
| `text-secondary` | `bg-elevated` | 5.99 | PASS | PASS |
|
||||||
|
| `text-secondary` | `bg-card` | 5.76 | PASS | PASS |
|
||||||
|
| `text-secondary` | `sidebar` | 6.20 | PASS | PASS |
|
||||||
|
| `text-secondary` | `hover` | 5.63 | PASS | PASS |
|
||||||
|
| `text-tertiary` | `bg` | 3.65 | **FAIL** | PASS |
|
||||||
|
| `text-tertiary` | `bg-elevated` | 3.42 | **FAIL** | PASS |
|
||||||
|
| `text-tertiary` | `bg-card` | 3.29 | **FAIL** | PASS |
|
||||||
|
| `text-tertiary` | `sidebar` | 3.54 | **FAIL** | PASS |
|
||||||
|
| `accent` | `bg` | 9.48 | PASS | PASS |
|
||||||
|
| `accent` | `bg-elevated` | 8.89 | PASS | PASS |
|
||||||
|
| `accent` | `bg-card` | 8.55 | PASS | PASS |
|
||||||
|
| `accent` | `sidebar` | 9.21 | PASS | PASS |
|
||||||
|
| `success` | `bg` | 9.75 | PASS | PASS |
|
||||||
|
| `success` | `bg-card` | 8.80 | PASS | PASS |
|
||||||
|
| `danger` | `bg` | 6.46 | PASS | PASS |
|
||||||
|
| `danger` | `bg-card` | 5.83 | PASS | PASS |
|
||||||
|
| `warning` | `bg` | 11.87 | PASS | PASS |
|
||||||
|
| `warning` | `bg-card` | 10.70 | PASS | PASS |
|
||||||
|
|
||||||
|
### Light theme
|
||||||
|
|
||||||
|
| Foreground | Background | Ratio | AA-normal | AA-large |
|
||||||
|
|---|---|---:|---|---|
|
||||||
|
| `text` | `bg` | 16.70 | PASS | PASS |
|
||||||
|
| `text` | `bg-elevated` | 15.58 | PASS | PASS |
|
||||||
|
| `text` | `bg-card` | 17.70 | PASS | PASS |
|
||||||
|
| `text` | `sidebar` | 15.85 | PASS | PASS |
|
||||||
|
| `text` | `nav-active` | 14.24 | PASS | PASS |
|
||||||
|
| `text` | `hover` | 14.64 | PASS | PASS |
|
||||||
|
| `text-secondary` | `bg` | 6.77 | PASS | PASS |
|
||||||
|
| `text-secondary` | `bg-elevated` | 6.32 | PASS | PASS |
|
||||||
|
| `text-secondary` | `bg-card` | 7.18 | PASS | PASS |
|
||||||
|
| `text-secondary` | `sidebar` | 6.43 | PASS | PASS |
|
||||||
|
| `text-secondary` | `hover` | 5.94 | PASS | PASS |
|
||||||
|
| `text-tertiary` | `bg` | 3.47 | **FAIL** | PASS |
|
||||||
|
| `text-tertiary` | `bg-elevated` | 3.24 | **FAIL** | PASS |
|
||||||
|
| `text-tertiary` | `bg-card` | 3.68 | **FAIL** | PASS |
|
||||||
|
| `text-tertiary` | `sidebar` | 3.30 | **FAIL** | PASS |
|
||||||
|
| `accent` | `bg` | 3.35 | **FAIL** | PASS |
|
||||||
|
| `accent` | `bg-elevated` | 3.12 | **FAIL** | PASS |
|
||||||
|
| `accent` | `bg-card` | 3.55 | **FAIL** | PASS |
|
||||||
|
| `accent` | `sidebar` | 3.18 | **FAIL** | PASS |
|
||||||
|
| `success` | `bg` | 3.98 | **FAIL** | PASS |
|
||||||
|
| `success` | `bg-card` | 4.22 | **FAIL** | PASS |
|
||||||
|
| `danger` | `bg` | 4.39 | **FAIL** | PASS |
|
||||||
|
| `danger` | `bg-card` | 4.65 | PASS | PASS |
|
||||||
|
| `warning` | `bg` | 2.56 | **FAIL** | **FAIL** |
|
||||||
|
| `warning` | `bg-card` | 2.72 | **FAIL** | **FAIL** |
|
||||||
|
|
||||||
|
### Severity ranking
|
||||||
|
|
||||||
|
1. **Light-theme `warning` on every surface** — fails AA-large too
|
||||||
|
(2.56–2.72:1). Anywhere this colour appears against `bg` or
|
||||||
|
`bg-card` is unreadable for low-vision users. Highest priority.
|
||||||
|
2. **Light-theme `accent` as link colour** — global `a { color:
|
||||||
|
var(--accent) }` rule at `colors_and_type.css:220` puts a
|
||||||
|
3.35:1 swatch on body text. Link semantics need AA-normal (4.5).
|
||||||
|
3. **`text-tertiary` in both themes** — used for `.k-caption` (12px),
|
||||||
|
`.k-eyebrow` (10px), `kbd` (11px) and many hint-text contexts
|
||||||
|
(399 class-name hits across the codebase). All small text. All
|
||||||
|
fail AA-normal in both themes.
|
||||||
|
4. **Light-theme `success` on bg / bg-card** — borderline (3.98 /
|
||||||
|
4.22). Fine if used only for icons / chips ≥18px, fails for
|
||||||
|
inline body copy.
|
||||||
|
5. **Light-theme `danger` on bg** — borderline (4.39); 4.65 on
|
||||||
|
bg-card. Fine if used for chips, marginal for inline.
|
||||||
|
|
||||||
|
### Suggested token shifts (estimates, eyeball before committing)
|
||||||
|
|
||||||
|
Numbers below are starting points, not finished colours. Re-run the
|
||||||
|
contrast script against any candidate before merging.
|
||||||
|
|
||||||
|
- `--text-tertiary` (light): `#8a8578` → roughly `#6f6a5c`. Aim
|
||||||
|
≥4.5:1 against `#faf8f5`.
|
||||||
|
- `--text-tertiary` (dark): `#716b60` → roughly `#888278`. Aim
|
||||||
|
≥4.5:1 against `#0f0e0c`.
|
||||||
|
- `--accent` (light): `#b87a4a` → roughly `#9a6535`. Aim ≥4.5:1 for
|
||||||
|
link role; UI / icon use already passes.
|
||||||
|
- `--warning` (light): `#b89a3e` → roughly `#8a7220`.
|
||||||
|
- `--success` (light) and `--danger` (light): nudge by ~5–10% if
|
||||||
|
you find them used as inline text during the walkthrough.
|
||||||
|
|
||||||
|
The Python contrast helper used to generate these tables is small
|
||||||
|
enough to keep inline if useful — it's at the bottom of this file.
|
||||||
|
|
||||||
|
## CI matrix state
|
||||||
|
|
||||||
|
Three workflows in `.github/workflows/`:
|
||||||
|
|
||||||
|
- `check.yml`. Runs `cargo check` on `ubuntu-22.04`,
|
||||||
|
`windows-latest`, `macos-latest` on every push to `main` and every
|
||||||
|
PR. Concurrent-cancels older runs on the same branch. **Should be
|
||||||
|
green at `0ca4e0e`** but cannot be confirmed from this machine
|
||||||
|
(`gh` CLI not installed). Worth eyeballing in the GitHub Actions
|
||||||
|
UI before declaring 10a done.
|
||||||
|
- `build.yml`. Full Tauri installer build (Linux .AppImage + .deb,
|
||||||
|
Windows .msi + .exe, macOS .dmg + .app). Triggers on tag push
|
||||||
|
`v*` or manual workflow_dispatch. **Has never been exercised
|
||||||
|
end-to-end.** First v0.1.0 tag will be its trial run. Strong
|
||||||
|
recommendation: `Run workflow` against `main` once before tagging,
|
||||||
|
to catch matrix-specific failures (Vulkan SDK on Windows,
|
||||||
|
libclang location on Linux 22.04, MoltenVK on macOS) without the
|
||||||
|
release artefact pressure.
|
||||||
|
- `audit.yml`. Weekly cargo + npm audit. Independent of v0.1.
|
||||||
|
|
||||||
|
## Clean-install test plan
|
||||||
|
|
||||||
|
Run on a spare user account or a fresh VM, with no prior Corbie /
|
||||||
|
Kon data. Three iterations: one per platform.
|
||||||
|
|
||||||
|
1. Install the artefact from the platform's `build.yml` output.
|
||||||
|
2. Launch from a clean shell (`corbie` from PATH, or the .app /
|
||||||
|
Start-menu shortcut).
|
||||||
|
3. Verify first-run setup flow renders. Walk through the Whisper /
|
||||||
|
LLM model download for the smallest tier.
|
||||||
|
4. Confirm app data lands at the expected path:
|
||||||
|
- Linux: `~/.local/share/kon/` (will become `~/.local/share/
|
||||||
|
corbie/` after Phase 10b rename).
|
||||||
|
- macOS: `~/Library/Application Support/com.corbel.kon/`.
|
||||||
|
- Windows: `%APPDATA%\com.corbel.kon\`.
|
||||||
|
5. Record a 10-second brain-dump → cleanup → task extraction.
|
||||||
|
Confirm no log leakage to stderr that references `target/` or
|
||||||
|
dev-only paths.
|
||||||
|
6. Quit the app. Open the SQLite db (`kon.db` for now) and verify
|
||||||
|
`SELECT version FROM schema_version ORDER BY version DESC LIMIT
|
||||||
|
1` returns `14`.
|
||||||
|
7. Re-launch. Confirm settings persist, history shows the test
|
||||||
|
transcript with manual + LLM tags.
|
||||||
|
8. Optional but recommended: launch with `RUST_LOG=debug` once and
|
||||||
|
archive the log. Anything referencing `/home/jake/Documents/
|
||||||
|
CORBEL-Projects/kon/target/` is a dev-leak bug.
|
||||||
|
|
||||||
|
For Phase 10c this gets re-run after the rename sweep to confirm
|
||||||
|
the migration shim correctly moves `~/.local/share/kon/` →
|
||||||
|
`~/.local/share/corbie/` and renames `kon.db` → `corbie.db`.
|
||||||
|
|
||||||
|
## Walkthrough checklist (deferred from Phase 9d)
|
||||||
|
|
||||||
|
These need a running dev server, so they belong in Jake's testing
|
||||||
|
session:
|
||||||
|
|
||||||
|
- [ ] Keyboard-only traversal across every page. Tab order respects
|
||||||
|
visual order. No keyboard traps.
|
||||||
|
- [ ] Focus-visible ring shows on every focusable element. Zero
|
||||||
|
invisible focus states.
|
||||||
|
- [ ] WCAG AA contrast verified visually in both themes after any
|
||||||
|
token shifts from the suggestions above.
|
||||||
|
- [ ] Dark-mode parity check: every page renders correctly in
|
||||||
|
light + dark. No hard-coded greys that look fine in one theme
|
||||||
|
and broken in the other.
|
||||||
|
- [ ] Icon-only-button audit: hover reveals the title attribute or
|
||||||
|
the aria-label is announced by VoiceOver / Orca / Narrator.
|
||||||
|
- [ ] `prefers-reduced-motion: reduce` enabled in OS — sparkline
|
||||||
|
stagger, badge entrance, settings-group chevron all stop.
|
||||||
|
- [ ] Screen-reader smoke test: at least announce page titles,
|
||||||
|
primary-button labels, and the sparkline summary line on one
|
||||||
|
platform's SR.
|
||||||
|
|
||||||
|
## Appendix — contrast helper
|
||||||
|
|
||||||
|
Drop into `scripts/contrast.py` or run inline. Pass two hex strings
|
||||||
|
(with or without `#`); prints the AA verdict.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def srgb_to_lin(c):
|
||||||
|
c = c / 255
|
||||||
|
return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
|
||||||
|
|
||||||
|
def luminance(hex_color):
|
||||||
|
h = hex_color.lstrip('#')
|
||||||
|
r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
|
||||||
|
return 0.2126 * srgb_to_lin(r) + 0.7152 * srgb_to_lin(g) + 0.0722 * srgb_to_lin(b)
|
||||||
|
|
||||||
|
def contrast(c1, c2):
|
||||||
|
l1, l2 = luminance(c1), luminance(c2)
|
||||||
|
return (max(l1, l2) + 0.05) / (min(l1, l2) + 0.05)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Anchors
|
||||||
|
|
||||||
|
- Roadmap: [docs/roadmap/2026-04-23-corbie-feature-complete-roadmap.md](../../roadmap/2026-04-23-corbie-feature-complete-roadmap.md)
|
||||||
|
- Phase 9 spec: [docs/superpowers/specs/2026-04-24-phase9-polish-debt-design.md](../specs/2026-04-24-phase9-polish-debt-design.md)
|
||||||
|
- Release-blocker index: [docs/issues/README.md](../../issues/README.md)
|
||||||
|
- Latest handover: [HANDOVER.md](../../../HANDOVER.md)
|
||||||
Reference in New Issue
Block a user