Contributor guide
Repo layout, dev server, conventions, and the rules that keep the seven planes coherent.
Repo layout (the parts you'll actually touch)
nodered/
├── r2d2-fleet/ <- the dashboard (Next.js 15, React 19, Tailwind 4)
│ ├── src/
│ │ ├── app/ <- App Router routes
│ │ │ ├── (root)/ <- the dashboard pages
│ │ │ ├── api/ <- 40+ route handlers (the OpenAPI source of truth)
│ │ │ └── droidspeak/ <- this handbook lives here
│ │ ├── components/ <- reusable UI primitives + per-plane components
│ │ ├── lib/ <- domain logic, no React
│ │ └── store/ <- Zustand stores
│ ├── content/docs/ <- MDX content for Droidspeak (you are here)
│ ├── public/openapi.yaml <- hand-authored OpenAPI spec (until zod adoption)
│ └── k8s/ <- deployment manifests + Helm values
├── instances/ <- the Node-RED registry (registry.yaml lives here)
└── ...Running the dev server
Always use the persisted dev server. Never start next dev directly — two dev
processes on the same port corrupt .next/ and you'll waste 20 minutes diagnosing
chunk-not-found errors.
cd r2d2-fleet
npm run dev:persist # starts (or attaches to) the dev server on :3210
npm run dev:persist:clean # nukes .next first, then startsOpen http://localhost:3210. The sidebar should appear with the Star-Wars-themed nav.
If you see a 500 Internal Server Error from a fresh checkout, run dev:persist:clean
once.
Conventions that aren't optional
- One source of truth per plane. Each plane has a single YAML config file
(
registry.yaml,videos-config.yaml,holocron-config.yaml,archives-config.yaml,republic-config.yaml). The Settings UI edits those files; nothing else does. - Read-before-write on every mutation. Especially for Restreamer — every PUT fetches the current process config first, diffs in-memory, and writes the merged result. We never blind-overwrite a process we haven't just observed.
- Reuse the table primitives.
src/components/table/DataView+ TanStack is the only way new tables get built. Never hand-roll a<table>— the toolbar, sort, filter, and pagination are non-trivial to get right. - No internal-guideline subtitles in page chrome. Page subtitles either say something the operator needs to know to do their job, or they're empty. They're not a place to paste design rationale.
- No internal tracker IDs in user-facing copy. This handbook is user-facing. If a decision is interesting enough to mention, it gets a paragraph here, not a code.
- Errors are structured. API routes return
{ ok: false, error: "<machine-readable-key>" }with an appropriate status. The dashboard's toast system reads that.
Adding a new API route
- Drop a
route.tsundersrc/app/api/<plane>/<resource>/. Stay inside an existing tag if one fits — the plane that owns the resource owns the route. - Use plain
NextResponse.json(...). No streaming, no SSE unless you have a strong reason — the existing 40+ routes are all JSON-in / JSON-out. - Validate the request body. Today that's hand-coded — there is an open plan to adopt
zod across every route, which will also auto-generate the OpenAPI spec. Until then,
the spec at
public/openapi.yamlis the source of truth and must be updated in the same PR as the route. - Audit-log every POST / PUT / DELETE that mutates external state. Restreamer routes
write to
data/restreamer-audit.jsonl; new mutating routes should pick the audit log for their plane (or create one).
Adding a Droidspeak page
- Create
content/docs/<plane>/<page>.mdxwith frontmattertitle+description. - Add the slug to the parent
meta.jsonso it appears in the sidebar in the right order. - Cross-link from sibling pages when relevant. Use relative links — Fumadocs rewrites them.
- Don't reference internal trackers, beads codes, or commit hashes in the body. Those
belong in
.settings/features/*and the commit message.
Star Wars vocabulary cheat-sheet
| Code name | What it is |
|---|---|
| R2-D2 | the dashboard itself |
| HoloVids | the camera feed grid (one per feed) |
| HoloChron | a UXV/map widget embedded from the Force |
| HoloNet | the MQTT Explorer iframe |
| Holoprojector | the camera wall projection (Views = layout + bindings) |
| Holocron | a config surface (fleet/map settings) |
| Magic URL | a public, auth-bypassed read-only URL for a panel/group |
| The Force | the collection of embedded apps |
| The Republic | the nations/ShopKeepers/holdings data model |
| Temple Archives | the telemetry archive |
| ShopKeepers | a CRM-ish vendor list |
| Rift Gates | network-overlay debug page |
| Probe | observability iframe |