Skip to content

TMU 0.4.0: move shared playback and state into a per-user daemon #108

Description

@txchen

Problem Statement

TMU currently binds the TUI, App Coordinator, Player, Download Pipeline, persistence, and external-tool processes to one terminal process. Closing or losing that terminal stops playback and downloads. Running TMU in multiple terminals creates independent App State and mpv processes that compete over shared persistence, the YouTube Cache, Background Sounds, and user expectations; atomic file replacement prevents partial JSON but does not prevent last-writer-wins state loss. Users with many terminal tabs also have no reliable way to reconnect to the TMU instance that owns playback.

TMU 0.4.0 needs one durable per-user owner for shared application state and work, with any number of short-lived terminal interfaces connected to it. Multiple terminals must observe and control the same trustworthy state without one terminal's navigation, exit, slowness, or failure disrupting another.

Solution

TMU 0.4.0 will replace the released single-process runtime with one long-lived, explicitly stopped TMU Daemon per user and one or more TUI Clients. Running tmu connects to the existing daemon or starts it on demand under a single-winner startup lock. The daemon owns shared App State, the Playing Playlist, Player/mpv, Download Pipeline, Providers, Cache mutations, Background Sounds operations, persistence, and Shared Command serialization. Each TUI Client owns only terminal-local UI State and a Viewed Playlist.

The daemon remains alive with no clients and never performs idle shutdown. q, Ctrl-C, terminal hangup, and terminal loss perform Quit Client; they leave playback and downloads running. Ctrl-Q requests a daemon-issued Confirmation Challenge and, after confirmation, gracefully shuts down the daemon, its owned work, and all clients. Public tmu daemon status and tmu daemon stop commands provide read-only inspection and a version-compatible shutdown escape hatch without becoming a second playback-control interface.

Clients communicate through a same-user local Unix domain socket using length-prefixed JSON frames and one exact-match integer protocol version. The daemon publishes immutable full Shared State Snapshots with monotonically increasing State Revisions. Shared facts are broadcast; Command Feedback is returned only to its initiating client; Daemon Notices are broadcast. All Shared Commands and daemon-owned background transitions commit through one serial sequence. Destructive or protected operations use daemon-owned, client-bound, single-use Confirmation Challenges so stale impact cannot be approved.

