Selaa lähdekoodia

feat(tui): enable cwd-scoped session tabs by default

Dax Raad 1 viikko sitten
vanhempi
sitoutus
d35ca49c31

+ 2 - 2
packages/tui/src/component/dialog-config.tsx

@@ -88,7 +88,7 @@ export const settings: Setting[] = [
     title: "Enabled",
     category: "Tabs",
     path: ["tabs", "enabled"],
-    default: false,
+    default: true,
     values: [false, true],
     labels: ["off", "on"],
   },
@@ -96,7 +96,7 @@ export const settings: Setting[] = [
     title: "Scope",
     category: "Tabs",
     path: ["tabs", "scope"],
-    default: "global",
+    default: "cwd",
     values: ["cwd", "global"],
     labels: ["current directory", "global"],
   },

+ 11 - 1
packages/tui/src/config/index.tsx

@@ -179,7 +179,7 @@ export const Info = Schema.Struct({
 })
 export type Info = Schema.Schema.Type<typeof Info>
 
-export type Resolved = Omit<Info, "attention" | "keybinds" | "leader" | "mouse"> & {
+export type Resolved = Omit<Info, "attention" | "keybinds" | "leader" | "mouse" | "tabs"> & {
   attention: {
     enabled: boolean
     notifications: boolean
@@ -191,6 +191,11 @@ export type Resolved = Omit<Info, "attention" | "keybinds" | "leader" | "mouse">
   keybinds: TuiKeybind.BindingLookupView
   leader: { timeout: number }
   mouse: boolean
+  tabs: {
+    enabled: boolean
+    scope: "global" | "cwd"
+    vertical?: boolean
+  }
 }
 
 export function resolve(input: Info, options: { terminalSuspend: boolean }): Resolved {
@@ -221,6 +226,11 @@ export function resolve(input: Info, options: { terminalSuspend: boolean }): Res
     }),
     leader: { timeout: input.leader?.timeout ?? 2000 },
     mouse: input.mouse ?? true,
+    tabs: {
+      ...input.tabs,
+      enabled: input.tabs?.enabled ?? true,
+      scope: input.tabs?.scope ?? "cwd",
+    },
   }
 }
 

+ 3 - 3
packages/tui/src/context/session-tabs.tsx

@@ -49,7 +49,7 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp
     const event = useEvent()
     const config = useConfig().data
     const paths = useTuiPaths()
-    const enabled = () => config.tabs?.enabled ?? false
+    const enabled = () => config.tabs.enabled
     // Keyed reconcile keeps tab object identity across reorders, so strip rows move instead of
     // mutating in place, which per-row animations and drag state depend on.
     const [store, updateStore] = useStorage().store<PersistedState>("tabs", {
@@ -66,12 +66,12 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp
     let closedTabs: ClosedSessionTab[] = []
 
     function state() {
-      if (config.tabs?.scope === "cwd") return store.cwd[paths.cwd] ?? fallback
+      if (config.tabs.scope === "cwd") return store.cwd[paths.cwd] ?? fallback
       return store.global
     }
 
     function update(mutation: (draft: TabsState) => void) {
-      const scope = config.tabs?.scope ?? "global"
+      const scope = config.tabs.scope
       void updateStore((draft) => mutation(scope === "cwd" ? (draft.cwd[paths.cwd] ??= empty()) : draft.global)).catch(
         // Failed writes lose only tab layout, but silence would hide tabs resetting every launch.
         (error) => console.error("Failed to persist session tabs", error),

+ 7 - 0
packages/tui/test/config-v2.test.tsx

@@ -3,6 +3,7 @@ import { testRender } from "@opentui/solid"
 import { expect, test } from "bun:test"
 import { Schema } from "effect"
 import { resolve, ConfigProvider, Info, useConfig, type Interface } from "../src/config"
+import { settings } from "../src/component/dialog-config"
 
 test("validates mini replay settings", () => {
   const decode = Schema.decodeUnknownSync(Info)
@@ -38,6 +39,12 @@ test("resolves nested config and keybind defaults", () => {
   expect(config.scroll).toEqual({ speed: 2, acceleration: true })
   expect(config.diffs).toEqual({ view: "split" })
   expect(config.debug).toEqual({ devtools: true })
+  expect(config.tabs).toEqual({ enabled: true, scope: "cwd" })
+})
+
+test("shows resolved tab defaults in settings", () => {
+  expect(settings.find((setting) => setting.path.join(".") === "tabs.enabled")?.default).toBe(true)
+  expect(settings.find((setting) => setting.path.join(".") === "tabs.scope")?.default).toBe("cwd")
 })
 
 test("provides config and its host interface", async () => {

+ 7 - 7
packages/tui/test/context/session-tabs.test.tsx

@@ -60,8 +60,8 @@ async function renderSessionTabs(
     await Bun.write(
       file,
       JSON.stringify({
-        global: { tabs: options.persisted.map((sessionID) => ({ sessionID })), unread: {} },
-        cwd: {},
+        global: { tabs: [], unread: {} },
+        cwd: { [directory]: { tabs: options.persisted.map((sessionID) => ({ sessionID })), unread: {} } },
       }),
     )
   }
@@ -153,15 +153,15 @@ test("loads persisted tab metadata concurrently on connect", async () => {
   }
 })
 
-test("stores session tabs globally by default", async () => {
+test("stores session tabs for the current working directory by default", async () => {
   const setup = await renderSessionTabs("first")
 
   try {
     const file = path.join(setup.state, "test", "tui", "tabs.json")
     await wait(() => Bun.file(file).size > 0)
     expect(await Bun.file(file).json()).toEqual({
-      global: { tabs: [{ sessionID: "first" }], unread: {} },
-      cwd: {},
+      global: { tabs: [], unread: {} },
+      cwd: { [directory]: { tabs: [{ sessionID: "first" }], unread: {} } },
     })
   } finally {
     setup.destroy()
@@ -180,7 +180,7 @@ test("concurrent TUIs do not alternate shared tab titles from divergent session
     await titled.data.session.sync("shared")
     await wait(async () => {
       if (!(await Bun.file(file).exists())) return false
-      return (await Bun.file(file).json()).global.tabs[0]?.title === "Generated title"
+      return (await Bun.file(file).json()).cwd[directory]?.tabs[0]?.title === "Generated title"
     })
     const observed = ["Generated title"]
     const pending = new Set<Promise<void>>()
@@ -189,7 +189,7 @@ test("concurrent TUIs do not alternate shared tab titles from divergent session
       const read = Bun.file(file)
         .json()
         .then((value) => {
-          const title = value.global.tabs[0]?.title
+          const title = value.cwd[directory]?.tabs[0]?.title
           if (title && observed.at(-1) !== title) observed.push(title)
         })
         .catch(() => undefined)