Skip to content

Commit 0835c62

Browse files
HanSur94claude
andcommitted
Merge v3.0 FastSense Companion milestone
Phases shipped: - 1018 Companion Shell + Project Handoff - 1019 Tag Catalog - 1020 Dashboard Browser - 1021 Inspector - 1022 Ad-Hoc Plot Composer - 1023 Industrial Plant Demo Integration - 1023.1 Cross-Phase Wiring Fixes (gap closure) Coverage: 32/32 v3.0 REQs (COMPSHELL, CATALOG, BROWSER, INSPECT, ADHOC, COMPDEMO). New library: libs/FastSenseCompanion/ (~2,933 LoC, 9 top-level + 5 private helpers). Test coverage: 737 LoC across 5 class-based suites (Test*Companion*, TestInspectorPane, etc.). Audit passed post 1023.1 gap closure (cross-phase wiring fixes for theme.Accent + theme.LineColors aliases, setProject listener leak, demoClose_ ctx-capture timing). Tag: v3.0 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 parents ed1db4f + a872f6b commit 0835c62

38 files changed

Lines changed: 5572 additions & 3 deletions

.claude/settings.local.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
"Skill(gsd:quick)",
1919
"Skill(gsd:debug)",
2020
"Skill(gsd:research-phase)",
21-
"WebSearch"
21+
"WebSearch",
22+
"Skill(gsd:ui-phase)",
23+
"Skill(gsd:ui-phase:*)"
2224
]
2325
}
2426
}

