From d8e522dd060a00bec3164620cc88d9a1710cd7f4 Mon Sep 17 00:00:00 2001 From: nmemmert <67961292+nmemmert@users.noreply.github.com> Date: Wed, 14 Oct 2026 12:42:11 -0400 Subject: [PATCH] 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 ", "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 --- src/App.jsx | 4 +- src/components/HandoutExport.jsx | 10 ++- src/components/WeeklyRecall.jsx | 7 +- src/components/settings/DataSection.jsx | 3 +- src/components/study/CompareSheet.jsx | 61 ++++++++++++++ src/components/study/NotesPane.jsx | 9 +- src/components/study/ResourcePane.jsx | 18 +++- src/components/study/SessionNotes.jsx | 42 ++++++++++ src/components/study/StudioShell.jsx | 8 +- src/pages/HomePage.jsx | 13 +++ src/pages/InsightsPage.jsx | 107 ++++++++++++++++++++++++ src/pages/StudyPage.jsx | 9 +- src/test/App.home.test.jsx | 10 +++ src/utils/backup.js | 11 +++ src/utils/backup.test.js | 10 +++ src/utils/handout.js | 23 +++-- src/utils/handout.test.js | 16 ++++ src/utils/insights.js | 78 +++++++++++++++++ src/utils/insights.test.js | 48 +++++++++++ src/utils/mergeStudies.js | 9 +- src/utils/ownWork.js | 4 + src/utils/personalSearch.js | 45 +++++++++- src/utils/personalSearch.test.js | 22 +++++ src/utils/projectDiff.js | 1 + 24 files changed, 540 insertions(+), 28 deletions(-) create mode 100644 src/components/study/CompareSheet.jsx create mode 100644 src/components/study/SessionNotes.jsx create mode 100644 src/pages/InsightsPage.jsx create mode 100644 src/utils/insights.js create mode 100644 src/utils/insights.test.js diff --git a/src/App.jsx b/src/App.jsx index 23d9e31..ae41543 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -37,6 +37,7 @@ const ImportPage = lazy(() => import('./pages/ImportPage.jsx')); const ReaderPage = lazy(() => import('./pages/ReaderPage.jsx')); const SettingsPage = lazy(() => import('./pages/SettingsPage.jsx')); const SetupPage = lazy(() => import('./pages/SetupPage.jsx')); +const InsightsPage = lazy(() => import('./pages/InsightsPage.jsx')); const StudyPage = lazy(() => import('./pages/StudyPage.jsx')); const OutlinePage = lazy(() => import('./pages/OutlinePage.jsx')); const ScriptPage = lazy(() => import('./pages/ScriptPage.jsx')); @@ -167,7 +168,7 @@ const AppInner = () => { useEffect(() => { const names = { home: 'My Studies', setup: 'Chunks', study: 'Study', outline: 'Outline', script: 'Script', export: 'Export', - import: 'Import', pronunciation: 'Pronunciation Guide', settings: 'Settings', admin: 'Admin', 'reading-plans': 'Reading Plans', prayer: 'Prayer journal', + import: 'Import', pronunciation: 'Pronunciation Guide', settings: 'Settings', admin: 'Admin', 'reading-plans': 'Reading Plans', prayer: 'Prayer journal', insights: 'Insights', }; let page = names[currentPage] ?? ''; if (currentPage === 'reader') { @@ -629,6 +630,7 @@ function AppRouter() { if (currentPage === 'prayer') return ; if (currentPage === 'reader') return ; if (currentPage === 'import') return ; + if (currentPage === 'insights') return ; if (currentPage === 'study') return ; if (currentPage === 'outline') return ; if (currentPage === 'script') return ; diff --git a/src/components/HandoutExport.jsx b/src/components/HandoutExport.jsx index 6abc32b..43d4004 100644 --- a/src/components/HandoutExport.jsx +++ b/src/components/HandoutExport.jsx @@ -1,14 +1,15 @@ // Export → Lesson handout: a printable page for each session (or one session), for a group to follow along. import { useState } from 'react'; -import { printHandout, sessionsFor } from '../utils/handout.js'; +import { HANDOUT_LAYOUTS, printHandout, sessionsFor } from '../utils/handout.js'; export default function HandoutExport({ project }) { const sessions = sessionsFor(project); const [which, setWhich] = useState('all'); const [lines, setLines] = useState(true); + const [layout, setLayout] = useState('standard'); const [blocked, setBlocked] = useState(false); if (sessions.length === 0) return null; - const go = () => setBlocked(!printHandout(project, which === 'all' ? null : [which], { answerLines: lines })); + const go = () => setBlocked(!printHandout(project, which === 'all' ? null : [which], { answerLines: lines, layout })); return (

Lesson handout

@@ -20,6 +21,11 @@ export default function HandoutExport({ project }) { {sessions.map((s) => )} + diff --git a/src/components/WeeklyRecall.jsx b/src/components/WeeklyRecall.jsx index 7a30379..2d38804 100644 --- a/src/components/WeeklyRecall.jsx +++ b/src/components/WeeklyRecall.jsx @@ -16,6 +16,7 @@ export default function WeeklyRecall() { const { projectIndex, saveChunkRecall, openStudyChunk } = useApp(); const [step, setStep] = useState('card'); // card -> write -> compare -> done const [text, setText] = useState(''); + const [gaps, setGaps] = useState(''); const pick = useMemo( () => pickRecall(projectIndex.map((e) => loadProjectById(e.id)).filter(Boolean)), [projectIndex], @@ -24,7 +25,7 @@ export default function WeeklyRecall() { if (!pick) return null; const finish = (rating) => { - saveChunkRecall(pick.projectId, pick.chunk.id, { at: Date.now(), text: text.trim(), rating }); + saveChunkRecall(pick.projectId, pick.chunk.id, { at: Date.now(), text: text.trim(), rating, gaps: gaps.trim() }); setStep('done'); }; @@ -63,6 +64,10 @@ export default function WeeklyRecall() { ))} +