freezmo/AGENTS.md
pepaskopec-blip bfc0f806b9 The scene stops heating the MacBook
"Terribly high CPU usage, the MacBook gets really hot" - a fanless MacBook
Air on a 144Hz screen. Measured on that machine with headed engines
(process CPU plus the per-process GPU time macOS keeps): an idle Aero
library cost LibreWolf ~110% CPU and ~230ms/s of GPU, the study feed ~130%,
Chromium up to ~57% CPU and ~350ms/s of GPU, Safari ~220-250ms/s of
WindowServer GPU. Priced one by one:

- the gradient mask on .ice-floats re-rendered the animated layer offscreen
  on every frame (~75 points of Gecko CPU). The floats now rise through a
  plain rectangular band clear of the header, the bottom chrome and the
  rail, and fade in and out inside it, so the clip never cuts a visible one;
- the overlay grain forced a backdrop readback per frame for a texture that
  measured a mean <=0.11/255 difference: removed;
- the invisible blob drift made every frame a full-screen redraw: the blobs
  are painted once;
- static glass (answer tiles, feedback panel, chips, inputs, dropzones,
  lists, segmented controls, Aero hint chips) re-blurred the moving scene
  every frame: only floating glass blurs now, and Aero's baked chat chrome
  drops a live blur that never showed under the bake;
- the scene also parks while the window has no focus and under any dialog
  or sheet scrim (.scene-pause); the loading icon is a compositor-run CSS
  animation instead of a framer-motion main-thread loop.

After, focused and animating: LibreWolf library 109% -> 33% CPU, study
134% -> 34%, chat 130% -> 42%; Chromium GPU 2.5-4.4x lower with 7-22 points
less CPU; Safari's WindowServer GPU -20-26% in Aero, about half in dark.
Unfocused or under a sheet: nothing. bubbles-test sweeps every float through
41 phases against the band and pins the parking and the cost contract;
tests/heat-probe.mjs (macOS) repeats the measurement; the suites find
Playwright's Chromium on macOS.

All 15 suites pass, lint clean, the contrast scan lost a finding. Version
0.6.7, SW v89.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-15 19:42:29 +02:00

3.6 KiB

AGENTS.md — working rules for this repo

Freezmo has two environments. Every session — human or agent — follows these rules. They are not optional: production is the app the user actually uses.

Environments

Dev (this working tree) Production
Path ~/freezmo (where you edit) ~/freezmo-prod/current (release symlink)
Service freezmo-dev.service freezmo.service
Port 3217 (http://<lan-ip>:3217, LAN only) 3207 + the public URL
Build folder .next-dev (hot reload) .next/standalone inside the release
Data ~/freezmo/.freezmo-dev/ ~/freezmo-prod/shared/freezmo/
Env ~/freezmo/.env.local ~/freezmo-prod/shared/env.local
Identity DEV badge, dev favicon/theme, "Freezmo Dev" PWA normal

Dev runs next dev with hot reload; all live, real-time changes go there. Production only ever runs a pushed, verified commit deployed by scripts/deploy.sh (releases under ~/freezmo-prod/releases/, last 5 kept, scripts/rollback.sh to go back).

Rules

  1. During a task, only the dev instance updates. Never restart or rebuild production mid-task; production changes only through scripts/deploy.sh at the very end.
  2. Never build into a running instance's folder. The dev service owns .next-dev; npm run build (used by the test suites' spawned instances) owns .next; a release build owns its own release directory. Any build or test that needs a production build uses a temporary directory and port.
  3. At the end of a task: run npm run lint, npm run test:e2e and npm run build, bump the service-worker cache (public/sw.js), update the README, commit, push to main, then run scripts/deploy.sh. Production only ever runs a pushed, verified commit.
  4. scripts/deploy.sh is the only deploy path. It refuses a dirty tree, refuses commits that aren't on origin/main, runs lint + build + the full e2e suite against the new release on a temporary port, switches the current symlink atomically, health-checks /api/healthz for the new commit and rolls back automatically on failure.
  5. The Ollama API key never leaves the browser. It must never appear in exports, sync payloads, share links, logs, URLs, .env files committed to the repo, or release directories. Server data (sync blobs, push subscriptions, question reports) lives only under shared/.
  6. Both languages, always. Every user-facing string goes into lib/i18n-dict.ts in en and cs (the i18n test enforces key parity).
  7. Phones are first-class: ≤639px layouts, 44px touch targets, 16px inputs, safe-area padding; verify at 360px and 390px as well as desktop.
  8. Versioned storage, never lose data. Deck/homework/chat schemas are validated with zod on read; backups and sync share the same payload schema.
  9. The test suites live in tests/ (npm run test:e2e, filterable: npm run test:e2e -- chat). Add or extend a suite for every feature; a green suite set is part of "done". Diagnostics that are not gates: tests/bubble-perf.mjs, tests/heat-probe.mjs (macOS), tests/model-compare.mjs.
  10. Keep the service worker honest: bump CACHE_VERSION in public/sw.js for every user-visible deploy (the deploy itself does not do this).
  11. Version the app once per task. Bump package.json version with npm run version:bump: the patch digit goes 0-9 and after 9 it rolls into the minor (0.1.9 → 0.2.0). The version is baked into the build (lib/version.ts), reported by /api/healthz and shown in Settings next to the running commit — so every task's release is identifiable.