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.
78 lines
3.5 KiB
TypeScript
78 lines
3.5 KiB
TypeScript
import { createStore } from "./store";
|
|
|
|
// The debug recorder's app-wide state — what several surfaces open or read, held outside any
|
|
// one of them: Settings, the command palette and the recorder itself all open the start dialog, and
|
|
// the composer opens for whichever session just ended.
|
|
|
|
const startOpen = createStore(false);
|
|
/** Open the start dialog (Settings → Diagnosztika, the palette). */
|
|
export const openRecorderStart = (): void => startOpen.set(true);
|
|
export const closeRecorderStart = (): void => startOpen.set(false);
|
|
export const useRecorderStartOpen = (): boolean => startOpen.useValue();
|
|
|
|
// The pill's step prompt — open, and what is typed in it — held here, not in the pill: the pill
|
|
// rides the topmost toast host, and a dialog or the player opening or closing moves it to another
|
|
// one (a remount). The prompt must come along with what was typed.
|
|
const stepPrompt = createStore<string | null>(null);
|
|
/** The step prompt's text while it is open, else null. */
|
|
export const useStepPrompt = (): string | null => stepPrompt.useValue();
|
|
export const setStepPrompt = (text: string | null): void => stepPrompt.set(text);
|
|
|
|
// Other tabs of this browser: a Start or a Stop in one is news to the rest, which otherwise learn of
|
|
// it only at their next `me` poll — and meanwhile record into a session that has ended.
|
|
const CHANNEL = "siftlode.debugSession";
|
|
let channel: BroadcastChannel | null | undefined;
|
|
function tabs(): BroadcastChannel | null {
|
|
if (channel === undefined)
|
|
channel = typeof BroadcastChannel === "undefined" ? null : new BroadcastChannel(CHANNEL);
|
|
return channel;
|
|
}
|
|
/** Tell the other tabs the recording started or stopped. */
|
|
export const announceSessionChange = (): void => tabs()?.postMessage("changed");
|
|
/** Run `fn` when another tab started or stopped a recording. */
|
|
export function onSessionChangeElsewhere(fn: () => void): () => void {
|
|
const c = tabs();
|
|
if (!c) return () => {};
|
|
const handler = () => fn();
|
|
c.addEventListener("message", handler);
|
|
return () => c.removeEventListener("message", handler);
|
|
}
|
|
|
|
const composing = createStore<string | null>(null);
|
|
/** Open the issue composer for session `id` — after Stop, when the cap ends it, from Settings. */
|
|
export const openComposer = (id: string): void => composing.set(id);
|
|
export const closeComposer = (): void => composing.set(null);
|
|
export const useComposerSession = (): string | null => composing.useValue();
|
|
|
|
// Step markers are numbered per session. The count survives a reload (sessionStorage, per tab): a
|
|
// reproduction is often "reload and it happens again", and step 4 coming back as step 1 would make
|
|
// the list the issue is drafted from contradict itself.
|
|
const STEP_KEY = "siftlode.debugSteps";
|
|
|
|
function readSteps(): Record<string, number> {
|
|
try {
|
|
const raw = JSON.parse(sessionStorage.getItem(STEP_KEY) ?? "{}") as unknown;
|
|
return raw && typeof raw === "object" ? (raw as Record<string, number>) : {};
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
const steps = createStore<Record<string, number>>(readSteps());
|
|
|
|
/** How many steps session `id` has marked in this tab. */
|
|
export const useStepCount = (id: string): number => steps.useValue()[id] ?? 0;
|
|
|
|
/** Take the next step number for session `id`. */
|
|
export function nextStep(id: string): number {
|
|
const n = (steps.get()[id] ?? 0) + 1;
|
|
// Only this session's count is kept: the others ended, and their numbers are in their events.
|
|
steps.set({ [id]: n });
|
|
try {
|
|
sessionStorage.setItem(STEP_KEY, JSON.stringify(steps.get()));
|
|
} catch {
|
|
// Storage off: the count lives until the reload, which is all it is for.
|
|
}
|
|
return n;
|
|
}
|