Add crosshair-link toggle to FastSense dashboard widgets (260602-mri)#184
Merged
Conversation
…ast hook - FastSenseWidget: add CrosshairLinked=false public property, setCrosshairLink(tf) setter (validates logical/0/1, throws FastSenseWidget:invalidCrosshairLink), toStruct omits field when false (legacy JSON byte-identical), fromStruct restores flag pre-render (no graphics touch) - HoverCrosshair: add BroadcastFcn_/BroadcastLeaveFcn_ callbacks + setBroadcastFcn; onMoveExternal(x) sets SuppressLeaveUntil_=tic then drives onMove with InBroadcast_=true; onLeaveExternal() clears suppress + hides; onMove fires BroadcastFcn_ at tail; onLeave honors SuppressWindow_ guard and fires BroadcastLeaveFcn_ when source leaves; delete() nulls callbacks first - tests/test_fastsense_crosshair_link.m: 11 cases (6 PURE + 1 pure-engine + 2 render-guarded suppress-leave proof + broadcast-reentry proof + 2-widget mirror integration) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ination
- collectLinkedCrosshairs_(widgets): pure flattening helper (public);
returns cell of {widget,hc} structs for linked+rendered FastSenseWidgets
- rewireCrosshairLinks_(): clears all active-page broadcast hooks then
re-primes linked crosshairs with engine broadcast closures
- broadcastCrosshairX_(sourceHc, x): mirrors data-x onto all OTHER linked
crosshairs via onMoveExternal
- broadcastCrosshairLeave_(sourceHc): tells all OTHER linked crosshairs to
hide via onLeaveExternal
- onCrosshairLinkToggle(widget): re-derives full active-page link set
- rewireCrosshairLinks_ called after rerenderWidgets, switchPage, detachWidget
(260512-eu2 re-establish-after-rerender lesson)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- DashboardLayout.realizeWidget: inject CrosshairLinkButton via ismethod(widget,'setCrosshairLink') duck-type after addPlantLogToggle, before final reflowChrome_ - addCrosshairLinkToggle(widget): idempotent 24x24 'X' pushbutton; highlighted when CrosshairLinked=true via chooseYLimitActiveBg_; calls onCrosshairLinkTogglePressed_ on click; tails with reflowChrome_ for callback-driven rebuilds - onCrosshairLinkTogglePressed_: toggles CrosshairLinked, calls EngineRef.onCrosshairLinkToggle, rebuilds button look; try/catch + warning + non-blocking uialert mirrors addPlantLogToggle pattern - reflowChrome_: re-anchors CrosshairLinkButton as LEFTMOST chrome button (xVisible - gap - bw, left of V/A cluster) after resize - DashboardWidget.clearPanelControls: add 'CrosshairLinkButton' to protectedTags so button survives re-render sweeps Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ursion fix Orchestrator live-MATLAB verification found the suppress-leave tic-window (SuppressLeaveUntil_/SuppressWindow_) was flaky: it depends on wall-clock elapsed time inside a synchronous motion dispatch, so the crux test passed in isolation (6.7ms) but failed in the full suite. Replaced with a deterministic IsMirrored_ boolean - set by onMoveExternal, cleared when the widget becomes the hover source (real onMove) or via onLeaveExternal. Also fixes a latent unbounded leave ping-pong: onLeaveExternal now hides directly via a new private hideGraphics_ helper instead of re-entering the broadcasting onLeave (which would recurse across >=2 linked widgets and hang). Test fixes: collectLinkedCrosshairs_ enumeration passes the widget list as a parameter, and the 2-widget integration test uses addWidget(), since DashboardEngine.Widgets is SetAccess=private. Verified R2025a: 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; live UI smoke pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Quick task 260602-mri complete and verified (R2025a live MATLAB): a crosshair-link 'X' toggle on the FastSense widget grey bar mirrors the hover crosshair across all FastSense widgets on the active dashboard page. - PLAN.md: the executable plan (3 tasks) — now tracked - SUMMARY.md: updated for the deterministic IsMirrored_ suppress redesign, the latent leave-recursion fix, and the green test/lint/live-smoke results - STATE.md: Quick Tasks Completed row + last-activity line Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
HanSur94
added a commit
that referenced
this pull request
Jun 3, 2026
Resolve conflicts from main's Dashboard API hardening (#178) and crosshair-link toggle (#184): - FastSenseWidget.m: keep all three methods -- 1041's setTimeWindow + isShowingEmptyState alongside main's setCrosshairLink (independent additions at the same location; added the missing end for isShowingEmptyState). All three backing properties coexist. - .planning/STATE.md: status/activity lines reconciled to current state. Merge verified locally: TestFastSenseWidget 16/16, test_dashboard_time_window 8/8, TestCompanionTimeBar 12/12. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a crosshair-link toggle (
X) to each FastSense dashboard widget's grey WidgetButtonBar (left of the V/A buttons). When enabled on 2+ FastSense widgets on the active page, moving the hover crosshair over any one of them mirrors the crosshair's data-x onto all the others — each showing its own per-series datatip at that x, so you can compare values at the same time/x across plots. Toggle off to unlink. Default OFF; legacy serialized dashboards load byte-identical.Quick task
260602-mri(routed via/gsd:do→/gsd:quick).How it works
The mirror rides on the existing per-crosshair
onMove/onLeavechain —HoverCrosshair.onMovebroadcasts the data-x viaBroadcastFcn_, peers'onMoveExternalre-show at that x, and the existingcomputeYAtX_gives each peer its own Y for free (no Y transmitted, raw-x). Zero new figure-WindowButtonMotionFcnclosures — the constraint that prevents repeating the260512-egv/eu2chained-WBM regression. A peer being mirrored is held visible by a deterministicIsMirrored_flag (its same-dispatch self-leave no-ops); the hover source always broadcasts leave on cursor-exit, andonLeaveExternalhides peers directly (no leave recursion).DashboardEnginederives the active-page link set on demand (collectLinkedCrosshairs_, flattening GroupWidget children) and re-primes the broadcast hooks afterrerenderWidgets/switchPage/detachWidget. TheXbutton is duck-typed intoDashboardLayout.realizeWidgetviaismethod(widget,'setCrosshairLink')(same pattern as the V/A/L buttons), re-anchored byreflowChrome_, and protected inclearPanelControls.Files
libs/FastSense/HoverCrosshair.msetBroadcastFcn/onMoveExternal/onLeaveExternal, deterministicIsMirrored_suppress,hideGraphics_helper, broadcast tail inonMovelibs/Dashboard/FastSenseWidget.mCrosshairLinkedproperty +setCrosshairLink+ toStruct/fromStruct (omit-when-false)libs/Dashboard/DashboardEngine.mlibs/Dashboard/DashboardLayout.mXbutton + reflow re-anchorlibs/Dashboard/DashboardWidget.mCrosshairLinkButtonadded toclearPanelControlsprotected tagstests/test_fastsense_crosshair_link.mVerification (MATLAB R2025a, live)
test_fastsense_crosshair_link11/11 (incl. deterministic suppress-leave crux + 2-widget mirror integration)test_hover_crosshair11/11 ·test_fastsense_widget_ylimit_modes11/11 ·test_time_range_selector_reinstall_after_rerenderpass (chained-WBM guard) ·test_dashboard_time_sync_all_pages5/5mh_style+mh_lintclean on all 6 files; Code Analyzer no new findingsOrchestrator verification caught and fixed two defects in the initial implementation: a flaky wall-clock suppress window (→ deterministic
IsMirrored_) and a latent unbounded leave ping-pong (→onLeaveExternalhides directly).Follow-up
The new test is function-based, so it runs locally + on the Octave job but not in the MATLAB CI coverage shards (those load
tests/suite/Test*.monly) → 0 Codecov patch coverage in those shards. A class-basedtests/suite/TestFastSenseCrosshairLink.mis recommended as a follow-up.Out of scope
DetachedMirror crosshair-link parity — detached widgets use a figure-level
FastSenseToolbar, not a WidgetButtonBar (matches the260513-sfpdetached V/A/L precedent).🤖 Generated with Claude Code