mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-09 08:13:24 +00:00
feat(tui): enable cwd-scoped session tabs by default
This commit is contained in:
parent
5cf97f1b96
commit
d35ca49c31
5 changed files with 30 additions and 13 deletions
|
|
@ -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"],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue