Speed up the app, run production build, remove chunk ink, start App.jsx split
Performance: - Lazy-load pages, docx, and mammoth (initial JS 1,298 KB -> ~290 KB) - Gzip responses and cache hashed assets for a year - Memoize DrawCanvas strokes so ink isn't redrawn on every render - Precompute whole-Bible search index and defer the query while typing - Reload once if a lazy chunk is missing after a deploy Deployment: - setup.sh builds and runs one production server on APP_PORT (default 5173); --dev keeps the old Vite dev setup - Session cookie uses secure: 'auto' so login works behind any proxy Chunk editor: - Remove the Draw layout, Ink Notes panel, and ink in HTML export (existing inkStrokes data is left in saved projects) Refactor: - Move module-level helpers out of App.jsx into src/utils/* - Extract Bible reader state and logic into hooks/useBibleReader Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
00932dee4a
commit
ade7f1af6f
20 files changed
+1314
-1252
No files matched your search
@@ -0,0 +1,213 @@
|
||||
// Markdown export and Claude prompt builders.
|
||||
import {
|
||||
formatChunkReference,
|
||||
getChunkVerseEntries,
|
||||
htmlToPlainText,
|
||||
} from './bibleUtils.jsx';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Export / prompt builders (exported for testing)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function buildMarkdownExport(project) {
|
||||
const chapters = Array.isArray(project.chapters) ? project.chapters : [];
|
||||
|
||||
const chapterSections = chapters.map((ch, chapterIndex) => {
|
||||
const chunkSections = ch.chunks.map((chunk) => {
|
||||
const scripture = formatChunkReference(project, chapterIndex, chunk, '-');
|
||||
const versesText = getChunkVerseEntries(project, chapterIndex, chunk)
|
||||
.map((verse) => `> **${verse.chapter}:${verse.number}** ${verse.text}`)
|
||||
.join('\n>\n');
|
||||
|
||||
const observation = (chunk.observation ?? '').trim() || '_No observation._';
|
||||
const interpretation = (chunk.interpretation ?? '').trim() || '_No interpretation._';
|
||||
const application = (chunk.application ?? '').trim() || '_No application._';
|
||||
const generalNotes = (chunk.generalNotes ?? '').trim();
|
||||
|
||||
const crossRefsLine = (chunk.crossReferences ?? []).length > 0
|
||||
? `**Cross-References:** ${chunk.crossReferences.join(', ')}\n\n`
|
||||
: '';
|
||||
|
||||
const greekSection = chunk.greekWords.length === 0
|
||||
? ''
|
||||
: `**Greek/Hebrew Words**\n\n${chunk.greekWords.map((word) => {
|
||||
const summary = [
|
||||
word.strongNumber,
|
||||
word.lexeme,
|
||||
word.transliteration && `(${word.transliteration})`,
|
||||
word.partOfSpeech,
|
||||
word.shortDefinition,
|
||||
].filter(Boolean).join(' — ');
|
||||
const definition = word.definitionHtml ? `\n ${htmlToPlainText(word.definitionHtml)}` : '';
|
||||
return `- ${summary}${definition}`;
|
||||
}).join('\n')}\n\n`;
|
||||
|
||||
return `### ${scripture}\n\n${versesText}\n\n${generalNotes ? `**Background / Notes**\n\n${generalNotes}\n\n` : ''}**Observation**\n\n${observation}\n\n**Interpretation**\n\n${interpretation}\n\n**Application**\n\n${application}\n\n${crossRefsLine}${greekSection}`;
|
||||
}).join('---\n\n');
|
||||
|
||||
return `## ${ch.book} ${ch.chapter}\n\n${chunkSections}`;
|
||||
}).join('\n');
|
||||
|
||||
return `# ${project.title}\n\n*${project.translation}*\n\n${chapterSections}`;
|
||||
}
|
||||
|
||||
export function buildChunkBodies(project) {
|
||||
const chapters = Array.isArray(project.chapters) ? project.chapters : [];
|
||||
let chunkIndex = 0;
|
||||
return chapters.map((ch, chapterIndex) => {
|
||||
return ch.chunks.map((chunk) => {
|
||||
chunkIndex += 1;
|
||||
const ref = formatChunkReference(project, chapterIndex, chunk, '–');
|
||||
|
||||
const verses = getChunkVerseEntries(project, chapterIndex, chunk)
|
||||
.map((v) => `${v.chapter}:${v.number} ${v.text}`)
|
||||
.join('\n');
|
||||
|
||||
const observation = (chunk.observation ?? '').trim() || 'No observation.';
|
||||
const interpretation = (chunk.interpretation ?? '').trim() || 'No interpretation.';
|
||||
const application = (chunk.application ?? '').trim() || 'No application.';
|
||||
|
||||
const crossRefs = (chunk.crossReferences ?? []).length > 0
|
||||
? chunk.crossReferences.join(', ')
|
||||
: 'None.';
|
||||
|
||||
const generalNotes = (chunk.generalNotes ?? '').trim();
|
||||
const episodeLabelLine = (chunk.episodeNumber ?? '').trim() || (chunk.episodeTitle ?? '').trim()
|
||||
? `Session: ${[chunk.episodeNumber?.trim(), chunk.episodeTitle?.trim()].filter(Boolean).join(' — ')}\n`
|
||||
: '';
|
||||
|
||||
const greekWords = chunk.greekWords.length === 0
|
||||
? 'None.'
|
||||
: chunk.greekWords.map((word) => {
|
||||
const summary = [
|
||||
word.strongNumber,
|
||||
word.lexeme,
|
||||
word.transliteration && `(${word.transliteration})`,
|
||||
word.partOfSpeech,
|
||||
word.shortDefinition,
|
||||
].filter(Boolean).join(' | ');
|
||||
const definition = word.definitionHtml
|
||||
? `\n ${htmlToPlainText(word.definitionHtml)}`
|
||||
: '';
|
||||
return `• ${summary}${definition}`;
|
||||
}).join('\n');
|
||||
|
||||
return `===
|
||||
|
||||
CHUNK ${chunkIndex} — ${ref}
|
||||
${episodeLabelLine}
|
||||
Scripture:
|
||||
${verses}
|
||||
${generalNotes ? `\nBackground / General Notes:\n${generalNotes}\n` : ''}
|
||||
Observation:
|
||||
${observation}
|
||||
|
||||
Interpretation:
|
||||
${interpretation}
|
||||
|
||||
Application:
|
||||
${application}
|
||||
|
||||
Cross-References: ${crossRefs}
|
||||
|
||||
Greek Words:
|
||||
${greekWords}
|
||||
`;
|
||||
}).join('\n');
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
export function buildClaudePrompt(project) {
|
||||
const chapters = Array.isArray(project.chapters) ? project.chapters : [];
|
||||
const chapterLabel = chapters.map((ch) => `${ch.book} ${ch.chapter}`).join(', ');
|
||||
|
||||
const header = `I've prepared a Bible study on ${chapterLabel} (${project.translation}) and need your help turning my notes into a polished study guide.
|
||||
|
||||
Below is my work organised by passage chunk, including my OIA notes and Greek word research. Please create a clear, structured study guide that:
|
||||
- Synthesises my notes into coherent teaching points
|
||||
- Naturally integrates the Greek word insights
|
||||
- Includes 2–3 reflection questions per chunk
|
||||
- Preserves the passage-by-passage structure
|
||||
|
||||
---
|
||||
|
||||
PROJECT: ${project.title}
|
||||
TRANSLATION: ${project.translation}
|
||||
PASSAGE: ${chapterLabel}
|
||||
`;
|
||||
|
||||
return header + buildChunkBodies(project);
|
||||
}
|
||||
|
||||
export function buildPodcastPrompt(project, podcastName) {
|
||||
const chapters = Array.isArray(project.chapters) ? project.chapters : [];
|
||||
const chapterLabel = chapters.map((ch) => `${ch.book} ${ch.chapter}`).join(', ');
|
||||
const episodeLabel = 'EPISODE';
|
||||
const showName = (podcastName ?? '').trim();
|
||||
const showLine = showName || 'MY BIBLE STUDY PODCAST';
|
||||
const signOffLine = showName
|
||||
? `Introduce yourself, then sign off with: "...and this is ${showName}."`
|
||||
: 'Introduce yourself and the name of the show as a sign-off.';
|
||||
|
||||
const header = `I'm recording an episode of my Bible study podcast${showName ? ` "${showName}"` : ''} and need a full script written from my study notes below.
|
||||
|
||||
Please write the script in this exact structure, using the section markers and tone shown:
|
||||
|
||||
${showLine.toUpperCase()}
|
||||
${episodeLabel}
|
||||
${chapterLabel} (${project.translation}) · [estimate XX–XX minutes based on content]
|
||||
|
||||
✝ OPENING PRAYER
|
||||
[Pause before opening the text — invite God into the study]
|
||||
A short prayer (4-6 sentences) tying into the themes of this passage.
|
||||
|
||||
— ✦ —
|
||||
|
||||
🎙️ COLD OPEN
|
||||
[Co-host delivers the cold open solo, if there is one — hands off to the host at the end]
|
||||
A few short punchy lines previewing the passage and its hook, ending with a hand-off line introducing the host and the show.
|
||||
|
||||
— ✦ —
|
||||
|
||||
📖 SEGMENT [N] — [SEGMENT TITLE]
|
||||
[Brief stage direction in brackets]
|
||||
One segment per passage chunk (use the chunk reference, observation, interpretation, and application notes below as the raw material). Conversational, spoken-word style — not academic prose. Work through the text the way the host would talk it through out loud, weaving in the OIA notes and cross-references naturally.
|
||||
|
||||
— ✦ —
|
||||
|
||||
📖 SEGMENT [N] — GREEK WORD STUDY
|
||||
[Work through the key terms — keep it vivid, help the words come alive]
|
||||
For each Greek/Hebrew word collected below, a block in this format:
|
||||
|
||||
[English Gloss] — [English meaning]
|
||||
[DO NOT READ: original-language word (transliteration) — part of speech]
|
||||
2-4 sentences unpacking the word's meaning and why it matters for this passage, conversational tone.
|
||||
|
||||
— ✦ —
|
||||
|
||||
💬 DISCUSSION QUESTIONS
|
||||
[From the study guide — ${chapterLabel}]
|
||||
3 reflection questions drawn from the application notes, numbered.
|
||||
|
||||
— ✦ —
|
||||
|
||||
✦ CLOSING
|
||||
[Grounded and direct — send them away with something to carry]
|
||||
A closing reflection that ties the segments together, a short pull-quote from the passage with its reference, then close out. ${signOffLine}
|
||||
"Until next time — keep studying verse by verse."
|
||||
|
||||
— End of Episode —
|
||||
|
||||
${showLine} · ${episodeLabel} · ${chapterLabel}
|
||||
|
||||
---
|
||||
|
||||
Here are my study notes, organised by passage chunk, including my OIA notes, cross-references, and Greek/Hebrew word research:
|
||||
|
||||
PROJECT: ${project.title}
|
||||
TRANSLATION: ${project.translation}
|
||||
PASSAGE: ${chapterLabel}
|
||||
`;
|
||||
|
||||
return header + buildChunkBodies(project);
|
||||
}
|
||||
Reference in new issue
Block a user