export type ReviewStatus = "draft" | "accepted" | "rejected" | "cancelled"; export type ReviewAction = "revise" | "accept" | "reject" | "cancel"; export interface ReviewState { revision: number; status: ReviewStatus; text: string; } export const ENTRY_TYPE = "workbook-review-desk-state"; export const MESSAGE_TYPE = "workbook-review-desk"; export const MAX_TEXT_LENGTH = 12_000; export function isReviewState(value: unknown): value is ReviewState { if (typeof value !== "object" || value === null) return false; return "revision" in value && Number.isSafeInteger(value.revision) && Number(value.revision) >= 0 && "text" in value && typeof value.text === "string" && value.text.length <= MAX_TEXT_LENGTH && "status" in value && ["draft", "accepted", "rejected", "cancelled"].includes(String(value.status)); } export function transition(state: ReviewState, action: ReviewAction, text?: string): ReviewState { if (text !== undefined && (text.trim().length === 0 || text.length > MAX_TEXT_LENGTH)) { throw new Error(`Draft must contain 1–${MAX_TEXT_LENGTH} characters, including non-whitespace text.`); } if (action === "revise" && text === undefined) throw new Error("revise requires text."); if (action !== "revise" && action !== "accept" && text !== undefined) { throw new Error("Rejection and cancellation preserve the original draft."); } return { revision: state.revision + 1, status: action === "revise" ? "draft" : action === "accept" ? "accepted" : action === "reject" ? "rejected" : "cancelled", text: text ?? state.text, }; }