Skip to content

feat(ui): focus the sidebar around a spaces tree with per-workspace agent disclosure - #1

Closed
matthias-scale wants to merge 11 commits into
fork/pr-basefrom
feat/focused-sidebar-tree
Closed

feat(ui): focus the sidebar around a spaces tree with per-workspace agent disclosure#1
matthias-scale wants to merge 11 commits into
fork/pr-basefrom
feat/focused-sidebar-tree

Conversation

@matthias-scale

Copy link
Copy Markdown
Owner

Intent

Ship the focused Herdr sidebar in the maintained matthias-scale/herdr fork: default Spaces tree with attach-local independent agent disclosure, flat priority/API projections, global canonical next/previous agent wrap independent of presentation, preserved tab navigation and compatibility, full verification, fork PR only, no upstream mutation, and no merge.

What Changed

  • Replaces the split Spaces/Agents sidebar with a single Spaces tree: every workspace keeps its roll-up row and independently discloses its detected agent panes, while ui.agent_panel_sort = "priority" and agent.view.set swap the tree for one flat projection at a time (the workspace picker temporarily restores the tree without clearing the projection). previous_agent / next_agent now traverse the canonical unfiltered workspace→tab→pane order and wrap at both ends regardless of disclosure, filtering, or collapsed mode; prefix+p / prefix+n remain previous/next tab.
  • Moves disclosure, reveal, and scroll into an attach-local SidebarPresentationState that the headless server swaps per client, so two attaches to one server keep independent expand/collapse and scroll state. The sidebar renders and hit-tests one unified row list (workspace_scroll only; agent_panel_scroll and the section-divider drag are retired), and the collapsed rail plus the mobile switcher render the same ownership and disclosure tree. Docs under docs/next (en/ja/zh-cn concepts, configuration, socket-api, changelog) describe the new hierarchy and navigation guarantees.
  • Also carries the upstream master work this branch is based on: static agent status marks in place of spinners, a render_signal path that skips rendering hidden PTY updates, linked-worktree workspace label preservation, navigate-mode retention after pane focus, ptmx fd counting under devpts, the simplified Chinese readme, and removal of the approved-contributor bypass workflow. The Test gate additionally fixed a pre-existing live_handoff flake by retrying agent.start until the pane shell is ready; the Document gate's translation-parity warning on integrations.mdx predates this branch and is left untouched.

Risk Assessment

