Add streaks, today view, custom plan editing/chapter ranges, and ntfy reminders
Build and Push Docker Image / build-and-push (push) Failing after 21s

Reading plans now track a cross-plan activity log for streaks, surface a
combined "today's reading" view on both Home and the Reading Plans page,
and let custom plans be edited after creation (including narrowing a book
to a specific chapter range). The reader auto-marks a plan's day read after
a dwell period, active plans that fall behind their pace get a "catch me
up" banner, and a new Settings section wires up daily reading reminders
via ntfy.sh, checked once a minute by a small server-side scheduler.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5 committed 2026-09-23 13:16:27 -04:00
1 parent 600b3d3ab9
commit 1dd6aef6a5
10 files changed
+711 -141

No files matched your search

+23
View File
@@ -78,6 +78,23 @@ export default function ReaderPage() {
.filter(Boolean);
}, [planProgress, readerBookAbbrev, readerChapter]);
// Auto-marks matching plan days as read after a dwell period on the chapter,
// so you don't have to remember to tap "Mark read" yourself. Resets whenever
// you navigate to a different book/chapter; a manual unmark isn't re-marked.
const planMatchesRef = useRef(planMatches);
planMatchesRef.current = planMatches;
const [autoMarkedNotice, setAutoMarkedNotice] = useState('');
useEffect(() => {
const timer = window.setTimeout(() => {
const justMarked = planMatchesRef.current.filter((m) => !m.done);
if (justMarked.length === 0) return;
justMarked.forEach((m) => toggleDay(m.progressId, m.dayIndex, m.plan.days));
setAutoMarkedNotice(`✓ Marked read — ${justMarked.map((m) => m.plan.title).join(', ')}`);
window.setTimeout(() => setAutoMarkedNotice(''), 4000);
}, 20000);
return () => window.clearTimeout(timer);
}, [readerBookAbbrev, readerChapter, toggleDay]);
const [readerDrawMode, setReaderDrawMode] = useState(false);
const [readerWideLayout, setReaderWideLayout] = useState(() => localStorage.getItem('reader-wide') === '1');
const readerPageInkStrokes = readerInkByPage?.[`${readerBookAbbrev}_${readerChapter}`] ?? NO_STROKES;
@@ -354,6 +371,12 @@ export default function ReaderPage() {
</div>
</div>
{autoMarkedNotice && (
<div className="mb-4 rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-2 text-sm font-semibold text-emerald-700">
{autoMarkedNotice}
</div>
)}
{/* Tool strip: font size · cross-refs · search */}
<div className="mb-4 flex flex-wrap items-center gap-2 rounded-3xl border border-slate-200 bg-white px-4 py-3 shadow-panel">
<span className="text-xs text-slate-500 mr-1">Text size</span>