Redesign the study side: Outline, Study, Script and Export stages
Build and Push Docker Image / build-and-push (push) Successful in 4m15s

Studies now move through a stage bar in the reader's themes. Outline splits
chunks by clicking between verses (join merges notes) and suggests chunks from
section headings. Study is three panes: outline, passage with an Observe /
Interpret / Apply accordion, and commentary / word study / cross-refs / people
& places for the chunk; original words are added by clicking them in the
interlinear. Script drafts an outline from notes; Export holds files and the
share link. Home, Import and the Pronunciation guide are restyled. Study
projects are BSB only.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-09-29 10:13:46 -04:00
1 parent b1bc1c8ea4
commit 31abee0695
31 files changed
+1911 -2460

No files matched your search

@@ -0,0 +1,56 @@
// Commentary for the chunk's verse range, from seven open sources. Some sources
// comment on whole passages, so when nothing starts inside the range we show the
// closest earlier entry. "Add to notes" quotes a paragraph, with its citation, into
// the chunk's background notes.
import { COMMENTARIES } from '../../hooks/useVerseStudy.js';
import { formatChunkReference } from '../../utils/bibleUtils.jsx';
export default function CommentaryChunkTab({ resources, chunk, project, chapterIndex, updateChunk, notify }) {
const { commentarySource, setCommentarySource, commentaryData, commentaryLoading, commentaryError } = resources;
const source = COMMENTARIES.find((c) => c.id === commentarySource) ?? COMMENTARIES[0];
const items = (commentaryData?.chapter?.content ?? []).filter((i) => i.type === 'verse').sort((a, b) => a.number - b.number);
let entries = items.filter((i) => i.number >= chunk.startVerse && i.number <= chunk.endVerse);
let earlier = false;
if (entries.length === 0) {
const before = [...items].reverse().find((i) => i.number <= chunk.startVerse);
if (before) { entries = [before]; earlier = true; }
}
const ref = formatChunkReference(project, chapterIndex, chunk, '–');
const quote = (text, verse) => {
const citation = `${text} (${source.name}, ${ref.split(':')[0]}:${verse})`;
const existing = chunk.generalNotes ?? '';
updateChunk(chunk.id, { generalNotes: existing ? `${existing}\n\n${citation}` : citation });
notify('Added to background notes.', 1600);
};
return (
<div>
<select aria-label="Commentary source" value={source.id} onChange={(e) => setCommentarySource(e.target.value)}
className="mb-3 w-full rounded-lg border border-[var(--r-line)] bg-[var(--r-bg)] px-2 py-1.5 text-sm text-[var(--r-text)]">
{COMMENTARIES.map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}
</select>
{commentaryLoading && <p className="text-sm text-[var(--r-sub)]">Loading commentary…</p>}
{commentaryError && <p className="text-sm text-[var(--r-sub)]">{commentaryError}</p>}
{!commentaryLoading && !commentaryError && commentaryData && entries.length === 0 && (
<p className="text-sm text-[var(--r-sub)]">No commentary found for {ref}.</p>
)}
{!commentaryLoading && !commentaryError && entries.map((v) => (
<div key={v.number} className="mb-4">
<p className="mb-1 text-xs font-medium text-[var(--r-sub)]">{earlier ? `Notes on the passage starting at verse ${v.number}` : `Verse ${v.number}`}</p>
{(v.content ?? []).map((p, i) => {
const text = typeof p === 'string' ? p : '';
if (!text) return null;
return (
<div key={i} className="group mb-2">
<p className="whitespace-pre-line text-[13px] leading-relaxed">{text}</p>
<button type="button" onClick={() => quote(text, v.number)} className="mt-0.5 text-[11px] text-[var(--r-accent)] opacity-70 hover:underline hover:opacity-100">Add to notes</button>
</div>
);
})}
</div>
))}
<p className="mt-2 text-[10px] text-[var(--r-sub)]">{source.credit}</p>
</div>
);
}