Fix settings search fan-out (#5452)

Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
OrcaWin
2026-06-15 20:52:06 -07:00
committed by GitHub
co-authored by Neil
parent 87d905b8c3
commit 52f2a32e62
4 changed files with 102 additions and 15 deletions
@@ -27,7 +27,7 @@ describe('Settings load-performance helpers', () => {
expect(Array.from(needed).sort()).toEqual(['general'])
})
it('adds matched sections immediately when search is non-empty', () => {
it('keeps search mounting scoped to the active section', () => {
const needed = deriveNeededSectionIds({
navSectionIds: ['general', 'agents', 'appearance', 'terminal', 'stats', 'repo-a'],
mountedSectionIds: new Set(['general']),
@@ -37,6 +37,20 @@ describe('Settings load-performance helpers', () => {
visibleSectionIds: new Set(['stats'])
})
expect(needed.has('stats')).toBe(false)
expect(needed.has('general')).toBe(false)
})
it('mounts the active matched section during search', () => {
const needed = deriveNeededSectionIds({
navSectionIds: ['general', 'agents', 'appearance', 'terminal', 'stats', 'repo-a'],
mountedSectionIds: new Set(['general']),
activeSectionId: 'stats',
pendingSectionId: null,
query: 'stats',
visibleSectionIds: new Set(['stats'])
})
expect(needed.has('stats')).toBe(true)
})
@@ -23,9 +23,9 @@ type SettingsSectionProps = {
badgeAccessory?: React.ReactNode
forceVisible?: boolean
/** When true, this section is the one currently selected in the sidebar.
* Sections render only when active or when a non-empty search matches them
* — that way the Settings page shows one focused pane at a time instead of
* one giant scrolling document. */
* Sections render only when active. During search, the sidebar lists every
* match while the content pane stays focused on the selected match instead
* of mounting every matching settings surface at once. */
isActive?: boolean
/** Rendered in the section header's upper-right corner — intended for
* section-scoped actions (e.g. "Import from Ghostty") that would otherwise
@@ -54,7 +54,7 @@ export function SettingsSection({
const matchesQuery = !searchEntries || matchesSettingsSearch(query, searchEntries)
if (!forceVisible) {
if (hasQuery) {
if (!matchesQuery) {
if (!sectionIsActive || !matchesQuery) {
return null
}
} else if (!sectionIsActive) {
@@ -16,23 +16,24 @@ export function deriveNeededSectionIds(args: {
query: string
visibleSectionIds: Set<string>
}): Set<string> {
const next = new Set(args.mountedSectionIds)
for (const sectionId of args.navSectionIds) {
if (EAGER_SECTION_IDS.has(sectionId)) {
next.add(sectionId)
const hasSearchQuery = args.query.trim() !== ''
const next = hasSearchQuery ? new Set<string>() : new Set(args.mountedSectionIds)
if (!hasSearchQuery) {
for (const sectionId of args.navSectionIds) {
if (EAGER_SECTION_IDS.has(sectionId)) {
next.add(sectionId)
}
}
}
if (args.activeSectionId) {
if (
args.activeSectionId &&
(!hasSearchQuery || args.visibleSectionIds.has(args.activeSectionId))
) {
next.add(args.activeSectionId)
}
if (args.pendingSectionId) {
next.add(args.pendingSectionId)
}
if (args.query.trim() !== '') {
for (const visibleSectionId of args.visibleSectionIds) {
next.add(visibleSectionId)
}
}
return next
}