2026 02 25_accessibility_contracts_diagnostics_and_validation_strategy - mark-ik/graphshell GitHub Wiki
SUPERSEDED — This document has been consolidated into
SUBSYSTEM_ACCESSIBILITY.md(Cross-Cutting Subsystem: Accessibility). Retained for historical reference only. Do not use as authoritative.
Status: Active / Project Goal Superseded (2026-02-25)
Scope: Cross-cutting guarantees for WebView accessibility bridge, graph accessibility, focus/navigation, live announcements, and future viewer surfaces
Companion docs:
-
→ see2026-02-24_spatial_accessibility_plan.mdSUBSYSTEM_ACCESSIBILITY.md -
../research/2026-02-24_spatial_accessibility_research.md(research basis) -
../research/2026-02-24_diagnostics_research.md(observability/registry design precedent)
The spatial accessibility plan defines features (WebView bridge, Graph Reader, navigation, sonification). This document defines guarantees:
- What must remain accessible as the system evolves
- How regressions are detected at runtime and in CI
- How future viewers/panes declare and prove accessibility support
Accessibility is a project-level reliability requirement, not a one-time UI deliverable.
Accessibility is treated as a first-class runtime subsystem with four layers:
- Accessibility Contracts (schema/invariants)
- Declarative requirements for focus, tree integrity, update handling, action routing, and degradation behavior.
- Accessibility Runtime State
- The live bridge/adapter state (queued updates, anchors, focus targets, mode state, counters).
- Accessibility Diagnostics
- Runtime channels, health metrics, and invariant violations emitted through the diagnostics system.
- Accessibility Validation
- Unit/integration/scenario tests + CI gates that enforce contract compliance over time.
This mirrors the diagnostics architecture principle: declarative contracts + runtime observability + deterministic validation.
These invariants are mandatory and should be encoded as explicit checks/tests/diagnostics.
- Stable IDs
- Virtual accessibility node IDs must be deterministic across refreshes for the same semantic entity.
- Focused element identity must survive non-semantic refreshes.
- No orphan subtrees
- No accessibility subtree may be injected without a valid registered parent/anchor in the current frame.
- No duplicate active roots
- A surface/viewer contributes at most one active root subtree per frame per anchor.
- Parent/child consistency
- Every child reference in emitted/injected nodes refers to a node present in the same update set or a stable pre-existing parent/anchor.
- Stale update safety
- Updates for closed/removed webviews or panes are dropped deterministically and logged, never causing panics or memory growth.
- Focus preservation on refresh
- If a focused semantic target still exists after refresh, focus remains on that target.
- Predictable fallback focus
- If the focused target disappears, focus falls back to a documented parent/sibling/root policy (never to arbitrary elements).
- Mode transitions preserve return path
- Room ↔ Map transitions retain enough state to restore focus to the prior semantic location.
- F6 region cycle completeness
- Top-level focus cycle is deterministic and visits all required regions in a stable order.
- Action delivery correctness
- AccessKit action requests are routed to the owning subsystem (egui widget, Graph Reader, or WebView bridge target).
- No cross-surface misrouting
- Actions intended for one webview/pane must not mutate another.
- Unsupported action behavior is explicit
- Unsupported actions return/log a clear outcome; they are not silently ignored in a way that appears successful.
- Graceful degradation is declared
- If a capability is unavailable (e.g., WebView bridge disabled), the system emits diagnostics and exposes user-visible status.
- Degradation is non-silent
- Repeated fallback/drop paths must be observable (counters/channels), not one-time logs only.
- Fallback remains usable
- Core app navigation remains accessible to the maximum supported extent even when one subsystem degrades.
To extend accessibility to future components, each surface/viewer should declare accessibility capabilities explicitly.
Each viewer/surface (e.g., Servo webview, Wry webview, graph canvas, tool pane) should provide:
surface_id-
owner_source(core,viewer,mod, etc.) -
capabilities:native_tree_bridgevirtual_treefocus_syncaction_routinglive_regionskeyboard_navigation
-
degradation_mode:fullpartialnone
-
notes/reasonfor unsupported capabilities
- Prevents silent regressions when new viewers are added
- Enables diagnostics-pane health summaries by surface
- Lets CI assert minimum accessibility support for core surfaces
- Provides a contract point for mod-contributed panes/viewers
This can be implemented as an AccessibilityRegistry or folded into existing viewer/surface registries with an accessibility sub-structure.
Accessibility must be observable via the diagnostics system, not just logs.
Suggested channel families (core/runtime):
accessibility.bridge.webview_update_receivedaccessibility.bridge.webview_update_queuedaccessibility.bridge.webview_update_injectedaccessibility.bridge.webview_update_droppedaccessibility.bridge.webview_update_conversion_failedaccessibility.bridge.webview_anchor_missingaccessibility.bridge.webview_stale_updateaccessibility.focus.sync_succeededaccessibility.focus.sync_failedaccessibility.action.routedaccessibility.action.route_failedaccessibility.graph.virtual_tree_rebuiltaccessibility.graph.virtual_tree_throttledaccessibility.graph.virtual_tree_invariant_failedaccessibility.announcer.message_emitted
Severity guidance:
-
*_failed,*_conversion_failed,*_invariant_failed→Error -
*_dropped,*_anchor_missing,*_stale_update,*_throttled→Warn - normal lifecycle channels (
received,queued,injected,routed,rebuilt) →Info
The diagnostics pane should expose an accessibility section with:
- WebView bridge status (
active,degraded,disabled) - Last update latency / queue depth
- Recent drop/conversion-failure counters
- Focus sync success/failure counts
- Active Graph Reader mode (
Off,Room,Map) - Capability coverage summary by surface/viewer
Accessibility invariant failures should follow the same pattern as diagnostics invariants:
- explicit invariant IDs
- structured context (surface/viewer, target IDs, reason)
- session counts
- last occurrence timestamp
- Unit tests (deterministic)
- Node ID derivation stability
- Semantic hierarchy ordering
- Room/Map tree builders
- WebView tree conversion compatibility (0.24 -> egui-compatible types)
- Fallback/degradation policy decisions
- Integration tests (headless/local state)
- Focus preservation across tree refreshes
- Action routing to correct target
- F6 region cycle order
- Room ↔ Map return-path focus restoration
- Scenario tests (harness / diagnostics-backed)
- WebView bridge receives updates and injects (or degrades with explicit diagnostics)
- Graph Reader updates emit tree rebuild/throttle channels
- Accessibility health remains green under typical workflows
- Manual smoke checks (screen reader)
- Platform-specific smoke scripts (NVDA/Windows, Orca/Linux, VoiceOver/macOS when applicable)
- Required for milestone gates, not every PR
Add a dedicated accessibility test lane with required checks for PRs touching:
shell/desktop/ui/**shell/desktop/workbench/**render/**app.rs- viewer integration / webview lifecycle code
- accessibility/diagnostics registries and adapters
Minimum CI requirements (phased):
Phase A (immediate):
- unit tests for ID stability + degradation policy
- compile-time guard that the WebView bridge fallback is observable (diagnostics/log path present)
Phase B (WebView bridge functional):
- integration test proving
received -> injectedpath works - no unexpected drop/conversion-failure diagnostics in the happy path scenario
Phase C (Graph Reader landed):
- deterministic linearization tests
- focus preservation tests
- F6 and mode-switch navigation tests
For virtual trees (Graph Reader, key tool panes), add golden snapshots:
- Tree shape
- Labels/descriptions
- Node IDs (or stable ID derivation traces)
Golden snapshots should be:
- small
- deterministic
- reviewed when changed (not auto-regenerated silently)
Accessibility support is allowed to degrade; silent undefined behavior is not.
For each surface/viewer:
-
Full: complete expected support -
Partial: some capabilities unavailable (must enumerate which) -
Unavailable: no active accessibility bridge/tree
When degraded/unavailable:
- diagnostics channels emitted
- diagnostics pane status reflects degradation
- log message is rate-limited (no spam)
- optional UI indicator for developers (debug/diagnostics mode)
Current known condition:
- Servo emits
accesskit 0.24 - egui 0.33 consumes
accesskit 0.21
Required behavior until fixed:
- queue updates
- convert or fail deterministically
- emit
accessibility.bridge.webview_update_conversion_failedor equivalent - report degraded bridge status
- avoid unbounded queue growth and panics
- Forwards native accessibility updates/events to GUI
- Preserves source identity (
WebViewId, window context) - Does not silently drop updates without diagnostics
- Queues, anchors, converts, injects, and diagnoses bridge updates
- Enforces stale-update and anchor-missing policies
- Tracks bridge health state and counters
- Produces deterministic virtual tree + stable IDs
- Enforces Room/Map tree integrity invariants
- Emits rebuild/throttle diagnostics
- Owns F6 cycles and Graph Reader command routing
- Preserves focus return-path semantics
- Records accessibility channels and invariant failures
- Exposes health summaries and forensic history
This sequence should be treated as a prerequisite scaffold for future accessibility features.
- Accessibility observability baseline
- Add channels + runtime counters + diagnostics pane status summary placeholders.
- WebView bridge plumbing hardening
- Anchor mapping (
WebViewId -> egui::Id) - stale-update policy
- queue metrics
- Type compatibility / conversion layer
- Resolve
accesskitversion split via compatibility conversion (or dependency alignment if feasible) - restore real injection path
- Bridge invariants + tests
- Add explicit invariant checks and CI tests for bridge happy path and degradation path
- Graph Reader implementation
- Build
GraphAccessKitAdapteron top of the above diagnostics/validation scaffolding
- Focus/navigation guarantees
- Add F6/mode/focus preservation tests and diagnostics
- Announcer + sonification
- Add observability and validation contracts as these subsystems land
Based on current code:
- Add a WebView accessibility bridge status struct/counters in
Guiand expose diagnostics channels. - Add
WebViewId -> egui::Idaccessibility anchor registration path in tile/webview rendering. - Implement an
accesskitcompatibility conversion spike (0.24 -> egui-compatible) with unit tests. - Replace the current warn-and-drain fallback with:
- convert-and-inject on success
- structured failure diagnostics on conversion error
This project can claim future-facing accessibility guarantees only when all of the following are true:
- Accessibility contracts are documented and encoded as tests/invariants
- Accessibility diagnostics channels are part of the core diagnostics schema
- CI has required accessibility checks for UI/viewer changes
- Degradation modes are explicit, observable, and tested
- New viewers/surfaces must declare accessibility capability coverage
Until then, accessibility is an implementation effort; after that, it becomes a maintained system property.