feat(tui): per-section visibility for the details accordion - #14968
Conversation
Adds optional per-section overrides on top of the existing global
details_mode (hidden | collapsed | expanded). Lets users keep the
accordion collapsed by default while auto-expanding tools, or hide the
activity panel entirely without touching thinking/tools/subagents.
Config (~/.hermes/config.yaml):
display:
details_mode: collapsed
sections:
thinking: expanded
tools: expanded
activity: hidden
Slash command:
/details show current global + overrides
/details [hidden|collapsed|expanded] set global mode (existing)
/details <section> <mode|reset> per-section override (new)
/details <section> reset clear override
Sections: thinking, tools, subagents, activity.
Implementation:
- ui-tui/src/types.ts SectionName + SectionVisibility
- ui-tui/src/domain/details.ts parseSectionMode / resolveSections /
sectionMode + SECTION_NAMES
- ui-tui/src/app/uiStore.ts +
app/interfaces.ts +
app/useConfigSync.ts sections threaded into UiState
- ui-tui/src/components/
thinking.tsx ToolTrail consults per-section mode for
hidden/expanded behaviour; expandAll
skips hidden sections; floating-alert
fallback respects activity:hidden
- ui-tui/src/components/
messageLine.tsx + appLayout.tsx pass sections through render tree
- ui-tui/src/app/slash/
commands/core.ts /details <section> <mode|reset> syntax
- tui_gateway/server.py config.set details_mode.<section>
writes to display.sections.<section>
(empty value clears the override)
- website/docs/user-guide/tui.md documented
Tests: 14 new (4 domain, 4 useConfigSync, 3 slash, 3 gateway).
Total: 269/269 vitest, all gateway tests pass.
The activity panel (gateway hints, terminal-parity nudges, background notifications) is noise for the typical day-to-day user, who only cares about thinking + tools + streamed content. Make `hidden` the built-in default for that section so users land on the quiet mode out of the box. Tool failures still render inline on the failing tool row, so this default suppresses the noise feed without losing the signal. Opt back in with `display.sections.activity: collapsed` (chevron) or `expanded` (always open) in `~/.hermes/config.yaml`, or live with `/details activity collapsed`. Implementation: SECTION_DEFAULTS in domain/details.ts, applied as the fallback in `sectionMode()` between the explicit override and the global details_mode. Existing `display.sections.activity` overrides take precedence — no migration needed for users who already set it.
- domain/details: extract `norm()`, fold parseDetailsMode + resolveSections into terser functional form, reject array values for resolveSections - slash /details: destructure tokens, factor reset/mode into one dispatch, drop DETAIL_MODES set + DetailsMode/SectionName imports (parseDetailsMode + isSectionName narrow + return), centralize usage strings - ToolTrail: collapse 4 separate xxxSection vars into one memoized `visible` map; effect deps stabilize on the memo identity instead of 4 primitives
There was a problem hiding this comment.
Pull request overview
Adds per-section visibility overrides for the TUI “details” accordion (thinking/tools/subagents/activity), with the activity panel now hidden by default, plus config + slash-command plumbing end-to-end (TUI ↔ gateway ↔ docs/tests).
Changes:
- Introduces
display.sectionsper-section overrides resolved via a sharedsectionMode()helper (activity defaults to hidden). - Threads
sectionsthrough UI state/config sync and down into transcript rendering (ToolTrail). - Extends
/detailsto support per-section overrides and persists them viaconfig.set details_mode.<section>(gateway writes todisplay.sections.<section>).
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| website/docs/user-guide/tui.md | Documents new defaults, per-section overrides, and /details syntax. |
| ui-tui/src/types.ts | Adds SectionName and SectionVisibility types for per-section overrides. |
| ui-tui/src/gatewayTypes.ts | Extends display config shape to include sections. |
| ui-tui/src/domain/details.ts | Adds section parsing, validation, defaults, and sectionMode() resolver. |
| ui-tui/src/components/thinking.tsx | Applies per-section modes to ToolTrail panels; hides activity by default; adjusts expand-all behavior. |
| ui-tui/src/components/messageLine.tsx | Passes sections through to ToolTrail for per-message rendering. |
| ui-tui/src/components/appLayout.tsx | Threads sections into streaming/progress and transcript rendering. |
| ui-tui/src/app/useConfigSync.ts | Parses display.sections from gateway config into UI state. |
| ui-tui/src/app/uiStore.ts | Initializes sections in the default UI store state. |
| ui-tui/src/app/slash/commands/core.ts | Extends /details parsing and persists per-section overrides via config.set. |
| ui-tui/src/app/interfaces.ts | Adds sections to UiState. |
| ui-tui/src/tests/useConfigSync.test.ts | Adds tests for parsing/dropping invalid display.sections. |
| ui-tui/src/tests/details.test.ts | Adds unit tests for section parsing/validation and default resolution. |
| ui-tui/src/tests/createSlashHandler.test.ts | Adds tests for /details <section> … set/reset/usage behavior. |
| tui_gateway/server.py | Implements config.set details_mode.<section> → display.sections.<section> with validation. |
| tests/test_tui_gateway_server.py | Adds pytest coverage for per-section config set/clear/validation. |
Comments suppressed due to low confidence (1)
ui-tui/src/components/appLayout.tsx:67
progress.showProgressAreacan be true in modes where<ToolTrail …/>intentionally returnsnull(e.g., globaldetailsMode: hiddenwithactivityresolved to hidden). In that case this wrapper<Box>will still render and may introduce a blank gap in the streaming area. Consider suppressing the wrapper when the effective activity section is hidden (or otherwise ensuring showProgressArea is false in that case).
{progress.showProgressArea && (
<Box flexDirection="column" marginBottom={progress.showStreamingArea ? 1 : 0}>
<ToolTrail
activity={progress.activity}
busy={busy}
detailsMode={detailsMode}
outcome={progress.outcome}
reasoning={progress.reasoning}
reasoningActive={progress.reasoningActive}
reasoningStreaming={progress.reasoningStreaming}
reasoningTokens={progress.reasoningTokens}
sections={sections}
subagents={progress.subagents}
t={t}
tools={progress.tools}
toolTokens={progress.toolTokens}
trail={progress.turnTrail}
/>
</Box>
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Copilot review on #14968 caught that the early returns gated on the global `detailsMode === 'hidden'` short-circuited every render path before sectionMode() got a chance to apply per-section overrides — so `details_mode: hidden` + `sections.tools: expanded` was silently a no-op. Three call sites had the same bug shape; all now key off the resolved section modes: - ToolTrail: replace the `detailsMode === 'hidden'` early return with an `allHidden = every section resolved to hidden` check. When that's true, fall back to the floating-alert backstop (errors/warnings) so quiet-mode users aren't blind to ambient failures, and update the comment block to match the actual condition. - messageLine.tsx: drop the same `detailsMode === 'hidden'` pre-check on `msg.kind === 'trail'`; only skip rendering the wrapper when every section resolves to hidden (`SECTION_NAMES.some(...) !== 'hidden'`). - useMainApp.ts: rebuild `showProgressArea` around `anyPanelVisible` instead of branching on the global mode. This also fixes the suppressed Copilot concern about an empty wrapper Box rendering above the streaming area when ToolTrail returns null. Regression test in details.test.ts pins the override-escapes-hidden behaviour for tools/thinking/activity. 271/271 vitest, lints clean.
Extends SECTION_DEFAULTS so the out-of-the-box TUI shows the turn as a live transcript (reasoning + tool calls streaming inline) instead of a wall of `▸` chevrons the user has to click every turn. Final default matrix: - thinking: expanded - tools: expanded - activity: hidden (unchanged from the previous commit) - subagents: falls through to details_mode (collapsed by default) Everything explicit in `display.sections` still wins, so anyone who already pinned an override keeps their layout. One-line revert is `display.sections.<name>: collapsed`.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
ui-tui/src/components/messageLine.tsx:113
showDetails(computed above this block) currently short-circuits ondetailsMode !== 'hidden', so when the global mode ishiddenthisToolTrailnever mounts for normal assistant messages—even if per-section overrides (or built-in section defaults) would resolvethinking/toolstoexpanded.
To make per-section visibility truly override global hidden (as /details <section> … and sectionMode() imply), compute showDetails using sectionMode() for the sections that actually have content (e.g., show when (thinking && thinkingMode!==hidden) or (msg.tools?.length && toolsMode!==hidden)), rather than checking only the global mode.
{showDetails && (
<Box flexDirection="column" marginBottom={1}>
<ToolTrail
detailsMode={detailsMode}
reasoning={thinking}
reasoningTokens={msg.thinkingTokens}
sections={sections}
t={t}
toolTokens={msg.toolTokens}
trail={msg.tools}
/>
</Box>
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Round-2 Copilot review on #14968 caught two leftover spots that didn't fully respect per-section overrides: - messageLine.tsx (trail branch): the previous fix gated on `SECTION_NAMES.some(...)`, which stayed true whenever any section was visible. With `thinking: 'expanded'` as the new built-in default, that meant `display.sections.tools: hidden` left an empty wrapper Box alive for trail messages. Now gates on the actual content-bearing sections for a trail message — `tools` OR `activity` — so a tools-hidden config drops the wrapper cleanly. - messageLine.tsx (showDetails): still keyed off the global `detailsMode !== 'hidden'`, so per-section overrides like `sections.thinking: expanded` couldn't escape global hidden for assistant messages with reasoning + tool metadata. Recomputed via resolved per-section modes (`thinkingMode`/`toolsMode`). - types.ts: rewrote the SectionVisibility doc comment to reflect the actual resolution order (explicit override → SECTION_DEFAULTS → global), so the docstring stops claiming "missing keys fall back to the global mode" when SECTION_DEFAULTS now layers in between. All three lookups (thinking/tools/activity) are computed once at the top of MessageLine and shared by every branch.
Recovers the manual click on the details accordion: with NousResearch#14968's new SECTION_DEFAULTS (thinking/tools start `expanded`), every panel render was OR-ing the local open toggle against `visible.X === 'expanded'`. That pinned `open=true` for the default-expanded sections, so clicking the chevron flipped the local state but the panel never collapsed. Local toggle is now the sole source of truth at render time; the useState init still seeds from the resolved visibility (so first paint is correct) and the existing useEffect still re-syncs when the user mutates visibility at runtime via `/details`. Same OR-lock cleared inside SubagentAccordion (`showChildren || openX`) — pre-existing but the same shape, so expand-all on the spawn tree no longer makes inner sections un-collapsible either.
Copilot review on NousResearch#14968 caught that the early returns gated on the global `detailsMode === 'hidden'` short-circuited every render path before sectionMode() got a chance to apply per-section overrides — so `details_mode: hidden` + `sections.tools: expanded` was silently a no-op. Three call sites had the same bug shape; all now key off the resolved section modes: - ToolTrail: replace the `detailsMode === 'hidden'` early return with an `allHidden = every section resolved to hidden` check. When that's true, fall back to the floating-alert backstop (errors/warnings) so quiet-mode users aren't blind to ambient failures, and update the comment block to match the actual condition. - messageLine.tsx: drop the same `detailsMode === 'hidden'` pre-check on `msg.kind === 'trail'`; only skip rendering the wrapper when every section resolves to hidden (`SECTION_NAMES.some(...) !== 'hidden'`). - useMainApp.ts: rebuild `showProgressArea` around `anyPanelVisible` instead of branching on the global mode. This also fixes the suppressed Copilot concern about an empty wrapper Box rendering above the streaming area when ToolTrail returns null. Regression test in details.test.ts pins the override-escapes-hidden behaviour for tools/thinking/activity. 271/271 vitest, lints clean.
Round-2 Copilot review on NousResearch#14968 caught two leftover spots that didn't fully respect per-section overrides: - messageLine.tsx (trail branch): the previous fix gated on `SECTION_NAMES.some(...)`, which stayed true whenever any section was visible. With `thinking: 'expanded'` as the new built-in default, that meant `display.sections.tools: hidden` left an empty wrapper Box alive for trail messages. Now gates on the actual content-bearing sections for a trail message — `tools` OR `activity` — so a tools-hidden config drops the wrapper cleanly. - messageLine.tsx (showDetails): still keyed off the global `detailsMode !== 'hidden'`, so per-section overrides like `sections.thinking: expanded` couldn't escape global hidden for assistant messages with reasoning + tool metadata. Recomputed via resolved per-section modes (`thinkingMode`/`toolsMode`). - types.ts: rewrote the SectionVisibility doc comment to reflect the actual resolution order (explicit override → SECTION_DEFAULTS → global), so the docstring stops claiming "missing keys fall back to the global mode" when SECTION_DEFAULTS now layers in between. All three lookups (thinking/tools/activity) are computed once at the top of MessageLine and shared by every branch.
Copilot review on NousResearch#14968 caught that the early returns gated on the global `detailsMode === 'hidden'` short-circuited every render path before sectionMode() got a chance to apply per-section overrides — so `details_mode: hidden` + `sections.tools: expanded` was silently a no-op. Three call sites had the same bug shape; all now key off the resolved section modes: - ToolTrail: replace the `detailsMode === 'hidden'` early return with an `allHidden = every section resolved to hidden` check. When that's true, fall back to the floating-alert backstop (errors/warnings) so quiet-mode users aren't blind to ambient failures, and update the comment block to match the actual condition. - messageLine.tsx: drop the same `detailsMode === 'hidden'` pre-check on `msg.kind === 'trail'`; only skip rendering the wrapper when every section resolves to hidden (`SECTION_NAMES.some(...) !== 'hidden'`). - useMainApp.ts: rebuild `showProgressArea` around `anyPanelVisible` instead of branching on the global mode. This also fixes the suppressed Copilot concern about an empty wrapper Box rendering above the streaming area when ToolTrail returns null. Regression test in details.test.ts pins the override-escapes-hidden behaviour for tools/thinking/activity. 271/271 vitest, lints clean.
Round-2 Copilot review on NousResearch#14968 caught two leftover spots that didn't fully respect per-section overrides: - messageLine.tsx (trail branch): the previous fix gated on `SECTION_NAMES.some(...)`, which stayed true whenever any section was visible. With `thinking: 'expanded'` as the new built-in default, that meant `display.sections.tools: hidden` left an empty wrapper Box alive for trail messages. Now gates on the actual content-bearing sections for a trail message — `tools` OR `activity` — so a tools-hidden config drops the wrapper cleanly. - messageLine.tsx (showDetails): still keyed off the global `detailsMode !== 'hidden'`, so per-section overrides like `sections.thinking: expanded` couldn't escape global hidden for assistant messages with reasoning + tool metadata. Recomputed via resolved per-section modes (`thinkingMode`/`toolsMode`). - types.ts: rewrote the SectionVisibility doc comment to reflect the actual resolution order (explicit override → SECTION_DEFAULTS → global), so the docstring stops claiming "missing keys fall back to the global mode" when SECTION_DEFAULTS now layers in between. All three lookups (thinking/tools/activity) are computed once at the top of MessageLine and shared by every branch.
Recovers the manual click on the details accordion: with NousResearch#14968's new SECTION_DEFAULTS (thinking/tools start `expanded`), every panel render was OR-ing the local open toggle against `visible.X === 'expanded'`. That pinned `open=true` for the default-expanded sections, so clicking the chevron flipped the local state but the panel never collapsed. Local toggle is now the sole source of truth at render time; the useState init still seeds from the resolved visibility (so first paint is correct) and the existing useEffect still re-syncs when the user mutates visibility at runtime via `/details`. Same OR-lock cleared inside SubagentAccordion (`showChildren || openX`) — pre-existing but the same shape, so expand-all on the spawn tree no longer makes inner sections un-collapsible either.
Copilot review on NousResearch#14968 caught that the early returns gated on the global `detailsMode === 'hidden'` short-circuited every render path before sectionMode() got a chance to apply per-section overrides — so `details_mode: hidden` + `sections.tools: expanded` was silently a no-op. Three call sites had the same bug shape; all now key off the resolved section modes: - ToolTrail: replace the `detailsMode === 'hidden'` early return with an `allHidden = every section resolved to hidden` check. When that's true, fall back to the floating-alert backstop (errors/warnings) so quiet-mode users aren't blind to ambient failures, and update the comment block to match the actual condition. - messageLine.tsx: drop the same `detailsMode === 'hidden'` pre-check on `msg.kind === 'trail'`; only skip rendering the wrapper when every section resolves to hidden (`SECTION_NAMES.some(...) !== 'hidden'`). - useMainApp.ts: rebuild `showProgressArea` around `anyPanelVisible` instead of branching on the global mode. This also fixes the suppressed Copilot concern about an empty wrapper Box rendering above the streaming area when ToolTrail returns null. Regression test in details.test.ts pins the override-escapes-hidden behaviour for tools/thinking/activity. 271/271 vitest, lints clean.
Round-2 Copilot review on NousResearch#14968 caught two leftover spots that didn't fully respect per-section overrides: - messageLine.tsx (trail branch): the previous fix gated on `SECTION_NAMES.some(...)`, which stayed true whenever any section was visible. With `thinking: 'expanded'` as the new built-in default, that meant `display.sections.tools: hidden` left an empty wrapper Box alive for trail messages. Now gates on the actual content-bearing sections for a trail message — `tools` OR `activity` — so a tools-hidden config drops the wrapper cleanly. - messageLine.tsx (showDetails): still keyed off the global `detailsMode !== 'hidden'`, so per-section overrides like `sections.thinking: expanded` couldn't escape global hidden for assistant messages with reasoning + tool metadata. Recomputed via resolved per-section modes (`thinkingMode`/`toolsMode`). - types.ts: rewrote the SectionVisibility doc comment to reflect the actual resolution order (explicit override → SECTION_DEFAULTS → global), so the docstring stops claiming "missing keys fall back to the global mode" when SECTION_DEFAULTS now layers in between. All three lookups (thinking/tools/activity) are computed once at the top of MessageLine and shared by every branch.
…n-visibility feat(tui): per-section visibility for the details accordion
Recovers the manual click on the details accordion: with NousResearch#14968's new SECTION_DEFAULTS (thinking/tools start `expanded`), every panel render was OR-ing the local open toggle against `visible.X === 'expanded'`. That pinned `open=true` for the default-expanded sections, so clicking the chevron flipped the local state but the panel never collapsed. Local toggle is now the sole source of truth at render time; the useState init still seeds from the resolved visibility (so first paint is correct) and the existing useEffect still re-syncs when the user mutates visibility at runtime via `/details`. Same OR-lock cleared inside SubagentAccordion (`showChildren || openX`) — pre-existing but the same shape, so expand-all on the spawn tree no longer makes inner sections un-collapsible either.
Copilot review on NousResearch#14968 caught that the early returns gated on the global `detailsMode === 'hidden'` short-circuited every render path before sectionMode() got a chance to apply per-section overrides — so `details_mode: hidden` + `sections.tools: expanded` was silently a no-op. Three call sites had the same bug shape; all now key off the resolved section modes: - ToolTrail: replace the `detailsMode === 'hidden'` early return with an `allHidden = every section resolved to hidden` check. When that's true, fall back to the floating-alert backstop (errors/warnings) so quiet-mode users aren't blind to ambient failures, and update the comment block to match the actual condition. - messageLine.tsx: drop the same `detailsMode === 'hidden'` pre-check on `msg.kind === 'trail'`; only skip rendering the wrapper when every section resolves to hidden (`SECTION_NAMES.some(...) !== 'hidden'`). - useMainApp.ts: rebuild `showProgressArea` around `anyPanelVisible` instead of branching on the global mode. This also fixes the suppressed Copilot concern about an empty wrapper Box rendering above the streaming area when ToolTrail returns null. Regression test in details.test.ts pins the override-escapes-hidden behaviour for tools/thinking/activity. 271/271 vitest, lints clean.
Round-2 Copilot review on NousResearch#14968 caught two leftover spots that didn't fully respect per-section overrides: - messageLine.tsx (trail branch): the previous fix gated on `SECTION_NAMES.some(...)`, which stayed true whenever any section was visible. With `thinking: 'expanded'` as the new built-in default, that meant `display.sections.tools: hidden` left an empty wrapper Box alive for trail messages. Now gates on the actual content-bearing sections for a trail message — `tools` OR `activity` — so a tools-hidden config drops the wrapper cleanly. - messageLine.tsx (showDetails): still keyed off the global `detailsMode !== 'hidden'`, so per-section overrides like `sections.thinking: expanded` couldn't escape global hidden for assistant messages with reasoning + tool metadata. Recomputed via resolved per-section modes (`thinkingMode`/`toolsMode`). - types.ts: rewrote the SectionVisibility doc comment to reflect the actual resolution order (explicit override → SECTION_DEFAULTS → global), so the docstring stops claiming "missing keys fall back to the global mode" when SECTION_DEFAULTS now layers in between. All three lookups (thinking/tools/activity) are computed once at the top of MessageLine and shared by every branch.
…n-visibility feat(tui): per-section visibility for the details accordion
Recovers the manual click on the details accordion: with NousResearch#14968's new SECTION_DEFAULTS (thinking/tools start `expanded`), every panel render was OR-ing the local open toggle against `visible.X === 'expanded'`. That pinned `open=true` for the default-expanded sections, so clicking the chevron flipped the local state but the panel never collapsed. Local toggle is now the sole source of truth at render time; the useState init still seeds from the resolved visibility (so first paint is correct) and the existing useEffect still re-syncs when the user mutates visibility at runtime via `/details`. Same OR-lock cleared inside SubagentAccordion (`showChildren || openX`) — pre-existing but the same shape, so expand-all on the spawn tree no longer makes inner sections un-collapsible either.
Copilot review on NousResearch#14968 caught that the early returns gated on the global `detailsMode === 'hidden'` short-circuited every render path before sectionMode() got a chance to apply per-section overrides — so `details_mode: hidden` + `sections.tools: expanded` was silently a no-op. Three call sites had the same bug shape; all now key off the resolved section modes: - ToolTrail: replace the `detailsMode === 'hidden'` early return with an `allHidden = every section resolved to hidden` check. When that's true, fall back to the floating-alert backstop (errors/warnings) so quiet-mode users aren't blind to ambient failures, and update the comment block to match the actual condition. - messageLine.tsx: drop the same `detailsMode === 'hidden'` pre-check on `msg.kind === 'trail'`; only skip rendering the wrapper when every section resolves to hidden (`SECTION_NAMES.some(...) !== 'hidden'`). - useMainApp.ts: rebuild `showProgressArea` around `anyPanelVisible` instead of branching on the global mode. This also fixes the suppressed Copilot concern about an empty wrapper Box rendering above the streaming area when ToolTrail returns null. Regression test in details.test.ts pins the override-escapes-hidden behaviour for tools/thinking/activity. 271/271 vitest, lints clean.
Round-2 Copilot review on NousResearch#14968 caught two leftover spots that didn't fully respect per-section overrides: - messageLine.tsx (trail branch): the previous fix gated on `SECTION_NAMES.some(...)`, which stayed true whenever any section was visible. With `thinking: 'expanded'` as the new built-in default, that meant `display.sections.tools: hidden` left an empty wrapper Box alive for trail messages. Now gates on the actual content-bearing sections for a trail message — `tools` OR `activity` — so a tools-hidden config drops the wrapper cleanly. - messageLine.tsx (showDetails): still keyed off the global `detailsMode !== 'hidden'`, so per-section overrides like `sections.thinking: expanded` couldn't escape global hidden for assistant messages with reasoning + tool metadata. Recomputed via resolved per-section modes (`thinkingMode`/`toolsMode`). - types.ts: rewrote the SectionVisibility doc comment to reflect the actual resolution order (explicit override → SECTION_DEFAULTS → global), so the docstring stops claiming "missing keys fall back to the global mode" when SECTION_DEFAULTS now layers in between. All three lookups (thinking/tools/activity) are computed once at the top of MessageLine and shared by every branch.
…n-visibility feat(tui): per-section visibility for the details accordion
Recovers the manual click on the details accordion: with NousResearch#14968's new SECTION_DEFAULTS (thinking/tools start `expanded`), every panel render was OR-ing the local open toggle against `visible.X === 'expanded'`. That pinned `open=true` for the default-expanded sections, so clicking the chevron flipped the local state but the panel never collapsed. Local toggle is now the sole source of truth at render time; the useState init still seeds from the resolved visibility (so first paint is correct) and the existing useEffect still re-syncs when the user mutates visibility at runtime via `/details`. Same OR-lock cleared inside SubagentAccordion (`showChildren || openX`) — pre-existing but the same shape, so expand-all on the spawn tree no longer makes inner sections un-collapsible either.
Summary
Two changes that together let users shape exactly what the TUI shows:
display.sectionsmap lets users override visibility for any subset of sections without affecting the rest. Pairs with the existing globaldetails_mode.Default matrix
thinkingtoolssubagentsdetails_mode(collapsed)activityStreaming/midstream assistant content is unaffected — it always renders inline, it's not part of the section accordion.
Opting out
Everything explicit in
display.sectionswins over the built-in defaults, so existing configs keep working unchanged. To reshape the layout:Or at runtime:
/details <section> [hidden|collapsed|expanded|reset].Slash command
Extended
/details:/details— show current global + active overrides/details [hidden|collapsed|expanded|cycle]— set global mode (existing)/details <section> [hidden|collapsed|expanded|reset]— per-section override (new)Sections:
thinking,tools,subagents,activity. Per-section overrides take precedence over both the section default and the global mode — sodetails_mode: hidden+sections.tools: expandedrenders the tools panel even when the global mode is hidden.Implementation
ui-tui/src/types.ts—SectionName+SectionVisibilitytypes.ui-tui/src/domain/details.ts—resolveSections,isSectionName,SECTION_NAMES, plusSECTION_DEFAULTS(thinking/tools expanded, activity hidden) andsectionModewhich resolves explicit override →SECTION_DEFAULTS→ global. Single source of truth for "what mode does this section render in".ui-tui/src/app/{interfaces,uiStore,useConfigSync}.ts—sections(the explicit-overrides map) threaded intoUiState, parsed fromdisplay.sectionson initial config sync.ui-tui/src/components/thinking.tsx—ToolTrailconsumes per-section modes viasectionMode. Hidden sections are skipped entirely; expanded ones seedopenstate to true. Renamed the localsectionsarray topanelsto avoid shadowing the new prop. Early-return now keys offallHidden(every section resolved to hidden) so per-section overrides still render when the global mode is hidden; floating-alert backstop surfaces under that all-hidden case.ui-tui/src/components/{messageLine,appLayout}.tsx— passsectionsthrough; skip the trail wrapper only when every section resolves to hidden.ui-tui/src/app/useMainApp.ts—showProgressArearebuilt aroundanyPanelVisible(mirrorsToolTrail's short-circuit, kills the empty-wrapper-Box cosmetic gap).ui-tui/src/app/slash/commands/core.ts—/details <section> <mode>parsing + dispatch viaconfig.setwithdetails_mode.<section>key.resetclears the override.tui_gateway/server.py—config.set details_mode.<section>handler writes todisplay.sections.<section>. Empty value clears the override. Validation rejects unknown sections / unknown modes with 4002.website/docs/user-guide/tui.md— documented defaults, overrides, and the opt-out story.Behavioural changes for existing users
details_mode: expanded: no visible change — thinking/tools were already open; activity is now hidden but gets a floating backstop for errors.details_mode: hidden(legacy quiet mode): thinking + tools now render open regardless (section defaults win over the global). To restore silence: pin each section explicitly (display.sections.thinking: hidden, etc.).display.sectionsentries: their overrides win. No migration needed.One config line reverts each default in every case.
Test plan
vitest run(ui-tui) — 272/272 passing (17 new: 6 domain, 4 config sync, 3 slash, 3 gateway, 1 override-escapes-hidden regression)tsc --noEmit(ui-tui) — clean (the pre-existingdom.tswarning on main is untouched)pytest tests/test_tui_gateway_server.py -k config_set_section— 3/3~/.hermes/directory → runhermes --tui→ thinking + tools stream open, activity never surfaces, spawn tree stays collapsed until a delegation fires/details thinking collapsed→ thinking back under a chevron + persists to config;/details thinking reset→ restores the new default (expanded)details_mode: hidden+sections.tools: expanded→ tools panel still renders (regression coverage for Copilot's catch)