Files
essim/CLAUDE.md
François 3395469810 TUI shell + ftxui via FetchContent.
- ftxui v6.1.9 fetched at configure time; vendored .a + headers dropped.
- TUI: visualisation area on top, input prompt at the bottom; ↑/↓ history,
  Tab completion (commands + file paths), Esc cancels prompts. History is
  persisted (XDG on Linux, %LOCALAPPDATA% on Windows when ported).
- Command registry: `new`, `load`, `search`, `clear`, `help`, `quit`/`exit`.
  Inline params or interactive prompts; either way the canonical inline
  form is what gets stored in history.
- `search`: second full-screen mode with module + parts/signals menus and
  a live-filtered list; Tab cycles focus, Esc returns to the main shell.
- Domain: `System::modules()` accessor + `SystemElementContainer::size()`
  to support load summary + search.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 10:17:03 +02:00

7.3 KiB

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

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<T> (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<T>::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 <question>? (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<std::string, CommandSpec> 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<Prompt> 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<T> 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.