Compare commits

..

2 Commits

Author SHA1 Message Date
Aiden Cline 34585dce41 Merge branch 'v2' into empty-variants 2026-08-16 16:03:04 -05:00
Aiden Cline 654f4ede55 fix(tui): drop unavailable saved variants 2026-08-16 04:14:49 +00:00
8 changed files with 29 additions and 28 deletions
@@ -126,15 +126,6 @@ export const settings: Setting[] = [
values: ["horizontal", "vertical"],
keywords: ["sidebar", "orientation", "left"],
},
{
title: "Shortcut numbers",
category: "Tabs",
path: ["tabs", "numbers"],
default: false,
values: [false, true],
labels: ["off", "on"],
keywords: ["keys", "numeric", "labels"],
},
{
title: "Layout",
category: "Diffs",
+2 -2
View File
@@ -664,7 +664,7 @@ function VerticalSessionTabs(props: { controller?: SessionTabsController; animat
selectable={false}
attributes={selected() ? TextAttributes.BOLD : undefined}
>
{config.tabs.numbers ? sessionTabShortcutLabel(index()) : ""}
{sessionTabShortcutLabel(index())}
</text>
<text
width={titleWidth()}
@@ -1145,7 +1145,7 @@ function HorizontalSessionTabs(props: { controller?: SessionTabsController; anim
{" "}
</text>
<text width={numberWidth()} fg={numberColor()} selectable={false} attributes={bold()}>
{tab === NEW_SESSION_TAB ? "+" : config.tabs.numbers ? sessionTabShortcutLabel(tabNumber() - 1) : ""}
{tab === NEW_SESSION_TAB ? "+" : sessionTabShortcutLabel(tabNumber() - 1)}
</text>
<text
width={availableTitleWidth()}
-5
View File
@@ -153,9 +153,6 @@ export const Info = Schema.Struct({
layout: Schema.optional(Schema.Literals(["horizontal", "vertical"])).annotate({
description: "Show tabs in a horizontal strip or vertical sidebar",
}),
numbers: Schema.optional(Schema.Boolean).annotate({
description: "Show numeric shortcuts beside session tabs",
}),
}),
).annotate({ description: "Tab strip settings" }),
mini: Schema.optional(
@@ -227,7 +224,6 @@ export type Resolved = Omit<Info, "attention" | "cursor" | "keybinds" | "leader"
enabled: boolean
scope: "global" | "cwd"
layout: "horizontal" | "vertical"
numbers: boolean
}
}
@@ -273,7 +269,6 @@ export function resolve(input: Info, options: { terminalSuspend: boolean }): Res
enabled: input.tabs?.enabled ?? true,
scope: input.tabs?.scope ?? "cwd",
layout: input.tabs?.layout ?? "horizontal",
numbers: input.tabs?.numbers ?? false,
},
}
}
+12 -5
View File
@@ -10,6 +10,7 @@ import { useClient } from "./client"
import { RGBA } from "@opentui/core"
import { readJson, writeJsonAtomic } from "../util/persistence"
import {
availableModelVariant,
createModelPreferenceRepository,
cycleModelVariant,
modelPreferenceKey,
@@ -221,7 +222,14 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
if (route.data.type === "session") return sessionSelection(route.data.sessionID)
const model = newSessionModel()
if (!model) return
return { ...model, variant: normalizeModelVariant(preferences.variant[modelPreferenceKey(model)]) }
const info = models()?.find((item) => item.providerID === model.providerID && item.id === model.modelID)
return {
...model,
variant: availableModelVariant(
preferences.variant[modelPreferenceKey(model)],
info?.variants?.map((item) => item.id) ?? [],
),
}
})
const currentModel = createMemo(() => {
@@ -262,13 +270,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
if (route.data.type === "session") {
const sessionID = route.data.sessionID
const current = sessionSelection(sessionID)
const preferred = normalizeModelVariant(
const preferred =
current?.providerID === model.providerID && current.modelID === model.modelID
? current.variant
: preferences.variant[modelPreferenceKey(model)],
)
: preferences.variant[modelPreferenceKey(model)]
const info = models()?.find((item) => item.providerID === model.providerID && item.id === model.modelID)
const variant = preferred && info?.variants?.some((item) => item.id === preferred) ? preferred : undefined
const variant = availableModelVariant(preferred, info?.variants?.map((item) => item.id) ?? [])
setSessionDraft(sessionID, { ...model, variant })
return true
}
+5
View File
@@ -39,6 +39,11 @@ export function normalizeModelVariant(value: string | undefined) {
return value === "default" ? undefined : value
}
export function availableModelVariant(value: string | undefined, variants: string[]) {
const variant = normalizeModelVariant(value)
return variant && variants.includes(variant) ? variant : undefined
}
export function modelPreferenceKey(model: ModelPreferenceModel) {
return `${model.providerID}/${model.modelID}`
}
@@ -43,7 +43,6 @@ test("releasing a transcript selection over tab controls does not activate them"
try {
app.renderer.start()
await app.waitForFrame((frame) => frame.includes("Second"))
expect(app.captureCharFrame()).not.toContain("1 First")
await app.mockMouse.pressDown(5, 1)
await app.mockMouse.release(40, 0)
expect(active()).toBe("first")
+3 -5
View File
@@ -20,12 +20,11 @@ test("validates mini replay settings", () => {
test("validates the session tabs setting", () => {
const decode = Schema.decodeUnknownSync(Info)
expect(decode({ tabs: { enabled: true, layout: "vertical", numbers: true } })).toEqual({
tabs: { enabled: true, layout: "vertical", numbers: true },
expect(decode({ tabs: { enabled: true, layout: "vertical" } })).toEqual({
tabs: { enabled: true, layout: "vertical" },
})
expect(() => decode({ tabs: { layout: true } })).toThrow()
expect(() => decode({ tabs: { enabled: "on" } })).toThrow()
expect(() => decode({ tabs: { numbers: "on" } })).toThrow()
expect(decode({ prompt: { image_preview: true } })).toEqual({ prompt: { image_preview: true } })
expect(decode({ session: { image_preview: true } })).toEqual({ session: { image_preview: true } })
expect(decode({ session: { new_location: "inherit" } })).toEqual({ session: { new_location: "inherit" } })
@@ -49,7 +48,7 @@ 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", layout: "horizontal", numbers: false })
expect(config.tabs).toEqual({ enabled: true, scope: "cwd", layout: "horizontal" })
expect(config.session.new_location).toBe("launch")
})
@@ -57,7 +56,6 @@ 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")
expect(settings.find((setting) => setting.path.join(".") === "tabs.layout")?.default).toBe("horizontal")
expect(settings.find((setting) => setting.path.join(".") === "tabs.numbers")?.default).toBe(false)
})
test("shows the new session location default in settings", () => {
+7 -1
View File
@@ -1,6 +1,6 @@
import { expect, test } from "bun:test"
import path from "node:path"
import { createModelPreferenceRepository, decodeModelPreference } from "../src/model-preference"
import { availableModelVariant, createModelPreferenceRepository, decodeModelPreference } from "../src/model-preference"
import { tmpdir } from "./fixture/fixture"
test("repairs known model preferences and preserves unrelated fields", () => {
@@ -19,6 +19,12 @@ test("repairs known model preferences and preserves unrelated fields", () => {
})
})
test("drops a saved variant that is no longer available", () => {
expect(availableModelVariant("medium", [])).toBeUndefined()
expect(availableModelVariant("medium", ["low", "high"])).toBeUndefined()
expect(availableModelVariant("medium", ["low", "medium", "high"])).toBe("medium")
})
test("atomically serializes patches and variant updates", async () => {
await using tmp = await tmpdir()
const file = path.join(tmp.path, "model.json")