Files
siftlode/frontend/src/lib/plexUrlState.ts
T
peter 4aaaaa7860 chore(comments): drop epic, sprint and review-round ids from the code
The code cited its plan instead of saying why: `E28 review J4`, `(R6 S3b)`, `S1 justified…`,
`deep-review round 5`. That plan lives in the private ops repo, so for every reader of this one the
citation was noise, and a sentence whose subject was the plan step ("round 2 showed…") hid its
lesson behind an id. Each one is gone and the "why" kept: a bare citation is deleted, a sentence
carrying a lesson is reworded around it ("a later attempt showed…").

Measured (`scripts/audit_metrics.py`, product code): 652 lines in 270 files carried an epic code by
the definition the next commit holds (507 / 241 by the old, two-digit-only one) -> 0. Across all
code, tests, scripts and configs: ~1100 epic-code lines plus ~320 sprint/round ids.

Behaviour unchanged, checked mechanically: every changed .py file has the same AST once docstrings
are blanked; css/sh/toml/yml/json files are identical once comments are stripped; the only
non-comment changes in TS/JS are test titles, one description string in a contract table, and five
ESLint messages, whose text is all the gates snapshot diff holds. Not touched: Markdown (docs and
the changelog keep their history), Alembic migrations, TV episode notation (`S2 · E5`), DESIGN.md
ids (`C1`, `B4`, `S3h`), tracker ids (`SEC-16`, `DX-17`).

One stale claim fixed on the way: test_hls_reaper's docstring said the player checkpoints "every
10 s whether or not it is playing", which plex/stream.py calls wrong; it now says what the code does.
2026-10-05 00:19:39 +02:00

68 lines
3.3 KiB
TypeScript

// Serialize the Plex-browse content state (library scope + watch-state + sort + metadata filters) to/
// from the address bar so a Plex-browse view is bookmarkable/shareable — the analogue of the feed's
// urlState. Keys are NAMESPACED (`pscope`/`psort`/`pshow`/`pf`) rather than reusing the feed's bare
// `scope`/`sort`/`show`: FeedFiltersProvider actively STRIPS its own KEYS from the URL whenever the app
// is off the feed page, so a shared key would be deleted out from under the Plex page. The namespaced
// keys are Plex-unique (like `?v=`/`?pl`), so no other provider ever reads or clears them. Only non-
// default values are emitted, so URLs stay clean.
import { parsePlexFilters, plexFiltersToParams, type PlexFilters } from "./api";
import { decodeSortParam, encodeSortParam, type SortState } from "./columnSort";
import { PLEX_SORT_FALLBACK } from "./plexSort";
// The content state this mirrors. `sort` is the RAW provider SortState (scope-healed only at the point of
// use), so the user's real preference round-trips through the URL.
export interface PlexUrlState {
scope: string;
sort: SortState;
show: string;
filters: PlexFilters;
}
const KEYS = ["pscope", "psort", "pshow", "pf"] as const;
const SCOPES = ["movie", "show", "both"];
const SHOWS = ["all", "unwatched", "in_progress", "watched"];
// Sort ⇄ `key:dir` (e.g. `year:desc`) via the shared codec in columnSort; the Plex default (`title:asc`)
// emits nothing.
function encodeSort(s: SortState): string | null {
if (!s || (s.key === PLEX_SORT_FALLBACK?.key && s.dir === PLEX_SORT_FALLBACK?.dir)) return null;
return encodeSortParam(s);
}
/** The address-bar delta for the Plex content state: each namespaced key mapped to its emitted value,
* or null to delete it (so a control back at its default clears the key). Pass `null` to clear ALL Plex
* keys — used when the app is off the Plex page (localStorage still holds the state; it re-mirrors on
* return). Feed the result into urlWithParams(). */
export function plexParamUpdates(state: PlexUrlState | null): Record<string, string | null> {
if (!state) return { pscope: null, psort: null, pshow: null, pf: null };
const pf = plexFiltersToParams(state.filters).toString();
return {
pscope: state.scope && state.scope !== "both" ? state.scope : null,
psort: encodeSort(state.sort),
pshow: state.show && state.show !== "all" ? state.show : null,
pf: pf || null,
};
}
/** True if the query string carries any Plex content key we recognize (a shared Plex-browse link). */
export function hasPlexParams(params: URLSearchParams): boolean {
return KEYS.some((k) => params.has(k));
}
/** Merge the Plex content keys from the URL over a base state (the localStorage-seeded defaults); an
* absent or invalid key keeps the base value, so a partial link (only `?psort=`) is honoured. */
export function plexParamsToState(params: URLSearchParams, base: PlexUrlState): PlexUrlState {
const scope = params.get("pscope");
const show = params.get("pshow");
const psort = params.get("psort");
const sort = psort != null ? (decodeSortParam(psort) ?? base.sort) : base.sort;
return {
scope: scope && SCOPES.includes(scope) ? scope : base.scope,
sort,
show: show && SHOWS.includes(show) ? show : base.show,
filters: params.has("pf")
? parsePlexFilters(new URLSearchParams(params.get("pf") ?? ""))
: base.filters,
};
}