git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commite930986f2d5c0c1a340262ced5695e83a8f44cb7
parent3099629185
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-08 08:53
docs: an accessibility and locale roadmap (rfc-accessibility-locale)

The 2026-10-07 audit found no accessibility support and no locale
handling. docs/rfc-accessibility-locale.md records the measured state —
no AT-SPI/AccessKit/ARIA, 11 of 27 apps immediate-mode, plate navigation
opt-in in four, "en-US" in all four FontSystem constructors, context-menu
labels used as identifiers, carets and TextBox assuming left-to-right
monospace text — and a phased plan: the locale from the environment;
an accessibility tree from UiContext plus a hook for immediate-mode apps;
platform adapters; keyboard first; bidi- and CJK-correct editing;
translatable strings.

CLAUDE.md points at it with the two rules that keep the retrofit cheap
until phase 1 lands: a new widget declares what it is, and an action is
keyed by an ID, never by its label text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 CLAUDE.md                        |  13 ++++
 docs/rfc-accessibility-locale.md | 137 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 150 insertions(+)

diff --git a/CLAUDE.md b/CLAUDE.md
index 639cb39..d27fd6c 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1562,6 +1562,19 @@ the ladder should be supplying, and the audit counts them.
   and a measured change for spacing: screenshot in a shadow session, count
   the columns of flat face at the edges and across a split, and they match.
 
+## Accessibility and locale are on the roadmap (read `docs/rfc-accessibility-locale.md`)
+
+There is no accessibility support yet (no AT-SPI, AccessKit, NSAccessibility or ARIA), the
+locale is the literal `"en-US"`, and the editors assume left-to-right text (`TextBox`
+assumes monospace too). The RFC has the measured state and a phased plan. Until it lands,
+two rules keep the retrofit cheap:
+
+- **A new widget declares what it is**: its `focus_role`, and a label that names it to a
+  person (not to a host).
+- **An action is keyed by an ID, never by its label text.** A context-menu row's label is its
+  identity today and hosts match on it, so translating any label breaks its action; do not
+  add more code that matches on displayed text.
+
 ## The `scene/` core rebuild (read `docs/rfc-core-rebuild.md` before touching it)
 
 `src/scene/` is a **retained scene graph being grown additively** to replace three overlaid legacy
