Skip to content

feat(theme): clean-modern light mode across theme surfaces#375

Open
HanSur94 wants to merge 22 commits into
mainfrom
claude/light-mode-design-d00ff7
Open

feat(theme): clean-modern light mode across theme surfaces#375
HanSur94 wants to merge 22 commits into
mainfrom
claude/light-mode-design-d00ff7

Conversation

@HanSur94

@HanSur94 HanSur94 commented Jul 8, 2026

Copy link
Copy Markdown
Owner

What

Redesigns the FastSense light theme with a clean-modern identity — designed in Pencil, then translated 1:1 into the existing MATLAB theme structs. Crisp white widget surfaces on a soft neutral-gray canvas (#F5F6F8), blue #2563EB drag/drop accent, hairline cool borders, and a crisp semantic green/amber/red status trio tuned for readability on white.

Every value maps to a flat-render primitive (solid fills, 1px borders, solid arcs, native dashed threshold) — no gradients, shadows, blur, or transparency stacks — so the theme stays cheap for MATLAB to draw.

Changes

  • libs/FastSense/FastSenseTheme.m — rewrite the 'light'/legacy-alias preset: Background [0.961 0.965 0.973], ForegroundColor [0.208 0.255 0.333], GridColor [0.914 0.929 0.949], GridAlpha 0.7, ThresholdColor [0.937 0.267 0.267]. LineColorOrder kept 'muted'. Dark preset untouched.
  • libs/Dashboard/DashboardTheme.m — rewrite the light branch (surfaces, hairline borders, blue accent, cool group/tab backgrounds). StatusOk/Warn/Alarm made per-preset with a guarded (if ~isfield) shared fallback, so light gets its crisp trio while dark keeps its original values.
  • CompanionTheme.m — no edit; Accent and LineColors derive from DashboardTheme, so the light palette flows through automatically.
  • Tests — update TestTheme (3) and TestFastSenseTheme (1) light Background assertions to the new value.

Verification (live MATLAB session)

  • Light values match the approved Pencil palette, field by field.
  • Dark preset byte-identical: isequaln == 1 for both FastSenseTheme('dark') and DashboardTheme('dark') vs a pre-change baseline snapshot.
  • Legacy aliases (default/ocean/scientific/industrial) resolve to light.
  • Companion inherits: Accent == DragHandleColor == [0.145 0.388 0.922].
  • Theme suites green: TestTheme 12/12, TestDashboardTheme 6/6, TestFastSenseTheme 11/11, TestStatusWidget 11/11, companion theme-walker 36/36.
  • Rendered example_dashboard_all_widgets (18 widgets) — figure background confirmed [0.961 0.965 0.973].

Backward compatibility

No field renamed or removed; no API change; legacy preset aliases intact; dark preset unchanged. Existing dashboards, scripts, and serialized themes keep working.

🤖 Generated with Claude Code

HanSur94 and others added 22 commits July 8, 2026 22:35
Redesign the FastSense 'light' preset with a clean-modern identity:
crisp white widget surfaces on a soft neutral-gray canvas, blue #2563EB
drag/drop accent, hairline cool borders, and a crisp semantic green/amber/red
status trio tuned for readability on white. Palette approved via a Pencil
mockup and mapped 1:1 to flat-render primitives (fills, 1px borders, solid
arcs) so it stays cheap to draw in MATLAB.

- FastSenseTheme.m: rewrite the light/legacy-alias preset (canvas bg, cool
  low-contrast grid at alpha 0.7, semantic-red threshold). 'muted' line
  palette unchanged.
- DashboardTheme.m: rewrite the light branch surfaces/borders/accent; make
  StatusOk/Warn/Alarm per-preset (guarded shared fallback) so light gets its
  own crisp trio while dark stays byte-identical.
- CompanionTheme inherits automatically (Accent<-DragHandle, LineColors<-muted).
- Update TestTheme/TestFastSenseTheme light Background assertions to the new value.

Verified in MATLAB: light values match the approved palette, dark preset
output isequaln to pre-change baseline, legacy aliases resolve to light,
companion inherits the accent. Theme suites green (TestTheme 12, DashboardTheme
6, FastSenseTheme 11, StatusWidget 11, companion theme-walker 36).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Give the dashboard grid inter-widget gutters + outer padding so widgets
read as separated cards on the canvas — the first, cheapest slice of the
approved Pencil redesign.

DashboardLayout.computePosition already consumes GapH/GapV/Padding and
allocatePanels already draws a hairline line-border per cell; they just
defaulted to 0 (flush grid). This wires tasteful values in via the theme:

- DashboardTheme.m: add additive shared fields WidgetGapH (0.006),
  WidgetGapV (0.010), DashboardPad ([0.008 0.010 0.008 0.010], normalized
  [L B R T]). Both presets; no field renamed/removed.
- DashboardEngine.m: before allocatePanels, set Layout.GapH/GapV/Padding
  from the theme, isfield-guarded so older serialized themes still get the
  refreshed spacing rather than a flush grid.
- DashboardLayout.m unchanged (already consumes the knobs).

Verified in MATLAB: example_dashboard_all_widgets (light, 18 widgets)
renders with visible gutters + edge padding, widgets read as separated
cards, no overlap or scroll breakage. Suites green: TestDashboardLayout
8/8, TestDashboardEngine 18/18, TestDashboardTheme 6/6. TestDashboardDetach
testDetachButtonInjected fails identically on the clean baseline (confirmed
pre-existing, not a regression; tracked separately).

Backward-compatible: additive theme fields only; dashboards built with a
directly-constructed DashboardLayout (no theme) keep Gap=0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
De-clutter the dashboard by suppressing the redundant MATLAB axes
exploration toolbar (download/brush/datatips/pan/zoom/home) that renders on
every plot widget. The dashboard already provides its own controls (widget
button bar + time navigator), so the per-axes toolbar is pure visual noise —
the approved Pencil redesign removes it.

- DashboardLayout.realizeWidget: after each widget renders, call a new
  private static helper hideAxesToolbars_(widget.hCellPanel). Central, so it
  covers all 14 axes-bearing widget types and re-applies on scroll-realize.
- hideAxesToolbars_: findall axes under the panel, set Toolbar.Visible='off',
  per-axes try/catch so a toolbar-less/stale axes never aborts realize.

Scope: axes-toolbar suppression only. The X V A L axis-toggle + ^/+ button
strip consolidation is deferred to its own phase (interaction-sensitive;
hover-hiding fights uifigure primitives).

Verified in MATLAB: example_dashboard_all_widgets (light, 18 widgets) — the
floating toolbars are gone from every visible plot (before/after exportapp
compare); the 3 residual 'visible' toolbars are on Visible='off' overlay axes
(render nothing). Suites green: TestDashboardLayout 8/8, TestDashboardEngine
18/18, TestScatterWidget 5/5, TestHeatmapWidget 5/5. No functional regression
(button bar, detach, zoom-via-scroll unaffected); standalone FastSense plots
untouched (helper only runs in the dashboard realize path).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Widget titles no longer overlap their plots. Chart widgets used to create a
full-panel axes then call title(ax, obj.Title), which floated text just above
the plot box with a ~10% margin — so 'Machine Mode', 'Violation Count by
Sensor', 'Temperature Distribution', 'Temp vs Pressure' collided with the plot
/ clipped at the panel top.

- DashboardWidget: new protected drawPanelTitle_(parentPanel, theme) renders
  obj.Title as a bold header band (a sibling uicontrol text, immune to the
  newplot title-clearing that forced the old in-refresh re-apply). Tagged +
  self-deleting so repeated render() calls never stack duplicates.
- BarChart/Heatmap/Histogram/Scatter/EventTimeline: draw the header via the
  helper in render(); drop the axes top to make room; remove the in-axes
  title() calls (both the render and the newplot re-apply sites in BarChart/
  Histogram).

Band sized to ~18% so it holds the font even in short dashboard cells (the
first cut used 9px, smaller than the 15px glyphs, so the text clipped to
nothing — fixed).

Verified in MATLAB: example_dashboard_all_widgets — the four main chart titles
now sit in clean headers above their plots, no overlap/clip (before/after
exportapp). Suites green: TestBarChartWidget 5/5, TestHistogramWidget 6/6,
TestScatterWidget 5/5, TestHeatmapWidget 5/5, TestDashboardEngine 18/18.

Known limitation: the 1-row EventTimeline is too short to show its header in
this example; deferred (needs a min-height or inline title for degenerate
cells). FastSenseWidget's core-drawn title left as-is (reads fine).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NumberWidget gets a proper card when the cell has real height: title
top-left (muted), a big left-aligned value with an inline unit, and a trend
indicator top-right in the accent color — matching the Pencil redesign.

Adaptive by design: dashboard KPI cells are often only a couple of grid rows
(an ~11px content panel in the all-widgets example), far too short for a
stacked card — a first pass there clipped the value/title to nothing (font >
band height). So render() branches on panel pixel height: >=55px uses the
card, otherwise the proven compact single-row layout (restored verbatim), so
short cells never regress.

Verified in MATLAB: main example KPI cells fall back to compact and still show
their values (72.9 / 56); a tall [1 1 8 8] number widget renders the card —
value fs=30 in a 282px box (no clip), title/unit/trend laid out cleanly.
Suites green: TestNumberWidget 12/12, TestDashboardEngine 18/18.

relayout_ (clears + re-renders on resize) picks the right branch for the new
size automatically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The clean-modern light theme (PR #375) set TabActiveBg (accent-soft) and
TabInactiveBg to near-identical luminance (delta 0.0027), so active vs
inactive group tabs were visually indistinguishable — TestGroupWidget/
testTabContrastAllThemes failed for the light preset and its legacy aliases.
Darken TabInactiveBg to [0.850 0.870 0.900] so the accent-soft active tab
clearly stands out (>=0.05 luminance delta). Dark preset unchanged.

Caught by a post-Phase-4 confidence sweep; regression predates the UI-refresh
phases (introduced with the light palette). TestGroupWidget now 36/36.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MultiStatusWidget derived a dot's color by calling tag.valueAt(now) and
treating value >= 0.5 as alarm. That 0/1 mapping is only valid for
monitor-kind tags (binary alarm signals). A plain SensorTag returns its raw
reading (e.g. 72.9 °F), which is always >= 0.5 — so every sensor dot rendered
alarm-red regardless of its actual state (visible in example_dashboard_all_widgets:
Temperature/Pressure/Flow all red despite 0 violations on Pressure/Flow).

Gate the >= 0.5 -> alarm mapping on monitor-kind in both color paths
(deriveColor bare-tag branch + deriveColorFromTag_), via tag.getKind()=='monitor'
(polymorphic, per Pitfall 1 — no isa-on-subclass). Non-monitor tags with no
threshold now fall to the default OK color.

Verified in MATLAB: example MultiStatus dots now OK-green x5, alarm-red x0
(were all red). Monitor-kind alarm behavior preserved: TestMultiStatusWidgetTag
7/7, test_multistatus_monitortag_bare 4/4. (Pre-existing unrelated failure:
test_multistatus_widget_tag references the removed 'Threshold' class.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hide the 14 native uicontrol/uipanel toolbar handles (Visible=off, never
deleted) and paint a custom axes+patch visual layer over obj.hPanel —
brand square + bold name (left, click-to-rename via inputdlg), flat
bordered action buttons/toggles, and a last-update label (right) — for
the light-mode redesign. Every click routes to the SAME existing
handler; the three toggles flip the hidden control's Value before
calling the on*Toggle method with the hidden handle, keeping
DashboardEngine/DashboardConfigDialog reads and existing tests working
unchanged. Cheap-render only; all colors/sizes from the theme struct.
Add paintToggleActive_ helper and wire it into setLiveActiveIndicator
(green/StatusOkColor), setFollowActiveIndicator and
setEventsActiveIndicator (blue/InfoColor) so the custom Live/Follow/
Events pills reflect active state — while preserving every existing
hidden *Panel HighlightColor + hEventsBtn String/Value behavior the
must-pass test suites assert. setLastUpdateTime now dual-writes both
the hidden hLastUpdate and the custom hLastUpdateText_ from one
computed timestamp string. Constructor primes the three custom pills
from the hidden controls' initial Values (Events starts active).
Token contract (Pencil var ↔ hex ↔ MATLAB RGB triplet ↔ theme field),
cheap-render budget, per-surface geometry (plot/dashboard/companion) mapped
to real MATLAB mechanisms, and redline gap list. Backs the light-mode work
on this branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dline d)

Compatibility-shim: 14 native toolbar handles hidden (Visible='off', never
deleted) behind a custom axes+patch layer matching the Pencil mock. Live-verified;
7/8 must-pass suites green, 1 failure proven pre-existing via A/B.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…le=off

- Add 'Visible','off' to the 4 folded creation sites: addPlantLogToggle (L),
  addCrosshairLinkToggle (X), addYLimitButton_ (V+A shared helper), addCreateEventButton (+)
- Buttons remain fully alive (Tag/Position/Callback/TooltipString/bg/UserData unchanged)
- addInfoIcon and addDetachButton left untouched (Info/Detach stay visible)
- MISS_HIT mh_style + mh_lint clean
- New addOverflowMenu_ (private) creates the single ... OverflowMenuButton,
  guarded to fold-eligible widgets only (>=1 of the 5 folded buttons present)
- New showOverflowMenu_ (private) rebuilds a fresh OverflowContextMenu on
  every click, routing items to the existing action methods
  (onCrosshairLinkTogglePressed_ / onYLimitButtonClicked_ /
  onPlantLogTogglePressed_ / invokeCreateEventCallback_) with live
  Checked/label state
- Wired into realizeWidget + the 3 callback-driven rebuild methods
  (addCrosshairLinkToggle, addPlantLogToggle, addYLimitButtons_), each
  guarded with try/catch -> DashboardLayout:overflowMenuFailed
- Theme tokens only (ToolbarBackground/ToolbarFontColor); glyph is
  char(8230) for Octave/MISS_HIT safety
- MISS_HIT mh_style + mh_lint clean
- Hoist xLink so it is computed unconditionally (was previously only
  assigned inside the CrosshairLinkButton presence check), reused by both
  the CrosshairLinkButton anchor and the new overflow anchor
- OverflowMenuButton lands in the leftmost folded slot: xLink ->
  xVisible -> xAll fallback, matching whichever folded buttons are present
- No change to how Detach/Create/Info/PlantLog/V/A/CrosshairLink anchor
- MISS_HIT mh_style + mh_lint clean
…ne e)

Fold X/V/A/L/+ behind a single '…' overflow menu via the hide-not-delete shim;
Info/Detach stay visible. Live-verified; isolated chrome suites green; two failures
(plant-log toggle multi-suite ordering + companion tag-registry) proven pre-existing
via A/B against base. Marks redlines d+e DONE in the design spec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ign-d00ff7

# Conflicts:
#	.planning/STATE.md
#	libs/Dashboard/BarChartWidget.m
#	libs/Dashboard/HistogramWidget.m
@codecov

codecov Bot commented Jul 12, 2026

Copy link
Copy Markdown

@HanSur94

Copy link
Copy Markdown
Owner Author

Re: the codecov/patch 0% — these are MATLAB dashboard UI/render files (DashboardToolbar, DashboardLayout, NumberWidget, DashboardTheme, …). Their tests did run and pass (MATLAB Tests (Dashboard) ✅), but they exercise the code by rendering figures, and headless MATLAB coverage doesn't instrument render-path lines — so those lines report as "missing" even though they're tested. That's why the patch shows 0%.

This check is advisory / non-blocking on this repo (the main branch is unprotected), and prior render-heavy UI PRs merged with the same 0% patch (e.g. #370 "dashboard dark-mode parity", #367 "dashboard fixes"). The new toolbar / overflow-menu / KPI-card code is verified by the passing dashboard suites plus live-render + screenshot checks, so no code change is warranted for this comment.

If you'd rather this stop red-flagging UI PRs going forward, a small codecov.yml marking patch as informational: true (or ignoring the render-only UI files) would do it — happy to add that on request rather than change the repo's coverage policy unprompted.

🤖 Addressed by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant