# essim — notes for Claude System digital twin: simulator for the interconnections between cards/boards. C++17, FTXUI for the TUI, importers for external netlist formats. ## Build ```sh cmake -S . -B build cmake --build build -j ./build/essim ``` - CMake **3.14+** required (uses `FetchContent_MakeAvailable`). - FTXUI is fetched at configure time from GitHub (`v6.1.9`, shallow clone). First configure pays ~20 s for the clone; subsequent ones are cached in `build/_deps/`. - Sources are collected with `file(GLOB_RECURSE ALL_SOURCES "src/*.cpp")`. **After adding a new `.cpp`, re-run `cmake -S . -B build`** — CMake does not re-glob automatically and link will fail with "undefined reference". ## Layout ``` src/ main.cpp -- launches Tui system/ -- domain model syselmts.hpp SystemElement + SystemElementContainer (templated, get/merge/iterate) modules.{hpp,cpp} Module, Modules parts.{hpp,cpp} Part, Parts pins.{hpp,cpp} Pin, Pins signals.{hpp,cpp} Signal, Signals connect.{hpp,cpp} Connection, Connections transform.{hpp,cpp} transforms applied to the model system.{hpp,cpp} System: owns Modules + Connections, exposes Load() imports/ -- adapters that populate the domain import_base.hpp ImportBase interface import_mentor.{hpp,cpp} Mentor Graphics netlist parser (done) tui/ -- FTXUI shell tui.{hpp,cpp} Tui: visualisation area on top, input + history at the bottom doc/classes.puml -- PlantUML class diagram ``` `include/` and `lib/` are kept empty by design — FTXUI used to live there as precompiled `.a` + headers, now it comes through FetchContent. ## Domain conventions - Everything in `system/` is **pointer-based** (commit `d8122d1`: "everything is pointer"). Containers store `T*`, ownership lives with the container. - `SystemElementContainer::merge(name)` is the get-or-create primitive — call it instead of `add` when you don't know whether the element already exists. `add` throws on duplicate names or empty names. - `using namespace std;` is present in `syselmts.hpp` — pre-existing, don't add more `using namespace` in headers. - Include guards `_NAME_HPP_` *and* `#pragma once` are both used. Match the existing style. ## TUI `Tui::Run()` enters an FTXUI fullscreen loop: - Top: scrollable visualisation area (`window` + `vscroll_indicator | yframe`, last line gets `focus` so it auto-scrolls). - Bottom: single-line `Input` inside a border. Label switches between `> ` (normal) and `? ` (during a multi-step prompt). - `CatchEvent` is wrapped **outside** the `Renderer` (not between `Renderer` and `Input`). Pattern: `Renderer(input_component, lambda)` then `CatchEvent(renderer, handler)`. Wrapping `CatchEvent(input, …)` inside a `Container::Vertical({…})` and then `Renderer(container, …)` was found to break the `Input`'s content rendering on at least one terminal — typed characters didn't show even though `on_enter` fired correctly. - `InputOption::transform` is overridden to avoid hard-coded colors (default sets `White on Black` when focused, which is unreadable on light terminal themes). The custom transform only applies `dim` to the placeholder; everything else inherits terminal default fg/bg, so the UI adapts to any theme. - `InputOption::multiline` **must** be set to `false` for the command prompt. Default is `true`, and FTXUI's `HandleReturn()` then both inserts `\n` into the content **and** fires `on_enter` — so `Submit()` would receive `"help\n"` instead of `"help"` and command lookups would all fall through to "unknown command". - Commands live in a `std::map` registry built in `RegisterCommands()`. Each `CommandSpec` declares a list of `Param{name, path_completion}` and an `action(args)` lambda. `Dispatch(raw)` tokenises the input (whitespace split with `"…"` quoting), takes inline args first, and pushes a `Prompt` onto the queue for each missing param. The last prompt's callback calls `Finalize()`. - `Finalize()` rebuilds the **canonical inline form** (`name arg1 "arg with spaces" arg3`) and writes that to history — so a `load` command answered via interactive prompts still shows up in `history` (and on disk) as a single inline line, ready to be replayed via ↑. - Multi-step prompts work via a `std::deque` queue. `Submit()` pops them one by one before falling back to dispatch. Adding a new command = one entry in `RegisterCommands()`; the prompt-flow and inline-flow are both handled automatically. - Tab completion: at the top-level prompt (no `pending`), completes built-in command names. Inside a prompt with `path_completion = true` (e.g. the `filename` step of `load`), completes file paths via `std::filesystem::directory_iterator` (handles `~/`, dirs get a trailing `/`). Logic: 1 match → replace; multiple with progress on the longest common prefix → extend; multiple stuck at LCP → list candidates in the visualisation area. Built-in commands: `new`, `load`, `search`, `clear`, `help`, `quit`/`exit`. `Esc` cancels an in-progress multi-step prompt. `search` switches to a second full-screen layout (handled by `Container::Tab({main, search}, &screen_idx)`). Layout: - Left column: `Menu` for the module list and `Menu` for the type (`parts` / `signals`). - Right column: `Input` for the live filter query, plus a results panel rebuilt every frame. - `Tab` cycles focus between the query input and the menus. **Implemented manually** in the outer `CatchEvent`: `Menu::OnEvent` consumes `Event::Tab` to cycle its own entries and returns `true`, which prevents `Container::Vertical` from ever seeing the event (Container only cycles between children when the active child returns `false`). So we short-circuit Tab/TabReverse upstream and mutate `search_focus_idx` directly. - `Esc` exits the search mode (flips `screen_idx` back to 0). The search state (selected module/type, query) is preserved across re-entries until `search` is run again. Adding a new screen mode = add a child to `Container::Tab` and a `screen_idx` value; key handling already lives in the outer `CatchEvent`. Command history is persisted on disk and loaded on startup. Path resolution is platform-aware: - Linux/macOS: `$XDG_DATA_HOME/essim/history`, falling back to `~/.local/share/essim/history`. - Windows: `%LOCALAPPDATA%\essim\history`, falling back to `%APPDATA%\…` then `%USERPROFILE%\AppData\Local\…`. Each successful submission appends a single line to the file (so a crash doesn't lose history). Multi-step prompt answers are NOT persisted — only top-level commands. ## Gotchas - `System::Load` for `IMPORT_ALTIUM` / `IMPORT_ODS`: the corresponding constructor lines are commented out, so `imp` stays uninitialised → UB on `imp->parse(...)`. Only `IMPORT_MENTOR` is safe today. Wrap calls in `try/catch` (the TUI does). - `System::Load` throws `std::runtime_error("Unknown import type")` for any value outside the three enum cases. - `Modules`/`Parts`/etc. have no const-correct iteration on the `*` accessor; iterators on `SystemElementContainer` are available but `begin()`/`end()` are non-const-safe in some places. ## Memory layout for sessions Persistent notes live in `~/.claude/projects/-home-francois-Projets-essim/memory/`. Index: `MEMORY.md`. Add project/feedback/user/reference entries there when relevant — see top-level Claude Code memory rules.