export type Note = { id: string; text: string; x: number; y: number; author: string; color: string; }; /** Shape accepted when writing a note; positional and color fields fall back to defaults. */ export type NoteInput = { id: string; text: string; author: string; x?: number; y?: number; color?: string; sourceMeta?: Record; }; export type Cluster = { label: string; noteIds: string[]; }; export type ClusterResponse = { clusters: Cluster[]; score: number; }; export type ValidationResult = { valid: boolean; reasons: string[]; }; /** noteId to embedding vector. */ export type EmbeddingMap = Map; const isStringArray = (value: unknown): value is string[] => Array.isArray(value) && value.every((entry) => typeof entry === 'string'); /** * Runtime guard for LLM output, which arrives as parsed JSON of unknown shape. * Structural correctness beyond this (complete coverage, no duplicates) is the * job of validateStructure. */ export const isClusterArray = (value: unknown): value is Cluster[] => { if (!Array.isArray(value)) return false; return value.every((entry) => { if (typeof entry !== 'object' || entry === null) return false; const candidate = entry as Record; return typeof candidate.label === 'string' && isStringArray(candidate.noteIds); }); };