diff --git a/packages/tui/src/component/dialog-experiments.tsx b/packages/tui/src/component/dialog-experiments.tsx index 8938ced5bc9..10a717e6c63 100644 --- a/packages/tui/src/component/dialog-experiments.tsx +++ b/packages/tui/src/component/dialog-experiments.tsx @@ -1,84 +1,19 @@ -import { createMemo, createSignal } from "solid-js" -import { useConfig } from "../config" import { DialogSelect } from "../ui/dialog-select" import { useTheme } from "../context/theme" -import { useToast } from "../ui/toast" - -type Experiment = { - id: string - title: string - description: string -} - -// In-flight features anyone can opt into. Each entry is temporary: an -// experiment either graduates (delete the entry, make the behavior -// unconditional) or dies (delete the entry and the branch it gated). -export const experiments: Experiment[] = [] export function DialogExperiments() { - const config = useConfig() const theme = useTheme() - const toast = useToast() - const [selected, setSelected] = createSignal() - const [saving, setSaving] = createSignal(false) - - const enabled = (experiment: Experiment) => config.data.experimental?.[experiment.id] === true - - const options = createMemo(() => - experiments.map((experiment) => ({ - title: experiment.title, - searchText: experiment.description, - footer: enabled(experiment) ? "on" : "off", - value: experiment, - })), - ) - - // All experiments are booleans, so either direction toggles. - async function change(experiment = selected()) { - if (saving()) return - if (!experiment) return - const next = !enabled(experiment) - setSaving(true) - await config - .update((draft) => { - if (!draft.experimental || typeof draft.experimental !== "object") draft.experimental = {} - draft.experimental[experiment.id] = next - }) - .catch(toast.error) - .finally(() => setSaving(false)) - } return ( 0} - onMove={(option) => setSelected(option.value)} - onSelect={(option) => void change(option.value)} + options={[]} + renderFilter={false} emptyView={ No experiments available } - footerHints={experiments.length > 0 ? [{ title: "←/→", label: "change" }] : []} - bindings={ - experiments.length > 0 - ? [ - { - bind: "left", - title: "Previous value", - group: "Experiments", - run: () => void change(), - }, - { - bind: "right", - title: "Next value", - group: "Experiments", - run: () => void change(), - }, - ] - : [] - } /> ) } diff --git a/packages/tui/src/context/session-tabs.tsx b/packages/tui/src/context/session-tabs.tsx index 29aaf235772..3e506c98807 100644 --- a/packages/tui/src/context/session-tabs.tsx +++ b/packages/tui/src/context/session-tabs.tsx @@ -1,4 +1,5 @@ import { createEffect, createMemo, createSignal, on, onCleanup } from "solid-js" +import { createStore } from "solid-js/store" import { useKeyboard, useRenderer } from "@opentui/solid" import { isDeepEqual } from "remeda" import { createSimpleContext } from "./helper" @@ -73,9 +74,7 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp }, key: "sessionID", }) - const [preview, updatePreview] = storage.memory<{ global?: string; cwd?: string }>("session-tab-preview", { - initial: {}, - }) + const [preview, updatePreview] = createStore<{ global?: string; cwd?: string }>({}) const fallback = empty() const [promptPulses, setPromptPulses] = createSignal>({}) let history: SessionTabHistory = { entries: [], index: -1 } @@ -107,16 +106,7 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp } const previewID = () => preview[config.tabs.scope] - const setPreview = (sessionID: string | undefined) => { - const scope = config.tabs.scope - updatePreview((draft) => { - if (sessionID === undefined) { - delete draft[scope] - return - } - draft[scope] = sessionID - }) - } + const setPreview = (sessionID: string | undefined) => updatePreview(config.tabs.scope, sessionID) function update(mutation: (draft: TabsState) => void) { const scope = config.tabs.scope @@ -179,11 +169,7 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp createEffect(() => { if (enabled()) return promotedSession = undefined - if (!preview.global && !preview.cwd) return - updatePreview((draft) => { - delete draft.global - delete draft.cwd - }) + updatePreview({ global: undefined, cwd: undefined }) }) // Shared storage updates must not re-admit a tab unless this client changes route or scope. diff --git a/packages/tui/test/component/session-tabs-mouse.test.tsx b/packages/tui/test/component/session-tabs-mouse.test.tsx index 67a554881b0..21e347f5d07 100644 --- a/packages/tui/test/component/session-tabs-mouse.test.tsx +++ b/packages/tui/test/component/session-tabs-mouse.test.tsx @@ -5,10 +5,7 @@ import { expect, test } from "bun:test" import { createSignal } from "solid-js" import { ConfigProvider } from "../../src/config" import { EMPTY_SESSION_TAB_STATUS, SessionTabs, type SessionTabsController } from "../../src/component/session-tabs" -import { Keymap } from "../../src/context/keymap" import { ThemeProvider } from "../../src/context/theme" -import { DialogProvider } from "../../src/ui/dialog" -import { ToastProvider } from "../../src/ui/toast" import { emptyThemeSource } from "../fixture/fixture" import { TestTuiContexts } from "../fixture/tui-environment" import { createTuiResolvedConfig } from "../fixture/tui-runtime" @@ -65,78 +62,6 @@ test("releasing a transcript selection over tab controls does not activate them" } }) -test("the horizontal tab context menu keeps preview tabs open without selecting them", async () => { - const [active, setActive] = createSignal("first") - const promoted: string[] = [] - const controller = { - tabs: () => [ - { sessionID: "first", title: "First" }, - { sessionID: "second", title: "Second" }, - ], - current: active, - select: setActive, - close() {}, - move() {}, - isPreview: (sessionID: string) => sessionID === "second", - promote: (sessionID: string) => promoted.push(sessionID), - status: () => EMPTY_SESSION_TAB_STATUS, - } satisfies SessionTabsController - const app = await testRender( - () => ( - - - - - - - - - - - - - - ), - { width: 60, height: 8 }, - ) - - try { - app.renderer.start() - await app.waitForFrame((frame) => frame.includes("Second")) - - const first = app - .captureCharFrame() - .split("\n") - .findIndex((line) => line.includes("First")) - await app.mockMouse.click(app.captureCharFrame().split("\n")[first]!.indexOf("First"), first, MouseButton.RIGHT) - await app.waitForFrame((frame) => frame.includes("Rename")) - expect(app.captureCharFrame()).toContain("Close") - expect(app.captureCharFrame()).not.toContain("Keep open") - - app.mockInput.pressKey("c", { ctrl: true }) - await app.waitForFrame((frame) => !frame.includes("Rename")) - - const second = app - .captureCharFrame() - .split("\n") - .findIndex((line) => line.includes("Second")) - await app.mockMouse.click(app.captureCharFrame().split("\n")[second]!.indexOf("Second"), second, MouseButton.RIGHT) - await app.waitForFrame((frame) => frame.includes("Keep open")) - expect(app.captureCharFrame()).toContain("Rename") - expect(app.captureCharFrame()).toContain("Close") - expect(active()).toBe("first") - const frame = app.captureCharFrame().split("\n") - const row = frame.findIndex((line) => line.includes("Keep open")) - await app.mockMouse.click(frame[row]!.indexOf("Keep open"), row) - await app.waitForFrame((frame) => !frame.includes("Rename")) - - expect(promoted).toEqual(["second"]) - expect(active()).toBe("first") - } finally { - app.renderer.destroy() - } -}) - test("double-clicking a preview tab keeps it open without promoting permanent tabs", async () => { const [active, setActive] = createSignal("first") const promoted: string[] = []