User Stories

  1. As a listener, I want playback to continue after I close the terminal tab containing TMU, so that terminal organization does not interrupt music.
  2. As a listener, I want paused playback to remain owned by TMU after all terminals disconnect, so that a paused daemon does not disappear unexpectedly.
  3. As a downloader, I want accepted downloads to continue after I close their submitting terminal, so that downloads are not tied to a tab.
  4. As a user with many terminal tabs, I want to run tmu anywhere and reconnect to the existing shared state, so that I do not need to find the original tab.
  5. As a user opening TMU for the first time, I want tmu to start the daemon automatically, so that daemon process management is not a prerequisite.
  6. As a user launching several clients concurrently, I want exactly one daemon to start, so that startup races cannot create competing state owners.
  7. As a user, I want the daemon to remain alive with zero connected clients, so that its lifetime is predictable.
  8. As a user, I want q to close only my TUI Client, so that shared playback and downloads continue.
  9. As a user, I want Ctrl-C to close only my TUI Client even in text entry or a modal, so that I always have a reliable terminal exit.
  10. As a user whose terminal hangs up or SSH connection closes, I want only that client to disappear, so that the daemon and other clients are unaffected.
  11. As a user, I want Ctrl-Q to be distinct from Quit Client, so that shutting down all TMU work is deliberate.
  12. As a user requesting Shutdown Daemon, I want to see current playback, download, and connected-client impact before confirming, so that I understand the consequences.
  13. As a user in any connected client, I want equal authority to request Shutdown Daemon, so that losing the original terminal does not strand the daemon.
  14. As a user in another client, I want a clear intentional-shutdown notice before disconnection, so that shutdown is not mistaken for a crash.
  15. As a user, I want Shutdown Daemon to save state, cancel owned work, stop mpv, and finish within a bounded period, so that shutdown is both safe and responsive.
  16. As a command-line user, I want tmu daemon status to inspect a running daemon without starting one, so that I can diagnose what owns playback.
  17. As a command-line user, I want daemon status to report PID, versions, uptime, runtime and log paths, client count, playback context, download counts, configuration source, recovery state, and latest severe error, so that it is operationally useful.
  18. As a command-line user, I want tmu daemon stop to show live impact and request confirmation, so that I can safely shut down without a TUI.
  19. As an automation author, I want non-interactive daemon stop to require explicit --force, so that scripts cannot silently stop active work.
  20. As an automation author, I want --force to skip confirmation but still perform graceful shutdown, so that force never means an unverified PID kill.
  21. As a user with two clients, I want both to see the same shared playback, Playlists, Cache, and Download Pipeline, so that TMU has one source of truth.
  22. As a user with two clients, I want changing tabs, selection, scroll, search text, URL input, or overlays in one terminal not to affect the other, so that UI State remains terminal-local.
  23. As a user, I want each new TUI Client to open on the Playback Tab viewing the Playing Playlist, so that reconnecting starts from a predictable context.
  24. As a user, I want a client session's UI State discarded when it closes, so that TMU does not accumulate stale terminal sessions.
  25. As a user browsing a Playlist, I want it to be my Viewed Playlist without changing shared playback, so that browsing never stops music or redirects another terminal.
  26. As a listener, I want exactly one Playing Playlist to own shared playback order, Current Track, position, and Repeat All, so that transport controls have unambiguous meaning.
  27. As a user, I want Play Selected or Play Now from my Viewed Playlist to promote it to Playing Playlist, so that starting playback explicitly changes the shared context.
  28. As a user viewing a non-playing Playlist, I want Space, Stop, Next, Previous, seek, and volume to continue controlling the Playing Playlist, so that global transport controls remain global.
  29. As a user, I want the Now Playing Bar to identify the Playing Playlist independently of my Viewed Playlist, so that I know which context transport controls affect.
  30. As a user, I want Add to Playlist, Play Next, Clear, Randomize, remove, and reorder to target my Viewed Playlist explicitly, so that list operations do not depend on hidden daemon UI state.
  31. As a user creating a Playlist, I want only my client to begin viewing it, so that creation does not redirect other clients or playback.
  32. As another client, I want to see a newly created shared Playlist without being switched into it, so that shared facts and local navigation remain separate.
  33. As a user deleting the Playing Playlist, I want confirmation that playback will stop, so that the destructive effect is explicit.
  34. As a user deleting the Playing Playlist, I want the next Playlist, or previous when deleting the last row, to become Playing without autoplay, so that the shared context remains valid but quiet.
  35. As a user viewing a Playlist deleted elsewhere, I want my Viewed Playlist to move to the same daemon-selected replacement, so that my UI does not point at a missing identity.
  36. As a user, I want the sole remaining Playlist protected from deletion, so that TMU always retains a valid Playlist context.
  37. As a user issuing a relative shortcut concurrently with another client, I want both intents applied in daemon receipt order, so that two Next or volume-up commands are not collapsed by stale client calculations.
  38. As a user operating an explicit control, I want absolute values such as a chosen volume applied as submitted, so that controls retain precise meaning.
  39. As a user, I want cross-process commands to identify Tracks, Playlists, Cache Entries, and Download Batches by stable identity rather than row index, so that concurrent reorder and filtering cannot redirect commands.
  40. As a user, I want accepted commands to finish even if my terminal disconnects immediately afterward, so that network/process timing does not create ambiguous cancellation.
  41. As a user, I want incomplete or unaccepted command frames never to execute, so that the command acceptance point is clear.
  42. As a user, I want ordinary commands to revalidate and apply to the latest state when executed, so that shared behavior is serial and deterministic.
  43. As a user confirming a protected operation, I want the daemon to calculate and display its current target and impact, so that the client cannot approve stale or invented consequences.
  44. As a user, I want a Confirmation Challenge bound to my client, target, revision, expiry, and one use, so that it cannot be replayed or transferred.
  45. As a user, I want a protected command rejected for renewed confirmation when relevant impact changes, so that intervening clients cannot invalidate what I approved.
  46. As a user, I want unused Confirmation Challenges invalidated when my client disconnects, so that abandoned approvals cannot linger.
  47. As a user, I want Shutdown Daemon, Playlist and Cache destruction, download cancellation/removal, and playlist-download acceptance protected by Confirmation Challenges, so that consequential operations remain deliberate.
  48. As a user, I want normal playback, rename/reorder, single-Track Playlist removal, challenge cancellation, and Quit Client to remain direct, so that routine interactions stay responsive.
  49. As a user, I want shared UI changes to appear only after a newer daemon snapshot arrives, so that the TUI never presents optimistic shared state requiring rollback.
  50. As a user, I want terminal-local UI interactions to update immediately, so that non-shared navigation remains responsive.
  51. As a user, I want to submit multiple relative commands while earlier ones are pending, so that the non-optimistic model does not throttle key presses.
  52. As a newly connected client, I want one complete Shared State Snapshot and State Revision, so that I can render without replaying history.
  53. As a connected client, I want complete newer snapshots after shared commits, so that missed intermediate publications cannot leave me divergent.
  54. As a connected client, I want duplicate or older revisions ignored, so that publication reordering cannot move state backward.
  55. As a low-power user, I want playback position and download/provider progress publication bounded by cadence, so that daemon mode does not increase idle rendering or IPC load.
  56. As a user, I want Command Feedback shown only in the client that issued the command, so that another terminal is not spammed with my validation messages.
  57. As a user, I want current shared capability failures visible to all clients, so that the snapshot reflects what is actually available.
  58. As a user, I want Daemon Notices such as recovery and shutdown broadcast to all connected clients, so that system-wide events are clear.
  59. As a newly connected user, I want current errors but not historical toasts replayed, so that the UI reflects present reality rather than old noise.
  60. As a long-running daemon user, I want Operational Errors and notices bounded, so that shared state does not grow indefinitely.
  61. As a downloader, I want a single-video Download Batch to become daemon-owned once accepted, so that client exit cannot cancel it.
  62. As a playlist downloader, I want metadata preparation to occur in the daemon and require a client-bound challenge before the Batch is accepted, so that large work is deliberate.
  63. As a playlist downloader who disconnects before confirmation, I want the pending submission cancelled, so that the daemon does not retain ownerless approval state.
  64. As a playlist downloader who disconnects after confirmation, I want the accepted Batch to continue, so that it follows normal daemon-owned lifetime.
  65. As a user, I want unsubmitted URL input to remain client-local, so that text in one terminal is not exposed to another.
  66. As a downloader, I want the daemon to retain the 500 most recent bounded Download Batch summaries, so that useful history lasts across a long daemon session.
  67. As a downloader, I want older summaries evicted without affecting committed Cache Entries, so that UI history limits never delete media.
  68. As a downloader, I want active and pending Batches excluded from the completed-summary cap, so that live work cannot disappear.
  69. As a user, I want slow clients isolated by per-connection outbound buffers, so that one terminal cannot stall playback, downloads, commands, or another terminal.
  70. As a slow client, I want full snapshots coalesced to the newest revision, so that recovery from temporary backpressure is efficient.
  71. As a user, I want Command Feedback, Confirmation Challenges, and shutdown control messages never silently dropped, so that consequential protocol messages remain trustworthy.
  72. As a user, I want a persistently slow client disconnected rather than blocking the daemon, so that shared work remains healthy.
  73. As a user on macOS, Linux, or WSL, I want client/daemon communication confined to a same-user Unix socket, so that no network listener or remote authentication is needed.
  74. As a user, I want runtime directories and sockets validated for ownership and restrictive permissions, so that another local user cannot control my daemon.
  75. As a user, I want an exact integer protocol version match for normal connections, so that compatibility behavior is simple and deterministic.
  76. As a user upgrading TMU while a daemon runs, I want differing software versions allowed only when their protocol integer matches, so that safe minor software changes do not force replacement.
  77. As a user with an incompatible client, I want a dedicated version error and stable status/stop escape hatch, so that I can inspect and shut down the old daemon safely.
  78. As a user, I want tmu daemon stop failure to report verified diagnostics rather than killing an uncertain PID, so that PID reuse cannot terminate an unrelated process.
  79. As a user, I want the detached daemon not to inherit a terminal's output streams, so that closing the launcher terminal cannot disrupt it.
  80. As a user, I want daemon logs in my user state directory with bounded rotation, so that diagnostics exist without unbounded disk use.
  81. As a privacy-conscious user, I want logs to omit full YouTube URLs, search/input text, and full media paths, so that diagnostics minimize sensitive data.
  82. As a user, I want high-frequency playback position ticks excluded from logs, so that logs remain useful and bounded.
  83. As the first client starting a daemon, I want a startup surface until configuration, recovery, snapshot restore, Cache scan, and socket readiness complete, so that the main TUI opens on trustworthy state.
  84. As a concurrently launching client, I want to wait on the same startup result, so that I neither start another daemon nor see partial state.
  85. As a user, I want optional dependency health checks to complete after daemon readiness, so that missing external tools disable only their features without blocking TMU.
  86. As a user, I want mpv started only on demand and Background Sounds probed lazily, so that opening a client remains lean.
  87. As a user facing startup failure, I want a stable error surface with the daemon log path and no restart loop, so that I can diagnose the problem.
  88. As a user editing TMU Config, I want changes to take effect after explicit daemon restart, so that all clients use one configuration snapshot.
  89. As a user, I want clients not to load independent configuration files, so that client versions cannot create divergent behavior.
  90. As a user, I want runtime identity and socket location fixed per user rather than configurable, so that configuration cannot accidentally create parallel daemons.
  91. As a listener, I want semantic playback changes checkpointed immediately and continuous playback checkpointed about every 30 seconds, so that crash rollback is bounded without continuous disk writes.
  92. As a listener, I want Quit Client not to trigger shared persistence, so that client churn has no ownership side effects.
  93. As a user after daemon crash, I want connected clients to remain on a non-operational connection-lost screen until I quit, so that failure remains visible and no client silently restarts shared state.
  94. As a user after daemon crash, I want a later explicit tmu launch to perform Daemon Recovery, so that replacement remains a deliberate user action.
  95. As a user after daemon crash, I want the new daemon to verify and clean orphan mpv and download processes and stale sockets, so that old resources do not compete with recovery.
  96. As a listener after daemon crash, I want recovery from the latest checkpoint without autoplay, so that state is trustworthy even though playback was interrupted.
  97. As a downloader after daemon crash, I want fully committed Cache Entries preserved and discoverable, so that completed work is not lost.
  98. As a downloader after daemon crash, I want unfinished and pending Download Pipeline work discarded rather than falsely resumed, so that recovery does not pretend to reconstruct yt-dlp state.
  99. As a user after recovery, I want a one-time Daemon Notice explaining interrupted downloads, so that missing pipeline work is not mysterious.
  100. As an existing 0.3.x user upgrading to 0.4.0, I want my former Active Playlist migrated to the Playing Playlist, so that my playback context is preserved.
  101. As an upgrading user, I want the original snapshot preserved once before successful 0.4.0 migration, so that a manual downgrade recovery remains possible.
  102. As an upgrading user, I want migration considered complete only after the new snapshot is atomically committed, so that failure cannot destroy the only valid state.
  103. As an upgrading user, I want unchanged YouTube Cache media left in place, so that 0.4.0 does not duplicate large files.
  104. As a TMU maintainer, I want 0.4.0 to expose only the daemon architecture and no legacy runtime flag, so that two state owners can never be selected accidentally.
  105. As a TMU maintainer, I want release notes to require closing pre-0.4.0 TMU processes before first launch, so that legacy processes cannot race the new daemon during migration.

