
Hello everyone, I’m Harkeerat Singh (Harkeerat24), and this is my final project report for Google Summer of Code 2026 with CircuitVerse for the project Structured Format for Saved Circuit Data.
TL;DR: Over this GSoC, I built and hardened a new Canonical v1 project format for CircuitVerse. It can deterministically export projects, rebuild them through a typed import pipeline, preserve real-world circuit topology and project state, automatically generate layouts with ELK.js when layout data is missing, and validate incoming files using JSON Schema + Ajv. The pipeline was also tested against all 12 Editor’s Picks projects.
The project started with a simple problem: two logically identical circuits could produce different saved data depending on the order in which they were built or stored.
That makes saved circuit data difficult to compare, hash, validate, migrate, or safely use as a stable format for future tools.
The goal was to introduce a structured canonical representation where the circuit’s logical structure has a deterministic identity, while still preserving the information needed to reconstruct the complete CircuitVerse project.
Canonical v1 now forms an end-to-end pipeline around:
The export pipeline turns a live CircuitVerse project into a stable Canonical v1 representation step by step:
canonicaliseProject() collects all circuit scopes and builds the SubCircuit dependency graph.khansAlgorithm() orders those scopes child-first. If a dependency cycle exists, export stops.canonicaliseScope(), which runs the scope-level canonical pipeline:indexNodes() maps live simulator nodes to stable indices.discoverNets() uses Union-Find to group electrically connected nodes into nets.buildComponentDrafts() reads component ports, properties and default state.buildStructuralComponentData() prepares the structure used for hashing and sorting.canonicalSort() uses 1-WL structural fingerprints to sort non-interface components while preserving the Input/Output interface order.assignComponentIds() assigns stable component IDs.attachComponentPorts() and buildCanonicalNets() attach component ports and assign stable net IDs.buildLayout() stores routing, intermediate nodes, port positions and annotations, while buildVisual() stores canvas state.buildCanonicalComponents() creates the final component representation.sha256() creates the structural hash for that scope. Child scope hashes are then used when hashing SubCircuit components, so their identity depends on the child circuit itself rather than a temporary scope ID.canonicaliseProject(), the ordered scopes are placed into the final circuits map, the scope hashes form the project hash, buildProjectMetadata() adds the project name, clock, focused circuit and tab order, and the final CanonicalProject is returned.The import side follows the reverse path: validate the Canonical project, rebuild its scopes and wiring, then canonicalise it again to make sure the reconstructed project still has the same identity.
importCanonical() receives the project. In the validation layer, validateCanonicalJson() first checks the JSON Schema and project cross-references before import state is modified.computeImportOrder() uses the same dependency idea to make sure child circuits are reconstructed before parents that use them as SubCircuits.newCircuit() creates the scope and importSingleScope() rebuilds it:buildComponents() creates the component instances and restores saved port positions.generateElkLayout() generates it with ELK.js.applyComponentLayout() places the components.buildAnnotations() restores annotations.wireComponents() reconnects simple nets.restoreDefaultState() restores saved component state.restoreIntermediateNodes() reconstructs routed wires and junctions.restoreScopeMetadata() restores the circuit metadata and canvas state.verifyRoundTrip() canonicalises each reconstructed scope again and compares its hash with the original scope hash.canonicaliseProject() runs once more and compares the complete project hash with the original Canonical v1 project.The pipeline is connected to the V1 simulator UI. Canonical projects can be:
.cv filesThe import flow also keeps a backup of the current project so a failed import does not destroy the user’s existing canvas.

The final pipeline is also tested against all 12 CircuitVerse Editor’s Picks projects using local fixtures.
For every fixture, the same complete path is tested:
legacy project → canonical export → canonical import → canonical re-export → comparison
The real-project fixtures verify that Canonical v1 preserves:
The saved representation preserves the complete project data while keeping the structural hash focused on canonical identity. Routing is represented through unified connections that can point to either a component port or an intermediate routing node.
Result: All 12 Editor’s Picks projects pass the complete round-trip tests. 🎉

