Skip to content

Commit bd01d63

Browse files
HanSur94claude
andauthored
Companion: Tile + Close all toolbar buttons (#143)
* feat(companion): tile/close-all buttons + figure tracking [260513-s0y-T1] - Add OpenedFigures_ private property (column vector of figure handles the companion opens) plus hTileBtn_ / hCloseAllBtn_ button handles. - Extend the inner toolbar grid from 1x4 to 1x6: Events / Live remain in cols 1-2; new Tile (col 3, 70 px, WidgetBorderColor) and Close all (col 4, 90 px, Accent) buttons; the gear moves from col 4 to col 6. - Add trackOpenedFigure_ / pruneOpenedFigures_ private helpers (dedupe by handle equality, prune dead handles before iteration). - Hook onOpenDashboardRequested_ to capture ed.Engine.hFigure and onOpenAdHocPlotRequested_ to capture hFig from openAdHocPlot so both paths feed OpenedFigures_. - tileOpenedWindows: ceil(sqrt(N)) grid on the companion's monitor with a 24 px screen margin and 8 px per-tile gap; row-major top-down fill; per-figure failures are skipped so the rest still tile. - closeAllOpenedWindows: snapshot OpenedFigures_, call close(h) per handle (honoring each figure's CloseRequestFcn), then re-prune. Implements S0Y-01 (Tile windows) and S0Y-02 (Close all windows). Constructor signature, public properties, and existing method names are unchanged -- pure additive surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(companion): tile/close-all coverage [260513-s0y-T2] Function-style test that mirrors tests/test_companion_filter_tags.m conventions and exercises the new tile + close-all surface end-to-end: - test_tracking_on_dashboard_open_ -- track DashboardEngine.hFigure - test_tracking_dedupes_same_figure_ -- same handle, 3 calls = 1 entry - test_pruning_after_external_close_ -- close(fig) outside, then tile; dead handle drops cleanly - test_tile_geometry_no_overlap_ -- 4 figs, pairwise non-overlap + positive size after tile - test_close_all_clears_tracking_ -- 3 figs all close + list empty - test_outside_figures_not_touched_ -- untracked fig keeps Position AND survives Close all - test_toolbar_buttons_present_ -- findall finds 'Tile' + 'Close all' Octave skipped explicitly (FastSenseCompanion is MATLAB-only). Adds two friend accessors -- getOpenedFiguresForTest_ and trackOpenedFigureForTest_ -- so the test can probe + drive private tracking state without spinning up a real DashboardListPane. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(companion): sync OpenedFigures_ from Engines_ before tile/close-all [260513-s0y-T4] The lazy tracking hooks in onOpenDashboardRequested_ and onOpenAdHocPlotRequested_ missed two real-world cases: 1. DashboardListPane fires OpenDashboardRequested BEFORE calling engine.render(), so the synchronous listener saw hFigure=[] on first open and skipped. 2. Engines passed into the companion constructor (e.g. demo/industrial_plant/run_demo's pre-rendered dashboard) never go through the event flow at all. Added syncOpenedFigures_ that prunes dead handles and then pulls every Engines_{k}.hFigure that's currently alive into OpenedFigures_. tileOpenedWindows and closeAllOpenedWindows now call sync at the top instead of bare prune, so both buttons work for the demo dashboard and for any dashboard the user clicks in the middle pane. trackOpenedFigure_ stays unchanged — it's still the path for ad-hoc plots (they aren't in Engines_). New 8th sub-test test_sync_pulls_prerendered_engine_ reproduces the live-demo scenario: render a DashboardEngine, pass it into FastSenseCompanion constructor, never fire OpenDashboardRequested, then confirm tile + closeAll find the figure via sync. Test suite: 8/8 pass; TestFastSenseCompanion regression 64/64 pass. * fix(companion): expose public trackOpenedFigure + hook detail-plot paths [260513-s0y-T5] Two more tracking gaps surfaced in live testing after the sync fix: 1. InspectorPane.onOpenDetail_ calls openAdHocPlot DIRECTLY (single-tag "Open detail" button), bypassing OpenAdHocPlotRequested entirely — the returned figure was discarded. 2. CompanionEventViewer.openEventDashboard_ creates an ephemeral DashboardEngine that isn't added to Companion_.Engines_ — syncOpenedFigures_ can't see it. Both paths spawn figures the user expects Tile / Close all to control. Fix: - Added public FastSenseCompanion.trackOpenedFigure(hFig) — thin wrapper over the private trackOpenedFigure_. Preserves dedupe + prune-aware semantics. - InspectorPane.onOpenDetail_ now captures openAdHocPlot's returned hFig and forwards to Orchestrator_.trackOpenedFigure with the same try/catch + ismethod guard used elsewhere. - CompanionEventViewer.openEventDashboard_ does the same with the ephemeral DashboardEngine's hFigure (Companion_ already cached at construction). New 9th sub-test test_public_trackopenedfigure_hook_ exercises the public method directly: tracks → dedupes → closeAll closes. Existing 8 sub-tests still pass. * fix(companion): de-maximize + normalize units before set Position in tile [260513-s0y-T6] DashboardEngine.render creates classical figures with Units='normalized' (Position=[0.05 0.05 0.9 0.9]). The old tile code computed pixel rectangles but did set(fig, 'Position', [x y w h]) directly — MATLAB interpreted those pixels as NORMALIZED fractions of the screen, pushing every figure thousands of screen-widths off-screen. The user reported "Tile button does nothing" because the windows went off-canvas. Also: figures with WindowState='maximized' silently ignore set(Position), so a maximized dashboard wouldn't tile either. Fix (distFig-style): before set(Position), coerce each figure to WindowState='normal' + Units='pixels'. Both wrapped in try/catch so older releases without WindowState still work. Verified on a synthetic 3-dashboard test: BEFORE = all normalized (one maximized), AFTER = all 'normal' + 'pixels' with concrete pixel rectangles laid out in the expected 2x2 grid. * style(test): add whitespace after semicolons in companion test runner mh_style flagged 2 whitespace_semicolon violations at the test dispatch table where the new sync + public-hook sub-tests were added (commits 1be2cc8 and e58bc35). Match the alignment of the existing 7 rows. * Merge origin/main into claude/sharp-villani-e7b970 (catch up perf baseline + sfp quick task) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5d70901 commit bd01d63