⚠️ Medium: The follow-up commit resolves all ten round-1 findings with real fixes rather than symptom patches — compute_sidebar_row_areas now clamps through the same workspace_list_bottom_start basis as normalized_workspace_scroll, agent_panel_scroll and the dead scrollbar/split machinery are gone, the collapsed rail scrolls with matching render and hit-test offsets, agent navigation reveals the target row, the desktop empty state and the mobile disclosure target are back — leaving one reachable state leak (force_spaces_tree never cleared on the mobile picker's create/menu exits) plus narrow edge cases and untested new paths, all safe to address as follow-ups on a large but coherent sidebar restructure.

Testing

Scope was deliberately narrow per instruction: no full-suite run, no linters/formatters/static analysis. Test selection targeted every symbol the diff touches (sidebar tree, agent disclosure, agent panel, agent navigation, workspace picker, projections, mobile switcher) across src/ui/sidebar.rs, src/ui/mobile.rs, src/app/state.rs, src/app/actions.rs, src/app/input/*, src/server/headless.rs, src/server/clients.rs — 152 tests, all green. Build required zig 0.15.2 exactly; the already-installed /opt/homebrew/opt/zig@0.15/bin/zig was used via the ZIG env var rather than installing or upgrading anything. Because this is a rendered-surface (TUI) change, unit tests alone were not treated as sufficient evidence. A disposable herdr-dev server was driven headlessly in tmux with a realistic scenario (4 Spaces: home, herdr with 2 agents across 2 tabs, website with 1 agent, scratch with none), and each acceptance criterion was exercised through the real UI — keyboard via tmux send-keys, disclosure chevrons via literal SGR mouse sequences, API projections via a raw Unix-socket JSON client. Every screen state was captured with tmux capture-pane -e, converted to styled HTML through a purpose-built ANSI/SGR renderer, and screenshotted with headless Chrome, producing 9 annotated PNGs of the actual rendered terminal. Two criteria are inherently sequential rather than single-frame — the global next_agent and previous_agent wrap cycles — so those are captured as ordered per-step transcripts (06, 07) showing the focused pane after each keypress, which is the faithful artifact for a navigation sequence; a single screenshot could not express the wrap. Attach-local isolation (09) is a single frame containing both clients' sidebars side by side, which is the strongest available proof that disclosure is per-connection and not shared server state. Everything was confined to a throwaway config root and session namespace, so the user's daily-driver herdr session and real agent configuration were never touched (an onboarding prompt offering to install agent integrations was explicitly declined for this reason). All demo state has been torn down: server stopped, tmux sessions killed, /tmp/herdr-sbdemo and the two empty demo-scoped pi session directories removed, target/ build output deleted. git status is clean at 6d6c356; no source or test file was modified this round. The round-1 flaky live_handoff finding is not re-reported — its fix (agent-start retry on agent_pane_busy plus the macOS lsof ptmx counting fix) is present in tests/live_handoff.rs and was not observed to regress.

  • Evidence: Default sidebar renders one Spaces tree with per-workspace agent disclosure (local file: /var/folders/fw/7q2msnnn4d9ft4vd15kl5jp40000gn/T/no-mistakes-evidence/01KYQ2H2SZE21C1PBNBWCXHZ4N/01-spaces-tree.png)
  • Evidence: Agent disclosure toggles independently per workspace (herdr expanded, website collapsed) (local file: /var/folders/fw/7q2msnnn4d9ft4vd15kl5jp40000gn/T/no-mistakes-evidence/01KYQ2H2SZE21C1PBNBWCXHZ4N/02-independent-disclosure.png)
  • Evidence: ui.agent_panel_sort = priority replaces the tree with one flat projection (local file: /var/folders/fw/7q2msnnn4d9ft4vd15kl5jp40000gn/T/no-mistakes-evidence/01KYQ2H2SZE21C1PBNBWCXHZ4N/03-priority-flat-projection.png)
  • Evidence: Workspace picker temporarily restores the Spaces tree without clearing the active projection (local file: /var/folders/fw/7q2msnnn4d9ft4vd15kl5jp40000gn/T/no-mistakes-evidence/01KYQ2H2SZE21C1PBNBWCXHZ4N/04-picker-restores-tree.png)
  • Evidence: prefix+p / prefix+n remain previous-tab / next-tab (keybind overlay + live tab switch) (local file: /var/folders/fw/7q2msnnn4d9ft4vd15kl5jp40000gn/T/no-mistakes-evidence/01KYQ2H2SZE21C1PBNBWCXHZ4N/05-tab-bindings-preserved.png)
Evidence: next_agent/previous_agent cycle all 3 agents in canonical order and wrap, with the tree fully collapsed
# Spaces tree, BOTH workspaces collapsed (no agent row visible in the sidebar)
# next_agent = prefix+y, previous_agent = prefix+u

focused agent at start : pi-review     workspace=w2  pane=w2:p2  
prefix+y  (next #1)   : pi-docs       workspace=w3  pane=w3:p1  
prefix+y  (next #2)   : pi-refactor   workspace=w2  pane=w2:p1  
prefix+y  (next #3)   : pi-review     workspace=w2  pane=w2:p2  
prefix+y  (next #4)   : pi-docs       workspace=w3  pane=w3:p1  
prefix+u  (prev #1)   : pi-review     workspace=w2  pane=w2:p2  
prefix+u  (prev #2)   : pi-refactor   workspace=w2  pane=w2:p1  
prefix+u  (prev #3)   : pi-docs       workspace=w3  pane=w3:p1  
Evidence: Same global agent cycle and wrap with a filtered agent.view projection active
# Active sidebar projection: API view  {"source":"cli:demo","label":"herdr space",
#                                       "filter":{"op":"eq","field":"workspace_id","value":"w2"}}
# The sidebar lists only pi-refactor and pi-review. pi-docs (workspace w3) is filtered OUT of the view.

focused agent at start : pi-docs      workspace=w3  pane=w3:p1  
prefix+y (next #1)    : pi-refactor  workspace=w2  pane=w2:p1  
prefix+y (next #2)    : pi-review    workspace=w2  pane=w2:p2  
prefix+y (next #3)    : pi-docs      workspace=w3  pane=w3:p1  
prefix+y (next #4)    : pi-refactor  workspace=w2  pane=w2:p1  

# next_agent still walks the canonical unfiltered workspace/tab/pane order and wraps,
# including the agent the projection hides.
- Evidence: agent.view.set renders one flat labeled, filtered projection in place of the tree (local file: /var/folders/fw/7q2msnnn4d9ft4vd15kl5jp40000gn/T/no-mistakes-evidence/01KYQ2H2SZE21C1PBNBWCXHZ4N/08-api-filtered-projection.png) - Evidence: Attach-local disclosure: two clients on one server show opposite tree state simultaneously (local file: /var/folders/fw/7q2msnnn4d9ft4vd15kl5jp40000gn/T/no-mistakes-evidence/01KYQ2H2SZE21C1PBNBWCXHZ4N/09-attach-local-disclosure.png) - Evidence: Collapsed sidebar rail preserves the same workspace to agent ownership order (local file: /var/folders/fw/7q2msnnn4d9ft4vd15kl5jp40000gn/T/no-mistakes-evidence/01KYQ2H2SZE21C1PBNBWCXHZ4N/10-collapsed-rail-tree.png) - Evidence: Mobile single-column switcher keeps spaces before tabs before menu with agents nested under their owning workspace (local file: /var/folders/fw/7q2msnnn4d9ft4vd15kl5jp40000gn/T/no-mistakes-evidence/01KYQ2H2SZE21C1PBNBWCXHZ4N/11-mobile-switcher-tree.png)
Evidence: Targeted test run result
ZIG=/opt/homebrew/opt/zig@0.15/bin/zig cargo nextest run --locked --bin herdr -E 'test(/agent_tree|sidebar|agent_panel|next_agent|previous_agent|workspace_picker|agent_disclosure|spaces_tree|agent_projection|switcher_uses|flat_agent/)'
Summary [ 1.204s] 152 tests run: 152 passed, 0 skipped
- Outcome: 🔧 1 issue found → auto-fixed ✅ across 2 runs (54m6s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 6 issues (1 warning, 5 infos)
  • ⚠️ src/app/input/mouse.rs:1139 - Mobile workspace-picker opened by tapping the menu button (src/app/input/mouse.rs:1139) sets mobile_switcher_scroll = 0 and mode = Navigate directly instead of calling begin_workspace_picker_presentation(), and the close path (mouse.rs:1147) sets mode = Terminal without end_workspace_picker_presentation(). The keyboard path (src/app/input/navigate.rs:259) does force the Spaces tree. Consequence: on mobile, when the client is in the flat priority projection or under an agent.view.set API override, tapping the menu opens a picker with no workspace rows and no "+ new workspace" affordance (src/ui/mobile.rs renders both only when sidebar_shows_spaces_tree()), so the user cannot switch or create a workspace at all from touch — while the same picker reached by keyboard works. Also force_spaces_tree is never cleared on the tap-close path, so the desktop/flat projection can stay overridden after the picker closes.
  • ⚠️ src/app/input/mouse.rs:608 - The sidebar now scrolls through one unified row list via workspace_scroll; agent_panel_scroll is forced to 0 every frame (src/ui.rs:246, :251) and is therefore inert. Several call sites still reset only the dead field when the projection changes: the sort toggle (src/app/input/mouse.rs:608), the agent-view API handler (src/app/api/agent_view.rs:90), and src/app/mod.rs:1458; NavigateAction::FocusAgent still calls ensure_agent_panel_entry_visible (src/app/input/navigate.rs:255), which also writes only the inert field. Consequence: switching between the Spaces tree and the flat priority/API projection keeps the previous workspace_scroll, so a user scrolled far down a long tree lands mid-list (or on a short flat list, at a clamped position that hides the top entries) instead of at the top, and the focused agent selected by index is not scrolled into view.
  • ⚠️ src/ui/sidebar.rs:1743 - render_agent_detail — which rendered the desktop sidebar's "no matching agents" empty state for the agent list — was demoted to #[cfg(test)] (src/ui/sidebar.rs:1743) and is no longer called from any render path. The mobile switcher kept its equivalent empty state (src/ui/mobile.rs:514). Consequence: on desktop, when a filter or the priority projection yields zero agent entries, the sidebar renders a blank region with no explanation, so the user cannot tell whether the filter matched nothing or the UI failed.
  • ⚠️ src/app/input/navigate.rs:275 - NavigateAction::PreviousAgent / NextAgent (src/app/input/navigate.rs:275-286) dropped the calls that scrolled the newly focused agent into view, and no replacement pending_workspace_reveal / row reveal is requested for the target agent row. Consequence: with more workspaces/agents than fit the sidebar, cycling agents with next/previous moves focus to a row that stays scrolled off-screen — the sidebar selection appears not to move even though the focused pane changed. Only the active-workspace reveal path (take_pending_workspace_reveal) scrolls, and it is not triggered here.
  • ⚠️ src/ui/sidebar.rs:828 - compute_sidebar_row_areas reads let scroll = app.workspace_scroll; raw (src/ui/sidebar.rs:828) after computing workspace_list_scroll_metrics, without clamping to the metrics' max offset, unlike the render path which normalizes through normalized_workspace_scroll (src/ui.rs:~244). Consequence: hit-testing computed from a stale-but-larger workspace_scroll (row count shrinks between an input event and the next compute_view, e.g. a workspace closes, an agent filter narrows, or a disclosure collapses) maps clicks to rows offset from what was drawn, so a click can activate the wrong workspace/agent. The test intended to cover this, agent_hit_testing_clamps_scroll_after_dynamic_filter_shrink (src/app/input/sidebar.rs), sets a field that is no longer read, so it passes vacuously.
  • ⚠️ src/ui/sidebar.rs:939 - render_sidebar_collapsed (src/ui/sidebar.rs:939) was rebuilt on the unified sidebar_rows list, which now interleaves disclosed agent rows with workspace rows, but the collapsed rail still has no scrolling of its own (src/ui.rs:251 only clamps workspace_scroll to sidebar_rows(app).len()-1). Consequence: with agent disclosure expanded on the first workspaces, later workspace icons are pushed below the visible rail height and become unreachable in collapsed mode; previously the collapsed rail listed only workspaces and could show many more.
  • ⚠️ src/ui/mobile.rs:595 - The mobile switcher renders a disclosure chevron and agent count on workspace rows (src/ui/mobile.rs:595), but mobile_switcher_target_at (src/ui/mobile.rs:133) never returns a disclosure target and no mobile key binding maps to toggle_workspace_agent_disclosure. Consequence: the chevron is an affordance the user cannot activate — tapping it selects/activates the workspace instead of expanding its agents, so the per-workspace agent disclosure is desktop-only despite being advertised on mobile.
  • ℹ️ src/app/input/sidebar.rs:102 - The Agents-panel scrollbar is gone from render, but its input surface survives: agent_panel_scrollbar_target_at (src/app/input/sidebar.rs:102), agent_panel_offset_for_drag_row (:126) and set_agent_panel_offset_from_bottom (:142), plus the drag handling in src/app/input/mouse.rs:614/623/747/749, all still route drags into the inert agent_panel_scroll. Consequence: dead code that can silently swallow drag events in the sidebar region and will drift out of sync with the row model; a future reader may believe agent-panel scrolling still exists.
  • ℹ️ src/app/input/mouse.rs:437 - The sidebar no longer has two sections: expanded_sidebar_sections returns (content, content) and sidebar_section_divider_rect returns Rect::default(), yet the section-split drag machinery is still wired in src/app/input/mouse.rs:437. Consequence: a divider drag can be initiated against a zero-sized rect and mutate split state nothing reads, leaving dead state that complicates the row-model invariants.
  • ℹ️ src/ui/sidebar.rs:1399 - render_workspace_list contains a duplicated header if/else branch (src/ui/sidebar.rs:1399-1420) whose arms render the same header content. Consequence: divergent future edits to one arm only, producing header inconsistencies between the two projections.

🔧 Fix: unify sidebar scroll and fix reveal, collapsed rail, mobile disclosure
6 issues (1 warning, 5 infos) still open:

  • ⚠️ src/app/input/mouse.rs:1124 - The mobile picker now forces the Spaces tree on open (begin_workspace_picker_presentation() at src/app/input/mouse.rs:1109) and clears it only in close_workspace_picker() (:1165). Three exit paths bypass that helper: MobileSwitcherTarget::NewWorkspace (:1124) returns MouseAction::NewWorkspace without closing, NewTab with prompt_new_tab_name (:1132) opens a dialog without closing, and Menu(action_idx) (:1153) runs a global menu action. begin_tui_workspace_create (src/app/creation.rs:119) then sets mode = Mode::Terminal directly, never routing through leave_navigate_mode, which is the only other caller of end_workspace_picker_presentation (src/app/input/navigate.rs:1787). Concrete sequence: mobile client in the flat priority projection (or under an agent.view.set override) → tap the menu → tap "+ new workspace" → the workspace is created and the picker closes, but sidebar_presentation.force_spaces_tree stays true, so sidebar_shows_spaces_tree() keeps returning true and that client's chosen flat/API projection is silently overridden until it opens and properly closes the picker again. The invariant "force_spaces_tree is scoped to an open picker" should be enforced at the shared boundary rather than at each tap site — e.g. clear it in reconcile_sidebar_presentation/compute_view whenever mode != Mode::Navigate, which also covers any future exit path.
  • ℹ️ src/app/actions.rs:1535 - ensure_agent_row_visible delegates the collapsed case to ensure_workspace_visible(ws_idx) (src/app/actions.rs:1535), but that function returns early when self.sidebar_collapsed (src/app/actions.rs:1179-1181), so the collapsed branch is a no-op. Cross-workspace focus still scrolls because compute_view_internal's collapsed branch consumes take_pending_workspace_reveal (src/ui.rs:248-256), but cycling next/previous agent within the active workspace leaves active unchanged, so no reveal fires and the newly focused agent's dot stays below the fold of the now-scrollable rail. Since collapsed_sidebar_row_scroll and sidebar_rows already give the collapsed rail a row index space, the collapsed branch can scroll to the agent row directly instead of returning early.
  • ℹ️ src/app/input/navigate.rs:255 - In FocusAgent, PreviousAgent and NextAgent (src/app/input/navigate.rs:255, :278, :286) ensure_agent_row_visible runs before leave_navigate_mode, which is what calls end_workspace_picker_presentation(). When the workspace picker is open (force_spaces_tree = true) on a client using the flat projection, the target row index and the resulting workspace_scroll are computed against the tree row list, then the projection reverts to flat one statement later, where the same offset points at an unrelated agent. Moving the reveal after leave_navigate_mode makes it resolve in the projection that will actually be rendered.
  • ℹ️ src/ui/mobile.rs:626 - mobile_workspace_disclosure_columns reserves 1 + digits(agent_count) columns at the right edge, but the workspace label is truncated independently to content.width - 5 (or - 8 when indented) at src/ui/mobile.rs:615-618, i.e. up to content.width - 2 columns of title. With a two-digit agent count (control width 3) and a label at full truncation width, filler saturates to 0 (:630) and the chevron renders one column right of the hit range and overflows the line by one cell, so a tap on columns.start lands on the label but is routed to WorkspaceDisclosure, and the count's last digit is clipped. Deriving the label truncation width from the same reserved control width keeps render and hit-testing aligned.
  • ℹ️ src/app/input/sidebar.rs:246 - Three behaviors introduced by this fix commit have no test: the collapsed-rail scroll offset now added to hit-testing (collapsed_workspace_at_row src/app/input/sidebar.rs:246 and collapsed_agent_detail_target_at :270 — a mismatch with render_sidebar_collapsed's skip(scroll) would silently misroute clicks), the new MobileSwitcherTarget::WorkspaceDisclosure hit column (src/ui/mobile.rs:182), and the picker presentation lifecycle (begin/end pairing on the mobile tap paths). All three are pure AppState + rect computations and are testable without PTYs, matching the project's AppState::test_new() convention.
  • ℹ️ src/app/state.rs:1528 - With set_sidebar_section_split and the divider drag removed, sidebar_section_split has no writer left and expanded_sidebar_sections ignores it, yet it is still threaded through workspace_list_rect, normalized_workspace_scroll, compute_sidebar_row_areas, the headless server, and the persisted snapshot (src/persist/snapshot.rs:26, :184, :262). It is harmless as-is; noting it so the follow-up cleanup that drops the parameter also handles the snapshot's Option<f32> backward compatibility rather than removing the field ad hoc.
🔧 **Test** - 1 issue found → auto-fixed ✅
  • ⚠️ tests/live_handoff.rs:1419 - Flaky integration test fixed (test-harness only, pre-existing — not introduced by this branch): live_handoff_keeps_agent_started_pane_after_agent_exits called agent.start immediately after workspace.create, before the freshly spawned shell became the pane's foreground process, so the server rejected the request with agent_pane_busy (src/app/agents.rs:191). It failed 2 of 5 isolated runs. Added a bounded start_agent_when_shell_is_ready retry helper that re-issues the request while the error code is agent_pane_busy, up to a 10s deadline; 6/6 isolated runs and the full 20-test live_handoff binary now pass. The feature range 73d9200..688db03 touches no agent-start eligibility code and does not modify this test function.
  • live product walkthrough: real herdr binary in a 150x42 PTY, 5 detected agents across 3 workspaces / 4 tabs / a split pane / a real linked git worktree, driven by real SGR mouse bytes, real keybinds, and the real unix-socket JSON API
  • default Spaces tree renders workspace roll-up rows with agent children nested under their owning workspace (01-spaces-tree.png)
  • per-workspace agent-disclosure chevron click collapses only the clicked workspace's agents, leaving sibling workspaces disclosed (02-disclosure-collapsed.png)
  • ui.agent_panel_sort = "priority" replaces the tree with one flat attention-ordered Agent list (03-priority-flat.png)
  • agent.view.set transient API projection replaces the tree with a labeled flat filtered list (04-agent-view-flat.png)
  • workspace picker (prefix+w) temporarily restores the Spaces tree over an active flat projection and returns to the projection on exit (05-picker-restores-tree.png)
  • global canonical next/previous agent traversal: 5 forward steps wrap to start and 3 backward steps wrap to end in workspace->tab->pane order while a flat 'needs attention' projection hides one of the agents (06-global-agent-cycle.txt)
  • tab navigation compatibility: default prefix+n / prefix+p still move to next/previous tab and wrap (07-tab-navigation-preserved.txt)
  • collapsed sidebar rail preserves workspace-owned ordering and disclosure state (08-collapsed-rail.png)
  • mobile layout switcher preserves workspace ownership and per-space disclosure chevrons mirroring desktop state (09-mobile-tree.png)
  • cargo nextest -E 'test(/sidebar/) or test(/agent_tree/) or test(/agent_panel/) or test(/workspace_picker/) or test(/agent_view/) or test(/next_agent/) or test(/previous_agent/) or test(/disclosure/) or test(/agent_card/)' -> 158 passed
  • cargo nextest -E 'test(/presentation/) or test(/handoff/) or test(/snapshot/) or test(/navigate/) or test(/mobile/) or test(/reveal/) or test(/legacy_sidebar/) or test(/scroll/)' -> 308 passed
  • cargo nextest -E 'binary(live_handoff)' -> 20 passed
  • flake characterization + fix verification: live_handoff_keeps_agent_started_pane_after_agent_exits run 5x before (2 failures) and 6x after (0 failures)

🔧 Fix: test: retry agent start until pane shell ready
✅ Re-checked - no issues remain.

  • ZIG=/opt/homebrew/opt/zig@0.15/bin/zig cargo nextest run --locked --bin herdr -E 'test(/agent_tree|sidebar|agent_panel|next_agent|previous_agent|workspace_picker|agent_disclosure|spaces_tree|agent_projection|switcher_uses|flat_agent/)' — 152 passed, 0 failed
  • Live TUI: default sidebar renders a single Spaces tree; per-workspace agent chevrons toggle independently (SGR mouse clicks on workspace_agent_chevron_rect)
  • Live TUI: attach-local disclosure — two App clients attached to one server show independent expand/collapse state simultaneously
  • Live TUI: ui.agent_panel_sort = "priority" replaces the tree with one flat priority projection
  • Live TUI: agent.view.set (source cli:demo, filtered) replaces the tree with one flat labeled projection
  • Live TUI: workspace picker temporarily shows the Spaces tree from both the priority projection and an active agent.view override, and restores the projection on exit
  • Live TUI: previous_agent/next_agent traverse all 3 agents in workspace/tab/pane order and wrap at both ends with the tree fully collapsed and with a filtered API view active
  • Live TUI: prefix+p / prefix+n remain previous-tab / next-tab (verified against a 2-tab workspace and the keybind overlay)
  • Live TUI: collapsed sidebar rail preserves the same workspace→agent ownership order
  • Live TUI: mobile layout at 58x34 (below ui.mobile_width_threshold = 64) — switcher lists spaces, then tabs, then menu, with agents nested under their owning workspace and chevrons only on workspaces that have agents
⚠️ **Document** - 1 warning
  • ⚠️ docs/next/website/src/content/docs/integrations.mdx - python3 scripts/docs_translation_parity.py --docs-root docs/next/website/src/content/docs fails for ja/zh-cn integrations.mdx (English h2=20, translated h2=19). Verified this failure already exists at base commit 1491b7d, so it is not caused by this change and fixing it would mean writing an unrelated agent-integration section translation. Left untouched under scope discipline; it will keep blocking just release-docs-check until translated separately.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@matthias-scale
matthias-scale changed the base branch from master to fork/pr-base July 29, 2026 16:26
@matthias-scale
matthias-scale changed the base branch from fork/pr-base to master July 29, 2026 16:37
@matthias-scale
matthias-scale changed the base branch from master to fork/pr-base July 29, 2026 16:37
@matthias-scale
matthias-scale changed the base branch from fork/pr-base to master July 29, 2026 16:40
@matthias-scale
matthias-scale changed the base branch from master to fork/pr-base July 29, 2026 16:40
matthias-scale added a commit that referenced this pull request Jul 30, 2026
* feat: add native full-width top status bar (tmux parity)

Compile a powerline-style status row into the Herdr TUI above the
sidebar/tabs. Collect host, user, network, memory, CPU, and battery
metrics in Rust (no tmux/powerline shell scripts), map segment colors
to the active theme, and drop tail segments responsively on narrow
widths.

Includes layout wiring, platform collectors, and characterization
tests for full-width placement plus fixture identity/metrics render.

* feat: modernize native status metrics

* fix: harden native status bar context

* fix: reap battery sampler child on wait errors

* no-mistakes(review): gate status row behind config and harden metric sampling

* fix: complete native status bar integration

* fix: harden macos status metrics

* fix: keep status network metrics coherent

* fix: report only active status vpn tunnels

* no-mistakes(review): Remove macOS sampler process launches

* no-mistakes(document): Document status sampling and responsive metric contracts

* no-mistakes: apply CI fixes

* fix: restore native macos status sources

* no-mistakes(review): fix status sampling and scoped refresh boundaries

* no-mistakes(document): format changed Rust sources

* no-mistakes(review): fix status focus and platform sampling fallbacks

* no-mistakes(review): avoid process cwd I/O during focus transitions

* no-mistakes(review): preserve focus context across disabled status transitions

* no-mistakes(review): detect IPv6-only VPN interfaces across Unix platforms

* no-mistakes(review): fix VPN classification and status reload focus

* no-mistakes(review): synchronize focused status context at pre-render boundaries

* no-mistakes(review): preserve runtime status context across unchanged renders

* no-mistakes(review): stabilize focused status context ownership

* no-mistakes(review): invalidate stale branch context when re-enabling status

* no-mistakes(document): Verify status-row documentation and lint

* refactor: simplify and right-align native status bar

* chore: refresh status bar pull request checks

* feat(ui): simplify and right-align native status row

* no-mistakes(review): fix PR #1 compatibility seams

* no-mistakes(document): refresh status-row documentation comments

* test(ui): characterize status row with sidebar

* no-mistakes(review): Decouple status-sidebar characterization from frame digest

* no-mistakes(document): Format status sidebar characterization

* fix(ui): remove Herdr version from status row

---------

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: matthias schedel <matze.schedel@gmail.com>
@matthias-scale

Copy link
Copy Markdown
Owner Author

superseded: sidebar tree behavior landed in slices 1-3 on fork/pr-base (PRs #39/#40/#41); closing per consolidation survey

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants