Commit Graph

85 Commits

Author SHA1 Message Date
9cf43696a2 Rename the power-adjacent category to "power management"
"Adjacent" read as jargon; "power management" is the standard EE umbrella
for enable/power-good/sense/fault/seq signals (cf. PMIC). Renamed across
the board: NameVerdict::PowerMgmt, stats/LoadResult field `mgmt`, analyze
tag [Power mgmt] + header "Pwr-mgmt" + glossary, load lines now say
"power-management (control/measure — kept as Other)" (TUI / script / wx
kept in sync).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:21:25 +02:00
e914c84c18 Power-control lexicon: adjust/trim/set, feedback variants, cmd, led, ref
ADJ/ADJUST/VADJ/TRIM, MARG/MARGIN, SET/VSET/ISET (regulator set-point),
FBK/FDB/VFB (feedback variants), CMD, LED (indicator drive), REF/VREF.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:46:29 +02:00
1943f1f88a Power inference: classify rail+control names as power-adjacent, not suspect
Names holding both a rail token (VCC/VDD/PWR/...) and a control token
(SENSE, EN, PG, FB, OK, FAULT, ...) are signals ABOUT a rail - feedback,
enable, power-good - so their non-Power classification is confident.
They used to land in the Suspect bucket, drowning the genuine ambiguities.

- classify_signal_name(): 3-state name verdict (Rail / PowerAdjacent /
  GndShield / Other) with whole-token matching (trailing digits stripped,
  long lexemes also match as suffix: VSENSE, PWRGOOD, NFAULT).
  infer_signal_type() becomes a thin wrapper, so the dashboard suspect
  count and the export suspect column shrink automatically.
- infer_signal_types(): PowerAdjacent -> Other + new `adjacent` stat,
  before the structural gate (a big-fanout sense net stays Other).
- LoadResult.adjacent rendered by all three consumers (TUI command,
  script engine, wx log) - outputs kept in sync.
- analyze Types tab: new [Pwr-adjacent] rows with the deciding token,
  deliberate sort order (Power, Suspect, Adjacent, Gnd), glossary entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:41:19 +02:00
c2b1f4c4ae Extract duplicate into core; support it in the script engine + wx GUI
A script using `duplicate` failed with "unsupported command 'duplicate'"
because the clone logic was still inline in the tui command. Extract it to
core/app/edit.hpp::duplicate_module(System*, src, dst) -> {ok, error, parts,
signals}: a deep clone of a module (parts, pins with spec + nc_origin, signals
with type overrides, pin→signal wiring; no connections), refusing on an unknown
source or an already-taken destination name.

  - the tui `duplicate` command renders the result (output unchanged);
  - the script engine dispatches `duplicate` to it — the failing script now runs;
  - the wx GUI gains Edit ▸ Duplicate module… (PickModule + a name prompt).

tests/test_edit.cpp: deep clone wires to the clone's own signal (not the
source's) and preserves the type; unknown source / existing destination
refused. 412 core assertions green; tui + wx build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 22:04:45 +02:00
794430e86c wx: stop tree/log content from freezing the layout after a script
Real cause of the log not resizing: on GTK a wxTreeCtrl/wxTextCtrl reports a
natural size that grows with its content. Once a script populated the tree with
many pins, the top area's minimum ballooned and consumed the vertical space,
pinning the log at its minimum so it no longer tracked the window (it worked on
an empty tree, hence "only after running a script").

Cap each control's min size (tree/overview 260x120, log 420x90) so content
can't inflate the sizer minimum; the proportions then govern and content
scrolls inside the controls. Keeps the frame sizer from the previous fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:59:47 +02:00
a9039a8eea wx: fix layout — log adapts and stays at the bottom on resize
The frame had no sizer; it relied on the implicit single-child fill, which
didn't reliably resize the panel, so the panel's vertical sizer never
redistributed and the log kept its size when the window grew. Add a frame
sizer holding the panel at proportion 1 + wxEXPAND, so a resize re-lays-out
the panel and the log (1/3) tracks the window.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:55:44 +02:00
b0e260a2ec wx: run scripts at boot via the engine (--source / --batch)
Now that core has run_script, wire WxFrontend::BootDispatch's `source` to it
instead of the "no script interpreter" note. `essim --source bring-up.essim`
(and with --batch) now executes the script in the wx frontend too, accumulating
the same output the tui produces into the console buffer surfaced by DumpOutput.