diff --git a/docs/rfc-accessibility-locale.md b/docs/rfc-accessibility-locale.md
new file mode 100644
index 0000000..8ff6187
--- /dev/null
+++ b/docs/rfc-accessibility-locale.md
@@ -0,0 +1,137 @@
+# RFC: Accessibility and locale — what the toolkit owes people who are not its author
+
+**Status:** Draft / roadmap (2026-10-08)
+**Scope:** Assistive-technology access (screen readers, switch and voice control),
+keyboard-only use, the user's locale, right-to-left and non-monospace text editing, and
+translatable UI strings — across `cce-ui` and the apps built on it.
+**Appetite:** Phased. Phase 0 is a few lines; each later phase is its own piece of work
+and leaves every app building and drawing as before until the step meant to change it.
+**Reads before this:** `CLAUDE.md` § "Plates, wells and seams" (focus roles, the walk),
+§ "Input-method composition is one model for every shell" (the IME model this extends);
+`docs/rfc-core-rebuild.md` (the widget tree the accessibility tree would mirror).
+
+---
+
+## 1. Why now
+
+The toolkit audit of 2026-10-07 found no accessibility support and no locale handling at
+all. Both are cheap to design for and expensive to retrofit: every widget added without a
+role, every label used as an identifier, and every editor that assumes left-to-right
+monospace text is a later rewrite. The widget set is still growing (the field family, the
+spreadsheet's selection and the trackball all landed in the last two weeks), so this is
+the cheapest moment there will be.
+
+## 2. Where things stand (measured 2026-10-08)
+
+**Accessibility.**
+- Nothing in the tree speaks to assistive technology: no AT-SPI, no AccessKit, no
+  NSAccessibility, no ARIA. A screen reader sees an untitled surface.
+- What exists is a good seed. Every retained widget reports a `FocusRole` (`Plate`, `Well`
+  or `None`), a `label()`, a value (`Adapted::get_value_string`), its rect, its focus and
+  its `type_name()`, and `UiContext` holds them all by `WidgetId`. Since phase 2 of the
+  pointer work (`widget::Owned`) every registered app widget sits at a stable address.
+- 11 of the 27 `Application` apps draw immediate-mode (no `UiContext` tree): calendar,
+  browser, grid, keyboard, screensaver, map, status bar, model, preview, terminal,
+  notifier. They have nothing for a tree to be built from.
+
+**Keyboard.**
+- Tab / Shift+Tab plate navigation exists (`UiContext::focus_step`, `focus_step_group`)
+  but is OPT-IN (`Application::plate_navigation`, default false). Four apps turn it on:
+  cce-data-editor, cce-files, cce-gallery, cce-system-interface (plus the demo).
+- There is no tooltip (where an accessible description usually lives), no modal dialog
+  widget (where focus must be trapped), and no radio group.
+
+**Locale.**
+- The locale is the literal `"en-US"` in all four `FontSystem` constructors
+  (`lib.rs`, `backend/text.rs` twice, `backend/frame.rs`). cosmic-text uses it to order
+  its font fallback, so a Japanese user gets the Chinese variants of shared Han glyphs.
+- No message catalogue. The toolkit itself ships about 20 English strings (the context
+  menu's Cut / Copy / Paste / Select All / Undo / Redo, Copy Path / Copy Key / Copy Value,
+  Expand / Collapse, Properties, Search…, OK / Cancel / Save / Delete).
+- **Labels are identifiers.** A context-menu row's text is its identity (CLAUDE.md: "the
+  text stays the row's identity, so hosts that match their own labels still match"), and
+  hosts dispatch by comparing label strings. Translating a label today breaks the action
+  behind it.
+
+**Text direction and script.**
+- cosmic-text shapes bidirectional text and lays out right-to-left runs, so DRAWING Arabic
+  or Hebrew mostly works.
+- EDITING does not. `ShapingMeasure::offsets` sorts glyph positions by byte and assumes x
+  grows with the text (false inside a right-to-left run); the `DocEditor` and `LineEdit`
+  carets are built on it. `TextBox` is a column editor: it measures one monospace advance
+  (`"MMMMMMMM"` / 8) and wraps by character count, which holds for neither proportional
+  fonts nor CJK, whose characters are two columns wide.
+- The IME model (`crate::ime`) is direction-agnostic and needs no change.
+
+## 3. The plan
+
+### Phase 0 — the locale comes from the user (small; do first)
+
+One `cce_core::locale()`: `LC_ALL`, else `LC_CTYPE`, else `LANG`, turned from POSIX form
+(`ja_JP.UTF-8`) into a BCP 47 tag (`ja-JP`), falling back to `en-US`; in the browser,
+`navigator.language`. The four `FontSystem` constructors take it. Nothing changes for an
+`en_US` session, which is every session today.
+
+### Phase 1 — an accessibility tree the toolkit can produce
+
+- A node per registered widget: **role** (from `type_name` and `FocusRole` first, then an
+  explicit `Input::a11y_role()` a widget can override: button, checkbox, toggle-button,
+  slider, spin-button, text-input, combo-box, list, tree, table, menu), **name** (the
+  label), **value** (`get_value_string`), **bounds**, **focus**, and the **actions** it
+  takes (activate, increment / decrement, set value, focus). Parent and child links come
+  from `UiContext`'s tree.
+- For the immediate-mode apps, an `Application::accessibility(&mut self, out)` hook where an
+  app declares its own nodes, the way it already declares `input_regions`.
+- Incremental: a node is keyed by `WidgetId`, and only changed nodes are re-sent. The
+  per-frame damage diff (`backend::frame`) is the model for what "changed" means.
+- **Rule from this phase on:** a new widget declares its role and name when it lands, and a
+  context-menu row or any other actionable label gets an ID separate from its text.
+
+### Phase 2 — speak to the platform
+
+- **Wayland / Linux:** AT-SPI over D-Bus. AccessKit's Unix adapter is the likely carrier
+  (evaluate it first; the alternative is a small AT-SPI server of our own). The compositor
+  needs nothing new: AT-SPI is a session-bus protocol between the app and the reader.
+- **macOS:** NSAccessibility through the same tree (AccessKit's macOS adapter, or direct).
+- **Browser:** mirror the tree as hidden ARIA elements over the canvas — the same move the
+  IME already makes with its hidden `<textarea>`.
+
+### Phase 3 — keyboard first
+
+- `plate_navigation` defaults to true; an app that routes Tab itself (a terminal, a web
+  view) opts OUT.
+- A modal dialog widget that traps focus, a tooltip that doubles as the accessible
+  description, a radio group.
+
+### Phase 4 — text that is not left-to-right monospace
+
+- Carets and selection from shaped clusters in VISUAL order (cosmic-text's layout runs
+  carry direction), not byte-sorted x positions: `ShapingMeasure::offsets` grows a
+  direction-aware form, and `DocEditor` and `LineEdit` use it.
+- `TextBox` moves from its column model to shaped wrapping (cosmic-text's own wrap), which
+  also makes it correct for proportional fonts and CJK.
+- Base direction per paragraph from the first strong character, as the Unicode
+  bidirectional algorithm does.
+
+### Phase 5 — translatable strings
+
+- The toolkit's ~20 strings through a small catalogue keyed by message ID, in `cce-core`
+  so services can use it too. Fluent is the candidate format.
+- Apps adopt it at their own pace. Only phase 1's rule — actions keyed by ID, never by
+  label — has to come first.
+
+## 4. Order and cost
+
+Phase 0 is an afternoon. Phase 1 is the real design work, and everything after it stands
+on it; it is additive, and the tree costs nothing for an app nobody inspects. Phases 3 and
+4 are independent of 1 and 2 and can go in parallel. Phase 5 waits on phase 1's ID rule.
+
+## 5. Open questions
+
+- AccessKit, or our own AT-SPI server? Decide by building phase 2 against AccessKit for one
+  app (cce-data-editor: plate navigation already on, a tree list, text fields, a menubar)
+  and measuring what it costs in dependencies and in frame time.
+- Should the compositor expose its own UI (window titles, overview, the grid) to AT-SPI?
+  It draws natively, so it would need its own tree.
+- Do immediate-mode apps get the hook or get ported? The status bar and the notifier are
+  the most-used surfaces in the DE, and both are immediate.