Files
sortof/docs/a11y-changes-2026-05-01.md

7.5 KiB
Raw Blame History

Frontend a11y refactor — changelog 2026-05-01

Implemented per /opt/sortof/docs/a11y-audit-2026-05-01.md. No backend changes; no DB migrations; no spec changes. Frontend only.

Files touched

  • /opt/sortof/frontend/index.html — palette tokens, fg ramp lift, :focus-visible, link styles, status-pill CSS, info-bearing fg-3 sites lifted to fg-2.
  • /opt/sortof/frontend/sortof-app.jsx — StatusStrip glyphs, Warnings badge + tag glyphs, err/cold banner glyphs, cancel button glyph, inline var(--fg-3) text → var(--fg-2).

Palette: before → after

Theme tokens

Token Before After Reason
--fg oklch(0.95 0.008 240) oklch(0.95 0.008 240) unchanged (already AAA)
--fg-1 oklch(0.78 0.008 240) oklch(0.85 0.008 240) secondary text headroom
--fg-2 oklch(0.60 0.010 240) oklch(0.80 0.010 240) tertiary text — was borderline AA (5.0:1), now 7.7:1 vs --bg-1 and ≥5.5:1 against every panel surface
--fg-3 oklch(0.45 0.010 240) oklch(0.68 0.010 240) decoration only (.dot-led background); every text-bearing usage was migrated to --fg-2 so this token never carries text meaning anymore

Late-stage tightening: the first ramp pass put fg-2 at 0.72 and fg-3 at 0.60. That cleared AA against --bg-1 (0.21) only. On darker panel surfaces (--bg-2 0.245, --bg-3 0.28, --bg-hi 0.32), fg-3 still failed AA, and many sites used fg-3 inside those panels. Fixed by:

  1. Lifting fg-2 to 0.80 (passes AAA against bg-1; passes AA against bg-hi worst-case).
  2. Lifting fg-3 to 0.68 (decoration only — passes AA hedge).
  3. Sed-replacing every color: var(--fg-3) to color: var(--fg-2) across index.html (background uses preserved). Result: 1 remaining --fg-3 reference (the .dot-led { background } rule). | --acc-green | oklch(0.78 0.13 195) (teal) | aliased to --acc-success | | --acc-amber | oklch(0.82 0.15 75) | aliased to --acc-warn | | --acc-red | oklch(0.68 0.18 30) | aliased to --acc-error | | --acc-blue | oklch(0.72 0.14 255) (deep) | aliased to --acc-info | | --acc-success | new | oklch(0.78 0.13 165) Okabe-Ito bluish-green | | --acc-warn | new | oklch(0.82 0.15 75) Okabe-Ito orange/yellow | | --acc-error | new | oklch(0.70 0.18 35) Okabe-Ito vermillion | | --acc-info | new | oklch(0.78 0.13 230) Okabe-Ito sky blue | | --focus-ring | new | var(--acc-info) |

Backwards-compat: --acc-green/amber/red/blue (and their -bg siblings) now alias to the semantic tokens. Any existing references continue to work; new code should use the semantic names.

Hue choices and CVD safety

Okabe-Ito's red ↔ green pair is the canonical CB-safe choice:

  • success at hue 165 (bluish-green) vs error at hue 35 (vermillion) — 130° apart. Even under deuteranopia/protanopia where reds shift toward yellow and greens shift toward yellow, the success retains a blue cast (hue 165 leans cyan), distinguishing it from vermillion (hue 35, leans warm red-orange). Both at similar L (0.78 vs 0.70) for parity.
  • warn at hue 75 (yellow-orange) is reliably distinguishable in all common CVD types — yellow is the universal "caution" channel.
  • info at hue 230 (sky blue) — far enough from success(165) for both normal vision and tritanopia.

Per-section CSS edits

