fix(plex,channels): deep-review fixes — durable watch-sync + no shared-link clobber
Deep-review of the quickfix batch surfaced two data/preference-loss bugs plus smaller items; all fixed with tests. - watch_sync _pull_apply: don't let the Plex-authoritative pull overwrite a DIRTY local row (a change whose immediate push failed) — that stranded the value forever since _repush_dirty only retries dirty rows. Leave it dirty for re-push. - PlexProvider / ChannelsProvider: a partial shared link opened in a fresh tab persisted mount-time DEFAULTS over the account's stored scope/sort/filters (base read before the account was pinned). Persist/keep only URL-present dims; re-hydrate the omitted ones from storage. - playback resume write-back: guard against out-of-order delivery (a superseded older push now drops) and evict the throttle map so it can't grow unbounded. - PlexPlayer: announce mid-playback buffering to assistive tech (role=status + sr-only label); new plex.player.buffering (HU+EN). - columnSort: hoist the shared key:dir sort codec used by both URL-state modules. - Tests: dirty-guard + flag-settle + per-key throttle scoping + superseded-push (backend); hold-repeat/suppress-click/unmount + partial-link no-clobber + refetch-doesn't-clobber-undo (frontend). Backend pytest 475, FE vitest 519.
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
// feed's URL-stripping can't delete them out from under the manager. Only non-default values are
|
||||
// emitted, so URLs stay clean. The manager's name search is deliberately EPHEMERAL (like the feed/plex
|
||||
// rail search) and stays OUT of the shared contract.
|
||||
import { parseSortState, type SortState } from "./columnSort";
|
||||
import { decodeSortParam, encodeSortParam, type SortState } from "./columnSort";
|
||||
import { type FilterMap } from "../components/tableHeader";
|
||||
|
||||
// The content state S8 mirrors. `view` is the raw tab string (the provider clamps it); `sort` is the
|
||||
@@ -20,18 +20,8 @@ export interface ChannelsUrlState {
|
||||
const KEYS = ["ctab", "csort", "cf"] as const;
|
||||
const VIEWS = ["subscribed", "discovery", "hidden", "blocked"];
|
||||
|
||||
// Sort ⇄ `key:dir` (e.g. `subscribers:desc`). Column sort keys are simple words (no colon), so a plain
|
||||
// split is safe. There is no default sort (fallback null), so any sort emits.
|
||||
function encodeSort(s: SortState): string | null {
|
||||
return s ? `${s.key}:${s.dir}` : null;
|
||||
}
|
||||
function decodeSort(raw: string): SortState {
|
||||
const i = raw.indexOf(":");
|
||||
const key = i === -1 ? raw : raw.slice(0, i);
|
||||
const dir = i === -1 ? "asc" : raw.slice(i + 1);
|
||||
return parseSortState({ key, dir });
|
||||
}
|
||||
|
||||
// Sort ⇄ `key:dir` (e.g. `subscribers:desc`) via the shared codec in columnSort. There is no default
|
||||
// sort here (fallback null), so any sort emits — no default short-circuit needed.
|
||||
// The FilterMap mixes scalar column filters (priority=select, name=text, the sync chip) with array ones
|
||||
// (tags=multi), so it's carried as a compact JSON blob rather than a nested query string — JSON round-
|
||||
// trips both shapes exactly (and future multi columns for free), and the values are opaque enum/id
|
||||
@@ -72,7 +62,7 @@ export function channelsParamUpdates(
|
||||
if (!state) return { ctab: null, csort: null, cf: null };
|
||||
return {
|
||||
ctab: state.view && state.view !== "subscribed" ? state.view : null,
|
||||
csort: encodeSort(state.sort),
|
||||
csort: encodeSortParam(state.sort),
|
||||
cf: encodeFilters(state.filters),
|
||||
};
|
||||
}
|
||||
@@ -92,7 +82,7 @@ export function channelsParamsToState(
|
||||
const csort = params.get("csort");
|
||||
return {
|
||||
view: tab && VIEWS.includes(tab) ? tab : base.view,
|
||||
sort: csort != null ? (decodeSort(csort) ?? base.sort) : base.sort,
|
||||
sort: csort != null ? (decodeSortParam(csort) ?? base.sort) : base.sort,
|
||||
filters: params.has("cf") ? decodeFilters(params.get("cf") ?? "") : base.filters,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user