Skip to content

The word that starts everything the visor says: an audible anchor for drawer announcements - #125

Merged
lannbot merged 2 commits into
mainfrom
a11y/audible-anchor-word
Aug 26, 2026
Merged

The word that starts everything the visor says: an audible anchor for drawer announcements#125
lannbot merged 2 commits into
mainfrom
a11y/audible-anchor-word

Conversation

@lannbot

@lannbot lannbot commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

What

Adds the audible anchor word — a per-identity provenance token for non-visual users, mirroring the anchor hue: rolled exactly once per identity by claim() from the EFF short wordlist 2.0 (verbatim upstream, minus the visor's own spoken vocabulary), persisted under a new required VisorConfig.wordKey, and spoken — never rendered, never crossing the visor API — as the prefix of host-emitted drawer lifecycle announcements ("«word»: «spoken» open/closed/back", silent on suspend/update/rebuild, plain "visor" pre-claim).

Why

The three-voice anti-spoofing discipline is pixels, and a screen reader consumes no pixels: app-frame text and visor speech arrive in one undifferentiated stream, and iframe boundaries are not announced — so an app could render a sentence that sounds like the visor. The word is unguessable by apps (audible-only, never leaves the device, visor-realm storage an opaque-origin frame cannot read), so its presence authenticates visor speech the way visor pixels do visually. Design record: new NOTES.md section "non-visual provenance".

How

  • speak() becomes a capped FIFO with a ~1.4 s dwell so synchronous sentence pairs (close→resume, teach→fresh-colour) both survive the live region.
  • speakWord() / rerollWord() (unclaimed-throws, reroll guaranteed-different) back two settings-sheet buttons that say the word without drawing it.
  • Every drawer tenant now declares a required framework-vocabulary spoken label.
  • e2e: harness seeds word keys; new drawer-announcements scenario asserts the spoken sentences, their ordering, suspend silence, and that the word never reaches drawn pixels (the live region is exempted only while it measures as clipped).
  • erase() clears the word key — a re-minted visor must not answer to the old word.

Independent review: non-blocking; advisories addressed (phantom-"back" interplay documented at the eviction loop, e2e ordering assertion tightened to lastIndexOf, pixel-probe scope note).

Gates

  • just check — 9/9 invariants
  • just site (deno check + bundles)
  • just e2e drawer-announcements settings-identity storage-picker storage-page-navigation tenant-precedence visor-reset solo-persistence — all passing (re-run post-merge with main: drawer-announcements + solo-persistence)

Automerge is armed (merge commit) per repo convention.

lann added 2 commits August 25, 2026 23:21
… drawer announcements

The three-voice discipline is pixels, and a screen reader consumes no
pixels: app-frame text and visor speech arrive in one undifferentiated
stream, so an app could render a sentence that SOUNDS like the visor.
This gives the visor an audible anchor — a word rolled exactly once per
identity (EFF short wordlist 2.0, minus the visor's own spoken
vocabulary), persisted beside the anchor hue, spoken and never drawn:
it prefixes host-emitted drawer lifecycle announcements ("«word»:
storage picker open / closed / back", silent on suspend and rebuild,
plain "visor" before the claim), it never crosses the visor API, and
the settings sheet can say it or re-roll it but never shows it.
speak() becomes a dwell-paced FIFO so synchronous sentence pairs
(close→resume, teach→fresh-colour) both survive the live region.
Every drawer tenant now declares a spoken, framework-vocabulary label,
and a new e2e scenario pins the sentences, their order, suspend
silence, and that the word never reaches drawn pixels.
@lannbot
lannbot enabled auto-merge August 26, 2026 03:23
@lannbot
lannbot merged commit 07273b7 into main Aug 26, 2026
3 checks passed
@lannbot
lannbot deleted the a11y/audible-anchor-word branch August 26, 2026 03:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants