refactor(tui): /clean pass on per-section visibility plumbing

- 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
This commit is contained in:
Brooklyn Nicholson 2026-04-24 02:42:03 -05:00
parent 728767e910
commit 005cc29e98
3 changed files with 88 additions and 124 deletions

View file

@ -1,7 +1,15 @@
import type { DetailsMode, SectionName, SectionVisibility } from '../types.js'
const MODES = ['hidden', 'collapsed', 'expanded'] as const
export const SECTION_NAMES: readonly SectionName[] = ['thinking', 'tools', 'subagents', 'activity']
export const SECTION_NAMES = ['thinking', 'tools', 'subagents', 'activity'] as const
// Activity panel = ambient meta (gateway hints, terminal-parity nudges,
// background-process notifications). Hidden out of the box because tool
// failures already render inline on the failing tool row — the panel itself
// is noise for typical use. Opt back in via `display.sections.activity` or
// `/details activity collapsed`.
const SECTION_DEFAULTS: SectionVisibility = { activity: 'hidden' }
const THINKING_FALLBACK: Record<string, DetailsMode> = {
collapsed: 'collapsed',
@ -9,66 +17,36 @@ const THINKING_FALLBACK: Record<string, DetailsMode> = {
truncated: 'collapsed'
}
export const parseDetailsMode = (v: unknown): DetailsMode | null => {
const s = typeof v === 'string' ? v.trim().toLowerCase() : ''
const norm = (v: unknown) => String(v ?? '').trim().toLowerCase()
return MODES.find(m => m === s) ?? 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)
export const resolveDetailsMode = (d?: { details_mode?: unknown; thinking_mode?: unknown } | null): DetailsMode =>
parseDetailsMode(d?.details_mode) ??
THINKING_FALLBACK[
String(d?.thinking_mode ?? '')
.trim()
.toLowerCase()
] ??
'collapsed'
parseDetailsMode(d?.details_mode) ?? THINKING_FALLBACK[norm(d?.thinking_mode)] ?? 'collapsed'
// Build a SectionVisibility from a free-form `display.sections` config blob.
// Skips keys that aren't recognized section names or don't parse to a valid
// mode — partial overrides are intentional, missing keys fall through to the
// global details_mode at render time.
export const resolveSections = (raw: unknown): SectionVisibility => {
const out: SectionVisibility = {}
// Build SectionVisibility from a free-form blob. Unknown section names and
// invalid modes are dropped silently — partial overrides are intentional, so
// missing keys fall through to SECTION_DEFAULTS / global at lookup time.
export const resolveSections = (raw: unknown): SectionVisibility =>
raw && typeof raw === 'object' && !Array.isArray(raw)
? (Object.fromEntries(
Object.entries(raw as Record<string, unknown>)
.map(([k, v]) => [k, parseDetailsMode(v)] as const)
.filter(([k, m]) => !!m && isSectionName(k))
) as SectionVisibility)
: {}
if (!raw || typeof raw !== 'object') {
return out
}
for (const [k, v] of Object.entries(raw as Record<string, unknown>)) {
const mode = parseDetailsMode(v)
if (mode && isSectionName(k)) {
out[k] = mode
}
}
return out
}
// Built-in per-section defaults applied when the user has no explicit
// override. The activity panel (gateway hints, terminal-parity nudges,
// background-process notifications) is hidden out of the box — it's noise
// for the typical day-to-day user, who only cares about thinking + tools +
// streamed content. Tool failures still surface inline on the failing tool
// row; this default only suppresses the ambient meta feed.
//
// Opt back in with `display.sections.activity: collapsed` (under chevron)
// or `expanded` (always open) in `~/.hermes/config.yaml`, or live with
// `/details activity collapsed`.
const SECTION_DEFAULTS: SectionVisibility = { activity: 'hidden' }
// Resolve the effective mode for one section: explicit override wins,
// then the SECTION_DEFAULTS fallback, then the global details_mode.
// Single source of truth — every render site that needs to know "is this
// section open by default" calls this.
// 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 nextDetailsMode = (m: DetailsMode): DetailsMode => MODES[(MODES.indexOf(m) + 1) % MODES.length]!
export const nextDetailsMode = (m: DetailsMode): DetailsMode =>
MODES[(MODES.indexOf(m) + 1) % MODES.length]!