refactor(ui): reuse the store, media-query and deleted-tag rules the review found rebuilt, and name a loading pick

E32 review round 1 (F3–F6).

- Channels' open-row store and the chat dock use lib/store's createStore instead of a hand-rolled
  listener set (the chat dock gets a test of its own).
- The Plex backdrop reads prefers-reduced-motion through useMediaQuery; the query string is one
  constant (REDUCED_MOTION) for the hook and the carousel's one-off read.
- A link-restored collection filter showed its raw rating_key in the picker while the titles
  loaded: usePlexCollectionTitles now says it is pending, the picker draws a placeholder
  (labelsPending, as the channel picker does) and so does the chip row.
- The "deleted tag" rule was written twice with two i18n keys and had drifted: the channel manager
  showed NO chip for a pick while the tag list loaded, or ever after a failed load, while the pick
  still narrowed the table. One rule now (lib/tagPicks), one key (common.deletedTag); the manager's
  row shows every pick, as a placeholder while loading and as its id after a failure.
This commit is contained in:
2026-10-04 02:29:03 +02:00
parent e09d613b04
commit e5b155ada9
24 changed files with 245 additions and 129 deletions
+7 -5
View File
@@ -10,11 +10,13 @@ import { qk } from "./queryKeys";
// This resolves the missing ones from the server and merges them over what the filter state already
// knows. It stays OUT of `collectionTitles`: that map is hashed into the Plex query keys, so writing a
// resolved title back in would split the cache from the logically identical state and refetch the grid
// for a label. Titles are display-only, so they belong in a display-only read.
// for a label. Titles are display-only, so they belong in a display-only read. `pending` says the
// missing ones are still on their way, so a token or chip can show a placeholder rather than the raw id
// (Combobox `labelsPending`, ActiveFilter `pending` — the feed's channel picker does the same).
export function usePlexCollectionTitles(
ids: string[],
known: Record<string, string> | undefined,
): Record<string, string> {
): { titles: Record<string, string>; pending: boolean } {
// Sorted so the same set of ids in a different order is the same query.
const missing = ids.filter((id) => !known?.[id]).sort();
const q = useQuery({
@@ -25,7 +27,7 @@ export function usePlexCollectionTitles(
// of the sidebar and the chip row.
staleTime: 60 * 60_000,
});
const out: Record<string, string> = { ...known };
for (const c of q.data?.collections ?? []) out[c.id] = c.title;
return out;
const titles: Record<string, string> = { ...known };
for (const c of q.data?.collections ?? []) titles[c.id] = c.title;
return { titles, pending: missing.length > 0 && q.isPending };
}