Add own-work tracking, Claude hand-off check, and weekly recall
- Long pastes into Observe/Interpret/Apply are tagged as pasted; notes show Mine / N pasted and an own-words share. Pastes follow text through merges. - The "-> Claude" buttons on the Script stage ask first when a chunk lacks enough of the user's own (non-pasted) words. - Weekly recall card on Home: write a passage from memory, compare with notes, self-rate; recalls are saved on the chunk and synced. - Recall history on Home with an eight-week strip and the full list. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
edc5a65d2b
commit
0c8a632cff
11 files changed
+429
-5
No files matched your search
@@ -11,6 +11,7 @@ import { useToast } from '../components/Toasts.jsx';
|
||||
import StudioShell from '../components/study/StudioShell.jsx';
|
||||
import { formatChunkReference } from '../utils/bibleUtils.jsx';
|
||||
import { buildClaudePrompt, buildPodcastPrompt } from '../utils/exportBuilders.js';
|
||||
import { chunksNeedingWork } from '../utils/ownWork.js';
|
||||
import { buildScriptDraft, spokenMinutes, wordCount } from '../utils/scriptDraft.js';
|
||||
|
||||
const has = (s) => (s ?? '').trim().length > 0;
|
||||
@@ -23,6 +24,7 @@ export default function ScriptPage() {
|
||||
const iq = useIqAvailable();
|
||||
const [gathering, setGathering] = useState(false);
|
||||
const [presenting, setPresenting] = useState(false);
|
||||
const [heldAction, setHeldAction] = useState(null); // an AI hand-off waiting on the own-work check
|
||||
const { toast } = useToast();
|
||||
|
||||
const chunk = allChunks.find((c) => c.id === project?.selectedChunkId) ?? allChunks[0] ?? null;
|
||||
@@ -54,6 +56,11 @@ export default function ScriptPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// AI hand-offs only go straight through once every chunk has your own notes; otherwise ask first.
|
||||
const needingWork = chunksNeedingWork(allChunks);
|
||||
const guarded = (action) => () => { if (needingWork.length === 0) action(); else setHeldAction(() => action); };
|
||||
const goStudy = (id) => { select(id); setHeldAction(null); setCurrentPage('study'); };
|
||||
|
||||
// Copies a prompt, then opens claude.ai so it can be pasted straight in.
|
||||
const copyPrompt = (prompt) => {
|
||||
navigator.clipboard.writeText(prompt).then(() => {
|
||||
@@ -130,12 +137,12 @@ export default function ScriptPage() {
|
||||
<input type="file" accept=".docx" className="hidden" onChange={(e) => { importDocx(e.target.files?.[0]); e.target.value = ''; }} />
|
||||
</label>
|
||||
<span className="mx-1 h-4 w-px bg-[var(--r-line)]" />
|
||||
<button type="button" onClick={() => copyPrompt(buildClaudePrompt(project))} className={btn}
|
||||
<button type="button" onClick={guarded(() => copyPrompt(buildClaudePrompt(project)))} className={btn}
|
||||
title="Copies your study notes as a Claude prompt, then opens claude.ai">Study guide → Claude ↗</button>
|
||||
<button type="button" onClick={() => copyPrompt(buildPodcastPrompt(project, authUser?.podcastName))} className={btn}
|
||||
<button type="button" onClick={guarded(() => copyPrompt(buildPodcastPrompt(project, authUser?.podcastName)))} className={btn}
|
||||
title="Copies a podcast script prompt for Claude — set your show name in Account Settings first">Podcast → Claude ↗</button>
|
||||
{iq && (
|
||||
<button type="button" disabled={gathering} onClick={copyPodcastWithContext} className={btn}
|
||||
<button type="button" disabled={gathering} onClick={guarded(copyPodcastWithContext)} className={btn}
|
||||
title={`Adds IQ Bible commentary, cross-references and word glosses for up to ${chunkRefs(project).refs.length} passages (each uses a few API requests)`}>
|
||||
{gathering ? 'Gathering…' : 'Podcast + background → Claude ↗'}
|
||||
</button>
|
||||
@@ -145,6 +152,25 @@ export default function ScriptPage() {
|
||||
<DictateButton label="Dictate script" onText={(said) => set({ finalScript: appendSpoken(script, said) })} />
|
||||
{notice && <span role="status" className="text-xs text-[var(--r-accent)]">{notice}</span>}
|
||||
</div>
|
||||
{heldAction && (
|
||||
<div role="alert" className="rounded-xl border border-[var(--r-accent)] bg-[var(--r-hl)] p-3 text-sm">
|
||||
<p className="font-medium">Is this your study, or Claude's?</p>
|
||||
<p className="mt-0.5 text-xs text-[var(--r-sub)]">These chunks don't have much of your own thinking yet. Write it first, then let Claude polish it.</p>
|
||||
<ul className="mt-2 space-y-1 text-xs">
|
||||
{needingWork.map(({ chunk: c, missing }) => (
|
||||
<li key={c.id} className="flex items-center justify-between gap-3">
|
||||
<span>{formatChunkReference(project, project.chapters.findIndex((ch) => ch.chunks.some((x) => x.id === c.id)), c, '–')}: {missing.map((m) => `${m.label} ${m.have}/${m.need}`).join(', ')}</span>
|
||||
<button type="button" onClick={() => goStudy(c.id)} className="text-[var(--r-accent)] hover:underline">Write it →</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-2.5 flex gap-2">
|
||||
<button type="button" onClick={() => goStudy(needingWork[0].chunk.id)} className="rounded-full bg-[var(--r-text)] px-3 py-1 text-xs font-medium text-[var(--r-bg)]">Go write my notes</button>
|
||||
<button type="button" onClick={() => { const run = heldAction; setHeldAction(null); run(); }} className="rounded-full border border-[var(--r-text)] px-3 py-1 text-xs hover:bg-black/5">Send to Claude anyway</button>
|
||||
<button type="button" onClick={() => setHeldAction(null)} className="text-xs text-[var(--r-sub)] hover:underline">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<textarea aria-label="Final script" value={script} onChange={(e) => set({ finalScript: e.target.value })}
|
||||
placeholder="Write or paste the final script here. “Build outline from notes” starts you from your study notes."
|
||||
className="min-h-[24rem] flex-1 resize-y rounded-2xl border border-[var(--r-line)] bg-[var(--r-bg)] px-5 py-4 font-serif text-[16px] leading-8 text-[var(--r-text)] placeholder:font-sans placeholder:text-sm placeholder:text-[var(--r-sub)] focus:outline-none focus:ring-2 focus:ring-[var(--r-line)]" />
|
||||
|
||||
Reference in new issue
Block a user