fix(tui): per-section overrides escape global details_mode: hidden

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.
This commit is contained in:
Brooklyn Nicholson 2026-04-24 02:49:58 -05:00
parent 005cc29e98
commit 70925363b6
6 changed files with 57 additions and 32 deletions

View file

@ -17,10 +17,12 @@ const THINKING_FALLBACK: Record<string, DetailsMode> = {
truncated: 'collapsed'
}
const norm = (v: unknown) => String(v ?? '').trim().toLowerCase()
const norm = (v: unknown) =>
String(v ?? '')
.trim()
.toLowerCase()
export const parseDetailsMode = (v: unknown): DetailsMode | null =>
MODES.find(m => m === norm(v)) ?? null
export const parseDetailsMode = (v: unknown): DetailsMode | null => MODES.find(m => m === norm(v)) ?? null
export const isSectionName = (v: unknown): v is SectionName =>
typeof v === 'string' && (SECTION_NAMES as readonly string[]).includes(v)
@ -42,11 +44,7 @@ export const resolveSections = (raw: unknown): SectionVisibility =>
// Effective mode for one section: explicit override → SECTION_DEFAULTS → global.
// Single source of truth for "is this section open by default / rendered at all".
export const sectionMode = (
name: SectionName,
global: DetailsMode,
sections?: SectionVisibility
): DetailsMode => sections?.[name] ?? SECTION_DEFAULTS[name] ?? global
export const sectionMode = (name: SectionName, global: DetailsMode, sections?: SectionVisibility): DetailsMode =>
sections?.[name] ?? SECTION_DEFAULTS[name] ?? global
export const nextDetailsMode = (m: DetailsMode): DetailsMode =>
MODES[(MODES.indexOf(m) + 1) % MODES.length]!
export const nextDetailsMode = (m: DetailsMode): DetailsMode => MODES[(MODES.indexOf(m) + 1) % MODES.length]!