Merge search and net screens into explore; drop both commands.

`explore` was already a superset of `search` (4 columns: module → type
→ filtered children → detail, with parts/signals/connections — vs
search's 2 columns of parts/signals only). It now also subsumes the
former `net` screen: when a signal entry is selected, the detail pane
shows the local pins followed by a `Net members (across connections)`
section listing every `(module, signal, type)` reachable through the
BFS over `Connection::pin_map`, with the count + dominant type and an
INCONSISTENT flag in the signal-detail header.

Removed:
- `src/tui/screen_search.cpp`, `src/tui/screen_net.cpp`.
- `commands["search"]`, `commands["net"]` (including its textual
  inline form). The `find_net` / `Net` API stays for explore's BFS
  panel and the analyze screen's net-mix check.
- `[s]` and `[n]` letter shortcuts on the dashboard.
- `net_*` and `search_*` state members + builders + constructor
  inits.

screen_idx renumbering (the slots vacated by search + net are
removed, not left dead):
  0 = console  (unchanged)
  1 = connect
  2 = set-connector-type
  3 = explore  (unchanged number, but now subsumes search + net)
  4 = dashboard (boot)
  5 = analyze

Palette signal items now jump to `explore` prefilled on the signals
tab with the child filter seeded to the exact signal name; the BFS
section in the detail pane is what shows the cross-module net.

Net-member rows in the detail pane are deliberately read-only for
now (Enter is a no-op): the signal-type popup is scoped to the
currently selected module, so opening it on a peer-module member
would mis-fire. Cross-module Enter navigation can come later if
needed.

DESIGN.md and user docs updated accordingly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 21:49:26 +02:00
parent 7145577df7
commit 792e4745d3
11 changed files with 109 additions and 463 deletions

View File

@@ -13,10 +13,7 @@ Tui::Tui()
loading(false), tick_in_flight(false),
loading_idx(0), loading_executed(0), loading_lineno(0),
loading_prev_in_source(false), screen_ptr(nullptr),
screen_idx(6), // boot to the dashboard; shell (screen 0) is now a sub-screen
search_types{"parts", "signals"},
search_module_idx(0), search_type_idx(0), search_focus_idx(0),
screen_idx(4), // boot to the dashboard; console (screen 0) is now a sub-screen
connect_m1_idx(0), connect_m2_idx(0),
connect_p1_idx(0), connect_p2_idx(0),
connect_focus_idx(0),
@@ -24,7 +21,6 @@ Tui::Tui()
explore_types{"parts", "signals", "connections"},
explore_type_idx(0), explore_child_idx(0),
explore_detail_idx(0), explore_focus_idx(0),
net_module_idx(0), net_sig_idx(0), net_focus_idx(0),
settype_m_idx(0), settype_p_idx(0), settype_focus_idx(0)
{
LoadHistory();
@@ -39,19 +35,16 @@ void Tui::Run() {
screen_ptr = &screen;
auto main_screen = BuildMainScreen(screen);
auto search_screen = BuildSearchScreen();
auto connect_screen = BuildConnectScreen();
auto settype_screen = BuildSettypeScreen();
auto explore_screen = BuildExploreScreen() | Modal(BuildSignalTypeModal(),
&sigtype_dialog_open);
auto net_screen = BuildNetScreen() | Modal(BuildSignalTypeModal(),
&sigtype_dialog_open);
auto dashboard_screen = BuildDashboardScreen();
auto analyze_screen = BuildAnalyzeScreen();
auto tab = Container::Tab(
{main_screen, search_screen, connect_screen, settype_screen, explore_screen,
net_screen, dashboard_screen, analyze_screen},
{main_screen, connect_screen, settype_screen, explore_screen,
dashboard_screen, analyze_screen},
&screen_idx);
// Palette is a global Modal — overlays the tab on every screen.
@@ -64,11 +57,11 @@ void Tui::Run() {
// Ctrl-P opens the palette from any screen.
if (e == Event::CtrlP) { OpenPalette(); return true; }
// screen_idx mapping: 0 = console, 1 = connect, 2 = set-connector-type,
// 3 = explore, 4 = dashboard (home), 5 = analyze.
switch (screen_idx) {
case 7: // analyze
if (e == Event::Escape) { screen_idx = 6; return true; }
// Tab and ←/→ both switch the active tab. ↑/↓ stay with the
// detail Menu so it can scroll.
case 5: // analyze
if (e == Event::Escape) { screen_idx = 4; return true; }
if (e == Event::Tab || e == Event::ArrowRight) {
analyze_focus_idx = (analyze_focus_idx + 1) % 3;
return true;
@@ -79,12 +72,8 @@ void Tui::Run() {
}
return false;
case 6: // dashboard (home)
// Home has no parent — Esc is swallowed. Use 'q' to quit.
case 4: // dashboard (home)
if (e == Event::Escape) { return true; }
// Scroll the dashboard when content overflows the viewport. The
// upper bound is clamped inside the Renderer (we don't know the
// line count from here).
if (e == Event::PageDown) { dashboard_scroll_offset += 10; return true; }
if (e == Event::PageUp) {
dashboard_scroll_offset = std::max(0, dashboard_scroll_offset - 10);
@@ -93,57 +82,36 @@ void Tui::Run() {
if (e == Event::Home) { dashboard_scroll_offset = 0; return true; }
if (e == Event::End) { dashboard_scroll_offset = 100000; return true; }
if (e == Event::Character("q")) { Dispatch("quit"); return true; }
// [c]onsole = the textual shell screen (former [l]og). [p]lug
// = the `connect` command (UI rename only; the underlying
// command stays `connect` for script + save/restore stability,
// with `plug` registered as an alias so the palette finds it).
if (e == Event::Character("c")) { screen_idx = 0; return true; }
if (e == Event::Character("p")) { Dispatch("connect"); return true; }
if (e == Event::Character("s")) { Dispatch("search"); return true; }
if (e == Event::Character("t")) { Dispatch("set-connector-type"); return true; }
if (e == Event::Character("e")) { Dispatch("explore"); return true; }
if (e == Event::Character("n")) { Dispatch("net"); return true; }
// [a]nalyze is the unified verify + analyze screen (issues +
// groups + types). The textual `verify` and `analyze` commands
// still exist for scripts.
if (e == Event::Character("a")) { screen_idx = 7; return true; }
if (e == Event::Character("a")) { screen_idx = 5; return true; }
return false;
case 5: // net
if (e == Event::Escape) { screen_idx = 6; return true; }
if (e == Event::Tab) { net_focus_idx = (net_focus_idx + 1) % 3; return true; }
if (e == Event::TabReverse) { net_focus_idx = (net_focus_idx + 2) % 3; return true; }
return false;
case 4: // explore
if (e == Event::Escape) { screen_idx = 6; return true; }
case 3: // explore
if (e == Event::Escape) { screen_idx = 4; return true; }
if (e == Event::Tab) { explore_focus_idx = (explore_focus_idx + 1) % 6; return true; }
if (e == Event::TabReverse) { explore_focus_idx = (explore_focus_idx + 5) % 6; return true; }
return false;
case 3: // set-connector-type
if (e == Event::Escape) { screen_idx = 6; return true; }
case 2: // set-connector-type
if (e == Event::Escape) { screen_idx = 4; return true; }
if (e == Event::Tab) { settype_focus_idx = (settype_focus_idx + 1) % 5; return true; }
if (e == Event::TabReverse) { settype_focus_idx = (settype_focus_idx + 4) % 5; return true; }
return false;
case 2: // connect
if (e == Event::Escape) { screen_idx = 6; return true; }
case 1: // connect
if (e == Event::Escape) { screen_idx = 4; return true; }
if (e == Event::Tab) { connect_focus_idx = (connect_focus_idx + 1) % 7; return true; }
if (e == Event::TabReverse) { connect_focus_idx = (connect_focus_idx + 6) % 7; return true; }
return false;
case 1: // search
if (e == Event::Escape) { screen_idx = 6; return true; }
if (e == Event::Tab) { search_focus_idx = (search_focus_idx + 1) % 3; return true; }
if (e == Event::TabReverse) { search_focus_idx = (search_focus_idx + 2) % 3; return true; }
return false;
default: // main (shell / log view)
default: // 0: main (console / log view)
if (e == Event::Special("\x02tick")) { ProcessNextSourceLine(); return true; }
if (e == Event::Escape) {
if (!pending.empty()) { CancelPending(); return true; }
screen_idx = 6; return true;
screen_idx = 4; return true;
}
if (e == Event::PageUp) { scroll_offset += 10; return true; }
if (e == Event::PageDown) { scroll_offset = std::max(0, scroll_offset - 10); return true; }