Accessibility and motion qualification
Build with npm run build. ctest --test-dir .build/cmake --output-on-failure -R gui_accessibility uses synthetic peers, offscreen rendering and private
D-Bus. It checks labelled primary fields, password privacy, keyboard traversal
without work, complete canonical action pages, contrast tokens, enlarged text,
minimum geometry and local motion persistence/safety. Existing approval tests
still enforce complete verified review and explicit one-shot decisions.
node scripts/qualify-accessibility.mjs --evidence NEW_PRIVATE_RUN runs that
fixture at actual device pixel ratios 1/1.5/2, with both themes and 15/30px
application fonts in a 520x400 logical window. It saves screenshots and actual
Qt/backend/DPR facts. Inspect the images; a passing geometry assertion alone
does not prove a usable layout. The runner deliberately exits1 while the
required real reader/installed-keyboard gate is unrun. It never installs or
starts a screen reader or changes a desktop automatically.
The GUI respects an enlarged application font. Heading sizes derive from that base, ordinary/muted text tokens meet 4.5:1 and essential border tokens meet 3:1 against both themes' surfaces. Keyboard focus uses the contrasting highlight. The Settings heading row scrolls horizontally, including when Tab focuses an offscreen heading, leaving the form usable at minimum size. Long form button and checkbox labels wrap within the viewport; explicit field focus cancels a deferred initial section jump. Tab leaves the composer; Enter/IME/Shift-Enter/send/cancel authority is unchanged.
Local motion preference
Appearance's Reduce motion is saved immediately, independently from the color
theme/Desktop Apply draft and all provider/auth/model drafts. The local
ui-accessibility.json beside the private GUI outbox contains only:
{"version":1,"reducedMotion":true}
Missing state means false. The GUI requires same-UID mode0700 parent and
mode0600 regular single-link state, refuses symlinks/unsafe ancestors, serializes
cooperating writers and compares accepted bytes before atomic fsynced replacement.
Corrupt/unknown/unsafe/conflicted state is reported and left untouched; there is
no automatic repair. Deliberate repair needs closing/reopening the GUI after
the owner inspects the file. No wire-v4 or daemon ui.toml schema changed.
Reduced motion removes application-authored drawer/dialog transition durations and inertial flings while preserving direct scrolling, progress/status text and all controls. It uses an explicit local preference, not an assumed system API. Partial response bodies are kept out of the accessible tree until completion; the Responding status and Cancel remain available. There are no application announcements for each streamed token or hidden reasoning.
Qt intentionally suppresses a password field's accessible Name while its password state is set. Preserve that state and masked Value. The field therefore also has a visible non-secret label and labelled Description, neither containing the entered value. This behavior was checked against the actual Qt6.12 build and Qt's password-name guard. The native regression checks the label/Description/password state and absence of the synthetic secret from every exposed string; it never clears password state merely to obtain a Name.
Complete-action and retained-source lists expose named read-only pages. Home/End and Up/Down select pages, and Tab enters their text. The exact-review test reads all accessible page values and reconstructs the verified canonical payload, including its final bytes, without approving/submitting anything. Navigation, labels and accessibility actions do not bypass the existing broker or decision checks.
Required real installed walkthrough
Offscreen assertions and QAccessible/AT-SPI tree dumps are preparatory evidence.
They cannot pass accessibility.real. First obtain a concrete session/assistive-
technology setup scope; there is no host package-install grant. A disposable
guest may be used after its separate boot/desktop/reader scope is approved.
Use the actual candidate installed package, a new private HOME/XDG/auth store,
synthetic credentials and a bounded loopback canned provider. Never use existing
accounts/stores, refresh a real catalog or send real inference. Record exact
package/source hashes, Qt/platform/session, AT-SPI transport, reader name/version
and actual operator observations with raw logs and cleanup.
The operator must exercise each of these at100/150/200%, including 520x400 and enlarged text:
- History filtering, selection, rename and keyboard traversal with visible focus.
- Compose/send against only the synthetic provider, then cancel a held synthetic stream. Focus/navigation alone must not submit or rerun anything.
- Settings sections and local motion, keeping chat/provider drafts and complete labels usable. Confirm nonessential transitions stop and progress remains.
- Synthetic auth/password entry, clearing/cancellation and privacy: the reader identifies the field without speaking its secret value.
- Full verified exact action, including risk/deadline/final bytes. Use a pending synthetic proposal and Deny/Close; no shell execution or automatic approval.
- Complete retained source, page navigation/copy and return to conversation. Do not treat a truncated preview as the complete action or source.
Keep real reader behavior and operator observations separate from the synthetic
format validator in tests/integration/src/m4-accessibility.ts. That validator
checks record completeness only. If any flow is missing, a secret is spoken,
work happens automatically or cleanup fails, retain the failing/unrun result.
A missing real-reader or final-installed keyboard run remains a release blocker
unless the owner accepts its exact scoped exception. The separate final owner
installed-build acceptance is still Task12.