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/photos.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/photos.md')
| -rw-r--r-- | docs/photos.md | 23 |
1 files changed, 23 insertions, 0 deletions
diff --git a/docs/photos.md b/docs/photos.md index ed4a826..c00a7e0 100644 --- a/docs/photos.md +++ b/docs/photos.md @@ -456,6 +456,29 @@ than Videos, not more. | P4 | Wrap-around vs. stop-at-ends for next/previous at album boundaries | UI choice, mirror whatever `video-player.js`'s own controls already do for consistency | | P5 | Album cover selection (always "first photo" vs. an operator/member choice) | Product call; "first photo, stable" is a reasonable, zero-config default and is what this document assumes | +## 10b. One photo, not two, in the cross-group Search view (2026-09-02) + +Reported against Videos and true here by construction: two groups sharing one +directory listed every photo twice inside one album. `source-merge.js` folds +entries on the content hash and resolves one source per album; the units come +from `groupPhotoAlbums` itself (exported for this), called on the un-merged +list purely to learn them. + +**One consequence is deliberate and is not a bug.** Albums are keyed by +directory (§2.5), so two groups whose roots have *different basenames* put the +same photo in two differently-named albums, and the merge — scoped to a unit — +leaves it in both. That is correct: they are two albums. Only same-named albums +collapse, which is the reported shape. Videos and Music do not have this case, +their units being title- and tag-based rather than path-based. + +The album card's source badge counts the union over **the album**, not the +cover photo: the cover is `photos.find((p) => p.thumb_hash) || photos[0]`, so +an album in two groups whose cover sits in only one would have claimed a single +source. + +Design: `docs/refactoring-search.md`. `test_search_media_merge.py` covers both +the collapsing and the non-collapsing case. + ## 11. Acceptance before shipping 1. Orientation correction verified against a real EXIF-rotated phone photo |