aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-02 15:23:30 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-02 15:23:30 +0200
commit313b72f15e8788ba3abcd3e44b5f7785fbc779fe (patch)
tree1c8e9b024d78384da950730b37b8d41c2789e010 /docs
parent15d3eec914cf5e474e66f615c9fbaf602eebe575 (diff)
downloadmeshbay-313b72f15e8788ba3abcd3e44b5f7785fbc779fe.tar.gz
fix(hub): one entry per file in the Search view's Videos grid
A library shared by two groups arrived in the cross-group Search view as two entries per file: every film was two poster cards, every episode was listed twice in the season list under the synopsis. Inside one group this cannot happen — GroupIndex is keyed by blake3 — so the duplication was the Search page's own, from concatenating N independently keyed indexes. source-merge.js folds entries on the content hash and resolves one source per *unit* (a film, a whole show), so a season does not scatter across two nodes. A group hosted by the reader's own node wins; failing that the pick is a hash of the unit key and the reader's id, stable across renders and reloads — a source that changed mid-stream would tear down the connection under a film that is playing — and spread across readers and units. The units come from video-app.js's own groupVideoEntries rather than a second copy of its keys here. Only the Videos view is wired up so far; Music, Photos, failover and the "N sources" badge are phases 5-8 of docs/refactoring-search.md. Every test was checked against the fix removed. That is how the first version of "a unit's files share its source" turned out to prove nothing: with every episode in every group, per-file and per-unit picking give the same answer, so it passed against a per-file implementation. It now uses a unit whose files have unequal sources. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AbwJDbNTkiRUh7HTWEoyss
Diffstat (limited to 'docs')
-rw-r--r--docs/refactoring-search.md26
1 files changed, 20 insertions, 6 deletions
diff --git a/docs/refactoring-search.md b/docs/refactoring-search.md
index 08774d1..f23e1ef 100644
--- a/docs/refactoring-search.md
+++ b/docs/refactoring-search.md
@@ -1,6 +1,9 @@
# Refactor: one file, one entry — merging duplicate sources in the Search view
-> Status: **planned, not built** (2026-09-02). Branch `feat/search-source-merge`.
+> Status: **phases 1–4 built** (2026-09-02), 5–9 planned. Branch
+> `feat/search-source-merge`. The Videos view is merged and the reported bug is
+> gone under test; Music, Photos, failover and the badge are still to come, so
+> a merged film still shows its chosen group's name rather than `N sources`.
> Scope: the cross-group Search page (`static/search-page.js`) and the three
> media applications it reuses (Videos, Music, Photos). The Files **explorer**
> inside Search is explicitly out of scope and must not change.
@@ -328,10 +331,10 @@ Each phase is independently testable and leaves the tree working.
| # | Phase | Files |
|---|---|---|
-| 1 | Export `groupPhotoAlbums`; record `is_node_admin` in `fetchGroupIndex`'s result | `photos-app.js`, `search-page.js` |
-| 2 | `source-merge.js` — `pickSource`, `mergeUnitEntries`, `sourceLabel`. No caller yet | new file |
-| 3 | Tests for phase 2, read out of the source | `test_search_source_merge.py` |
-| 4 | Wire the Videos list through the merge | `search-page.js` |
+| 1 ✅ | Export `groupPhotoAlbums`; record `is_node_admin` in `fetchGroupIndex`'s result | `photos-app.js`, `search-page.js` |
+| 2 ✅ | `source-merge.js` — `pickSource`, `mergeUnitEntries`, `sourceLabel`. No caller yet | new file |
+| 3 ✅ | Tests for phase 2, read out of the source | `test_search_source_merge.py`, `test_search_video_merge.py` |
+| 4 ✅ | Wire the Videos list through the merge | `search-page.js` |
| 5 | Same for Music, including the `onPreview` queue (§6.4) | `search-page.js` |
| 6 | Same for Photos | `search-page.js` |
| 7 | `downGroups` and failover | `search-page.js` |
@@ -352,11 +355,22 @@ value from the real source rather than restating a constant.
| Test | Holds |
|---|---|
-| `test_search_source_merge.py` | `pickSource` / `mergeUnitEntries` lifted out of `source-merge.js` and executed: a file in two groups yields one entry with two sources; a local source always wins; the pick is stable across calls and varies with the salt; a unit's files share the unit's source; a file the unit's source lacks falls back to its own; a down group is skipped; all-down falls back to the full list |
+| `test_search_source_merge.py` ✅ | The whole of `source-merge.js` executed standalone — it has no imports precisely so that it can be, and the test refuses a build where it gains one. A file in two groups yields one entry with two sources; a local source always wins, over every salt; the pick is stable across calls, varies with the salt, and spreads one reader across units; source order does not decide it; a unit's files share the unit's source; a file the unit's source lacks falls back with its siblings; a down group is skipped, a down *local* group yields to a live remote, all-down still returns an entry; no field is back-filled from another source |
+| `test_search_video_merge.py` ✅ | The reported symptom end to end. `groupVideoEntries` + `buildSeasons` (video-app.js) and `videoUnits` (search-page.js) are lifted from their real sources, the pipeline is assembled as the page assembles it, and the result is re-grouped the way `VideoApp` re-groups it — so what is counted is what the grid renders. Two groups sharing one library give one film card and one show whose seasons hold three episodes, not six; one group is unchanged; an episode only one group has survives |
| extend `test_locales.py` | already fails on a key present in `en.js` and missing elsewhere — no change needed, listed so the ten-catalogue edit is not forgotten |
| `test_search_files_unmerged.py` | reads `search-page.js` and refuses a build where `fileEntries` is fed through the merge — the one guarantee §6.1 makes, and the one a later refactor is most likely to break by tidying the four lists into one |
| extend `test_transport_contracts.py` | the existing "declared vs. called setters" check covers the new state in `search-page.js` for free |
+**Every one of these was checked against the fix removed**, which is where the
+first version of "a unit's files share its source" turned out to prove nothing:
+with every episode in every group, picking per file and picking per unit give
+the same answer — the same key over the same set — so the test passed against a
+per-file implementation. It now uses a unit whose files have *unequal* sources,
+which is the only shape where the two rules come apart. Five mutations are
+caught: dropping the local preference, picking per file, not de-duplicating a
+group announcing a file twice, dropping the group-id sort, and hashing the salt
+without the unit key.
+
Beyond the suite, this needs a person: two groups sharing one directory, one
film and one multi-season show, checked in Posters, Flat list, the detail modal,
Music and Photos, plus one playback and one download from a merged entry. The