Performance for builders
Zvessa must feel instant and never drain the battery or heat the phone, on a five-year-old Android as much as a new iPhone. These are the budgets every block, kit, engine and mini-app keeps. The app-wide rules and how the core meets them: docs/PERFORMANCE.md.
The hooks #
src/lib/activity.ts tells a block when it may do work:
| Hook | True when |
|---|---|
useLive() | The screen is showing and the app is in the foreground |
useDecorativeMotion() | useLive() and the phone's Reduce Motion is off |
useReduceMotion() | The phone's Reduce Motion is on |
useAppActive() | The app is in the foreground |
Timers, sensors, media and game loops start only while useLive() is true and stop when it turns false. Decorative animation (beams, shimmer) runs only while useDecorativeMotion() is true.
Budgets #
Targets every change is measured against:
| What | Budget |
|---|---|
| Frames | 60 fps while scrolling and animating; no work over 16 ms on the JS thread |
| Start | The dashboard shows from cache before the network answers |
| Idle | Zero timers, animations, sensors or network when nothing on screen changes |
| A page | Draws only what's on screen; long lists are virtualised |
| A query | Reads through an index with a limit; returns only what the screen shows |
| Images | Sized to their box before upload; cached; no full-size photos in lists |
| A mini-app | Well under 200 KB of HTML; sleeps when hidden |
| App size | Only what's used ships: packages are added when their block starts |
Rules #
Nothing runs that can't be seen.
- Stop animations, timers, sensors and media when a block scrolls off screen, its tab is hidden or the app goes to the background. Resume on return.
- Mini-apps are paused for you when hidden:
requestAnimationFrame, timers and media stop, the page getsmyaibio:pause/myaibio:resumeevents, andwindow.zvessa.pausedsays which. Draw only while something moves.
Do the least work.
- Animate with Reanimated on the UI thread, never by setting state every frame. Animate transform and opacity, not layout.
- Respect
motion.speed: offandhaptics: off. - Lists longer than a screen use FlashList, with stable keys (block or record ids).
- Memoise renderers that take big props; never rebuild the theme per block (read it from
useWorkspace()). - Sensors at the lowest rate that works, one shared subscription (Device).
Ask the server for less.
- Convex queries are live: subscribe once, never poll.
- Take a limit on every read (
take(n)); page long lists. - Counts and totals on big data come from aggregates (Events engine), not by reading every row.
- Heavy or outside work (AI, HTTP, MCP) runs in background jobs and streams results back.
- Keep the blueprint small: data goes in collections, big files in storage, never inline.
Network and battery.
- Upload compressed media; download thumbnails first.
- Batch pushes on the server; never wake the phone for something it will show anyway.
- Nothing runs in the background unless the person turned it on for a kit (a step counter, a call).
Checking #
- Profile in a release-like build, not development mode.
- Watch the JS and UI frame rates while scrolling the longest page and running the heaviest mini-app.
- Leave the app idle on a page for a minute: CPU should sit near zero.