.planning/ROADMAP.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Roadmap: FastSense Advanced Dashboard
2+
3+
## Milestones
4+
5+
-**v1.0 FastSense Advanced Dashboard** — Phases 1-9 (shipped 2026-04-03)
6+
-**v1.0 Dashboard Engine Code Review Fixes** — Phase 1 (shipped 2026-04-03)
7+
-**v1.0 Dashboard Performance Optimization** — Phase 1 (shipped 2026-04-04)
8+
-**v1.0 First-Class Thresholds & Composites** — Phases 1000-1003 (shipped 2026-04-15)
9+
-**v2.0 Tag-Based Domain Model** — Phases 1004-1011 (shipped 2026-04-17)
10+
- 📋 **v2.1 Tag-API Tech Debt Cleanup** — Phases 1012-1017 (carry-forward, parallel — not active)
11+
-**v3.0 FastSense Companion** — Phases 1018-1023 + 1023.1 gap closure (shipped 2026-04-30)
12+
13+
## Phases
14+
15+
<details>
16+
<summary>✅ v1.0 FastSense Advanced Dashboard (Phases 1-9) — SHIPPED 2026-04-03</summary>
17+
18+
- [x] Phase 1: Infrastructure Hardening (4/4 plans) — completed 2026-04-01
19+
- [x] Phase 2: Collapsible Sections (2/2 plans) — completed 2026-04-01
20+
- [x] Phase 3: Widget Info Tooltips (3/3 plans) — completed 2026-04-01
21+
- [x] Phase 4: Multi-Page Navigation (3/3 plans) — completed 2026-04-01
22+
- [x] Phase 5: Detachable Widgets (3/3 plans) — completed 2026-04-02
23+
- [x] Phase 6: Serialization & Persistence (2/2 plans) — completed 2026-04-02
24+
- [x] Phase 7: Tech Debt Cleanup (1/1 plan) — completed 2026-04-03
25+
- [x] Phase 8: Widget Improvements (3/3 plans) — completed 2026-04-03
26+
- [x] Phase 9: Threshold Mini-Labels (2/2 plans) — completed 2026-04-03
27+
28+
Full details: [milestones/v1.0-ROADMAP.md](milestones/v1.0-ROADMAP.md)
29+
30+
</details>
31+
32+
<details>
33+
<summary>✅ v2.0 Tag-Based Domain Model (Phases 1004-1011) — SHIPPED 2026-04-17</summary>
34+
35+
- [x] Phase 1004: Tag Foundation + Golden Test
36+
- [x] Phase 1005: SensorTag + StateTag (data carriers)
37+
- [x] Phase 1006: MonitorTag (lazy, in-memory)
38+
- [x] Phase 1007: MonitorTag streaming + persistence
39+
- [x] Phase 1008: CompositeTag
40+
- [x] Phase 1009: Consumer migration (one widget at a time)
41+
- [x] Phase 1010: Event ↔ Tag binding + FastSense overlay
42+
- [x] Phase 1011: Cleanup — collapse parallel hierarchy + delete legacy
43+
44+
Full details: [milestones/v2.0-ROADMAP.md](milestones/v2.0-ROADMAP.md)
45+
46+
</details>
47+
48+
<details>
49+
<summary>🚧 v2.1 Tag-API Tech Debt Cleanup (Phases 1012-1017) — in flight</summary>
50+
51+
- [x] Phase 1012: Migrate examples to Tag API
52+
- [x] Phase 1013: Dead code deletion — EventDetector, IncrementalEventDetector, EventConfig
53+
- [x] Phase 1014: DashboardSerializer .m export for Tag-bound widgets
54+
- 🚧 Phase 1017: Tag system event auto-wiring — registry default EventStore, dual-key emission
55+
56+
</details>
57+
58+
<details>
59+
<summary>✅ v3.0 FastSense Companion (Phases 1018-1023 + 1023.1) — SHIPPED 2026-04-30</summary>
60+
61+
- [x] Phase 1018: Companion Shell + Project Handoff (3/3 plans) — completed 2026-04-29
62+
- [x] Phase 1019: Tag Catalog (3/3 plans) — completed 2026-04-29
63+
- [x] Phase 1020: Dashboard Browser (3/3 plans) — completed 2026-04-29
64+
- [x] Phase 1021: Inspector (4/4 plans) — completed 2026-04-30
65+
- [x] Phase 1022: Ad-Hoc Plot Composer (3/3 plans) — completed 2026-04-30
66+
- [x] Phase 1023: Industrial Plant Demo Integration (2/2 plans) — completed 2026-04-30
67+
- [x] Phase 1023.1: Cross-Phase Wiring Fixes (gap closure) — completed 2026-04-30
68+
69+
Full details: [milestones/v3.0-ROADMAP.md](milestones/v3.0-ROADMAP.md)
70+
71+
</details>
72+
73+
## Phase Details
74+
75+
(no active milestone — see [milestones/](milestones/) for archived versions)
76+
77+
## Progress
78+
79+
| Phase | Milestone | Plans Complete | Status | Completed |
80+
|-------|-----------|----------------|--------|-----------|
81+
| 1-9 | v1.0 Advanced Dashboard | 24/24 | Complete | 2026-04-03 |
82+
| 01. Code Review Fixes | v1.0 Code Review | 4/4 | Complete | 2026-04-03 |
83+
| 01. Performance Optimization | v1.0 Performance | 3/3 | Complete | 2026-04-04 |
84+
| 1000-1003 | v1.0 First-Class Thresholds | 14/14 | Complete | 2026-04-15 |
85+
| 1004. Tag Foundation + Golden Test | v2.0 | 3/3 | Complete | 2026-04-16 |
86+
| 1005. SensorTag + StateTag | v2.0 | 3/3 | Complete | 2026-04-16 |
87+
| 1006. MonitorTag (lazy, in-memory) | v2.0 | 3/3 | Complete | 2026-04-16 |
88+
| 1007. MonitorTag streaming + persistence | v2.0 | 3/3 | Complete | 2026-04-16 |
89+
| 1008. CompositeTag | v2.0 | 3/3 | Complete | 2026-04-16 |
90+
| 1009. Consumer migration | v2.0 | 4/4 | Complete | 2026-04-17 |
91+
| 1010. Event ↔ Tag binding + overlay | v2.0 | 3/3 | Complete | 2026-04-17 |
92+
| 1011. Cleanup + delete legacy | v2.0 | 5/5 | Complete | 2026-04-17 |
93+
| 1012. Migrate examples to Tag API | v2.1 | 10/10 | Complete ||
94+
| 1013. Dead code deletion | v2.1 || Complete ||
95+
| 1014. DashboardSerializer .m export | v2.1 | 1/1 | Complete ||
96+
| 1017. Tag system event auto-wiring | v2.1 | 0/? | In progress ||
97+
| 1018. Companion Shell + Project Handoff | v3.0 | 3/3 | Complete | 2026-04-29 |
98+
| 1019. Tag Catalog | v3.0 | 3/3 | Complete | 2026-04-29 |
99+
| 1020. Dashboard Browser | v3.0 | 3/3 | Complete | 2026-04-29 |
100+
| 1021. Inspector | v3.0 | 4/4 | Complete | 2026-04-30 |
101+
| 1022. Ad-Hoc Plot Composer | v3.0 | 3/3 | Complete | 2026-04-30 |
102+
| 1023. Industrial Plant Demo Integration | v3.0 | 2/2 | Complete | 2026-04-30 |
103+
| 1023.1. Cross-Phase Wiring Fixes | v3.0 | gap-closure | Complete | 2026-04-30 |

