Add Insights, Observe-first, compare, handout layouts, richer search, backup nudge

- 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>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-14 12:42:11 -04:00
1 parent 2ddc4e1ff6
commit d8e522dd06
24 files changed
+540 -28

No files matched your search

+61
View File
@@ -0,0 +1,61 @@
// Compare two passages side by side — your notes and the verses — for parallels (the Gospels), repeated themes,
// or the same chapter studied twice. Pick any chunk from any of your studies to set beside the one you're on.
import { useMemo, useState } from 'react';
import { useApp } from '../../context/AppContext.js';
import { loadProjectById } from '../../utils/projectStorage.js';
import { formatChunkReference, getChunkVerseEntries } from '../../utils/bibleUtils.jsx';
const FIELDS = [['observation', 'Observe'], ['interpretation', 'Interpret'], ['application', 'Apply']];
// Every chunk in your studies, as a pick-list entry that remembers where it sits.
function choices(projectIndex, currentProject) {
const projects = projectIndex.map((e) => (e.id === currentProject?.id ? currentProject : loadProjectById(e.id))).filter(Boolean);
return projects.flatMap((project) => project.chapters.flatMap((chapter, chapterIndex) => chapter.chunks.map((chunk) => ({
key: `${project.id}:${chunk.id}`, project, chapterIndex, chunk,
label: `${formatChunkReference(project, chapterIndex, chunk, '–')} · ${project.title}`,
}))));
}
function Column({ entry }) {
if (!entry) return <p className="p-4 text-sm text-[var(--r-sub)]">Pick a passage to compare.</p>;
const verses = getChunkVerseEntries(entry.project, entry.chapterIndex, entry.chunk);
return (
<div className="min-w-0 space-y-3 p-4">
<h3 className="font-medium">{entry.label}</h3>
<div className="max-h-48 space-y-1 overflow-y-auto rounded-xl bg-[var(--r-bar)] p-3 font-serif text-[13px] leading-relaxed">
{verses.map((v) => <p key={v.number}><sup className="mr-1 text-[var(--r-accent)]">{v.number}</sup>{v.text}</p>)}
</div>
{FIELDS.map(([f, label]) => (
<div key={f}>
<p className="text-[11px] uppercase tracking-wide text-[var(--r-sub)]">{label}</p>
<p className="whitespace-pre-line font-serif text-[14px] leading-relaxed">{entry.chunk[f]?.trim() || <span className="text-[var(--r-sub)]">Nothing written.</span>}</p>
</div>
))}
</div>
);
}
export default function CompareSheet({ currentChunkId, onClose }) {
const { projectIndex, project } = useApp();
const all = useMemo(() => choices(projectIndex, project), [projectIndex, project]);
const mine = all.find((c) => c.chunk.id === currentChunkId && c.project.id === project?.id);
const [otherKey, setOtherKey] = useState('');
const other = all.find((c) => c.key === otherKey);
return (
<div role="dialog" aria-modal="true" aria-label="Compare passages" className="fixed inset-0 z-[70] flex flex-col bg-[var(--r-bg)] text-[var(--r-text)]">
<div className="flex shrink-0 flex-wrap items-center gap-x-3 gap-y-2 border-b border-[var(--r-line)] bg-[var(--r-bar)] px-4 py-2.5">
<h2 className="mr-auto text-sm font-medium sm:mr-0">Compare passages</h2>
<select aria-label="Compare with" value={otherKey} onChange={(e) => setOtherKey(e.target.value)}
className="order-last min-w-0 basis-full rounded-lg border border-[var(--r-line)] bg-[var(--r-bg)] px-2.5 py-1.5 text-sm sm:order-none sm:basis-0 sm:flex-1">
<option value="">Choose a passage to set beside this one…</option>
{all.filter((c) => c.key !== mine?.key).map((c) => <option key={c.key} value={c.key}>{c.label}</option>)}
</select>
<button type="button" onClick={onClose} className="rounded-lg border border-[var(--r-line)] px-3 py-1.5 text-sm hover:bg-black/5">Close</button>
</div>
<div className="grid min-h-0 flex-1 overflow-y-auto md:grid-cols-2 md:divide-x md:divide-[var(--r-line)]">
<Column entry={mine} />
<Column entry={other} />
</div>
</div>
);
}