v2.12.0 — Mobile-ready · installable PWA · pytest + CI
v2.12.0 — Mobile-ready · installable PWA · pytest + CI
A polish-and-foundation release. The web UI now feels native on phones (and installs like one), and the codebase finally has a real test suite + CI to keep regressions out.
Two big themes:
📱 Mobile audit + PWA installability
The web UI got rewritten for phones from the ground up — not "shrunk to fit," properly redesigned for touch and small viewports.
- Every component touched: BLACKOUT button, the 7-tab row, modals, scene/group/cue/chase builders, chat composer, tool panels — all reworked for portrait phones.
- Touch targets ≥ 44px everywhere per Apple HIG. No more thumb-fumbling on dense control rows.
- 16px form inputs to prevent iOS Safari from auto-zooming on focus.
- Safe-area insets respected (notch / home indicator).
- Tabs row scrolls horizontally when 7 tabs don't fit — no more wrapping.
- Full-screen modals on phones, stacked layouts for cue/chase builders.
- Edge-to-edge chat composer with proper keyboard handling.
PWA installability lands too:
/manifest.jsonwith proper icons, theme color, display: standalone./icon.svgserved from the control server (no asset pipeline required)./sw.jsservice worker registered on load.- Apple-specific meta tags (
apple-mobile-web-app-capable, status bar style, etc.) so iOS "Add to Home Screen" gives a chromeless app. - Android Chrome shows the install prompt automatically.
Open http://lights.local:9999 on your phone → Add to Home Screen → it launches like a native app.
✅ Test suite + GitHub Actions CI
After 11 minor releases of "ship and hope," the project finally has automated guardrails.
~180 pytest tests covering pure-function helpers across the v2.3 → v2.11 buildout:
test_time_parser.py— cue list timestamp parsing ("0:32.500","32s","1:23:45", int ms)test_cct.py— Tanner Helland CCT-to-RGB + WWA proportional blendtest_strobe.py— strobe rate → DMX 0..255 mapping, off forms, clampingtest_palette.py—_normalize_palette_valueacross all six accepted shapestest_cue_normalizer.py— scene / chase / action cue shape normalizationtest_misc_helpers.py— direction + run-order enums, fixture ID coercion,_parse_level(0-255/"75%"/"+30")
Sub-second full suite. Tests the contract, not the implementation.
GitHub Actions CI runs three jobs in parallel on every push + PR:
python— pytest matrix on Python 3.11 + 3.12, plusast.parsesyntax check on bothapp.pyandmcp-server/server.py.js— extracts the inline<script>fromtemplates/index.htmland runsnode --checkon it (would have caught the duplicate</style>we hit during the mobile rewrite).html— verifies balanced open/close tags fordiv,script,style,button,selectin the template.
What's NOT covered (yet): Flask routes, the persistent WebSocket loop, AI provider calls, XML workspace mutations, cue list playback engine. All deliberate — integration-test territory for later if they become regression hotspots. See control-server/tests/README.md for the full breakdown and how to add new tests.
Upgrade
./scripts/lightsctl.sh updateThen ./scripts/lightsctl.sh restart and open the web UI on your phone — it should feel like a different app.
What's next
With mobile + PWA + CI in place, the foundation work is done. Next up: integration tests for the Flask layer, more chase patterns, and the visual cue list timeline view.