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

+39 -3
View File
@@ -1,4 +1,5 @@
import { lazy, Suspense, useEffect, useRef, useState } from 'react';
import { getPlanSchedule } from './utils/readingPlans.js';
import { AppContext, useApp } from './context/AppContext.js';
import { useAudio } from './hooks/useAudio.js';
import { useAuth } from './hooks/useAuth.js';
@@ -38,9 +39,15 @@ const App = () => {
const saveTimerRef = useRef(null);
const [syncStatus, setSyncStatus] = useState(''); // '' | 'syncing' | 'synced' | 'error'
const [studyBuildBusy, setStudyBuildBusy] = useState(false);
const reader = useBibleReader(currentPage);
const audio = useAudio(reader.readerBookAbbrev, reader.readerChapter, reader.readerTranslation);
const readingPlans = useReadingPlans();
// The plan day currently open in the Reader ({ progressId, dayIndex }) — set when
// you read from a plan, cleared when you leave the Reader. While set, the Reader
// uses that plan's own translation.
const [readerPlan, setReaderPlan] = useState(null);
const readerPlanProgress = readerPlan ? readingPlans.planProgress.find((p) => p.id === readerPlan.progressId) ?? null : null;
const reader = useBibleReader(currentPage, readerPlanProgress?.translation ?? null);
const audio = useAudio(reader.readerBookAbbrev, reader.readerChapter, reader.readerTranslation);
useEffect(() => { if (currentPage !== 'reader') setReaderPlan(null); }, [currentPage]);
const { authUser, setAuthUser, authServerDown, signIn, logout } = useAuth({
onSignIn: (user) => {
setProjectIndex(switchStorageUser(user.id));
@@ -120,12 +127,37 @@ const App = () => {
const openReadingPlans = () => setCurrentPage('reading-plans');
// Jumps straight into a specific plan day's first chapter and opens the reader.
// Jumps straight into a specific chapter and opens the reader (no plan context).
const readReadingPlanDay = (bookAbbrev, chapter) => {
reader.jumpToReaderVerse(bookAbbrev, chapter, 1);
setCurrentPage('reader');
};
// Opens the Reader inside a plan: on `ref` (or the day's first unread chapter),
// in the plan's own translation, with the plan strip and end-of-day card active.
const openPlanReading = (progressId, dayIndex, ref) => {
const progress = readingPlans.planProgress.find((p) => p.id === progressId);
if (!progress) return;
const refs = getPlanSchedule(progress.planId)[dayIndex] ?? [];
const done = progress.completedDays.includes(dayIndex);
const read = new Set(done ? [] : progress.chaptersRead?.[dayIndex] ?? []);
const target = ref ?? refs.find((r) => !read.has(`${r.book}.${r.chapter}`)) ?? refs[0];
setReaderPlan({ progressId, dayIndex });
if (target) {
reader.setReaderBookAbbrev(target.book);
reader.setReaderChapter(target.chapter);
reader.setReaderSelectedVerse(null);
reader.setReaderCrossRefs(null);
}
setCurrentPage('reader');
};
// Changing the translation while a plan is open changes that plan's translation.
const setReaderTranslation = (id) => {
if (readerPlan) readingPlans.setPlanTranslation(readerPlan.progressId, id);
else reader.setReaderTranslation(id);
};
// Starts book-mode audio playback at a specific plan day's chapter, then
// shows the Home audio player. Uses handlePlayBookAudioFrom (not the reader
// player) so this works even without opening the reader first.
@@ -233,6 +265,10 @@ const App = () => {
...audio,
// Reader
...reader,
readerPlan, setReaderPlan,
readerPlanActive: !!readerPlanProgress,
openPlanReading,
setReaderTranslation,
// Setup (chunk-builder state lives in SetupPage via useChunkBuilder)
project, setProject,
allChunks,