Skip to content

Commit e8b5a25

Browse files
authored
Merge pull request #110 from HanSur94/claude/reverent-rosalind-9c101e
fix(companion): theme walker covers uilistbox + 7 widget classes
2 parents 0fdabf4 + b250c4b commit e8b5a25

10 files changed

Lines changed: 546 additions & 40 deletions

File tree

.planning/ROADMAP.md

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,21 @@
99
-**v2.0 Tag-Based Domain Model** — Phases 1004-1011 (shipped 2026-04-17)
1010
- 📋 **v2.1 Tag-API Tech Debt Cleanup** — Phases 1012-1017 (carry-forward, parallel — not active)
1111
-**v3.0 FastSense Companion** — Phases 1018-1023 + 1023.1 gap closure (shipped 2026-04-30)
12+
- 🚧 **Pending milestone** — Phases 1025-1028 (promoted from backlog 2026-05-08, awaiting milestone scoping; 1024 closed via quick task 260508-d7k)
1213

1314
## Phases
1415

16+
<details open>
17+
<summary>🚧 Pending milestone (Phases 1025-1028) — promoted from backlog 2026-05-08</summary>
18+
19+
- [x] Phase 1024: Fix companion app dark mode — closed via quick task [260508-d7k](./quick/260508-d7k-fix-companion-app-dark-mode-switching-th/) (2026-05-08)
20+
- [ ] Phase 1025: FastSense hover crosshair + datatip
21+
- [ ] Phase 1026: Dashboard time slider preview
22+
- [ ] Phase 1027: Companion detachable log window
23+
- [ ] Phase 1028: Tag update perf — MEX + SIMD
24+
25+
</details>
26+
1527
<details>
1628
<summary>✅ v1.0 FastSense Advanced Dashboard (Phases 1-9) — SHIPPED 2026-04-03</summary>
1729

@@ -70,10 +82,6 @@ Full details: [milestones/v3.0-ROADMAP.md](milestones/v3.0-ROADMAP.md)
7082

7183
</details>
7284

73-
## Phase Details
74-
75-
(no active milestone — see [milestones/](milestones/) for archived versions)
76-
7785
## Progress
7886

7987
| Phase | Milestone | Plans Complete | Status | Completed |
@@ -101,3 +109,56 @@ Full details: [milestones/v3.0-ROADMAP.md](milestones/v3.0-ROADMAP.md)
101109
| 1022. Ad-Hoc Plot Composer | v3.0 | 3/3 | Complete | 2026-04-30 |
102110
| 1023. Industrial Plant Demo Integration | v3.0 | 2/2 | Complete | 2026-04-30 |
103111
| 1023.1. Cross-Phase Wiring Fixes | v3.0 | gap-closure | Complete | 2026-04-30 |
112+
| 1024. Fix companion app dark mode | pending | quick-task | Complete (via 260508-d7k) | 2026-05-08 |
113+
| 1025. FastSense hover crosshair + datatip | pending | 0/? | Not started ||
114+
| 1026. Dashboard time slider preview | pending | 0/? | Not started ||
115+
| 1027. Companion detachable log window | pending | 0/? | Not started ||
116+
| 1028. Tag update perf — MEX + SIMD | pending | 0/? | Not started ||
117+
118+
## Phase Details (Pending Milestone)
119+
120+
### Phase 1024: Fix companion app dark mode — CLOSED
121+
122+
**Status:** Closed 2026-05-08 via quick task [260508-d7k](./quick/260508-d7k-fix-companion-app-dark-mode-switching-th/).
123+
124+
**Root cause:** `applyThemeToChildren_` walker silently skipped widget classes without an explicit `case`. `uilistbox` (TagCatalogPane Row 7 — the tag list) was the visible casualty.
125+
126+
**Fix:** Added 8 widget cases to the walker (`ListBox`, `TextArea`, `CheckBox`, `NumericEditField`, `StateButton`, `ToggleButton`, `RadioButton`, `ButtonGroup`). Regression test asserts dark→light→dark flip across all classes.
127+
128+
**Promoted from:** Backlog 999.1 (2026-05-08)
129+
130+
### Phase 1025: FastSense hover crosshair + datatip
131+
132+
**Goal:** Add a vertical crosshair line that follows the mouse when hovering over a FastSense plot/widget, with a context datatip window showing the values of all lines at the hovered x position.
133+
134+
**Promoted from:** Backlog 999.2 (2026-05-08)
135+
**Requirements:** TBD
136+
**Plans:** 0 plans
137+
138+
### Phase 1026: Dashboard time slider preview
139+
140+
**Goal:** Fix the lower dashboard time slider so it shows a preview overlay of all graphed plot lines and detected events across the full time range. Currently the slider track is empty — investigate why the preview rendering isn't happening and restore it.
141+
142+
**Promoted from:** Backlog 999.3 (2026-05-08)
143+
**Requirements:** TBD
144+
**Plans:** 0 plans
145+
146+
### Phase 1027: Companion detachable log window
147+
148+
**Goal:** In the FastSense Companion app, make the log panel detachable into its own draggable, resizable window — same pop-out pattern as detachable widgets in the main dashboard.
149+
150+
**Promoted from:** Backlog 999.4 (2026-05-08)
151+
**Requirements:** TBD
152+
**Plans:** 0 plans
153+
154+
### Phase 1028: Tag update perf — MEX + SIMD
155+
156+
**Goal:** Profile and accelerate the tag update path (SensorTag/StateTag/MonitorTag/CompositeTag streaming + recompute). Identify hot spots and replace with C MEX kernels using SIMD (AVX2 / NEON) where it pays off, consistent with existing FastSense MEX patterns.
157+
158+
**Promoted from:** Backlog 999.5 (2026-05-08)
159+
**Requirements:** TBD
160+
**Plans:** 0 plans
161+
162+
## Backlog
163+
164+
(empty — last 5 items promoted to phases 1024-1028 on 2026-05-08)