.planning/STATE.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
gsd_state_version: 1.0
3+
milestone: v3.0
4+
milestone_name: FastSense Companion
5+
status: shipped
6+
last_updated: "2026-04-30T09:50:00.000Z"
7+
last_activity: 2026-04-30 -- v3.0 milestone shipped (audit passed post 1023.1 gap closure)
8+
progress:
9+
total_phases: 6
10+
completed_phases: 6
11+
total_plans: 18
12+
completed_plans: 18
13+
shipped_at: 2026-04-30
14+
---
15+
16+
# State
17+
18+
## Current Position
19+
20+
Milestone: v3.0 FastSense Companion — SHIPPED 2026-04-30
21+
Status: Awaiting next milestone (run `/gsd:new-milestone` to scope v3.x or v4.0)
22+
Last activity: 2026-04-30 -- v3.0 archived to .planning/milestones/v3.0-ROADMAP.md
23+
24+
## Progress Bar
25+
26+
```
27+
v3.0 FastSense Companion
28+
Phase 1018 [██████████] 100% (3/3 plans complete in Phase 1018; 1/6 phases complete overall)
29+
Phase 1019 [██████████] 100% (3/3 plans complete in Phase 1019; 6/6 plans complete overall)
30+
```
31+
32+
## Accumulated Context
33+
34+
### Roadmap Evolution
35+
36+
- 2026-04-29 — Milestone v3.0 FastSense Companion started (programmatic MATLAB uifigure companion app; design brainstormed prior; v2.1 Tag-API Tech Debt Cleanup carried forward in parallel)
37+
- 2026-04-29 — v3.0 roadmap created: 5 phases (1018-1022) covering 28 REQ-IDs across COMPSHELL, CATALOG, BROWSER, INSPECT, ADHOC categories
38+
- 2026-04-29 — v3.0 phase 1023 added (Industrial Plant Demo Integration): wraps `demo/industrial_plant/run_demo.m` in `FastSenseCompanion`; 4 new COMPDEMO REQ-IDs; total now 6 phases / 32 REQ-IDs
39+
40+
### Phase Numbering Note
41+
42+
v2.1 phases in the phases/ directory extend to 1017 (1012, 1013, 1014, 1017). v3.0 phases start at 1018 to avoid collision.
43+
44+
### Brainstorm Outcomes (v3.0)
45+
46+
Design decisions locked during the v3.0 brainstorm conversation (2026-04-29):
47+
48+
- **Scope:** A + B + C combined — library browser + live monitoring + tag-first explorer. **Not** D (no in-app dashboard authoring/editing).
49+
- **UI tech:** Programmatic `uifigure` (no App Designer, no `.mlapp`).
50+
- **Connection contract:** Loose handoff via constructor: `FastSenseCompanion('Dashboards', {d1, d2}, 'Registry', TagRegistry)`. Tags pulled from `TagRegistry` singleton by default; pass `'Registry', reg` to override. Single project per app instance (no multi-project switcher).
51+
- **Dashboard rendering:** Opening a dashboard pops it into its own MATLAB figure via existing `DashboardEngine.render()`. Companion is purely a control panel / navigator. Zero changes required to `DashboardEngine`.
52+
- **Layout:** Three-pane window — left = searchable tag catalog with multi-select checkboxes and filter pills; middle = dashboard list; right = adaptive inspector.
53+
- **Inspector states:** `welcome` (empty) / `tag` (single tag selected — metadata, thresholds, "used in" cross-references, "Plot this tag" → `SensorDetailPlot`) / `multitag` (N>1 — plot composer with Linked grid / Overlay, time range All / Last 1h, Live Off/2s/5s) / `dashboard` (dashboard tile selected — summary + open + live toggle). Most-recent click wins (`LastInteraction = 'tags' | 'dashboard'`).
54+
- **Tag grouping:** Derived from `Tag.Labels` (existing property; no new model field). Filter pills also reflect `Tag.Criticality`.
55+
- **Ad-hoc plotting modes:** Linked grid (`FastSenseGrid` with shared `LinkGroup`) and Overlay (single `FastSense` instance with multiple lines). Dropped "Separate figures" as YAGNI.
56+
- **Live refresh:** Companion does **not** own a refresh timer for dashboards — uses each `DashboardEngine`'s own `LiveInterval` and start/stop. For ad-hoc plots, companion runs a `timer` that calls `tag.getXY()` and `updateData()` on the open figure; timer stored on figure `UserData`, stops on figure close.
57+
- **File structure:**
58+
- `libs/FastSenseCompanion/FastSenseCompanion.m` (orchestrator, public API)
59+
- `libs/FastSenseCompanion/TagCatalogPane.m` (left pane)
60+
- `libs/FastSenseCompanion/DashboardListPane.m` (middle pane)
61+
- `libs/FastSenseCompanion/InspectorPane.m` (right pane)
62+
- `libs/FastSenseCompanion/CompanionTheme.m` (static color/font helper, mirrors `DashboardTheme`)
63+
- `libs/FastSenseCompanion/private/companionUsageIndex.m` (tag → dashboards map)
64+
- `libs/FastSenseCompanion/private/filterTags.m` (search + filter pure logic)
65+
- `libs/FastSenseCompanion/private/openAdHocPlot.m` (figure factory)
66+
- **Event wiring:** MATLAB `events`/`notify`. Pane events: `TagSelectionChanged`, `DashboardSelected`, `OpenSensorDetail`, `OpenAdHocPlot`, `OpenDashboard`. Orchestrator owns selection state (`SelectedTagKeys`, `SelectedDashboardIdx`, `LastInteraction`).
67+
- **Public API:** `FastSenseCompanion(name-value)`, `setProject(dashboards, registry)`, `addDashboard(d)`, `removeDashboard(key)`, `selectTags(keys)`, `close()`. Private: pane handles. Not on surface: live-refresh control (delegates to `DashboardEngine`), dashboard creation/edit (out of scope).
68+
- **Errors:** All namespaced `FastSenseCompanion:*`. Constructor / `setProject` validate eagerly. Every event callback wrapped in try/catch → `uialert(fig, ...)`. Downstream throws (e.g., `DashboardEngine.render`) never crash the companion.
69+
- **Testing:** Pure-logic unit tests (`tests/test_companion_filter_tags.m`, `tests/test_companion_usage_index.m`). Class-based integration suite (`tests/suite/TestFastSenseCompanion.m`) — hidden `uifigure('Visible','off')`, drives state via `selectTags`, mocks `openAdHocPlot` via DI seam (constructor accepts a callable, defaults to real helper). No pixel-perfect UI tests.
70+
- **Out of scope (v1 of Companion):** dashboard authoring; multi-project; cross-session persistence; status strip with global KPIs; custom time-range picker; detachable panes; WebBridge integration.
71+
72+
### Cross-Cutting Engineering Constraints (locked in Phase 1018)
73+
74+
These apply to every phase and are reflected in phase success criteria rather than separate REQ-IDs:
75+
76+
- `Listeners_` cell array on every class that calls `addlistener`; `delete(obj.Listeners_)` in `CloseRequestFcn`
77+
- `stop(t); delete(t);` always in that order for every timer (companion and ad-hoc)
78+
- Companion is the only `uifigure`; all spawned figures are classical `figure` — never parent one inside the other
79+
- `axes(uipanel)` not `uiaxes(uipanel)` for embedded plots (9x performance difference)
80+
- Errors namespaced `FastSenseCompanion:*`; every callback wrapped in try/catch + non-blocking `uialert`
81+
- Pure-logic helpers (`filterTags_`, `flattenWidgets_`) ship with unit tests
82+
83+
### Research Flags for Planning
84+
85+
- **Phase 1020 planning:** Read `libs/Dashboard/DashboardPage.m` and `libs/Dashboard/GroupWidget.m` to confirm `Widgets` and `Children` GetAccess. Determines whether `DashboardEngine.getWidgets()` wrapper is required or if `d.Widgets`/`d.Pages{i}.Widgets` suffices.
86+
- **Phase 1021 planning:** Run 20-line scratch test of `SensorDetailPlot(tag, 'Parent', uipanelHandle)` to verify resize behavior under embedded panel parenting.
87+
- **Phase 1022 planning:** Write standalone 50-line `FastSenseGrid` + `timer` + `CloseRequestFcn` prototype before full implementation; verify zero orphan timers in `timerfindall` after close.
88+
89+
### Decisions (Phase 1020)
90+
91+
- **1020-02:** applyFilter_() is the single rebuild path for DashboardListPane row list; onRowClicked_ sets SelectedIdx_ then calls applyFilter_() for highlight rather than painting individual buttons
92+
- **1020-02:** addDashboard uses handle identity (==) for duplicate detection; removeDashboard uses Name (case-sensitive strcmp) for lookup per CONTEXT.md
93+
- **1020-02:** Listeners re-wired in setProject after detach clears them; SelectedDashboardIdx_ clamped to 0 in refresh() when engine list shrinks
94+
95+
### Carry-Forward
96+
97+
- **v2.1 Tag-API Tech Debt Cleanup** — in flight, parallel to v3.0. Phases 1012-1017. Does not block v3.0 work.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
function companion = buildCompanion(ctx)
2+
%BUILDCOMPANION Construct the FastSenseCompanion for the industrial plant demo.
3+
% companion = buildCompanion(ctx) wraps the demo's already-built
4+
% DashboardEngine (ctx.engine) and the populated TagRegistry singleton
5+
% inside a FastSenseCompanion window so the milestone canary covers
6+
% both surfaces with a single run_demo() invocation.
7+
%
8+
% Preconditions:
9+
% - ctx.engine must be a live DashboardEngine handle (run_demo calls
10+
% ctx.engine = buildDashboard(ctx) BEFORE invoking this helper).
11+
% - TagRegistry singleton has been populated by registerPlantTags so
12+
% the companion's catalog reflects every plant tag.
13+
%
14+
% Theme is hardcoded to 'dark' -- matching the existing dashboard's
15+
% visual register and avoiding a parameterization ahead of need. If the
16+
% demo gains a theme option in a later phase, lift this through ctx.
17+
%
18+
% Returns:
19+
% companion - live FastSenseCompanion handle (uifigure already visible).
20+
%
21+
% See also: run_demo, teardownDemo, FastSenseCompanion, buildDashboard.
22+
23+
companion = FastSenseCompanion( ...
24+
'Dashboards', {ctx.engine}, ...
25+
'Theme', 'dark');
26+
end

