diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-02 17:31:50 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-02 17:31:50 +0200 |
| commit | 7b25f1c09ba1b8692988d9616f3c33c97af9f3ca (patch) | |
| tree | 1a7d346ea6a3847269df4aeff3c6b41d515b5d7b /docs/musicbay.md | |
| parent | 10f8266e7152d7dc38dbfe2449327829bf020ad1 (diff) | |
| download | meshbay-7b25f1c09ba1b8692988d9616f3c33c97af9f3ca.tar.gz | |
fix(hub): stop the maintenance loop racing the tests, and pin _ASSETS
Two defects found while closing out the Search merge, neither of them in
that feature.
The maintenance loop. create_app's lifespan starts cleanup_loop as an
asyncio task, so every test — each entering that lifespan — ran a purge
pass concurrently with its own requests. On SQLite :memory: that is not
merely noisy: the engine uses a StaticPool, one connection for the whole
process, so the request's session and the cleanup task's session
interleave transactions on the same connection. A registration could
commit and then be invisible to the login three lines later, surfacing
as 401 Invalid credentials for an account created moments before, in
roughly one run of test_node_ws_auth.py in four.
The purge itself is not at fault and this is not a production condition.
A passive SQL listener caught the DELETE removing 0 rows, and the INSERT
carrying status='active' — so neither the pending-account mechanism nor
the purge filter is involved, and PostgreSQL gives every session its own
connection. What the fixture removes is the second user of the shared
one. 60 runs of the previously flaky file, 0 failures; reproductions
before the fix landed on attempts 4, 6, 13 and 29 of separate loops, so
a clean run of 60 has about a 1% chance of being luck.
_ASSETS. source-merge.js shipped missing from webapp._ASSETS, the
cache-busting hash's input list — exactly the silent failure docs/apps.md
§4 step 5 warns about: the file changes, the asset URL does not, and a
browser holding the old page keeps the old copy. Harmless this time only
because search-page.js changed in the same commit and is listed, which is
the worst way for it to go unnoticed. Found by re-reading that checklist
for the doc pass, not by any test — so there is a test now, holding
_ASSETS to every .js in static/ (sw.js excepted, unversioned on purpose).
It was the only one missing.
Phase 9 of docs/refactoring-search.md also lands here: mediacenter.md
§10.6, musicbay.md §9b, photos.md §10b, apps.md §2b and step 5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AbwJDbNTkiRUh7HTWEoyss
Diffstat (limited to 'docs/musicbay.md')
| -rw-r--r-- | docs/musicbay.md | 34 |
1 files changed, 34 insertions, 0 deletions
diff --git a/docs/musicbay.md b/docs/musicbay.md index 2ca8b23..d9794d2 100644 --- a/docs/musicbay.md +++ b/docs/musicbay.md @@ -434,6 +434,40 @@ actually implement (§3.2), not a property the protocol gives for free. | M5 | Gapless playback, crossfade, lyrics, waveform seek preview | Nice-to-haves, no architectural prerequisite from this plan either way | | M6 | Photos app | Out of scope of this document, per `apps.md`'s own list — unaffected by anything here | +## 9b. One album, not two, in the cross-group Search view (2026-09-02) + +Reported against Videos and true here by construction: an operator hosting two +groups that share one directory saw every track listed twice inside one album. +Inside a group it cannot happen (`GroupIndex` is keyed by blake3); the Search +page concatenates *N* indexes, and that is where the duplication is born. + +The fix is `source-merge.js`, applied identically to all three media views — +fold on the content hash, resolve **one source per unit**. For Music the unit +is an album, and a track loose enough to carry no artist at all is a unit of +its own (or it would be dropped from the merge entirely, since +`groupMusicEntries` never puts it in an album bucket). + +**`foldKey` is exported for this**, and the reason is worth keeping. §5.1's +grouping folds case and `&`/`and` for the *key* while keeping the first-seen +spelling for display — and which group is seen first is whichever index +happened to arrive first. Keying a merge unit on the display strings would +therefore let the chosen source change between page loads. The folded key is +the one grouping actually uses, and is stable. + +The units come from `groupMusicEntries` itself, called on the un-merged list +purely to learn them — never a second copy of its keys in the Search page. + +**The player needed no change.** `music-player.js` resolves a connection from +`entry.groupId` (§2.3), and a merged entry carries exactly one. The queue built +in `search-page.js`'s `onPreview` needed none either: it filters by `groupId` +and is reachable only from the Files explorer, which is deliberately *not* +merged — there each group is a folder and merging would remove a track from +one of them. + +Design and the adversary it names: `docs/refactoring-search.md`. +`test_search_media_merge.py` covers the album cases, including a differently- +cased tag not splitting the unit. + ## 10. Acceptance before shipping 1. Tag-coverage measurement against a real local library (not committed — |