.planning/STATE.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ gsd_state_version: 1.0
33
milestone: v3.0
44
milestone_name: FastSense Companion
55
status: shipped
6-
last_updated: "2026-05-08T06:38:30.000Z"
7-
last_activity: 2026-05-08 -- Completed quick task 260508-bxh: Gate WebSocket /ws endpoint with same origin policy as HTTP CORS
6+
last_updated: "2026-05-08T07:35:00.000Z"
7+
last_activity: 2026-05-08 -- Completed quick task 260508-d7k: fix companion app dark mode — uilistbox + 7 prophylactic widget classes added to theme walker
88
progress:
99
total_phases: 6
1010
completed_phases: 6
@@ -19,7 +19,7 @@ shipped_at: 2026-04-30
1919

2020
Milestone: v3.0 FastSense Companion — SHIPPED 2026-04-30
2121
Status: Awaiting next milestone (run `/gsd:new-milestone` to scope v3.x or v4.0)
22-
Last activity: 2026-05-08 -- Completed quick task 260508-bxh: Gate WebSocket /ws endpoint with same origin policy as HTTP CORS
22+
Last activity: 2026-05-08 -- Completed quick task 260508-d7k: fix companion app dark mode — uilistbox + 7 prophylactic widget classes added to theme walker
2323

2424
### Quick Tasks Completed
2525

@@ -31,6 +31,7 @@ Last activity: 2026-05-08 -- Completed quick task 260508-bxh: Gate WebSocket /ws
3131
| 260508-b8m | Refresh CLAUDE.md for Tag-based API and add Running MATLAB code section | 2026-05-08 | 90d9c03 || [260508-b8m-refresh-claude-md-for-tag-based-api-and-](./quick/260508-b8m-refresh-claude-md-for-tag-based-api-and-/) |
3232
| 260508-bju | Lock down WebBridge CORS to localhost with env-var override | 2026-05-08 | 518b778 | Verified | [260508-bju-lock-down-webbridge-cors-to-localhost-on](./quick/260508-bju-lock-down-webbridge-cors-to-localhost-on/) |
3333
| 260508-bxh | Gate WebSocket /ws endpoint with same origin policy as HTTP CORS | 2026-05-08 | e1aeebc || [260508-bxh-gate-websocket-ws-endpoint-with-same-ori](./quick/260508-bxh-gate-websocket-ws-endpoint-with-same-ori/) |
34+
| 260508-d7k | Fix companion app dark mode — add uilistbox + 7 widget classes to theme walker | 2026-05-08 | 4472cc2 | Verified | [260508-d7k-fix-companion-app-dark-mode-switching-th](./quick/260508-d7k-fix-companion-app-dark-mode-switching-th/) |
3435