A valid Canonical v1 project does not have to contain layout data. If layout exists, the importer preserves it exactly. If it is missing, the pipeline can generate a readable layout automatically using ELK.js.
The layout rule is:
When layout is missing, the Auto Layout adapter follows a small pipeline of its own:
Fan-out needs an additional adapter because ELK works best with simple source-to-target edges while a CircuitVerse net can contain one source connected to several targets.
The Auto Layout adapter therefore uses a temporary zero-size junction to preserve one shared branching point during layout. After ELK finishes, that temporary representation is converted back into CircuitVerse’s normal intermediate routing nodes and bend points.
Generated coordinates are deduplicated and snapped to the simulator grid, and the ELK-specific junction never becomes part of Canonical v1 itself.
The final Canonical v1 pipeline includes a strict runtime validation layer so imported project data is checked before it can affect the simulator. The JSON Schema defines the structure the format accepts, while Ajv and the cross-reference checks make sure that the project is both well-formed and internally consistent.
Together, these checks turn Canonical v1 into a format that the importer can trust instead of simply assuming that incoming JSON is valid.
The schema follows JSON Schema Draft 2020-12 and uses reusable $ref definitions with constraints for:
unknown data to CanonicalProject.Validation happens before importCanonical() modifies project state, and errors include useful JSON paths so invalid data is easier to locate.
layout remains optional because a schema-valid project without layout can continue into the ELK.js Auto Layout path.
One useful result of Canonical v1 is that a circuit can be described through its components and nets without depending on canvas coordinates. Components describe what exists in the circuit, while nets describe which ports are electrically connected.
The same representation gives LLMs a much cleaner target. Instead of asking a model to generate both circuit logic and exact canvas coordinates, it can generate the Canonical v1 components, nets and SubCircuit relationships and leave layout out. CircuitVerse can validate that JSON, rebuild the logical circuits in dependency order, and let ELK.js place and route them automatically.
For the demo, I used a small hierarchical 2-bit ripple-carry adder with three circuit scopes. The LLM generated a reusable HalfAdder, a FullAdder built from HalfAdder SubCircuit instances, and finally TwoBitAdder from two FullAdder SubCircuit instances.
LLM → hierarchical Canonical v1 .cv without layout → validation → child-first import → ELK.js Auto Layout → working CircuitVerse project
| PR | What it delivered | Status |
|---|---|---|
| #1093 | Initial deterministic canonical export pipeline | Superseded by #1095 |
| #1094 | Multi-circuit canonical project support | Superseded by #1095 |
| #1095 | Deterministic export, subcircuits, Kahn’s Algorithm and cycle detection | Merged |
| #1131 | Canonical import pipeline and round-trip verification | Merged |
| #1132 | Export/import UI integration and rollback flow | Merged |
| #1173 | Strict canonical types and pipeline refactor | Merged |
| #1221 | Complete real-world round trips and 12 Editor’s Picks tests | Merged |
| #1222 | ELK.js Auto Layout for layout-less canonical projects | Merged |
| #1236 | Canonical v1 JSON Schema | Merged |
| #1242 | Ajv + cross-reference runtime validation | Merged |
A few things from this project will definitely stay with me:
Now that Canonical v1 is in place, there are a few areas that can be improved or built on in the future.
I wrote a blog every week, so the complete GSoC journey is documented here:
| Period | Blog |
|---|---|
| Community Bonding | From a Student Who Used CircuitVerse to a GSoC Contributor |
| Week 1 | Canonicalization, Community & a Win |
| Week 2 | Scaling to Multi-Circuit Support |
| Week 3 | Subcircuits, Sorting & Demo Day |
| Week 4 | A Slow Week, Big Progress |
| Week 5 | Back on Track with Import |
| Week 6 | First PR Finally Merged |
| Week 7 | The Full Pipeline Comes Alive |
| Week 8 | Midterms Passed! |
| Week 9 | Rethinking Type Safety |
| Week 10 | Stricter Types, PR Merged |
| Week 11 | Real Circuits Broke My Assumptions |
| Week 12 | T-Shirt, Tests & Auto Layout |
| Week 13 | Auto Layout |
| Week 14 | Schema, AJV & the Final Stretch |
I’m grateful to my mentors Aboobacker MK, Josh Varga and Aryann for their reviews, questions, ideas and support throughout the project. Their feedback constantly pushed me to question assumptions, simplify the design and make the final pipeline stronger.
I’m also very thankful to the CircuitVerse community for the trust, feedback and support throughout the summer, and to Google Summer of Code for giving me the opportunity. I learnt a huge amount from building, testing and refining this project, and I’m genuinely proud of the final result we were able to put together. 🩵