Selector Before After
.tagline color: var(--fg-3) color: var(--fg-2)
footer.app color: var(--fg-3) color: var(--fg-2)
.label-meta color: var(--fg-3) color: var(--fg-2)
.branch-name color: var(--fg-3) color: var(--fg-2)
.branch-deps color: var(--fg-3) color: var(--fg-2)
.branch-pos color: var(--fg-3) color: var(--fg-2)
.status-pill.idle color: var(--fg-3) color: var(--fg-2)
.status-pill.nonmod color: var(--fg-3) color: var(--fg-2)
a color: fg-1; border-bottom: 1px dotted color: var(--acc-info); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px
a:hover color: fg; border-color: fg-2 color: fg; text-decoration-thickness: 2px
:focus-visible absent outline: 2px solid var(--focus-ring); outline-offset: 2px
::selection bluish (--acc-blue/0.35 hex) bluish (--acc-info/0.35)
.status-glyph new class min-width 12px, font-weight 600, line-height 1
.status-pill.<state> rules colored .dot-led only recolor .status-glyph AND .dot-led; old .dot-led kept for legacy callers

Component fix triples (color × glyph × text now)

Component Color Glyph Text
StatusStrip · cached count success "12 cached"
StatusStrip · queued count warn (blink) "5 queued"
StatusStrip · draining count info (blink) "3 draining"
StatusStrip · expanding info (blink) "expanding collection…"
StatusStrip · unknown error ? "1 unknown"
StatusStrip · non-mod fg-2 "1 non-mod"
StatusStrip · idle fg-2 "ready when you are"
StatusStrip · success/done success "done. N mods, W warnings"
StatusStrip · error error "something went sideways"
StatusStrip · failed error "job failed"
StatusStrip · cold warn "cache miss - be patient"
Warnings header · red badge error ! "{N}"
Warnings header · amber badge warn "{N}"
Warning row · w-tag error/warn ! or "MISSING" / "CYCLE" / "CONFLICT"
Cold banner · err-tag warn "cold"
Err banner · err-tag error "err"
Cancel button error (hover) "cancel"
Diff stats · add/rm/mv success/error/warn + / / count
Branch row · picked success mod_id
Copy button · default/copied info / success IconCopy / IconCheck SVG "copy" / "copied"
Sort button (disabled) dimmed border none "sort" + disabled cursor (acceptable; no glyph because button text+disabled cursor pair is the convention)

Build / verification

This codebase has no build step — index.html loads sortof-app.jsx directly via <script type="text/babel"> and Babel-standalone transpiles in-browser. No npm, no Vite, no bundler. Verification approach:

  • curl http://100.114.205.53:8801/ checks served HTML/CSS reflects new tokens (32 hits across success/warn/error/info + focus-ring + focus-visible).
  • curl http://100.114.205.53:8801/sortof-app.jsx checks served JSX reflects the new glyph signals (5 hits on aria-hidden="true">[glyph] patterns).
  • Public mirror (Caddy + Tailscale) sees the same content.

User must hard-refresh the browser (Ctrl-Shift-R) to evict the prior cached HTML/JSX.

No backend services were touched.

Out-of-scope items deferred

  • Form validation pattern (red border + ⚠ icon + text message): no form-validation surface in the app today. Spec'd in the audit doc; will land alongside any future form (e.g., admin-curated precacher list, settings panel).
  • Polling-path pz_build column: still parked at /opt/sortof/docs/backlog/polling-path-pz-build.md. Unrelated to a11y.
  • Tweaks panel (tweaks-panel.jsx): dev-only, gated behind ?tweaks=1. Skipped this pass — not user-facing.

Backups

  • /opt/sortof/frontend/index.html.bak-20260502-...-a11y-full
  • /opt/sortof/frontend/sortof-app.jsx.bak-20260502-...-a11y-full

Plus prior .bak-...-a11y and .bak-...-emdash siblings still in place. Working tree is dirty per directive — no commits, no auto-cleanup.