3536
## Progress Bar
3637

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
---
2+
phase: 260508-d7k
3+
plan: 01
4+
subsystem: FastSenseCompanion/applyThemeToChildren_
5+
tags: [theme-walker, dark-mode, uilistbox, companion, tdd]
6+
dependency_graph:
7+
requires: []
8+
provides: [expanded-walker-coverage]
9+
affects: [TagCatalogPane, CompanionSettingsDialog, FastSenseCompanion]
10+
tech_stack:
11+
added: []
12+
patterns: [TDD-red-green, recursive-walker, switch-case-expansion]
13+
key_files:
14+
created:
15+
- tests/test_companion_apply_theme_walker.m
16+
modified:
17+
- libs/FastSenseCompanion/private/applyThemeToChildren_.m
18+
decisions:
19+
- "Button-like StateButton and ToggleButton use WidgetBorderColor (neutral) for BackgroundColor — same rule as the existing Button case — so the active-state repaint owned by each pane's setTheme override is not overwritten by the walker."
20+
- "CheckBox and RadioButton receive FontColor only; no BackgroundColor because these controls inherit their background from the parent container in MATLAB's UIFigure system."
21+
- "ButtonGroup receives BorderColor in addition to BackgroundColor because it renders a visible border like Panel."
22+
- "Prophylactic cases (StateButton, ToggleButton, RadioButton, NumericEditField) are added unconditionally to the walker switch even though some MATLAB versions do not support those constructors — a never-matched switch string is free and future-proofs the walker."
23+
metrics:
24+
duration: "< 5 min"
25+
completed: "2026-05-08"
26+
tasks_completed: 1
27+
files_modified: 2
28+
---
29+
30+
# Phase 260508-d7k Plan 01: Expand Companion theme walker (uilistbox + 7 prophylactic classes) Summary
31+
32+
Expanded `applyThemeToChildren_` to cover `uilistbox` (fixing the reported tag-catalog dark-mode regression) and 7 prophylactic widget classes, with a TDD regression test that asserts the dark->light->dark colour flip.
33+
34+
## What Was Built
35+
36+
### Root Cause
37+
38+
`applyThemeToChildren_` used a `switch class(ch)` block with an `otherwise` silent-skip branch. The `matlab.ui.control.ListBox` class (used by `uilistbox()`) had no explicit `case`, so every theme switch left the tag list (`TagCatalogPane` Row 7) on whatever palette it was constructed with (white by default). The user symptom — "tag catalog stays light when switching to dark" — maps directly to this missing case.
39+
40+
### Widget Class Coverage Audit
41+
42+
| Widget class | Constructor | Status before | Status after |
43+
|---|---|---|---|
44+
| `matlab.ui.container.Panel` | `uipanel` | covered | covered |
45+
| `matlab.ui.container.GridLayout` | `uigridlayout` | covered | covered |
46+
| `matlab.ui.container.ButtonGroup` | `uibuttongroup` | NOT covered | covered (NEW) |
47+
| `matlab.ui.control.Label` | `uilabel` | covered | covered |
48+
| `matlab.ui.control.EditField` | `uieditfield` | covered | covered |
49+
| `matlab.ui.control.DropDown` | `uidropdown` | covered | covered |
50+
| `matlab.ui.control.Spinner` | `uispinner` | covered | covered |
51+
| `matlab.ui.control.Button` | `uibutton` | covered | covered |
52+
| `matlab.ui.control.Table` | `uitable` | covered | covered |
53+
| `matlab.ui.control.ListBox` | `uilistbox` | NOT covered (BUG) | covered (NEW) |
54+
| `matlab.ui.control.TextArea` | `uitextarea` | NOT covered | covered (NEW) |
55+
| `matlab.ui.control.CheckBox` | `uicheckbox` | NOT covered | covered (NEW) |
56+
| `matlab.ui.control.NumericEditField` | `uinumericeditfield` | NOT covered | covered (NEW) |
57+
| `matlab.ui.control.StateButton` | `uistatebutton` | NOT covered | covered (NEW) |
58+
| `matlab.ui.control.ToggleButton` | `uitogglebutton` | NOT covered | covered (NEW) |
59+
| `matlab.ui.control.RadioButton` | `uiradiobutton` | NOT covered | covered (NEW) |
60+
61+
### Files Modified
62+
63+
**`libs/FastSenseCompanion/private/applyThemeToChildren_.m`**
64+
- Added `matlab.ui.container.ButtonGroup` case (recurses into children like Panel/GridLayout)
65+
- Added `matlab.ui.control.ListBox` case: `BackgroundColor = WidgetBackground`, `FontColor = ForegroundColor`
66+
- Added `matlab.ui.control.TextArea` case: same as ListBox
67+
- Added `matlab.ui.control.CheckBox` case: `FontColor = ForegroundColor` only (no BackgroundColor — inherits from parent)
68+
- Added `matlab.ui.control.NumericEditField` case: same as EditField
69+
- Added `matlab.ui.control.StateButton` case: `BackgroundColor = WidgetBorderColor`, `FontColor = ForegroundColor`
70+
- Added `matlab.ui.control.ToggleButton` case: same as StateButton
71+
- Added `matlab.ui.control.RadioButton` case: `FontColor = ForegroundColor` only (no BackgroundColor — inherits from parent)
72+
- Updated function header comment with expanded coverage inventory
73+
74+
**`tests/test_companion_apply_theme_walker.m`**
75+
- New Octave-style function test (MATLAB-only, skips on Octave)
76+
- Builds a hidden `uifigure('Visible','off')` with all covered widget classes
77+
- Prophylactic widgets (`uistatebutton`, `uinumericeditfield`) wrapped in `tryMakeWidget` for version portability
78+
- `uitogglebutton` and `uiradiobutton` parented to separate `uibuttongroup` instances (MATLAB requirement)
79+
- Asserts dark->light->dark colour flip for all available widget classes (18 assertions on current MATLAB version)
80+
- `onCleanup(@() delete(hFig))` prevents figure leak on test failure
81+
- Local `add_companion_private_path()` helper mirrors the pattern in `add_fastsense_private_path.m` with R2025b+ proxy fallback
82+
83+
## TDD Execution
84+
85+
| Step | Commit | Result |
86+
|---|---|---|
87+
| RED: failing test for uilistbox + 7 prophylactic classes | `93312b8` | FAIL: uilistbox BackgroundColor was [1 1 1], expected [0.09 0.13 0.24] |
88+
| GREEN: expand walker cases | `101ab9c` | PASS: All 18 tests passed |
89+
90+
## Deviations from Plan
91+
92+
### Auto-fixed Issues
93+
94+
**1. [Rule 1 - Bug] Wrapped additional widget constructors in tryMakeWidget**
95+
- **Found during:** RED step
96+
- **Issue:** `uistatebutton` and `uinumericeditfield` are not available in the local MATLAB version (Home License). The test crashed at widget construction before reaching the uilistbox assertion.
97+
- **Fix:** Added `tryMakeWidget` wrappers for all prophylactic widgets (`uistatebutton`, `uitextarea`, `uicheckbox`, `uinumericeditfield`, `uitogglebutton`, `uiradiobutton`, `uibuttongroup`). Assertions for unavailable widgets are skipped. `uilistbox`, `uieditfield`, `uilabel`, etc. are constructed directly since they are used in production code today and confirmed available.
98+
- **Files modified:** `tests/test_companion_apply_theme_walker.m`
99+
- **Commit:** included in RED commit `93312b8`
100+
101+
**2. [Rule 1 - Bug] uitogglebutton/uiradiobutton require ButtonGroup parent**
102+
- **Found during:** RED exploration
103+
- **Issue:** `uitogglebutton` and `uiradiobutton` throw "Parent must be a ButtonGroup" when parented to a GridLayout. The plan's try/catch guidance assumed they might be unavailable, but the real constraint is parent type.
104+
- **Fix:** Created dedicated `uibuttongroup(hFig)` instances as parents for each. `hBgToggle` parents `uitogglebutton`; `hBgRadio` parents `uiradiobutton`. Both groups wrapped in `tryMakeWidget` so Octave or versions without these classes still work.
105+
- **Files modified:** `tests/test_companion_apply_theme_walker.m`
106+
- **Commit:** included in RED commit `93312b8`
107+
108+
## Known Drift Risk (Out of Scope)
109+
110+
The two log tables in `FastSenseCompanion.m` (lines 745-749 and 787-789) hardcode the same dark/light RGB pairs that `applyThemeToChildren_` already derives from `theme.DashboardBackground`. This duplication is NOT the source of the reported bug — the walker already overwrites the tables on each theme apply. However, if someone refines the `tableBg` derivation logic inside the walker without updating these construction-time hardcodes, the tables will show the wrong palette briefly during first construction before the first `applyTheme` call.
111+
112+
**Recommended follow-up:** A small DRY-up task to replace the hardcoded pairs at lines 745-749 and 787-789 with `CompanionTheme.get(preset).DashboardBackground`-derived values at construction time. Backlog item suggested.
113+
114+
## Self-Check: PASSED
115+
116+
- `tests/test_companion_apply_theme_walker.m` exists and passes (18 tests).
117+
- `libs/FastSenseCompanion/private/applyThemeToChildren_.m` exists and contains `case 'matlab.ui.control.ListBox'`.
118+
- RED commit `93312b8` confirmed in `git log`.
119+
- GREEN commit `101ab9c` confirmed in `git log`.

