- FrameOS is an "operating system for single-function smart frames" designed to run on Raspberry Pi–class hardware with a mix of e-ink and standard displays. The ecosystem includes a backend control plane, a frontend web UI, and firmware/runtime components for the frames themselves. 【F:README.md†L1-L40】
- Typical usage: run the backend service to manage frames, configure hardware-specific scenes, and deploy code or prebuilt scenes to devices over SSH.
backend/– Python FastAPI application that exposes REST/WS APIs, schedules background jobs, and manages persistence via SQLAlchemy. Includes Alembic migrations, ARQ worker tasks, and pytest suites. 【F:backend/app/fastapi.py†L1-L101】【F:backend/app/tasks/worker.py†L1-L64】【F:backend/app/models/user.py†L1-L16】frontend/– React + TypeScript single-page application built with esbuild, Tailwind, and kea state management. Compiled assets live infrontend/distand are served by the backend when present. 【F:frontend/package.json†L1-L66】【F:backend/app/fastapi.py†L38-L86】frameos/– Nim-based runtime for devices, containing system drivers, Nim app definitions, and assets compiled into the on-device application. Nim apps live insrc/apps; repo-provided JavaScript app templates live outside the runtime underrepo/apps/code(with the general repo app layout stillrepo/apps/<folder>/<app>). Entry pointsrc/frameos.nimboots the async runtime. 【F:frameos/src/frameos.nim†L1-L6】e2e/– Scene/asset generation utilities and snapshot-based end-to-end tests for validating rendered output. 【F:e2e/README.md†L1-L6】- Supporting files at the root include Docker configuration, Procfile, install scripts, and version metadata for packaging and deployment. 【F:docker-compose.yml†L1-L14】
- Environment configuration uses
Configclasses driven by env vars such asSECRET_KEY,DATABASE_URL,REDIS_URL,HASSIO_RUN_MODE, and debug/test toggles. During development (DEBUG=1) it autogenerates a.envwith a fallbackSECRET_KEY. 【F:backend/app/config.py†L1-L86】 - FastAPI application wiring (in
app/fastapi.py):- Registers gzip middleware, websocket routers, and routes grouped by auth level (
api_public,api_no_auth,api_with_auth). - Serves compiled frontend assets (or source HTML during tests) unless running in Home Assistant public ingress mode.
- Initializes a shared
httpx.AsyncClient, Redis listener, and PostHog analytics integration during startup. - Custom exception handlers degrade gracefully to JSON for API calls and reuse the SPA shell for 404/validation errors in non-test scenarios. 【F:backend/app/fastapi.py†L1-L112】
- Registers gzip middleware, websocket routers, and routes grouped by auth level (
- Persistence uses SQLAlchemy ORM models (e.g.,
Userwith hashed passwords) and Alembic migrations (seemigrations/). Session factory exposed fromapp/database.py. 【F:backend/app/models/user.py†L1-L16】 - Background jobs run through
arqwith Redis: worker defined inapp/tasks/worker.pyloads tasks for deploying/resetting frames, building SD images, and controlling agents. Startup hooks share HTTP, Redis, and DB clients. Run viaarq app.tasks.worker.WorkerSettings. 【F:backend/app/tasks/worker.py†L1-L64】 - Tests rely on pytest + pytest-asyncio fixtures defined in
app/conftest.py; there is broad coverage across API, websocket, and model layers underapp/api/testsandapp/models/tests. 【F:backend/app/conftest.py†L1-L65】【F:backend/app/api/tests/test_frames.py†L1-L183】 - Common local workflows:
- Install dependencies:
pip install -r requirements.txt(generated fromrequirements.in). - Run the web server:
uvicorn app.fastapi:app --reload(ensuringfrontend/distexists orTEST=1to use source HTML). - Start worker:
arq app.tasks.worker.WorkerSettings. - Execute tests:
pytest(optionally viabackend/bin/testshelper). 【F:backend/bin/tests†L1-L3】
- Install dependencies:
- Built as an ESM React app with TypeScript; kea manages state and type generation (
kea-typegen). - Build pipeline orchestrated by
build.mjsusing esbuild, with Tailwind/PostCSS for styling and optional bundle analysis viavite-bundle-visualizer. - Development:
pnpm installfollowed bypnpm --dir frontend run dev(spawns kea typegen watch and esbuild dev build concurrently). - Repo-level local development runner:
pnpm devstartsmprocswith panes for backend API, ARQ worker, the main frontend dev server, and the frame-local frontend watcher.redis,frameos, andbackend-dockerpanes are available but do not autostart. Thebackend-dockerpane runsscripts/backend-docker.sh, which persists a generated DockerSECRET_KEYin the gitignored.env.docker.local.mprocs.yamldefines the process list. - Production build:
pnpm --dir frontend run buildwhich chains kea codegen, schema generation (ts-json-schema-generator), TypeScript type-checking, and final bundling todist/. 【F:frontend/package.json†L6-L66】 - The published
frameos-editorpackage has its owneditorcomponent hash/version inversions.json;project-folders.jsonincludes shared frontend sources so frontend-only editor changes receive a new npm version during release. - Output folder is consumed by the backend’s static file mounts; ensure
frontend/distexists (e.g., viapnpm --dir frontend run build) before running the Python app outside of test mode. 【F:backend/app/fastapi.py†L38-L86】 - ALWAYS prefer writing frontend business logic in kea logic files over using effects like
useStateoruseEffect. - This includes small functions and callbacks inside components. Prefer to keep as much code as possible in logic files, treating React as a templating layer.
- When adding a frame model key that is tracked for deploy changes in
frontend/src/scenes/frame/frameLogic.ts, also add a marker toFRAME_KEY_INTRODUCED_FRAMEOS_VERSION. For unreleased work, use the next patch after the currentversions.jsonFrameOS base version.
- HARD RULE — no image proxies for frames, EVER. Frames download and render images directly from their sources; never route a frame's image fetches through the backend (or any other middleman) to resize or fetch on its behalf, and don't paper over device limits with host-side resize params either. When a source serves images too large for a device, THE fix is better on-device streaming decode (incremental inflate, row-by-row unfilter/scale into the target — a multi-MB PNG should need its compressed body plus a few rows, not a full-resolution RGBA buffer). Proxies are fine for in-browser previews only. Proxying has been implemented and reverted before — do not implement it again.
frameos/frameoshouses the on-device runtime written in Nim with asyncdispatch.- Entry point
src/frameos.nimwaits onstartFrameOS()defined undersrc/frameos/frameos. Drivers, system integrations, and Nim app implementations live in nested directories (src/apps,src/drivers,src/system); JavaScript example app sources/configs live underrepo/apps/<folder>/<app>. 【F:frameos/src/frameos.nim†L1-L6】 - JavaScript repo apps under
repo/apps/codeare catalog templates for custom code apps. Do not generate or commit Nim wrappers insiderepo/apps; compiled scenes that use them copy their sources into generatedsrc/apps/sceneapp_*folders during build/deploy. - Project uses Nimble (
frameos.nimble) for builds;Makefilelikely wraps build/deploy steps for device firmware.
e2e/directory contains scripts (run,makescenes.py,makesnapshots.py) to render scenes and compare against stored snapshots ine2e/snapshots. Run all tests with./runfrom that directory, or specify individual scenes like./run dataGradient. 【F:e2e/README.md†L1-L6】- Do not run frontend visual regression tests (
e2e/frontend-visual, Playwright screenshots, or snapshot updates) during normal iteration. Only run them when the user explicitly asks for visual tests or asks to commit changes. - Never commit visual regression snapshot updates produced by a backend/frontend stack running locally. Local macOS font rendering and browser rasterization differ from CI's Linux environment, so CI should be the source of truth for Playwright visual baselines.
- Docker support: top-level
docker-compose.ymlbuilds the full stack (backend plus dependencies) exposing port 8989 and persisting SQLite DB under a named volume. DockerfileandProcfile(not detailed here) package the backend/frontend bundle; watchtower example commands inREADME.mdshow daily update flows.- Environment variables documented in backend config govern integration with Home Assistant (HASSIO), Redis, PostHog analytics, and secret management. 【F:README.md†L24-L71】【F:backend/app/config.py†L1-L86】
- Install JS deps once from the repo root (
pnpm install) and build the frontend (pnpm --dir frontend run build). - Install backend deps (
cd backend && pip install -r requirements.txt). - Launch the local development stack with
pnpm dev, or run the API (uvicorn app.fastapi:app --reload) and background worker (arq app.tasks.worker.WorkerSettings) separately if needed. - Optionally bring up the stack via Docker (
docker compose up --build). - Use the backend UI/API to manage frames, deploy scenes, and monitor logs.
Keep this file updated as architecture or workflows change so future agents have an accurate snapshot of the repository.