5 files changed

Lines changed: 666 additions & 11 deletions

File tree

.planning/STATE.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ milestone: v3.0
44
milestone_name: FastSense Companion
55
status: shipped
66
last_updated: "2026-05-12T09:20:00.000Z"
7-
last_activity: 2026-05-13 - Completed quick task 260513-sfp: Added auto-y-limit V/A/L buttons to WidgetButtonBar with backward-compatible default. Verified on live industrial-plant demo.
7+
last_activity: 2026-05-14 -- Quick task 260513-s0y shipped (PR #143): Tile + Close all buttons on FastSenseCompanion top toolbar with 3 tracking fixes (sync from Engines_, public trackOpenedFigure hook, de-maximize + Units=pixels coercion). Verified live on industrial-plant demo. 9/9 sub-tests + 64/64 regression PASS.
88
progress:
99
total_phases: 6
1010
completed_phases: 2
@@ -20,7 +20,7 @@ Phase: 1028
2020
Plan: Not started
2121
Milestone: v3.0 FastSense Companion — SHIPPED 2026-04-30
2222
Status: Awaiting next milestone (run `/gsd:new-milestone` to scope v3.x or v4.0)
23-
Last activity: 2026-05-13 - Completed quick task 260513-sfp: Added auto-y-limit V/A/L buttons to WidgetButtonBar with backward-compatible default. Verified on live industrial-plant demo.
23+
Last activity: 2026-05-14 - Quick task 260513-s0y shipped as PR #143 (commits 182d6f1, 2867caa, 1be2cc8, e58bc35, c47c0c1, db9ef88). FastSenseCompanion: Tile + Close all toolbar buttons. Three tracking fixes uncovered by live verification: (1) syncOpenedFigures_ walks Engines_ — DashboardListPane fires OpenDashboardRequested BEFORE engine.render(), and run_demo's pre-rendered demo dashboard never goes through that event flow; (2) public trackOpenedFigure hook on the companion called from InspectorPane.onOpenDetail_ + CompanionEventViewer.openEventDashboard_ which spawn figures directly bypassing the event flow; (3) de-maximize + Units=pixels coercion before set(Position) — DashboardEngine.render defaults to Units=normalized so pixel rectangles got treated as screen fractions, pushing figures off-canvas (root cause of "Tile button does nothing" report). 9/9 sub-tests + 64/64 regression PASS. Verified on live industrial-plant demo.
2424

2525
### Quick Tasks Completed
2626

@@ -65,6 +65,7 @@ Last activity: 2026-05-13 - Completed quick task 260513-sfp: Added auto-y-limit
6565
| 260513-ovt | Preserve widget X and Y views across Live ticks + Follow toggle reaches every page — (1) added LiveViewMode='follow' guard inside FastSenseWidget.autoScaleY_, (2) removed `autoScaleY_(y)` from FastSenseWidget.refresh/update, (3) removed `broadcastTimeRange(tStart, tEnd)` from DashboardEngine.onLiveTick, (4) flipped FastSenseWidget.LiveViewMode default 'reset'→'preserve', (5) made FastSenseToolbar.syncFollowState public so FastSense.onXLimChanged's auto-disengage hook actually syncs the Follow button, (6) made DashboardEngine.{allPageWidgets,activePageWidgets} public + onFollowToggle uses allPageWidgets() so Follow actually flips every FastSenseWidget across all pages on multi-page dashboards (was silently no-op via swallowed MethodRestricted). Live mode is now strictly "append data only"; Follow does width-preserving slide with 2% right-edge gap. test_fastsense_follow_toggle 10/10, test_dashboard_time_sync_all_pages 5/5, test_dashboard_range_selector_integration 2/2; verified end-to-end on industrial plant demo (Follow ON: XLim+0.140d toward tail, width preserved exactly, 2/2 widgets switched; OFF: 2/2 reverted) | 2026-05-13 | 498a5f3, ca5be95, 8d41c48, 63cdff4 | — | [260513-ovt-when-follow-button-is-pressed-y-axis-lim](./quick/260513-ovt-when-follow-button-is-pressed-y-axis-lim/) |
6666
| 260513-q7w | Debounced post-resize refresh + ZOMBIE-PANEL fix that stops widgets going white during drag-resize and tab switching — TWO parallel timers on every figure resize event (300 ms cheap two-pass refresh + 1.2 s unconditional rerenderWidgets backstop). switchPage cancels both timers AND waits up to 3 s for in-flight rerenderWidgets to complete before mutating state. `IsRerendering_` flag prevents rerender-cascade scheduling. Re-entrancy guard aborts instead of self-rescheduling. **Root-cause fix**: rerenderWidgets now deletes the OUTER cell panel (via hCellPanel, falling back to hPanel for pre-realization widgets) — previous code deleted only `hPanel` which after realization points to the INNER content panel, leaving the outer cell + its WidgetButtonBar chrome alive on the canvas as "zombies" that stacked up over multiple rerenders and painted over freshly switched-to pages. test_dashboard_range_selector_integration 2/2, test_dashboard_time_sync_all_pages 5/5; canvas-children-count canary verifies zero zombie accumulation across 4 rerenders + resize + tab switch (constant 29) | 2026-05-13 | 577bf95, 99c8808, 4eda604, bc305dc, 54d5aa0, 20bcd4c | — | [260513-q7w-during-dashboard-figure-resize-fastsense](./quick/260513-q7w-during-dashboard-figure-resize-fastsense/) |
6767
| 260513-sfp | Add auto-y-limit control buttons (V/A/L) to FastSenseWidget WidgetButtonBar — new YLimitMode property (auto-visible / auto-all / locked, default 'auto-visible' reproduces pre-260513-sfp behaviour), setYLimitMode public method (clears UserZoomedY on explicit click so click re-engages autoscale), autoScaleY_ refactored to dispatch on mode AFTER existing precedence guards (YLimits pin / UserZoomedY / FastSense.LiveViewMode=='follow') so 260513-ovt Follow semantics are preserved. DashboardLayout duck-types widget chrome via ismethod(widget,'setYLimitMode'), so future widgets that expose Y-rescale modes opt in without touching DashboardLayout. ASCII glyphs (V/A/L) match existing Info/Detach. reflowChrome_ re-anchors on resize. toStruct omits the default so legacy dashboards stay diff-invisible. test_fastsense_widget_ylimit_modes 11/11, test_fastsense_widget_tag 7/7, test_fastsense_follow_toggle 10/10, test_dashboard_time_sync_all_pages 5/5. Verified on live industrial-plant demo, all 8 scenarios approved. Known caveat: V/A/L cluster butts against Info button (0-px gap) — inherited from pre-existing addInfoIcon 28-px-typo, explicitly out-of-scope per plan; logged in deferred-items.md | 2026-05-13 | 4db9138, cc18c7f, a9cc181 | Verified | [260513-sfp-add-auto-y-limit-control-buttons-to-fast](./quick/260513-sfp-add-auto-y-limit-control-buttons-to-fast/) |
68+
| 260513-s0y | Add Tile + Close all buttons to FastSenseCompanion top toolbar — private OpenedFigures_ tracking + syncOpenedFigures_ (walks Engines_ before tile/close-all) + public trackOpenedFigure hook (InspectorPane.onOpenDetail_ and CompanionEventViewer.openEventDashboard_ forward their figure handles). tileOpenedWindows: ceil(sqrt(N))×ceil(N/cols) grid on monitor containing the companion, 24px margin, 8px gutter, row-major top-down. Before set(Position), coerces each figure to WindowState='normal' + Units='pixels' — root cause of initial "Tile does nothing" report was DashboardEngine.render defaulting to Units='normalized' (pixel rects got treated as screen fractions, pushing figures off-canvas). closeAllOpenedWindows: snapshot + close(h) per handle (honors each figure's CloseRequestFcn). Inner toolbar grid 1×4→1×6 (Events / Live / Tile / Close all / spacer / gear; gear Layout.Column 4→6). 9 sub-tests in test_companion_tile_close_buttons.m PASS; TestFastSenseCompanion regression 64/64 PASS. Verified on live industrial-plant demo. Shipped as PR #143. | 2026-05-14 | 182d6f1, 2867caa, 1be2cc8, e58bc35, c47c0c1, db9ef88 | Shipped (PR #143) | [260513-s0y-add-tile-windows-and-close-all-windows-b](./quick/260513-s0y-add-tile-windows-and-close-all-windows-b/) |
6869

6970
## Progress Bar
7071

libs/FastSenseCompanion/CompanionEventViewer.m

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,6 +1262,18 @@ function openEventDashboard_(obj, ev)
12621262
end
12631263

12641264
obj.bringFigureToFront_(d.hFigure);
1265+
1266+
% Register with the companion so the Tile / Close all buttons treat
1267+
% the event-detail dashboard like any other companion-opened window.
1268+
% The DashboardEngine is ephemeral (not in Companion_.Engines_), so
1269+
% syncOpenedFigures_ won't find it on its own.
1270+
try
1271+
if ~isempty(obj.Companion_) && isvalid(obj.Companion_) && ...
1272+
ismethod(obj.Companion_, 'trackOpenedFigure')
1273+
obj.Companion_.trackOpenedFigure(d.hFigure);
1274+
end
1275+
catch
1276+
end
12651277
end
12661278

12671279
function onTableRowSelectionChanged_(obj, evt)

0 commit comments

Comments
 (0)