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

commit8a4650bafa2a8a1b2d50536f43b56aade0cc2d10
parent639bed8bba
authorLucas Galante <[email protected]>
date2026-07-11 14:12
docs(rfc): record Phase 6ao — hand-aggregate apps off the getters; display-manager UAF fixed

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018u7qTwzX95dd5ysAkaSCLk

 docs/rfc-core-rebuild.md | 39 ++++++++++++++++++++++++++++++++++-----
 1 file changed, 34 insertions(+), 5 deletions(-)

diff --git a/docs/rfc-core-rebuild.md b/docs/rfc-core-rebuild.md
index ebce6c6..dc0f061 100644
--- a/docs/rfc-core-rebuild.md
+++ b/docs/rfc-core-rebuild.md
@@ -1369,18 +1369,47 @@ Constraint respected: **each crate still builds standalone** — the new core is
       the getters are gone from here. Settings A/B AE=0 on Accounts + (stash-based)
       the spinbox-heavy Audio page. The last difference vs the getter is
       widget_font→text_font, which coincides except for a custom-font TextBox.
-    - `.text_labels()` / `.text_labels_with_bounds()` → **four hand-aggregate
+    - ~~`.text_labels()` / `.text_labels_with_bounds()` → **four hand-aggregate
       apps** (email, authenticator, display-manager, layout-interface) whose
-      `display_list()` emits widget text by calling the getter per widget.
+      `display_list()` emits widget text by calling the getter per widget.~~
+      **DONE (6ao)** — see below.
     - Note: the "orphaned" containers Layer / Page are NOT deletable — Layer is
       the embedded base of the live Plate/Page; Page is embedded by the live
       Paginator (transitive liveness through inheritance, not direct app use).
   - **6an — render_widget off the getters (see above). DONE (all 18 apps
     compile; 176 tests; settings A/B AE=0 on Accounts + Audio).** One of the
     three getter-consumer classes cleared.
-  - **Still to do:** the two remaining getter-consumer classes — cce-designer's
-    custom render loop and the four hand-aggregate apps' `.text_labels()` — THEN
-    delete the getters, then `Element` + `Adapted` (TreeList → narrow traits).
+  - **6ao — the four hand-aggregate apps off the getters. DONE (176 tests;
+    all four A/B-verified live).** New `scene::painter::append_widget_text(ui,
+    &dyn Element, &mut PaintCtx)`: walks the subtree and appends only its
+    `Prim::Text` items — per-widget content font, walk clip composed into prim
+    bounds (the 6an recipe as a reusable helper). Every per-widget
+    `.text_labels()`/`.text_labels_with_bounds()` call in email, authenticator,
+    display-manager and layout-interface replaced with it. A/B results:
+    email + layout-interface byte-identical; authenticator + display-manager
+    identical except widget-owned labels now render in the widgets' configured
+    control font (legacy aggregates dropped the font to `None` — the same
+    widget_font→text_font delta as 6an, here visible because these apps
+    configure a monospace control font).
+    - **Display-manager UAF found + fixed:** its `new()` linked the ui_context
+      tree and captured `focused_widget` while the State was a stack local, so
+      every registered pointer dangled after the move — the walk's child
+      descent was the first render-path consumer to dereference them (abort on
+      a garbage-length alloc); `propagate_event` and the `all_*` child
+      aggregation read the same stale pointers all along. Fix: per-frame
+      idempotent `relink_tree()` (register/link are id-keyed) + initial focus
+      re-derived from the boxes' own focus flags. Also: its flat `widgets_iter`
+      lists the card AND the card's children, so text moved to walking the TRUE
+      roots (bg + root_container) — flat would double-emit — and `LoginCard`
+      (a container with own, non-aggregating labels) got the Plate-style
+      `paint_self` override for its two header labels.
+    - Lesson for the getter deletion: an app whose tree is linked from `new()`
+      by value is a dangling-registry candidate — audit any remaining
+      `link_parent_child` calls made before the owning struct reaches its
+      final address.
+  - **Still to do:** the last getter-consumer class — cce-designer's custom
+    render loop (`get_text_items`) — THEN delete the getters, then `Element` +
+    `Adapted` (TreeList → narrow traits).
 
 Order rationale: each phase is independently valuable and reversible, and no phase requires the
 next to compile. Phase 0 can land immediately regardless of the rest.