Skip to content

Modernize the desktop UI: readable cards, light/dark theme, and a °C/°F toggle#62

Open
jgamblin wants to merge 14 commits into
StuckAtPrototype:masterfrom
jgamblin:ui-redesign
Open

Modernize the desktop UI: readable cards, light/dark theme, and a °C/°F toggle#62
jgamblin wants to merge 14 commits into
StuckAtPrototype:masterfrom
jgamblin:ui-redesign

Conversation

@jgamblin

@jgamblin jgamblin commented Jun 30, 2026

Copy link
Copy Markdown

Summary

This refreshes the scripts/aircube_app.py desktop GUI to fix a readability problem and bring the look up to date. No device behavior, serial protocol, or CSV format changes, and there are tests pinning that. The headline fix: the live sensor values were rendered in a near invisible low contrast color (only the color coded VOC value stood out). They are now crisp, high contrast metric cards with per reading accent colors and tabular numerals.

Along the way it adds a light/dark theme (System, Light, or Dark), a Celsius/Fahrenheit toggle, and extracts the UI into a small, testable aircube_ui/ package, plus the unit tests CONTRIBUTING.md asks for.

Before and after

Screenshots are attached just below this line (original UI, new light theme, new dark theme, and the °C/°F toggle). The same data is used in every shot.

before_light after_light after_dark after_dark_fahrenheit

Appearance default

The app defaults to Light, so existing users get the same out of box look as today (no behavior change). Dark, and a System mode that follows the OS light/dark setting, are opt in under View, then Appearance. The choice is remembered with QSettings.

What is preserved (no functional regressions)

  • CSV output is byte identical: same header and columns, still logs both temperature_c and temperature_f regardless of the display unit. This is now locked by a regression test (tests/test_csv_logging.py).
  • Serial protocol and parsing unchanged (parse_json_line and SerialReaderThread moved verbatim).
  • "VOC Level" terminology kept.
  • Port refresh, connect and disconnect, history depth, and CSV logging behave exactly as before.
  • No new runtime dependencies.

What changed

Readability and visuals:

  • Sensor values now use an explicit high contrast color (the old labels had no color set and rendered washed out).
  • New metric card layout: per reading accent stripe, muted label, large value, subtle unit. VOC keeps its color coding plus a band pill (Good, Moderate, Poor, Unhealthy). The band colors are unchanged from the originals, so they still match the firmware LED gradient.
  • Tabular numerals so digits do not shift width as values update.
  • Replaced the hardcoded Windows only Segoe UI font with the platform UI font.

New features:

  • View, then Appearance: System / Light / Dark, persisted with QSettings.
  • View, then Units: Celsius / Fahrenheit (display only, defaults to Celsius), persisted with QSettings.

Refactor:

  • Extracted scripts/aircube_ui/ (theme.py, widgets.py, plotting.py, serial_reader.py) so appearance lives in one place and the pure logic is testable. aircube_app.py is now mostly wiring. It runs the same way (python aircube_app.py from scripts/).

Testing

  • 23 unit and smoke tests pass (cd scripts && python -m pytest), covering the serial parser, the VOC band and °C/°F math, the CSV log format, and a headless main window smoke test. The parser and color math tests are ones CONTRIBUTING.md lists as wanted.
  • Verified python build_exe.py still produces a working standalone binary that bundles the new package. This is the same build that produces the Windows .exe on Releases.
  • Tested on macOS only. The app forces the Qt Fusion style, which keeps the stylesheet rendering consistent across platforms, but I have not been able to verify Windows or Linux directly. A sanity check there, especially a Windows build, would be welcome.

Dependency note

Bumped the PyQt6 floor to >=6.5 because the auto theme uses QStyleHints.colorScheme() (added in 6.5). Tabular numerals use the QFont.setFeature font API (6.7+) and degrade gracefully: they simply do not apply on 6.5 or 6.6, with no error.

Scope

This is one cohesive change (refactor, restyle, and the two toggles). I am happy to split it into separate PRs, for example the refactor and restyle first, then the toggles, if you would prefer smaller reviews.

jgamblin and others added 14 commits June 29, 2026 07:45
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Qt Style Sheets do not support the CSS font-variant-numeric property;
it was ignored and logged a warning on every restyle. Enable tabular
figures via QFont.setFeature(Tag('tnum')) guarded for older PyQt6.

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

Replace the inline SensorDisplay/PlotCanvas with the aircube_ui widgets,
drop the hardcoded Segoe UI font and global stylesheet, and add a View
menu with Appearance (System/Light/Dark) and Units (Celsius/Fahrenheit),
both persisted via QSettings. CSV format and serial handling unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Code review found that MetricCard's value text kept the light-theme color
after switching to dark (invisible on the dark card), because the guard
keyed off the stylesheet string. Track a value-color override flag instead,
so plain values follow the theme while the VOC band color is preserved.
Also theme the CSV path/status labels via the palette rather than hardcoded
hex. Adds regression tests for both behaviors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Labels with a per-widget color stylesheet were picking up the page
background instead of the card surface, leaving mismatched boxes behind
the metric text. Scope the background fill to QMainWindow and keep labels
transparent so they show the surface behind them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The auto light/dark theme relies on QStyleHints.colorScheme() (Qt 6.5+),
so raise the floor from 6.4 to match. Drive the Connect/Disconnect
button's disabled color from the palette instead of a fixed gray.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Locks the header and row layout (both temperature_c and temperature_f)
so the desktop log stays compatible with the other AirCube scripts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Existing users see no change out of the box; System (follow the OS) and
Dark remain available under View > Appearance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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