- axe is clean across every screen in both themes, phone and desktop. Fixed:
the hidden file inputs (aria-hidden + tabIndex -1 — they are opened by
labelled buttons, so they should not be in the accessibility tree), the
heading order (deck/homework/smart-review cards are h2 under the page h1;
LearningSection takes a level so it stays h2 on the library and h3 inside
the settings dialog) and landmarks around the study and review feeds.
- tests/contrast.mjs (new diagnostic): samples rendered pixels, because axe
cannot judge text on blurred glass — it reports those nodes as incomplete.
It skips disabled controls (1.4.3 exempts them) and content behind an open
sheet. Result: no below-minimum text on any scanned screen.
- The report control exists in the homework guide too: under each "Try it
yourself" exercise, labelled "Report exercise", without a skip (there is no
queue to skip). lib/question-reports now takes deckId/deckTitle directly.
- The rail's two creation actions are buttons now (.nav-action) rather than
quiet nav rows — once the library is full they were the only way to start
something on desktop and read as just more navigation. "Make my homework"
is "New homework" wherever it is an action (rail, empty state, share
chooser, manifest shortcut); the page title and mode switch keep their
wording.
Version 0.1.6 (three task bumps), SW -> freezmo-v52.
- "Check my work" now takes photos and documents of the attempt: documents
are read in the browser into text, photos go to /api/homework as
solutionImages and are transcribed by the vision model before the check
("Reading your solution..."). The finished attempt comes back in
meta.solutionText and is stored as the record's attempt, so "Redo"
re-checks the same work without keeping the files.
- Deleting a deck now drops its due schedules and mistakes from the library's
smart review immediately (the counts resolve against the live deck list);
Undo brings them back, because the learning data itself is never erased.
- The phone override for the glacier blobs was still at 0.1/0.2: it is now a
quarter of the original value there too (0.025/0.05), matching the desktop.
- License: GPL-3.0-only (LICENSE, package.json, README section).
Tests: phase-3 photos-only API + desktop attachment checks (28/28), review
suite deck-delete checks (18/18), i18n/mobile/phase-2 green. README:
decisions 57-58. SW -> freezmo-v45.
"Freezmo uses AI and can make mistakes. Double-check important info."
(plus a natural informal Czech translation), rendered by a shared
AiDisclaimer so the copy, style and translations live in one place.
Placements — only where the model's words reach the screen:
- Chat: under the composer, below the context/model chips — always (chat
is pure model output, so it also shows in the empty state).
- Make my homework: under the start CTA (desktop) and the mode cards'
helper line (phone), plus once under the generated result for all three
modes (make / check / guide).
- New deck wizard: under the summary helper (phone step 3) and the desktop
CTA, next to Freeze my deck.
- AI grading (short answers): under the graded feedback; study, review and
exam all share it.
Stored, already-generated content (deck page, study cards, explanations)
deliberately carries no note: the disclaimer belongs to the moment of
generation, not to browsing. Style: --text-subtle (documented at 5.6:1
light / 8.6:1 dark), 11.5px, centred, wraps to two lines at 360px, not
clickable, no icon.
Suites: chat (composer, phone + desktop), wizard (deck step 3 across
360/390 x en/cs x dark/light, homework start cards), phase3 (homework
trigger + result, phone + desktop), phase2 (stubbed grading feedback).
SW -> freezmo-v37. README: decision 50 + chat composer note.
Also fixes the deploy script's test instance: it now execs the server so
the cleanup kill reaps the real PID (the wrapper subshell left orphans
squatting on the test port — three from earlier sessions were found while
verifying this task), and the readiness poll checks the port serves the
NEW commit rather than just answering, so a leftover instance can never
be tested silently.
Check my work (third homework mode)
- One model call with its own system prompt marks the student's own
attempt: verdict, what's right, each issue with why + fix, and the
corrected version that keeps everything the student got right.
Unasked-for extras are never errors. The attempt is typed/pasted
(photos of handwriting are documented as unsupported) and stored in
the record so Redo re-checks the same work.
- Server: mode "check" + solution on POST /api/homework (zod, 400
server.needSolution without one) and the same NDJSON stream; new
checkHomework() pipeline with repair-once validation.
Follow-ups
- New POST /api/homework/refine (plain JSON, RATE_LIMIT_REFINE, zod,
auth before body): shorter / more detail / explain, plain text back,
language preserved.
- Shorter + More detail + Original rewrite the finished answer and the
corrected version in place (FollowUpBar); every guide step has
Explain step N differently; failed calls say so without losing text.
Turn into a study deck
- Any result hands its content (answer, corrected version or assembled
guide) to New deck through a one-shot sessionStorage seed with the
options step pre-filled.
UI
- Phone step 2 gains the third card: tapping it reveals the solution
field + submit; desktop has three cards and the solution field shows
with the mode. Homework history labels and share/card previews cover
the new mode.
Verified: new phase-3 suite 20/20 against a stub Ollama (check payload,
missing-solution 400, refine validation, phone + desktop check flows,
shorter follow-up with restore, guide explain, deck hand-off) plus
regression 74/74, mobile 52/52, declutter 116/116, wizard 80/80,
animation 8/8, native 35/35, phase1 38/38, phase2 43/43, i18n 25/25.
Key parity 734/734. Lint + build clean. SW freezmo-v27.
Safety net and errors, mobile-first, desktop unchanged in behaviour.
Backup and persistence
- Settings -> Data & backup: 'Export all data' writes one zod-validated
JSON (decks, folders, homework, progress, safe settings; the API key
is not part of the shape and never appears in the file).
- 'Import backup' validates, shows a preview with added/replaced
counts and offers merge or replace; invalid files get a friendly
error. Storage persistence (navigator.storage.persist) is shown and
requestable, with the result reported.
- A dismissible nudge appears after 5 new decks or 14 days without a
backup and stays snoozed for 14 days.
Opt-in encrypted sync (off by default)
- New lib/sync.ts: PBKDF2-SHA256 (310k) -> AES-GCM-256, derived key
kept as a non-extractable CryptoKey in IndexedDB (lib/sync-key.ts);
the passphrase and key never leave the browser.
- New POST/GET/DELETE /api/sync following the route pattern (locale,
cross-origin guard, body cap, rate limit RATE_LIMIT_SYNC, auth
before body parsing, zod validation). One atomic JSON file per
random 128-bit sync ID under SYNC_DIR; server-stamped updatedAt is
the conflict clock; SYNC_MAX_BYTES and SYNC_TOTAL_BYTES enforced
(413 / 507). The server only ever sees an opaque envelope.
- Settings -> Sync: enable with a passphrase, copy the sync ID, sync
now, last-synced time, explicit keep-this-device / use-synced-copy
conflict choice, join from another device (ID + passphrase, wrong
passphrase rejected), unlock after the key is gone, delete the
synced copy (works even on a locked device). Every data store marks
sync dirty on write; pulled payloads are applied without marking
dirty again.
Monthly usage vs short-term throttling
- lib/ollama.ts classifies the real 429 body (monthly/quota/upgrade
vs burst) into new 'usage_limit' and 'throttled' kinds, mapped in
every route and in the health probe.
- The client renders the same localized message in the deck flow,
homework, grading and the Settings banner: the required
upgrade/new-key copy with 'Open Settings' (jumps to and focuses the
key field) and 'Open Ollama'. Input and wizard drafts are kept so
Retry works right after swapping the key. No model fallback.
Verified: new phase-1 suite 38/38 (backup content + no key, import
preview/merge/replace, junk file, persistence row, nudge + snooze,
sync enable/upload/opaque blob/push/join/wrong passphrase/delete,
server validation 400/401, usage-limit copy and key focus, and the
real route mapping against a stub Ollama for both 429 variants) plus
regression 74/74, mobile 52/52, wizard 80/80, declutter 116/116,
animation 8/8, native 35/35, i18n 25/25. Key parity 638/638. Lint +
build clean. SW freezmo-v25.
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.
Mobile only (<=639px); desktop markup untouched.
Bug fix: the Back/Next bar was fixed-positioned inside the screen
entrance animation's transformed wrapper, so it painted under the content
and jumped to the viewport bottom about a second in. It is now portalled
to <body> and only fades in, pinned to the bottom from the first frame.
The other fixed/sticky elements (tab bar, drawer, sheets, install sheet)
were checked and live outside the animated wrappers.
- The step name is now the large headline with a one-line description;
the repeated page title is dropped on phones. Thin progress bar + 1/3.
- Step 1: formats/limits hint, one big dashed Add photos or files area
(plus icon), Camera and Paste text instead as secondaries, and a
'nothing added yet' placeholder that becomes the thumbnail/chip list.
- Next is disabled until a photo, file or pasted text exists; step 3 can
never be reached empty.
- Step 1 shows Cancel (back to the Library) instead of a dead Back.
- Step 3 is a real summary: thumbnail/chip strip plus Files, Difficulty,
Activities and Folder rows, each with an Edit link to its step; Freeze
is the single primary action.
- Step 2 got a visible Difficulty label and a helper line under every
group, defaults still preselected.
- Bottom chrome: flat full-width Back/Next bar above the safe area, and
the tab bar hides inside both wizards. The lower-right orb is hidden on
phones so it can never glow behind the bar.
- Make my homework matches: headline, hint, big add area, placeholder,
Cancel, gated Next, flat bar.
Verified: new wizard suite 80/80 (bar position sampled at 5 frames during
the entrance from tab bar/drawer/empty state; 360 + 390, dark + light,
EN + CS, reduced motion on/off; gating, summary Edit links, hidden tab
bar and orb), declutter 116/116, regression 74/74, mobile 52/52,
animation 8/8, i18n 25/25. Lint + build clean. SW freezmo-v22.
Mobile only (<=639px / coarse pointers); desktop keeps its behaviour.
Foundation
- lib/motion.ts owns the spring presets (snappy/gentle/sheet), durations,
easings and variants (screens, tabs, wizard steps, list stagger).
- Native-feel CSS: press feedback scales every button, card, chip, tab,
row and summary to 0.96 with a spring-like cubic-bezier on transform
(scoped to phones/coarse pointers), tap-highlight off, touch-action
manipulation, chrome text non-selectable, reduced motion drops it.
- App shell fades in once on launch; the study feed fades in on phones.
Applied
- Tab bar: sliding active pill (shared layoutId) + icon bounce.
- Study Decks/Homework, language toggle, difficulty and size pickers:
the same gliding highlight, gated so desktop keeps the old styling.
- Route content enters direction-aware (deeper = from the right, back =
from the left, same-depth = crossfade) via RouteTransition on the
library/create/homework content columns; library tab switches
crossfade; wizard steps slide forward/backward (reduced motion safe).
- Library: cards fade up in a capped stagger on first load, glide with
layout animations and shrink away on delete (AnimatePresence).
- Study feed: correct feedback pulses, wrong shakes, the progress bar
settles with a spring curve, score and XP count up on the results.
- Settings sheet uses the sheet spring; the drawer now follows the
finger and closes on release velocity, like the sheet.
- Generation progress: the stage message fades/slides between stages;
the icon keeps its gentle wobble; the feed fades in on hand-off.
Skipped, on purpose: LazyMotion (the sliding highlights need layout
animations = domMax, same features the current import already has);
route exit animations (the App Router unmounts the outgoing page —
exits are handled by in-page AnimatePresence); replacing the study
feed's native scroll-snap with finger-follow drag (would fight the
platform scroller); finger-follow height animation for native <details>
expanders.
Verified: animation suite 8/8 (reduced motion + 4x-CPU-throttled trace:
no long tasks, CLS 0.0000), regression 74/74, mobile 52/52, declutter
116/116, i18n 25/25. Lint + build clean. SW freezmo-v21.
Phones only (<=639px); desktop markup is untouched.
- New deck is a 3-step flow: progress (1/3 + bar) and a fixed Back/Next
bar above the tab bar. Step 1 = Add photos or files / Camera / Paste
text instead (no drop zone or paste copy on touch), step 2 = folder,
difficulty and count with defaults plus one collapsed focus/notes row,
step 3 = one summary line (photos/docs/text · difficulty · count ·
folder) with Freeze. Going Back keeps every input; step transitions
respect prefers-reduced-motion.
- Make my homework is a 2-step flow sharing the same pattern; tapping a
mode card starts it (no separate CTA on phones), with a short preview
of the assignment on step 2. The hero is now 'Make my homework' /
'Udělej mi úkol' everywhere ('Solve your homework' is gone) and the
intro is one line.
- Library: stats line removed, folder row and 'Unorganized' heading
hidden until folders exist (New folder lives in the drawer), Import is
a small icon button beside the tabs. Deck and homework cards are
compact on phones: title, one meta line (activities · difficulty ·
best), a one-line description that expands on tap, the whole card
tappable and a small Study/Open pill instead of the full-width button.
- Settings sheet on phones: no .env.local subtitle; Language stays first;
one Connection card (status dot + 'Connected · 2 models ready', key
field with the eye inside, Save only when edited, auto re-check on
save, refresh icon instead of Test connection); privacy one-liner with
a More expander (daemon tip inside); raw health rows behind one
collapsed Details list; Remove key as a small danger button at the very
bottom (same confirmation).
- Decorative orbs on phones moved further off the screen edges so they
never glow under cards or primary buttons.
- Shared markup for folder/difficulty/count and mode cards keeps one
source of state; new lib/use-phone.ts gates the layouts.
Verified: new declutter suite 116/116 (360 + 390, EN + CS, photo + PDF +
pasted text, Back keeps input, summaries, settings flow), regression
74/74, mobile 52/52, i18n 25/25. Lint + build clean. SW freezmo-v20.
Correcting the previous change: instead of stripping the snowflake from
"Freeze my deck", "Make my homework" now carries the homework flow's own
sparkles icon, and the snowflake is back on the deck button.
Both CTAs are now identical in shape — feature icon + label, full-width,
44px, 14px radius, helper line centred underneath, same muted disabled chip
and ice gradient when enabled. The computed-style diff between the two
buttons is empty and their accessible names are unchanged.
Service worker: freezmo-v14.
Verified: CTA-parity suite 32/32, desktop regression 74/74, mobile 51/51.
Lint + build clean.
Library ("Your icebox")
- Two tabs, never mixed: Study Decks (the folder + card library) and Homework
(saved solutions and step-by-step guides). The active tab lives in the URL
(/?tab=homework), so the sections are linkable and survive a reload.
- Decks tab keeps Import, the folder bar, stats and its own empty state — now
a single decks-only CTA.
- Homework tab lists saved results as cards (mode icon, date, step count,
preview) with Open and delete-with-confirm; its empty state has its own CTA
("Homework Solver") that opens the solver directly. Opening a card
deep-links to /homework?open=<id>, which the screen opens on arrival.
- .segmented now also styles link segments (same control as settings).
/create
- The redundant "Study Deck | Homework Solver" toggle is gone: /create is the
deck uploader only, and the bottom bar's + goes straight to it. Homework has
its own screen, reached from the library tab, the drawer and CTAs.
Copy
- New keys (en + cs): tabDecks, tabHomework, homeworkHeroTitle, plus homework
empty-state/open/step-count strings. The homework hero is now "Solve your
homework" / "Vyřeš svůj úkol" — no study-deck text in the homework flow.
- Removed the now-unused ModeSwitch component and homework.title key.
Service worker: freezmo-v10.
Verified: library-tabs suite 37/37 (tab switching, context empty states,
deep-link opening, deletion, no switch on /create, heroes, redirects, desktop
grid, 360px + 44px targets, axe contrast en+cs light+dark), mobile 49/49,
i18n 27/27, desktop regression 74/74. Lint + build clean.
Homework (/homework — entry points in the drawer and on the create screen)
- Assignment intake reuses the study pipeline: pasted text, photos
(camera/picker, compressed in the browser) and documents (PDF/DOCX/PPTX/
ODT/text) through lib/image + lib/documents, the same vision step and the
same language guard. Raw files never leave the device — only text and JPEG
data URLs reach the server.
- Two large choice cards: “Just make it” returns the finished homework
(answer only, plain text, copy + make it again); “Explain how to make this
homework” builds an interactive guide.
- The guide is one step per screen in the study-feed snap layout:
explanation, three progressive hints (nudge -> hint -> the answer) and an
optional checkpoint rendered by the existing activity components (graded
through /api/grade). The last screen assembles the solution from the step
answers, with a copy button. Checkpoints carry xp 0 so the guide stays free
of XP/streak chrome.
- Results live in freezmo.homework.v1 with a small history (open/delete);
“Make it again” re-runs from the stored assignment text (the vision
transcription when photos were used) — no original files are stored.
- The answer and the guide are written in the assignment's language, never
translated into the UI language.
Server
- POST /api/homework: NDJSON stage events like /api/generate, the same
http-guard, auth checked before parsing, and its own RATE_LIMIT_HOMEWORK
(documented in .env.example + the README table).
- New prompts in lib/prompts.ts and zod schemas in lib/schemas.ts with the
same single repair retry; TEXT_MODEL for content, VISION_MODEL only for
photos. New shared client pieces: useNotesIntake, lib/homework store,
lib/clipboard, and a shared NDJSON reader in lib/client-api.
Also finishing the mobile polish pass from the previous request
- Phone ice scene: the top-left orb moved behind the glass header (it sat
behind the page heading/subtitle). The settings sheet's scroll cue is now a
bare chevron inside a content fade (mask) instead of a chip over the status
banner, and the API key card's note/disclosure/actions are tightened.
- theme-color now follows the in-app theme (SSR metas still fall back to the
OS preference); apple-mobile-web-app-status-bar-style stays “default”
because iOS fixes the status-bar text colour at launch.
Verified: 78/78 homework checks (text/photo/PDF, both modes, EN+CS, dark+light,
360/390px, copy, history persistence, storage privacy, API language, axe), plus
the existing suites re-run (mobile 47/47, pixel contrast 0 below AA, i18n 27/27,
desktop regression 72/72). Lint + build clean; service worker freezmo-v6.