Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .planning/STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Phase: 1028 (tag-update-perf-mex-simd) — COMPLETE 2026-05-19 (this branch)
Plan: 6 of 6 executed (with 03/04 deferred per Plan 02d data). Shipped plans: 01, 02, 02b, 02d, 05, 06.
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 1028 closed. WithIO `tickMin` reduced 4497 ms → 3603 ms (−19.9%) on Octave Linux x86_64 CI run 26089658442, almost entirely from Plan 02d's in-memory prior-state cache. Plan 06 ships per-tick fs-stat coalescing reducing 1600 → 1 syscalls/tick (−99.94% mechanism-level; wall-time +3.2% within variance on tmpfs CI). PR #114 carries the phase. Follow-up candidates for a future perf phase: in-memory propagation refactor; `containers.Map` → struct-array refactor; `.mat` save-side optimization. K2/K3/K4 deferred per data (target regions bucket as 0 ms post-cache).
Last activity: 2026-05-19 — Completed phase 1028: Tag update perf — MEX + SIMD. Plan 06 shipped per-tick fs-stat coalescing seam (1600 → 1 syscall/tick = −99.94% reduction; wall-time within variance), phase wrap docs (VERIFICATION.md Final Result, ROADMAP.md, STATE.md, 1028-06-SUMMARY.md). Cumulative phase win: WithIO −19.9% from Plan 02d's read-side cache. K2/K3/K4 deferred per data; in-memory propagation + Map refactor remain as candidates for a follow-up perf phase.
Last activity: 2026-05-26 - Completed quick task 260526-r9x: Add PerTag composer mode to FastSenseCompanion - spawn one DashboardEngine window per selected tag

### Note on parallel v4.0 work (main branch state)

Expand Down Expand Up @@ -88,6 +88,8 @@ Other main PRs (#138, #139, #141, #144, #145, #146) auto-merged without conflict
| 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/) |
| 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/) |
| 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/) |
| 260526-pw3 | Show all tag events in FastSense widgets across industrial plant demo | 2026-05-26 | c475d2a | — | [260526-pw3-in-the-industrial-plant-demo-ensure-all-](./quick/260526-pw3-in-the-industrial-plant-demo-ensure-all-/) |
| 260526-r9x | Add PerTag composer mode to FastSenseCompanion — spawn one DashboardEngine window per selected tag | 2026-05-26 | abdc80b | Verified | [260526-r9x-add-pertag-composer-mode-to-fastsensecom](./quick/260526-r9x-add-pertag-composer-mode-to-fastsensecom/) |
| 260519-bs4 | Add Tag Status Table window to FastSenseCompanion — new `TagStatusTableWindow.m` (classical figure, not uifigure, per CONTEXT.md), opened via new **Tags ↗** button on companion top toolbar (col 3 in the post-merge 1×7 grid: Events / Live / Tags / Tile / Close all / spacer / gear). Detached-only window with 12-column `uitable`: Key, Name, Type, Criticality, Units, Latest, Status (smart per-type — Monitor→OK/ALARM, State→state label, others→—), Last updated (X(end) timestamp), Activity (Live/Inactive at 5-min threshold), Events (count from EventStore), Samples, Labels. All 18 demo tags listed (snapshot from `TagRegistry.find(@(t)true)`). Two parallel refresh paths: (a) push-on-write via existing `FastSenseCompanion.scanLiveTagUpdates_` → `markStatusTableDirty_(keys)` when companion is in Live mode, (b) window-owned `RefreshTimer_` (1s fixedSpacing, unique UUID name, BusyMode='drop', self-stop after 2 consecutive tick errors) so the table refreshes regardless of companion's IsLive — addresses user feedback that Activity/Last updated must stay correct when companion is idle. Pause/Resume polling toggle freezes both paths (markTagsDirty becomes a no-op while paused; header shows "Last refreshed: HH:MM:SS (paused)"). "Last refreshed" heartbeat label updates every tick. Filter chips mirror TagCatalogPane pattern: Type (Sensor/Monitor/Composite/State/Derived), Criticality (Low/Medium/High/Safety), Activity (Live/Inactive) — multi-toggle, AND-across-groups / OR-within-group; broadened free-text search across Key+Name+Units+Labels. Push-on-write hook in companion stays — both mechanisms run in parallel. Six atomic commits + 1 merge: 01 base class + 11 pure-logic tests; 02 companion wiring + 7 lifecycle tests; 03 Activity column + own timer (+5 logic + 2 lifecycle tests, deviation from "push-on-write only" CONTEXT decision per user); 04 last-refreshed header + chip filters + broader search (+4 logic + 2 lifecycle tests); 05 Pause/Resume polling toggle (+4 lifecycle tests); 06 Events count column (+4 logic + 1 lifecycle test); 07 merge with main (PR #143 toolbar grid conflict). Final test counts post-merge: `test_companion_tag_status_table` 24/24 (pure-logic), `TestTagStatusTableWindow` 16/16 (UI lifecycle), `test_companion_tile_close_buttons` 9/9 (main's new test still PASS), `TestFastSenseCompanion` 64/64 (no regression) = 113/113 total. Verified end-to-end on live industrial-plant demo: 4 MonitorTags showed real event counts (29/32/33/35), 14 others showed 0; Activity flipped Live→Inactive at exactly 5-min boundary via static buildRow_ proof; companion IsLive=0 throughout (window polled itself). Deferred / out-of-scope: (1) polling-scope clarification dismissed by user (heartbeat-only vs. passive-observation vs. only-update-changed-cells — left as-is, table updates all cells every tick); (2) Info button + markdown help — scoped up to a milestone-sized "unified in-app help/wiki" effort, parked as backlog 999.1. | 2026-05-19 | b2ed937, e8a1be5, 43d2d3b, 2a24965, 50d464c, 10df740, 73a3bf1 | Verified | [260519-bs4-implement-a-new-table-view-in-the-compan](./quick/260519-bs4-implement-a-new-table-view-in-the-compan/) |

