T9.5 - C1+S8: the help-tip is real, the audit is written, BL-001 is dead

S8, finished where the plan said it would be: every .help-tip badge is a
<button> - upgraded by the component itself at load (help.js), with
helpTipUpgrade() for late renders, so a badge added tomorrow is born
reachable. The count the task warned about came true: 15 at wave 0, 18 at the
wave 6 exit, 20 at the start of this task - all 20 buttons now, and the fix
being in the component is what stops the number growing again. One
viewport-clamped role=tooltip bubble serves every badge: focus shows it,
Escape hides it, tap toggles it, tap-elsewhere closes it - the touch path
Field View's tablets never had. The injected styles now use theme tokens
(four raw hexes of the S5 kind, gone).

BL-001, CLOSED after three causes and nine waves: the old CSS ::after escaped
its badge to the right and was the creator's last 390px overflow. The clamped
bubble ends it - scrollWidth 390 vs clientWidth 390 - and frame_check's pin
FLIPPED, exactly as designed: it asserted the failure until the fix landed,
and now asserts the fix so a regression reopens the entry loudly.

The audit (docs/reference/accessibility-audit.md), every number probe-backed:
- div/span click handlers: 12/2 at wave 0 -> 0 (the wizard's constraint
  library entries and the dashboard chips became buttons here; the comments
  backdrop stopped pretending to be a control)
- outline:none without replacement: 0 (wp-chrome's one is the documented S12
  exception - its ring is on :focus-within, one ring not two)
- aria-live: every toast system and banner announces
- native dialogs: 79 -> 21, all on surfaces no S1 task named (admin, users,
  launcher) - documented as BL-024 with the T7.9 kit ready for them
- keyboard-only primary flow: covered leg by leg by the probes that dispatch
  real CDP key events, cited in the document

Three stale count-pins re-pointed to the numbers this task reached (stepper's
baseline-minus-10, form_structure's one-span-left, frame_check's BL-001 pin) -
each now pins the TARGET so slack cannot hide a regression.

