-
Spool: a media player for webOS, desktop and Android.
-
Prefer a smaller, faster, easier-to-extend codebase over compatibility scaffolding or speculative abstractions.
-
The app is prerelease software: bump/reset caches on schema changes instead of adding migrations or fallback readers.
-
mpv/is a submodule. Commit mpv changes insidempv/, then commit its pointer here. -
Make small conventional commits as coherent work finishes. Never push unless the user explicitly asks.
-
After any push or tag, watch the Actions runs it triggers and report the first failure the moment it appears. A release is not delivered because it was tagged; it is delivered when its runs are green.
-
Watch jobs, not runs.
gh run watchandgh run view --exit-statusonly return once every job has finished, so a matrix whose Windows leg failed in two minutes stays silent until the slowest leg finishes an hour later. Poll the job list and break on the first failed job:until gh run view "$id" --json jobs \ --jq '.jobs[] | select(.conclusion == "failure") | .name' | grep -q .; do gh run view "$id" --json status --jq .status | grep -q completed && break sleep 60 done gh run view "$id" --log-failed
Run that in the background per run id, and read
--log-failedfor the failing job alone rather than downloading the whole log.
The app knows no media backend. Every source is a provider package (JS logic
and QML screens) run by src/provider/; docs/providers.md describes the
pieces and sdk/ is the current format-3 provider contract. Jellyfin, Emby, Plex and
Stremio live in their spool-player/spool-* repositories; Open Movies lives in
spool-player/spool-provider-example. All five are bundled from digest-pinned
archives in providers/lock.json. Providers and host cut over together; do not add
compatibility shims for older provider/host builds.
- Core is everything under
src/exceptsrc/providers/(native providers; onlyLocalProvidertoday) andsrc/main.cpp, the composition root. Core never includessrc/providers/;tools/check-module-seam.shis themodule-seamctest. - The app sees one
Provider,SourceHub, which routes to every enabled account. IDs leaving it are scoped (<8 hex>:<id>) and stay opaque to routes, caches and QML. Pages reach media throughCatalog,SearchSource,UserItemStateSink,ArtworkSource,PlaybackSourceandStreamQualityControl, never a provider type. - Shared QML gates optional controls on the
ProviderCapabilitiessingleton (search, userItemState, playbackReporting, segments, groupPlayback, remoteControl, streamQuality, trickplay): what the enabled accounts can do between them. Provider-specific UI is the provider's own QML, mounted byshell/ProviderSurfacewith aProviderUiContext; item-menu entries come from the manifest'sactions. - Group playback and remote control are generic (
GroupPlaybackController,AppControllerRemote.cpp); providers translate their protocol intogroupandremoteevents. Do not touchsrc/playerorqml/pages/Player*for provider work beyond renames. qml/primitivesandqml/themereach exactly two singletons,Art.urlandSettings.uiScalePercent. Keep it that way; page- and shell-level QML is where backend-shaped data belongs.- Keep backend branding out of shared code. Settings copy, theme colour names and network user agents should read as the product, not as Jellyfin.
tools/manifests/toolchain.jsonsets the Qt and FFmpeg versions for every platform. Nothing else may name one;tools/toolchain-versions.sh --checkis what CI runs to keep the Qt module manifests honest.- Qt source manifests use the official-listed FAU archive mirror rather than a geo-selected redirect. Mirror changes must preserve version/module SHA-256 pins; verify the full archive digest before changing a source URL.
- webOS GCC coroutine calls use named argument maps before
co_await; nesting a member expression inside a temporary argument aggregate triggered a compiler internal error. Keep the wire arguments unchanged when restructuring such calls. - The launch screen carries the version, so it is rendered per build by
tools/generate-splash.shfromtools/manifests/splash.jsonand is not committed. Configuring the app is enough to get it on every platform.
- Launch the native release app with
nix run. Clean Git revisions substitute the immutable package from Cachix; dirty tracked changes use the incremental checkout build. - Launch the already-built native release app without rebuilding with
nix run .#run. It fails with a build-command hint when the binary is missing. - Build without launching with
nix run .#build. - Run the test suite the way CI does with
nix run .#tests(release build, thenpython tools/run-tests.py --build-dir <dir> --workers N: traditional selectors finish before real GUI e2e; failures aggregate without GPU exclusions). - Leave interactive UI testing to the user. Do not drive their desktop with xdotool/xdgtool or similar input automation, or launch visible smoke tests unless explicitly requested. Use builds and isolated/offscreen checks for verification.
- Use
nix develop .#native -c ...for targeted development commands (e.g.cmake --preset linux-dev, thencmake --build build/linux-dev/app --target spool). - The image-diagnostics equivalents remain
nix run .#image-debug-buildfollowed bynix run .#image-debug. - Batch coherent edits, then run one build and one
qmlformat/clang-formatinvocation over all touched files; don't build or format file-by-file.
- Trace is opt-in:
QT_LOGGING_RULES='spool.provider.trace.debug=true'. Enable it only for the shortest necessary inspection, then unset it. - Never print or share unfiltered app logs, diagnostics JSON, provider responses, credential stores, URLs, request headers, or account configuration. Restoration must use the real app's existing credential path, never pasted tokens.
- Always filter trace output before displaying it: select only
preview metadata,preview session,preview fetch failed, andpreview load failedmessages fromspool.provider.trace. Prefer an explicit field allowlist (item ID, enabled, availability, format, dimensions/count, numeric network/status codes, frame/speculative). Redact authentication, URL/query, user/account and path values even if upstream logging is already sanitized. spooletmay inspect restored-account library rows and drive an explicitly requested offscreen preview smoke. Filter JSON to the required titles, IDs, player/preview state and dimensions. Never dump raw provider payloads or support reports to discover preview availability.
- Do not build, install, launch, deploy, or test on a TV unless the user explicitly requests it. "Deploy" means install-only; never launch unless separately asked.
- For a requested deployment, use this exact build-and-install command from the repository root:
TV_HOST=root@tv.local; ./build-ipk.sh && nix develop -c bash tools/webos/verify-device.sh --no-launch --host "$TV_HOST" ./build/com.sachk.spool_0.3.0_arm.ipkbuild-ipk.shdefaults to the complete freshallpipeline. Never invoke theapp,stage, orpackagephases separately. - The packaged IPK is the only supported way to update the TV app. Never copy binaries or libraries to the TV manually, mutate the installed application tree, or substitute hand-written CMake/staging/install steps.
- If webOS diagnosis is requested, read current logs first and make one targeted change — no speculative deploy loops.
- Never write to TV partitions, patch package metadata, restart system services, or wipe app/user data.