Verified: `essim --batch --source script.essim` (wx) runs load + set-signal-type
+ verify and prints the source summary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:50:22 +02:00
fc71cce647 Add a core script engine; wire File ▸ Run script in the wx GUI.
The wx frontend had no way to run essim command scripts (only the tui shell
did). Add a frontend-agnostic engine in core/app/script.{hpp,cpp}:
run_script(unique_ptr<System>&, path, ostream&) -> {ok, error, lines, errors}.
It parses essim scripts (# comments, blank lines, "quoted" args, set + $var /
${var} expansion, nested source with a depth guard) and dispatches the
scriptable, system-building commands — new, load, connect, set-connector-type,
set-signal-type, attach-bsdl, verify, export, save, restore — to the existing
app::* operations, writing their (TUI-identical) output to the stream.
Unsupported/interactive commands are reported and counted, execution continues.
System is taken by reference so new/restore can replace it.

wx gains File ▸ Run script…: pick a .essim, run it, echo the output into the
log and refresh. WxFrontend exposes system_ptr() for the engine.

tests/test_script.cpp: a full script (comment + set/$var + new + load + set-
signal-type + verify) builds the system and produces the expected log; missing
file reported; unsupported command flagged and skipped. 400 core assertions
green; tui + wx build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:49:05 +02:00
184b0d306f wx: drive the edit ops from the tree selection + a right-click menu
Each tree node now carries a NodeData (kind + Module/Part/Signal pointers), so
edits can act on what's selected instead of always re-asking:
  - Set connector type / Attach BSDL / Connect act on the selected Part (or the
    Pin's owning part); Connect uses it as the first endpoint, then prompts for
    the second. Set signal type acts on the selected Signal.
  - With nothing relevant selected, each falls back to its modal picker, so the
    menu-driven flow still works.
  - Right-click a Part or Signal → a context menu of the actions valid for it;
    the items reuse the menu IDs and select the clicked node first, so they run
    the same handlers.

wx-only; builds clean, window opens with no asserts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:38:23 +02:00
d4eac9557b wx: enrich the explore tree down to pins and signals
The model tree was modules → parts only. Drill it down:
  - each Part now lists its Pins, each labelled with the signal it is wired to
    and that signal's type, or "(NC[, imported|dropped])";
  - each Module gains a "Signals (N)" branch — the per-module net view, each
    signal labelled with its type and fan-out.
Pin/part/signal lists sort in natural order ("2" < "10") via a small helper.
Modules expand to show their parts + Signals node; pins and the signal list
stay collapsed (revealed on demand) so large parts don't flood the view.

wx-only change (no core, no tui); builds clean, window opens with no asserts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:32:21 +02:00
19dbec9672 Extract set-signal-type into core; add it to the wx GUI.
Fourth editing op into the wx frontend. Extract the type-name parse + apply
into core/app/edit.hpp::set_signal_type(Signal*, name) -> {ok, error, type},
failing without mutation on an unrecognised name. The interactive sigtype modal
keeps its own SignalType-cycling path (different interaction, trivial mutation).

The TUI `set-signal-type` command now renders that result (output unchanged).
The wx GUI gains Edit ▸ Set signal type…: a shared PickModule() helper (PickPart
now builds on it) + inline signal choice + a power/gnd/other dropdown, then the
core op, logged as "module/signal: signal type = …" and reflected.

tests/test_edit.cpp: name parsed and applied; unknown name refused without
mutation. 387 core assertions green; tui + wx build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:27:44 +02:00
fc3ef333fa wx: add Connect parts to the Edit menu
Third editing op in the wx GUI. No core change — app::connect_parts was already
extracted and unit-tested; this is pure wiring. Edit ▸ Connect parts… picks two
parts (PickPart twice, now caption-parameterised to label "first/second part"),
derives their parent modules from Part::prnt, calls app::connect_parts and
renders the same outcomes the TUI does: refused / identity NC fill / connected
(N wires) / failed.

wx builds clean, window opens with no asserts; tui + tests unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:21:49 +02:00
b999446151 Extract attach-bsdl into core; add it to the wx GUI.
Second editing op into the wx frontend. Extract the logic (parse the .bsd,
apply it to the part, record bsdl_path) into core/app/edit.hpp::attach_bsdl
(Part*, path) -> {ok, error, entity, bound, unbound, ports_total}, failing
without mutation when the file can't be parsed.

The TUI `attach-bsdl` command now renders that result (output unchanged); the
wx GUI gains an Edit ▸ Attach BSDL… menu reusing PickPart() + a .bsd file
dialog. Prune the now-dead bsdl_model include from commands.cpp.

tests/test_edit.cpp: parse failure leaves the part untouched; null part. The
success path is covered by a batch run (entity + bound/total ports). 381 core
assertions green; tui + wx build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:18:46 +02:00
7e88f82446 Extract set-connector-type into core; add it to the wx GUI.
First of the editing ops to reach the wx frontend. Extract the business logic
(validate the kind, tag the part, apply the connector model) into
core/app/edit.{hpp,cpp}: app::set_connector_type(Part*, kind) -> {ok, error,
materialised}, refusing without mutation when the kind is invalid for the part.

Both TUI call sites now use it: the `set-connector-type` command and the
interactive settype screen (de-dup) — output unchanged. The wx GUI gains an
Edit ▸ Set connector type… menu: a reusable PickPart() (module → part choice
dialogs) + a kind prompt, then the same core op, logged and reflected in the
model tree. Prune the now-dead pin_model/transform_vpx includes from
commands.cpp.

Unit-tested by tests/test_edit.cpp (free-form kind tags; invalid kind refused
without mutation; null part). tui + wx build clean; 376 core assertions green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:13:08 +02:00
76807b0307 docs: README + DESIGN for the wx frontend and essim_add_frontend
Document the wxWidgets GUI frontend (wx dependency + -DESSIM_FRONTEND=wx in
README; layout tree + architecture note in DESIGN) and the factored
essim_add_frontend() per-frontend CMake helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:03:06 +02:00
4803d7d01c Add a wxWidgets GUI frontend (second frontend, proves the split).
A native wx GUI built entirely on essim_core via the Frontend interface — no
Tui reuse, no command shell. Demonstrates the core/frontends architecture by
adding a real second frontend:

  - WxFrontend : public Frontend — owns the System + a console buffer; handles
    boot headlessly (restore for --restore/--batch; honest note for source);
    Run() boots wx without a generated main (SetInstance + wxEntryStart/
    CallOnInit/OnRun) so the shared frontend_main stays in control.
  - EssimFrame (wxFrame) — menu-driven window: Load (app::load_module), Restore/
    Save (persist), Export (app::export_connections), Verify (app::verify),
    rendered into a model tree (modules → parts), an overview + verify-health
    panel, and a log. Each handler is a thin wrapper over a core/app op.
  - main.cpp: construct WxFrontend, call frontend_main — same 4 lines as tui.
  - CMakeLists.txt: find_package(wxWidgets) + essim_add_frontend(wx LIBS …);
    select with -DESSIM_FRONTEND=wx. ESSIM_FRONTEND gains wx in its STRINGS.

Set LC_CTYPE from the environment at GUI init so wxString decodes the UTF-8 in
narrow literals (em dash, ellipsis); LC_NUMERIC stays "C". .gitignore: build*/.

Needs libwxgtk3.2-dev. Verified: builds clean (wx 3.2.9 GTK3), window opens and
renders with no wx asserts; --commands-md/--restore/--batch behave headlessly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:01:02 +02:00
e561c0f960 build: factor per-frontend CMake into essim_add_frontend()
Every frontend repeated the same target wiring (glob sources minus main.cpp →
essim_<name> lib linking essim_core; essim exe from main.cpp linking the lib +
essim_frontend; RUNTIME_OUTPUT_DIRECTORY=build/). Move it into a reusable
helper cmake/EssimFrontend.cmake::essim_add_frontend(name LIBS ...), included
once at the top level.

The tui CMakeLists now just fetches FTXUI and calls
essim_add_frontend(tui LIBS ftxui::screen ftxui::dom ftxui::component). A new
frontend's CMakeLists is its toolkit setup + one call. No behaviour change;
binary stays ./build/essim, tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:46:10 +02:00
091ef6fe4b Merge separate-core-ui: split business logic from the T/GUI layer.
Reorganises essim around a hard core/frontends boundary so multiple GUI/TUI
engines can be built on the same business logic:

  - src/core/{domain,imports,app} — frontend-agnostic essim_core (no GUI
    toolkit); src/frontends/<name>/ — per-frontend UI. CMake selects one with
    -DESSIM_FRONTEND=<name> (default tui; "none" = core+tests, no FTXUI).
  - Application layer (core/app/): export / verify / connect / load extracted
    as System-in, result-struct-out operations; TUI commands and screens are
    thin wrappers. The analyze screen and dashboard now share app::verify
    instead of recomputing the passes (de-dup).
  - Frontend interface (frontends/frontend.hpp) + shared frontend_main: main.cpp
    is frontend-agnostic; a new frontend is a 4-line main plus a Frontend impl.
  - ImportBase hardened (read-only open, fail-fast on a missing file) and made
    leak-free (valgrind: no leaks, 0 errors).

Tests: 78 core cases / 368 assertions + the TUI suite, all green. Binary stays
./build/essim; --batch/--commands-md/--help behaviour unchanged.
2026-06-03 20:37:35 +02:00
3b6e626c8f docs: DESIGN — Frontend interface + frontend_main, app/ ops, layout
Reflect the new shared frontends layer (frontend.hpp / frontend_main) in the
Architecture section and layout tree, and list the verify/connect/load app ops
alongside export.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:35:24 +02:00
af36f7c150 Add a Frontend interface; make the process entry frontend-agnostic.
main.cpp was entirely TUI-specific (constructed Tui, parsed argv, drove
BootDispatch/DumpOutput/Run directly). Introduce a shared frontends layer so a
second frontend can reuse the whole launch flow:

  - src/frontends/frontend.hpp — abstract Frontend interface (BootDispatch,
    DumpCommandsMd, DumpOutput, Run), header-only, no GUI toolkit, no core dep.
  - src/frontends/frontend_main.{hpp,cpp} — frontend_main(argc, argv, Frontend&):
    all the argv parsing (--source/--restore/--batch/--commands-md/--help) and
    the boot → batch/run flow, driving any frontend through the interface.
  - Tui now implements Frontend (the four methods already matched; just marked
    override).
  - The TUI main.cpp shrinks to: construct Tui, call frontend_main. A second
    frontend's main() is identical with its own Frontend type.

Build: a small GUI-toolkit-free static lib essim_frontend (frontend_main.cpp)
is added at the top level when a frontend is selected, and the essim exe links
it. ESSIM_FRONTEND=none still builds core+tests only (no essim_frontend, no
FTXUI). Binary stays ./build/essim.

Behaviour unchanged across --batch/--commands-md/--help/exit codes; only the
usage text is genericised ("the TUI" → "the interface", "console screen" →
"console") now that the launcher is shared.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:34:29 +02:00
0517a82a5c Fix importer leak: own Parts in ~ImportBase and delete the importer.
System::Load never deleted the ImportBase it allocated, and ~ImportBase was
defaulted so the Parts container it held leaked too. Give ~ImportBase a body
that deletes prts (the container only — the Part objects have been transferred
to the Module via add(), which owns them; the default ~Parts frees the map
without touching the elements, so no double free), and delete the importer at
the end of System::Load (and on the early unreadable-file error path).

Drop ImportMentor's explicit destructor, which called ImportBase::~ImportBase()
in its body — harmless when the base dtor was empty, but a double delete of
prts now that it frees memory. The implicit destructor calls the base once.

Verified with valgrind on a batch load (mentor + a missing-file error path):
"All heap blocks were freed — no leaks are possible", 0 errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:29:43 +02:00
4ef110ab70 Harden ImportBase: open read-only and fail fast on an unreadable file.
ImportBase opened the input with a default std::fstream (in|out), which had
two consequences: a missing file silently produced an empty module (no error),
and a present-but-read-only file failed to open and also loaded as empty. Open
the stream read-only (std::ios::in) instead, and expose is_open().

System::Load now builds the importer first, checks is_open(), and throws
"cannot open file: <path>" before creating the module — so a failed load
surfaces as `load failed: …` and leaves no empty module behind. A read-only
but present file now loads correctly.

Flip the test that pinned the old silent-empty behaviour to assert the clean
failure (error + no module created).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:24:11 +02:00
b36af3167a Extract load into core (app::load_module); thin the command.
Move the import orchestration — System::Load + drop_singleton_signals +
infer_signal_types + the post-import counts — out of the `load` command into
core/app/load.{hpp,cpp}: app::load_module(System*, name, path, ImportType)
returns a LoadResult (ok/error, parts, signals, dropped, power/gnd/kept_other)
with no Print/dialog/FTXUI. The "mentor|altium|ods" string→enum mapping moves
to app::import_type_from_name (mirrors export_format_from_path). The command
only parses the type and renders the counts; output is byte-identical.

Add tests/test_load.cpp (core, no UI): the name mapping; a minimal Mentor
netlist that imports two parts and drops one singleton signal; and a pin test
of the pre-existing missing-file behaviour (ImportBase doesn't check is_open(),
so a missing file yields an empty module rather than an error — preserved by
the extraction and pinned so any future hardening is a deliberate change).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:18:35 +02:00
a040cc1957 Extract connect into core (app::connect_parts); thin the command + screen.
Move the wiring orchestration — transform lookup, identity-compatibility
check, identity NC fill, transform apply, Connection creation/add — out of
the `connect` command and the interactive connect screen into
core/app/connect.{hpp,cpp}: app::connect_parts(System*, m1,p1, m2,p2) returns
a ConnectResult (ok/refused/error, connection_name, transform_name, wires,
identity_info, nc_added) with no Print/dialog/FTXUI. Name/pattern resolution
and ambiguity reporting stay in the command — that is arg-parsing, not the op.

Both frontends now call the one core op, removing the duplicated logic. This
also unifies a divergence: the interactive screen previously called
CheckIdentityCompatible without the info pointer and so never filled identity
NC pins (unlike the command); routing it through app::connect_parts makes the
screen fill NCs and surface the same warning, matching the scriptable path.

Command output is unchanged. Prune the now-dead transform.hpp / domain
connect.hpp includes from the frontends (commands.cpp keeps transform_vpx.hpp
only for ValidatePartForKind).

Add tests/test_connect.cpp (core, no UI): identity-compatible pair wires,
unknown type pairing is refused with nothing created, subset side gets NC
pins filled and the warning reported.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:12:11 +02:00
25939998ab De-dup verify passes: drive analyze screen + dashboard from app::verify.
The analyze Issues pane and the dashboard Health rows each recomputed the
same verify passes inline (pin-role mismatches, Power/Gnd net-mix, NC orphan
rollup, model-driven checks) — the third and second copies of what the verify
command also did. Route both screens through app::verify(System*) instead, so
the passes live in exactly one place.

Enrich VerifyReport with a per-pin OrphanPin detail list (module/part/pin +
dropped flag) so the dashboard can still nest its dropped-singleton breakdown
under the NC health line without re-walking modules/parts/pins. Output is
unchanged in both screens (same label formats, same numbers).

Prune the now-dead includes (nets/bsdl_check/connect/parts/pins as applicable,
<unordered_set>) from both screens. Extend tests/test_verify.cpp to cover the
new orphans detail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:04:31 +02:00
e3350b8d95 Extract verify into core (app::verify); thin the TUI command.
Move the 7-pass verify orchestration out of the TUI command lambda and
into core/app/verify.{hpp,cpp}: app::verify(System*) returns a structured
VerifyReport (role mismatches, net inconsistencies, orphan counts, the four
model-driven anomaly vectors) with no Print/dialog/FTXUI. The nets are
computed once and fed to the net-based checks.

The verify command is now a thin renderer over the report, byte-identical
output. Prune the now-dead nets.hpp / bsdl_check.hpp / <unordered_set>
includes from commands.cpp.

Add tests/test_verify.cpp: builds small systems by hand and asserts the
report (empty system, Power/GndShield bridged-net inconsistency, orphan
counts by import origin) — pure core, no UI.

This is the structuring extraction: the same VerifyReport can now back the
analyze screen's Issues pane and the dashboard health rows, removing the
triple duplication of passes 1-3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:51:53 +02:00
cccc5f131d docs: rewrite DESIGN + README for the core/frontends structure
DESIGN.md: new layered Build section (essim_core + ESSIM_FRONTEND, FTXUI per
frontend, split tests), a rewritten Layout tree (src/core/{domain,imports,app},
src/frontends/tui, tests/{,tui}), and a new "Architecture — core vs frontends"
section stating the rule (core never depends on a frontend) with the export
operation as the worked example. README: layered-build note, FTXUI-is-the-tui-
frontend's dependency, core-vs-frontend test split, nested project-layout tree.

Also keep the binary at ./build/essim via RUNTIME_OUTPUT_DIRECTORY now that the
exe is produced from the frontend subdir.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:39:21 +02:00
63ca17d048 build: split core/ from frontends/; prepare for multiple GUI/TUI targets
Reorganise the tree into business vs frontend as separate directories:
  src/core/{domain,imports,app}   (was system/, imports/, app/)
  src/frontends/tui/              (was tui/ + main.cpp)
  tests/tui/                      (the FTXUI-coupled helper test)
All cross-dir #include paths rewritten; same-dir includes untouched.

CMake: essim_core is the frontend-agnostic business library — links libzip,
pugixml and bsdl, NO GUI toolkit. Each frontend is a self-contained
src/frontends/<name>/ (own CMakeLists, toolkit, main.cpp) that links
essim_core, selected with -DESSIM_FRONTEND=<name> (default tui; 'none' = core +
tests only, no toolkit fetched). FTXUI moved into the tui frontend. Tests are
split: essim_tests links essim_core (no FTXUI), essim_tui_tests links essim_tui.

Verified: default tui build green (ctest 2/2); ESSIM_FRONTEND=none builds the
core + tests with FTXUI never fetched and no `essim` binary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:33:06 +02:00
3010bb25eb core/ui: extract export into src/app (frontend-agnostic), thin TUI command
First step of separating business logic from the TUI. The export command built
the CSV/ODS file inside its lambda, mixed with Print/ShowError/dialog calls.
Move all of it — CSV + ODS building, sheet-name sanitising, file writing — into
src/app/export.{hpp,cpp} (namespace app, no FTXUI/console dependency):
export_connections(const System*, path, format) -> ExportResult. The TUI
command is now a thin wrapper (resolve args/dialog, call the core, render). The
core is unit-tested without any UI (test_export); 342 assertions pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:23:41 +02:00
ac2edd90c4 tui: show the Computing... progress as a real Modal (was invisible)
The Renderer-overlay approach to the global progress box didn't render. Use a
proper Modal like the palette / file dialog, driven by a plain bool
'computing_open' raised when a source starts and lowered when it ends or
aborts. The tick handler stays ahead of the modal guard, so the script keeps
running (and the screen behind it keeps updating) while the modal is shown;
computing_open is also added to the guard so stray keys are ignored mid-load.
The console screen's own Computing block was already removed, so no duplicate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:11:27 +02:00
53eb79c760 source: abort guard compares to the originating screen, not 0
A script opened from the dashboard (screen_idx 4) aborted after its first
line: the guard treated any non-console screen as 'an interactive command
opened a screen'. Record the screen the source started from and abort only
when a sourced line navigates away from it (what a bare interactive command
does). Now 'o' from the dashboard runs the whole script in place — the
dashboard populates live behind the global Computing overlay — while a bare
connect/explore inside a script still aborts. Batch unaffected (BootDispatch
pins screen 0, so origin 0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:06:18 +02:00
29cb353d75 tui: global script progress + dashboard source/restore work
Opening a script from the dashboard did nothing visually: the tick driving the
line-by-line loader was only handled in the console case, and the Computing…
overlay was console-only. Move both to the global layer — ticks now process on
any screen (the dashboard updates live as the script loads) and the Computing…
box overlays whatever screen is active. Add an 'r' dashboard shortcut to
restore a snapshot via the file picker (open mode, like 'o'). Dashboard help
hints (loaded + empty state) updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:02:13 +02:00
c70e767cf1 filedialog: add open mode (no overwrite prompt) and use it for 'o'
The picker is built for saving, so it asks 'file exists — overwrite?' on
confirm. That's wrong when opening a script (you want an existing file). Add a
confirm_overwrite flag (default true; FileDialogState + OpenFileDialog param);
ConfirmFileDialog only prompts when it's set. The dashboard 'o' shortcut now
opens the dialog with confirm_overwrite=false. save/export keep the guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:52:53 +02:00
527a48145b dashboard: show 'o' (open script) in the no-system help panel too
The empty-dashboard panel (early_help) is a separate hint list from the
loaded-system one, and didn't list the new o/s keys — so 'o' worked but wasn't
advertised when no system is loaded (exactly when you'd use it). Add 'o' to
early_help and mention it in the 'no system loaded' line. (s/x need a system,
so they stay out of the empty-state panel.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:49:42 +02:00
60c00eb914 dashboard: o/s shortcuts to open a script and save via a file dialog
Two new dashboard keys, mirroring the existing x=export: 'o' opens a file
picker to run a .essim script (-> source <path>), 's' opens a file picker to
write a system snapshot (-> save <path>, only when a system is loaded). Both
use OpenFileDialog with a per-key persisted last directory; the global
CatchEvent already yields to the file-dialog modal while it is open. Dashboard
help hints updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:44:41 +02:00
29242ae016 dashboard: nest the dropped-NC detail under the NC row
The detail block was rendered after *all* health rows, so it dangled below
the new model: row instead of the NC: row it explains — its indentation read
as broken. Build it into health_rows right after the NC row, with a tree
marker (↳ dropped (only 1 pin on the net):) and consistent nesting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:38:58 +02:00
7810711fd4 ui: plainer wording across labels and command descriptions
Replace internal jargon and uncommon words in user-facing strings (better for
non-native English readers): drop "TransformRegistry-driven" / "drives
transforms" from the connect/set-connector-type subtitles; "transform lookup"
→ "tells connect how to wire its pins"; "populate pin specs" → "fills in each
pin's role and direction"; "clear the visualization area" → "clear the console
output"; "materialised" → "added"; refresh the verify description. Regenerated
commands.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:33:32 +02:00
1a31dd64b6 dashboard: simpler wording for the dropped-NC detail label
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:30:20 +02:00
3cee5c2e49 dashboard: clearer label for the dropped-NC detail rows
"dropped detail:" said nothing about what those pins are. They were detached
by drop_singleton_signals because each was the lone pin on its net (nowhere to
connect → NC). Relabel to "dropped — lone pin on its net (→ NC):".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:29:11 +02:00
a914b9d7e8 P3: BSDL completeness check (missing device power/ground pins)
check_bsdl_completeness(System*): for each BSDL-attached part, re-parse the
.bsd and report the device power/ground ports with no matching pin on the
netlist part (matched by port name or physical pad) — a rail the schematic
symbol is missing. One aggregated BsdlPinMissing per part; restricted to
power/ground so unused I/O balls don't create noise. Surfaced as a 7th verify
pass and in the analyze/dashboard model counts. 76 cases / 327 assertions
green; the real 8-card system reports 0 (all FPGA rails present). This closes
out P3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 16:21:02 +02:00
c9ac186a20 P3.3: surface model anomalies in analyze + dashboard
The analyze screen's Issues pane now lists the model-driven checks
(check_pin_specs / check_jtag_chain / check_source_conflicts) alongside the
pin-role, net-mix and structural ones, with an "N model" count in the header;
the dashboard gains a "model:" health row. check_pin_specs/check_jtag_chain
take an optional precomputed net list, so verify, analyze and the dashboard
each compute the nets once and reuse them across checks instead of redoing the
transitive closure per check. Unit tests (75) green; verify output unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 16:14:41 +02:00
fe5b2c3d96 P3.2: source precedence + model-vs-netlist conflict check
Rank the spec sources (spec_source_rank: UserOverride > Bsdl > ConnectorModel
> Inferred > Imported); apply_model now refuses to overwrite a spec owned by a
higher-rank source, so one model never clobbers a more authoritative one. New
check_source_conflicts(System*) emits SourceConflict for a pin the BSDL
declares power/ground (a must-connect rail) that the netlist leaves
unconnected — a rail floated in the schematic; surfaced as a sixth `verify`
pass. Unit tests (75 cases) green; the real 8-card system reports 0 conflicts
(its rails are all connected) while the JTAG findings remain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 16:08:28 +02:00
cb61e9b084 P3: unify connector layout + BSDL behind one PinModel provider
New PinModel interface (spec_for / layout / source) + a single apply_model(
Part*, const PinModel&) that materialises missing layout pins and sets each
pin's spec only where the model speaks (spec.source != None), so one source
never clobbers another's. ConnectorModel wraps pin_role/pin_layout;
BsdlPinModel wraps a parsed BsdlModel (indexed by port name and physical pad).
set-connector-type and screen_settype now use ConnectorModel + apply_model;
attach-bsdl and the restore re-apply keep calling apply_bsdl, now a thin
adapter over apply_model. Behaviour-preserving: unit tests (73 cases) green and
the real 8-card system re-runs identically (1517/1517 bound, same JTAG
findings). Covered by test_pin_model.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 16:02:39 +02:00
a4f8254cb3 docs: document the BSDL workflow + add a batch/TUI tutorial
DESIGN.md: libbsdl dependency and --batch headless mode; bsdl_model/bsdl_check
in the layout; the attach-bsdl command and the `B` persist tag; PinSpec is now
BSDL-populated; verify's five passes incl. the model-driven and JTAG checks
and the new AnomalyKinds. README: libbsdl dependency, --batch usage, tutorial
link. New doc/user/tutorial.md: end-to-end batch and TUI walkthroughs (load →
tag → connect → attach-bsdl → verify, with the pin/JTAG findings explained).
Regenerated commands.md (adds attach-bsdl); index.md links the tutorial.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:46:56 +02:00
581028a83d verify: only flag undriven-net on a fully-modelled net
Real-data testing (3 BSDL-attached FPGAs in an 8-card system) showed undriven
over-fires when only one side of a net has a known direction: the driver sits
on an un-modelled part (direction Unknown). Require known == net pin count, so
"undriven" is concluded only when every pin on the net is modelled. Drops 216
false positives to 0 on the sample system while the genuine JTAG findings
remain; the unit test is unaffected (its net is fully modelled).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:38:54 +02:00
7cec6e1b0c tui: add --batch mode to run a script and print output headless
BootDispatch already runs --restore/--source synchronously before the TUI
starts (Source takes its headless drain branch when no screen is attached),
so the console buffer is complete by then. New --batch flag dumps that buffer
(Tui::DumpOutput) to stdout and exits without launching the TUI — enabling
scripted/CI runs and verify output capture (e.g. essim --batch --source s).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:36:41 +02:00
952afe3979 verify: JTAG boundary-scan chain integrity (flagship)
New check_jtag_chain(System*): collects TAP pins by PinSpec.function, resolves
each to its net, and flags JtagTapIncomplete (a device missing TDI/TDO/TMS/
TCK), JtagBusUnbridged (TMS or TCK not common to every TAP device), and
JtagChainBreak (dangling TDO/TDI, chain fan-out, or not a single head->tail
daisy chain). Surfaced as a pass in `verify`; AnomalyKind extended. Covered by
test_bsdl_check (healthy chain, broken chain + split bus, incomplete TAP).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:24:00 +02:00
5caa4c530d verify: model-driven pin checks (contention / undriven / NC-wired)
New bsdl_check.{hpp,cpp}: check_pin_specs(System*) walks the nets and uses
each pin's PinSpec direction/function to flag DriveContention (>=2 push-pull
output drivers), UndrivenNet (a multi-pin net with input(s) but no driver),
and NcWired (a no-connect pin wired onto a multi-pin net). Added as a pass in
`verify`; AnomalyKind extended accordingly. Nets with no direction data are
skipped, so un-modelled parts produce no noise. Covered by test_bsdl_check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:15:11 +02:00
943b808a75 BSDL: attach-bsdl command + persist the attached model
New `attach-bsdl <module> <part> <file.bsd>` command: parse via BsdlModel,
apply_bsdl() onto the part, store the path on Part::bsdl_path, report bound/
unbound. Persist a `B\t<path>` line under the part; on restore, re-parse and
re-apply each attached model (the .bsd path is persisted, not the derived
pin specs). Round-trip covered by test_bsdl_apply.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:07:12 +02:00
279be513a4 BSDL: ingest libbsdl into essim and populate PinSpec from a device model
Link libbsdl dynamically (add_subdirectory ../libbsdl, overridable via
-DBSDL_DIR). New BsdlModel wraps the C ABI and reduces a parsed .bsd to
essim's pin vocabulary; apply_bsdl() binds each port to a Pin (by name, then
by physical pad) and sets its spec: direction, function (TAP role / power /
ground / signal), pad, and source = Bsdl.

This feeds the PinSpec fields from P1, so verify's existing power/ground
placement pass now lights up for BSDL-modelled parts. Covered by
test_bsdl_apply (name + pad binding, TAP roles, linkage classification).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:01:00 +02:00