Commit graph

15 commits

Author SHA1 Message Date
13b3a80e1c Aero glass is transparent now, blurred without the per-frame cost
The white-ish fills are gone: every surface is a thin translucent glass with
the gloss and a bright rim, and the wallpaper reads through all of it.

The blur is a pre-blurred copy of the wallpaper (public/aero/
background-blur.webp, 3 kB) glued to the viewport with
background-attachment: fixed, layered under the tint and the gloss, instead
of a live backdrop-filter. Blurring the live scene measured 66ms per frame
at 2x CPU throttle (115 frames over 34ms) because the bubbles move behind
every surface; with the pre-blurred layer the same scene runs at 33ms and 8
slow frames — the same as the other themes, so the MacBook stays cool.

Sticky chrome, sheets and dropdowns keep a real backdrop-filter (they sit
over scrolling content, and their area is small).

Contrast: 0 below-minimum findings, 0 axe violations, all three themes.
Version 0.2.5, SW -> freezmo-v58.
2026-10-13 07:45:22 +00:00
9b0354fb11 Fix the clipped answer field + the doubled AI note; ordering only where it fits
UI bug from a phone screenshot (short answer, Czech):
- The textarea was flex-shrunk inside the slide's column (58px instead of
  ~82px), clipping the typed answer mid-line. Every direct child of a
  study/review/guide slide is now shrink-0 and the slide scrolls instead.
- Two AI disclaimers were visible at once: the activity's own after the
  grading box plus the feed's row. The activity-level copy is gone; the
  feed/guide row (which also carries the report control) is the single one.
  The phase-2 check now asserts the note sits below the graded feedback.
- The mobile suite's backdrop-tap check is deterministic too (the spring
  animation made the measured box mid-flight).

Ordering activities: the deck prompt now allows them only when the notes
hold a real sequence (chronological events, process stages, procedure steps,
a lifecycle, a ranked scale) and points at matching / multiple choice /
true-false when they do not - "sort these" was landing on decks with no
order at all.

