Commit graph

14 commits

Author SHA1 Message Date
6564e88138 Mobile declutter pass: step flows, compact cards, simplified settings
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.
2026-10-10 10:43:32 +00:00
6fb11fa405 Grade against the question: stop requiring details the question never asked for
A short-answer case: "Why was Kryštof Harant executed?" with the student
answering "because he took part in the anti-Habsburg uprising" was failed
for not naming the year 1621 — a detail only present in the reference
answer, never asked by the question. The generator had put the date into
keyPoints, and the grader treated every key point as mandatory.

- Grading prompt: the QUESTION defines what is required. A key point
  counts only when the question asks for it or the answer is wrong or
  incomplete without it; extra detail in the reference answer (dates,
  places, names, numbers) is a bonus — never listed as missing, never a
  reason to fail. An accepted answer must return an empty "missing"
  array.
- Generation spec: short_answer keyPoints must contain only the ideas
  the question asks for (a "why" question requires the reason, not the
  year).
- gradeShortAnswer drops any stray "missing" entries when correct is
  true, so the UI can never show a gap on an accepted answer.

Verified: mock-Ollama integration test 9/9 through the real /api/grade
route (new rules sent upstream, incorrect answers keep their missing
point, accepted answers normalise to missing: []), regression 74/74,
mobile 51/51, i18n 27/27. Lint + build clean. SW freezmo-v18.
2026-10-09 18:13:29 +00:00
9d6ef90f02 Keep the library header steady across tabs; simplify the empty states
Header & action bar
- Import JSON no longer disappears on the Homework tab: it sits directly
  under the "Your icebox" header/subtitle, above the tab switcher, on every
  tab — so switching pools no longer shifts the layout or hides actions. It
  is one secondary button at all sizes now, not a quiet link when empty.
- The decks-only "Homework Solver" header action is gone; the homework tab's
  empty-state CTA and the drawer cover starting a new solve.

Empty states
- Dropped the "Using your own Ollama models — check the connection in
  Settings." line (and its two dictionary keys) everywhere.
- Both tabs share one structure: ice icon -> heading -> short description ->
  primary CTA button, starting at the same height:
    Study Decks: "No decks yet" -> "Freeze a page of notes into flashcards
    and quizzes." -> "Create study deck"
    Homework: "No homework yet" -> "Solved assignments and step-by-step
    guides show up here." -> "Solve homework"
- CTA labels read as actions on the standardized nouns; removed the now
  unused actionStudyDeck/actionHomework keys.

Tabs
- Link segments are 44px tall at every breakpoint (min-height in the base
  .segmented > a rule).

Service worker: freezmo-v11.

Verified: library-consistency suite 49/49 (Import visible with a fixed
position on both tabs, no Ollama text, empty states at the same y, primary
ice CTA buttons >=44px, deep links, deletion, redirects, desktop, 360px, axe
en+cs light+dark), mobile 51/51, i18n 27/27, desktop regression 74/74.
Lint + build clean.
2026-10-08 21:50:18 +00:00
8c44f47881 Separate Study Decks and Homework in the library; drop the /create mode switch
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.
2026-10-08 21:26:08 +00:00
461dd2ffcf Fix the mode-choice redundancy: standardized names, deep links, compact switch
Terminology (lib/i18n-dict.ts)
- One name per mode everywhere: "Study Deck" / "Studijní balíček" and
  "Homework Solver" / "Řešitel úkolů". The library empty-state cards and the
  /create switcher now read the same, so the choice no longer changes name
  between screens.

Deep links
- Library quick actions carry the mode: /create?mode=deck and
  /homework?mode=homework.
- Both pages canonicalize: /create?mode=homework redirects to the homework
  flow and /homework?mode=deck redirects to /create, so a mode link always
  opens that mode directly.

