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>
This commit is contained in:
86
CLAUDE.md
Normal file
86
CLAUDE.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# 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<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.
|
||||
Reference in New Issue
Block a user