import { createStore } from "./store"; // The debug recorder's app-wide state (E16) — 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.use(); // 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(null); /** The step prompt's text while it is open, else null. */ export const useStepPrompt = (): string | null => stepPrompt.use(); 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(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.use(); // 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 { try { const raw = JSON.parse(sessionStorage.getItem(STEP_KEY) ?? "{}") as unknown; return raw && typeof raw === "object" ? (raw as Record) : {}; } catch { return {}; } } const steps = createStore>(readSteps()); /** How many steps session `id` has marked in this tab. */ export const useStepCount = (id: string): number => steps.use()[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; }