Nine surfaces hand-rolled the same anchored-overlay + row-list + barrier + keyboard-nav pattern. Extract one owned primitive (no Material): - ClideAnchoredOverlay (clide_anchored.dart): positioning + lifecycle — LayerLink/CompositedTransformFollower or centred Positioned, side/align + auto-flip on viewport bounds, full-screen tap-away barrier, OverlayEntry bookkeeping, focus capture, Esc-to-close. Driven by a ClideOverlayController. - ClideMenu + ClideMenuListController (clide_menu.dart): a dropdown-token row surface (items + separators) with arrow/enter/escape nav, skip-disabled, active mark, per-item colour/leading glyph, keepOpenOnSelect (live-apply), and onArrowLeft/Right hooks. The nav controller is reusable by surfaces that keep bespoke rows (typeaheads, quick-open). Additive — no call sites changed yet. D-88 records the convention (new `design` domain): anchored pickers build on these; modal pickers stay on DialogRouter. Tests: clide_anchored_test (open/close, barrier, Esc, centred, clean dispose) and clide_menu_test (list-nav skip/wrap, select + onClose, disabled, Esc, keepOpenOnSelect; pure ClideMenuListController cases). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2.2 KiB
2.2 KiB
Design Decisions
User-facing surface — UX, UI conventions, and the public shape of clide-owned widget primitives.
D-88: clide-owned anchored popover + menu primitive
- Date: 2026-06-08
- Decision: Anchored, non-modal popovers (dropdowns, pickers, typeaheads, the command palette) build on two clide-owned primitives in
lib/widgets/:ClideAnchoredOverlay(positioning + lifecycle — aLayerLink/CompositedTransformFolloweror centredPositioned, a full-screen tap-away barrier,OverlayEntrybookkeeping, focus capture, Esc-to-close, and auto-flip on viewport bounds) andClideMenu+ClideMenuListController(a dropdown-token row surface with arrow/enter/escape nav, skip-disabled/separator, active mark, and a reusable nav controller for surfaces that keep bespoke rows). No Material/Cupertino. Modal, centred dialogs (session / project / branch pickers) stay on the kernelDialogRouter— a separate concern. - Rationale: Nine surfaces had hand-rolled the same anchored-overlay + row-list + barrier + keyboard-nav pattern (menu-bar dropdowns, theme picker, slash + @ typeaheads, quick-open, three modal pickers), each re-deriving positioning, dismissal, and nav — divergent a11y, inconsistent dismissal, and a
pumpAndSettle-hostile spread of ad-hoc overlays. Owning one primitive (per "own the rendering stack", D-5) makes the behaviour uniform and testable once, and turns the tenth surface (the T-275 permission-mode picker) into a few lines instead of another hand-roll. - Cost: A migration sweep across the existing surfaces (menu bar, theme picker, typeaheads, quick-open); the typeaheads keep their text-completion/key pipeline and only delegate anchoring + body, so the primitive must stay composable (a bare lifecycle wrapper + an optional turnkey menu), not a monolith. New UI authors must reach for the primitive rather than rolling another overlay.
- Raised by: 2026-06-08 — user, while building the T-275 permission-mode picker: "since we don't do Material doesn't mean we can't make components of our own." Realises the "own the rendering stack" guardrail at the component level. Tracked by epic T-286.