## Progress Bar
Expand Down
4 changes: 3 additions & 1 deletion demo/industrial_plant/private/buildCoolingPage.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function buildCoolingPage(engine, ctx) %#ok<INUSD>
function buildCoolingPage(engine, ctx)
%BUILDCOOLINGPAGE Populate the Cooling page.
% Raw axes for cooling.flow vs time (demonstrates RawAxesWidget),
% a static table summarising cooling stats, a scatter of in_temp vs
Expand All @@ -18,6 +18,8 @@ function buildCoolingPage(engine, ctx) %#ok<INUSD>
engine.addWidget('fastsense', ...
'Title', 'Cooling Flow', ...
'Tag', coolFlow, ...
'ShowEventMarkers', true, ...
'EventStore', ctx.store, ...
'Thresholds', thFlow, ...
'Description', ['FastSenseWidget | Tag: SensorTag cooling.flow. ' ...
'Lower-threshold line from MonitorTag cooling.flow.low ' ...
Expand Down
2 changes: 2 additions & 0 deletions demo/industrial_plant/private/buildEventsPage.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ function buildEventsPage(engine, ctx)
fsP = FastSenseWidget( ...
'Title', 'Reactor Pressure with Event Markers', ...
'Tag', reactorPress, ...
'ShowEventMarkers', true, ...
'EventStore', ctx.store, ...
'Thresholds', thPress, ...
'Description', ['FastSenseWidget | Tag: SensorTag reactor.pressure. ' ...
'Threshold from MonitorTag reactor.pressure.critical ' ...
Expand Down
6 changes: 5 additions & 1 deletion demo/industrial_plant/private/buildFeedLinePage.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function buildFeedLinePage(engine, ctx) %#ok<INUSD>
function buildFeedLinePage(engine, ctx)
%BUILDFEEDLINEPAGE Populate the Feed Line page.
% FastSense plots for pressure + flow, a status indicator bound to the
% feedline.pressure.high MonitorTag, a bar chart summarising feedline
Expand All @@ -25,6 +25,8 @@ function buildFeedLinePage(engine, ctx) %#ok<INUSD>
fsPress = FastSenseWidget( ...
'Title', 'Feedline Pressure', ...
'Tag', feedPress, ...
'ShowEventMarkers', true, ...
'EventStore', ctx.store, ...
'Thresholds', thFeedPress, ...
'Description', ['FastSenseWidget | Tag: SensorTag feedline.pressure. ' ...
'Threshold line from MonitorTag feedline.pressure.high ' ...
Expand All @@ -33,6 +35,8 @@ function buildFeedLinePage(engine, ctx) %#ok<INUSD>
fsFlow = FastSenseWidget( ...
'Title', 'Feedline Flow', ...
'Tag', feedFlow, ...
'ShowEventMarkers', true, ...
'EventStore', ctx.store, ...
'Description', ['FastSenseWidget | Tag: SensorTag feedline.flow. ' ...
'No MonitorTag defined for this signal.'], ...
'Position', [1 4 16 3]);
Expand Down
4 changes: 4 additions & 0 deletions demo/industrial_plant/private/buildOverviewPage.m
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ function buildOverviewPage(engine, ctx)
engine.addWidget('fastsense', ...
'Title', 'Reactor Pressure (live)', ...
'Tag', reactorPressure, ...
'ShowEventMarkers', true, ...
'EventStore', ctx.store, ...
'Thresholds', thReactorPress, ...
'Description', ['FastSenseWidget | Tag: SensorTag reactor.pressure. ' ...
'Threshold line drawn from MonitorTag ' ...
Expand Down Expand Up @@ -141,6 +143,8 @@ function buildOverviewPage(engine, ctx)
engine.addWidget('fastsense', ...
'Title', 'Feedline Pressure', ...
'Tag', feedlinePressure, ...
'ShowEventMarkers', true, ...
'EventStore', ctx.store, ...
'Thresholds', thFeedPress, ...
'Description', ['FastSenseWidget | Tag: SensorTag feedline.pressure. ' ...
'Threshold line drawn from MonitorTag ' ...
Expand Down
8 changes: 7 additions & 1 deletion demo/industrial_plant/private/buildReactorPage.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function buildReactorPage(engine, ctx) %#ok<INUSD>
function buildReactorPage(engine, ctx)
%BUILDREACTORPAGE Populate the Reactor page.
% Second FastSense instance of reactor.pressure (no detach here, the
% detached one lives on Overview), a fastsense for reactor.temperature,
Expand Down Expand Up @@ -28,6 +28,8 @@ function buildReactorPage(engine, ctx) %#ok<INUSD>
fsP = FastSenseWidget( ...
'Title', 'Reactor Pressure', ...
'Tag', reactorPress, ...
'ShowEventMarkers', true, ...
'EventStore', ctx.store, ...
'Thresholds', thPress, ...
'Description', ['FastSenseWidget | Tag: SensorTag reactor.pressure. ' ...
'Threshold line from MonitorTag reactor.pressure.critical ' ...
Expand All @@ -37,6 +39,8 @@ function buildReactorPage(engine, ctx) %#ok<INUSD>
fsT = FastSenseWidget( ...
'Title', 'Reactor Temperature', ...
'Tag', reactorTemp, ...
'ShowEventMarkers', true, ...
'EventStore', ctx.store, ...
'Thresholds', thTemp, ...
'Description', ['FastSenseWidget | Tag: SensorTag reactor.temperature. ' ...
'Threshold line from MonitorTag reactor.temperature.high ' ...
Expand All @@ -62,6 +66,8 @@ function buildReactorPage(engine, ctx) %#ok<INUSD>
fsRpm = FastSenseWidget( ...
'Title', 'Reactor RPM (live)', ...
'Tag', reactorRpm, ...
'ShowEventMarkers', true, ...
'EventStore', ctx.store, ...
'Description', ['FastSenseWidget | Tag: SensorTag reactor.rpm. ' ...
'Housed in the Advanced collapsible below.'], ...
'Position', [1 1 24 3]);
Expand Down
4 changes: 2 additions & 2 deletions libs/FastSenseCompanion/AdHocPlotEventData.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
%
% Properties (read-only after construction):
% TagKeys - cellstr of selected tag keys
% Mode - char in {'Overlay','LinkedGrid'}
% Mode - char in {'Overlay','LinkedGrid','PerTag'}
%
% See also InspectorPane, FastSenseCompanion, event.EventData.

Expand All @@ -36,7 +36,7 @@
'AdHocPlotEventData: tagKeys{%d} must be char.', i);
end
end
validModes = {'Overlay', 'LinkedGrid'};
validModes = {'Overlay', 'LinkedGrid', 'PerTag'};
if ~ischar(mode) || ~any(strcmp(mode, validModes))
error('FastSenseCompanion:invalidEventData', ...
'AdHocPlotEventData: mode must be one of: %s. Got: ''%s''.', ...
Expand Down
Loading
Loading