Skip to content

Release v0.1.0#96

Merged
meduzapat merged 14 commits into
masterfrom
development
Jul 8, 2026
Merged

Release v0.1.0#96
meduzapat merged 14 commits into
masterfrom
development

Conversation

@meduzapat

Copy link
Copy Markdown
Owner

First tagged release of LEDSpicer UI — the visual editor for the LEDSpicer
platform.

Highlights

  • Visual cabinet/layout editor — in-place element editing, snap-to-grid, drag-and-drop, and persisted window/dialog geometry.
  • Hardware configuration — devices, elements, and groups via guided dialogs.
  • Profiles, animations & transitions — full authoring and management.
  • Input & restrictor mapping — including a restrictor Test button that drives the rotator binary, and a restrictor-only test mode.
  • Live daemon integration — real IPC for layout/light test commands, behind a central test-readiness gate, with configurable test-light timeout.
  • Project migration tool — migrates older projects and reacts to read-only project configs.
  • Packaging & docs — Ubuntu PPA, Arch AUR, Fedora COPR, one-line installer; bundled UI assets compiled into the binary as a GResource; new theme/CSS work.

Notes

  • Includes a range of stability fixes (project load across modes, save validation, layout bug fixes, non-RGB hardware crash fixes).

Built with GTKmm 3 + tinyxml2.

meduzapat added 14 commits June 21, 2026 23:58
Task-Url: #60

* **Manual Connection Toggle:** Added a mode-driven "Connect" toggle to
the top bar that adapts based on the environment (hidden in Portable,
disabled in Local, enabled in Interactive).
* **Isolated Testing Configuration:** Configured the handler to build a
temporary, isolated test config and a hidden `__ledspicerui_test__`
profile, keeping testing completely independent of standard profiles.
* **Daemon Takeover & Clean Restore:** Implemented a single-step
discovery process that captures running daemon states. It gracefully
stops the daemon via `systemctl` or `SIGTERM`, and safely restores it
afterward using a detached launch (`setsid`) so it remains completely
independent of the UI lifecycle.
* **Targeted Hardware Testing:** Implemented generic command sending
where UI elements map directly to hardware actions. Strips now pack
evenly into display cells, and interacting with a segment cleanly lights
or clears the entire block.
* **Context-Aware UI Controls:** Restricted element testing so it is
only available while actively connected, and automatically deactivates
any active element if a disconnect occurs.
* **Visual Transition Overlay:** Integrated a modal, borderless busy
dialog with an auto-starting spinner and label that pumps the main loop
during the brief connection/disconnection window.
* **Flashed Debug Feedback:** Added a non persistent "Verbose UI
Debugging" option in Settings that routes semantic test actions through
a new, fast-fading `Debug` severity level.
* **Terminal & UI Cleanups:** Removed dead observers, silenced redundant
console noise, and refined the spacing and margins of the Settings
layout.
60: Real daemon IPC for layout test commands
Design decision:
- Moved the testing check from individual components into a single
central gate: command() checks readiness before sending.
- Component UI-disabling remains separate and is purely visual; message
validation happens at the central gate.

DaemonHandler:
- Added a public std::function<bool()> isReady predicate, set at
initialization.
- command() returns false immediately if isReady() evaluates to false.
- Made isRunning() private; disconnect() now skips the killall command
when no daemon is running (removing the "no process found" stderr
message).

MainWindow:
- Added ensureDaemonReady(): returns false if offline; if the
configuration is stale, it reconnects and cancels the current test;
returns true when online and fresh. This is injected into DaemonHandler
at initialization.
- Built the test sandbox after settings load (once the mode is known)
rather than in the constructor—this fixes the bug where connecting did
nothing until Settings had been opened at least once.
- The refresh process pauses consumers before relaunching and resumes
them on success; it drops the connection on failure. Consumers are
disabled before the loop is pumped, eliminating the need for a
re-entrancy guard.

Layout / LayoutElement:
- Removed all reconnection and stale logic (ensureTestable, reconnect),
which is now handled by MainWindow.
- Gating simplified to a basic testing flag for visual management;
removed the unused Settings dependency.
- Added LayoutElement::stopTesting() to cancel active tile lighting and
timers when paused.
- lightTarget and clearTarget now return the send result, ensuring a
strip group stops if testing is paused mid-group.
- Inlined the single-use deactivate() method directly into setTesting().

DaemonSandbox:
- Made baseDir a const variable initialized in the constructor
initializer list.
- Aligned getter documentation with the project style guidelines.

Settings:
- Removed the obsolete setMode() method.

Comments:
- Trimmed the readiness-gate and testing comments to remove overly long
explanations.
Task-Url: #84

Adds a Test affordance to the Restrictor dialog that drives the
`rotator`
binary so users can validate restrictor wiring without the command line.

- The way icons become a single-active toggle group (none allowed,
re-click
  clears, never assumes hardware position). Toggling a way runs
  `rotator -c <sandbox.conf> <player> <joystick> <ways> …` for the
restrictor's
  player mappings.
- Three modes drive the affordance: **portable** (icons informational),
  **local** (disabled hint), **interactive + connected** (live).
- Staging reuses the daemon sandbox + `ensureDaemonReady()`; restrictor
edits
  now stale the staged config so the test re-stages fresh data.
- Multi-interface restrictors with 2+ mappings can scope the test to
selected
  rows (none selected == all).
- Output goes to the status bar: stdout only when "debug hardware test"
is on,
  red on a non-zero exit or stderr.

- `Settings`: `debugDaemon` → `debugHardwareTest`, `daemonStale` →
`configDirty`.
- `Defaults::runCommand`: output-less overload + stderr folded into
output.
- Way tokens aligned to rotator's CLI vocabulary; `Ways` stays in
`Defaults`,
  the way-token map + arg builder live in `DialogRestrictor`.
