From 7ac82cf43b2ca3d68585ece63df52e02e6d2cc36 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 16:55:36 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=EC=8B=9C=EC=9E=91=20=E2=80=94=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC=204=EB=8B=A8=EA=B3=84=20PR=2022:=20=EB=82=98=EB=AC=B4?= =?UTF-8?q?=EB=A5=BC=20=EA=B7=B8=EB=A6=AC=EB=8A=94=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=EB=93=A4=EC=9D=B4=20=EA=B0=80=EC=83=81=20=ED=8C=A8=EB=84=90?= =?UTF-8?q?=EA=B3=BC=20=EB=B2=A1=ED=84=B0=EB=A5=BC=20=EA=B0=80=EB=A6=AC?= =?UTF-8?q?=ED=82=A8=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0115cC8RgMiEKctiBiBbMasD From d345d1ce53765464302de64bf9d5c18d19bbcbee Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 17:00:17 +0000 Subject: [PATCH 2/3] docs: point the tree's maps at the virtual panel and the contract vectors docs/README.md, REPOSITORY.md, CONTRIBUTING.md, tools/README.md, the virtual panel's README and the two web docs now name what step 4 PRs 1 to 20 added. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0115cC8RgMiEKctiBiBbMasD --- CONTRIBUTING.md | 2 +- docs/README.md | 11 ++++++----- docs/REPOSITORY.md | 6 +++--- tools/README.md | 3 ++- tools/virtual-panel/README.md | 17 ++++++++++++++++- web/ARCHITECTURE.md | 4 ++-- web/README.md | 19 +++++++++++-------- 7 files changed, 41 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 243adfbba..291c10739 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ Patternflow is a device, a firmware, a website and a pile of documentation, and | A fix to the firmware **core** (`patternflow.ino`, `src/`) | the file itself — for a bug or a real improvement to the device, never to make room for a feature | | the same, and the change must still be right on a build that has no features at all | | An enclosure or mechanical **remix** | `hardware/case/remixes/-/`, with a README that states Author, License, what it is based on, which board it fits, and the print or cut settings | one new folder; the official folders stay as they are | the README fields are checked | | A **bridge** to host software (TouchDesigner, Node-RED, Max, a DAW…) | `integrations//` with a README, plus a row in `integrations/README.md`. It talks to the panel through the contracts in `docs/` and never imports firmware or web code | one new folder | the README has to name the contract it is built against | -| A change to a **wire contract** | [`docs/rest-api.md`](docs/rest-api.md), [`osc-spec.md`](docs/osc-spec.md), [`midi-spec.md`](docs/midi-spec.md), [`mqtt-spec.md`](docs/mqtt-spec.md), [`audio-ws-spec.md`](docs/audio-ws-spec.md), [`pfst-v2-spec.md`](docs/pfst-v2-spec.md) | the spec: bump its version line and add a Version history row | `docs-links` | +| A change to a **wire contract** | [`docs/rest-api.md`](docs/rest-api.md), [`osc-spec.md`](docs/osc-spec.md), [`midi-spec.md`](docs/midi-spec.md), [`mqtt-spec.md`](docs/mqtt-spec.md), [`audio-ws-spec.md`](docs/audio-ws-spec.md), [`pfst-v2-spec.md`](docs/pfst-v2-spec.md) | the spec: bump its version line and add a Version history row. A vector that quotes a sentence you change changes with it, in the folder beside the spec ([`contract-vectors.md`](docs/contract-vectors.md)) | `docs-links`, and `drift`, which replays every vector against the [virtual panel](tools/virtual-panel/README.md) | | Web app work | `web/` — start at [`web/README.md`](web/README.md) and [`web/ARCHITECTURE.md`](web/ARCHITECTURE.md) | | `web-ci`: lint, typecheck, `check:ci` (the smoke suites and the drift checks), `next build` | | A **pattern** | **not this repository.** Make it in the [Pattern Lab](https://patternflow.work/pattern-lab), press **Publish**, and it is on the [Community](https://community.patternflow.work/community) for every Patternflow to play, under the license you choose. [PATTERN_GUIDE.md](PATTERN_GUIDE.md) walks the loop. The presets bundled in the firmware and the Basics pack are curated by the maintainer from the repository's own sources | | | diff --git a/docs/README.md b/docs/README.md index b834f7f8a..0ce9a0888 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,12 +4,13 @@ What is here, and what is not. The rule for the split: **the repository root kee ## Contracts — other software is built against these, not against the firmware source -- [`rest-api.md`](rest-api.md) — the device's HTTP API (`/api/*`), the console pages, and the table for choosing between HTTP, OSC, MIDI and MQTT -- [`osc-spec.md`](osc-spec.md) — OSC over UDP (DAWs, Max, TouchDesigner) -- [`midi-spec.md`](midi-spec.md) — the panel as a network MIDI port -- [`mqtt-spec.md`](mqtt-spec.md) — MQTT topics, both directions, the roles, and why the channel decides whether a write sticks -- [`audio-ws-spec.md`](audio-ws-spec.md) — the audio-react WebSocket the browser extension and the phone app speak +- [`rest-api.md`](rest-api.md) — the device's HTTP API (`/api/*`), the console pages, and the table for choosing between HTTP, OSC, MIDI and MQTT, with test vectors in [`rest-api-vectors/`](rest-api-vectors/) +- [`osc-spec.md`](osc-spec.md) — OSC over UDP (DAWs, Max, TouchDesigner), with test vectors in [`osc-vectors/`](osc-vectors/) +- [`midi-spec.md`](midi-spec.md) — the panel as a network MIDI port, with test vectors in [`midi-vectors/`](midi-vectors/) and those for `/api/midi` in `rest-api-vectors/` +- [`mqtt-spec.md`](mqtt-spec.md) — MQTT topics, both directions, the roles, and why the channel decides whether a write sticks, with test vectors in [`mqtt-vectors/`](mqtt-vectors/) and those for `/api/mqtt` in `rest-api-vectors/` +- [`audio-ws-spec.md`](audio-ws-spec.md) — the audio-react WebSocket the browser extension and the phone app speak, with test vectors in [`audio-ws-vectors/`](audio-ws-vectors/) and those for `/api/audio` in `rest-api-vectors/` - [`pfst-v2-spec.md`](pfst-v2-spec.md) — the `.pfs` show table, with test vectors in [`pfst-v2-vectors/`](pfst-v2-vectors/) +- [`contract-vectors.md`](contract-vectors.md) — how a vector of the first five is written, and how `npm run check:rest-vectors` and the four like it replay them against the [virtual panel](../tools/virtual-panel/README.md), a Patternflow with no board - [`panel-compatibility.md`](panel-compatibility.md) — a buying guide first (which HUB75 panels light up and which stay dark), then the reference for other sizes ## How the firmware is put together diff --git a/docs/REPOSITORY.md b/docs/REPOSITORY.md index 48a6ff1ba..d05a54e38 100644 --- a/docs/REPOSITORY.md +++ b/docs/REPOSITORY.md @@ -10,8 +10,8 @@ A map for people. (AI coding agents get the same map with more rules in [`../AGE | `hardware/` | `pcb/` (KiCad source, Gerbers per revision, schematic PDF), `case/` (STLs by printer bed size, knob plates, the Blender source, legacy v2 cases, the old v1/v2 laser-cut drawings in `legacy_lasercut/`, and `remixes/` — enclosures other people designed and built, one folder each), `bom/` (the CSV that is the BOM source of truth) | [`hardware/README.md`](../hardware/README.md) | | `web/` | The Next.js site at patternflow.work: landing page, the interactive guide (`/guide`, with `/guide/build`, `/guide/play`, `/guide/make` and `/guide/audio`), Live Editor, Pattern Lab, the community (only on the community host), the browser flasher (the **Flash Patternflow** button on the Pattern tab and in the Play guide; the images it installs are `public/flash/`), edition shelf, feature catalogue, device update handoff, journal, roadmap. The JS presets in `src/lib/presets/` are the source of truth for the firmware's preset headers. The breadboard build guide is a React page here, `src/app/build/breadboard/` | [`web/README.md`](../web/README.md), then [`web/ARCHITECTURE.md`](../web/ARCHITECTURE.md) | | `shared/` | `constants.json`, the values the firmware and the site both carry: the panel geometries, the knob count, the encoder's 24 detents a turn, the bus maximum and the button timings. `npm run gen:shared` in `web/` writes `firmware/patternflow/src/core_shared.h` and `web/src/lib/shared/constants.ts` from it. Neither is edited by hand, and `check:shared` fails while either is stale. The folder exists because the lab counted 20 detents a turn, against the encoder's 24, until 31 July 2026 | [`constants.json`](../shared/constants.json) | -| `docs/` | Contracts other software is built against, how the firmware is put together, walk-throughs, the audio guide (`AUDIO_GUIDE.md`), the assembly map, the legacy v2.x build guide (`legacy/`), the writing voice (`voice.md` and `voice-banned.txt`), project records (`releases/`, one file per release) and media | [`README.md`](README.md) | -| `tools/` | Clients Patternflow ships that run on their own: the audio-react browser extension (also the authoring source of the device's `/audio-in` page), the Android capture app, an RTP-MIDI probe. Also `case-models/`, the scripts that make the /build page's 3D model of each case, the official one and every remix on its case switch | [`tools/README.md`](../tools/README.md) | +| `docs/` | Contracts other software is built against, each with a folder of test vectors beside it, how the firmware is put together, walk-throughs, the audio guide (`AUDIO_GUIDE.md`), the assembly map, the legacy v2.x build guide (`legacy/`), the writing voice (`voice.md` and `voice-banned.txt`), project records (`releases/`, one file per release) and media | [`README.md`](README.md) | +| `tools/` | Clients Patternflow ships that run on their own: the audio-react browser extension (also the authoring source of the device's `/audio-in` page), the Android capture app, an RTP-MIDI probe. Also `case-models/`, the scripts that make the /build page's 3D model of each case, the official one and every remix on its case switch. `virtual-panel/` is the other end of the contracts: a Patternflow with no board, on the firmware's ports, that the vectors in `docs/` are replayed against | [`tools/README.md`](../tools/README.md) | | `integrations/` | Bridges that run inside or beside someone else's software (Ableton / Max for Live today) and talk to the panel only through the contracts in `docs/` | [`integrations/README.md`](../integrations/README.md) | | `.github/` | Issue and pull-request templates (including the guide's **Stuck here?** form and **Share your build**), `SUPPORT.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, the CI workflows (web, every firmware composition, the core-names-no-feature checks, doc links, the drift checks, community folders carrying their README, console-page sync, firmware release assets, Discord dev-log) and their helper scripts | the header comment of each workflow | | root | `README.md`, the three guides (`BUILD_GUIDE.md`, `PATTERN_GUIDE.md`, `FEATURE_GUIDE.md`), `CHANGELOG.md`, `CONTRIBUTING.md`, `CITATION.cff`, the two license texts and the `LICENSE` pointer, `AGENTS.md` (+ `CLAUDE.md`, a one-line shim) | | @@ -26,7 +26,7 @@ The rule for where a document lives: **the root keeps three guides and no others | Use the one you built | [patternflow.work/guide/play](https://patternflow.work/guide/play) — flashing, the knobs, installing patterns and the console, on a Patternflow you can turn. The first flash goes through download mode (hold BOOT, tap RST, let go of BOOT), and the Wi-Fi step usually does not appear the first time: the [Flash chapter](https://patternflow.work/guide/play#flash) and [`BUILD_GUIDE.md` §8](../BUILD_GUIDE.md#8-firmware) say what to do, in order | | Make a pattern | [`PATTERN_GUIDE.md`](../PATTERN_GUIDE.md) — the Pattern Lab, the community, and the device. No local toolchain involved. The same loop as a tutorial: [patternflow.work/guide/make](https://patternflow.work/guide/make). [`firmware/CUSTOM_PATTERNS.md`](../firmware/CUSTOM_PATTERNS.md) is the older, more hands-on route | | Write a firmware feature or cut an edition | [`FEATURE_GUIDE.md`](../FEATURE_GUIDE.md) → [`EDITIONS.md`](EDITIONS.md) → [`features/README.md`](../firmware/patternflow/features/README.md) → [`bundles/README.md`](../firmware/bundles/README.md) | -| Control the panel from other software | [`rest-api.md`](rest-api.md) has the table for choosing between HTTP, OSC, MIDI and MQTT; then the spec for the one you chose | +| Control the panel from other software | [`rest-api.md`](rest-api.md) has the table for choosing between HTTP, OSC, MIDI and MQTT; then the spec for the one you chose. [`tools/virtual-panel/`](../tools/virtual-panel/README.md) answers on the same ports with no board behind it | | Put sound into it | [`AUDIO_GUIDE.md`](AUDIO_GUIDE.md). The browser tab, the microphone and MIDI with a DAW, step by step: [patternflow.work/guide/audio](https://patternflow.work/guide/audio) | | Work on the site | [`web/README.md`](../web/README.md) → [`web/ARCHITECTURE.md`](../web/ARCHITECTURE.md) | | Contribute anything | [`CONTRIBUTING.md`](../CONTRIBUTING.md) — one table of where each kind of contribution goes | diff --git a/tools/README.md b/tools/README.md index 57fad5205..ef17f503b 100644 --- a/tools/README.md +++ b/tools/README.md @@ -1,6 +1,6 @@ # tools/ -Clients that Patternflow ships and that run on their own — a browser extension, a phone app, a diagnostic script. The line between this folder and [`integrations/`](../integrations/README.md): a **tool** is a standalone program of ours; an **integration** is a bridge that runs inside or beside someone else's software (a DAW, Max, TouchDesigner, Node-RED). Both talk to the panel only through the contracts in [`docs/`](../docs/README.md#contracts--other-software-is-built-against-these-not-against-the-firmware-source). +Clients that Patternflow ships and that run on their own — a browser extension, a phone app, a diagnostic script. The line between this folder and [`integrations/`](../integrations/README.md): a **tool** is a standalone program of ours; an **integration** is a bridge that runs inside or beside someone else's software (a DAW, Max, TouchDesigner, Node-RED). Both talk to the panel only through the contracts in [`docs/`](../docs/README.md#contracts--other-software-is-built-against-these-not-against-the-firmware-source). `virtual-panel/` is the one folder here on the panel's side of them. | Folder | What it is | Contract | | :--- | :--- | :--- | @@ -8,6 +8,7 @@ Clients that Patternflow ships and that run on their own — a browser extension | [`patternflow-audio-android/`](patternflow-audio-android/README.md) | The phone-side twin: captures what the phone is playing and drives the knobs with it, for filming. It takes its mapping from the panel | [`audio-ws-spec.md`](../docs/audio-ws-spec.md), and the microphone endpoints of [`rest-api.md`](../docs/rest-api.md#microphone-audio-edition) | | [`rtpmidi-probe/`](rtpmidi-probe/README.md) | A plain-Python RTP-MIDI session initiator that walks the MIDI contract against a panel from a machine with no MIDI driver | [`midi-spec.md`](../docs/midi-spec.md) | | [`case-models/`](case-models/README.md) | Not a client of the panel: the scripts that make the 3D model of each case the /build page's preview shows (`web/public/cases//model.glb`), from the case's own files in `hardware/case/`. Run by hand; the site's build does not run them | The node names `web/src/components/3d/caseModels.ts` finds in every model, listed in its README | +| [`virtual-panel/`](virtual-panel/README.md) | Not a client: a Patternflow with no board. The guide's `DeviceSim` answers on the firmware's ports, and the vectors beside each contract in `docs/` are replayed against it on every pull request. `npm run virtual-panel` in `web/` starts one | [`rest-api.md`](../docs/rest-api.md), [`osc-spec.md`](../docs/osc-spec.md), [`midi-spec.md`](../docs/midi-spec.md), [`mqtt-spec.md`](../docs/mqtt-spec.md) and [`audio-ws-spec.md`](../docs/audio-ws-spec.md), the device's side of each | ## The extension is also firmware source diff --git a/tools/virtual-panel/README.md b/tools/virtual-panel/README.md index 1ddc3be06..a95c1c820 100644 --- a/tools/virtual-panel/README.md +++ b/tools/virtual-panel/README.md @@ -9,4 +9,19 @@ HTTP is on 80 here as on the device, and Linux keeps every port below 1024 from `GET /frame.png` and everything under `/vp/` are the panel's own, and the firmware has neither. The PNG is the 128×64 frame as the LEDs would show it, brightness included. `/vp/turn?knob=2&detents=3` and `/vp/button?knob=4&down=1` are a hand, and `/vp/advance?ms=500` moves the manual clock. `/vp/state` says what the running pattern has been given since start, its knob deltas and button presses, with the name card over it and the first error it threw. Knobs count from 1, as in every contract. -A transport is a file in `transports/` whose default export is a `Transport` (`panel.ts`). `start()` attaches each one whose `feature` is `null`, and a feature's only when asked for it. Each gets a `Seam` with the `DeviceSim`, the panel's clock, the HTTP routes and its ports, a place for the fields it adds to `/api/status`, and events for a hand's turn, a button, a pattern change and sleep. Step 4's plan lists the transports to come ([k4 to k20](../../docs/investigations/2026-10-cleanup-step4-plan.md#the-prs)). +A transport is a file in `transports/` whose default export is a `Transport` (`panel.ts`). `start()` attaches each one whose `feature` is `null`, and a feature's only when asked for it. Each gets a `Seam` with the `DeviceSim`, the panel's clock, the HTTP routes and its ports, a place for the fields it adds to `/api/status`, and events for a hand's turn, a button, a pattern change and sleep. There are eight: + +| Transport | Feature | What it answers | `--port` | +| :--- | :--- | :--- | :--- | +| `rest-state.ts` | core | Device state in [rest-api.md](../../docs/rest-api.md): `/api/status`, `/api/params`, `/api/sleep`, `/api/display`, `/api/knobs`, `DELETE /api/crash` and the favicon | `http`, 80 | +| `rest-patterns.ts` | core | The pattern list, select and a module's files, over the Basics pack as `basics.zip` carries it, and the volume's fields in `/api/status` | `http` | +| `mqtt.ts` | `mqtt` | [mqtt-spec.md](../../docs/mqtt-spec.md) as a broker's client: the roles, the channels, the topics and the eight-second snapshot. `GET /vp/mqtt` is the banner a `message` put up | none, it dials the broker `/api/mqtt` names | +| `mqtt-http.ts` | `mqtt` | `/api/mqtt`, the device page's `/api/mqtt/config` and `/api/mqtt/forget`, and the three `mqtt` fields in `/api/status`. The Director's routes are the 404 | `http` | +| `osc.ts` | `osc` | [osc-spec.md](../../docs/osc-spec.md) both ways | `osc`, 9001, and replies to `oscReply`, 9000 | +| `midi.ts` | `midi` | The RTP-MIDI session in [midi-spec.md](../../docs/midi-spec.md) and its *Host → device* table | `midi`, 5004, and the next port for data | +| `midi-out.ts` | `midi` | *Device → host* on `midi.ts`'s session, `/api/midi` and the `midi` block in `/api/status` | `http` | +| `audio-ws.ts` | `audio` | The WebSocket in [audio-ws-spec.md](../../docs/audio-ws-spec.md), its 500 ms release and `/api/audio` | `ws`, 81 | + +Each file's header comment says which lines of the spec it follows, and which firmware source it reads where the spec says nothing. What none of them does is in each vector folder's README, under the vectors it does not have. + +Nothing in CI runs `main.ts`. The suites in `web/scripts/drift/` call `start()` themselves: a vector suite gives each vector a fresh panel on free ports and the manual clock, and `check:virtual-panel` gives each preset in `firmware/patternflow/presets/` one of its own for two seconds and fails when any of them throws. diff --git a/web/ARCHITECTURE.md b/web/ARCHITECTURE.md index abf538bf1..90e187949 100644 --- a/web/ARCHITECTURE.md +++ b/web/ARCHITECTURE.md @@ -118,6 +118,6 @@ PostHog (`src/providers/PostHogProvider.tsx`, event helpers in `src/lib/posthogE - Styling: Tailwind v4 (`@import "tailwindcss"` in `globals.css`) + global custom CSS; larger components use CSS Modules (`*.module.css`). - **Layering:** `lib/` is the bottom. `app/` and `components/` import from it; it never imports from them (ESLint enforces it). A type a component and a serializer share belongs in `lib/` — `lib/community/cardTypes.ts` is the precedent. - Adding a preset: add the JS file under `src/lib/presets/`, register it in `index.ts`, then generate the firmware `.h` with the Pattern Lab's **To hardware** → *Copy the conversion prompt* flow. -- Tests are the `check:*` scripts in `package.json` — bespoke smoke suites under `scripts/*-smoke.ts`, one per subsystem — plus `check:panels`, a Vitest + jsdom + Testing Library harness (`vitest.config.ts`, `test/setup.ts`) for the Pattern Lab panels, where pointer gestures, keyboard shortcuts, undo and the Director's keyframes need a DOM. Those tests live beside the panels (`panels/*.test.tsx`) and read the store's buffers rather than the canvas, which jsdom does not have. `npm run check:ci` runs every one that needs nothing a CI runner lacks; `check:module` wants the Xtensa toolchain and stays local. The drift checks are `check:*` scripts too, one file each in `scripts/drift/`. Each compares two things the tree keeps by hand, the BOM and the build guide's parts table for one, and `check:drift`, the last step of `check:ci`, runs them all. -- CI (`.github/workflows/web-ci.yml`) lints, typechecks (`npm run typecheck`), builds and runs `check:ci` on every PR touching `web/`. `.github/workflows/drift.yml` runs `check:drift` on every PR, whatever it touches, and `check:human-lock` against the PR's base. It sets `PF_DRIFT_STRICT=1`: at a desk a check skips a side it cannot read (a tag the clone lacks, no `pdftotext`), and in CI that skip fails. +- Tests are the `check:*` scripts in `package.json` — bespoke smoke suites under `scripts/*-smoke.ts`, one per subsystem — plus `check:panels`, a Vitest + jsdom + Testing Library harness (`vitest.config.ts`, `test/setup.ts`) for the Pattern Lab panels, where pointer gestures, keyboard shortcuts, undo and the Director's keyframes need a DOM. Those tests live beside the panels (`panels/*.test.tsx`) and read the store's buffers rather than the canvas, which jsdom does not have. `npm run check:ci` runs every one that needs nothing a CI runner lacks; `check:module` wants the Xtensa toolchain and stays local. The drift checks are `check:*` scripts too, one file each in `scripts/drift/`. Each compares two things the tree keeps by hand, the BOM and the build guide's parts table for one, and `check:drift`, the last step of `check:ci`, runs them all. Five hold a contract against a running panel instead. `check:rest-vectors`, `check:osc-vectors`, `check:midi-vectors`, `check:mqtt-vectors` and `check:audio-vectors` replay the vectors beside each spec in `docs/` against the virtual panel in `tools/virtual-panel/`, which is `lib/guide/`'s `DeviceSim` behind the firmware's ports. A vector also fails when the spec sentence it quotes is gone. `check:virtual-panel` is not a vector suite: it runs every preset on that panel. A file there whose name starts with `_` is a library the suites share. `_broker.ts` among them is a test broker, because a Patternflow is a broker's client and the MQTT suite has to bring one. +- CI (`.github/workflows/web-ci.yml`) lints, typechecks (`npm run typecheck`, which reads `tools/virtual-panel/` too), builds and runs `check:ci` on every PR touching `web/`. `.github/workflows/drift.yml` runs `check:drift` on every PR, whatever it touches, and `check:human-lock` against the PR's base. It sets `PF_DRIFT_STRICT=1`: at a desk a check skips a side it cannot read (a tag the clone lacks, no `pdftotext`), and in CI that skip fails. - **Deployment weight:** Vercel meters storage per *retained deployment* — every one counts at its full output size, static files and function bundles both, with nothing shared between deployments — so size × push count is what the Hobby allowance (10 GB each) is spent on. Only `main` deploys: `vercel.json` turns deployments off for `dev`, which was three quarters of them and whose previews nobody opened. `public/` is ~90 % of a deployment's static weight (journal and build-map photos). Server functions must carry none of it: `outputFileTracingExcludes` in `next.config.ts` keeps `public/` out of every function, and a build that prints "Dynamic filesystem access causes tracing of the whole project" has found a new way in — the comment there has the history. diff --git a/web/README.md b/web/README.md index 3ac0be4f6..97a545bd4 100644 --- a/web/README.md +++ b/web/README.md @@ -14,13 +14,16 @@ npm run dev # http://localhost:3000 Other scripts: ```bash -npm run build # production build — must pass before merging (CI hard gate) -npm run lint # eslint -npm run typecheck # tsc --noEmit -npm run check:ci # every smoke suite CI runs, then the drift checks (check:module wants the Xtensa toolchain and stays local) -npm run check:drift # the drift checks alone; check:bom, check:editions and the others run one each -npm run check:human-lock # fails if a word of the maintainer's own writing changed since the merge-base with origin/dev -npm start # serve the production build +npm run build # production build — must pass before merging (CI hard gate) +npm run lint # eslint +npm run typecheck # tsc --noEmit, here and in ../tools/virtual-panel +npm run check:ci # every smoke suite CI runs, then the drift checks (check:module wants the Xtensa toolchain and stays local) +npm run check:drift # the drift checks alone; check:bom, check:editions and the others run one each +npm run check:rest-vectors # a drift check: ../docs/rest-api-vectors against a virtual panel; osc-, midi-, mqtt- and audio-vectors the same +npm run check:virtual-panel # a drift check too, but no vectors: every preset runs two seconds on a virtual panel without throwing +npm run virtual-panel # a Patternflow with no board, until Ctrl-C; on Linux add -- --port http=8080 (../tools/virtual-panel/README.md) +npm run check:human-lock # fails if a word of the maintainer's own writing changed since the merge-base with origin/dev +npm start # serve the production build ``` Requires Node 20+ (matches CI). @@ -41,7 +44,7 @@ None are required — the site runs fully without them. The community host needs - `src/lib/presets/` — JS pattern library; **source of truth** for the firmware preset headers (`firmware/toolchain/check_presets.py` keeps the two in step). - `content/` — markdown/MDX site copy (`build.md`, `pattern.md`, `inside.md`) and the journal. The journal is the maintainer's own writing. - `public/flash/` — esp-web-tools manifest + firmware binaries for the browser flasher (there is no `/flash` page: the **Flash Patternflow** button lives on the Pattern tab and in the Play guide). `public/packs/` — the Basics pack. `public/guide/` — the guide's models, its flasher captures, and the live console and editor demos (`firmware/toolchain/console_demo.py`, `editor_demo.py`). -- `scripts/` — build-time helpers (`build-sandbox.ts`, `import-patterns.ts`, ...), the build worker, and the `check:*` smoke suites (`*-smoke.ts`, run with `tsx`); `test/setup.ts` — the setup for the Vitest component tests that sit beside the code (`src/**/*.test.ts(x)`, `npm run check:panels`); `drizzle/` — community DB migrations; `.env.example` — every variable the community host reads. +- `scripts/` — build-time helpers (`build-sandbox.ts`, `import-patterns.ts`, ...), the build worker, the `check:*` smoke suites (`*-smoke.ts`, run with `tsx`), and the drift checks in `scripts/drift/`, the contract-vector suites among them; `test/setup.ts` — the setup for the Vitest component tests that sit beside the code (`src/**/*.test.ts(x)`, `npm run check:panels`); `drizzle/` — community DB migrations; `.env.example` — every variable the community host reads. - `AGENTS.md` here is a block `next dev` writes for AI agents; `CLAUDE.md` is a one-line shim to it. The project's own agent context is the root `AGENTS.md`. ## Contributing From 2ff123a0447dd0e198ffd3da859cfb1f9b5ced63 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 17:02:57 +0000 Subject: [PATCH 3/3] docs: name the MQTT routes the virtual panel answers 404 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0115cC8RgMiEKctiBiBbMasD --- tools/virtual-panel/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/virtual-panel/README.md b/tools/virtual-panel/README.md index a95c1c820..7cade56ab 100644 --- a/tools/virtual-panel/README.md +++ b/tools/virtual-panel/README.md @@ -16,7 +16,7 @@ A transport is a file in `transports/` whose default export is a `Transport` (`p | `rest-state.ts` | core | Device state in [rest-api.md](../../docs/rest-api.md): `/api/status`, `/api/params`, `/api/sleep`, `/api/display`, `/api/knobs`, `DELETE /api/crash` and the favicon | `http`, 80 | | `rest-patterns.ts` | core | The pattern list, select and a module's files, over the Basics pack as `basics.zip` carries it, and the volume's fields in `/api/status` | `http` | | `mqtt.ts` | `mqtt` | [mqtt-spec.md](../../docs/mqtt-spec.md) as a broker's client: the roles, the channels, the topics and the eight-second snapshot. `GET /vp/mqtt` is the banner a `message` put up | none, it dials the broker `/api/mqtt` names | -| `mqtt-http.ts` | `mqtt` | `/api/mqtt`, the device page's `/api/mqtt/config` and `/api/mqtt/forget`, and the three `mqtt` fields in `/api/status`. The Director's routes are the 404 | `http` | +| `mqtt-http.ts` | `mqtt` | `/api/mqtt`, the device page's `/api/mqtt/config` and `/api/mqtt/forget`, and the three `mqtt` fields in `/api/status`. It has no Director, so `/api/mqtt/director`, `/flowlocal` and `/mode` are the 404 | `http` | | `osc.ts` | `osc` | [osc-spec.md](../../docs/osc-spec.md) both ways | `osc`, 9001, and replies to `oscReply`, 9000 | | `midi.ts` | `midi` | The RTP-MIDI session in [midi-spec.md](../../docs/midi-spec.md) and its *Host → device* table | `midi`, 5004, and the next port for data | | `midi-out.ts` | `midi` | *Device → host* on `midi.ts`'s session, `/api/midi` and the `midi` block in `/api/status` | `http` |