Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/tui/src/config/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const PluginOptions = Schema.Record(Schema.String, Schema.Unknown)
export const PluginSpec = Schema.Union([Schema.String, Schema.mutable(Schema.Tuple([Schema.String, PluginOptions]))])

export const LeaderTimeoutDefault = 2000
export const SidebarWidthDefault = 42
export const LeaderTimeout = Schema.Int.check(Schema.isGreaterThan(0)).annotate({
description: "Leader key timeout in milliseconds",
})
Expand Down Expand Up @@ -51,6 +52,7 @@ export const Attention = Schema.Struct({
}).annotate({ description: "Attention notification and sound settings" })

const PromptSize = Schema.Int.check(Schema.isGreaterThan(0))
const SidebarWidth = Schema.Int.check(Schema.isGreaterThan(0))
export const Prompt = Schema.Struct({
max_height: Schema.optional(PromptSize).annotate({ description: "Prompt textarea max height" }),
max_width: Schema.optional(Schema.Union([PromptSize, Schema.Literal("auto")])).annotate({
Expand All @@ -72,10 +74,11 @@ export const Info = Schema.Struct({
diff_style: Schema.optional(DiffStyle),
cursor: Schema.optional(Cursor),
mouse: Schema.optional(Schema.Boolean).annotate({ description: "Enable or disable mouse capture (default: true)" }),
sidebar_width: Schema.optional(SidebarWidth).annotate({ description: "Sidebar width in columns (default: 42)" }),
})
export type Info = Schema.Schema.Type<typeof Info>

export type Resolved = Omit<Info, "attention" | "keybinds" | "leader_timeout" | "mouse" | "cursor"> & {
export type Resolved = Omit<Info, "attention" | "keybinds" | "leader_timeout" | "mouse" | "cursor" | "sidebar_width"> & {
attention: {
enabled: boolean
notifications: boolean
Expand All @@ -87,6 +90,7 @@ export type Resolved = Omit<Info, "attention" | "keybinds" | "leader_timeout" |
keybinds: TuiKeybind.BindingLookupView
leader_timeout: number
mouse: boolean
sidebar_width: number
cursor?: {
style: "block" | "underline" | "line" | "default"
blinking: boolean
Expand Down Expand Up @@ -126,6 +130,7 @@ export function resolve(input: Info, options: ResolveOptions): Resolved {
}),
leader_timeout: input.leader_timeout ?? LeaderTimeoutDefault,
mouse: input.mouse ?? true,
sidebar_width: input.sidebar_width ?? SidebarWidthDefault,
cursor: input.cursor
? {
style: input.cursor.style ?? "block",
Expand Down
8 changes: 5 additions & 3 deletions packages/tui/src/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { DialogTimeline } from "./dialog-timeline"
import { DialogForkFromTimeline } from "./dialog-fork-from-timeline"
import { DialogSessionRename } from "../../component/dialog-session-rename"
import { Sidebar } from "./sidebar"
import { clampSidebarWidth } from "../../util/sidebar-width"
import { SubagentFooter } from "./subagent-footer.tsx"
import { filetype } from "../../util/filetype"
import parsers from "../../parsers-config"
Expand Down Expand Up @@ -275,8 +276,9 @@ export function Session() {
if (sidebar() === "auto" && wide()) return true
return false
})
const sidebarWidth = createMemo(() => clampSidebarWidth(tuiConfig.sidebar_width, dimensions().width))
const showTimestamps = createMemo(() => timestamps() === "show")
const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 42 : 0) - 4)
const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? sidebarWidth() : 0) - 4)
const providers = createMemo(() => Model.index(sync.data.provider))

const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig))
Expand Down Expand Up @@ -1339,7 +1341,7 @@ export function Session() {
<Show when={sidebarVisible()}>
<Switch>
<Match when={wide()}>
<Sidebar sessionID={route.sessionID} />
<Sidebar sessionID={route.sessionID} width={sidebarWidth()} />
</Match>
<Match when={!wide()}>
<box
Expand All @@ -1351,7 +1353,7 @@ export function Session() {
alignItems="flex-end"
backgroundColor={RGBA.fromInts(0, 0, 0, 70)}
>
<Sidebar sessionID={route.sessionID} />
<Sidebar sessionID={route.sessionID} width={sidebarWidth()} />
</box>
</Match>
</Switch>
Expand Down
4 changes: 2 additions & 2 deletions packages/tui/src/routes/session/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { usePluginRuntime } from "../../plugin/runtime"
import { getScrollAcceleration } from "../../util/scroll"
import { WorkspaceLabel } from "../../component/workspace-label"

export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
export function Sidebar(props: { sessionID: string; overlay?: boolean; width: number }) {
const pluginRuntime = usePluginRuntime()
const project = useProject()
const sync = useSync()
Expand All @@ -27,7 +27,7 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
<Show when={session()}>
<box
backgroundColor={theme.backgroundPanel}
width={42}
width={props.width}
height="100%"
paddingTop={1}
paddingBottom={1}
Expand Down
5 changes: 5 additions & 0 deletions packages/tui/src/util/sidebar-width.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { SidebarWidthDefault } from "../config"

export function clampSidebarWidth(configured: number | undefined, terminalWidth: number) {
return Math.max(20, Math.min(configured ?? SidebarWidthDefault, terminalWidth - 40, 100))
}
5 changes: 5 additions & 0 deletions packages/tui/test/config.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Info,
LeaderTimeoutDefault,
PluginSpec,
SidebarWidthDefault,
resolve,
TuiConfigProvider,
type Info as TuiConfigInfo,
Expand Down Expand Up @@ -45,6 +46,7 @@ test("validates config constraints", () => {
expect(() => decodeInfo({ prompt: { max_width: 0 } })).toThrow()
expect(() => decodeInfo({ scroll_speed: 0 })).toThrow()
expect(() => decodeInfo({ cursor: { style: "beam" } })).toThrow()
expect(() => decodeInfo({ sidebar_width: 42.7 })).toThrow()
expect(decodeInfo({ attention: { sounds: { unknown: "sound.wav" } } })).toEqual({ attention: { sounds: {} } })
})

Expand All @@ -61,6 +63,7 @@ test("resolves host-neutral defaults", () => {
})
expect(config.leader_timeout).toBe(LeaderTimeoutDefault)
expect(config.mouse).toBe(true)
expect(config.sidebar_width).toBe(SidebarWidthDefault)
expect(config.keybinds.has("terminal.suspend")).toBe(true)
expect(config.keybinds.has("session.list")).toBe(true)
expect(config.cursor).toBeUndefined()
Expand All @@ -70,6 +73,7 @@ test("resolves overrides without mutating input", () => {
const input: TuiConfigInfo = {
theme: "custom",
mouse: false,
sidebar_width: 56,
leader_timeout: 750,
attention: {
enabled: true,
Expand All @@ -87,6 +91,7 @@ test("resolves overrides without mutating input", () => {
expect(config).toMatchObject({
theme: "custom",
mouse: false,
sidebar_width: 56,
leader_timeout: 750,
attention: input.attention,
cursor: { style: "block", blinking: false },
Expand Down
28 changes: 28 additions & 0 deletions packages/tui/test/util/sidebar-width.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { describe, expect, test } from "bun:test"
import { clampSidebarWidth } from "../../src/util/sidebar-width"

describe("util.sidebar-width", () => {
test("passes through a configured width within bounds", () => {
expect(clampSidebarWidth(48, 120)).toBe(48)
})

test("clamps widths below the minimum to 20", () => {
expect(clampSidebarWidth(12, 120)).toBe(20)
})

test("clamps widths above the terminal content limit", () => {
expect(clampSidebarWidth(90, 120)).toBe(80)
})

test("clamps widths above the hard cap to 100", () => {
expect(clampSidebarWidth(120, 200)).toBe(100)
})

test("keeps the minimum when terminal bounds are inverted", () => {
expect(clampSidebarWidth(30, 55)).toBe(20)
})

test("defaults absent configuration to 42", () => {
expect(clampSidebarWidth(undefined, 120)).toBe(42)
})
})
Loading