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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user