diff --git a/AGENTS.md b/AGENTS.md index 0dcd596c..cd03e8be 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,12 +8,12 @@ Patternflow is an open-source hardware instrument: four rotary encoders controll It is standalone (not Eurorack), powered from a 5 V power bank through a screw terminal, and it is not an analog video synthesizer. Sound arrives through the Audio edition (on-board microphone, OSC, MIDI), not the default firmware. ## Repository map -- `docs/` — contracts and long-form docs: `EDITIONS.md` (the firmware seam — read it before touching firmware), `rest-api.md` / `osc-spec.md` / `midi-spec.md` / `audio-ws-spec.md` (wire contracts integrations are built against), `pfst-v2-spec.md` (show files), `panel-compatibility.md`, `RELEASING.md`, `SERVICES.md` (production server ops), the assembly map (`docs/assembly/`), the legacy v2.x build guide (`docs/legacy/BUILD_GUIDE_v2.md`), `AUDIO_GUIDE.md` (sound into the panel), the manifesto, investigations, and media (build-guide images in `docs/build-guide/`). Root-level guides: `BUILD_GUIDE.md` (the **v3 board**: v3.9, and v3.0 the same way), `PATTERN_GUIDE.md` (community → deck → Pattern Lab → device), `FEATURE_GUIDE.md` (writing a firmware feature or edition). The live roadmap is patternflow.work/journal and the `/roadmap` page. +- `docs/` — contracts and long-form docs: `EDITIONS.md` (the firmware seam — read it before touching firmware), `rest-api.md` / `osc-spec.md` / `midi-spec.md` / `mqtt-spec.md` / `audio-ws-spec.md` (wire contracts integrations are built against, each with a folder of vectors beside it), `contract-vectors.md` (the vectors' format), `pfst-v2-spec.md` (show files, and `pfst-v2-vectors/`, the first vectors), `panel-compatibility.md`, `RELEASING.md`, `SERVICES.md` (production server ops), the assembly map (`docs/assembly/`), the legacy v2.x build guide (`docs/legacy/BUILD_GUIDE_v2.md`), `AUDIO_GUIDE.md` (sound into the panel), the manifesto, investigations, and media (build-guide images in `docs/build-guide/`). The vector folders are `rest-api-vectors/`, `osc-vectors/`, `midi-vectors/`, `mqtt-vectors/` and `audio-ws-vectors/`. Each vector is a JSON file that quotes one sentence of its spec in `says` and gives the steps that show a panel doing it. It follows the spec, not the firmware: where the firmware disagrees, the vector keeps the spec and the disagreement goes to an issue. An edit that rewords a quoted sentence fails its vector until the vector is brought along. Root-level guides: `BUILD_GUIDE.md` (the **v3 board**: v3.9, and v3.0 the same way), `PATTERN_GUIDE.md` (community → deck → Pattern Lab → device), `FEATURE_GUIDE.md` (writing a firmware feature or edition). The live roadmap is patternflow.work/journal and the `/roadmap` page. - `firmware/` — PlatformIO/Arduino code for the ESP32-S3. Main sketch folder: `firmware/patternflow/` with `patternflow.ino`, `config.h` (pin mappings, brightness, limits, LED calibration), `net_config.h` (Wi-Fi / Improv / OTA / self-update defaults and the lane scale — core only; a feature's own tunables live in `features//_config.h`, and settings TUNE a feature, they never add one; per-device secrets in gitignored `patternflow_secrets.h`), and `pattern_registry.h` (function-pointer pattern table; compiles in Origin alone — the rest of the curated set ships as the Basics pack). Shared engine code lives in `firmware/patternflow/src/` (`core_display.h` HUB75 driver, `core_encoders.h`, canvas/color/math/noise helpers, Wi-Fi/OTA/web-console modules, the hotspot in `core_hotspot.h`) — core only: OSC, audio, MQTT, weather, shows, MIDI and the clock all live in `features/`. `core_display.h` drives the panel through `src/hub75/`, ESP32-HUB75-MatrixPanel-DMA vendored at tag 3.0.13. Its `UPSTREAM.md` names the upstream commit and each commit taken from upstream since, and `patternflow.patch` holds every line Patternflow changed (`VENDORED.md` says why). An edit under `src/hub75/` rebuilds the patch with the command in `UPSTREAM.md`, never by hand: upstream ends some lines in spaces, and an editor that trims them breaks the hunks. `firmware/patternflow/abi/` is the frozen host ⇄ module contract. Curated patterns live in `firmware/patternflow/presets/` (`preset_origin.h`, `preset_wave_saw.h`, dated presets), each using its own pattern namespace; user patterns are `.pfm` modules uploaded to the device over Wi-Fi, built from `firmware/modules/` via `firmware/toolchain/build_module.py` (the old `custom1.h`–`custom3.h` root slots are gone). Features attach through `firmware/patternflow/features/` (named `addons/` until 2026-08-30; legacy spellings are shimmed for out-of-tree bundles — see docs/EDITIONS.md) — a directory and a descriptor of function pointers, with the core naming none of them (see its README). `firmware/bundles/` names firmwares built from the same tree (the shelf editions `audio` and `performance`, plus `clock` and `midi`, compositions the tree keeps buildable but does not publish; the default build has no bundle): two files each, saying which features compile in and what the build calls itself; `firmware/bundles/build.sh` builds the default or a named one, `shelf.sh` stages a publishable image. Device console pages are plain HTML in `firmware/patternflow/console/`, spliced into `*_index.h` by `firmware/toolchain/console_pages.py`. Tooling lives in `firmware/toolchain/` (repo-level scripts) and `firmware/patternflow/toolchain/` (PlatformIO `extra_scripts`, which must stay sketch-relative). Also: `firmware/encoder_test/` (standalone encoder diagnostic) and `firmware/CUSTOM_PATTERNS.md` (pattern authoring guide). - `hardware/` — Hardware designs. `case/` (Blender source in `source/`, STLs by printer bed size, `knobs/`, `legacy_v2/` for the v2.x board, `legacy_lasercut/` for the v1/v2 acrylic drawings, and `remixes/` — community enclosures, one folder each, whose README is its license header), `pcb/` (KiCad 10.0 source, Gerbers per revision, schematic PDF), `bom/` (`bom_v3.9.csv` — **the BOM source of truth**; the guide's table is derived from it. `bom_v3.0.csv` is kept for the previous board). - `web/` — Next.js site at patternflow.work: landing page (`/`, `/pattern`, `/build`, `/inside` are tabs of one view), the interactive guide (`/guide` is a hub over `/guide/build`, `/guide/play`, `/guide/make` and `/guide/audio`, each with a `/ko` twin; one 3D stage in `components/guide/`, the firmware simulator in `lib/guide/`), Pattern Lab (`/pattern-lab`), community (`/community/**`, SQLite + Drizzle + Better Auth, only on the Pi deployment), browser flasher (no page of its own: the **Flash Patternflow** button on `/pattern` and in the Play guide, installing `public/flash/manifest.json` and the images in `public/flash/bin/`; the bare `/flash` redirects to `/guide/play#flash`), the edition shelf (`/editions`; `/variants` redirects there), the feature catalogue (`/features`, reels per feature), device update handoff (`/update`), journal, roadmap. `lib/pattern/` is the pattern runtime and annotations, `lib/lab/` the Pattern Lab, `lib/community/` the community, `lib/ai/` the Gemini client. Architecture doc: `web/ARCHITECTURE.md`. The JS presets in `web/src/lib/presets/` are the source of truth for firmware preset headers. - `shared/` — `constants.json`, the values the firmware and the site both carry: the panel geometries `PANEL_GEOMETRY` picks from, the knob count, the encoder's 24 detents a turn, the bus maximum and the button timings. Two files are generated from it, `firmware/patternflow/src/core_shared.h` (each key with `PF_SHARED_` in front) and `web/src/lib/shared/constants.ts`, and neither is edited by hand. It exists because the lab counted 20 detents a turn, against the encoder's 24, until 31 July 2026. -- `tools/` — desktop-side helpers: `patternflow-audio-extension` (the browser audio-react extension — also the authoring source the device's `/audio-in` console page is assembled from), `patternflow-audio-android` (phone capture app), `rtpmidi-probe`, and `case-models` (builds the /build preview's per-case 3D models, `web/public/cases//model.glb`, from the case files in the repo; run by hand, not by the site build). +- `tools/` — desktop-side helpers: `patternflow-audio-extension` (the browser audio-react extension — also the authoring source the device's `/audio-in` console page is assembled from), `patternflow-audio-android` (phone capture app), `rtpmidi-probe`, `case-models` (builds the /build preview's per-case 3D models, `web/public/cases//model.glb`, from the case files in the repo; run by hand, not by the site build), and `virtual-panel`. `virtual-panel` is the one that is not a client: a Patternflow with no board, the guide's `DeviceSim` (`web/src/lib/guide/deviceSim.ts`) behind the ports the firmware listens on, with the five contracts in `transports/`. It runs each pattern's JS twin, never a `.pfm` module. The vector suites replay against it, so a change to `DeviceSim` is a change to the panel they test. It is a test fixture, and Wi-Fi and firmware updates are not part of it. - `integrations/` — host-software integrations, each with its own README. `integrations/ableton/` is the Max for Live bridge (knobs → Live parameters over OSC). The Home Assistant integration left this repository on 2026-09-03; its author maintains it separately. Integrations are built against the contracts in `docs/` (`rest-api.md`, `osc-spec.md`, `midi-spec.md`), not against the firmware source. `docs/rest-api.md` also has the table for choosing between HTTP, OSC and MQTT, and the rules that make the device's single-connection web server easy to knock over. ## Hard rules (do not violate) @@ -29,9 +29,11 @@ It is standalone (not Eurorack), powered from a 5 V power bank through a screw t ## Common commands - Web dev server: `npm run dev` (inside the `web/` directory) - Web production build: `npm run build` (inside the `web/` directory); the smoke suites are `npm run check:*`, and CI runs the ones `npm run check:ci` chains together. -- Web lint and typecheck: `npm run lint` and `npm run typecheck` (inside `web/`). CI runs lint with `--max-warnings=0`, so one warning fails a pull request the way an error does. The rules in `eslint.config.mjs` stay as they are. An exception is an `eslint-disable-next-line` comment for one rule, with its reason after `--`. +- Web lint and typecheck: `npm run lint` and `npm run typecheck` (inside `web/`). CI runs lint with `--max-warnings=0`, so one warning fails a pull request the way an error does. The rules in `eslint.config.mjs` stay as they are. An exception is an `eslint-disable-next-line` comment for one rule, with its reason after `--`. `typecheck` also reads `tools/virtual-panel/`, and lint does not. - Shared constants: change `shared/constants.json`, then run `npm run gen:shared` (inside `web/`) and commit the two files it writes. `npm run check:shared` fails while either is stale. It also reads the two copies the generator cannot write, the 1000 in the frozen `abi/pf_params.h` and the 128 and 64 in `firmware/toolchain/build_module.py`, and fails when they no longer match the JSON. - Drift checks: `npm run check:drift` (inside `web/`) runs each file in `web/scripts/drift/`, and each compares two things the tree keeps by hand, such as `hardware/bom/bom_v3.9.csv` and the build guide's parts table. `npm run check:` runs one: `links-absolute`, `bom`, `bom-schematic`, `features`, `editions`, `presets`, `roadmap`, `changelog` or `shared`. `check:ci` ends with it, and `.github/workflows/drift.yml` runs it on every pull request with `PF_DRIFT_STRICT=1`. At a desk a check skips a side it cannot read (a tag the clone lacks, no `pdftotext`, no `gh` login); in that workflow the skip fails. One check has no script of its own: `markers` fails on a `TODO` or `FIXME` in first-party code, and runs alone as `npx tsx scripts/drift.ts markers`. +- Contract vectors: `npm run check:rest-vectors` (inside `web/`) gives each file in `docs/rest-api-vectors/` a fresh virtual panel on free ports and prints what the vector expects beside what the panel did. `check:osc-vectors`, `check:midi-vectors`, `check:mqtt-vectors` and `check:audio-vectors` do the same for their folders. The panel's clock stands still between steps, so a 500 ms release costs no real time. `check:virtual-panel` runs each preset with a JS twin on a panel of its own for two seconds of panel time. A thrown error fails it. The six are files in `web/scripts/drift/`, so `check:drift` and `drift.yml` run them with the drift checks. +- Virtual panel: `npm run virtual-panel -- --port http=8080` (inside `web/`) starts one to talk to by hand. HTTP is on 80 as on the device, and Linux keeps ports below 1024 from a process that is not root, so the flag moves it; `--port '*=0'` takes a free port for each and prints them. `--features mqtt` composes a feature in by its directory name in `firmware/patternflow/features/`. `GET /frame.png` is the frame as the LEDs would show it, and `/vp/` is a hand and the clock. The firmware has neither. `tools/virtual-panel/README.md` has the rest. - Locked text: `npm run check:human-lock` (inside `web/`) fails when a word changes in the maintainer's own writing, the regions `docs/rfc-cleanup.md` §2.9 lists plus each path in `docs/.human-written`. Markup and link paths in them may change. Text moved word for word passes, and the move needs the maintainer's approval on the PR. A typo is reported in the PR, not fixed. `drift.yml` runs it on pull requests against the base commit; at a desk the base is the merge-base with `origin/dev`, or with `origin/main` in a clone that has no `dev`. - Voice lint: `npm run check:voice` (inside `web/`) reads the lines a branch adds since that same base and prints `file:line` for each banned word from `docs/voice-banned.txt`, list of three, opener that says what the text is about, or closing summary. Run it by hand before opening a PR that adds prose; CI does not run it. Files given as arguments are read whole, and `-` reads a PR description from stdin. - Firmware compilation: `./firmware/bundles/build.sh` (PlatformIO, bundled toolchain — not the Arduino IDE; the IDE path cannot build the editions' library set). That builds the default: the device with no features. Named editions are `./firmware/bundles/build.sh audio`, `performance`, `clock` and `midi` (the last builds in its own PlatformIO env, named in `bundles/midi/env`, because its USB port must be the OTG controller — a build flag, not a setting). `./firmware/bundles/build.sh all` builds every composition and scans each binary for per-feature marker strings, proving each image carries exactly its features — run it before pushing anything that touches firmware.