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

+16 -7
View File
@@ -30,16 +30,24 @@ export function questionsFor(chunk) {
return fallback.length ? { kind: 'think', items: fallback } : { kind: 'none', items: [] };
}
function sessionHtml(project, session, { answerLines }) {
// layout: 'standard' (everything), 'questions' (passage + questions only, one page), 'large' (big print), 'notes' (passage and a blank page to write).
export const HANDOUT_LAYOUTS = [
{ id: 'standard', label: 'Standard: passage, outline, questions' },
{ id: 'questions', label: 'Questions only: passage and discussion questions' },
{ id: 'large', label: 'Large print: passage and questions' },
{ id: 'notes', label: 'Note-taking: passage and lots of lines' },
];
function sessionHtml(project, session, { answerLines, layout = 'standard' }) {
const { chunk, chapterIndex, number, reference } = session;
const series = (project.series ?? '').trim();
const title = (chunk.episodeTitle ?? '').trim();
const num = (chunk.episodeNumber ?? '').toString().trim() || number;
const verses = getChunkVerseEntries(project, chapterIndex, chunk);
const outline = flatten(outlineForSession(project, chunk));
const q = questionsFor(chunk);
const outline = layout === 'standard' ? flatten(outlineForSession(project, chunk)) : [];
const q = layout === 'notes' ? { kind: 'none', items: [] } : questionsFor(chunk);
const blank = (n) => Array.from({ length: n }, () => '<div class="rule"></div>').join('');
return `<section class="page">
return `<section class="page${layout === 'standard' ? '' : ` ${layout}`}">
<header>
<p class="kicker">${esc(series || project.title)}</p>
<h1>Session ${esc(num)}${title ? `: ${esc(title)}` : ''}</h1>
@@ -49,13 +57,13 @@ function sessionHtml(project, session, { answerLines }) {
${verses.length ? `<div class="passage">${verses.map((v) => `<p><sup>${esc(v.number)}</sup> ${esc(v.text)}</p>`).join('')}</div>` : ''}
${outline.length ? `<h2>Outline</h2><ul class="outline">${outline.map(({ node, depth, label }) => `<li style="margin-left:${depth * 1.2}em"><b>${esc(label)}</b> ${esc(node.text || '')}${node.ref ? ` <i>(${esc(node.ref)})</i>` : ''}</li>`).join('')}</ul>` : ''}
${q.items.length ? `<h2>${q.kind === 'questions' ? 'Discussion questions' : 'Think about it'}</h2><ol class="questions">${q.items.map((item) => `<li>${esc(item)}${answerLines ? blank(2) : ''}</li>`).join('')}</ol>` : ''}
${answerLines ? `<h2>Notes</h2>${blank(q.items.length ? 4 : 10)}` : ''}
${answerLines || layout === 'notes' ? `<h2>Notes</h2>${blank(layout === 'notes' ? 16 : q.items.length ? 4 : 10)}` : ''}
<footer>${esc(project.title)}</footer>
</section>`;
}
// ids: the chunk ids to include, or null for every session
export function buildHandoutHtml(project, ids = null, { answerLines = true } = {}) {
export function buildHandoutHtml(project, ids = null, { answerLines = true, layout = 'standard' } = {}) {
const sessions = sessionsFor(project).filter((s) => !ids || ids.includes(s.chunk.id));
return `<!doctype html><html><head><meta charset="utf-8"><title>${esc(project.title)} — handout</title><style>
@page { margin: 18mm 16mm; }
@@ -71,8 +79,9 @@ export function buildHandoutHtml(project, ids = null, { answerLines = true } = {
.outline { list-style: none; padding: 0; margin: 0; } .outline li { margin-bottom: .25em; } .outline b { color: #a84e27; }
.questions { padding-left: 1.4em; } .questions li { margin-bottom: .7em; }
.rule { border-bottom: 1px solid #c9bfa8; height: 1.7em; }
.large { font-size: 17pt; line-height: 1.55; } .large h1 { font-size: 30pt; } .large .rule { height: 2.2em; }
footer { position: absolute; bottom: 0; left: 0; right: 0; text-align: center; color: #9b9484; font: 9pt -apple-system, Helvetica, Arial, sans-serif; }
</style></head><body>${sessions.map((s) => sessionHtml(project, s, { answerLines })).join('')}</body></html>`;
</style></head><body>${sessions.map((s) => sessionHtml(project, s, { answerLines, layout })).join('')}</body></html>`;
}
export function printHandout(project, ids = null, options) {