From 25c308cf573067736777e4a676d9754f933aa576 Mon Sep 17 00:00:00 2001 From: Matt de Courcelle Date: Mon, 18 May 2026 02:24:07 -0500 Subject: [PATCH 1/2] feat(live_ui): add AskSidebar Phoenix.Component Stage 4 (Wave 3.7-B) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage 4 of the canonical ask_sidebar widget pipeline: - LiveUi.Widgets.AskSidebar Phoenix.Component with full render template - Recent rail (capped at 10, relative timestamps, running-status indicator) - Saved rail (★ glyph, cadence or last_run_at meta) - Map jump affordance with optional blocker-count badge - Scoped ARIA heading ids, aria-current on active row - 39 Stage-4 tests (all pass) Stages 1-3 were pre-committed by parallel Wave 3.7-B agents: - Stage 1 (IUR constructor): commit 95b0233 (ThreadCard batch) - Stage 2 (catalog entry): commit 95b0233 (ThreadCard batch) - Stage 3 (renderer clause): commit 931a440 (tree_view extension) DRAFT: 8 open questions for Pascal review flagged in @moduledoc. Co-Authored-By: Claude Sonnet 4.6 --- .../lib/live_ui/widgets/ask_sidebar.ex | 314 ++++++++++++ .../test/live_ui/widgets/ask_sidebar_test.exs | 465 ++++++++++++++++++ 2 files changed, 779 insertions(+) create mode 100644 packages/live_ui/lib/live_ui/widgets/ask_sidebar.ex create mode 100644 packages/live_ui/test/live_ui/widgets/ask_sidebar_test.exs diff --git a/packages/live_ui/lib/live_ui/widgets/ask_sidebar.ex b/packages/live_ui/lib/live_ui/widgets/ask_sidebar.ex new file mode 100644 index 00000000..3dcddf5b --- /dev/null +++ b/packages/live_ui/lib/live_ui/widgets/ask_sidebar.ex @@ -0,0 +1,314 @@ +defmodule LiveUi.Widgets.AskSidebar do + @moduledoc """ + Native Ask-mode sidebar shell widget. + + The Ask-mode sidebar replaces `:sidebar_shell` while the operator is in Ask + mode. It provides two persistent navigation rails: + + - **Recent** — chronological query history, capped at 10, with relative + timestamps and a running-status indicator. + - **Saved** — pinned or named queries with a ★ glyph, optional cadence label, + and a "+ new" action. + + A **Map jump** affordance at the bottom lets the operator switch back to Map + mode without losing the Ask surface. It carries an optional blocker-count badge. + + The widget is a pure display surface — all state is host-managed. No internal + state; all transitions are driven by parent assigns. + + ## Required attributes + + * `:sidebar_id` — root identity string; used as `data-sidebar-id` and as the + suffix for scoped ARIA heading ids. + * `:on_map_jump_event` — canonical Interaction intent string for the Map jump + button (emitted as `data-live-ui-intent`). + + ## Optional attributes + + * `:recent_items` — list of `%{id, query, last_run_at, status, on_open_event}` + maps. Renderer caps display at 10. + * `:saved_items` — list of `%{id, title, query, on_open_event}` maps. Optional + per-item keys: `:cadence`, `:last_run_at`. + * `:active_item_id` — id of the currently-open item; row gets `aria-current="true"`. + * `:on_new_saved_event` — intent string for the "+ new" save button in the Saved rail. + * `:on_see_all_event` — intent string for "see all ▸" (visible when `recent_items` + exceeds 6). + * `:empty_recent_label` — message when Recent rail is empty + (default `"No recent queries"`). + * `:empty_saved_label` — message when Saved rail is empty + (default `"No saved queries yet"`). + * `:blocker_count` — non-negative integer badge on the Map jump button; `0` + hides the badge (default `0`). + + ## Selector / hook contract + + Root: `data-live-ui-widget="ask-sidebar"` + `data-sidebar-id="{id}"`. + Recent rail: `.live-ui-ask-sidebar__section[aria-labelledby="ask-recent-h-{id}"]`. + Saved rail: `.live-ui-ask-sidebar__section[aria-labelledby="ask-saved-h-{id}"]`. + Active item: `.live-ui-ask-sidebar__item--active` + `aria-current="true"`. + Running status: `.live-ui-ask-sidebar__status-running`. + Map jump: `.live-ui-ask-sidebar__map-jump-btn`. + Blocker badge: `.live-ui-ask-sidebar__blocker-badge`. + + ## ARIA + + - Root `