demo/industrial_plant/private/buildDashboard.m

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,17 @@
6161
end
6262

6363
function demoClose_(fig, ctx)
64-
%DEMOCLOSE_ Figure CloseRequestFcn callback -- tears down timers then deletes fig.
64+
%DEMOCLOSE_ Figure CloseRequestFcn callback -- closes companion (if any), tears down timers, deletes fig.
65+
% Phase 1023 -- close the companion BEFORE teardownDemo so the close()
66+
% deletes the uifigure cleanly via the public API. teardownDemo's own
67+
% companion sweep is a belt-and-braces fallback.
68+
if isfield(ctx, 'companion') && ~isempty(ctx.companion) && isvalid(ctx.companion)
69+
try
70+
ctx.companion.close();
71+
catch
72+
% Best-effort.
73+
end
74+
end
6575
try
6676
teardownDemo(ctx);
6777
catch

demo/industrial_plant/run_demo.m

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
function ctx = run_demo()
1+
function ctx = run_demo(varargin)
22
%RUN_DEMO Industrial plant live demo entry point (plan-01 scaffold).
33
% ctx = run_demo() bootstraps the synthetic data generator, the live
44
% TagRegistry wiring, and the LiveTagPipeline -- the non-UI plumbing
55
% for the Phase 1015 demo. Plan 02 extends this entry point to build
66
% the multi-page dashboard; for now the returned ctx carries all the
77
% handles a caller (or a test) needs to observe the pipeline.
88
%
9+
% Options (name-value pairs):
10+
% 'Companion' (logical, default true) -- when true, run_demo also
11+
% constructs a FastSenseCompanion window over the demo's
12+
% DashboardEngine + populated TagRegistry. Pass false to suppress
13+
% (e.g., for headless tests that only need the dashboard).
14+
%
915
% Returns:
1016
% ctx - struct with fields:
1117
% writerTimer - IndustrialPlantDataGen MATLAB timer (running)
1218
% pipeline - LiveTagPipeline (running)
1319
% engine - [] (plan 02 populates this with a DashboardEngine)
20+
% companion - FastSenseCompanion handle (or [] when 'Companion'=false)
1421
% store - EventStore wired into every MonitorTag
1522
% plantHealthKey - 'plant.health' (top-level rollup)
1623
% rawDir - absolute path to demo/industrial_plant/data/raw
@@ -26,10 +33,30 @@
2633
% [x, y] = TagRegistry.get('reactor.pressure').getXY();
2734
% teardownDemo(ctx);
2835
%
36+
% % Suppress the companion window (dashboard only):
37+
% ctx = run_demo('Companion', false);
38+
%
39+
% % Tear down both windows when done:
40+
% teardownDemo(ctx);
41+
%
2942
% See also: plantConfig, registerPlantTags, makeDataGenerator,
3043
% startLivePipeline, teardownDemo, TagRegistry, LiveTagPipeline.
3144

