storage.ts's registry gives each key a kind (pref, device, view, cache, public); a key without one does not compile. A contract test refuses a key spelled out elsewhere, an account preference read or written straight from storage instead of through usePref, and an e2e seed of a key nobody reads. The keys the next account's wipe keeps are the public ones. The seven preferences kept only in the browser move onto usePref: start on Home, the onboarding dismissal, the release banner's last version, the channel manager's layout, the Plex playlist layout, the playlist rail's sort and channel discovery's sort. usePref reads an older bare-string copy, and a value only this browser held is saved to the server once, so the account's other devices get it. Performance mode is the device's: it is neither read from the account nor saved to it.
79 lines
3.6 KiB
TypeScript
79 lines
3.6 KiB
TypeScript
import { LS } from "./storage";
|
|
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 = LS.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;
|
|
}
|