Entry points (no sixth tab): Ask in the Library header (desktop button,
phone icon), Ask about this deck in the deck menu, Ask a question on
study/homework results (compact result travels as context), and a Chat
row in the desktop sidebar. Phones get a full-screen route with back +
New chat; the tab bar steps aside.
- Client-side retrieval: chunk scoped decks (~800 tokens), BM25 against
the question + recent turns, top chunks under ~7k tokens, each
labelled with deck + section. Notes come from the deck's stored
sourceText (already kept for Regenerate); decks without it fall back
to generated content and the UI says so.
- Stateless server: /api/chat validates against the CHAT_MODELS
allowlist, trims the history to ~6k tokens, caps context at ~9k and
streams token/thinking/done/error NDJSON. /api/chat/extract reduces
attachments to text through the existing vision step.
- UI: user bubbles + full-width assistant text, streaming with stop,
auto-scroll that pauses with a jump-to-bottom button, safe markdown
(React nodes, no HTML injection; KaTeX dynamically imported only when
a message has math), Thinking row for reasoning, copy/regenerate/
edit-and-resend, model + context pickers (last model remembered),
composer with visualViewport keyboard handling, safe-area, 16px
textarea, Enter-to-send on desktop.
- Usage-limit failures keep the draft and show the shared message with
Settings/Ollama actions; stopping maps the reader abort to a clean
stopped state; offline disables sending with a reason.
- Settings: Keep notes text for chat toggle; deck menu: Delete notes
text. RATE_LIMIT_CHAT + CHAT_MODELS in .env.example and README.
- Tests: chat suite (63 checks, stub Ollama) wired into run-all.
Full set: 12/12 suites, 487 checks. SW -> freezmo-v32.
Also: tests/model-compare.mjs (diagnostic) benchmarks two models on the
same notes (latency, repair rounds, activity types) so the gpt-oss vs
gemma default can be decided on numbers; defaults unchanged.
Answer history and schedules
- Every answer is recorded (deck, activity, topic, correct/wrong, time)
in a versioned IndexedDB log (freezmo-learning v1, stores answers +
srs), mirrored in memory for synchronous React reads and capped at
20k answers (oldest pruned). StudyFeed records as it grades.
- Light SM-2 schedule per activity: 1 day, 3 days, then interval x
ease; wrong answers return in ten minutes with the ease nudged down.
Review, mistakes, stats
- Library 'Smart review' card: due count, mistakes count (latest
answer wrong — clears itself when answered correctly), today's goal
progress. Review sessions freeze their item list at start.
- New /review (due across decks, ?mistakes=1 global or ?deck= per
deck) built on a shared SessionFeed with the study feed's snap/keyboard
mechanics, wake lock and a results slide.
- New /stats: day streak, five-week calendar, accuracy per subject and
per deck, weakest topics (>=3 answers), daily-goal progress.
- Daily goal configurable in Settings (10/20/30/50).
Exam mode
- New /exam: pick decks, question count, optional timer; no hints
(a NoHints context hides hint rows and the self-check escape);
results with score, per-deck and per-topic breakdown; wrong answers
land in the mistakes set through the normal history.
Reminders (conditional)
- VAPID-gated: GET/POST/DELETE /api/push and POST /api/push/test with
the standard route pattern, RATE_LIMIT_PUSH, zod, auth before body.
Subscriptions stored server-side as endpoint + keys only (PUSH_DIR).
UI hidden entirely without keys; iOS install note; service worker
push/notificationclick handlers; scripts/send-reminders.mjs for a
cron/systemd timer; web-push added as the one server dependency.
Verified: new phase-2 suite 42/42 (history/SRS shapes from a real study
run, mistakes clearing, review session, stats math, exam timer and
breakdowns, VAPID-less hidden state plus the configured
subscribe/validate/test/delete path against a second instance) plus
regression 74/74, mobile 52/52, wizard 80/80, declutter 116/116,
animation 8/8, native 35/35, phase1 38/38, i18n 25/25. Key parity
709/709. Lint + build clean. SW freezmo-v26.
Mobile only (<=639px / coarse pointers); desktop keeps its behaviour.
System integration
- Manifest: id, display_override, monochrome icon, Make my homework
shortcut and a share target (text/URL via GET, files via POST).
iOS startup images for the common iPhone sizes. Pinch-zoom is allowed
again (double-tap zoom is disabled via touch-action instead).
- Share target: /share chooser with New deck / Make my homework; POSTed
files wait in a 10-minute in-memory store, reach the wizard through
sessionStorage and never touch disk.
- Web Share: Share rows for decks (JSON file where supported) and the
homework result, with a clipboard fallback and a toast.
- Offline: a quiet pill appears and Freeze / mode cards are disabled
with a clear reason; input is never lost (error cards keep Retry).
- State: wizard drafts (text, options, photos up to a size budget)
survive reloads and OS kills and are cleared on success; re-tapping
the active tab scrolls to the top; the deck page shows a skeleton
instead of a spinner.
Navigation and gestures
- System Back closes the topmost layer first: a small history-entry
stack (lib/use-back-close.ts) covers the drawer, Settings sheet,
install sheet, deck actions panel and one wizard step; navigations
are never rewound and stale entries are skipped.
- Destructive-but-reversible actions use Undo toasts: deleting a deck
(no confirm) and removing a file in the wizard. Irreversible ones
keep their confirm.
Input and feedback
- Every phone input got inputmode / enterkeyhint / autocomplete /
autocapitalize / spellcheck; Enter on the last field advances the
wizard; a clipboard-paste button appears where the API exists.
- Haptics behind a Settings toggle (on by default): tab switches,
toggles, correct/wrong, completion; Vibration API + the iOS hidden
switch tick, throttled and silent everywhere else.
- Wake lock during the study feed and homework guide, released on
leave and re-acquired when the tab returns.
Verified: new native suite 35/35 (manifest/head/viewport, offline,
share target GET + POST, back-gesture layers, undo, share fallback,
tab re-tap, haptics persistence, draft restore), regression 74/74,
mobile 52/52, wizard 80/80, declutter 116/116, animation 8/8,
i18n 25/25. Lint + build clean. SW freezmo-v23.
Needs a real device (documented in the README): iOS haptic tick,
system share sheet entry, wake-lock grant, iPhone startup images,
iOS edge-swipe back, install prompts. Pull-to-refresh and the app
badge were skipped on purpose.
In user-key mode a browser with no stored Ollama key now gets a polished
gate instead of the app shell: one masked input with a show/hide toggle,
a single Continue button, inline validation errors, and a "?" help sheet
with the steps to a free key.
- Validates the draft key through /api/health (real 1-token probe) before
saving; invalid-key and network failures read differently.
- Key still lives only in this browser's localStorage; nothing server-side.
- Gate wraps /, /create and deck study; server-key and local-daemon modes
skip it; removing the key in Settings brings it back.
- Removes the now-redundant dashboard/create API-key banner.
- Monochrome corner glows (dark near-black / light near-white), 24px card,
48px field and button, gentle fade/slide-in, reduced-motion aware.
- Help modal: centred dialog on desktop, bottom sheet on mobile, closes via
X, Escape or backdrop.
- README and browser suites updated (34 gate checks, 15 key-flow checks,
12 shipped-default checks; ui/folders/docs/pwa/security/contrast green).
Next.js 15 app that turns photos, PDFs, DOCX/PPTX and text into a swipeable
feed of AI-generated activities (flashcards, multiple choice, true/false,
fill-in-the-blank, matching, ordering, short answer) using the user's own
Ollama models.
Highlights:
- All AI calls run in server route handlers; the browser never talks to Ollama.
- Bring-your-own-key mode (default): the key lives only in the browser and is
sent per-request; server, local-daemon and shared-key modes also supported.
- Client-side document extraction (pdf.js, mammoth, jszip) with scanned-PDF
page rendering for the vision model; notes stay in their original language.
- ChatGPT-style shell: collapsible sidebar / mobile drawer, neutral token-based
light + dark themes, folder organisation, PWA (manifest, SW, offline page).
- Strict JSON validation with zod + auto-repair, rate limiting, and request
guards (cross-origin, body-size, API-key sanitising).
- setup.sh bootstraps deps, .env.local and a production build.
Gitignore excludes .env*, the generated pdf.js worker and local scratch files.