feat(diagnostics): the debug recorder — start, the pill, steps, screenshots, stop (E16 S2)
- the start dialog is the consent: level (Részletes/Teljes), what is recorded, retention and the cap (now served as diagnostics.session_minutes) - the pill docks under the toasts: elapsed time, Step (an optional label, its own Escape layer), Screenshot (tab capture where the browser has it), Stop; compact on a phone - while recording, lib/log tags every event and every request path (req, beacon, uploadReq via one identityHeaders) names the session; applyDiagnostics raises the level for the session's run and never lowers a raised one, and lapses at whichever ends first - Stop sends what is waiting before the server closes the session; the cap says so and opens the composer - Settings → Diagnosztika and the command palette start/step/stop it - the evidence leaves the log upload's own request lines out - the step shortcut is deliberately palette-only: the keymap's D1 rule bars a new modifier chord, and a bare letter collides with the players'
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
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();
|
||||
|
||||
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.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<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.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;
|
||||
}
|
||||
Reference in New Issue
Block a user