docs: plan for the compact readouts dock (step 3 of the redesign)
This commit is contained in:
1 parent
1627abcd4a
commit
a7a1609d48
1 file changed
+107
@@ -0,0 +1,107 @@
|
|||||||
|
# Compact readouts dock -- step 3 of the badge-bar redesign 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:** The dock shows the cursors and the measurements as compact, read-only readouts (about 280 px wide, always on screen); the full cursors panel and the full measurements panel (mode, reset, add, statistics, edit) become the `cursors` and `measure` slide-over pages, reached by an "Edit" text button in the dock, by the `m` key, and by clicking a measurement's dock line.
|
||||||
|
|
||||||
|
**Architecture:** A new unit `src/ui_dock.cpp` (spec: `ReadoutsDock`) builds the dock's "Readouts" section: a cursors block (a small grid mirroring `App::cursors_lines`) and a measurements block (one line per `App::measures`). It owns no formatting: `update_cursors_pane()` and `update_measures()` already compute the strings, and they now write them to the page's widget *and* to the dock's, from the one string. `cursors_panel` and the measures panel are *reparented* (as the trigger form and the signal list were) into slide-over pages with ids `cursors` and `measure`. The dock's old contents (the two full panels) leave it.
|
||||||
|
|
||||||
|
**Tech Stack:** C++20, GTK4, libadwaita >= 1.8, CMake. No unit-test suite: verification is the app's own headless self-check (`xvfb-run`, mock sources) -- extend `src/ui_probe.cpp` probes, never a separate harness.
|
||||||
|
|
||||||
|
Spec: `docs/superpowers/specs/2026-11-02-ui-badges-dock-design.md` ("Right dock", "Slide-over panel", rollout step 3). Earlier plans: `2026-11-02-ui-badges-dock-shell.md` (step 1), `2026-11-03-ui-badges-signals.md` (step 2). Branch: `feature/ui-badges-dock` (continue on it).
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
Copied from the spec and the repository's rules; every task includes them.
|
||||||
|
|
||||||
|
- **Right dock (spec, verbatim):** "Fixed width, about 280 px; always on screen, shown but insensitive until the stream has signals ('disable, do not hide'). It stays an `AdwOverlaySplitView` sidebar ... **Readouts** (top): the active cursor values and measurement values, read-only, one compact line per value, through the one formatter both panes share (`invariants-readouts-and-cursors.md`). An 'Edit' affordance opens the slide-over's `measure` or `cursors` page; clicking a measurement row opens that measurement's editor."
|
||||||
|
- **One formatter:** a readout quotes a stored sample and never a value between two (`docs/claude/invariants-readouts-and-cursors.md`); the dock and the page show the *same string*, set from the same variable in the same call -- no second `format_measure()` call site for the same value.
|
||||||
|
- **Layout must not depend on content** (`invariants-plot-and-layout.md`): the dock has a fixed width (280 px: `min-sidebar-width` = `max-sidebar-width` in `main-window.ui`), every dock label is ellipsized with a fixed `width-chars`/`max-width-chars`, so a long unit or value never widens the dock; the badge bar keeps its fixed height; the slide-over overlays and never resizes the plot.
|
||||||
|
- **A `GtkOverlay` child is picked wherever its allocation is**: this step adds no overlay child.
|
||||||
|
- Page ids: `trigger`, `signal:<name>`, `signals`, `measure`, `cursors`, `events`, `source-<id>`. This step adds `measure` and `cursors`. One slide-over page at a time; each page has its own scroller (`slideover_add_page` provides it); the open page is not saved.
|
||||||
|
- "Disable, do not hide": with the cursors off, the dock's cursor block stays on screen with `--` cells (the existing blanking in `update_cursors_pane`), insensitive; an empty measurement list shows one dim line, never nothing.
|
||||||
|
- Text buttons only for new controls ("Edit", not an icon). Existing icon buttons that move into the pages are not changed in this step.
|
||||||
|
- The widget-lifetime trap (`invariants-gtk-and-chrome.md`): a click handler that calls `rebuild_measures_pane()` / `rebuild_cursors_pane()` destroys widgets it may have been emitted from; hold the emitter with `outlive_the_rebuild(widget)` first. The dock's lines are rebuilt by those two functions, so a click on a dock line must not be handled by a widget that the rebuild destroys synchronously (open the dialog from the handler, rebuild later).
|
||||||
|
- Every new source file carries the SPDX MIT header (`// SPDX-License-Identifier: MIT` / `// SPDX-FileCopyrightText: 2026 Martino Ferrari`).
|
||||||
|
- Baselines: a clean build emits exactly **one** warning (the icons' `-Woverlength-strings`), an incremental build **zero**; the dump's `gtk warnings:` reads `0`; no `Gtk-CRITICAL`/`Gtk-WARNING`.
|
||||||
|
- Verification runs are short and serial; kill every mock you start (`tools/mock_udpstreamer.py` port 8114, `tools/mock_e1608.py` port 54211); seed a tour-seen config so the first-run tour does not close slide-over pages (see `.superpowers/sdd/task-5-report.md` for commands). Do not re-run to chase cosmetics.
|
||||||
|
- Do not push, merge or amend. Commit per task on `feature/ui-badges-dock`.
|
||||||
|
|
||||||
|
## Deliberate step-3 deviations from the spec (state them in the final report)
|
||||||
|
|
||||||
|
- **The measurement editor stays a dialog window** (`open_measure_dialog`): the `measure` page holds the list, Add, the Statistics switch and each row's edit button, which open that dialog. Moving the dialog's form into the slide-over is not needed for the readouts and is not done here.
|
||||||
|
- The `measure` and `cursors` pages hold the existing panels unchanged (heading, mode dropdown, reset, switch, grid / add, statistics, rows), so their content is not re-designed.
|
||||||
|
- Dock cursor lines carry the same cells as the page's grid (`who`, A, B, delta), at a smaller size; they are not reduced to the time line only, because the modes that read per-row values would then have no readout on screen.
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
| File | Create/Modify | Responsibility |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `src/ui_dock.cpp` | Create | The dock's Readouts section: build, rebuild of the cursor lines and the measurement lines, the Edit buttons, the click-to-edit on a measurement line, the `[dock]` probe line. |
|
||||||
|
| `CMakeLists.txt` | Modify | Add `src/ui_dock.cpp` next to `src/ui_badges.cpp`. |
|
||||||
|
| `src/ui_app.hpp` | Modify | `App::DockCursorLine`, `App::dock_cursor_lines`, `App::dock_measure_labels`, `App::dock_readouts` widgets; declarations. |
|
||||||
|
| `src/ui_cursors.cpp` | Modify | `update_cursors_pane()` writes each cell's string to the page and to the dock; the rebuild calls the dock's rebuild. |
|
||||||
|
| `src/ui_measure.cpp` | Modify | `update_measures()` writes each reading to the page's label and to the dock's; `rebuild_measures_pane()` rebuilds the dock lines. |
|
||||||
|
| `src/ui.cpp` | Modify | Pages `cursors` / `measure` registered; panels removed from `dock_page`; `dock_show_section()` removed; `ShowMeasure` opens the page; dock assembly. |
|
||||||
|
| `src/ui/main-window.ui` | Modify | Dock width 280 (`min/max-sidebar-width`), `side_column` `width-request` 260, comments. |
|
||||||
|
| `src/ui_tour.cpp`, `src/ui_context_help.cpp` | Modify | Steps / hints that pointed at the cursors and measurements panels in the dock now open the page (as the signals step does). |
|
||||||
|
| `src/ui_probe.cpp` | Modify | Probes that walked, measured or scrolled the panels while they lived in the dock; the `dock:` readings. |
|
||||||
|
| `README.md`, `docs/claude/*.md` | Modify | Task 3. (`USER-GUIDE.md` and the Mallard manual are spec step 4.) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: The compact readouts in the dock (panels still in the dock)
|
||||||
|
|
||||||
|
The Readouts section is added at the top of the dock, above the existing full panels, so the app stays whole: nothing is removed yet. Both read from the same strings.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/ui_dock.cpp`
|
||||||
|
- Modify: `CMakeLists.txt`, `src/ui_app.hpp`, `src/ui_cursors.cpp`, `src/ui_measure.cpp`, `src/ui.cpp` (dock assembly, ~line 5161), `src/ui_probe.cpp` (a `[dock]` line in an existing probe)
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces (declared in `ui_app.hpp`, defined in `ui_dock.cpp`):
|
||||||
|
- `GtkWidget* build_readouts_dock(App& app);` -- the Readouts box (a vertical `GtkBox`: a "Readouts" heading; a cursors block = a row with the "Cursors" label and an "Edit" `GtkButton` (text), then a `GtkGrid` of dock cursor lines; a measurements block = a row with the "Measurements" label and an "Edit" button, then a vertical box of lines). Stores `app.dock_cursor_grid`, `app.dock_measures_box`, `app.dock_cursors_edit`, `app.dock_measures_edit`.
|
||||||
|
- `void dock_rebuild_cursors(App& app);` -- called at the end of `rebuild_cursors_pane()`: rebuilds `app.dock_cursor_lines` (one `DockCursorLine {GtkWidget *label,*a,*b,*d;}` per `app.cursors_lines`, same order) in `dock_cursor_grid`. The first row is the same header `cursors_header()` writes (`who`/A/B/delta), small.
|
||||||
|
- `void dock_rebuild_measures(App& app);` -- called from `rebuild_measures_pane()` after the page's rows: one line per `app.measures` (signal chip, type name ellipsized, value label), pushing the value label to `app.dock_measure_labels` (same index as `app.measure_labels`). With no measurements: one dim line "no measurements".
|
||||||
|
- `void dock_cursor_cell(App& app, std::size_t line, const std::string& a, const std::string& b, const std::string& d);` and `void dock_measure_value(App& app, std::size_t i, const std::string& text);` -- set the dock's labels; no-ops when the index is out of range (mid-rebuild).
|
||||||
|
- Consumes: `App::cursors_lines`, `App::measures`, `App::measure_labels`, `measure_color()`, `chip_markup()`, `signal_color_index()`, `open_measure_dialog(App&, int)`, `slideover_open(App&, const std::string&)` (page ids used in Task 2; in this task the Edit buttons call it and the page may not exist yet -- `slideover_open` of an unknown id is a no-op; Task 2 registers the pages).
|
||||||
|
|
||||||
|
- [ ] **Step 1: Dock widgets and rebuild functions.** Create `ui_dock.cpp` with the functions above. Fixed sizes: dock label `width-chars`/`max-width-chars` of 9 for value cells, ellipsize end, CSS class `caption` and `numeric` (check `ui_widgets.cpp` for the existing CSS before adding any). The whole Readouts box is the first child of `app.dock_page` (insert before the cursors panel with `gtk_box_prepend`).
|
||||||
|
- [ ] **Step 2: One string, two widgets.** In `update_cursors_pane()` (`ui_cursors.cpp`) replace the `put(line.a, va)` etc. by a helper that sets the page cell and calls `dock_cursor_cell(app, index, va, vb, vd)`; do the same for the blanking branch (`"--"`). In `update_measures()` (`ui_measure.cpp`, the two `gtk_label_set_text(label, ...)` sites that also write `app.measure_text[i]`) call `dock_measure_value(app, i, text)` from the same variable. Never format twice.
|
||||||
|
- [ ] **Step 3: Rebuild hooks.** `rebuild_cursors_pane()` (ui.cpp ~1564) ends with `dock_rebuild_cursors(app)`; `rebuild_measures_pane()` calls `dock_rebuild_measures(app)` before its final `update_measures(...)`. Both are no-ops until `build_readouts_dock` ran (null guards).
|
||||||
|
- [ ] **Step 4: Assemble.** In `ui.cpp` where the dock is assembled (after `gtk_box_append(dock_page, measures_panel)`), prepend `build_readouts_dock(*app)` and add its separator. The Edit buttons call `slideover_open(app, "cursors")` / `slideover_open(app, "measure")`; clicking a measurement line (a `GtkGestureClick` on the line box, not on the value label which is selectable) calls `open_measure_dialog(app, i)` with the index stored in the widget data, as `measures_row`'s edit button does.
|
||||||
|
- [ ] **Step 5: Probe.** Add to the existing cursors probe (find it with `grep -n "cursors" src/ui_probe.cpp`) and measures probe a reading proving the dock agrees with the page: print `dock cursors: lines=<n> page_lines=<n> same_text=<0|1>` and `dock measures: lines=<n> page_rows=<n> same_text=<0|1>` where `same_text` compares every dock label's text to the page label's text. Also `dock_w=<allocated width of the dock readouts box>` and assert it does not exceed the sidebar width. Run with 3 measurements and the cursors on in `Both` mode (cursors mode set via `rows->set_cursor_mode`).
|
||||||
|
- [ ] **Step 6: Build, run the probes against `tools/mock_udpstreamer.py`, verify `same_text=1`, `gtk warnings: 0`.**
|
||||||
|
- [ ] **Step 7: Commit** (`git add src/ui_dock.cpp CMakeLists.txt src/ui_app.hpp src/ui_cursors.cpp src/ui_measure.cpp src/ui.cpp src/ui_probe.cpp`; message `ui: compact cursor and measurement readouts at the top of the dock`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: The `cursors` and `measure` pages; the dock is only readouts
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ui.cpp`, `src/ui/main-window.ui`, `src/ui_app.hpp`, `src/ui_probe.cpp`, `src/ui_dock.cpp`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: Task 1's `build_readouts_dock`, `slideover_add_page(App&, id, title, widget)`, `slideover_toggle(App&, id)`.
|
||||||
|
- Produces: slide-over pages `cursors` (title "Cursors", content `app.cursors_panel`) and `measure` (title "Measurements", content the measures panel); `AppAction::ShowMeasure` toggles the `measure` page; `dock_show_section()` and its declaration/uses are deleted.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Register the pages** next to the `trigger` and `signals` registrations (`ui.cpp` ~5156): `slideover_add_page(*app, "cursors", "Cursors", app->cursors_panel)` and `slideover_add_page(*app, "measure", "Measurements", measures_panel)`. Remove both `gtk_box_append(dock_page, ...)` of the panels and the separator between them; the dock keeps only `build_readouts_dock` (and, later, step 4's events).
|
||||||
|
- [ ] **Step 2: Keys and actions.** `AppAction::ShowMeasure` -> `slideover_toggle(app, "measure")`. Delete `dock_show_section()`. Make sure the tour/help code does not call it (`grep -n dock_show_section src`).
|
||||||
|
- [ ] **Step 3: Dock width.** `main-window.ui`: `min-sidebar-width`/`max-sidebar-width` 280, `side_column` `width-request` 260; update its comments.
|
||||||
|
- [ ] **Step 4: Badge/header state.** The page ids join the "open" bookkeeping: `slideover_page(app)` reports them; nothing in the badge bar marks them (no badge). The header's Cursors toggle still mirrors the `cursors_switch` inside the page (both exist).
|
||||||
|
- [ ] **Step 5: Probes.** Fix every probe that read the panels' allocation, sensitivity or scroll position while they sat in the dock (`dock_section_probe`, the `dock:` reading in the layout dump, the `cursors=... in the toolbar, dock=..., reset=... in the cursors pane` line, any that walks `dock_page`): they open the page first or read the widget tree without allocation. Add `[readouts] pages cursors=<0|1> measure=<0|1> dock_children=<n> dock_w=<n>` (pages registered; dock holds only the readouts box; width 280). Re-baseline `.superpowers/sdd/ui-signals-baseline-udps.txt` only where this step explains the difference.
|
||||||
|
- [ ] **Step 6: Run** the UDPS and E-1608 probe runs serially; `gtk warnings: 0`; Esc closes the pages; the measure Add and the measurement dialog still work from the page (the existing measure probes).
|
||||||
|
- [ ] **Step 7: Commit** (`ui: the cursors and measurements panels are slide-over pages; the dock is the readouts`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Tour, context help, docs
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/ui_tour.cpp`, `src/ui_context_help.cpp`, `README.md`, `docs/claude/{architecture,verification,probe-readings,invariants-plot-and-layout,invariants-readouts-and-cursors,invariants-gtk-and-chrome}.md`, `help/C/*.page` mentions of the cursors / measurements panels in the dock or the side panel (the full Mallard sweep and `USER-GUIDE.md` are step 4)
|
||||||
|
|
||||||
|
- [ ] **Step 1: Tour.** Steps pointing at `cursors_panel`/`cursors_head`/`measures_head`/`side_box` now target the dock's readouts, or open the `cursors` / `measure` page through the existing "tour opens a page" mechanism (`invariants-gtk-and-chrome.md`), as the signals step does. Rewrite the step text: readouts in the dock, "Edit" opens the page.
|
||||||
|
- [ ] **Step 2: Context help.** Hints attached to `cursors_head` / `measures_head` live in the pages: show only while the page is open, or re-attach to the dock block headings -- whichever the existing layer supports without a second code path (follow how `signals_head` was handled). Run the help-figures probe and the tour probe (`UDPSCOPE_PROBE_*` in `verification.md`) and keep their lines sane.
|
||||||
|
- [ ] **Step 3: Docs.** README (layout, keys, the dock, readouts, deviations listed above), `architecture.md` (chrome section), `verification.md` and `probe-readings.md` (new `[dock]`/`[readouts]` lines, changed `dock:` readings), `invariants-readouts-and-cursors.md` (the dock and the page share one string: rule and why), `invariants-plot-and-layout.md` (dock width 280, ellipsized cells).
|
||||||
|
- [ ] **Step 4: Run** the tour/help-figure probes and one E-1608 run; confirm baselines (build 0 warnings, `gtk warnings: 0`).
|
||||||
|
- [ ] **Step 5: Commit** (`docs: the readouts dock, the cursors and measurement pages`).
|
||||||
Reference in new issue
Block a user