Skip to content

Sccarda/c erearchitecting#3416

Draft
ScottCarda-MS wants to merge 88 commits into
mainfrom
sccarda/CErearchitecting
Draft

Sccarda/c erearchitecting#3416
ScottCarda-MS wants to merge 88 commits into
mainfrom
sccarda/CErearchitecting

Conversation

@ScottCarda-MS

@ScottCarda-MS ScottCarda-MS commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Circuit Editor: internal re-architecture (Data → Actions → Renderer → Editor)

Reviewer TL;DR. This is a large, internal re-architecture of the
circuit-vis circuit editor. It replaces four monolithic files with a
layered data / actions / renderer / editor structure, then builds the
group-aware, multi-target drag-and-drop editing on that foundation. No
public API changes and no user-facing behavior regressions
— the editor
does more (multi-target/group editing, measurement-dependency safety), but
nothing it did before is removed. It is big because the core is a single
atomic refactor that cannot compile in smaller pieces; see the
Why this is one PR and How to review this efficiently sections below.


At a glance

Scope source/npm/qsharp/ux/circuit-vis/** + source/npm/qsharp/test/** only
Size 114 files, ~+26,500 / −3,956
Language TypeScript (frontend) + .mjs tests. No Rust, no VS Code host changes.
Public API Unchanged — index.ts exports the same surface
Tests 418 circuit-editor tests across 29 files, all green; plus snapshot fixtures under test/circuits-cases/
Behavioral risk Low for existing flows; new capability is additive

What this PR does

Three things, in dependency order:

  1. Splits the monoliths into a layered architecture. The old editor was
    four large files with tangled responsibilities. They are removed and
    replaced with a strict, one-directional layering:

    data/  →  actions/  →  renderer/  →  editor/  →  sqore.ts  →  state-viz/
    (pure)    (pure)       (read data)   (DOM glue)  (entrypoint) (parallel)
    

    The hard rule: data/ and actions/ never touch the DOM. The renderer
    reads data to produce SVG; the editor glues DOM events to action calls.

  2. Adds group-aware, multi-target drag-and-drop editing. Editing inside
    expanded groups, moving multi-target gates as rigid units, shift-to-extend
    a group onto new wires, and the dropzone geometry that makes those
    reachable. This is the capability the re-architecture existed to enable —
    it is woven into the core move algorithm, not layered beside it.

  3. Hardens classical-register / measurement integrity. Moving or deleting
    a measurement that later gates depend on now cascades correctly (with a
    confirm prompt) instead of crashing or leaving dangling classical
    references.

Files removed (old monoliths)

  • circuitManipulation.ts (−668)
  • events.ts (−1,095)
  • panel.ts (−437)
  • draggable.ts (−531, old version)
  • contextMenu.ts (−335, old version)

Their responsibilities now live in the layered modules below.


Why this is one PR

The core is a single atomic refactor. The new data / actions / renderer / editor files are mutually dependent by design — for example, the core move
algorithm (actions/circuit-actions/move.ts) imports the classical-register
analysis, the entrypoint (sqore.ts) imports the view-state type, and the
editor shell wires in the renderer. There is no by-file grouping of the final
tree that yields smaller PRs which each compile and pass tests on their
own
: a hypothetical "data-layer-only" PR has no consumers, and a
"core-without-classical" PR wouldn't build because core files import it.

Splitting further would require hand-authoring intermediate versions of the
shared files that never actually existed and were never tested — trading a
large-but-truthful PR for several smaller-but-fictional ones. We chose to keep
the core honest and reviewable instead of artificially fragmented. The size is
inherent to the change, not a packaging choice.


How to review this efficiently

There is a companion reading guide checked into the branch:
REVIEW-ORDER.md (bottom-up, dependency-ordered) plus ARCHITECTURE.md
(the structural map + two end-to-end flow walkthroughs). Read
ARCHITECTURE.md first, then follow REVIEW-ORDER.md.

Note: ARCHITECTURE.md, REVIEW-ORDER.md, ROADMAP.md, and
CIRCUIT_EDITOR_TODO.md are maintained locally and are not part of this
PR. If you'd like them attached to the PR for review, say so and we can
include the two review-oriented ones (ARCHITECTURE.md + REVIEW-ORDER.md).

Suggested order and the natural stopping points:

  1. Data layer (data/) — small, pure value types (Location,
    CircuitModel, ViewState). The vocabulary everything else is written in.
  2. Action layer (actions/ + actions/circuit-actions/) — pure mutations;
    the correctness core. Cross-reference the
    test/circuit-editor/circuit-actions/ suite as you go — each topic file
    maps to a cluster of functions here.
    → Good checkpoint: you've now seen the entire pure core.
  3. Renderer (renderer/) — Circuit → SVG; reads data, never mutates.
  4. Editor (editor/ + editor/controllers/) — DOM glue. Read the shared
    InteractionContext first, then the controllers.
    → Good checkpoint: trace one end-to-end flow from ARCHITECTURE.md.
  5. Entrypoint (sqore.ts, utils.ts, index.ts) — ties it together.
  6. state-viz (state-viz/) — parallel subsystem, only import-path updates
    here; safe to skim last.

What to focus your scrutiny on

  • Layer purity: do data/ and actions/ stay DOM-free?
  • .targets authority: every mutator in actions/ should keep the eager
    .targets cache correct on the way out (the decision record for why the
    cache is eager lives in test/circuit-editor/circuitTargets.bench.md).
  • Controller independence: controllers share model / interaction and
    re-render via one renderFn, with no controller-to-controller coupling.
  • Cleanup on every exit path of a drag (ghost teardown, listener removal,
    resetTransient).
  • Deep-copy-on-render isolation in sqore.ts — the host's circuit object
    is never mutated by rendering.

What you can safely skim

  • state-viz/ diffs — mechanical import-path updates from the move
    (../events.js../editor/events.js, ../circuit.js
    ../data/circuit.js). No behavior change.
  • Snapshot fixtures under test/circuits-cases/ — generated.

Testing

  • 418 circuit-editor tests across 29 .mjs files, all passing
    (data, actions/circuit-actions, renderer, controllers, sqore),
    plus snapshot fixtures under test/circuits-cases/.

  • The action layer is the most heavily covered: the
    test/circuit-editor/circuit-actions/ suite pins every move / control /
    extend-cascade / classical-ref-remap / clone-move path.

  • Run locally from source/npm/qsharp/:

    # build the UX bundle
    npm run build:ux
    # run the circuit-editor suite
    $f = Get-ChildItem test/circuit-editor -Filter *.test.mjs -Recurse
    node --test $f.FullName

const svg = container.querySelector("svg.qviz") as SVGElement;

const overlay = document.createElementNS(
"http://www.w3.org/2000/svg",
isControl: boolean,
) => {
// Generate svg element to wrap around ghost element
const svgElem = document.createElementNS("http://www.w3.org/2000/svg", "svg");
const ghostY = svgHeight;

const ghostLayer = document.createElementNS(
"http://www.w3.org/2000/svg",
*/
const _dropzoneLayer = (context: Context) => {
const dropzoneLayer = document.createElementNS(
"http://www.w3.org/2000/svg",
});

// Generate svg container to store gate elements
const svgElem = document.createElementNS("http://www.w3.org/2000/svg", "svg");
const buttonX = 1;

const runButtonGroup = document.createElementNS(
"http://www.w3.org/2000/svg",
runButtonGroup.setAttribute("role", "button");

// Rectangle background
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("class", "svg-run-button-rect");

// Text label
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants