Parity with Capital Device Modeler 2512.2608

Every stock feature, its state in the replacement, and the evidence. Status words: Done (built and exercised on live Capital, result read back independently through the stock app's session), Built (in the app, not yet exercised against Capital), Gap (not built), N/A (not a Device Modeler feature, or broken in the stock app too). Test objects are all named ZZTEST_*. Last updated 2026-09-24.

ICD

Feature Status Evidence
ICD list, search by text Done 13 stock ICDs listed with release level and part number
View ICD, all stock fields and sections Done AUDIO UNIT:A field-for-field vs stock page text
New ICD (uid minted up front) Done ZZTEST_ICD2:A created from the UI, bulk pins 1-3, two nets on one pin
Edit fields, tags, effectivity, assigned to Done description, Assigned To, pin option G2 saved and read back
Pins: add, bulk add, delete, rename, several nets per pin Done pin 2 -> ZZTEST_NET2 + ZZTEST_NET3 read back
Net attributes: shield, signal group, option expression Done shield true, signal group GRP/AUDIO read back; net option via the dialog
Pin and net user properties Done PinColor=Red on pin 1, NetGauge=22AWG on its net read back
ICD user properties (rich-text ones read-only) Done ZZ Note=hello read back
Option expression dialog (project options, operators, validation) Done G1..G7 listed, bogus name rejected, G1 \|\| G2 saved
Applicability (Design tags), Projects (Project tags), Domains Done 2-Audio, Aerospace ELA Power Design saved; no domains exist here
Release level change along allowed transitions Done A1 Draft -> Obsolete (form locks) -> Draft
Edit locked by release level behaviour / permissions Done Obsolete shows read-only with the stock reason
Assign part (syncWithPart), clear part Done ZZTEST_ICD2_copy + ZZTEST_DEV3: DEV, ZZ_FP, ZZ-SPN-1, ZZ-CPN-1, pins A1/A2
Copy / Revise / Create descendant (unsaved template, then Create) Done ZZTEST_ICD2_copy:A, ZZTEST_ICD2:A1, ZZTEST_ICD2_child:A (parentRef read back)
Ancestor ICD picker (candidateparenticds) Done picked ZZTEST_ICD1:A from 18 candidates in the UI; parentRef read back
Delete Done copy deleted; stock query no longer finds it
Export (.zip) Done 11913-byte zip downloaded from the UI
Import (.zip), Import Merge Done exported zip re-imported (part, pins, tags, effectivity kept); merge updated in place
XSD import (Library Synchronizer) Built no XSD file available to test
Create component from ICD Done ZZTEST_DEV_FROM_ICD2 created and assigned from the UI
ICD Table Editor (multi-edit) Done role changed on ZZTEST_ICD2:A, pins untouched
Harness Footprint (device connector footprint, pin -> connector cavity) Done J1 + J2 clones of C-81334, 1->J1.1, 2->J1.2, 3->J2.5 read back; duplicate cavity refused; unused clone dropped. With a part assigned the part footprint shows read-only, as stock
Backshell signals (nets on a connector backshell termination) Done test backshell BS1 on J1 created by direct PUT (stock has no create action; it renders it after a full reload); nets edited from the UI (ZZTEST_NET3 added) and read back with the backshell and footprint intact; footprint locked while a backshell exists, as stock
ICD query builder (power queries) Done site icdqueries.json used when present (absent here, where stock shows none); built-in set otherwise. Applicable ICDs 2-Audio -> 17, By Role ZZTEST -> 5
Net filter inside the ICD form (wildcards * ?) Done Z*1A -> pin 1 only; ZZTEST_NET? -> pins 1, 2
Deep links Done hash form /devices/#/ICD/editICD/{uid} opens the ICD from a cold load (AUDIO UNIT:A). The path form /devices/{name}/ICD/editICD/{uid} returns 404 from the stock server too (checked 2026-09-24); the replacement still redirects it to the hash form when it is served. Not tested from the ConnectivityHub plugin itself

Library

Feature Status Evidence
Latest devices, part-number search Done 27 latest; Enter queries Capital (finds new parts the fixed list omits)
New device Done ZZTEST_DEV3
Edit: pins, properties by type code, footprints + pin mapping to connector cavity, customer / supplier PNs, extra Done all read back on ZZTEST_DEV3; cavity 1 of C-81334 resolved
Copy / Revise device Done ZZTEST_DEV4, ZZTEST_DEV4:B
Type code change warning dialog (Remove / Keep / Revert) Done ZZTEST_DEV3 DEV -> other type listed CAGE CODE; Revert kept DEV, nothing dirty. Orphan properties editable to unset
Library query builder (power queries) Done site libraryqueries.json or built-in set; partnumber=* added (Capital ignores other keys without it). Type code NOPE -> 0, ZZTEST* -> 8
Select mode (from ICD assign part) Done ICD "View/Select part" picker

Dictionary

Feature Status Evidence
Devices, Logic Designs, Nets grids with column filters and sort Done 14 / 5 / 116
Lock / unlock (client side), add, edit, delete, rename, user properties Done modifiedTime 178, 179
Saved filters (per browser) Done "ZZ speakers" (2N-SPKR) saved, survived a reload, re-applied 117 -> 12; localStorage, as stock
Validation (lengths, unique names) Done duplicate blocked
Import / Import Merge / Export N/A 404 on the devices dictionary in the stock app too
Signals / Messages (network dictionary) N/A separate Capital Dictionary app

Connectivity (new)

Feature Status Evidence
All ICDs / one ICD / one net views Done 88 -> 36 bends on the audio sample
Straight, pin-facing wires Done all 1:1 wires straight
Filters: applicability, project, build list, effectivity, options (ICD, pin and net level) Done except build list effectivity 16 -> 3; project + G3 hides, G1 shows; net G2 hidden under G1
Build list filter Built no ICD carries a build-list tag, so nothing to match
From / To table with expandable details Done 200 rows drawn, "Show all", CSV export of every row
Checks (new, 0.9.3) Done offline nets that go nowhere, near-duplicate names, connections no option combination allows, ends that never connect, non-overlapping effectivities, duplicate pins, unwired pins, nets not in the dictionary, several revisions in view. Live data: 15 genuine findings. Error branches exercised only on built-to-fail input (tools/logic_selftest.mjs, 20/20 incl. a clean control) because live data triggers none
Compare revisions (new, 0.9.3) Done offline ZZTEST_ICD1 A vs A1 matches an independent diff of the raw data; pin/net/option branches exercised on built input
Group by applicability / project / role (new, 0.9.3) Done offline synthetic 300 ICDs grouped: 10 boxes, 0.4 s, 0 through, 0 overlaps
Layout off the main thread (new, 0.9.3) Done ELK in its own worker, routing in a second; status line says which ran. Main-thread lag on a 300-ICD relayout 1.36 s (MessageChannel heartbeat, 300 ms control caught); the rest is SVG rendering
Export SVG, saved views, Copy for ConnectivityHub (new, 0.9.3) Done offline exported SVG parses, 20 boxes / 70 wires, no hit paths; saved view round trip in logic test. Capital desktop registers no URL protocol, so no direct link into Logic Designer or ConnectivityHub is possible

Repeatable proof (Phase 4)

#/selftest replays the app's own write paths against live Capital on throwaway ZZTEST_SELFTEST* objects and checks each result by an independent read-back: ICD create (two nets on one pin), edit (effectivity, Design tag, ICD and net options), harness footprint (J1.1), revise, table edit, delete (empty-200 read as missing), library device save, dictionary add + remove. It carries two negative controls that must fail, and cleans up its ICDs and dictionary entries (cleanup confirmed from the stock session). 2026-09-24: 10 of 10 passed. Re-run after every Capital update.

The per-row differential against the stock app is the Evidence column above: every Done write was read back through the stock app's own session, and the footprint and backshell shapes were first captured from the stock model itself.

Side-by-side run, 2026-09-24

Same actions driven through the UI of the unmodified stock war (served by tools/prod_preview.py --root <extracted stock war> --port 5193) and of the replacement (5191), both upstream of tools/capture_proxy.py, so every request each app sent is on disk (session captures/20260924_160213). Compared on the request body and on the object Capital stored.

Action Stock request Ours Result
Create ICD: 2 pins on one net, Design tag, effectivity 00265 POST /icds 00274 Pass: 41 fields, only the name differs plus one extra empty attributes.percentComplete from ours (accepted, stored identically). Stored objects: 0 differences outside id/time/name; control against an unrelated ICD: 8
Dictionary: add a net with a short description 00297 PUT devicesdictionary 00311 Failed first (00302: ours sent name Z and modified 10-AJARLT-1; added rows re-sorted while typed). Fixed in 0.9.2, server data restored and read back. Rerun: 16 fields, only name and modifiedTime differ; stored entries identical
Delete ICD 00328 DELETE /icds/{uid} 00335 Pass: same request, 204; both absent by query (control query on an existing ICD returns 2)
Library device: edit description 00354 PUT /parts/devices/{uid} 00362 Pass: 43 fields, only the value typed and modifiedTime differ

Also found: stock sorts names with its own natural comparator (1N-... before 10-...). Ported to lib/sortOrder.ts, identical to the stock function on all 16641 pairs of live net names; applied to the dictionary, and (0.9.3) the ICD list, table editor, cavity pickers and pin grid. The stock code applies this comparator to cavities and footprint pins; its master lists arrive in server order from the same queries ours sends. The ICD master list order in stock was not observed directly (DERIVED). Content-Type differs only in case (application/JSON vs application/json).

Scripted request parity (0.9.3)

node tools/request_parity.mjs runs our request-building code on the inputs of every write the stock app made in the captures and diffs the result. 23 stock writes across 11 operations match: ICD create (blank and from a copy/revise template), edit, copy, revise, descendant, create component, table editor, device create and edit, dictionary add. Accepted, with reasons in the script: an empty attributes.percentComplete on create, and numCavities sent as a string (stock itself sends a string on create and a number on edit). A planted change is caught. Not covered: import/export (multipart files), dictionary delete (no stock capture).

Not yet done