fix(tui): load root sessions in session switcher (#33931)

Request root sessions before applying the session list limit so child
sessions cannot crowd roots out of the switcher.

Keep the synchronized cache available while requests are pending or fail,
reconcile results with live updates, and retain current and pinned sessions.
Preserve selection by session ID when asynchronous results reorder the list.

Closes #16270
Closes #32725
This commit is contained in:
Simon Klee
2026-06-25 18:36:08 +02:00
committed by GitHub
parent 69f75dff1a
commit e7c59b17a8
5 changed files with 193 additions and 13 deletions
+74 -3
View File
@@ -9,7 +9,7 @@ import {
import type { Binding } from "@opentui/keymap"
import { useTheme, selectedForeground } from "../context/theme"
import { entries, filter, flatMap, groupBy, pipe } from "remeda"
import { batch, createEffect, createMemo, createSignal, For, Show, type JSX, on } from "solid-js"
import { batch, createEffect, createMemo, createSignal, For, Show, type JSX, on, onCleanup } from "solid-js"
import { createStore } from "solid-js/store"
import { useTerminalDimensions } from "@opentui/solid"
import * as fuzzysort from "fuzzysort"
@@ -35,6 +35,7 @@ export interface DialogSelectProps<T> {
skipFilter?: boolean
renderFilter?: boolean
locked?: boolean
preserveSelection?: boolean
actions?: {
command: string
title: string
@@ -93,6 +94,9 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
})
const [focusedAction, setFocusedAction] = createSignal<number>()
const actionFocused = createMemo(() => focusedAction() !== undefined)
let selection: { value: T; category?: string } | undefined
let resetSelection = false
let visibilityGeneration = 0
createEffect(
on(
@@ -102,6 +106,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
const currentIndex = flat().findIndex((opt) => isDeepEqual(opt.value, current))
if (currentIndex >= 0) {
setStore("selected", currentIndex)
selection = flat()[currentIndex]
}
}
},
@@ -209,11 +214,69 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
const selected = createMemo(() => flat()[store.selected])
createEffect(
on(
() => props.options,
() => {
if (!props.preserveSelection) return
if (resetSelection && store.filter.length > 0) {
const option = flat()[0]
if (!option) return
setStore("selected", 0)
selection = option
return
}
if (!selection) {
if (props.current !== undefined) {
const index = flat().findIndex((option) => isDeepEqual(option.value, props.current))
if (index >= 0) {
setStore("selected", index)
selection = flat()[index]
return
}
}
const option = selected()
if (!option) return
selection = option
return
}
const previous = selection
const index = flat().findIndex((option) => isDeepEqual(option.value, previous.value))
if (index >= 0) {
const option = flat()[index]
const moved = index !== store.selected || option.category !== previous.category
setStore("selected", index)
selection = option
if (!moved) return
const value = option.value
const generation = ++visibilityGeneration
requestAnimationFrame(() => {
requestAnimationFrame(() => {
if (generation !== visibilityGeneration) return
if (!props.preserveSelection || store.filter.length > 0) return
if (!isDeepEqual(selected()?.value, value)) return
scrollToSelection(false)
})
})
return
}
const next = Math.min(store.selected, flat().length - 1)
if (next < 0) return
setStore("selected", next)
selection = flat()[next]
},
),
)
onCleanup(() => {
visibilityGeneration++
})
createEffect(
on([() => store.filter, () => props.current], ([filter, current]) => {
if (filter.length > 0) resetSelection = true
setTimeout(() => {
if (filter.length > 0) {
moveTo(0, true)
moveTo(0, true, false)
} else if (current) {
const currentIndex = flat().findIndex((opt) => isDeepEqual(opt.value, current))
if (currentIndex >= 0) {
@@ -233,11 +296,19 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
moveTo(next, true)
}
function moveTo(next: number, center = false) {
function moveTo(next: number, center = false, preserve = true) {
setFocusedAction(undefined)
setStore("selected", next)
const option = selected()
if (option) {
selection = option
resetSelection = !preserve
}
if (option) props.onMove?.(option)
scrollToSelection(center)
}
function scrollToSelection(center: boolean) {
if (!scroll) return
let remaining = store.selected
let index = 0