16 KiB
Events in the dock, the left pane deleted -- Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Step 4 of the badges/dock redesign (spec docs/superpowers/specs/2026-11-02-ui-badges-dock-design.md): a compact Events section in the dock, the full events list and its actions on an events slide-over page, the left events pane / divider / header toggle deleted, and the Mallard manual, USER-GUIDE and remaining docs swept for the new layout.
Architecture: The dock gets a third block under the readouts (build_events_dock in src/ui_dock.cpp): one compact row per EventRecord (title, time, small marker; no thumbnails), a "Manage" button, one click replays. The existing pane widgets (events-pane.ui: head with Live, Compare, Accumulate, the multi-select list with thumbnails) stop being a GtkRevealer beside the plot and become the content of the events slide-over page; the list keeps its builder, thumbnail cache and selection logic. Both views are rebuilt from the same event_store->list() by the same refresh entry point.
Tech Stack: C++20, GTK4/libadwaita, the headless self-check probes (no unit tests).
Global Constraints
- The dock is fixed at 280 px; dock event rows are ellipsized labels with fixed
width-chars/max-width-chars; no thumbnails in the dock (layout must not depend on content). - No new overlay child; the page uses
slideover_add_page(App&, id, title, widget); the page id isevents. - "Disable, do not hide": the dock's Events block stays on screen, insensitive, when unavailable. Availability rule (deviation from the spec's "insensitive outside trigger mode", kept on purpose): the existing
sync_events_availabilitycondition (event_recording || replaying || events_cached) -- a replay or a group leaves trigger mode, and a block that greys itself under the click that started it is the trapinvariants-trigger-and-pinning.mdrecords. Document the deviation in README anddocs/claude. - A handler that rebuilds destroys the widgets it was emitted from: use
outlive_the_rebuild(widget).refresh_events_panemust keep nullingevents_thumbbefore it removes children.update_group_buttonskeeps itsapp.window == nullptrteardown guard. model->signals()is read only undermodel->mutex().- The page content is the pane's
events_box, never theGtkRevealer(an unrevealed revealer measures 0x0). - Selection survives marking:
mark_events_on_screenupdates titles in place and now also updates the dock rows' marker; it never rebuilds the page list. - Every new source file carries the SPDX MIT header (this plan adds none).
- Baselines: clean build = 1 warning (icons
-Woverlength-strings), incremental = 0,gtk warnings: 0, noGtk-CRITICAL. Probes need a tour-seen config for slide-over page legs (see.superpowers/sdd/task-5-report.md,verification.md). Mocks:tools/mock_udpstreamer.py(8114),tools/mock_e1608.py(54211); run serially, kill them. - The session file has no events-pane field (
events_pane_widthis memory only); there is nothing to migrate. README's claim that the width is remembered across sessions is wrong and is removed.
Task 1: The Events block in the dock (the left pane still exists)
Adds the dock block so the app stays whole; nothing is removed.
Files:
- Modify:
src/ui_dock.cpp,src/ui_app.hpp,src/ui_events.cpp,src/ui.cpp(dock assembly, availability sync, tick),src/ui_probe.cpp,CMakeLists.txt(only if a file is added; none expected)
Interfaces:
-
Produces (declared in
ui_app.hpp, defined inui_dock.cpp):GtkWidget* build_events_dock(App& app);-- verticalGtkBox: a row with the "Events" label and a "Manage" textGtkButton(callsslideover_open(app, "events"); a no-op until Task 2 registers the page), then aGtkListBox(app.dock_events_list, selection mode none, single-click activation) of compact rows, inside aGtkScrolledWindowwith a fixedmin-content-height/max-content-height(about 4 rows) andvscrollbar-policyautomatic. Storesapp.dock_events_box,app.dock_events_list,app.dock_events_manage.void dock_rebuild_events(App& app);-- clears and refillsdock_events_listfromapp.event_store->list()(newest first, as the pane does): each row = a small marker label (dock_event_marker, see below), the title (ellipsized, same text asevent_row_title), the time as the pane shows it (ellipsized, dim). With no records: one dim "no events yet" row. Attaches theEventRecordto the row as"udpscope-event"(same key as the pane). Null guard: a no-op beforebuild_events_dockran or whenapp.event_storeis null.void dock_mark_events(App& app);-- updates each dock row's marker/title from the same datamark_events_on_screenuses (the event on screen and the group members), without rebuilding rows.void dock_events_set_sensitive(App& app, bool available);-- the one place the block's sensitivity is set;sync_events_availabilitycalls it with its ownavailablevalue.
-
Row activation (single click): calls the existing
replay_event(app, record)thaton_event_row_activated(ui_events.cpp:961) calls, with the same argument it passes; do not duplicate the replay logic -- extract the shared body ifon_event_row_activatedhas any. -
Consumes:
App::event_store,EventRecord,event_row_title()(ui_events.cpp:784),replay_event,mark_events_on_screen,slideover_open. -
Step 1: Build the block. Write
build_events_dock,dock_rebuild_events,dock_mark_events,dock_events_set_sensitiveinui_dock.cpp. Marker: a single-character label -- the on-screen event gets a filled marker, the group members another, others none (read howmark_events_on_screen(ui_events.cpp:796) decides the on-screen/group titles and reuse its predicate; do not invent a second one). CSS: checkui_widgets.cppfor existing.udpscope-event-*classes anddim-labelbefore adding any. -
Step 2: Wire it. Append
build_events_dockto the dock after the readouts box (and a separator), inui.cppwherebuild_readouts_dockis assembled. Calldock_rebuild_events(app)at the end ofrefresh_events_pane's non-empty and empty paths (every place it finishes) and fromrecord_eventunconditionally (today'srecord_eventrefreshes the pane only if the revealer is revealed; the dock must follow every latch), anddock_mark_eventsfrommark_events_on_screen. Calldock_events_set_sensitivefromsync_events_availability(ui_events.cpp:40-70) with itsavailableresult, and once after building (insensitive until available). -
Step 3: Probe. In the existing events probe (
events_probe_next,UDPSCOPE_PROBE_EVENTS_MS) printdock events: rows=<n> pane_rows=<n> same_titles=<0|1> sensitive=<0|1> dock_w=<n>after at least two latched events (the probe already produces them), wheresame_titlescompares each dock title to the pane row's title label ("udpscope-event-title") in order, and again after a replay (on_screenmarker present on exactly one dock row). Add one click on a dock row to the probe and printdock replay: clicked=<0|1> replaying=<0|1>(the dock row click starts a replay as the pane's activate does). -
Step 4: Build; run the events probe (UDPS mock), the readouts probes (regression), and check
dock_w<= 280,gtk warnings: 0. -
Step 5: Commit (
git add src/ui_dock.cpp src/ui_app.hpp src/ui_events.cpp src/ui.cpp src/ui_probe.cpp; messageui: compact events block in the dock).
Task 2: The events page; the left pane, divider and header toggle deleted
Files:
- Modify:
src/ui_events.cpp,src/ui.cpp,src/ui/main-window.ui,src/ui/header-bar.ui,src/ui/events-pane.ui,src/ui_app.hpp,src/ui_trigger.cpp(:597-601,:723-724),src/ui_layout.cpp(:380comment),src/ui_widgets.cpp(:58-60comment),src/ui_probe.cpp,src/ui_dock.cpp
Interfaces:
-
Consumes: Task 1's
build_events_dock,dock_rebuild_events,dock_mark_events,dock_events_set_sensitive;slideover_add_page,slideover_open. -
Produces: slide-over page
events(title "Events") whose content isevents_boxofevents-pane.ui;build_events_pane(App&)now returns that box (not a revealer);App::events_revealer,App::events_divider,App::events_pane_width,sync_events_divider, the headerevents_toggleand its handler are gone;events_cached/sync_events_availabilitykeep driving the dock block's sensitivity (and the page's: setevents_boxinsensitive with it). -
Step 1: The page.
events-pane.ui: drop theevents_panerevealer soevents_boxis the root (rename nothing else); keepevents_head,events_live,events_compare,events_accumulate,events_list.build_events_panestops creatingevents_revealer. Register the page next to thecursors/measureregistrations inui.cpp:slideover_add_page(*app, "events", "Events", app->events_box)(add theevents_boxmember if absent).record_event's refresh conditiongtk_revealer_get_child_revealedbecomes "theeventspage is open" (slideover_page(app) == "events"), because the dock refreshes independently; opening the page (the slide-over's open path or the Manage button) must refresh the list once so it is current -- put this in aslideover_set_on_open-style hook only if the slide-over already has one; otherwise refresh in the Manage button's handler and inrefresh_events_panecallers that run while closed (cheap; the list is small) -- state in the report which you chose. -
Step 2: Delete the pane.
main-window.ui: removeevents_divider;left_columnbecomes theside_viewcontent directly (keep every id inside it); fix the header comment (lines ~6, 23, 29).ui.cpp: removesync_events_divider, its tick call, theevents_dividercreation (~5201-5206), the null guard (~4998), the toggle handler (~4951-4959) and the toolbar-order probe string entry (~3010);header-bar.ui: removeevents_toggle; keep theudpscope-events-symbolicicon (used bypreferences.ui).ui_app.hpp: remove the members and declarations (events_divider,events_pane_width,events_revealer, the toggle pointer).sync_events_availabilityloses the toggle/revealer branches, keeps computingavailable, and drivesdock_events_set_sensitiveplus the page's sensitivity. Fix the comments atui_trigger.cpp:597-601,:723-724,ui_layout.cpp:380,ui_widgets.cpp:58-60. -
Step 3: Probes. Rewrite
events_probe_next(ui_probe.cpp:5590-5860) to open the page withslideover_open(*a, "events")instead of clicking the toggle, then drive selection/Compare/Accumulate exactly as before on the page's list; delete the divider/revealer/pane_wreads; rewritereport_events_availability(ui.cpp:5712-5724) as[events] available=<0|1> dock_sensitive=<0|1> page_sensitive=<0|1> page=<id>and theevents pane:dump (ui.cpp:6242-6255) asevents pane: compare= accumulate= list= nested=(no revealer). Any layout dump or toolbar probe naming the toggle is updated. The plot's width must no longer change when events exist: addplot_w_stable=<0|1>comparing the plot area's allocated width before and after the first latched event (it must read 1; the old pane squeezed it). -
Step 4: Run the events probe, the trigger probes, the layout/toolbar probes, the readouts probes, one E-1608 run;
gtk warnings: 0, noGtk-CRITICAL, in particular none fromevents_thumb,update_group_buttonsat teardown, or the removed revealer. Re-baseline.superpowers/sdd/ui-signals-baseline-udps.txtonly where this step explains a difference. -
Step 5: Commit (
git addthe modified files by name; messageui: the events list is a slide-over page; the left pane, its divider and the header toggle are gone).
Task 3: Tour, context help, docs/claude, README
Files:
-
Modify:
src/ui_tour.cpp,src/ui_context_help.cpp(:102-110),README.md(97, 841-850, 883, 1032, 1194, 3025-3027),docs/claude/{architecture,verification,probe-readings,invariants-gtk-and-chrome,invariants-trigger-and-pinning,invariants-plot-and-layout}.md -
Step 1: Tour and help. The
docktour stop text mentions the Events block (what it lists, that one click replays, that Manage opens the list). The context-help hintsevents(anchored to&App::events_head, now on a page) andevents_recordfollow how thecursors/measureshints were handled in step 3 (anchor on the page heading, shown while the page is open; reword "the events pane"). Run the help-figures and tour probes. -
Step 2: docs/claude.
architecture.md(chrome section: the dock now has readouts and events; pageevents;eventsno longer "reserved", ~line 105),invariants-gtk-and-chrome.md(paned/revealer traps at ~212-228 lose the events divider; teardown ~281 andevents_thumb~297, thumb cache ~309 now say "the page's list"),invariants-trigger-and-pinning.md(~441-453: the availability rule drives the dock block's sensitivity; the spec's "outside trigger mode" deviation and why),invariants-plot-and-layout.md(no events divider; the plot is never squeezed by events),verification.md(~724 and the events probe wording),probe-readings.md(dock events:,dock replay:, the new[events]andevents pane:lines and what each must read; the oldpane_w=240rows and the[events]table near 1660-1675 rewritten). -
Step 3: README. Layout/dock description, the header (no Events toggle), the events page, the single-click replay in the dock, deviations: availability rule instead of "outside trigger mode"; the dock list has no thumbnails (the page does); delete the false "remembers the width" claim; update the pending-removal note at ~3025-3027 (it is done).
-
Step 4: Run the tour and help-figure probes, one UDPS and one E-1608 run;
gtk warnings: 0. -
Step 5: Commit (code:
ui: tour and help describe the events block; docs:docs: events in the dock and on its page).
Task 4: The Mallard manual and USER-GUIDE sweep for the whole redesign
Files:
-
Modify:
help/C/org.udpscope.app/*.page(trigger.page169-196 "Events pane on the left", the history button, "Drag the pane's edge";overview.pageincl. its layout description and any figure caption;signals.page,math.page,sessions.page, plus any page that mentions the side panel, the sidebar tabs ("Measure", "Signals" tab), the header's sidebar toggle, or the Events toggle),docs/USER-GUIDE.md,README.mdleftovers -
Step 1: Find every stale mention.
grep -rniE "side panel|sidebar|side pane|tab switcher|\bMeasure tab|Signals tab|events pane|history button|Events toggle|drag.*(edge|divider)" help/C docs/USER-GUIDE.md README.md src/ui_*.cppand read each hit in context; also check figures the manual embeds (help/C/org.udpscope.app/figures/and how they are generated --UDPSCOPE_*help-figures probe inverification.md). List every hit and its disposition in the report. -
Step 2: Rewrite each page to the real UI: header bar (time window, Trigger, Pause, Cursors, Dock toggle, menu), the plot, the badge bar (signal badges,
+, N signals, Trigger, Source), the slide-over pages (trigger, signals, signal:, cursors, measure, events, source), the dock (Readouts: cursors, measurements; Events). Read the code ordocs/claude/architecture.mdto be accurate; do not invent shortcuts (mtoggles Measurements,gthe signals page -- checksrc/shortcuts.cpp). Regenerate figures only if the repository already has a script that does so headlessly; otherwise say which figures remain stale. -
Step 3: Validate the Mallard pages if
yelp-check/xmllintis available (xmllint --noout help/C/org.udpscope.app/*.page); build withcmake --build build -j(the help may be compiled/installed by a target). -
Step 4: Commit (
docs: the manual and the user guide describe the badge bar, the dock and the slide-over pages).