Reading plans: integrated into the Reader, redesigned, with per-plan translation
Build and Push Docker Image / build-and-push (push) Failing after 19s

Reader: plan strip with a dot per chapter, plan sheet (day picker, chapter
ticks, translation, streak, other plans), end-of-day card that steps through
the day and rolls into the next, and chapter-level auto-marking.
Each plan remembers its own translation (BSB, ESV, NLT, CSB, NASB, NET) and the
Reader follows it while a plan is open. Plans home redesigned in the reader's
themes with a Continue card, in-progress plans, a filterable catalog, and
sheets for details and the custom-plan builder.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-09-29 09:47:51 -04:00
1 parent c11b6a60bf
commit b1bc1c8ea4
17 files changed
+1205 -863

No files matched your search

+34 -13
View File
@@ -1,18 +1,39 @@
// Thin strip under the top bar while the open chapter belongs to an active
// reading plan. Shows which day you're on and lets you mark it read.
export default function PlanStrip({ matches, notice, onToggle }) {
if (!matches.length && !notice) return null;
// Thin strip under the top bar while reading a plan: which plan and day, one dot
// per chapter of today's reading (filled = read, ringed = the chapter on screen),
// and the translation. Tapping it opens the plan sheet. When you've wandered off
// today's chapters (e.g. following a cross-reference) it offers a way back.
export default function PlanStrip({ info, translation, inPlan, notice, onOpen, onJoin, onDot, onBackToDay }) {
if (!info && !notice) return null;
return (
<div className="mx-auto flex max-w-2xl flex-col gap-1 px-4 pt-2 font-sans">
<div className="mx-auto flex max-w-2xl flex-col gap-1 px-3 pt-2 font-sans">
{notice && <p className="rounded-lg bg-[var(--r-bar)] px-3 py-1.5 text-xs text-[var(--r-accent)]">{notice}</p>}
{matches.map((m) => (
<button key={m.progressId} type="button" onClick={() => onToggle(m)}
className="flex items-center justify-between gap-3 rounded-lg bg-[var(--r-bar)] px-3 py-1.5 text-left text-xs text-[var(--r-sub)] hover:bg-black/5"
title={m.done ? 'Tap to unmark this day' : 'Tap to mark this day read'}>
<span className="truncate"><span className="text-[var(--r-accent)]">{m.done ? '✓' : '●'}</span> {m.plan.title} · Day {m.dayIndex + 1} of {m.plan.days}</span>
<span className="shrink-0">{m.total > 1 ? `${m.position} of ${m.total}` : m.done ? 'Read' : 'Mark read'}</span>
</button>
))}
{info && (
<div className="rounded-xl bg-[var(--r-bar)] px-3 py-2 text-xs text-[var(--r-sub)]">
<button type="button" onClick={inPlan ? onOpen : onJoin} className="flex w-full items-center justify-between gap-3 text-left">
<span className="truncate font-medium text-[var(--r-text)]">{info.plan.title} · Day {info.dayIndex + 1}</span>
<span className="shrink-0">{inPlan ? translation : 'Open plan'}</span>
</button>
{inPlan && info.position === 0 && (
<button type="button" onClick={onBackToDay} className="mt-1.5 rounded-full border border-[var(--r-line)] px-3 py-1 text-[11px] text-[var(--r-accent)] hover:bg-black/5">
Back to today’s reading
</button>
)}
{inPlan && info.position > 0 && (
<div className="mt-1.5 flex items-center gap-1.5">
{info.refs.map((r, i) => {
const key = `${r.book}.${r.chapter}`;
const read = info.readSet.has(key);
const current = i + 1 === info.position;
return (
<button key={key} type="button" onClick={() => onDot(r)} aria-label={`Go to chapter ${r.chapter}`}
className={`h-2.5 w-2.5 rounded-full border-[1.5px] ${read ? 'border-[var(--r-accent)] bg-[var(--r-accent)]' : 'border-[var(--r-sub)]'} ${current ? 'ring-2 ring-[var(--r-line)]' : ''}`} />
);
})}
<span className="ml-auto">{info.done ? 'Day read' : `${info.position} of ${info.total}`}</span>
</div>
)}
</div>
)}
</div>
);
}