/create
- The two bulky square cards (106px tall, wrapping/clipping "Study Deck &
  Qu…" on phones) are replaced by a compact segmented control (52px) placed
  directly above "1. Add your notes": ice pill, snowflake/sparkles icons,
  44px segments and one-line labels at 360px. The pill slides to the chosen
  segment and the route follows ~190ms later, so toggling reads as one smooth
  transition (immediate under prefers-reduced-motion).

Service worker: freezmo-v9.

Verified: mode-switch suite 26/26 (deep links, cross-mode redirects, 52px
control, 44px segments, no clipping/wrapping at 360px, "1. Add your notes" at
y=394 of 740 with the upload controls above the fold, pill slide, axe contrast,
en+cs, phone+desktop), mobile 49/49, i18n 27/27, desktop regression 73/73.
Lint + build clean.
2026-10-08 19:38:02 +00:00
22af888ec0 Redesign the quick-action CTAs: two ways in (study deck vs. homework)
Library empty state ("Your icebox")
- Replace the single "Make your first deck" button with two quick-action
  cards: "Create interactive test" (primary, ice-gradient icon + subtle
  accent border) and "Help me with my homework" (glass card, sparkles
  icon). Stacked on phones, side by side from 640px up; both >=44px tall.

/create
- Replace the "Got homework instead of notes?" banner with a two-card mode
  switcher above "1. Add your notes": "Study Deck & Quizzes" (the current
  view — aria-current, accent tint, ice icon) and "Homework Solver" (a
  shortcut to /homework). Icon-on-top tiles on phones, inline rows from
  640px up; glass tokens + backdrop blur in both themes.

Strings
- New keys in both dictionaries: emptyStateCreateDeck, emptyStateHomework,
  actionStudyDeck, actionHomework. Drop the now-unused homework.entry and
  library.makeFirst keys.

Drive-by AA fix (light mode, /create only)
- The "New folder" dashed button and the difficulty blurbs move from
  frost-500 to frost-600 (4.0:1 -> ~6:1). Dark mode is unchanged.

Service worker: freezmo-v8.

Verified: redesign suite 32/32 (stacked/side-by-side layouts, 360/390/1280,
dark+light, en+cs, >=44px targets, both cards navigate, no overflow, axe
contrast), mobile 49/49, i18n 27/27, desktop regression 73/73. Lint + build
clean.
2026-10-08 19:19:20 +00:00
ef2a6a3589 Add “Make my homework”, and finish the mobile polish pass
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.
2026-10-08 18:30:33 +00:00
9fb728d735 Mobile UI pass: sheet swipe, ice-scene layering, contrast, touch targets
Phones (<=639px) only for layout/gestures; the theme-level fixes apply
everywhere, as they are token/design changes.

Settings sheet
- Swipe-to-dismiss reusing the InstallSheet gesture: the sheet follows the
  finger, closes past ~90px or on a fast flick, snaps back otherwise. Drag
  starts only from the handle/header and only while the body is scrolled to
  the top. Backdrop tap still closes.
- Fixed header + scrollable body: pb-safe + spacer, so nothing is clipped;
  a scroll cue (chevron) fades in while more content is below and out at
  the end.
- Show/Save stack under the field at 44px on phones; close, Test connection
  and Remove key are 44px too. Remove key is now a rose destructive action
  with a confirmation that the full-screen key setup comes back.
- API key note split into a short privacy line plus a collapsible
  'Using the local daemon?' tip (OLLAMA_AUTH=local / .env.local / signin).
- Fixed the missing space in the English tip ('ollama signin instead.').
- Health: kept the auto-check and banner; added a loading row and a
  'Checked just now' / 'Checked at HH:MM' freshness label (also for errors).

Ice scene / readability
- Verified the orbs were never above content (fixed z-index:-1 layer,
  pointer-events:none); they were just shining through the glass. Phones get
  lower blob/orb opacity and orbs tucked to the edges; the bottom-right orb
  is moved out from under the tab bar and the tab bar paints a more opaque
  surface so its labels hold contrast.

Contrast / themes
- Dark --text-subtle raised to #9db1d0 (8.6:1 page, 7.2:1 glass); helper
  text now uses the tokens.
- Disabled buttons are a visible muted chip (.btn-disabled) instead of a
  40%-opacity ghost.
- One ice primary gradient in both themes; danger/success now actually show
  their colour (bg-none strips the primary background-image that used to
  paint over !bg-rose-600).
- Light mode: cool card rims instead of white-on-white, stronger shadow.

Redundancy / touch
- Removed only the header gear; the hamburger drawer (folders, Settings,
  theme) and the tab-bar Settings stay.
- Theme toggle is a bordered chip with an action aria-label (drawer row too).
- Empty library hides the folder pills, demotes Import to a quiet link and
  keeps a single primary CTA. 44px targets for nav rows, segmented control
  and folder pills on phones.
- Fixed a pre-existing 16px horizontal overflow (header -mx-4 vs a
  padding-less parent) and let info values wrap instead of truncating.

Verified: mobile suite 47/47 (EN+CS, 360/390px), pixel-sampled contrast
audit 0 runs below WCAG AA (8 runs, both themes/locales; axe marks the glass
'incomplete'), i18n 27/27, desktop regression 72/72, lint + build clean.
Service worker cache bumped to freezmo-v5.
2026-10-08 17:37:51 +00:00
618ad5fb9c Add an English/Czech interface language switch
English stays the default; Czech is a full translation. Only the app chrome
is translated - notes and generated decks keep their own language, which is a
core feature, not a setting.

i18n
- lib/i18n-dict.ts: flat en/cs dictionaries (441 keys each), translate() and
  translatePlural() with real Czech plural rules (1 sada / 2-4 sady / 5+ sad).
  English is the default and the fallback for a missing key.
- lib/i18n.ts: browser store (freezmo.locale.v1), useI18n() hook, and the
  x-freezmo-locale header. The hook's t/plural are bound to the locale React
  is rendering with, so the first client render matches the server HTML and
  then re-renders in the stored language; module-level t is only for event
  handlers and non-React helpers.
- <html lang> follows the choice (inline script + setLocale).

UI
- Settings -> Language segmented control (English / Cestina), plus an EN/CS
  button on the key gate for first run.
- Every screen translated: sidebar, header, tab bar, library, deck cards,
  folder bar, create flow, all seven activities, study chrome, results,
  settings + health panel, API-key gate and help, install sheet, offline and
  the deck-not-found page.
- Plural-aware counts everywhere (decks, folders, activities, runs, photos,
  documents, pages, words, wrong attempts).

Server
- API routes and guards (generate, grade, health, http-guard, rate-limit,
  ollama error mapping) answer in the request's language, so error cards and
  the health panel never fall back to English mid-sentence.

Fixes
- A hydration mismatch when a non-default locale was stored silently dropped
  the .dark class after hydration; render-time t() is now bound to the
  hook's locale (UploadScreen.describeExtraction, ResultsScreen.answerFor,
  DeckCard's folder fallback).
- Bumped the service worker cache to freezmo-v4 for this deploy.

Tests: i18n 27/27 (gate toggle, persistence, html lang, Czech plurals,
settings switch) and a rebuilt regression suite 72/72 (English default,
library, create, all 7 activity types, results, settings, install sheet,
offline, PWA, security, Czech smoke).
2026-10-08 13:56:32 +00:00
33967f9454 Fix ordering giveaways, the stuck false label, add Regenerate deck
Ordering cards
- Generation prompt now forbids dates, years, times, step numbers and
  ordinals in ordering items (spec + hard rules), and the server-side
  validation drops any ordering card whose items still contain them
  instead of shipping a giveaway.

True/False
- The 'false ✕' swipe label was permanently visible on the card: its
  opacity curve was inverted, so the clamp at rest was 1. Flipped the
  range (and tightened both ranges for the elastic drag) — each label now
  only fades in while swiping in that direction.

Regenerate deck
- Results screen gains a Regenerate deck button: rebuilds the activities
  from the deck's stored source notes, replaces the deck in storage
  (attempts/best score reset) and starts the fresh run from slide 1.
- StudyFeed tracks the current deck so the header, export and messages use
  the regenerated title/activities.
- Fixed a spurious 0% attempt recorded when a regenerated deck swapped in:
  scroll anchoring held the old results position, so the feed briefly
  reported 5/5 with zero answers. The scroll now resets before the new
  slides render, overflow-anchor is disabled on the feed, and a run with
  no answers is never recorded.

Tests: new deck-fixes suite 15/15 (TF overlay at rest/drag, regenerate
with loading state + storage reset, ordering guard drop/keep); retry
14/14, deck chrome 15/15, pwa 16/16, final-ux 12/12, security 8/8,
contrast 20/20.
2026-10-07 11:25:22 +00:00
f98795436d Fix the iOS install sheet: exact Safari flow, chips, iOS blue, layout
Steps
- iOS now uses the exact flow: Safari users get 4 steps (⋯ → Share/More →
  Add to Home Screen → Add); non-Safari (CriOS/FxiOS/EdgiOS/in-app) get a
  first 'Open this page in Safari' step and 5 total. The vague 'in some
  browsers it's under…' wording is gone.

Chips and icons
- The ⋯ chip renders a single three-dot icon (icon-only pill) instead of
  the icon plus an '⋯' character (which showed two sets of dots).
- The More chip uses a chevron-down icon instead of the rounded-square.
- The accent pill uses real iOS blue: #0A84FF dark / #007AFF light, still
  the only coloured element.
- Steps and chips share one 16px type scale; chip labels are medium weight.

Typography and layout
- Numbers are small muted circles in a fixed 24px column, aligned with the
  first text line via matching line-height.
- Removed trailing periods after chips; steps tightened.
- Bottom padding is calc(24px + env(safe-area-inset-bottom)) and a
  secondary 'Maybe later' button sits below the steps.
- max-height 90dvh with inner scrolling; 36×4px low-contrast handle with
  real swipe-down-to-close (momentum off, spring back).
- App card shows the hostname only (no port), truncated.

Themes
- Dark (default): layered surfaces #1c1c1e sheet / #2c2c2e card / #3a3a3c
  chips with 8% white borders; subtle logo tile instead of solid white;
  backdrop is 50% black with 4px blur.
- Light only when the app theme was explicitly set to light, ignoring the
  system preference; borders #e0e0e0, chip fills #f2f2f2.

Also fixes a crash the test pressure exposed: a 429 (rate-limited)
/api/health response crashed Settings at health.vision.available.
fetchHealth now rejects non-OK/malformed responses so the dialog shows its
error banner instead of the app error screen.

Install-sheet suite now 64 checks; pwa/final-ux/security/contrast green.
2026-10-07 08:49:32 +00:00
26e36bc0c0 Add the Install the app bottom sheet for mobile browsers
- New InstallSheet mounted in the root layout: appears ~1s after load on
  iOS/Android phones and tablets, over any screen including the key gate,
  with a dimmed backdrop. Never on desktop, never in standalone mode, and
  at most once per 7 days after a dismissal (localStorage).
- Platform/browser detection (lib/install.ts): iPhone/iPad incl. iPadOS
  reporting as Mac with touch, Android, Safari vs Chrome vs other vs
  in-app browsers, with step 1 adapted accordingly.
- iOS: numbered steps with chips (Safari, Share, ... , More, Add to Home
  Screen) and the accent Add pill. Android: captured beforeinstallprompt
  turns into a native Install button; fallback steps use Install app /
  Add to Home screen chips and an accent Install pill.
- Close via X, backdrop, Escape or swipe-down on the handle; the site is
  untouched afterwards. Anchored to 100dvh so browser toolbars never hide
  the last step; pb-safe respects the home indicator.
- Dark sheet by default; light only when the app theme was explicitly set
  to light. Settings gains a mobile-only Install app row that reopens it.
- The inline head script captures beforeinstallprompt before hydration and
  records appinstalled. Old InstallHint banner removed.
- Tests: new install-sheet suite (45 checks across iPhone Safari/Chrome/
  in-app, Android Chrome/Samsung, desktop, prompt path, dismissal, 7-day
  expiry, standalone, Settings row, both themes); pwa suite updated.
2026-10-07 08:27:43 +00:00
dfa7b7502a Add full-screen API-key gate
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).
2026-10-06 21:20:22 +00:00
3ea2cea100 Freezmo: self-hosted notes-to-activities study app
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.
2026-10-06 20:56:55 +00:00