Version 0.2.4, SW -> freezmo-v57.
2026-10-13 07:27:39 +00:00
f1c4a89adc Aero uses the real wallpaper + liquid glass buttons
The wallpaper is the one from the mockup: "The S Aesthetic That You
Definitely Recognize Frutiger Aero" via WallpaperSafari
(https://wallpapersafari.com/w/IlWuz7) — the URL in the request pointed at a
different (teal abstract) wallpaper, this is the grass/sky/water one that
matches the screenshot. Re-encoded to a 237 kB WebP in public/aero/, used as
the scene in aero only (light/dark never download it), and credited in
Settings next to the theme picker.

The drawn CSS scene (rays, reeds, skyline, caustics) is off over the photo;
the ten rising bubbles stay, and the orbs became extra bubbles. Buttons and
segments are liquid glass now — translucent, glossed top half, bright rim,
backdrop blur — instead of the solid aqua fill. Helper lines that sit
straight on the wallpaper (the keyboard tip, the AI note) get a small glass
chip: dark text on grass measured 2.5:1.

Also carries: the underwater scene + bubbles work, the floating cookie
notice, the Czech title (Tvůj mrazák) and the review-suite fix that made the
last deploy fail its gate.

Contrast: 0 below-minimum findings, 0 axe violations across all three
themes. Version 0.2.2, SW -> freezmo-v56.
2026-10-13 06:21:43 +00:00
67e2cefcfe Aero goes underwater with rising bubbles; floating cookie notice; Czech title
Frutiger Aero, closer to the mock:
- The scene is water now: sun shafts through a bright surface, caustic
  patches, reeds on the left, a city silhouette on the right, and ten soap
  bubbles that actually rise (own keyframes, varied sizes/speeds/sway; the
  spans are display:none outside aero so they cost nothing elsewhere, and
  they sit still under reduced motion).
- Pale blue glass at 0.8: still reads as glass, but dark navy text clears
  4.5:1 over the water. The phone tab bar mixes the sheet glass instead of
  the page — in aero the page *is* the water, which left its labels at 3.9:1.
- The aero page-level text (muted/subtle) is much darker for the same reason.
- The PWA status bar follows the new water blue (#2fa7e6).

The cookie notice is floating glass now (blurred backdrop, rim, shadow): as a
plain card it read as part of the page background instead of a notice.

Czech: "Tvoje mraznička" -> "Tvůj mrazák" (library title), test updated.

Contrast: 0 below-minimum findings and 0 axe violations across every screen,
all three themes. Version 0.2.1, SW -> freezmo-v55.
2026-10-13 05:46:53 +00:00
e56653dae1 Frutiger Aero theme, pixel-measured contrast fixes, sheet surfaces
Frutiger Aero (Settings -> Theme): a third theme, not a dark/light variant.
A sky gradient with a strip of grass, the glacier blobs become clouds, the
orbs soap bubbles, glass gets the classic horizon gloss and the buttons a
glossy aqua. The light/dark toggles (header, rail, chat header) hide while it
is on and the picker says why; the pre-paint script applies the class before
first paint and the PWA status bar follows (#3fa3e0). Its muted/frost text is
darker than Light's because those labels sit directly on the sky.

Contrast, measured on real pixels (tests/contrast.mjs, now covering aero):
- The light settings sheet was translucent over a dark scrim, so its muted
  text measured ~2.8:1. Sheets and dialogs now use --glass-sheet-bg (a more
  opaque surface), and Light's --text-muted is a touch darker.
- Result: 0 below-minimum findings and 0 axe violations across every screen,
  all three themes, phone and desktop.

Also: the suites now seed the cookie notice as already dismissed (they run as
a returning user); the regression suite opts out to test the notice itself —
the previous deploy failed its gate because the notice blocked a chat chip
click. The notice also hides during immersive sessions (overlay-open), so it
can never cover a study feed.

Version 0.1.8, SW -> freezmo-v54.
2026-10-12 22:10:16 +00:00
464781a7fd Privacy policy, terms of service, cookie policy + a cookie notice
Three plain-language documents in both app languages (lib/legal.ts, rendered
by components/LegalPage.tsx): /privacy describes the local-first storage, the
key never leaving the browser and the three opt-in server copies (sync,
reminders, reports); /terms covers the GPL-3.0-only software, your own Ollama
key, fair use and liability; /cookies states what is true — no cookies at all
(no Set-Cookie, no document.cookie), strictly-necessary local storage instead,
no tracking.

A cookie notice tells every new browser the same thing once and remembers the
dismissal (freezmo.consent.v1). It sits at z-44: below the immersive study
feeds so it can never cover a session, above the phone tab bar. Settings links
all three documents. The regression suite checks the pages render with real
sections and that dismissing the notice sticks.

Version 0.1.7, SW -> freezmo-v53.
2026-10-12 21:26:55 +00:00
df34337b4a Accessibility pass, report control for homework exercises, clearer rail actions
- 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.
2026-10-12 21:15:56 +00:00
8822baf4b7 Security: model budget + concurrency caps, SSRF guards, spoof-proof limits
Audit pass with the threats that actually apply to a self-hosted, account-less
app: getting the Ollama key throttled or banned, and someone grinding the app
down. Per-route windows already existed; these are the missing layers.

- lib/model-guard.ts: every route that spends a model call now takes a slot —
  60 calls per window across all clients (RATE_LIMIT_MODEL_TOTAL), 3 in flight
  per IP, 6 in total — and the slot is released when the call finishes, on
  errors and on client disconnects. Over the concurrency cap answers 429
  kind=busy, not a punishment. Slots are taken after every cheap rejection so
  a malformed request cannot hold one (found by the new suite: early 400s
  were leaking slots).
- Images must be base64 data URLs (dataUrlSchema in lib/schemas): a plain URL
  in an images field made Ollama fetch an arbitrary address on our behalf.
- Push endpoints must be a real push service (PUSH_ALLOWED_HOSTS, defaulting
  to FCM/Mozilla/Apple/WNS): the server POSTs to whatever is stored.
- The rate-limit identity is the LAST x-forwarded-for hop. Reading the first
  let a client spoof its own IP and dodge every per-IP limit.
- Disk guards: reports.jsonl stops at REPORTS_MAX_BYTES (8 MB), reminder
  devices cap at PUSH_MAX_SUBSCRIPTIONS (50).
- Security headers: frame-ancestors 'none' + X-Frame-Options DENY, nosniff, a
  referrer policy and a Permissions-Policy without camera/mic/geo/payment.

New tests/security-test.mjs (13 checks, registered in run-all) proves each
guard against a slow stub Ollama with deliberately tiny limits. README:
security notes + rate-limit tables updated. Version 0.1.3, SW -> v51.
2026-10-12 20:40:57 +00:00
aac6befb0d App versioning + the create page's missing top padding
Versioning: package.json carries the app version (0.1.2 here; one bump per
task with `npm run version:bump`, patch 0-9 rolling into the minor at 10),
baked into the build via lib/version.ts, reported by /api/healthz and shown
in Settings next to the running commit. AGENTS.md documents the rule; the
native suite checks both the API and the Settings row.

Fix: on desktop the app header is hidden, so every page owns its top padding —
/create was the only one without `lg:pt-10`, which slammed the Study deck /
Make my homework switch against the top of the window while /homework had a
comfortable gap. Both now sit at 40px; the wizard suite asserts the clearance
and that the two pages stay in step (90 checks).

SW -> freezmo-v50.
2026-10-12 20:04:26 +00:00
d559865a09 Check-my-work attachments, smart review follows deletions, GPL-3.0-only
- "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.
2026-10-12 16:04:53 +00:00
756a6257a2 Chat mode, phase 1: grounded streaming chat over your notes
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.
2026-10-11 18:32:11 +00:00
dbd5dfbc58 Move the e2e suites into the repo (tests/), out of /tmp
The browser harness lived in /tmp/opencode and an overnight reboot
wiped it. It now lives in tests/ and survives reboots, is versioned
and runs with one command.

- tests/lib.mjs: shared helpers (Chromium auto-detect, seeding,
  IndexedDB fixtures, stub/extra instance spawn with process-group
  cleanup, check/finish).
- tests/run-all.mjs + npm run test:e2e [filter]: runs the ten suites
  in order with a summary; suites that need a stub Ollama or VAPID
  keys start their own next start instance on 3208/3209, verify the
  port is free and kill the group on exit. Test scratch in
  tests/.tmp/ (gitignored).
- Suites rebuilt and green against the current build: regression 38,
  mobile 94 (tab matrix, ... sheet, settings sheet, axe), wizard 83,
  native 31 (PWA/offline/share/back/undo/haptics/draft), i18n 13,
  freeze-loading 6, review 15, phase1 36 (backup/sync/usage limits),
  phase2 42 (learning engine/reminders), phase3 21 (homework check).
  379 checks, 10/10 suites in one full run.
- playwright-core + axe-core added as devDependencies; CHROME env or
  the newest ~/.cache/ms-playwright chromium is used.
- README: new decision 45 documenting the harness; decision 44's
  verification note updated.

The older suites' exact sources were lost with the wipe, so the
rebuilt versions reconstruct the same feature areas rather than
being byte-identical.
2026-10-11 15:11:06 +00:00
e4a8e0ba01 Phase 2: learning engine — history, SRS review, mistakes, stats, exam, reminders
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.
2026-10-10 14:15:21 +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
3baa0fa8ce Initial commit from Create Next App 2026-10-05 07:05:33 +00:00