Skip to content

Commit 3dddde2

Browse files
HanSur94claude
andcommitted
docs(feature-scout): add ranked feature-idea log (2026-06-28..29)
Local discovery notes from /feature-scout runs: per-run ranked candidates, rationale, dedup decisions, and recorded no-file outcomes. Tracks which enhancement issues (#285-#325) were filed and why, so successive runs move down the list instead of re-proposing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 025d38d commit 3dddde2

2 files changed

Lines changed: 623 additions & 0 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Feature scout — ranked ideas (2026-06-28)
2+
3+
Run context: 69 prior `auto-generated` enhancement issues already filed (#210#283),
4+
heavily covering widget options, event queries, Tag stats/persistence, and engine
5+
time/page navigation. Backlog Phase 999.1 (unified in-app help/wiki system) is the only
6+
roadmap-pending feature and is treated as **roadmap-covered** for dedupe purposes.
7+
8+
Discovery sources mined this run: TODO/FIXME (only vendored sqlite3 — none in app code),
9+
`.planning/ROADMAP.md` + `STATE.md` backlog, code asymmetries across Dashboard widgets,
10+
and the current-milestone headline features (tabs/groups, per-widget info, detached mirrors).
11+
12+
---
13+
14+
## #1 — FILED THIS RUN
15+
16+
### BarChartWidget & HistogramWidget: fixed axis limits for a stable live frame
17+
- **Signal (asymmetry):** ScatterWidget has `XLimits`/`YLimits` (#263) and HeatmapWidget has
18+
`ColorLimits` (#231) — both filed explicitly "for a stable live frame / live color scale."
19+
BarChartWidget and HistogramWidget have no equivalent. Verified in `render()`: both call plain
20+
`bar`/`barh` with no `ylim`/`xlim` clamp, so the value axis (and Histogram's bin/X range)
21+
auto-rescales on every live `DataFcn` refresh → bars and axes visibly jump.
22+
- **Value:** Operators watching a live dashboard get a stable visual frame; bar heights stay
23+
comparable refresh-to-refresh instead of re-normalizing each tick. Direct sibling of two
24+
already-accepted issues.
25+
- **Fit:** toolbox-free; pure MATLAB; backward-compatible (default `[]` = current auto behavior;
26+
`toStruct` omits when empty, exactly like the existing `NumBins`/`EdgeColor` pattern, so legacy
27+
serialized dashboards stay byte-identical); works through the `DashboardWidget` contract.
28+
- **Effort:** S — add property + one `ylim`/`xlim` clamp after the `bar()` call in each widget's
29+
`render()`, plus `toStruct`/`fromStruct` lines.
30+
- **Confidence:** High — established sibling pattern, no dedupe hit.
31+
32+
---
33+
34+
## #2 — next candidate
35+
36+
### ScatterWidget: XLabel / YLabel axis labels
37+
- **Signal (asymmetry):** #253 added `XLabel`/`YLabel` to BarChart & Histogram but **not** Scatter,
38+
which is the most axis-label-natural widget (explicit X-vs-Y sensor pairing).
39+
- **Value:** Medium — clearer scatter axes; today they rely on auto/sensor-derived labels only.
40+
- **Fit:** Clean additive, toolbox-free, backward-compatible.
41+
- **Effort:** S. **Confidence:** Medium-high.
42+
43+
## #3
44+
45+
### DashboardEngine.setTheme(preset): runtime theme switch with live restyle
46+
- **Signal (asymmetry):** `Theme` is a construction-time preset string (`'light'`/`'dark'`) with a
47+
`ThemeCache_`; `applyThemeToChrome` exists but there is no single public call that swaps the
48+
preset AND re-themes all already-rendered widgets at runtime.
49+
- **Value:** Medium — runtime light↔dark toggle for demos/presentations without rebuilding.
50+
- **Fit:** Additive, toolbox-free. **Effort:** M (must fan out restyle across widgets + chrome).
51+
- **Confidence:** Medium.
52+
53+
## Run 2 (later 2026-06-28) — updates
54+
55+
- **#285 filed** (run 1) — now known ground.
56+
- **#286** appeared from a parallel loop run — `IconCardWidget: trend-direction arrow (ShowTrend)`. Noted.
57+
- **#2 (Scatter XLabel/YLabel) — DROPPED.** Verified `ScatterWidget.render` already auto-labels both
58+
axes from the bound sensors via `axisLabelForSensor_` (lines 100–105). An explicit override would be
59+
marginal, not a real gap. Removed from the active list.
60+
- **FILED THIS RUN: #3 promoted →** `DashboardEngine.setTheme(preset)` runtime theme switch (see below).
61+
62+
### FILED RUN 2 — DashboardEngine.setTheme(preset): runtime theme switch with live restyle
63+
- **Signal (code asymmetry):** `Theme` is a public property (`'light'`/`'dark'`, default `'light'`), and the
64+
machinery exists — `getCachedTheme()` rebuilds `ThemeCache_` when the preset string changes,
65+
`applyThemeToChrome()` restyles the figure/chrome, `rerenderWidgets()` re-renders widgets with the new
66+
theme. But there is **no single public call** that ties them together: `applyThemeToChrome`'s own doc
67+
says "Widget panels are NOT touched here — call rerenderWidgets() after." So switching theme after
68+
render today requires manually chaining `d.Theme=…; d.applyThemeToChrome(); d.rerenderWidgets();`
69+
(and detached mirrors aren't re-themed at all).
70+
- **Value:** Medium — one-call runtime light↔dark for demos/screenshots and control-room night mode,
71+
without rebuilding the dashboard. No dedupe hit.
72+
- **Fit:** Additive, toolbox-free, backward-compatible (no serialization change).
73+
- **Effort:** S–M (`DashboardEngine.m`; validate preset, set `Theme`, restyle chrome + rerender + re-theme
74+
any `DetachedMirrors`; guard live mode). **Confidence:** High.
75+
76+
## Run 3 (later 2026-06-28) — updates
77+
78+
- **#287 filed** (run 2) — now known ground.
79+
- **#288** appeared from a parallel loop run — `Tag: exportCsv(path)`. Export vein now taken
80+
(joins #210 dashboard export, #220 event CSV, #272 PlantLogStore CSV). Noted.
81+
- **FILED THIS RUN:** `DashboardEngine.refresh()` — public one-shot data refresh (see below).
82+
83+
### FILED RUN 3 — DashboardEngine.refresh(): public one-shot data refresh (no live timer)
84+
- **Signal (code asymmetry):** the per-widget data-refresh loop (mark dirty → `w.update()` for
85+
FastSenseWidget / `w.refresh()` otherwise, with realized/visible guards + detached-mirror tick) lives
86+
entirely **inside the private `onLiveTick`**, driven by the live timer. The public surface offers only
87+
`markAllDirty()` (flags dirty, no redraw), `rerenderWidgets()` (full tear-down + rebuild — loses zoom/
88+
state, heavy), and `startLive`/`stopLive` (continuous polling). There is **no lightweight public call
89+
to pull latest data into all widgets and redraw exactly once.**
90+
- **Value:** Scripts that load/append data (re-run a batch analysis, append to a Tag/DataStore, swap the
91+
backing `.mat`) and want to update the dashboard **on demand once** must today either run continuous
92+
live mode (timer churn) or `rerenderWidgets()` (rebuild). A `d.refresh()` does one live-tick's worth of
93+
data update without the timer or the rebuild. No dedupe hit (distinct from #248 `setLiveInterval`).
94+
- **Fit:** toolbox-free; additive; backward-compatible (no serialization change); works through the
95+
existing `DashboardWidget.refresh()` / `FastSenseWidget.update()` contract.
96+
- **Effort:** S–M — extract the existing `onLiveTick` dirty→refresh loop into a private helper
97+
(`refreshWidgets_`) and expose a public `refresh()` wrapper (active page + detached mirrors; no-op
98+
before render). Mostly a refactor of code that already exists. **Confidence:** High.
99+
100+
## Run 4 (later 2026-06-28) — updates
101+
102+
- **#289 filed** (run 3) — now known ground.
103+
- **#290** appeared from a parallel loop run — `FastSenseWidget: forward DownsampleMethod/DownsampleFactor`
104+
(joins the FastSenseWidget-forwarding vein: #216, #227, #237, #290). Noted.
105+
- **Checked & cleared:** `TagRegistry` is NOT an add-only collection — it already has `unregister`,
106+
`clear`, `find`, `findByLabel`, `findByKind`, `list`, `printTable`, `viewer`. No gap there.
107+
- **FILED THIS RUN:** `TagRegistry.toStructs()` — catalog export, inverse of `loadFromStructs` (see below).
108+
109+
### FILED RUN 4 — TagRegistry.toStructs(): serialize the whole catalog (parity with loadFromStructs)
110+
- **Signal (round-trip asymmetry):** `TagRegistry.loadFromStructs(structs)` does a full two-phase
111+
**import** of an entire catalog (kind-dispatch via `instantiateByKind`, duplicate detection, cross-ref
112+
resolution). Every Tag already implements `toStruct` and it emits the `kind`/`key` fields that
113+
`loadFromStructs` consumes (verified in `SensorTag.toStruct`). But there is **no registry-level
114+
`toStructs()` / export** — the outbound half of the round-trip is missing. To persist a catalog today
115+
you must manually `find(@(t)true)` and call `toStruct` on each tag yourself.
116+
- **Value:** Catalog persistence — the registry is an in-memory singleton with no built-in way to save
117+
the catalog *definitions* and reload them across sessions. A `structs = TagRegistry.toStructs()` (the
118+
exact inverse of `loadFromStructs`), optionally wrapped by `saveToFile(path)` / `loadFromFile(path)`
119+
(JSON via `jsonencode`), lets engineers maintain a standard tag set on disk. Distinct from #288
120+
(per-tag resolved *data* CSV — values, not definitions).
121+
- **Fit:** toolbox-free; additive; backward-compatible; pure MATLAB/Octave. Each Tag's `toStruct`
122+
already produces `loadFromStructs`-ready output, so `toStructs` = iterate `catalog()` and collect.
123+
- **Effort:** S (`TagRegistry.m`; one static `toStructs()` + thin optional file wrappers).
124+
**Confidence:** High.
125+
126+
### Recorded (NOT filed) — widget click / drill-down callback
127+
- **Signal (domain gap / asymmetry):** No dashboard widget exposes a user click/selection callback
128+
(`grep` for `ButtonDownFcn`/`OnClick`/`Callback` across `libs/Dashboard/*Widget.m` finds only
129+
`GroupWidget.ReflowCallback`, injected internally). A sensor engineer can't click a bar/point/cell to
130+
drill into detail.
131+
- **Value:** High (interactive drill-down is a real analysis workflow).
132+
- **Why not filed:** **Needs an architecture decision** — a base-class hit-testing/callback mechanism plus
133+
per-widget wiring. L effort, design-heavy, no concrete prior signal. Better as a planned phase than a
134+
crisp single auto-issue. Parked for human triage.
135+
136+
---
137+
138+
## #4 — NOT FILED (roadmap-covered)
139+
140+
### Per-widget Markdown info (render `Description` via MarkdownRenderer)
141+
- **Signal (asymmetry):** `DashboardEngine.showInfo` renders its `InfoFile` as rich Markdown via
142+
`MarkdownRenderer`; the per-widget info popup (`DashboardLayout.openInfoPopup`) shows
143+
`widget.Description` as plain text in a read-only `uicontrol` edit box. The headline
144+
"per-widget info tooltips" feature is therefore plain-text-only.
145+
- **Value:** High. **Fit:** Perfect (reuses existing `MarkdownRenderer`). **Effort:** S.
146+
- **Why not filed:** Backlog **Phase 999.1** (unified in-app help/wiki system) explicitly scopes
147+
"every pane/widget … render markdown … reusing `MarkdownRenderer`." Treated as roadmap-covered
148+
per the dedupe guardrail. Revisit only if 999.1 is descoped/declined.

0 commit comments

Comments
 (0)