examples/simple_live_dashboard.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ function closeAll_(fig)
9494
catch
9595
end
9696
try
97-
if ~isempty(engine) && isvalid(engine) ...
98-
&& ismethod(engine, 'stopLive')
97+
if ~isempty(engine) && isvalid(engine) && ...
98+
ismethod(engine, 'stopLive')
9999
engine.stopLive();
100100
end
101101
catch

libs/FastSenseCompanion/FastSenseCompanion.m

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -511,8 +511,8 @@ function stopLiveMode(obj)
511511
%STOPLIVEMODE Stop the inspector refresh timer (timer object kept for reuse).
512512
if ~obj.IsLive; return; end
513513
try
514-
if ~isempty(obj.LiveTimer_) && isvalid(obj.LiveTimer_) ...
515-
&& strcmp(obj.LiveTimer_.Running, 'on')
514+
if ~isempty(obj.LiveTimer_) && isvalid(obj.LiveTimer_) && ...
515+
strcmp(obj.LiveTimer_.Running, 'on')
516516
stop(obj.LiveTimer_);
517517
end
518518
catch
@@ -932,8 +932,8 @@ function onLiveTick_(obj)
932932
% intentionally NOT refreshed (they would lose selection/scroll).
933933
if ~obj.IsLive || isempty(obj.hFig_) || ~isvalid(obj.hFig_); return; end
934934
try
935-
if ~isempty(obj.InspectorPane_) && isvalid(obj.InspectorPane_) ...
936-
&& ismethod(obj.InspectorPane_, 'refreshLive')
935+
if ~isempty(obj.InspectorPane_) && isvalid(obj.InspectorPane_) && ...
936+
ismethod(obj.InspectorPane_, 'refreshLive')
937937
obj.InspectorPane_.refreshLive();
938938
end
939939
obj.scanLiveTagUpdates_();
@@ -1045,11 +1045,11 @@ function resolveInspectorState_(obj)
10451045
state = 'multitag';
10461046
payload = struct('tags', {tags}, ...
10471047
'tagKeys', {obj.SelectedTagKeys_});
1048-
elseif strcmp(obj.LastInteraction_, 'dashboard') ...
1049-
&& isnumeric(obj.SelectedDashboardIdx_) ...
1050-
&& isscalar(obj.SelectedDashboardIdx_) ...
1051-
&& obj.SelectedDashboardIdx_ > 0 ...
1052-
&& obj.SelectedDashboardIdx_ <= numel(obj.Engines_)
1048+
elseif strcmp(obj.LastInteraction_, 'dashboard') && ...
1049+
isnumeric(obj.SelectedDashboardIdx_) && ...
1050+
isscalar(obj.SelectedDashboardIdx_) && ...
1051+
obj.SelectedDashboardIdx_ > 0 && ...
1052+
obj.SelectedDashboardIdx_ <= numel(obj.Engines_)
10531053
state = 'dashboard';
10541054
payload = struct('dashboard', ...
10551055
obj.Engines_{obj.SelectedDashboardIdx_});

0 commit comments

Comments
 (0)