From 84a5ec21db0832e986eae5433a7b826acc8f21ae Mon Sep 17 00:00:00 2001 From: Hannes Suhr Date: Tue, 2 Jun 2026 18:07:59 +0200 Subject: [PATCH 1/2] Rename dashboard toolbar 'Reset' button to 'Redraw' (260602-p2t) The top-bar button labelled 'Reset' actually forces a full re-render of every widget on the active page (onReset -> DashboardEngine.rerenderWidgets), not a reset. Rename the user-facing label to 'Redraw' so its purpose is clear; the tooltip already described the re-render behaviour. Internal handle/handler (hResetBtn/onReset) keep their historical names. The time-panel 'Reset' button (resetTimeRange) is a genuine reset and is left unchanged. Updates test_dashboard_toolbar_buttons label assertion + button-names list. Co-Authored-By: Claude Opus 4.8 (1M context) --- libs/Dashboard/DashboardToolbar.m | 8 +++++--- tests/test_dashboard_toolbar_buttons.m | 12 ++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/libs/Dashboard/DashboardToolbar.m b/libs/Dashboard/DashboardToolbar.m index 687f76dd..ea450285 100644 --- a/libs/Dashboard/DashboardToolbar.m +++ b/libs/Dashboard/DashboardToolbar.m @@ -151,16 +151,18 @@ 'TooltipString', 'Reset all widgets to global time range', ... 'Callback', @(~,~) obj.Engine.resetGlobalTime()); - % Reset button — manual recovery; forces full re-render of every + % Redraw button — manual recovery; forces full re-render of every % widget on the active page. Placed next to Sync because both % are "fix the dashboard" actions, but their roles differ: - % Sync resets the time range; Reset re-renders widget panels. + % Sync resets the time range; Redraw re-renders widget panels. + % (Internal handle/handler keep the historical hResetBtn/onReset + % names — only the user-facing label was renamed to "Redraw".) rightEdge = rightEdge - btnW - 0.005; obj.hResetBtn = uicontrol('Parent', obj.hPanel, ... 'Style', 'pushbutton', ... 'Units', 'normalized', ... 'Position', [rightEdge btnY btnW btnH], ... - 'String', 'Reset', ... + 'String', 'Redraw', ... 'TooltipString', ['Force re-render of all widgets on the active page ' ... '(recovery action when widgets get stuck)'], ... 'Callback', @(~,~) obj.onReset()); diff --git a/tests/test_dashboard_toolbar_buttons.m b/tests/test_dashboard_toolbar_buttons.m index 642fccc3..0579f8f0 100644 --- a/tests/test_dashboard_toolbar_buttons.m +++ b/tests/test_dashboard_toolbar_buttons.m @@ -53,7 +53,7 @@ function test_dashboard_toolbar_buttons() d.Toolbar.hExportBtn, d.Toolbar.hSyncBtn, ... d.Toolbar.hResetBtn, ... d.Toolbar.hInfoBtn}; - names = {'Live', 'Config', 'Image', 'Export', 'Sync', 'Reset', 'Info'}; + names = {'Live', 'Config', 'Image', 'Export', 'Sync', 'Redraw', 'Info'}; for i = 1:numel(handles) tip = get(handles{i}, 'TooltipString'); assert(~isempty(tip), ... @@ -100,18 +100,18 @@ function test_dashboard_toolbar_buttons() nFailed = nFailed + 1; end - % Reset button is created and labelled "Reset" after render + % Redraw button is created and labelled "Redraw" after render try d = DashboardEngine('ResetButtonExistsTest'); d.addWidget('number', 'Title', 'T', 'Position', [1 1 6 2], 'StaticValue', 1); d.render(); set(d.hFigure, 'Visible', 'off'); assert(~isempty(d.Toolbar.hResetBtn), ... - 'reset button should exist after render'); + 'redraw button should exist after render'); assert(ishandle(d.Toolbar.hResetBtn), ... - 'reset button should be a valid handle'); - assert(strcmp(get(d.Toolbar.hResetBtn, 'String'), 'Reset'), ... - 'reset button label should be "Reset"'); + 'redraw button should be a valid handle'); + assert(strcmp(get(d.Toolbar.hResetBtn, 'String'), 'Redraw'), ... + 'redraw button label should be "Redraw"'); close(d.hFigure); nPassed = nPassed + 1; catch err From d5122af68a806e4400d864811f216e613bf70528 Mon Sep 17 00:00:00 2001 From: Hannes Suhr Date: Tue, 2 Jun 2026 18:08:59 +0200 Subject: [PATCH 2/2] docs(quick-260602-p2t): Rename dashboard toolbar Reset button to Redraw Quick-task artifacts (PLAN/SUMMARY) + STATE.md tracking row for the toolbar 'Reset' -> 'Redraw' label rename. Co-Authored-By: Claude Opus 4.8 (1M context) --- .planning/STATE.md | 3 +- .../260602-p2t-PLAN.md | 57 +++++++++++++++++++ .../260602-p2t-SUMMARY.md | 51 +++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 .planning/quick/260602-p2t-rename-dashboard-toolbar-reset-button-to/260602-p2t-PLAN.md create mode 100644 .planning/quick/260602-p2t-rename-dashboard-toolbar-reset-button-to/260602-p2t-SUMMARY.md diff --git a/.planning/STATE.md b/.planning/STATE.md index de0fdb67..a5c07bdb 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -27,7 +27,7 @@ Phase: 1040 Plan: Not started Milestone: v3.0 FastSense Companion — SHIPPED 2026-04-30; v4.0 Multi-User LAN Concurrency — shipping via PR #152 (parallel branch); v1.0 perf line tracks phase 1028 — now COMPLETE via PR #114. Status: Phase complete — ready for verification -Last activity: 2026-06-02 - Completed quick task 260602-mri: crosshair-link toggle on the FastSense widget bar (mirrors the hover crosshair across active-page FastSense widgets) +Last activity: 2026-06-02 - Completed quick task 260602-p2t: renamed dashboard toolbar "Reset" button to "Redraw" (the button forces a full widget re-render, not a reset) ### Note on parallel v4.0 work (main branch state) @@ -97,6 +97,7 @@ Other main PRs (#138, #139, #141, #144, #145, #146) auto-merged without conflict | 260529-rxf | Real per-event email alerts for background monitoring — new `EmailTransport` (SMTP auth/STARTTLS:587 default, also `none`/`ssl`; Octave `exist('sendmail','file')` log-and-skip guard; pure static `buildMailProps` CI seam) that `NotificationService` now delegates to via an injectable `Transport` property; per-(sensor,threshold) email cooldown (default 5 min, 0 disables; dry-run honors it too) with public `SuppressedCount`; `LiveEventPipeline.processMonitorTag_`/`runCycle` now forward real per-event `sensorData` (X/Y/thresholdValue/thresholdDirection from the live tick) so `IncludeSnapshot` rules attach PNGs in live mode. MATLAB-only per user decision. **Backward-compat preserved**: pipeline still defaults to `NotificationService('DryRun', true)` and all prior tests stay green. Verified locally (R2025a, live MATLAB MCP): `test_email_transport` 5/5, `test_notification_service` 10/10 (7 original + 3 new: delegation / cooldown-suppress / cooldown-expiry-via-Hidden-DI-seam), `test_live_event_pipeline_tag` 3/3, plus class suites `TestEmailTransport` 5/5, `TestNotificationService` 7/7, `TestLiveEventPipelineTag` 3/3. MISS_HIT (`mh_style`+`mh_lint`) clean on all 8 files; MATLAB Code Analyzer clean on the 3 new/edited libs. Real SMTP delivery is the single manual step via `examples/05-events/smoke_email_send.m` (FASTSENSE_SMTP_* env vars, STARTTLS:587), out of CI. | 2026-05-29 | 203da7a, 2ac6887, 341bab2, cef1fc5 | Verified | [260529-rxf-real-per-event-email-alerts-for-backgrou](./quick/260529-rxf-real-per-event-email-alerts-for-backgrou/) | | 260529-fnt | Add `FunctionTransport` adapter (`libs/EventDetection/FunctionTransport.m`) — wraps a user-supplied function handle as a `NotificationService` `Transport` so an existing site/company MATLAB mailer can be reused for alerts with **no SMTP config** (no server/port/creds, no Gmail App Password). Drop-in duck-typed `send(recipients,subject,body,attachments)` (same as EmailTransport), normalizes recipients to a flat cellstr, defaults attachments to `{}`, Octave-safe (only calls user code). Purely additive — EmailTransport/NotificationService behavior unchanged. Built via **/gsd:fast** (inline, no subagents). Verified (R2025a): `test_function_transport` 5/5 (forwarding / recipients-normalization / attachments-default / invalid-handle / NotificationService integration), `test_notification_service` 10/10 (no regression); MISS_HIT + Code Analyzer clean on all touched files. `example_live_pipeline.m` gains a commented FunctionTransport option. Follow-up to 260529-rxf after the user opted to reuse their company mailer instead of configuring Gmail SMTP. | 2026-05-29 | 706e9d5 | Verified | (inline) | | 260602-mri | Add a crosshair-link toggle ('X') to the FastSenseWidget grey WidgetButtonBar that mirrors the hover crosshair across all FastSense widgets on the **active dashboard page**. New `CrosshairLinked` public property + `setCrosshairLink(tf)` on `FastSenseWidget` (default OFF; `toStruct` omits when false so legacy serialized dashboards are byte-identical; `fromStruct` restores). `HoverCrosshair` gains `setBroadcastFcn`/`onMoveExternal`/`onLeaveExternal` + a **deterministic `IsMirrored_` suppress flag** so a mirrored peer's same-dispatch self-leave is a no-op — **ZERO new figure-WBM closures**, respecting the 260512-egv/eu2 chained-WBM constraint; the broadcast rides on existing per-crosshair `onMove`/`onLeave` and each peer computes its OWN per-series datatip at the shared data-x via `computeYAtX_` (raw-x, no Y transmitted). `DashboardEngine` derives the active-page link set on demand (`collectLinkedCrosshairs_`, flattens GroupWidget children via `flattenWidgetsForPreview_`) through `broadcastCrosshairX_`/`broadcastCrosshairLeave_`/`onCrosshairLinkToggle`, and re-primes broadcast hooks via `rewireCrosshairLinks_` after `rerenderWidgets`/`switchPage`/`detachWidget`. Duck-typed 'X' button injected in `DashboardLayout.realizeWidget` via `ismethod(widget,'setCrosshairLink')` (leftmost chrome button, left of V/A), re-anchored by `reflowChrome_`, protected in `DashboardWidget.clearPanelControls`. **Orchestrator verification (R2025a, live MCP)** replaced the executor's flaky `tic`-window suppress (`SuppressLeaveUntil_`/`SuppressWindow_` — passed in isolation, failed in-suite) with the deterministic `IsMirrored_` flag, and fixed a **latent unbounded leave ping-pong** (`onLeaveExternal` now hides directly via a private `hideGraphics_` instead of re-entering broadcasting `onLeave`). Tests: new `test_fastsense_crosshair_link` **11/11**; regressions green (`test_hover_crosshair` 11/11, `test_fastsense_widget_ylimit_modes` 11/11, `test_time_range_selector_reinstall_after_rerender` pass, `test_dashboard_time_sync_all_pages` 5/5); MISS_HIT clean on all 6 files; Code Analyzer no new findings; live UI smoke on a rendered 2-widget dashboard (button renders + toggles, hovering one linked widget mirrors crosshair+datatip on the other, leave hides both, unlink stops mirroring). DetachedMirror crosshair-link parity OUT OF SCOPE (detached widgets use a figure-level `FastSenseToolbar`, not a WidgetButtonBar — matches 260513-sfp). | 2026-06-02 | a495cbc9, 635632e2, 8950abd5, 485154b7 | Verified | [260602-mri-add-crosshair-link-toggle-to-fastsense-w](./quick/260602-mri-add-crosshair-link-toggle-to-fastsense-w/) | +| 260602-p2t | Rename the dashboard top-bar (toolbar) button from "Reset" to "Redraw". The button forces a full re-render of every widget on the active page (`onReset` → `DashboardEngine.rerenderWidgets`), so "Reset" was misleading — the label now matches the behaviour (and the existing tooltip, "Force re-render of all widgets…"). Label-only change in `libs/Dashboard/DashboardToolbar.m`; internal handle/handler `hResetBtn`/`onReset` kept (not user-facing). The **time-panel** "Reset" button (`DashboardEngine.hTimeResetBtn` → `resetTimeRange`) is a genuine time-window reset and is NOT in the top bar — left unchanged. Synced `tests/test_dashboard_toolbar_buttons.m` (button-names list + label assertion `'Reset'`→`'Redraw'`). Verified R2025a (live MCP): `test_dashboard_toolbar_buttons` **7/7** (label, tooltip-mentions-widget, onReset re-render); MATLAB Code Analyzer no new findings on both files. Executed inline (single-label rename + test sync) with GSD bookkeeping — no separate planner/executor subagents. | 2026-06-02 | 84a5ec2 | Verified | [260602-p2t-rename-dashboard-toolbar-reset-button-to](./quick/260602-p2t-rename-dashboard-toolbar-reset-button-to/) | ## Progress Bar diff --git a/.planning/quick/260602-p2t-rename-dashboard-toolbar-reset-button-to/260602-p2t-PLAN.md b/.planning/quick/260602-p2t-rename-dashboard-toolbar-reset-button-to/260602-p2t-PLAN.md new file mode 100644 index 00000000..b58271bc --- /dev/null +++ b/.planning/quick/260602-p2t-rename-dashboard-toolbar-reset-button-to/260602-p2t-PLAN.md @@ -0,0 +1,57 @@ +# Quick Task 260602-p2t: Rename dashboard toolbar "Reset" button to "Redraw" + +**Created:** 2026-06-02 +**Mode:** quick (inline execution) + +## Task Boundary + +The dashboard top bar (toolbar) has a button labelled "Reset" whose callback +(`onReset` → `DashboardEngine.rerenderWidgets`) deletes and re-creates every +widget panel on the active page — i.e. it forces a full re-render. The label +"Reset" is misleading; rename the user-facing label to "Redraw" so the button's +purpose is clear. + +Out of scope: the **time-panel** "Reset" button (`DashboardEngine.hTimeResetBtn` +→ `resetTimeRange`) is a genuine time-window reset and is NOT in the top bar — +leave it unchanged. + +## Approach Note + +This is a single-string user-facing rename with a matching test assertion. Given +the change was fully scoped during investigation, it was executed inline (no +separate planner/executor subagents) while preserving GSD guarantees: scoped +artifacts, STATE.md tracking, and an atomic commit. + +## Tasks + +### Task 1 — Rename the toolbar button label + +- **Files:** `libs/Dashboard/DashboardToolbar.m` +- **Action:** Change the toolbar push-button `'String'` from `'Reset'` to + `'Redraw'` (~line 163). Update the adjacent explanatory comment to use the new + label and note that the internal handle/handler (`hResetBtn`/`onReset`) keep + their historical names. Leave the tooltip ("Force re-render of all widgets…") + unchanged — it already describes the redraw behaviour. +- **Verify:** Static analysis clean (no new issues); button label reads + "Redraw" after render. +- **Done:** Toolbar shows "Redraw"; internal names and behaviour untouched. + +### Task 2 — Sync the toolbar-button test + +- **Files:** `tests/test_dashboard_toolbar_buttons.m` +- **Action:** Update the button-names list (`'Reset'` → `'Redraw'`) and the + label assertion (`strcmp(..., 'Reset')` → `'Redraw'`) plus its comment/message. +- **Verify:** `test_dashboard_toolbar_buttons` passes (7/7). +- **Done:** Test asserts the label is "Redraw" and is green. + +## must_haves + +- truths: + - The toolbar button that triggers a full widget re-render is labelled "Redraw". + - The time-panel "Reset" button (`resetTimeRange`) is unchanged. + - Internal handle `hResetBtn` and handler `onReset` are unchanged. +- artifacts: + - `libs/Dashboard/DashboardToolbar.m` (label = "Redraw") + - `tests/test_dashboard_toolbar_buttons.m` (asserts "Redraw") +- key_links: + - `libs/Dashboard/DashboardToolbar.m` onReset → DashboardEngine.rerenderWidgets diff --git a/.planning/quick/260602-p2t-rename-dashboard-toolbar-reset-button-to/260602-p2t-SUMMARY.md b/.planning/quick/260602-p2t-rename-dashboard-toolbar-reset-button-to/260602-p2t-SUMMARY.md new file mode 100644 index 00000000..6d6a6276 --- /dev/null +++ b/.planning/quick/260602-p2t-rename-dashboard-toolbar-reset-button-to/260602-p2t-SUMMARY.md @@ -0,0 +1,51 @@ +# Quick Task 260602-p2t: Summary + +**Completed:** 2026-06-02 +**Status:** Done — verified (7/7 tests pass) + +## What changed + +Renamed the dashboard top-bar (toolbar) button from **"Reset" → "Redraw"** so the +label matches what the button actually does: force a full re-render of every +widget on the active page (a manual recovery action). + +### Files + +- **`libs/Dashboard/DashboardToolbar.m`** + - Button `'String'`: `'Reset'` → `'Redraw'` (the toolbar push-button wired to + `onReset` → `DashboardEngine.rerenderWidgets`). + - Updated the adjacent comment to use the new label and to note that the + internal handle/handler (`hResetBtn` / `onReset`) intentionally keep their + historical names — only the user-facing label changed. + - Tooltip left unchanged ("Force re-render of all widgets on the active page…") + — it already describes the redraw behaviour. + +- **`tests/test_dashboard_toolbar_buttons.m`** + - Button-names list: `'Reset'` → `'Redraw'`. + - Label assertion: `strcmp(get(hResetBtn,'String'), 'Reset')` → `'Redraw'`, + plus comment/message wording. + +## Deliberately NOT changed + +- **Time-panel "Reset" button** (`DashboardEngine.hTimeResetBtn` → + `resetTimeRange`): this is a genuine time-window reset and is on the time + panel, not the top bar. Left as "Reset" — renaming it would be wrong. +- **Internal names** `hResetBtn` / `onReset`: not user-facing; kept to avoid + rippling changes through `DashboardEngine.m` and the test suite for no user + benefit. + +## Verification + +- `mh`-style static analysis (MATLAB Code Analyzer) on both files: no new issues + (only pre-existing `info`-level suggestions outside the edited lines). +- `test_dashboard_toolbar_buttons`: **7 passed, 0 failed**, including: + - label assertion now expects "Redraw" ✓ + - tooltip still mentions "widget" ✓ + - `onReset()` still re-renders widgets (replaces panel handle) ✓ + +## Notes + +Executed inline rather than via separate planner/executor subagents: the change +was a fully-scoped single-label rename + matching test sync, so the subagent +orchestration would have added cost/worktree complexity with no benefit. GSD +guarantees preserved (scoped artifacts, STATE.md tracking, atomic commit).