Make episode info, general notes, and final script per-chunk fields

Each chunk is unique, so these fields no longer share a single project-level value.
This commit is contained in:
nmemmert
2026-06-10 10:56:54 -04:00
parent 91b01c3117
commit faf9ba9e2e
2 changed files with 33 additions and 32 deletions
Binary file not shown.
+33 -32
View File
@@ -114,6 +114,10 @@ export function migrateChunk(chunk) {
return { return {
...chunk, ...chunk,
spilloverEndVerse: Number.isInteger(chunk.spilloverEndVerse) ? chunk.spilloverEndVerse : null, spilloverEndVerse: Number.isInteger(chunk.spilloverEndVerse) ? chunk.spilloverEndVerse : null,
generalNotes: chunk.generalNotes ?? '',
episodeNumber: chunk.episodeNumber ?? '',
episodeTitle: chunk.episodeTitle ?? '',
finalScript: chunk.finalScript ?? '',
}; // already new format }; // already new format
} }
return { return {
@@ -123,6 +127,10 @@ export function migrateChunk(chunk) {
application: '', application: '',
crossReferences: [], crossReferences: [],
spilloverEndVerse: null, spilloverEndVerse: null,
generalNotes: '',
episodeNumber: '',
episodeTitle: '',
finalScript: '',
notes: undefined, notes: undefined,
}; };
} }
@@ -187,10 +195,6 @@ export function migrateProject(raw) {
if (Array.isArray(raw.chapters)) { if (Array.isArray(raw.chapters)) {
return { return {
...raw, ...raw,
generalNotes: raw.generalNotes ?? '',
episodeNumber: raw.episodeNumber ?? '',
episodeTitle: raw.episodeTitle ?? '',
finalScript: raw.finalScript ?? '',
chapters: raw.chapters.map((ch) => ({ chapters: raw.chapters.map((ch) => ({
...ch, ...ch,
chunks: ch.chunks.map(migrateChunk), chunks: ch.chunks.map(migrateChunk),
@@ -205,10 +209,6 @@ export function migrateProject(raw) {
translation: raw.translation ?? 'BSB', translation: raw.translation ?? 'BSB',
lastEdited: raw.lastEdited ?? Date.now(), lastEdited: raw.lastEdited ?? Date.now(),
selectedChunkId: raw.selectedChunkId ?? null, selectedChunkId: raw.selectedChunkId ?? null,
generalNotes: raw.generalNotes ?? '',
episodeNumber: raw.episodeNumber ?? '',
episodeTitle: raw.episodeTitle ?? '',
finalScript: raw.finalScript ?? '',
chapters: [ chapters: [
{ {
book: raw.book ?? '', book: raw.book ?? '',
@@ -524,6 +524,11 @@ function buildChunkBodies(project) {
? chunk.crossReferences.join(', ') ? chunk.crossReferences.join(', ')
: 'None.'; : 'None.';
const generalNotes = (chunk.generalNotes ?? '').trim();
const episodeLabelLine = (chunk.episodeNumber ?? '').trim() || (chunk.episodeTitle ?? '').trim()
? `Episode: ${[chunk.episodeNumber?.trim(), chunk.episodeTitle?.trim()].filter(Boolean).join(' — ')}\n`
: '';
const greekWords = chunk.greekWords.length === 0 const greekWords = chunk.greekWords.length === 0
? 'None.' ? 'None.'
: chunk.greekWords.map((word) => { : chunk.greekWords.map((word) => {
@@ -543,10 +548,10 @@ function buildChunkBodies(project) {
return `=== return `===
CHUNK ${chunkIndex}${ref} CHUNK ${chunkIndex}${ref}
${episodeLabelLine}
Scripture: Scripture:
${verses} ${verses}
${generalNotes ? `\nBackground / General Notes:\n${generalNotes}\n` : ''}
Observation: Observation:
${observation} ${observation}
@@ -582,7 +587,6 @@ Below is my work organised by passage chunk, including my OIA notes and Greek wo
PROJECT: ${project.title} PROJECT: ${project.title}
TRANSLATION: ${project.translation} TRANSLATION: ${project.translation}
PASSAGE: ${chapterLabel} PASSAGE: ${chapterLabel}
${(project.generalNotes ?? '').trim() ? `\nBACKGROUND / GENERAL NOTES:\n${project.generalNotes.trim()}\n` : ''}
`; `;
return header + buildChunkBodies(project); return header + buildChunkBodies(project);
@@ -610,9 +614,7 @@ export function buildPronunciationGuide(project) {
export function buildPodcastPrompt(project) { export function buildPodcastPrompt(project) {
const chapters = Array.isArray(project.chapters) ? project.chapters : []; const chapters = Array.isArray(project.chapters) ? project.chapters : [];
const chapterLabel = chapters.map((ch) => `${ch.book} ${ch.chapter}`).join(', '); const chapterLabel = chapters.map((ch) => `${ch.book} ${ch.chapter}`).join(', ');
const episodeLabel = (project.episodeNumber ?? '').trim() const episodeLabel = 'EPISODE';
? `EPISODE ${project.episodeNumber.trim()}${(project.episodeTitle ?? '').trim() ? `${project.episodeTitle.trim()}` : ''}`
: (project.episodeTitle ?? '').trim() || 'EPISODE';
const header = `I'm recording an episode of my Bible study podcast "Verse by Verse with Nate: A Journey Through Scripture" and need a full script written from my study notes below. const header = `I'm recording an episode of my Bible study podcast "Verse by Verse with Nate: A Journey Through Scripture" and need a full script written from my study notes below.
@@ -674,7 +676,6 @@ Here are my study notes, organised by passage chunk, including my OIA notes, cro
PROJECT: ${project.title} PROJECT: ${project.title}
TRANSLATION: ${project.translation} TRANSLATION: ${project.translation}
PASSAGE: ${chapterLabel} PASSAGE: ${chapterLabel}
${(project.generalNotes ?? '').trim() ? `\nBACKGROUND / GENERAL NOTES:\n${project.generalNotes.trim()}\n` : ''}
`; `;
return header + buildChunkBodies(project); return header + buildChunkBodies(project);
@@ -930,10 +931,6 @@ const App = () => {
translation: setup.translation, translation: setup.translation,
lastEdited: Date.now(), lastEdited: Date.now(),
selectedChunkId: null, selectedChunkId: null,
generalNotes: '',
episodeNumber: '',
episodeTitle: '',
finalScript: '',
chapters: [ chapters: [
{ {
book: setup.book, book: setup.book,
@@ -1012,6 +1009,10 @@ const App = () => {
application: '', application: '',
crossReferences: [], crossReferences: [],
greekWords: [], greekWords: [],
generalNotes: '',
episodeNumber: '',
episodeTitle: '',
finalScript: '',
}; };
return { ...ch, chunks: [...ch.chunks, newChunk] }; return { ...ch, chunks: [...ch.chunks, newChunk] };
}); });
@@ -2814,29 +2815,29 @@ const restoreRemoteProject = async (id) => {
</div> </div>
</div> </div>
{/* Episode metadata for podcast prep */} {/* Episode metadata for podcast prep — unique per chunk */}
<div className="rounded-3xl border border-slate-200 bg-slate-50 p-5"> <div className="rounded-3xl border border-slate-200 bg-slate-50 p-5">
<h3 className="text-sm font-semibold text-slate-900">Episode Info</h3> <h3 className="text-sm font-semibold text-slate-900">Episode Info</h3>
<p className="text-xs text-slate-500">Used to label the script when preparing podcast content.</p> <p className="text-xs text-slate-500">Used to label the script when preparing podcast content for this chunk.</p>
<div className="mt-3 flex flex-col gap-3 sm:flex-row"> <div className="mt-3 flex flex-col gap-3 sm:flex-row">
<input <input
type="text" type="text"
value={project.episodeNumber ?? ''} value={selectedChunk?.episodeNumber ?? ''}
onChange={(e) => updateProject((current) => ({ ...current, episodeNumber: e.target.value }))} onChange={(e) => updateChunk(selectedChunk.id, { episodeNumber: e.target.value })}
placeholder="Episode #" placeholder="Episode #"
className="w-full rounded-2xl border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200 sm:w-32" className="w-full rounded-2xl border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200 sm:w-32"
/> />
<input <input
type="text" type="text"
value={project.episodeTitle ?? ''} value={selectedChunk?.episodeTitle ?? ''}
onChange={(e) => updateProject((current) => ({ ...current, episodeTitle: e.target.value }))} onChange={(e) => updateChunk(selectedChunk.id, { episodeTitle: e.target.value })}
placeholder="Episode title" placeholder="Episode title"
className="w-full rounded-2xl border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200" className="w-full rounded-2xl border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200"
/> />
</div> </div>
</div> </div>
{/* Background / general notes for the project */} {/* Background / general notes — unique per chunk */}
<div className="rounded-3xl border border-slate-200 bg-slate-50 p-5"> <div className="rounded-3xl border border-slate-200 bg-slate-50 p-5">
<button <button
type="button" type="button"
@@ -2845,14 +2846,14 @@ const restoreRemoteProject = async (id) => {
> >
<div> <div>
<h3 className="text-sm font-semibold text-slate-900">Background / General Notes</h3> <h3 className="text-sm font-semibold text-slate-900">Background / General Notes</h3>
<p className="text-xs text-slate-500">Context, history, authorship, themes — applies to the whole study, not just this chunk.</p> <p className="text-xs text-slate-500">Context, history, authorship, themes for this chunk.</p>
</div> </div>
<span className="text-slate-400">{collapsedSections.generalNotes ? '' : ''}</span> <span className="text-slate-400">{collapsedSections.generalNotes ? '' : ''}</span>
</button> </button>
{!collapsedSections.generalNotes && ( {!collapsedSections.generalNotes && (
<textarea <textarea
value={project.generalNotes ?? ''} value={selectedChunk?.generalNotes ?? ''}
onChange={(e) => updateProject((current) => ({ ...current, generalNotes: e.target.value }))} onChange={(e) => updateChunk(selectedChunk.id, { generalNotes: e.target.value })}
rows={4} rows={4}
placeholder="e.g. author, date, audience, historical context, key themes…" placeholder="e.g. author, date, audience, historical context, key themes…"
className="mt-4 w-full resize-y rounded-2xl border border-slate-300 bg-white px-4 py-3 text-sm leading-6 text-slate-900 shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200" className="mt-4 w-full resize-y rounded-2xl border border-slate-300 bg-white px-4 py-3 text-sm leading-6 text-slate-900 shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200"
@@ -3140,14 +3141,14 @@ const restoreRemoteProject = async (id) => {
> >
<div> <div>
<h3 className="text-sm font-semibold text-slate-900">Final Script</h3> <h3 className="text-sm font-semibold text-slate-900">Final Script</h3>
<p className="text-xs text-slate-500">Paste the finished episode script here once Claude has helped you write it — keeps the project as a complete archive.</p> <p className="text-xs text-slate-500">Paste the finished script for this chunk once Claude has helped you write it — keeps the project as a complete archive.</p>
</div> </div>
<span className="text-slate-400">{collapsedSections.finalScript ? '' : ''}</span> <span className="text-slate-400">{collapsedSections.finalScript ? '' : ''}</span>
</button> </button>
{!collapsedSections.finalScript && ( {!collapsedSections.finalScript && (
<textarea <textarea
value={project.finalScript ?? ''} value={selectedChunk?.finalScript ?? ''}
onChange={(e) => updateProject((current) => ({ ...current, finalScript: e.target.value }))} onChange={(e) => updateChunk(selectedChunk.id, { finalScript: e.target.value })}
rows={8} rows={8}
placeholder="Paste the final recorded/recordable episode script here…" placeholder="Paste the final recorded/recordable episode script here…"
className="mt-4 w-full resize-y rounded-2xl border border-slate-300 bg-white px-4 py-3 text-sm leading-6 text-slate-900 shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200" className="mt-4 w-full resize-y rounded-2xl border border-slate-300 bg-white px-4 py-3 text-sm leading-6 text-slate-900 shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200"