tags are how the marketplace is browsed rather than scrolled: a list of them on
the left, the plugins that carry one on the right. a closed vocabulary
(PLUGIN_TAGS in the platform) because free text becomes homelab, home-lab, Home
Lab and selfhosted inside a month, and a filter list of synonyms is worse than
no filter at all.
declared here rather than curated in the catalogue, so a plugin can say what it
is and nobody edits a table when one ships. the marketplace caches it on sync,
exactly as it already does for label and summary.
It read `process.env.DATA_PATH ?? cwd/data`. DATA_PATH stopped being an env var on 2026-08-13, when
the install layout became something derived from the working directory rather than configured — so run
from the repo this looked for platform/data/music/.server, which is one level too deep, found nothing,
and said the sidecar was not running.
Imports DATA_PATH from the platform instead, so there is one derivation rather than a copy that fell
behind. Verified it resolves to /home/pastilhas/officer-dev/data.
Also corrects the usage line: the script moved into this plugin and the header still said
`bun scripts/reindex-music.ts` from the platform repo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
reverts 0e08aef, b430a70 and 1170e41 — stream-audio.ts and MUSIC_API.md are
now byte-identical to 2a70bc1.
all three were unwound over two days chasing a mobile playback failure that
turned out to have nothing to do with this plugin. the reasoning that undid
them was elimination, not diagnosis: the duration header did not fix it, so
the etag "must" be the cause. the option never considered was that none of it
was mine, which is the one that was true.
nothing here was measured wrong. removing the per-file ffprobe from the path
before the first byte is the speedup, and it is confirmed in real use.
what the mobile client should know, unchanged from MUSIC_API.md: x-audio-duration
is gone, read durationSec from /meta instead — it arrives with the queue,
before the first stream request.
playback did not come back when x-audio-duration did, so the duration header
was not the only thing i broke. the range and whole-file branches are
byte-identical to the version that worked, which leaves these three as the
only remaining difference on /stream.
a cache that keeps a validator after evicting the body sends if-none-match,
gets a 304 and has no audio. plausible, unproven, and not worth another round
trip through someone's phone to find out — the etag was an optimisation and
playback is not.
/stream is now byte-identical to the last version known to play, verified by
running both implementations side by side over no-range, closed, open and
invalid ranges and diffing status, every header and body length.
if-none-match is still accepted and ignored. re-introducing this needs a
capture of what the player actually sends.
the previous commit read the duration from meta.json instead of spawning
ffprobe. correct for an indexed album and wrong everywhere else: /stream
serves any home-relative path and never required an index, so a library that
has not been indexed got no header at all. alpha is exactly that case — its
music cache is empty — which is how this was caught rather than shipped.
"always present" replaced with "present if indexed" is the same regression as
removing the header, one layer down.
so both, in order: meta.json when it can answer, ffprobe when it cannot, each
cached. verified on an indexed album (247 from meta, 0.3ms warm) and on the
same file with its index deleted (7.03 from ffprobe, 83ms cold, 0.1ms warm).
removing it was wrong: the mobile player stopped playing. nothing in the web
read the header and /meta already carried durationSec, so it looked dead. it
was not — it is a documented part of the contract and something depended on it.
the header is back and the ffprobe is not. the value is read from the album's
meta.json, which is the same number ffprobe produced (the indexer got it from
ffprobe at index time), cached per album and keyed on the meta file's mtime so
a reindex invalidates it. 1.5ms cold, 0.1ms warm, against 50ms warm and 315ms
cold for the process spawn.
first pass looked for meta.json beside the audio; it lives under the sidecar's
own cache root mirroring the relative path. uses metaFilePath now rather than
re-deriving it.
verified against a real file: header present and equal to meta, 304 on
whole-file revalidation, 206 on range with a matching etag.
openPlayer checked `findCliamp()` before `resolveInHome(files)`, so a path
escaping the owner's home was answered "cliamp not found on host" on any
machine without the binary — telling the caller about the host in response to
input that should simply have been refused.
It also made the guarantee depend on an unrelated binary being installed,
which is why cliamp-ws.test.ts's "rejects a path that escapes the owner home"
failed on this machine (and every other one without cliamp) since the tests
started running from plugins/ at all. It was being carried as a known-failing
baseline rather than read.
Validate the request, then inspect the host. No behavioural change on a
machine that has cliamp.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>