3245
here = fileparts(mfilename('fullpath'));
46+
47+
% Parse name-value options (matches FastSenseCompanion's varargin parser convention).
48+
useCompanion = true;
49+
for k = 1:2:numel(varargin)
50+
key = varargin{k};
51+
switch key
52+
case 'Companion'
53+
useCompanion = logical(varargin{k+1});
54+
otherwise
55+
error('run_demo:unknownOption', ...
56+
'Unknown option ''%s''. Valid options: Companion.', key);
57+
end
58+
end
59+
3360
rawDir = fullfile(here, 'data', 'raw');
3461
tagsDir = fullfile(here, 'data', 'tags');
3562

@@ -58,6 +85,7 @@
5885
'writerTimer', writerTimer, ...
5986
'pipeline', pipeline, ...
6087
'engine', [], ...
88+
'companion', [], ...
6189
'store', store, ...
6290
'plantHealthKey', plantHealthKey, ...
6391
'rawDir', rawDir, ...
@@ -68,6 +96,45 @@
6896
% starts the live timer, and wires a CloseRequestFcn that calls
6997
% teardownDemo(ctx).
7098
ctx.engine = buildDashboard(ctx);
99+
100+
% Phase 1023 -- opt-in companion window (built AFTER buildDashboard so
101+
% ctx.engine is a live handle the companion can wrap).
102+
if useCompanion
103+
ctx.companion = buildCompanion(ctx);
104+
else
105+
ctx.companion = [];
106+
end
107+
108+
% Phase 1023.1 cross-phase fix: re-bind the dashboard figure's
109+
% CloseRequestFcn with the now-complete ctx. buildDashboard set the
110+
% callback when ctx.companion was still [], so the closure captured
111+
% an empty companion handle and never cascaded close. Re-set the
112+
% callback here so dashboard X-button → demoClose_ → ctx.companion.close()
113+
% cascade works (COMPDEMO-04).
114+
if ~isempty(ctx.engine) && ~isempty(ctx.engine.hFigure) && ishandle(ctx.engine.hFigure)
115+
set(ctx.engine.hFigure, 'CloseRequestFcn', @(src, ~) demoClose_(src, ctx));
116+
end
117+
end
118+
119+
function demoClose_(fig, ctx)
120+
%DEMOCLOSE_ Re-exposed from buildDashboard for run_demo's CloseRequestFcn rebind.
121+
% Identical contract to buildDashboard's nested demoClose_ — closes companion
122+
% first (if valid), then teardownDemo, then deletes the figure.
123+
if isfield(ctx, 'companion') && ~isempty(ctx.companion) && isvalid(ctx.companion)
124+
try
125+
ctx.companion.close();
126+
catch
127+
% Best-effort.
128+
end
129+
end
130+
try
131+
teardownDemo(ctx);
132+
catch
133+
end
134+
try
135+
delete(fig);
136+
catch
137+
end
71138
end
72139

73140
function waitForFirstSample_(tagKey, maxSeconds)

0 commit comments

Comments
 (0)