Orchestrate building an OpenSkyhawk cockpit controller end-to-end — research → schematic → CAD → backlight → PCB → firmware → order → assembly → test. Use when starting a new panel or controller, advancing one to its next stage, resuming a partially-built one (detect where it stands and continue), or partitioning the cockpit's panels into controllers. A thin orchestrator: it sequences stages, enforces gates, keeps the GitHub Projects + repo tracking + InvenTree inventory/cost in sync, owns th...
Installs into .claude/skills of the current project.
Are you the author of Panel Pipeline?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/openskyhawk-panel-pipeline)
---
name: panel-pipeline
description: Orchestrate building an OpenSkyhawk cockpit controller end-to-end — research → schematic → CAD → backlight → PCB → firmware → order → assembly → test. Use when starting a new panel or controller, advancing one to its next stage, resuming a partially-built one (detect where it stands and continue), or partitioning the cockpit's panels into controllers. A thin orchestrator: it sequences stages, enforces gates, keeps the GitHub Projects + repo tracking + InvenTree inventory/cost in sync, owns the cross-cutting decisions (controller grouping, NODE_ID, prerequisites), and delegates the discipline work to panel-mapping, pcb-design, cad, firmware, and verify. Multi-session and resumable.
---
# Panel Pipeline — controller build orchestrator
Drives a physical panel/controller from *Not started → Done*. **Thin orchestrator**: it owns
sequencing, gates, cross-cutting decisions (controller grouping, NODE_ID, prerequisites), and
state tracking — it does **not** duplicate discipline depth. Each stage delegates to the owning
skill.
The work spans **multiple sessions** (PCB fab waits, assembly, sim testing). On every invocation:
**first detect where the panel/controller stands, then drive the next stage to its gate and stop**
where the owner is human.
## Detect stage & resume
Read the panel/controller's **GitHub Project status** (the tracker) and cross-check **repo signals**:
| Stage reached | Repo signal |
|---|---|
| Grouped | NODE_ID claimed in `Firmware/NODE_IDS.md` |
| Researched | `docs/_source/controllers/<Panel>.md` exists + Project item research complete |
| Schematic | KiCad project exists + ERC clean |
| CAD | `.FCStd`/`.f3d` source exists |
| Backlight | LED-array count set in schematic + re-ERC clean |
| PCB | routed + DRC clean + gerbers exported |
| Firmware | `Firmware/Panels/<Controller>/` compiles + tests pass |
| Ordering / Assembly / Testing | Project Status + order ref / boards in hand |
If the Project and the repo disagree, trust the repo signal and correct the Project. *(Conflict-resolution
is a thin detail — refine in use.)*
## Scope: controller, not panel
A **controller** = one STM32 PanelGroup MCU = one CAN **NODE_ID**, hosting a **host panel + I²C
breakouts within ~12–18"**. GitHub Project #1 keeps **one item per physical panel**; the build pipeline runs
**per controller** (Project #2). The grouping rule + budgets are summarized in A2 below.
## Readiness — pick what's buildable now
A panel/controller is **Ready** when every control type it uses already has an implemented
OpenSkyhawk building block (firmware class **and** KiCad symbol / Design Block); **Blocked** when one
or more are missing.
From the A1 inventory + the implemented-block list, classify the portfolio:
- **Ready** → can run B1–B9 end-to-end with today's toolkit. **Prefer these — build them now.**
- **Blocked** → list the missing blocks (each → a per-board prerequisite ticket, out of scope to
build here); defer the controller until its blocks land.
This is the main **scheduling lever**: maximize throughput by building everything the current toolkit
supports, while the missing control types are prototyped separately (in parallel). Invoke with
*"what can I build now?"* to get the **Ready** set and pick the next controller.
---
## Phase A — project pre-step (ONCE, before any per-panel mapping)
### A1 — Bulk inventory · *owner: AI*
Parse the **full A4EC mod Lua** (`clickabledata.lua` / `command_defs.lua` / `A-4E-C.lua` — **all**
controls including those not exposed in DCS-BIOS, with native model-viewer IDs and 3D positions)
**plus** the derived **DCS-BIOS JSON**. Per control, record `{name, native ID, type, routing,
positions}`:
- **type** — auto-estimated from the DCS-BIOS type field (selector / toggle_switch / momentary /
variable_step / analog → firmware class → GPIO/ADC count); from the Lua for HID controls.
- **routing** — control in the JSON → **DCS-BIOS** (record address). Absent from the JSON →
**HID-routed** (still consumes GPIO/ADC, just a different firmware path). HID controls **must be
identified** or the I/O budget — and the grouping — is wrong. (Routing is by `controlId` range:
`DCSIN_*` ≥ 0x8000 → DCS-BIOS via PanelBridge; `CTRL_*` < 0x8000 → HID via SimGateway.)
- **positions** — from the mod files (not manual Model Viewer) → feed A2 distances.
Output: per-panel I/O + actuator budget, recorded in the panel's GitHub Project item. Also run a **bulk
readiness check** — map each control's estimated type → required OpenSkyhawk block → does it exist?
→ mark each panel **Ready** (all blocks exist) or **Blocked** (list missing blocks). Provisional
(estimated types); confirmed at B1 once types are sim-verified. Feeds the Readiness scheduling above.
### A2 — Grouping · *owner: AI proposes → human confirms*
Partition all panels into controllers with the **3 hard gates**, then claim NODE_IDs:
1. **REACH** (hard) — every breakout ≤ **12–18"** I²C harness (mod-file positions × 1.10 → mm).
Beyond → its own CAN node.
2. **FIT** (hard, expandable) — ≤ 2 I²C buses; ≤ 8 MCP23017/bus (224 inputs); ≤ 4 ADS1115/bus
(32 analog ch).
3. **PERIPHERALS** — steppers / servos / Hall. Gauges route through an I²C expander at low step
rates; direct-MCU-GPIO stepping is ~4/MCU. (Full budgets: the controller-grouping design note.)
**Backend / interface class per sub-panel** (#197 decision — hardware-standards →
Shift-Register I/O): panels with **rotary encoders or fast gauges (≳150 °/s)** → SPI-class
(74HC ShiftBus J_SR leg, `SHIFTBUS_ISR_HZ=1000`; one end-node leg per host chain); everything
else → I²C-class (MCP/OLED/ADS — internals free, the connector is the standard). Weigh
**OLED + fast-gauge co-residency** on one node: a display flush stalls same-node stepper slews
regardless of backend (+35 % w/ jitter at a simulated 25 ms flush); slow rare slews (APN-153
DRIFT) accepted.
Bias to **split** — up to 63 CAN nodes are essentially free. Grouping is **provisional here**
(mod-file distances) and **confirmed at B3 CAD** against the real harness path (re-split if a
breakout exceeds 18"). Claim each NODE_ID in `Firmware/NODE_IDS.md`.
#### PanelGroup base — fixed resources (cache; full ref: `docs/_source/base-boards.md`)
Every controller starts from the **PanelGroup base** (STM32F103C8T6, LQFP48). The base fixes
these — don't re-derive, build the per-controller budget on top:
- **Reserved (not available):** CAN PA11/PA12 · diag USART1 PA9/PA10 · SWD PA13/PA14 ·
status LED PB14(red)/PB15(grn) · backlight PWM PA6(BL1)/PA7(BL2) · I2C1 PB6/PB7 ·
I2C2 PB10/PB11 · I2C1 INT PB12/PB13 · I2C2 INT PB8/PB9 · OSC PD0/PD1 · NC PC13/14/15 + PB2.
- **Breakout (free panel I/O) — 13 GPIO:** ADC+PWM `PA0 PA1 PA2 PA3 PB0 PB1` · ADC-only
`PA4 PA5` · PWM/digital (PA15/PB3/PB4 freed from JTAG by `STM32Board::begin()`) `PA8 PA15 PB3 PB4 PB5`. **8 ADC-capable.**
- **I²C:** 2 buses, each with INT_A/INT_B brought to its 8-pin JST-XH header → **one INT-driven
MCP23017 (inputs) per bus**; output-only expanders need no INT. Addressing MCP23017 0x20–0x27,
ADS1115 0x48–0x4B per bus. Prefer a **direct STM32 ADC breakout pin** over an ADS1115 when only
1–2 analog channels are needed.
- **Backlight:** 2 PWM-dimmed low-side zones on-base (sized at B4).
---
## A2b — Graduate to GitHub issues · *owner: AI*
Tracking lives in two **private** GitHub Projects (org `OpenSkyHawk`): **git is the data source
of truth, the Projects are the generated tracker.** Get the field/option node IDs live with
`gh project field-list <n> --owner OpenSkyHawk --format json`.
- **#1 Panel Research & Assignment** — every panel as a **draft** through A1/B1 (fields: Console,
#Controls, Breakdown, Panel Type, Priority, + independent flag columns `Controls?` /
`Screenshot?` / `Analysis?`). `Analysis?=Done` only after a ModelViewer screenshot is reviewed
**and** the input list verified — it's downstream of `Screenshot?`, never auto-set from migrated data.
- **#2 Controller Build** — each controller as an **issue** through B1–B9.
**Graduate a controller** once A2 groups its panels (draft→issue conversion is **one-way**, and
issues are **public** since the repo is public — the Projects stay private via their own ACL):
1. Create the **controller** issue — label `controller`, add to #2, set build `Status`. Body =
member panels + NODE_ID + I/O architecture. Each controller step (B6 Firmware · host/MCU PCB ·
B7 Order · B8 Assembly · B9 Integration test) is its own **sub-issue** (label `build-step`)
under the controller, created in build order.
2. For each member panel: **convert** its #1 draft → issue (`convertProjectV2DraftIssueItemToIssue`),
label `panel`, keep it in #1 *and* add to #2, then **link as a sub-issue** of the controller (`addSubIssue`).
3. Panel issue body = its control inventory (B1 / `panel-mapping` output). Each panel step
(B2 Schematic · B3 CAD · B4 Backlight · B5 PCB) is its own **sub-issue** (label `build-step`)
under the panel, created in build order.
4. **Close** a step sub-issue when its PR merges (link with `Closes #<step>`); advance the
controller `Status` as steps complete. All panel sub-issues closed = hardware done → finish
group firmware + test.
**Sub-tasks are defined by issue type:** `controller` → firmware/test/order/assembly · `panel` →
schematic/PCB/CAD/backlight. **Default is 3-level** — every build step is its own **sub-issue**
(label `build-step`; controller steps under the controller, panel steps under the panel; GitHub
nests ≤8 deep), giving assignable, PR-closable steps + fine progress roll-up on the parent.
**2-level** (steps as body checkboxes, no step issues) is the lighter fallback for a quick or
low-priority controller. **Step sub-issues are NOT added to the Projects board** — they roll up
under their parent; only controllers (#2) + panels (#1 + #2) sit on the board. The **checkpoint**
is the `Status` field; reserve **Milestones** for coarse gates (a console, a release). Issue
templates have no per-template ACL, so this skill (not a template) owns the structure.
---
## Phase B — per-controller build (repeats per controller)
### B1 — Research (deep) · `panel-mapping` · *AI drafts → human photos + in-sim type confirm*
A1 gave the type *estimate*; B1 makes it build-ready — **AI drafts from the A4EC Lua
(`clickabledata.lua` gestures) + DCS-BIOS + the A1 analysis; human confirms each control in the
real sim.** Deliverables:
- **Interaction model — per control, sim-confirmed.** *How the pilot operates it*, beyond the type:
physical action (toggle up/down · push momentary/latching · rotate detented-N-pos / continuous /
multi-turn · pull-to-unlock · **concentric** inner/outer · **guarded** · **spring-return**), exact
**positions + labels**, detent feel, special behaviours (push-to-set, spring-loaded slew,
lift-lock). Drives part selection + confirms the FW class. Record as an **`Interaction`** column in
the Inputs table (the *how*, beside FW class = the *what*).
- Confirm control **types in the real sim**; capture reference photos (human). **Dimensions** (×1.10)
+ switch **centre-to-centre spacing** → CAD. **Part selection** per control.
- **Engineering pass (controller-wide):**
- **Pin budget** — tally against the base (13 breakout GPIO / 8 ADC-capable · 2 I²C buses, ≤8
MCP23017 & ≤4 ADS1115 per bus). Confirm it fits with headroom.
- **Gauge type + drive** — classify every gauge: **DrumDisplay = OLED** (I²C, cheap buffer write)
vs **NeedleGauge = stepper / DRV8833** (needs fast coil drive). **MCP23017-driven steppers are
I²C-rate-limited** (~490 steps/s @400 kHz, *shared per bus*) — default to MCP for low/slow gauge
counts, but **bench-validate the real step rate + smoothness before B5** (hard gate; don't lay
out copper on an unproven rate). Fast / dense clusters → direct MCU GPIO or the **74HC595
shift-register backend (#133)**. **DrumDisplay = OLED supersedes any motorized-drum assumption —
recount expanders / steppers on that basis** (it usually collapses them).
- **Inter-panel connections** — per host↔sub-panel link: I²C harness (8-pin JST-XH:
SDA/SCL/INT_A/INT_B/+3V3/GND/spare-analog), analog passthrough → host ADC, what stays **local**
(stepper coils + local MCP — don't cross the harness), backlight +12 V, power.
- **Pin map per MCU (NODE)** — the authoritative table B2 consumes: every STM32 pin → function,
I²C addressing (MCP23017 0x20–0x27/bus, OLED mux channels), INT routing.
- **Gap analysis** — for each control type, check the OpenSkyhawk building block exists: **both** the
**firmware class** (e.g. `OpenSkyhawk::Switch3Pos`) **and** the **KiCad symbol / Design Block**.
Each missing piece → a **per-board prerequisite ticket** (blocker on this controller): missing
**class** blocks **B6**, missing **symbol** blocks **B2**. **Building a new control type is OUT OF
SCOPE here** — its own firmware / KiCad effort (TechSpec + prototype, e.g. how `Switch2Pos` was
built); the pipeline flags, tickets, and blocks until it lands. Implemented today: `Switch2Pos ·
Switch3Pos · SwitchMultiPos · AnalogMultiPos · AnalogInput · AngleSensorInput · RotaryEncoder ·
RotaryAcceleratedEncoder · ActionButton · LED · DrumDisplay (OLED) · NeedleGauge · Dimmer ·
IntegerOutput · SwitchWithCover2Pos · PinRef · ShiftBus`. Planned: none for v0.1.0;
after v1.0 `ServoMotor (#132)`. **Confirms the A1 provisional
Ready/Blocked** now that types are sim-verified.
- **Body cleanup** — refine the A1 first-pass into build-ready issue bodies; **preserve Notes +
Screenshot verbatim** (humans own those).
- Out: Controls Inventory + **Interaction** + I/O Summary + Dimensions + **Pin map** + prerequisites
→ Project item / issue body + `docs/_source/controllers/<Panel>.md`.
### B2 — Schematic · `pcb-design` · *AI analysis → human draws → AI reviews*
- **AI:** scaffold (`/new-kicad-project`) + analyze needs beyond the base PanelGroup (steppers /
DRV8833, gauges, servos / PCA9685, Hall, extra MCP23017 / ADS1115, backlight). Component list +
flags.
- **Human draws** the schematic; **AI reviews** (ERC + against hardware-standards + the I/O map).
- **All backlight CONTROL circuitry is defined here** — MOSFET zones, PWM signal nets, LED-string /
harness connectors. The **only** thing deferred to B4 is the **number of LED arrays**.
- Gate: **ERC clean**.
### B3 — CAD · `cad` · *human-led, AI assists*
- Model the **panel sandwich**: 1/16" face plate (legends) + 1/8" diffuser + 1/8" backplate (LED
passthrough holes) + the PCB behind it (LEDs on the PCB front face shine forward through the
backplate holes → diffuser → face).
- Fit-check vs the PCB (connector clearance, stepper shaft-through-PCB, LED positions). Out:
**lit-area map → B4**.
- **Confirms the A2 grouping** against real harness distance (re-split if a breakout > 18").
- Gate: model + fit-check.
### B4 — Backlight · *AI calc → human confirms zones*
- Size the **number of LED arrays/strings** from the CAD lit-areas (5 × 5050 in series @ 12 V,
120 Ω/string, IRLML2502/zone — applying the standard, not re-deriving the EE). All control
circuitry was already defined in B2.
- **Update the B2 schematic** with the array count → **re-ERC**.
### B5 — PCB Layout · `pcb-design` · *human lays out → AI reviews*
- In: the post-B4 schematic + B3 CAD (board outline, LED / connector positions, mounts) +
`pcb-design-rules`.
- Place (LEDs on the front face, everything else on the back) + route (JLCPCB 2-layer, DRC) →
**gerbers + BOM** (JLC part numbers).
- **BOM → InvenTree** — create/confirm each part (+ supplier part) and build the panel **Assembly**'s
BOM (see *Inventory & cost tracking*).
- **B3 ↔ B5 iterate** — the board outline fits the panel; the PCB-front LED positions must align
with the backplate passthrough holes.
- Gate: DRC clean + gerbers.
### B6 — Firmware · `firmware` · *AI drafts → human review* · **parallelizable\***
- **Gated by B1, not B5** — needs the I/O map, *not* the physical board. Runs in parallel with
B2–B5; only needs the board at B9.
- **\*Conditional:** parallelizable **only if no building blocks are missing**. If B1's gap analysis
ticketed a missing firmware class, B6 is **blocked** on that prerequisite (building it is out of
scope — a separate ticket) until it lands.
- In: B1 inventory (control → type → pin, DCS-BIOS / HID routing) + NODE_ID + cleared prerequisites.
- PanelGroup sketch (`registerExpander` / `registerADC`, an input/output object per control) +
A4EC input-map entries + tests.
- Gate: compiles + tests pass.
### B7 Order / B8 Assembly / B9 Test · *human*
- **B7** — gerbers + BOM → JLCPCB order, source parts. Human (financial). **Async** (fab/ship wait).
Mirror each order (JLC / LCSC) as an InvenTree **Purchase Order** — part lines + shipping/tax as
extra lines; **receive** on arrival → stock at landed cost (see *Inventory & cost tracking*).
- **B8** — solder (T962 reflow) + install + harness.
- **B9** — flash + verify each control in DCS (inputs register, gauges/backlight move, CAN reaches
CONNECTED). AI supplies the checklist (`verify`). → **Done**.
---
## Inventory & cost tracking (InvenTree)
Components, BOMs, and orders are tracked in **InvenTree** (self-hosted, `http://192.168.85.85`, LXC
on pve2) — the **system of record for inventory + cost**. Goal: per-panel **cost-to-source** and,
with labor + machine time, a **sell price** (panels sold **assembled**). This skill wires panel
parts / BOMs / orders into InvenTree; it does **not** duplicate it. Live state, IDs, and API gotchas
live in memory `project_inventree`.
**Boundary — InvenTree is downstream of design.** Part selection (B1–B5) is **engineering-driven**;
stock / availability **never** drives what part you pick. InvenTree consumes the *finalized* BOM —
the dependency flows one way (design → BOM → InvenTree, never back). The only feedback loop is
*"can I build N now?"* (Build Orders vs stock — a production question, not a design one). The
committed KiCad project stays **self-contained on `PCB/Libraries/`** (LCSC / MPN as symbol fields);
InvenTree is a **decoupled downstream consumer**, never referenced in the project's lib tables (the
repo is public — no live link to the private instance).
- **3-layer part identity:** Part → **ManufacturerPart** (real maker + MPN) → **SupplierPart**
(LCSC / JLCPCB + SKU + price). LCSC/JLC are *distributors*, not makers.
- **Categories:** `Electronics{Passives,Semiconductors,Connectors,Modules,Crystals}` · `Controls`
(HMI: switches / encoders / pots / buttons / indicators) · `Fabricated{3D-Printed,Laser-Cut,UV}` ·
`RawMaterial` (units: filament g / resin mL / sheet cm² / ink mL) · `Fasteners` · `Bare PCB` ·
`Assemblies`.
- **A panel = an InvenTree Assembly** whose BOM = the PanelGroup base assembly + panel-specific
HMI / fabricated / fastener parts + its bare PCB. Multi-level → per-panel cost rolls up.
- **Touchpoints:**
- **B5 (post-BOM)** — push the *finalized* KiCad BOM → InvenTree (CSV import, or `inventree-part-import`
for LCSC parts — see **LCSC import** below) → creates the parts (HMI → `Controls`, etc.) + the panel
**Assembly**'s BOM. **Decoupled push, not a live KiCad link.** Per-panel part list also derivable from
the control inventory (`docs/_source/a4ec-control-inventory.csv`) via FW-class → physical-part map.
- **B7** — each LCSC / JLC order → a **Purchase Order** (part lines + shipping/tax as *extra
lines*); **receive** on arrival → stock at landed cost.
- **Cost → price** — BOM rollup = material; add **`Assembly Labor (hr)` + `Machine time` virtual
BOM parts** (priced per the build's labor/machine rates) → fully-loaded unit cost → set the
assembly's **Sale Price** → margin. A **Build Order** is the production run (consumes component
stock, outputs the assembly at build cost).
- **Access:** API token at `~/.config/inventree/token` (read it, never commit/echo). Small LXC →
**retry + backoff on HTTP 500** (DB-lock) for bulk writes.
- **LCSC import — `inventree-part-import`** (invoke `~/ipi-venv/bin/python -m inventree_part_import`; v1.10.0
with LCSC API v3 — no console script, so use `-m`). Pulls a part's full data (description, parameters,
**image**, price breaks) straight from an LCSC part number. Use at **B5** (seed new BOM parts) / **B7**
(order-time price refresh):
- **First-time setup = the tool's OWN `--configure lcsc` wizard.** Never hand-write its
`inventree.yaml` / `suppliers.yaml` internals.
- **Import NEW parts by C-number:** `~/ipi-venv/bin/python -m inventree_part_import -i false -o lcsc C1234 C5678 …`
(`-i false` = non-interactive/safe; `-d` = dry-run, *not* allowed with `--update`). Run once with `-i true`
when a new LCSC category needs mapping to your tree — the answer is saved to `categories.yaml`. Declare new
subcategories there with `_aliases` (LCSC category strings) → the tool auto-creates them on the next run.
- ⚠️ **NEW parts ONLY.** It matches an existing InvenTree part by **`name == MPN`** — OpenSkyhawk parts use
*descriptive* names (e.g. `R 1k 0.1% 0805`), so a re-import creates an **MPN-named duplicate** instead of
updating (`--update CAT` searches by name too → same trap). To refresh an existing descriptively-named part
(image/description), attach the data **directly via the API**, not through import.
- Config dir `~/Library/Application Support/inventree_part_import/`. Full workflow + the duplicate
merge-repair pattern (reassign SupplierPart/ManufacturerPart → original, deactivate-then-delete the dup)
live in memory `project_inventree`.
---
## Cross-stage couplings (non-linear)
- **B4 → B2** — set the LED-array count → re-ERC.
- **B3 ↔ B5** — mechanical/electrical co-design (board outline + LED-hole alignment).
- **B1 prerequisites → B2 + B6** — a missing class/symbol gets a per-board blocker ticket;
implementing it is out of scope (its own firmware/KiCad effort). Missing class → blocks B6;
missing symbol → blocks B2.
- **B6 ∥ B2–B5** — firmware is parallelizable once B1 is done, **unless** a prerequisite ticket
blocks it.
## Conventions
- **Tracking** — one GitHub Project item per physical panel (Project #1; controller issue in #2),
notes in the item/issue **body**. Advance `Status` at each gate. Open GitHub issues for prerequisites and deferred items.
- **Panel item / issue body structure** (in this order, all sections present):
1. **Header line** — auto-generated control count summary (`**N controls** — X inputs · Y gauges · Z LEDs`)
2. **Notes** — human-authored observations: physical layout, construction decisions, open questions. Never overwrite.
3. **Screenshot** — embedded image tag(s). Manual scans or ModelViewer shots. Never overwrite. Screenshots are attached directly to GitHub issues/drafts — do NOT commit images to the repo.
4. **Prerequisites** — AI-generated checklist of missing `OpenSkyhawk::` firmware classes / KiCad symbols, each linked to its GitHub issue. One line per blocker.
5. **Research checklist** — three standard items (A-phase only): `- [x] Control list identified (Lua + DCS-BIOS)` / `- [ ] Reference screenshot attached to ticket` / `- [ ] Assigned to a controller`. Coords + dimensions are B3 (CAD), not tracked here.
6. **Analysis notes** — AI-generated prose: confirmed control types, physical layout findings, design decisions, open questions.
7. **Inputs** — table: `Control | Identifier | DCS-BIOS addr | FW class | Notes`
8. **Outputs** — table: `Display group | Identifiers | Digits | Flag | FW class`
9. **Component Summary** — AI-generated I²C device + discrete-driver + STM32-direct-pin tables for the panel's own I/O (parts + LCSC + addresses/pins). Per-panel scope; the controller-wide budget + remaining-pin availability lives on the controller issue, not here. Each part is tracked in **InvenTree** (Part + SupplierPart + price); the panel itself is an InvenTree **Assembly** with a BOM — see *Inventory & cost tracking*.
When updating a ticket: **always fetch the current body first**, extract Notes + Screenshot verbatim, then rebuild as fetched-Notes + fetched-Screenshot + updated AI sections. **Never reconstruct from memory or session context** — the user edits Notes and Screenshot directly in the GitHub UI, and overwriting them destroys their work irreversibly.
- **NODE_ID** — claimed per controller in `Firmware/NODE_IDS.md` at A2.
- **Repo mirror** — condensed per-panel record in `docs/_source/controllers/<Panel>.md`.
- **Never** auto-merge PRs or place orders — the human owns B7–B9 and all merges.
## Delegates to
`panel-mapping` (B1) · `pcb-design` (B2, B5) · `cad` (B3) · `firmware` (B6) · `verify` (B9).