import type { ChecklistAnnotation, EffectiveChecklistJudgement } from "../../runDirectory/annotations.js"; import { itemStatus as itemStatusOf, liveQuestions, score as scoreOf, type ItemStatus } from "./judgement.js"; import type { Annotator, ChecklistQuestion, ChecklistRevision, DeepReadonly, Fields } from "./types.js"; /** One trace being judged: its id and the named text the screen shows for it. */ export type SessionItem = { /** The run directory this item is; shown to the person and never re-derived. */ runDir: string; traceId: string; fields: Fields; }; export type { ChecklistAnnotation }; export type SessionEditor = { kind: "none"; } | { kind: "question"; draft: string; } | { kind: "note"; draft: string; }; /** * What a person can ask for. `submitEditor` and `signOff` are intents rather * than state transitions: the first has to allocate a question id and the * second has to write files, and a pure reducer does neither. The controller * turns them into the deterministic events below. */ export type SessionAction = { kind: "nextItem"; } | { kind: "previousItem"; } | { kind: "focusItem"; traceId: string; } | { kind: "nextQuestion"; } | { kind: "previousQuestion"; } | { kind: "toggleAnswer"; } | { kind: "beginQuestion"; } | { kind: "beginNote"; } | { kind: "appendEditorText"; text: string; } | { kind: "backspaceEditor"; } | { kind: "cancelEditor"; } | { kind: "submitEditor"; } | { kind: "toggleQuestionDeleted"; } | { kind: "signOff"; }; /** What actually moves the state. Every one is deterministic: no clocks, no * random ids, no filesystem. */ export type SessionEvent = Exclude | { kind: "questionAdded"; question: ChecklistQuestion; } | { kind: "noteSaved"; traceId: string; note: string; } | { kind: "revisionAdopted"; revision: ChecklistRevision; } | { kind: "annotationCommitted"; row: ChecklistAnnotation; }; export type SessionState = { items: SessionItem[]; revision: ChecklistRevision; /** Question edits staged but not yet published. The controller turns these * into a revision; the reducer never writes one. */ stagedQuestions: ChecklistQuestion[] | null; answersByTraceId: Record>; notesByTraceId: Record; /** traceId → the question ids it was signed off against. */ reviewedByTraceId: Record; itemIndex: number; questionIndex: number; editor: SessionEditor; annotator: Annotator; }; export type SessionSnapshot = { items: readonly DeepReadonly[]; itemIndex: number; questionIndex: number; currentItem: DeepReadonly | null; currentQuestion: DeepReadonly | null; questions: readonly DeepReadonly[]; answers: Readonly>; note: string; editor: DeepReadonly; statuses: Readonly>; scores: Readonly>; progress: { reviewed: number; total: number; stale: number; }; canSignOff: boolean; hasStagedQuestions: boolean; }; export type InitSessionArgs = { items: readonly SessionItem[]; revision: ChecklistRevision; /** traceId → this annotator's folded answers and latest note on this * checklist, as the run directory folds them. */ judgements: Readonly>; annotator: Annotator; }; /** Questions in play right now: the staged edit if there is one, else the * published revision. */ export declare function questionsOf(state: SessionState): ChecklistQuestion[]; export declare function initSession(args: InitSessionArgs): SessionState; export declare function currentItem(state: SessionState): SessionItem | undefined; export declare function currentQuestion(state: SessionState): ChecklistQuestion | undefined; export declare function itemStatus(state: SessionState, traceId: string): ItemStatus; export declare function sessionSnapshot(state: SessionState): SessionSnapshot; /** The answers a sign-off would record: every live question, with an untouched * box written as an explicit `false` because you looked at it and moved on. */ export declare function signOffPayload(state: SessionState): { traceId: string; coveredQuestionIds: string[]; answers: Record; note: string; } | undefined; export declare function reduceSession(state: SessionState, event: SessionEvent): SessionState; export { liveQuestions, itemStatusOf, scoreOf };