aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/group-name.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-02 16:21:48 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-02 16:21:48 +0200
commit10f8266e7152d7dc38dbfe2449327829bf020ad1 (patch)
tree5e72ac45b2e79008812663e251a4fcab62dc0650 /packages/meshbay-hub/src/meshbay_hub/static/group-name.js
parent313b72f15e8788ba3abcd3e44b5f7785fbc779fe (diff)
downloadmeshbay-10f8266e7152d7dc38dbfe2449327829bf020ad1.tar.gz
fix(hub): merge duplicate sources in Search's Music and Photos too
Phases 5-8 of docs/refactoring-search.md, extending the Videos merge outward. A library shared by two groups now lists each track once inside an album and each photo once inside a photo album, and a card served by several groups says "N sources" instead of naming one of them. Units come from each application's own grouping, never a copy of its keys. For Music that meant exporting foldKey: groupMusicEntries folds case to group but keeps the first-seen spelling to display, and which group is seen first is whichever index arrived first — so keying a unit on the display strings would let the chosen source change between page loads. A group whose connection fails is marked down and stops being chosen, so a unit fails over to another group that has the file. Eviction is not a failure. Every source being down still yields an entry: a tile that fails to load beats a film that vanished from the grid. sourceLabel now takes the whole unit rather than one entry. A show's poster entry is picked for its thumbnail, so a show in two groups whose cover episode sits in only one of them would have claimed a single source. SourceTag lives in group-name.js — source-merge.js must keep importing nothing (its test executes it standalone), and a copy in each of the three apps is three chances to disagree. test_search_files_unmerged.py holds the one thing that must not change: the Files explorer is not merged, because there each group is a folder and merging would remove a file from one of them. It also asserts the other three lists are merged, or deleting the merge outright would leave it passing and saying nothing. One plan item was dropped as wrong rather than built: the Music queue in onPreview needed no change. It filters by groupId and is reachable only from FilesPanel, which is not merged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AbwJDbNTkiRUh7HTWEoyss
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/group-name.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/group-name.js35
1 files changed, 35 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-name.js b/packages/meshbay-hub/src/meshbay_hub/static/group-name.js
index af8879c..f109da7 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/group-name.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/group-name.js
@@ -1,4 +1,6 @@
import { html } from './vendor/htm-preact.js';
+import { t } from './i18n.js';
+import { sourceLabel } from './source-merge.js';
/**
* A group's name with the `@owner` handle under it.
@@ -20,3 +22,36 @@ export function GroupName({ name, owner, inline = false }) {
</span>
`;
}
+
+/**
+ * Which group serves an entry, or how many groups have it.
+ *
+ * The Search view merges a file several groups share into one entry, so the
+ * badge under a card cannot always name a group. One source keeps naming it
+ * and keeps linking to it; more than one becomes a count, and *which* one was
+ * picked is deliberately not shown (docs/refactoring-search.md §5.5).
+ *
+ * `entries` is the whole unit — every episode of a show, every track of an
+ * album — not the entry the card was drawn from; `sourceLabel` explains why.
+ * Renders nothing at all on the single-group Group page, where an entry
+ * carries no group and there is only ever one source anyway.
+ *
+ * It lives here rather than in `source-merge.js` because that module is
+ * executed standalone by its test and must keep importing nothing; and here
+ * rather than in one of the three apps that need it, because a copy apiece is
+ * three chances to disagree about what a merged card says.
+ *
+ * A `div` by default: the three card badges rely on `text-overflow: ellipsis`,
+ * which does nothing on an inline box. `link` gives the flat row's inline
+ * pill instead.
+ */
+export function SourceTag({ entries, cls, link = false }) {
+ const { count, name, groupId } = sourceLabel(entries);
+ if (count > 1) return html`<span class=${cls}>${t('search.n_sources', { n: count })}</span>`;
+ if (!name) return null;
+ if (link && groupId) {
+ return html`<a href="#/group/${groupId}" class=${cls}
+ onClick=${(e) => e.stopPropagation()}>${name}</a>`;
+ }
+ return html`<div class=${cls}>${name}</div>`;
+}