- Commentary and study aids wait for 15 words of your own Observe notes. - Insights page: this week's own work, themes across studies, words you keep studying, what slipped in recalls, open follow-ups. - Recall can record what slipped; session notes (teach it back, who came, group notes, follow-ups); "Pray about this" from Apply. - Compare any two passages side by side. - Handout layouts: standard, questions only, large print, note-taking. - Search understands "in <book>", "last week/month/year" and a year. - Home shows the last backup age and nudges after two weeks. - Phone: the stage bar scrolls instead of clipping Script and Export. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
83 lines
4.7 KiB
React
83 lines
4.7 KiB
React
// Home card: once a week, write what you remember of a passage you studied, then compare it with your notes.
|
|
import { useMemo, useState } from 'react';
|
|
import { useApp } from '../context/AppContext.js';
|
|
import { loadProjectById } from '../utils/projectStorage.js';
|
|
import { pickRecall } from '../utils/recall.js';
|
|
|
|
const RATE = [
|
|
{ id: 'solid', label: 'I had it' },
|
|
{ id: 'partly', label: 'Partly' },
|
|
{ id: 'forgot', label: 'Most of it slipped' },
|
|
];
|
|
const box = 'mb-4 rounded-xl border border-[var(--r-line)] bg-[var(--r-bar)] px-4 py-3 text-sm';
|
|
const noteLabels = [['observation', 'Observe'], ['interpretation', 'Interpret'], ['application', 'Apply']];
|
|
|
|
export default function WeeklyRecall() {
|
|
const { projectIndex, saveChunkRecall, openStudyChunk } = useApp();
|
|
const [step, setStep] = useState('card'); // card -> write -> compare -> done
|
|
const [text, setText] = useState('');
|
|
const [gaps, setGaps] = useState('');
|
|
const pick = useMemo(
|
|
() => pickRecall(projectIndex.map((e) => loadProjectById(e.id)).filter(Boolean)),
|
|
[projectIndex],
|
|
);
|
|
if (step === 'done') return <div role="status" className={box}>Recall saved. See you next week.</div>;
|
|
if (!pick) return null;
|
|
|
|
const finish = (rating) => {
|
|
saveChunkRecall(pick.projectId, pick.chunk.id, { at: Date.now(), text: text.trim(), rating, gaps: gaps.trim() });
|
|
setStep('done');
|
|
};
|
|
|
|
if (step === 'card') {
|
|
return (
|
|
<div className={`${box} flex flex-wrap items-center justify-between gap-3`}>
|
|
<span><span className="font-medium">Weekly recall:</span> {pick.reference} <span className="text-[var(--r-sub)]">· {pick.projectTitle}</span></span>
|
|
<button type="button" onClick={() => setStep('write')} className="rounded-full bg-[var(--r-text)] px-3 py-1 text-xs font-medium text-[var(--r-bg)]">Start</button>
|
|
</div>
|
|
);
|
|
}
|
|
return (
|
|
<section aria-label="Weekly recall" className={box}>
|
|
<p className="font-medium">{pick.reference} <span className="font-normal text-[var(--r-sub)]">· {pick.projectTitle}</span></p>
|
|
{step === 'write' ? (
|
|
<>
|
|
<p className="mt-0.5 text-xs text-[var(--r-sub)]">From memory, no peeking: what did this passage say, what did it mean, and what did it ask of you?</p>
|
|
<textarea aria-label="What you remember" value={text} onChange={(e) => setText(e.target.value)} rows={6}
|
|
className="mt-2 w-full resize-y rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] px-3 py-2 font-serif text-[15px] leading-7 focus:outline-none" />
|
|
<div className="mt-2 flex gap-2">
|
|
<button type="button" disabled={!text.trim()} onClick={() => setStep('compare')} className="rounded-full bg-[var(--r-text)] px-3 py-1 text-xs font-medium text-[var(--r-bg)] disabled:opacity-40">Show my notes</button>
|
|
<button type="button" onClick={() => { setText(''); setStep('card'); }} className="text-xs text-[var(--r-sub)] hover:underline">Not now</button>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="mt-2 grid gap-3 sm:grid-cols-2">
|
|
<div>
|
|
<p className="mb-1 text-[11px] uppercase tracking-wide text-[var(--r-sub)]">What you remembered</p>
|
|
<p className="whitespace-pre-line font-serif text-[13px] leading-relaxed">{text}</p>
|
|
</div>
|
|
<div>
|
|
<p className="mb-1 text-[11px] uppercase tracking-wide text-[var(--r-sub)]">Your notes</p>
|
|
{noteLabels.map(([f, label]) => pick.chunk[f]?.trim() && (
|
|
<p key={f} className="mb-1.5 whitespace-pre-line font-serif text-[13px] leading-relaxed"><span className="font-sans text-xs font-medium">{label}: </span>{pick.chunk[f]}</p>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<label className="mt-3 block text-xs text-[var(--r-sub)]">What did you miss or get wrong? <span className="opacity-70">(one per line; collected in Insights)</span>
|
|
<textarea aria-label="What slipped" value={gaps} onChange={(e) => setGaps(e.target.value)} rows={2}
|
|
className="mt-1 w-full resize-y rounded-lg border border-[var(--r-line)] bg-[var(--r-bg)] px-2.5 py-1.5 text-sm text-[var(--r-text)] focus:outline-none" />
|
|
</label>
|
|
<p className="mt-3 text-xs text-[var(--r-sub)]">How did you do?</p>
|
|
<div className="mt-1 flex flex-wrap gap-2">
|
|
{RATE.map((r) => (
|
|
<button key={r.id} type="button" onClick={() => finish(r.id)} className="rounded-full border border-[var(--r-text)] px-3 py-1 text-xs hover:bg-black/5">{r.label}</button>
|
|
))}
|
|
<button type="button" onClick={() => openStudyChunk(pick.projectId, pick.chunk.id)} className="text-xs text-[var(--r-accent)] hover:underline">Open this chunk to review</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|