feat(ui): focus the sidebar around a spaces tree with per-workspace agent disclosure - #1
Closed
matthias-scale wants to merge 11 commits into
Closed
feat(ui): focus the sidebar around a spaces tree with per-workspace agent disclosure#1matthias-scale wants to merge 11 commits into
matthias-scale wants to merge 11 commits into
Conversation
…ail, mobile disclosure
This was referenced Jul 29, 2026
matthias-scale
pushed a commit
that referenced
this pull request
Jul 30, 2026
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>
8 tasks
Owner
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
ui.agent_panel_sort = "priority"andagent.view.setswap the tree for one flat projection at a time (the workspace picker temporarily restores the tree without clearing the projection).previous_agent/next_agentnow traverse the canonical unfiltered workspace→tab→pane order and wrap at both ends regardless of disclosure, filtering, or collapsed mode;prefix+p/prefix+nremain previous/next tab.SidebarPresentationStatethat 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_scrollonly;agent_panel_scrolland the section-divider drag are retired), and the collapsed rail plus the mobile switcher render the same ownership and disclosure tree. Docs underdocs/next(en/ja/zh-cn concepts, configuration, socket-api, changelog) describe the new hierarchy and navigation guarantees.render_signalpath 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-existinglive_handoffflake by retryingagent.startuntil the pane shell is ready; the Document gate's translation-parity warning onintegrations.mdxpredates this branch and is left untouched.Risk Assessment
compute_sidebar_row_areasnow clamps through the sameworkspace_list_bottom_startbasis asnormalized_workspace_scroll,agent_panel_scrolland 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_treenever 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./var/folders/fw/7q2msnnn4d9ft4vd15kl5jp40000gn/T/no-mistakes-evidence/01KYQ2H2SZE21C1PBNBWCXHZ4N/01-spaces-tree.png)/var/folders/fw/7q2msnnn4d9ft4vd15kl5jp40000gn/T/no-mistakes-evidence/01KYQ2H2SZE21C1PBNBWCXHZ4N/02-independent-disclosure.png)/var/folders/fw/7q2msnnn4d9ft4vd15kl5jp40000gn/T/no-mistakes-evidence/01KYQ2H2SZE21C1PBNBWCXHZ4N/03-priority-flat-projection.png)/var/folders/fw/7q2msnnn4d9ft4vd15kl5jp40000gn/T/no-mistakes-evidence/01KYQ2H2SZE21C1PBNBWCXHZ4N/04-picker-restores-tree.png)/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
Evidence: Same global agent cycle and wrap with a filtered agent.view projection active
/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
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
src/app/input/mouse.rs:1139- Mobile workspace-picker opened by tapping the menu button (src/app/input/mouse.rs:1139) setsmobile_switcher_scroll = 0andmode = Navigatedirectly instead of callingbegin_workspace_picker_presentation(), and the close path (mouse.rs:1147) setsmode = Terminalwithoutend_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 anagent.view.setAPI override, tapping the menu opens a picker with no workspace rows and no "+ new workspace" affordance (src/ui/mobile.rs renders both only whensidebar_shows_spaces_tree()), so the user cannot switch or create a workspace at all from touch — while the same picker reached by keyboard works. Alsoforce_spaces_treeis 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 viaworkspace_scroll;agent_panel_scrollis 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::FocusAgentstill callsensure_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 previousworkspace_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 replacementpending_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_areasreadslet scroll = app.workspace_scroll;raw (src/ui/sidebar.rs:828) after computingworkspace_list_scroll_metrics, without clamping to the metrics' max offset, unlike the render path which normalizes throughnormalized_workspace_scroll(src/ui.rs:~244). Consequence: hit-testing computed from a stale-but-largerworkspace_scroll(row count shrinks between an input event and the nextcompute_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 unifiedsidebar_rowslist, 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 clampsworkspace_scrolltosidebar_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), butmobile_switcher_target_at(src/ui/mobile.rs:133) never returns a disclosure target and no mobile key binding maps totoggle_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) andset_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 inertagent_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_sectionsreturns(content, content)andsidebar_section_divider_rectreturnsRect::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_listcontains 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 inclose_workspace_picker()(:1165). Three exit paths bypass that helper:MobileSwitcherTarget::NewWorkspace(:1124) returnsMouseAction::NewWorkspacewithout closing,NewTabwithprompt_new_tab_name(:1132) opens a dialog without closing, andMenu(action_idx)(:1153) runs a global menu action.begin_tui_workspace_create(src/app/creation.rs:119) then setsmode = Mode::Terminaldirectly, never routing throughleave_navigate_mode, which is the only other caller ofend_workspace_picker_presentation(src/app/input/navigate.rs:1787). Concrete sequence: mobile client in the flat priority projection (or under anagent.view.setoverride) → tap the menu → tap "+ new workspace" → the workspace is created and the picker closes, butsidebar_presentation.force_spaces_treestaystrue, sosidebar_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_treeis scoped to an open picker" should be enforced at the shared boundary rather than at each tap site — e.g. clear it inreconcile_sidebar_presentation/compute_viewwhenevermode != Mode::Navigate, which also covers any future exit path.src/app/actions.rs:1535-ensure_agent_row_visibledelegates the collapsed case toensure_workspace_visible(ws_idx)(src/app/actions.rs:1535), but that function returns early whenself.sidebar_collapsed(src/app/actions.rs:1179-1181), so the collapsed branch is a no-op. Cross-workspace focus still scrolls becausecompute_view_internal's collapsed branch consumestake_pending_workspace_reveal(src/ui.rs:248-256), but cycling next/previous agent within the active workspace leavesactiveunchanged, so no reveal fires and the newly focused agent's dot stays below the fold of the now-scrollable rail. Sincecollapsed_sidebar_row_scrollandsidebar_rowsalready 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- InFocusAgent,PreviousAgentandNextAgent(src/app/input/navigate.rs:255, :278, :286)ensure_agent_row_visibleruns beforeleave_navigate_mode, which is what callsend_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 resultingworkspace_scrollare 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 afterleave_navigate_modemakes it resolve in the projection that will actually be rendered.src/ui/mobile.rs:626-mobile_workspace_disclosure_columnsreserves1 + digits(agent_count)columns at the right edge, but the workspace label is truncated independently tocontent.width - 5(or- 8when indented) at src/ui/mobile.rs:615-618, i.e. up tocontent.width - 2columns of title. With a two-digit agent count (control width 3) and a label at full truncation width,fillersaturates to 0 (:630) and the chevron renders one column right of the hit range and overflows the line by one cell, so a tap oncolumns.startlands on the label but is routed toWorkspaceDisclosure, 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_rowsrc/app/input/sidebar.rs:246 andcollapsed_agent_detail_target_at:270 — a mismatch withrender_sidebar_collapsed'sskip(scroll)would silently misroute clicks), the newMobileSwitcherTarget::WorkspaceDisclosurehit column (src/ui/mobile.rs:182), and the picker presentation lifecycle (begin/endpairing on the mobile tap paths). All three are pureAppState+ rect computations and are testable without PTYs, matching the project'sAppState::test_new()convention.src/app/state.rs:1528- Withset_sidebar_section_splitand the divider drag removed,sidebar_section_splithas no writer left andexpanded_sidebar_sectionsignores it, yet it is still threaded throughworkspace_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'sOption<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_exitscalledagent.startimmediately afterworkspace.create, before the freshly spawned shell became the pane's foreground process, so the server rejected the request withagent_pane_busy(src/app/agents.rs:191). It failed 2 of 5 isolated runs. Added a boundedstart_agent_when_shell_is_readyretry helper that re-issues the request while the error code isagent_pane_busy, up to a 10s deadline; 6/6 isolated runs and the full 20-testlive_handoffbinary now pass. The feature range73d9200..688db03touches 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 APIdefault 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 passedcargo 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 passedcargo nextest -E 'binary(live_handoff)' -> 20 passedflake 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 failedLive 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 simultaneouslyLive TUI: ui.agent_panel_sort = "priority" replaces the tree with one flat priority projectionLive TUI: agent.view.set (source cli:demo, filtered) replaces the tree with one flat labeled projectionLive TUI: workspace picker temporarily shows the Spaces tree from both the priority projection and an active agent.view override, and restores the projection on exitLive 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 activeLive 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 orderLive 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 agentsdocs/next/website/src/content/docs/integrations.mdx-python3 scripts/docs_translation_parity.py --docs-root docs/next/website/src/content/docsfails 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 blockingjust release-docs-checkuntil translated separately.✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.