Implementation Decisions

  • Release the architecture as TMU 0.4.0 in one product switch. Internal implementation may be staged, but no user-facing legacy single-process mode or feature flag remains.
  • Introduce one high-level DaemonClient interface as the seam used by TUI Clients. It connects, submits Shared Commands, requests and confirms Confirmation Challenges, receives Command Feedback and Daemon Notices, observes Shared State Snapshots, and disconnects. The interface hides transport and daemon implementation details.
  • Provide an in-process DaemonClient adapter during refactoring and a production Unix-socket adapter. The in-process adapter is a replacement path for tests and incremental development, not a released legacy runtime mode.
  • Move App Coordinator and all shared App State into the TMU Daemon. Move UI State and its reducer/store into each TUI Client. Providers remain daemon implementation objects; snapshots contain only serializable provider data.
  • Replace the overloaded Active Playlist model with one daemon-owned Playing Playlist and one client-owned Viewed Playlist per connection. New clients start on Playback viewing Playing. UI State is not durable.
  • Make all Playlist and Library commands carry an explicit Playlist identity. Global transport commands operate on Playing. List commands operate on Viewed. Play Selected and Play Now promote their target to Playing.
  • Creating a Playlist returns its identity only to the initiating client, which changes its Viewed Playlist locally. The shared Playlist exists even if that feedback cannot be delivered.
  • Deleting Playing uses a challenge, stops playback, selects the next or previous replacement without autoplay, and publishes the replacement identity so clients viewing the deleted Playlist converge. The sole Playlist remains protected.
  • Add a daemon ingress command executor that serializes Shared Commands and daemon-owned background state transitions. Long-running downloads and helper processes execute outside the queue but commit short state transitions through it.
  • Define command acceptance as complete frame validation plus enqueue. Accepted commands continue after client disconnect; incomplete/unaccepted frames do not execute. Disconnect invalidates unreturned challenges but does not roll back accepted confirmation.
  • Express shortcut commands as relative intent evaluated at execution time. Explicit controls may submit absolute values. Stable domain identities cross the connection; list indexes never do.
  • Assign a monotonically increasing State Revision to committed shared state. Revision numbers may skip between published snapshots when cadence coalesces progress.
  • Publish complete immutable Shared State Snapshots on connection and after committed changes, subject to existing bounded low-power cadence for progress-only changes. Clients ignore duplicate/older revisions and never optimistically mutate shared snapshots.
  • Keep client UI updates immediate and local. Pending indicators are allowed; Shared State changes wait for daemon publication.
  • Direct Command Feedback to the initiating connection. Broadcast Daemon Notices and snapshots. Remove the unbounded shared appErrors model in favor of current capability errors, Command Feedback, Daemon Notices, latest severe status, and bounded logs/notices.
  • Implement daemon-owned Confirmation Challenges for Shutdown Daemon, Delete Playlist, Clear Playlist, healthy Cache deletion, incomplete Cache cleanup, active download cancellation, pending Batch removal, and playlist-download acceptance. Tokens are opaque, client-bound, target-bound, revision/impact-bound, single-use, expiring, and invalidated on disconnect or relevant change.
  • Use one same-user Unix domain socket. Prefer $XDG_RUNTIME_DIR/tmu when ownership and permissions are safe; otherwise create a per-user 0700 runtime directory. Validate ownership and permissions before use.
  • Frame socket messages as length-prefixed JSON. Use one exact-match integer protocol version. Software versions may differ when the protocol integer is equal; incompatible protocol changes increment the integer.
  • Keep a deliberately minimal, long-lived status/stop handshake separate from the normal protocol so incompatible installed clients can inspect and stop an old daemon.
  • Bound each connection's outbound buffer. Coalesce unsent full snapshots to the latest revision. Never silently drop feedback, challenges, or shutdown/control messages; disconnect a persistently slow client rather than blocking shared work.
  • Use a single-winner daemon startup lock. Normal tmu connects to an existing daemon or starts one; concurrent launchers wait for the same startup result. Do not expose a normal daemon start workflow or integrate launchd/systemd in 0.4.0.
  • Declare daemon ready after config load, recovery, snapshot restore/migration, initial YouTube Cache scan, and socket listening. Run helper health checks asynchronously afterward. Start mpv on demand and probe Background Sounds lazily.
  • On startup timeout/failure, keep the launching TUI on a diagnostic surface with log path. Do not restart-loop.
  • The daemon never idle-shuts down. Normal termination occurs through confirmed Ctrl-Q, confirmed/forced tmu daemon stop, host shutdown/signal handling, or fatal failure.
  • Implement Quit Client for q, Ctrl-C, SIGHUP, and terminal/connection loss. Quit Client performs no daemon teardown and no shared persistence operation.
  • Implement Shutdown Daemon as an irreversible terminal state: stop accepting commands, broadcast notice, cancel preparations and download work, stop mpv, drain persistence, and exit within an approximately five-second total grace budget. After grace, terminate only verified owned child processes. Report failure but do not leave clients waiting indefinitely.
  • tmu daemon stop interactively prints live impact and confirms. Non-interactive use refuses unless --force; force skips confirmation only and still uses graceful shutdown. Never automatically signal an uncertain PID.
  • tmu daemon status is read-only, never starts a daemon, returns nonzero when absent, and reports the agreed operational state. Do not add playback/editing daemon CLI commands in 0.4.0.
  • Detach daemon stdio from the launching terminal. Write bounded logs under $XDG_STATE_HOME/tmu or ~/.local/state/tmu, retaining the current log and one size-rotated predecessor around 5 MiB each.
  • Redact full YouTube URLs, client search/input text, and full media paths from logs. Exclude routine position ticks. Log lifecycle, failures, bounded download outcomes, recovery, and persistence errors.
  • Load TMU Config exactly once in the daemon. Clients use redacted config data from shared state and never independently load configuration. No file watch or hot reload exists in 0.4.0; restart applies edits. Runtime/socket identity is not configurable.
  • Keep Download Pipeline daemon-lifetime and FIFO with one active Track download. Accepted Batches survive client disconnect. Playlist preparation without a connected confirmer cancels instead of entering the pipeline.
  • Retain at most 500 completed Download Batch summaries in shared state. Bound each failure string, evict oldest completed summaries, exclude active/pending Batches from the cap, and clear summaries on daemon recovery.
  • Keep Cache Entry commit atomic. After crash, retain only complete committed entries; terminate verified orphan download processes, remove recognized temporary files, and discard active/pending/confirmation/summary pipeline state.
  • On unexpected daemon disconnect, keep each TUI Client mounted on a non-operational connection-lost surface. Do not exit, retry, reconnect, or start a daemon. The user exits and explicitly runs tmu again.
  • On a later explicit launch, verify runtime metadata before cleaning orphan mpv/download processes and stale sockets. Never trust PID alone or adopt old mpv as authoritative state. Restore the latest checkpoint without autoplay.
  • Persist semantic playback changes immediately, continuous playing position approximately every 30 seconds, and a final snapshot during graceful shutdown. Keep writes atomic and serialized by the daemon.
  • Migrate the legacy snapshot's activePlaylistId to Playing Playlist. Before first successful format migration, preserve the original snapshot once under an explicit pre-0.4.0 backup name. Complete migration only after atomic new-format commit; do not copy unchanged Cache media or automatically delete the backup.
  • Update all user documentation and shortcut help to distinguish TMU Daemon, TUI Client, Quit Client, Shutdown Daemon, Playing Playlist, and Viewed Playlist. Mark the prior single Active Playlist decision as superseded.

Testing Decisions

  • Tests assert externally observable behavior through the highest available seam. Avoid inspecting command-queue internals, socket parser private state, timers, process maps, or adapter call order unless the behavior cannot be observed through DaemonClient or a real process.
  • Make the DaemonClient interface the primary test seam. Build one reusable contract suite and run it against both the in-process adapter and the Unix-socket adapter.
  • The DaemonClient contract covers initial snapshot/revision, command acceptance, directed feedback, broadcast snapshots/notices, relative command ordering, stable-identity targeting, non-optimistic convergence, challenge lifecycle, stale-impact rejection, disconnect semantics, and slow-client isolation.
  • Multi-client contract scenarios cover simultaneous Next/volume commands, independent Viewed Playlists and UI State, Playlist create/delete/reorder effects, Playing promotion, deletion replacement, command completion after disconnect, and one client's Quit Client leaving another usable.
  • Reuse the existing App Coordinator test style for Provider/Playlist/Player workflows, but move shared behavior behind the daemon-facing interface. Existing App Coordinator tests are prior art for playback advancement, persistence checkpoints, downloads, Cache deletion, and Player failure recovery.
  • Reuse StatePublicationGate tests as prior art for immutable semantic snapshots and bounded progress cadence. Extend externally through DaemonClient to prove revision ordering and complete-snapshot convergence rather than testing publication internals twice.
  • Add focused protocol codec tests for length-prefix boundaries, malformed/oversized frames, strict exact protocol mismatch, message validation, request IDs, and clean handling of partial frames. These tests may use the codec seam because framing failures precede DaemonClient semantics.
  • Add focused startup/runtime ownership tests for single-winner locking, safe runtime directory permissions, stale socket handling, verified PID/process identity, readiness gates, and failure diagnostics.
  • Add process-level Unix-socket tests with two or more child clients to prove a single daemon owner, serialized state, client isolation, bounded backpressure, intentional shutdown notices, and no automatic replacement after crash.
  • Use real PTY/package tests only for behavior that interface tests cannot prove. Extend the existing node-pty packaging smoke prior art to cover first-launch auto-start, reconnect by running a second tmu, q, Ctrl-C, Ctrl-Q, alternate-screen restoration, raw-mode Ctrl-Q delivery, client exit while playback continues, connection-lost error surface, and daemon status/stop.
  • Test tmu daemon status against absent, starting, ready, terminating, recovered, version-matched, and version-mismatched daemons. Verify it never creates a daemon.
  • Test tmu daemon stop interactive confirmation, cancellation, non-interactive refusal, --force graceful behavior, incompatible normal protocol with compatible stop handshake, bounded shutdown, nonzero cleanup failure, and refusal to signal an unverified/recycled PID.
  • Test slow-client behavior with a deliberately unread socket: newer snapshots replace queued snapshots, non-droppable buffer overflow disconnects only that client, and another client continues commands and publications.
  • Test daemon crash behavior with real child processes where practical: clients remain on error surfaces, no connected client restarts/reconnects, a later explicit launch cleans only verified orphan helpers, snapshot position rolls back by no more than the checkpoint cadence under controlled time, and playback does not autoplay.
  • Test Download Pipeline ownership: accepted single and confirmed playlist Batches survive client disconnect; disconnected pre-confirmation playlist preparation cancels; crash discards unfinished work; complete Cache Entries survive; 500-summary eviction and bounded error text work without affecting live Batches or Cache files.
  • Extend existing playlist snapshot persistence tests for active-to-Playing migration, one-time pre-0.4.0 backup, atomic commit ordering, failed migration recovery, no backup deletion, and new-format restore. Do not duplicate media in migration fixtures.
  • Preserve existing Player adapter tests for mpv IPC control. Add only daemon ownership/teardown/adoption behavior above the Player seam rather than re-testing mpv command details through every layer.
  • Preserve existing Vue TUI reducer and rendering tests for UI State. Add scenarios proving each client has an independent store, shared snapshots do not overwrite UI State, missing Viewed identity selects the daemon-announced replacement, and connection loss disables shared actions.
  • Add log tests for bounded rotation, detached output routing, redaction, absence of progress ticks, and latest severe status summary. Use synthetic sensitive inputs and assert they never appear.
  • Run the existing build, typecheck, unit suite, package smoke suite, and supported Node/platform CI matrix as release gates. Add Linux and macOS coverage for Unix socket paths and terminal behavior; WSL follows Linux behavior.

Out of Scope

  • Remote, cross-machine, TCP, HTTP, WebSocket, browser, or mobile clients.
  • Multi-user daemon sharing, authentication beyond same-user OS permissions, roles, owner clients, or administrator clients.
  • launchd, systemd, login startup, OS service installation, or automatic idle shutdown.
  • Automatic client reconnect, automatic daemon restart after an established connection fails, seamless failover, or mpv adoption.
  • Persistent TUI Client sessions, restoration of tabs/search/input/scroll/Viewed Playlist, or client identity across launches.
  • Persistent Download Pipeline jobs, yt-dlp resume after daemon crash, replay of pending URLs, or permanent download audit history.
  • Incremental event replication, event sourcing, snapshot diffs, capability negotiation, major/minor protocol versions, or schema migration within one protocol integer.
  • Optimistic shared-state updates or client-side conflict resolution.
  • A general daemon control CLI for playback, Playlist editing, downloads, Cache operations, or Background Sounds. Only daemon status and daemon stop are public in 0.4.0.
  • Hot configuration reload or configurable runtime/socket identity.
  • A legacy single-process runtime switch or dual-mode release.
  • Native Windows support; existing macOS, Linux, and WSL scope remains.
  • Automatic downgrade. The pre-0.4.0 snapshot backup supports documented manual recovery only.
  • Changes to the YouTube Cache media format or copying existing media during migration.

Further Notes

  • Target release: TMU 0.4.0.
  • This spec follows the accepted daemon/client, Playing/Viewed Playlist, serialized-command, full-snapshot, Unix-socket, recovery, and 0.4.0 migration ADRs. The former single Active Playlist ADR is superseded where it conflicts.
  • The design intentionally favors predictable ownership and recoverable truth over uninterrupted playback after a daemon crash.
  • Full snapshots are the initial correctness choice. Incremental transport may be reconsidered only after measured snapshot size or publication cost demonstrates a problem.
  • The current code already has useful seams in App Coordinator, Player, immutable state publication, UI State reducer/store, and atomic persistence. The implementation should deepen one DaemonClient interface rather than expose those internal seams to TUI callers.
  • Release notes must tell users to close every pre-0.4.0 tmu process before launching 0.4.0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentFully specified, ready for an AFK agent

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions