2026 02 24_spatial_accessibility_research - mark-ik/graphshell GitHub Wiki
Status: Research Goal: Define the architecture for making the entire application (Graph, UI, Web Content) navigable, understandable, and efficient for non-visual users.
Accessibility is not limited to the spatial graph. The application has three distinct accessibility domains that must be unified:
-
Standard UI (Toolbar, Panels, Settings): Handled by
egui's built-inaccesskitintegration. Requires validation of tab order and label semantics. - Web Content (Servo): Handled by Servo's internal accessibility tree. Requires a Bridge to merge Servo's tree into the host window's accessibility tree.
- Spatial Graph (Canvas): The novel challenge. Requires custom linearization and sonification (detailed below).
Graphshell's primary interface is spatial (force-directed graph). Screen readers (SR) are linear (stream of text/controls).
- Visual User: Perceives clusters, outliers, and density at a glance.
- Non-Visual User: Perceives one item at a time.
The Goal: Provide "Glanceability" via audio/haptics and "Navigability" via structured linearization.
We need a deterministic way to flatten the graph into a list or tree structure that accesskit can consume.
When a node is focused, treat it as a "Room".
- Content: The node's title, URL, tags.
- Doors (Edges): List of connected nodes, grouped by relationship type (Parent, Child, Associated).
- Walls (Context): "You are in the 'Rust' cluster. 5 other nodes nearby."
How to traverse the whole graph without getting lost?
- Spatial Sweep: Sort nodes by Y, then X (reading order). Good for finding "what's top-left".
-
Semantic Hierarchy: Use UDC tags or Community Detection (Louvain/Leiden) to build a tree.
- Level 1: Clusters ("Rust", "News", "Uncategorized").
- Level 2: Hub nodes (high degree).
- Level 3: Leaf nodes.
- Minimum Spanning Tree (MST): A spanning tree allows traversing all nodes without cycles.
Recommendation: Implement Semantic Hierarchy as the primary navigation tree for SRs. It aligns with the mental model of "folders".
Map Arrow Keys to physical direction.
- Up: Find nearest node in the -Y cone (45 degrees).
- Right: Find nearest node in the +X cone.
- Benefit: Allows exploring the physical layout created by physics.
- Tab: Next node in the Linearization (see §2.2).
- Shift+Tab: Previous node.
- Ctrl+Arrow: Jump between Clusters.
Using audio to convey spatial properties that text cannot.
- Stereo Pan: Map Node X coordinate (relative to viewport center) to Left/Right audio balance.
- Volume/Reverb: Map Node Y coordinate (or distance from center) to Volume or Reverb (farther = quieter/wetter).
- Pitch: Map Node Degree (importance) to Pitch. High degree = Lower, resonant pitch (Bass). Leaf = High, light pitch (Tink).
-
Timbre: Map Content Type (MIME) to instrument.
- Web: Piano.
- PDF: Strings.
- Image: Percussion.
As the cursor moves (or during physics simulation), play a background texture representing graph density/energy.
- High Energy (Moving): Active, chaotic texture.
- Low Energy (Settled): Calm, harmonic drone.
We cannot rely solely on egui's default widget accessibility for a custom painted graph.
-
Action: Implement a
GraphAccessKitAdapter. -
Function: Syncs
Graphstate toegui::Context::accesskit_root. - Optimization: Only update the "Virtual Tree" when the graph settles or selection changes.
Linearizing a 10k node graph or synthesizing audio shouldn't block the UI.
-
AccessibilityWorker: A background task (supervised by
ControlPanel) that computes the Linearization and drives the Audio Engine. -
Updates: Listens to
GraphIntent(likeAddNode,SelectionChanged) and pushes updates to the UI/Audio.
-
accesskit: Already in use byegui. We need to feed it custom tree updates for the canvas. -
rodio/symphonia: Already selected forAudioViewer. Reuse for sonification. -
fundsp: For procedural audio synthesis (generating the "Density Hum" or UI sounds dynamically).
A dedicated TileKind::List or a mode in TileKind::Graph that renders the graph as a standard egui::Table or Tree.
- Why: Sometimes a list is just better.
-
Integration:
Ctrl+Ltoggles the active Graph Pane between "Canvas Mode" and "List Mode".
Currently, gui.rs drops accessibility updates from Servo (notify_accessibility_tree_update).
- Gap: Web content is invisible to screen readers.
-
Fix: Implement a bridge that accepts
accesskit::TreeUpdatefrom Servo and grafts it into theeguiaccessibility tree at theWebViewwidget's node ID. -
Mechanism:
eguiexposes hooks to append external trees. We must map Servo's root ID to the egui widget ID.
- Blind Test: Navigate from Node A to Node B using only keyboard and audio.
- Screen Reader: Verify NVDA/VoiceOver reads "Node: Rust Homepage, 3 connections" instead of "Graphic".
- Web Content: Verify screen reader can enter a webview and read page content (via the Bridge).
- Gap: Dynamic events (physics settling, sync completion, node arrival) are currently visual-only.
-
Refinement: Implement an
Announcerservice that pushes text updates toaccesskit's live region API. - Policy: "Polite" announcements for background events (sync), "Assertive" for errors or direct interactions.
- Gap: Users may get "trapped" in the graph canvas or webview if keyboard navigation doesn't provide an escape hatch.
-
Refinement:
-
Skip Links: "Skip to Toolbar", "Skip to Graph", "Skip to Content" shortcuts (e.g.,
F6). -
Programmatic Focus: When switching views (Graph <-> Detail), explicitly move
accesskitfocus to the primary element of the new view.
-
Skip Links: "Skip to Toolbar", "Skip to Graph", "Skip to Content" shortcuts (e.g.,
- Gap: Force-directed motion triggers vestibular disorders.
-
Refinement: Query OS
prefers-reduced-motion.- If true: Physics defaults to
Paused(static layout) orInstant(compute layout in background, then render). - Animations (zoom, orbit) become instantaneous.
- If true: Physics defaults to
- Gap: Sonifying 500 nodes individually creates audio chaos.
-
Refinement: Audio LOD must match Visual LOD.
- Zoomed In: Hear individual nodes (pitch by degree).
- Zoomed Out: Hear cluster "drones" or summary sounds (pitch by cluster size).