- `DaemonHandler` readiness gate installed via a setter.

- `RestrictorTest`/`DefaultsTest` updated; full suite green (27/27).
- Verified on hardware.
84: Restrictor dialog: add Test button driving the rotator binary
Task-Url: #85

The test toggle was gated on LED elements, meaning a restrictor-only
project (no LED hardware) could never enter test mode, leaving the
rotator test unreachable.

Reframe the single toggle as a shared sandbox configuration with two
zones: the daemon zone (devices, layout test) and the restrictor zone
(rotator test). Test mode is available when either zone contains data;
staging is always performed, while ledspicerd launches only when devices
are present.

- updateDaemonControls: Eligibility is now based on (devices and port)
OR restrictors. The port requirement applies exclusively to the daemon
zone.
- launchDaemon: Always stage the sandbox configuration; connect the
daemon only when devices exist.
- connectDaemon/reconnectDaemon: Drive layout consumers and the "Daemon
connected" status exclusively for the daemon zone.
- Relabel the toggle tooltip and status messages to remove
daemon-specific wording.
85: Test mode for restrictor-only configs
than it was created in

Task-Url: #88

A project saved under one mode (Portable vs Local) resolved its
ledspicer.conf to the other mode's location on reload, so ConfigFile
threw and openProject silently wiped the UI — project name in the title,
nothing populated.
- Derive a project's origin from disk (project-local ledspicer.conf =>
  Portable, absent => Local) and refuse to open or save it under a
  mismatching mode, with a clear message and no state change.
- On an unreadable/corrupt config, report and stay idle instead of
  entering an empty, editable project.
- Validate new project names before creating: require a projects
  directory, a unique name, and a non-reserved ".bak" suffix; flag the
  entry inline. Add read-only Defaults::comboBoxHasId for the lookup.
- Stop the color-combo refresh from marking the project dirty with no
  project open.
- Add Settings::getModeLabels for the mismatch messages.
88: Fix empty UI when a project is opened under a different mode
* 89: Save validation rejects valid restrictor-only projects

Task-Url: #89

Refactor DaemonSandbox to use sandboxDir and improve profile handling
Task-Url: #91

Bug fixes:
- Random colors list no longer carries over stale entries between
project loads (boxRandomColors is now wiped unconditionally before
repopulating).
- Settings > General tab: Switch/Label order swapped to match the rest
of the app (Label first, then Switch), and labels given expand=True so
all switches align flush-right within their row.
- Removed 3 dead `.clear()` calls in readConfigFile (redundant with
load()'s own internal clear).
- Removed a stale, contentless TODO comment in DialogLinkEditor.cpp.
- Re-selecting the already-open project now offers a real revert
(discard
  unsaved changes + reload from disk) instead of just an info message,
when the project is dirty.

Save-time "All" group optimization:
- ConfigFile::save() now omits the "All" group from the saved file when
it exactly matches the default device order (position-for-position),
since the daemon auto-builds it identically in that case.
- Strip elements are expanded into their children (not the unlinkable
parent) when building that comparison, matching daemon behavior.
- xmlSection() gained an `allowEmpty` parameter so `<layout>`'s
defaultProfile/defaultProject attributes still get written even when the
groups body ends up empty after "All" is omitted.

Code quality / review follow-ups:
- Moved ProjectFile.hpp / Storage/Element.hpp includes into
ConfigFile.hpp (header carries the deps, not the .cpp).
- Multi-line comments in ConfigFile.cpp switched from stacked `//` to
`/* */`.
- Extracted the strip-expansion and order-comparison logic out of save()
into two named, testable static methods: expandDeviceElements() and
matchesDeviceOrder().

Tests:
- Added 7 unit tests covering both extracted helpers: plain element
order, strip-parent exclusion (children only, correct position), mixed
plain+strip sequencing, and matching cases for matchesDeviceOrder
(identical, reordered, size mismatch, strip children reordered).
- Added tests/mocks/MockDevice.hpp (reusable minimal Parent stand-in for
a device), matching the existing MockData/MockBasicData conventions.
- Verified with a deliberate negative control (temporarily broke
matchesDeviceOrder) to confirm the new tests actually catch the
regression they're meant to catch, not passing vacuously.

Investigated, found not to be bugs:
- "Default profile not selected after loading" — confirmed to be
expected behavior in Local Mode, where projects share one physical
ledspicer.conf file (not a defect).
- Cascade delete on Element removal (dependency system correctly purges
stale Group links, including in "All", when devices are wiped) —
verified correct, no dangling pointers.
Task-Url: #90

- Settings: replace global Portable/Local/Interactive mode with
per-project ConfigSource (embedded config → Project, else System)
resolved from disk at project selection; add hasBinary(),
isRootConfigWritable(), isProjectDirWritable(),
isSystemConfigAvailable(), isPathWritable(). Remove mode-mismatch
guards: opening always reads the correct config location.

- Read-only lock-down: widgets carrying RoLockedConfig/RoLockedProject
Glade markers desensitize (or flip edit→view) via
Defaults::applyWritability, swept over toplevels and re-applied per item
on display. Save skips a read-only source after confirmation; partial
saves reported on the status bar.

- Path choosers freeze while the project is dirty; a binary change
reloads the open project.

- Project dialog: portable toggle for new projects, config-location
conversions (make portable / deploy to system / use system config),
subtitle shows the source and a lock marker when read-only.
@meduzapat meduzapat self-assigned this Jul 8, 2026
@meduzapat meduzapat merged commit ea906e3 into master Jul 8, 2026
1 check passed
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