Circuit design as text¶
A source
This page is a source of edakit – a concept, a prompt or the rules of a session –, shown as it is in the repository. Read it as such; the guides say what edakit does.
Status 2026-10-28: being built; all decisions are taken (table at the end), and the state of each step is in
implementation-plan.md. The findings come from reading edakit at a8b6011 (with PRs
78 and #79) and the repositories of the reference project and the Christmas bell.¶
Goal¶
Everything needed to create, edit and change a circuit is text in the repository, and every step runs through an LLM. edakit is close. Two things exist only in EasyEDA today: the positions the auto-layout sets, and own footprints and symbols. Three smaller ones come on top: the rule set of EasyEDA's DRC, three order settings (thickness, mask and print colour) and the position of single designators.
Every piece of a project has to pass two questions:
- Text: Is it in a file in the repository that an LLM can read and write – not only in EasyEDA, in a picture or in someone's head?
- Executable: Can Claude do the step with a command, a script or an MCP call, without a hand on the mouse?
Generated content need not be text: the schematic and the PCB in EasyEDA, the Gerber files and the pictures are outputs. Everything that determines them has to be text.
Where it stands¶
A project holds ten kinds of sources as text today, each with one writer and one check. Not in the repository, because generated: the EasyEDA project, Gerber, BOM, CPL and the pictures for the reviews.
| Source | File | Written by | Checked by |
|---|---|---|---|
| Circuit: parts, pins, nets with voltage ranges, purpose, hand-over to the layout | hardware/graph.json |
Claude as circuit designer | rules P and G, simulation E, circuit hash |
| Parts data from datasheets, with sources | hardware/parts/*.json |
agent part-selector, skill own-part | rules P, circuit hash |
| Operating cases of the simulation | hardware/sim.json |
Claude | stage E, circuit hash |
| Arrangement of the schematic | hardware/schematic.toml |
Claude | stage S |
| Board: outline, holes, plan, routing rules, pours, silkscreen, order form | hardware/boards/<name>.toml |
Claude as PCB designer, edakit pcb place --write |
stage L, F-6, layout hash |
| Silkscreen artwork | SVG, often from a generator | Claude | layout hash (sign-offs of scope layout), and the fab hash through the gate run's silkscreen (M-5) |
| Project steps edakit cannot do | project scripts | Claude | nothing yet (concept project-scripts.md) |
| Project, boards, gate | edakit.toml |
Claude | checked on loading |
| Decisions | docs/decisions.md |
Claude writes, the owner decides | – |
| Sign-offs | hardware/signoff.json |
review agents | count only for the hash they checked |
edakit also writes two versioned files that are derived, not sources: hardware/stock.json (the stock snapshot, P-4)
and hardware/firmware.json (the firmware contract, #70).
The knowledge lives in edakit: rule catalog, building blocks, handbook, skills and agents. New knowledge comes in
through propose-rule and propose-feature. Library parts are dependencies by their LCSC number. edakit part import
(#32) reads their symbol pins and footprint without EasyEDA into a cache; P-1 and the plan use it. The layout hash
takes only the geometry pcb dump measures in EasyEDA.
Gap 1: positions from the auto-layout¶
What EasyEDA's auto-layout places is in no file. The fab hash sees it at the next gate run, through the CPL rows and the Gerber layers; the layout hash does not. Proposal: edakit writes these positions into the plan of the board file after the auto-layout.
Today:
edakit pcb autolayoutlets EasyEDA place the unlocked parts and waits until their positions stand still (pcb.py,autolayout). It writes nothing. In the reference project the auto-layout places every part except the fixed ones.edakit pcb place --write(#57) already puts every part with a host –layout.near,[near], decoupling – into[place], markedauto = true; a later run may move those rows. The auto-layout places the rest.pcb dumpturns every part to 0° and TOP to measure it and leaves it so. Afterwardspcb applyand the auto-layout have to run again, and the auto-layout may place the parts differently.- The layout hash covers the board files,
[release] layout_filesand the footprint geometry (gate.py,layout_files). The geometry measures each part relative to its own origin (measure), without its position. A new auto-layout changes the board. Since #65 the fab hash sees that, so M-5 with its scopefablapses at the next gate run. Only an M-5 listed with scopelayoutwould still count; the Christmas bell binds its M-5 to the fab hash of the run its reviewer checked (D-20). - L-7 holds only the parts in
[place]to the plan. For parts from the auto-layout there is nothing to hold them to. The same goes for the direction of polarised parts (uniform, #60), the mated plugs (mate, #62) and the mechanical model: they see only[place]. - The handbook already knows the way out for a single part: a part in the way is taken into
[place](12).
Proposal:
- After the positions stand still,
edakit pcb autolayoutwrites position, rotation and side of every part it placed as an entry in[place]of the board file, next to the entries by hand (decision 3). The entry carriesauto = true, as the rows ofpcb placedo: a reader sees that nobody designed it. - Everything that holds for fixed parts then holds for these:
pcb applyputs them back after a dump, L-7 holds them to the plan, the plan check sees overlaps andnear, the direction and plug checks and the mechanical model see them, and the layout hash covers them. - From then on the auto-layout only runs for parts without an entry. To have a part placed anew, delete its entry; to move it, change it.
- A new check in stage L: every part of the graph has an entry in the plan, as an error, not a hint. That makes
"everything as text" checkable for the placement. A board placed before, with its routing, gets its positions once
from a command that reads them in EasyEDA and writes them into
[place]as fixed entries; a new auto-layout would move the parts off their tracks. The reference project needs that when it moves to edakit. The bell has all 19 parts in[place], on all four boards.
Effort: small. The loop already reads the positions while it waits, the conversion from EasyEDA to plan coordinates
exists (Board.from_eda), and write_place_rows already writes rows into [place].
Gap 2: own footprints and symbols¶
Symbol and footprint of an own part exist only in EasyEDA; the repository knows them only as a UUID. Proposal: pads, outline and print go into the parts entry as data, and edakit builds the project's copy from them.
Today:
- A part without an LCSC number carries an
easyeda_devicewithuuidandlibraryUuidin the graph, a pointer into an EasyEDA library. The skill own-part asks for a footprint from the user's library – drawn in the GUI or by a script. - The projects help themselves with scripts that drive the API directly:
- The Christmas bell rebuilds the project's copy of a 2×12 female header into the T-Display: 24 pins, 24 pads, outline and labels, 180 lines of Python. A device created through the API cannot be placed in the local client; the copy of a system part can.
- The reference project rebuilds the RP2040-Zero into the RP2350-Zero. A second script makes the polarity print of all LED footprints the same and changes holes and pad positions.
- The parts entry names the footprint and knows its leads (
lead), the body height (body.h_mm) and the box of a mated plug (mate) – but no pads, no outline and no print. The symbol it knows only as the pins' split onto its sides, forschematic draw. pcb dumpmeasures the geometry in EasyEDA. For an own part it measures what a script or a hand put there before.edakit part import(#32) reads the footprint of an LCSC part without EasyEDA – pads with number, position, shape, size, rotation and hole – into a cache. An own part has no such source.
Proposal:
- The parts entry gets a footprint as data: origin, pads in the form
part importcaches them (number, position, shape, size, rotation, hole), outline and print (lines, texts, polarity mark), in mm. The symbol follows frompinsandsymbol, as the drawing does today. - An edakit command builds the project's copy from it: a system part is placed as the carrier, then symbol and footprint of the copy are rebuilt. Both scripts already go this way.
- A check holds the copy to the data: pins and pads in EasyEDA equal the parts entry. A pad without a pin in the entry is a finding, and so is the reverse.
- To change a library footprint, edakit writes its measured geometry into the entry as data. From then on it is an own footprint, and the change is a line of text.
- The footprint data counts in the layout hash. A changed footprint voids the sign-offs of scope
layoutas a changed geometry does today, and the fab hash sees it at the next gate run.
Effort: medium. Two projects have tried the API path, and the pad format exists in the import cache. New are the command, the check and the handbook.
Fixed tracks: no gap¶
The board file can already prescribe tracks: [[routing.fixed_wires]] with net, layer, width and points in plan mm
(7). edakit writes them into the DSN as protected wiring; Freerouting routes around them
and keeps them.
What remains: a fixed track lies on one layer, and the format has no fixed layer change. edakit writes fixed vias only for the fanout of a pour. The reference project still lays its track skeleton (D-48) with a script of its own.
Smaller findings¶
Three smaller things are also outside text. None of them stops a board, but each has to be known or set up outside the repository today.
| Finding | Today | Proposal | Effort |
|---|---|---|---|
| Rule set of EasyEDA's DRC | L-3 asks for a DRC without findings, with the rule set the project holds. edakit does not set it; the handbook advises setting track rules in the GUI (9). | edakit writes the rules from the board file through the API (overwriteNetRules) – or reads them and checks them against the board file. |
small to medium |
| Order settings | Since #40 finish, mark, remark and tab zones are in [order] of the board file; every gate run writes order.md, and F-6 checks it. The thickness (thickness_mm) feeds only the mechanical model; mask and print colour are in no file. |
[order] gets mask and print colour and takes the thickness from thickness_mm. |
small |
| Position of single designators | silk apply places designators where there is room, steered by groups, priority and gap. A single one cannot be fixed. |
A position per designator in [silk], as [place] does for parts. |
small |
Scope¶
Decisions, the order (M-2) and the acceptance after delivery (M-4) stay with a person on purpose. That does not contradict the goal: Claude prepares, the owner decides, and the decisions are text in the repository too.
Not part of the goal:
- Headless, CI: EasyEDA Pro as a desktop program with MCP is enough.
- The two clicks in EasyEDA: creating the project by hand and one Import Changes. They go once the API allows it (upstream issue 4); until then they do not stand in the way.
The concept project-scripts.md serves the same goal: which script runs when is then checked text
in edakit.toml instead of a sentence in a project's README. Gap 2 would replace two of today's scripts, the
T-Display's and the LED footprints'. The concept requirements.md covers what the circuit should
do, which today is spread over READMEs, decisions and chats.
README¶
The README advertises circuit design as code, the name the user chose (decision 1); "hardware as text" was the alternative.
Draft for the head of the README:
**Circuit design as code.** Everything a board is made of is text in your repository: the circuit, the parts with
their datasheet sources, placement, routing rules, copper pours and silkscreen per board, every decision and every
review sign-off. Claude Code does every step from there – checks, simulation, schematic, PCB, routing, release –
through edakit and EasyEDA Pro. A change is a diff, and the release gate checks it on the files that go to the
manufacturer.
The paragraph is fully true only once the gaps and findings above are closed. It goes in now anyway, with the open points under "Status and limits"; each closed point removes a line there (decision 2).
Further: a language of its own¶
A small language for the circuit makes the graph readable without touching the chain of checks. edakit translates it into today's JSON, and every check keeps running on that.
What is cryptic about the JSON, on the Christmas bell's graph:
- Pins are numbers of the EasyEDA symbol:
U1.9instead ofPD6. - The names mislead: pin 1 of LED5 is called "C" in the symbol but is the anode. Only the role in the parts entry says so.
- A path through the circuit falls apart: step 1 (D6 → 330 Ω → green LED → GND) is spread over three nets, two components and a pin lock.
- JSON has no comments. Reasons live in
notesfields.
Today, step 1 shortened:
{"designator": "R4", "type": "resistor", "part": "330 Ω 0805", "lcsc": "C17630", "value": "330Ω", "pins": {"1": "1", "2": "2"}},
{"designator": "LED5", "type": "led", "part": "XL-504UGD", "lcsc": "C2895549", "bom": false, "pins": {"2": "A", "1": "C"}},
{"name": "LED_G1", "pins": ["U1.9", "R4.1"], "v": [0, 5.5]},
{"name": "LED_G1_A", "pins": ["R4.2", "LED5.1"], "v": [0, 5.5]},
{"name": "GND", "pins": ["U1.4", "U1.29", "LED5.2", "…"], "v": [0, 0]},
"pin_locks": {"U1.9": "LED_G1", "…": "…"}
A sketch with the same meaning:
part R4 resistor 330Ω 0805 lcsc C17630
part LED5 led XL-504UGD lcsc C2895549 no-bom # 5 mm, green, countdown step 1
# step 1: D6 → 330 Ω → green LED → GND
net LED_G1 0..5.5 V U1.PD6 R4.1 locked
net LED_G1_A 0..5.5 V R4.2 LED5.anode
net GND 0 V U1.GND LED5.cathode …
What it costs: a parser, a formatter and a translator into the JSON, error messages with line numbers, and a
handbook chapter Claude reads in every session. Tools that write the graph today (edakit block add, skill own-part)
then write the language. Touchstone: both projects translate there and back without a difference.
A smaller step with most of the gain: address pins in today's graph by name or role (U1.PD6, LED5.anode) and
use TOML instead of JSON for the comments. That needs no parser.
Models: atopile with a language of its own, SKiDL in Python. What counts for edakit is that the language maps the graph exactly – voltage ranges, waivers with their reason, the hand-over to the layout, the purpose – so every check stays.
Decided: first write the whole Christmas bell down both ways, as a language and as TOML with names, then choose which to build (decision 8).
The sketch is circuit-language.md: the bell both ways, a translator that takes all four
configurations there and back without a difference, what each form costs, and the decisions to take. Reviewed on
2026-10-31: the gains are independent of a language of its own and are built in any case – pins by name or role, the
pin map from the parts entry, the hash rule, edakit graph json –; the language is deferred until it offers what a
format cannot, which its section "Review 2026-10-31" spells out. Decided the same day: TOML with names as the form,
hardware/graph.toml, with the resolved graph.json generated beside it (its decisions 1–7); what both forms share
has a plan of its own, circuit-language-plan.md; the language itself is thought further in
circuit-dsl.md: what it would have to be, on which basis, when, and with what toolchain.
Order¶
First the small things, then gap 2, the language last. Each step is useful on its own.
- README with the open points under "Status and limits" – small, now.
- Gap 1: write the positions back and check "every part in the plan" – small; trying it needs EasyEDA.
- The remaining order settings and single designators – small each, without EasyEDA.
- Rule set of EasyEDA's DRC – small to medium; first find out what the API carries.
- Gap 2: footprints as data – medium.
- Language: first the sketch of the whole bell, then the decision, then building it – medium to large.
A fixed layer change in fixed_wires comes when a project needs one.
Decisions¶
All taken on 2026-10-28, 7 by #40. "Accepted" is the user's yes to the recommendation; "decided by Claude" marks a technical point the user left to Claude and then confirmed.
| # | Question | Decision | Status |
|---|---|---|---|
| 1 | Name in the README | "circuit design as code" | accepted |
| 2 | README now with its limits, or only after the gaps | now, with its limits | accepted |
| 3 | Gap 1: where the positions go | into [place] of the board file, marked auto = true like the rows of pcb place |
the user's choice; a file of their own beside the board file (<b>.auto.toml) was weighed and not taken |
| 4 | Gap 1: check "every part in the plan" | an error in the gate, not only a hint; a board placed before takes over its positions once | accepted |
| 5 | Gap 2: where the footprint data goes | into the parts entry; a change then voids the circuit reviews too – right for M-1, which checks polarity | decided by Claude |
| 6 | Rule set of EasyEDA's DRC | write it if the API carries it; otherwise read and check it | decided by Claude: reading works, net rules can be written (overwriteNetRules); step 4 finds out about the whole set |
| 7 | Where the order settings go | [order] in the board file (#40); mask and print colour join it |
settled by #40 |
| 8 | A language, or TOML with names | first sketch both on the Christmas bell; after the review of 2026-10-31 the language is deferred and the form decided: TOML with names, the resolved JSON beside it (circuit-language.md, decisions 1–7) | accepted |
| 9 | The order settings in the fab hash and the estimate | thickness, colours, finish and mark count in the fab hash, the remark does not; edakit cost names an order it does not price |
the user's choice on 2026-10-28, built as a follow-up of step 1.7 |
Sources¶
- edakit at
a8b6011: pcb.py (autolayout,measure,dump,auto_place,write_place_rows), part.py (part import,footprint_geometry), gate.py (layout_files), fab.py (L-3, L-7,fab_layers), order_sheet.py (F-6), route.py (fixed_wires,write_dsn), config.py (Placement, board file keys), schematic.py (easyeda_device), silk.py, skills own-part and pcb-round, handbook 4 (lead,body,mate), 7, 8 (8.2 hashes), 9, 10, 12. - The Christmas bell:
hardware/graph.json,hardware/parts/C2895549.json,variants/esp32/hardware/lib/tdisplay_device.py. - The reference project:
hardware/README.md,hardware/easyeda/led_footprints.py,hardware/easyeda/bahnen_hinten.py,docs/bestellung/.