// "Is this study my own work?" — how much of each chunk's Observe / Interpret / // Apply notes has been written by hand (long pastes don't count as your own). Used to hold back the "→ Claude" prompts // until the thinking has been done by you, not handed to AI. export const OWN_WORK_MINIMUMS = { observation: 20, interpretation: 20, application: 10 }; const LABELS = { observation: 'Observe', interpretation: 'Interpret', application: 'Apply' }; const words = (s) => (s ?? '').trim().split(/\s+/).filter(Boolean).length; // A paste this long into a note box is remembered as "not typed by me" (chunk.aiPastes[field] = [text, …]). export const PASTE_MIN_WORDS = 15; export const NOTE_FIELDS = Object.keys(OWN_WORK_MINIMUMS); // -> the chunk's new aiPastes after pasting `pasted` into `field`, or null when the paste is too short to count. export function withPaste(chunk, field, pasted) { const text = (pasted ?? '').trim(); if (words(text) < PASTE_MIN_WORDS) return null; const all = chunk?.aiPastes ?? {}; return { ...all, [field]: [...(all[field] ?? []), text] }; } // Words in `field` that came from a long paste still present in the note (delete or rewrite it and it stops counting). export function pastedWords(chunk, field) { const body = chunk?.[field] ?? ''; return (chunk?.aiPastes?.[field] ?? []).filter((t) => body.includes(t)).reduce((n, t) => n + words(t), 0); } export const ownWords = (chunk, field) => Math.max(0, words(chunk?.[field]) - pastedWords(chunk, field)); // Share of the three notes' words that are typed or dictated rather than pasted, 0–100 (null when nothing is written). export function ownShare(chunk) { const total = NOTE_FIELDS.reduce((n, f) => n + words(chunk?.[f]), 0); if (!total) return null; return Math.round((NOTE_FIELDS.reduce((n, f) => n + ownWords(chunk, f), 0) / total) * 100); } // "That's mine": stop counting the pastes in one field. export const clearPastes = (chunk, field) => ({ ...(chunk?.aiPastes ?? {}), [field]: [] }); // The note fields of one chunk that are still under their minimum word count. export function thinFields(chunk) { return Object.entries(OWN_WORK_MINIMUMS) .filter(([field, min]) => ownWords(chunk, field) < min) .map(([field]) => ({ field, label: LABELS[field], have: ownWords(chunk, field), need: OWN_WORK_MINIMUMS[field] })); } // Chunks that still need your own notes, with what's missing in each. export function chunksNeedingWork(chunks) { return (chunks ?? []).map((chunk) => ({ chunk, missing: thinFields(chunk) })).filter((entry) => entry.missing.length > 0); } // Commentary and study aids stay folded away until you've written this much of your own Observe notes. export const OBSERVE_FIRST_MIN = 15; export const observeFirstDone = (chunk) => ownWords(chunk, 'observation') >= OBSERVE_FIRST_MIN; // "No, I wrote it": take one remembered paste back out, so those words count as your own again. export function withoutPaste(chunk, field, pasted) { const text = (pasted ?? '').trim(); const all = chunk?.aiPastes ?? {}; const list = [...(all[field] ?? [])]; const at = list.lastIndexOf(text); if (at >= 0) list.splice(at, 1); return { ...all, [field]: list }; }