Verification (each probe run alone): NEW tests/helptip_check.py 13/13.
Regressions: a11y_check 22/22, stepper_check 71/71, form_structure_check
50/51 (BL-022's product question), pipeline_check 44/44, frame_check 38/38.

Items: C1, S8 (BL-001 closed, BL-024 opened)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 13:37:10 -07:00
parent b83f2fd8d5
commit 0dcea8d725
13 changed files with 436 additions and 28 deletions

View File

@@ -0,0 +1,73 @@
# Accessibility audit — C1 + S8 (T9.5, 2026-08-19)
Approved Aug 14 2026 (C1): any component rebuilt ships accessible or it is not
done. This document records the audit at the end of wave 9 against the wave 0
baseline, per CLAUDE.md's rules. Every number below is re-measured by a probe
on every run — the citations name which one.
## The metrics
| Metric | Wave 0 baseline | Now | Target | Verified by |
|---|---|---|---|---|
| `<div>` / `<span>` with `onclick` | 12 / 2 | **0** | 0 | `helptip_check.py` (grep, comments stripped) |
| `.help-tip` unreachable by keyboard | 15 (18 by wave 6) — re-measured at T9.5 start: **20** | **0** | 0 | `helptip_check.py` (driven with real keys and taps) |
| `aria-live` regions | 0 | ≥1 per toast system and banner (login, both toasts, release banner, autosave indicator, list-import reports, field toast) | ≥1 each | `a11y_check.py`, `warning_check.py`, `creator_dialogs_check.py` |
| Text below 4.5:1 | present | none found on the audited surfaces | 0 | `a11y_check.py` (creator sweep), `frame_check.py` BL-013 note |
| `outline: none` without replacement | present | **0** (grep with replacement detection) | 0 | `helptip_check.py` |
| Native dialogs | 79 | **21** | 0 or documented | `creator_dialogs_check.py` prints the count; see the gap below |
**The count went up before it went down, exactly as the task predicted:** the
wave 6 exit counted 18 unreachable help-tips; at the start of T9.5 there were
**20** (T6.x and wave 7/8 tasks reused the component as designed). All 20 are
buttons now — the fix is in the component (`help.js` upgrades every badge at
load and exposes `helpTipUpgrade()` for late renders), so a badge added
tomorrow is born reachable.
## The documented gap — 21 native dialogs
`admin.js` (6), `users.js` (10), `index.html` (5). These are the operator
consoles and the launcher — surfaces **no S1 task ever named** (S1's two
halves were the wizard, T5.8, and the creator, T7.9; both measure 0). They are
admin-only or low-frequency flows, every one a genuine confirm-before-destroy.
Logged as **BL-024** for conversion to the T7.9 dialog kit rather than done
here: converting three more pages inside the audit task is the drive-by
CLAUDE.md forbids.
## The help-tip component (S8)
- The badge is a `<button>` with `aria-label`, `aria-expanded`, and a
`:focus-visible` ring from the shared `--cds-focus` token.
- The tooltip is one `role="tooltip"` bubble, viewport-clamped on both axes —
which also ended BL-001: the old CSS `::after` escaping its badge was the
creator's last 390px overflow.
- Paths: keyboard (focus shows, Escape hides), touch (tap toggles, tap
elsewhere closes), pointer (hover shows). Driven at 390px by
`helptip_check.py`.
- The injected styles now use theme tokens; the block previously carried four
raw hexes of the kind S5 counted.
## Keyboard-only primary flow
Sign in → pick a project → SOP wizard → create a work package → issue it.
Covered by probes that dispatch **real CDP key events** (synthetic
`KeyboardEvent`s never reach native activation — the wave 5 lesson, recorded
in `form_structure_check.py`):
| Leg | Probe |
|---|---|
| Sign in | `server/smoketest.py` (form submit), `login.html` roles verified in `a11y_check.py` |
| Launcher → project | `launcher_check.py` (B3, keyboard section) |
| SOP wizard steps | `stepper_check.py` (A4/S9: ten real buttons, keyboard operable) |
| Creator sections + save | `form_structure_check.py` §7 (Tab/Enter/Space on rail and headings), `creator_dialogs_check.py` (validation focus order) |
| Issue | `hold_check.py` (the status control end to end) |
## Per-page results
| Page | Interactive elements | Announcements | Focus | Notes |
|---|---|---|---|---|
| login.html | native form controls | `role="alert"`/`role="status"` (the app's reference pattern) | visible | the pattern every other page copies |
| index.html (launcher) | buttons/links | status line announced | visible | 5 native dialogs → BL-024 |
| work-package-suite.html (wizard) | 0 div/span handlers; library entries are buttons (T9.5) | `wp-toast` role-differentiated | T3.4 ring | 0 native dialogs |
| wp-creation-index.html (creator) | 0 div/span handlers; chips are buttons (T9.5) | toast + release banner + field errors, all live regions | ring on all 120+ focusables (`a11y_check`) | 0 native dialogs |
| field.html | buttons throughout, 44px targets | `role="status"`/`alert` toast | visible | offline drawings reachable (files_check) |
| admin.html / users.html | buttons | banners | visible | 16 native dialogs → BL-024 |

View File

@@ -302,6 +302,7 @@ Wave 9 adds these:
python tests/export_check.py # CR-008/CR-017 - export walk + hours guard 20 checks
python tests/sample_check.py # S7 - one sample affordance, confirmed+fenced 10 checks
python tests/icon_check.py # S6 - one icon system, no emoji, mapped 5 checks
python tests/helptip_check.py # C1/S8 - tips by keyboard+touch, audit greps 13 checks
```
**Three probes were re-pointed at `T7.1`.** `sections_check.py` 5b drove the live

View File

@@ -54,7 +54,7 @@ deliberately deferred.
## Found during implementation
### BL-001 — The creator overflows horizontally at 1440px
### BL-001 — CLOSED at T9.5 — The creator overflows horizontally (1440px, then 390px)
- **Found during:** T0.2
- **Where:** `html/wp-creation-index.html` / `html/wp-creation-styles.css`
@@ -123,6 +123,13 @@ deliberately deferred.
owns this entry now. `tests/form_structure_check.py` reports the measurement on
every run, and `tests/frame_check.py` keeps the failure pinned so the entry
cannot be closed by silence.
- **CLOSED, T9.5.** The `S8` rebuild replaced the escaping CSS `::after` tooltip
with a viewport-clamped bubble element, and the creator measures
**scrollWidth 390 vs clientWidth 390** at a 390px viewport. `frame_check.py`'s
pin flipped: it now asserts the ABSENCE of overflow, so a regression reopens
this entry loudly. Three causes in this entry's lifetime - the user-menu run
(fixed by `T1.2`), the injected `--nav-w` (spent by `T7.1`), the tooltip
(fixed here) - each found only because the measurement kept running.
### BL-002 — `outline: none` appears three times in the wizard sheet, not once
@@ -507,3 +514,16 @@ deliberately deferred.
- **Why not now:** new scope — needs its own item id per the working rules, and
a product conversation about where it displays and who reads it.
- **Suggested wave or follow-up:** next revision; needs Nick for placement.
### BL-024 — 21 native dialogs remain on the operator consoles and the launcher
- **Found during:** T9.5 (the audit's dialog count)
- **Where:** `admin.js` (6), `users.js` (10), `index.html` (5)
- **What:** the app-wide native dialog count fell 79 → 21 across `S1`'s two
tasks (`T5.8` wizard, `T7.9` creator). The remainder sit on surfaces no `S1`
task ever named — admin-only or low-frequency flows, every one a genuine
confirm-before-destroy. The T7.9 dialog kit (`wpConfirmDialog`/
`wpPromptDialog`) is built and proven; conversion is mechanical.
- **Why not now:** converting three more pages inside the audit task is the
drive-by CLAUDE.md forbids; the audit's job was to measure and document.
- **Suggested wave or follow-up:** next revision, one task, using the T7.9 kit.