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.
This commit is contained in:
2026-10-05 00:19:39 +02:00
parent c73d8be094
commit 4aaaaa7860
669 changed files with 1831 additions and 1904 deletions
+3 -3
View File
@@ -31,11 +31,11 @@ export interface Me {
/** Admin-only: false when SMTP is incomplete, so every outbound message is silently skipped. */
email_configured: boolean;
preferences: Record<string, unknown>;
/** The consented diagnostics level in force and when it lapses (E22 S5). */
/** The consented diagnostics level in force and when it lapses. */
diagnostics: DiagnosticsState;
/** The debug session recording right now, if any (E16) — a reload resumes it. */
/** The debug session recording right now, if any — a reload resumes it. */
debug_session: DebugSession | null;
/** Where a bug report goes — the tracker's repository — or null: download only (E16 S4). */
/** Where a bug report goes — the tracker's repository — or null: download only. */
issues: { repo: string } | null;
}
+5 -5
View File
@@ -55,7 +55,7 @@ export interface ConfigItem {
}
export interface SystemConfigData {
groups: Record<string, ConfigItem[]>;
/** The page's headings, in order, each with its groups in order; every group is in one (E31). */
/** The page's headings, in order, each with its groups in order; every group is in one. */
sections: { id: string; groups: string[] }[];
secrets_manageable: boolean;
}
@@ -74,14 +74,14 @@ export interface AdminUserRow {
* account) from "we took it away" — different problems needing different help. */
password_revoked_at: string | null;
has_google: boolean;
/** The Google account's address, ONLY when it differs from the account address (E9.5 S3). */
/** The Google account's address, ONLY when it differs from the account address. */
google_email: string | null;
created_at: string | null;
/** Last sign-in (Google or password). Null = not since 0.88 (no backfill). */
last_login_at: string | null;
}
/** The account panel's view of why someone can or cannot get in (E12 S5). */
/** The account panel's view of why someone can or cannot get in. */
export interface AdminUserAccess extends AdminUserRow {
/** The address passes the invite gate — so Google sign-in will be let through, not filed as a
* fresh access request. */
@@ -193,7 +193,7 @@ export interface SchedulerJob {
last_started: string | null;
last_finished: string | null;
last_result: string | null; // the English one-liner — the fallback for a job with no `result`
// The last result as data (E30): counts, a `skipped` / `reason` word, a nested dict per phase —
// The last result as data: counts, a `skipped` / `reason` word, a nested dict per phase —
// named in the reader's language by lib/jobResult.
result: JobResult | null;
last_error: string | null;
@@ -241,7 +241,7 @@ export const adminApi = {
req(`/api/admin/config/${key}`, { method: "DELETE" }),
testEmail: (): Promise<{ sent: boolean; to: string }> =>
req("/api/admin/config/test-email", { method: "POST" }),
/** The bug-report tracker (E16): answers, takes issues, and is private. */
/** The bug-report tracker: answers, takes issues, and is private. */
testIssues: (): Promise<{ ok: true; repo: string; url: string | null }> =>
req("/api/admin/config/test-issues", { method: "POST" }),
// --- users & roles ---
+3 -3
View File
@@ -6,7 +6,7 @@ import { req, uiLang } from "./core";
/** Why password sign-in refused. The three account-shaped reasons name the mailbox and both gates,
* so the card can render an honest state instead of a red line; `invalid` and `rate_limited` answer
* BEFORE the password is proven and therefore carry nothing (they must stay identical for a real
* account and an unknown one). Since E9.5 S1 the gates are order-independent — read both flags, never
* account and an unknown one). The gates are order-independent — read both flags, never
* infer one from the other. */
export type LoginBlocked = {
reason: "email_unverified" | "pending_approval" | "suspended" | "invalid" | "rate_limited";
@@ -101,8 +101,8 @@ export const authApi = {
// Confirm an email-verification token the SPA read from the URL fragment (SB3).
// `password_reset_required` = SEC-10 dropped the password this account registered with, so signing
// in would fail with the uniform credentials error. The card sends the user to the reset flow instead
// of leaving them to guess. Rare since E9.5 S1: only accounts stripped before that change, and the
// Google-adoption path, still reach it.
// of leaving them to guess. Rare now: only accounts stripped before the gates became
// order-independent, and the Google-adoption path, still reach it.
// `email` is the address that was just confirmed — echoed back so the banner can name it, which
// matters when the browser holds a session for a DIFFERENT account.
verifyEmail: (
+2 -2
View File
@@ -97,7 +97,7 @@ describe("uploadReq", () => {
// Both abort producers are asserted through `isAborted`, not through `{status: 0}`: what the
// upload dialog reads is the SENTINEL, and a cancelled upload that stops matching it becomes a
// red "the upload failed" toast. Changing either `reject` to a friendlier message left every
// test green, because the only consumer test hand-builds the error it expects (round 6).
// test green, because the only consumer test hand-builds the error it expects.
it("rejects immediately if the signal is already aborted (never sends)", async () => {
const ac = new AbortController();
ac.abort();
@@ -403,7 +403,7 @@ describe("req — what it has already put in front of the user", () => {
});
});
// E16: while a debug session records, every request path names it — the server tags ITS lines of
// While a debug session records, every request path names it — the server tags ITS lines of
// that request with it. A path that forgot the header would leave holes in the evidence exactly
// where the upload (XHR) or the unload beacon happened.
describe("the debug session a tab records into", () => {
+6 -6
View File
@@ -13,7 +13,7 @@ import { currentDebugSession, newRequestId } from "../log";
/** The active UI language, as a bare code the server understands ("en"/"hu" — never "en-GB").
* Lives here so the request builders can stamp it themselves: a signal that has to be passed in by
* every caller is one every caller can forget, which is how the registration language shipped as a
* field nobody ever filled (E9.5 S6 → S7). */
* field nobody ever filled. */
export const uiLang = (): string => i18n.language.split("-")[0] || "en";
// Errors `req` has already put in front of the user for good — the blocking error dialog, or the
@@ -24,7 +24,7 @@ export const uiLang = (): string => i18n.language.split("-")[0] || "en";
// NOT the connection notice. It says the server is unreachable, not that this action failed, and it
// removes itself on the next answer — which every caller's own resync refetch fetches at once. Marked
// as reported, a delete that hit a keepalive reset (a 502) flashed the notice for a moment and was
// then reported nowhere at all, the list still there (deep-review round 4).
// then reported nowhere at all, the list still there.
//
// A caller that reports a failure itself marks it too (`notifyActionError` does), which is what lets
// the ONE app-wide mutation reporter (lib/queryClient) stay quiet for a mutation whose own `onError`
@@ -96,7 +96,7 @@ function localizeDetail(d: unknown): string {
return t(`downloads.errors.edit.${o.reason}`, t("downloads.errors.edit.generic"));
if (o?.code === "cookies")
return t(`downloads.errors.cookies.${o.reason}`, t("downloads.errors.cookies.generic"));
// Password sign-in refusals (E9.5 S4). The card renders the account-shaped ones as a full panel
// Password sign-in refusals. The card renders the account-shaped ones as a full panel
// off `detailData`; this is the one-line form every other consumer (and the card's own fallback)
// gets. A reason this build doesn't know falls through to the generic message rather than showing
// a raw key, which is what an i18n miss would otherwise put on screen.
@@ -109,7 +109,7 @@ function localizeDetail(d: unknown): string {
return o.reason === "weak_password"
? t("welcome.auth.weakPassword", { min: o.limit ?? o.min })
: t("welcome.auth.deadLink.short");
// Local-upload ingest (E7 S5): the per-file cap carries the limit in bytes; empty is a bare code.
// Local-upload ingest: the per-file cap carries the limit in bytes; empty is a bare code.
if (o?.code === "upload_too_large")
return t("downloads.errors.upload.tooLarge", { size: formatBytes(o.limit, i18n.language) });
if (o?.code === "upload_empty") return t("downloads.errors.upload.empty");
@@ -136,7 +136,7 @@ function localizeDetail(d: unknown): string {
// from that address" — so an unknown reason falls back to the generic refusal rather than a key.
if (o?.code === "url_blocked")
return t(`downloads.errors.urlBlocked.${o.reason}`, t("downloads.errors.urlBlocked.generic"));
// E10 S2: the one-step "move to another list". Both reasons mean the same thing to a reader —
// The one-step "move to another list". Both reasons mean the same thing to a reader —
// the list moved under you — but they are different enough to be worth naming separately, and an
// unknown reason falls back to the plain failure rather than printing a key.
if (o?.code === "playlist_move")
@@ -267,7 +267,7 @@ export const setupHeaders = (token: string) => ({
// sessionStorage (per-tab, survives F5, not shared across tabs). null = use the session default.
// The storage key + reader live in storage.ts (so its per-account helpers can use them too).
const ACTIVE_ACCOUNT_HEADER = "X-Siftlode-Account";
// The debug session this tab is recording (E16): the server tags its own lines of the request with
// The debug session this tab is recording: the server tags its own lines of the request with
// it, and logs them at the session's level. Ignored by the server unless it is the caller's own.
const DEBUG_SESSION_HEADER = "X-Debug-Session";
+2 -2
View File
@@ -1,4 +1,4 @@
// The user's diagnostics level (E22 S5) and the browser log it lets out. Only touches core's req and
// The user's diagnostics level and the browser log it lets out. Only touches core's req and
// beacon. The streaming itself — what is sent, when, and when it stops — is `lib/diagnostics.ts`.
import type { LogEvent } from "../log";
import type { ServerLogRow } from "./admin";
@@ -37,7 +37,7 @@ export const diagnosticsApi = {
sendLogEventsOnUnload: (events: LogEvent[]): void => beacon(DIAGNOSTICS_EVENTS_PATH, { events }),
};
// --- debug sessions (E16) ----------------------------------------------------------------------
// --- debug sessions ----------------------------------------------------------------------
/** The two levels a recording can run at — it always records more than the base. */
export type DebugSessionLevel = "DEBUG" | "TRACE";
+1 -1
View File
@@ -51,7 +51,7 @@ describe("discoverApi query strings", () => {
});
});
describe("the reader's language (E30)", () => {
describe("the reader's language", () => {
it("asks for Hungarian Plex cards on every Home read, and leaves English implied", async () => {
await discoverApi.discover("any", 0, "hu");
expect(lastUrl()).toContain("lang=hu");
+2 -2
View File
@@ -1,4 +1,4 @@
// The Home surface ("Mit nézzek most?", E24): one hero suggestion, a cross-source resume rail, and
// The Home surface ("Mit nézzek most?"): one hero suggestion, a cross-source resume rail, and
// the comfort shelf. Types mirror `backend/app/discover/` — the card carries the NATIVE feed / Plex
// payload under `video` / `plex`, so the page renders through the existing VideoCard /
// PlexPosterCard instead of a third card component.
@@ -71,7 +71,7 @@ export interface DiscoverPayload {
}
export const discoverApi = {
// `lang` "hu" serves a Plex card's Hungarian title (E30); en is the server default.
// `lang` "hu" serves a Plex card's Hungarian title; en is the server default.
discover: (budget: DiscoverBudget, seed: number, lang?: string): Promise<DiscoverPayload> =>
req(`/api/discover?budget=${budget}&seed=${seed}${lang === "hu" ? "&lang=hu" : ""}`),
// The "Mást kérek" reroll: a new hero alone, so pressing it does not re-fetch the rails under the
+1 -1
View File
@@ -194,7 +194,7 @@ export const downloadsApi = {
spec?: DownloadSpec;
display_name?: string;
}): Promise<DownloadJob> => req("/api/downloads", { method: "POST", body: JSON.stringify(body) }),
// Ingest a local file into the library (E7 S5). Sent via XHR rather than `req` so the dialog can
// Ingest a local file into the library. Sent via XHR rather than `req` so the dialog can
// show upload progress; any file type is accepted (media gets the full function set, a generic
// file is download-only). Returns the already-`done` job the ingest creates.
// The body IS the file, and the two names ride as headers. Not a style choice: a multipart part is
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, it, expect, beforeAll } from "vitest";
// The R7.5 guard: lib/api.ts is a spread-only façade over per-domain slice modules. These tests
// The façade guard: lib/api.ts is a spread-only façade over per-domain slice modules. These tests
// mechanise the two invariants that make that structure safe — so a future edit that reintroduces
// the interleave (a method dropped into two slices → a silent last-wins spread override) or bloats
// the barrel (a method defined inline instead of in a domain module) fails CI instead of shipping.
+1 -1
View File
@@ -1,4 +1,4 @@
// E32 S2: lib/api/feed was barely covered, and it is where a filter turns into a query string.
// Lib/api/feed was barely covered, and it is where a filter turns into a query string.
// FastAPI IGNORES a query parameter it does not know, so a misspelt or renamed key does not fail —
// the filter simply stops applying while its chip still shows. Locked two ways: what each filter
// emits, and that every key emitted is one the backend's feed endpoints actually read.
+2 -2
View File
@@ -99,7 +99,7 @@ export interface FeedFilters {
year?: number;
}
/** One year's video count for the year control's chips + "more…" dialog (item 8 / E8·S2). */
/** One year's video count for the year control's chips + "more…" dialog. */
export interface YearCount {
year: number;
count: number;
@@ -169,7 +169,7 @@ export const feedApi = {
// the separate feedCount + facets the feed and sidebar used to fire on every filter change (PERF-13).
feedSummary: (f: FeedFilters): Promise<{ count: number; facets: Record<string, number> }> =>
req(`/api/feed/summary?${filterParams(f).toString()}`),
// Per-year video counts for the year control (item 8 / E8·S2). The backend drops the publish-date
// Per-year video counts for the year control. The backend drops the publish-date
// narrowing, so callers key this on the date-stripped filters (picking a year mustn't refetch it).
feedYears: (f: FeedFilters): Promise<{ years: YearCount[] }> =>
req(`/api/feed/years?${filterParams(f).toString()}`),
+8 -8
View File
@@ -55,8 +55,8 @@ export interface PlexCard {
show_id?: string | null; // episode: the show's rating_key (for grouping a playlist by show/season)
summary?: string | null; // movie/show hover-overview synopsis (trimmed) + episode summary
}
// TMDB enrichment block (E5 S2), served from OUR DB (never a live TMDB call) on a movie/show detail.
// null until the row has been enriched. Powers the S5 enriched detail page: multi-source ratings row,
// TMDB enrichment block, served from OUR DB (never a live TMDB call) on a movie/show detail.
// null until the row has been enriched. Powers the enriched detail page: multi-source ratings row,
// facts sidebar, keyword chips (→ filter), and the full crew.
interface PlexTmdbCrew {
name: string;
@@ -73,7 +73,7 @@ export interface PlexTmdb {
keywords: string[];
crew: PlexTmdbCrew[];
studios: string[];
// ISO code (null on a row enriched before E30 whose name TMDB's list lacked) + TMDB's English name.
// ISO code (null on an older enriched row whose name TMDB's list lacked) + TMDB's English name.
production_countries: { code: string | null; name: string }[];
recommendations: { tmdb_id?: number | null; title?: string | null; in_library: boolean }[];
backdrops: string[];
@@ -133,7 +133,7 @@ export interface PlexMarker {
start_s: number;
end_s: number;
}
// What the player names an audio/subtitle track from (E30, `lib/vocab` trackLabel). Plex's own title
// What the player names an audio/subtitle track from (`lib/vocab` trackLabel). Plex's own title
// (`label`) is in the Plex SERVER's language, so it is only the fallback for a track with no language tag.
export interface PlexTrackNaming {
ord: number;
@@ -239,7 +239,7 @@ export interface PlexPersonCard {
export interface PlexPersonDetail {
name: string;
photo?: string | null; // cached headshot, or null (placeholder)
// TMDB bio (E5 S6), fetched ahead of time by the people_enrich job (E30 S6) and served from the row.
// TMDB bio, fetched ahead of time by the people_enrich job and served from the row.
// Null when the person has no linked tmdb_person_id (never appeared in a TMDB-enriched title), TMDB
// has none, or it is still on its way (`enriching`).
biography?: string | null;
@@ -270,7 +270,7 @@ export interface PlexFilters {
actorMode?: "any" | "all"; // how multiple actors combine (default any = OR)
studios: string[]; // OR — from any selected studio
playable: string[]; // OR — playback class (direct | remux | transcode_hw | transcode_sw); movie-only
keywords: string[]; // AND — TMDB keyword chips from the detail page (E5 S5)
keywords: string[]; // AND — TMDB keyword chips from the detail page
collections: string[]; // Plex collection rating_keys; a title can be in several
collectionMode?: "any" | "all"; // how multiple collections combine (default any = OR)
collectionTitles?: Record<string, string>; // rating_key → title, for the active-filter chips
@@ -400,7 +400,7 @@ export function parsePlexFilters(p: URLSearchParams): PlexFilters {
export interface PlexFacets {
genres: { value: string; count: number }[];
content_ratings: { value: string; count: number }[];
studios: { value: string; count: number }[]; // studio vocabulary for the sidebar picker (E5 S7)
studios: { value: string; count: number }[]; // studio vocabulary for the sidebar picker
// Playback-class facet (movie-only): value ∈ direct|remux|transcode_hw|transcode_sw. Empty on shows.
playable: { value: string; count: number }[];
year_min: number | null;
@@ -418,7 +418,7 @@ export interface PlexPlaySession {
tonemap?: boolean; // hls HW transcode only: 10-bit/HDR source tone-mapped HDR→SDR on the GPU (OpenCL)
}
// Discover surface (E5 S4): the consolidated rail payload. Every rail is a plain PlexCard list the
// Discover surface: the consolidated rail payload. Every rail is a plain PlexCard list the
// dedicated Discover landing renders as a horizontal Carousel; `because_you_watched` is null when the
// user has no TMDB-enriched watch history (rail hidden). Served from DB — no live Plex/TMDB call.
export interface PlexDiscover {
+1 -1
View File
@@ -6,7 +6,7 @@ import { plexFiltersToParams, type PlexFilters } from "./plex";
// FastAPI ignores a query parameter it does not know, so a Plex filter key the frontend misspells or
// the backend renames does not fail anywhere — the filter silently stops narrowing while its chip
// still shows (E32 S2, the sibling of lib/api/feed.test.ts). Every key the filter set can put on the
// still shows (the sibling of lib/api/feed.test.ts). Every key the filter set can put on the
// wire must be a field of the backend's LibraryFilters, which /library and /facets both inject.
// Required: a new filter field fails tsc here until it has a value, so the checks below see its key.
const EVERYTHING: Required<PlexFilters> = {
+1 -1
View File
@@ -1,6 +1,6 @@
// @vitest-environment jsdom
//
// E30: every Plex read that returns cards carries the reader's language, so the server can serve the
// Every Plex read that returns cards carries the reader's language, so the server can serve the
// Hungarian titles. The grid and the detail pages always did; these reads did not.
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { plexApi } from "./plex";
+1 -1
View File
@@ -10,7 +10,7 @@ export interface Tag {
channel_count: number;
}
// One suggested user-tag for a channel, learned from the user's own manual-tag pattern (E8·S4).
// One suggested user-tag for a channel, learned from the user's own manual-tag pattern.
interface TagSuggestion {
tag_id: number;
confidence: number;