Skip to content

v5.0.5

Latest

Choose a tag to compare

@jmf-pobox jmf-pobox released this 01 Sep 12:47
· 5 commits to main since this release
a1b27ab

Fixed

  • The test suite no longer publishes a vox.music scene into the operator's live Lux hub (vox-ghqw). tests/test_voxd_daemon.py drives the real VoxDaemon._lifespan to prove the background tasks are wired, and that lifespan composes the music player. It did so as MusicPlayerSubsystem(service, service.changes) — two arguments, leaving the third to default — so the subsystem built VoxLuxClients(), voxd's production identity resolved against whatever luxd was running. Within about 300ms of the scene task starting, its subscription leg asked that factory for a hub and connected. The row the operator kept seeing was this module's own turn_on(style="techno", vibe="calm", name="mix", ...): AlbumTags.display_title title-cases mix to Mix, the Genre cell is the style, and the test's blocking producer leaves Tracks at 0. Scenes are owned per connection, so the test's scene did not overwrite the real one — it stood a second "Music" frame beside it, which is why the widget appeared to flip between two states and why restarting voxd, the hub, or the display never helped. The client factory is now a VoxDaemon constructor argument, so whoever composes the daemon decides which luxd it reaches; a regression test asserts the lifespan asks the injected factory for a leg, checking the call count rather than trusting an exception, because both legs run inside tasks whose exceptions are discarded when the lifespan cancels them. This is the same defect class as the hook tests spawning real vox-panels, one layer down in the daemon, which is why fixing those did not stop it.

Changed

  • voxd's boot sequence and its background-task set are their own objects. daemon.py had grown to 336 lines — above the 300-line standard — by holding three jobs: the daemon class, the ordered startup (crash sinks, then logging, then keys, then compose), and the five background tasks with their hand-ordered teardown. The startup moves to DaemonBoot, where the order is the content: each step exists to make the next one's failures visible, which read as CLI plumbing while it sat beside the class it builds. The tasks move to BackgroundTasks, which cancels in reverse start order so the teardown sequence cannot drift out of step with the start sequence the way two hand-maintained lists do — kept as two groups, because service.shutdown() has to land between the producers stopping and the consumer that drains them stopping. daemon.py is now 290 lines with one fewer internal dependency (efferent coupling 13 → 12). DaemonConfig also gained for_user() and user_log_dir(), so callers name the intent instead of reaching into the module-private _run_dir/_config_dir/_log_dir triple; that removed three reportPrivateUsage errors rather than silencing them with a file-level pragma.