Skip to content
Closed
4 changes: 3 additions & 1 deletion packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@ export function Session() {
})

const dimensions = useTerminalDimensions()
const [sidebar, setSidebar] = kv.signal<"auto" | "hide">("sidebar", "hide")
const validSidebar = (v: unknown): v is "auto" | "hide" => v === "auto" || v === "hide"
const sidebarDefault = validSidebar(sync.data.config.tui?.sidebar) ? sync.data.config.tui.sidebar : "hide"
const [sidebar, setSidebar] = kv.signal<"auto" | "hide">("sidebar", sidebarDefault)
const [sidebarOpen, setSidebarOpen] = createSignal(false)
const [conceal, setConceal] = createSignal(true)
const [showThinking, setShowThinking] = kv.signal("thinking_visibility", true)
Expand Down
4 changes: 4 additions & 0 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,10 @@ export namespace Config {
.enum(["auto", "stacked"])
.optional()
.describe("Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column"),
sidebar: z
.enum(["auto", "show", "hide"])
.optional()
.describe("Sidebar visibility: 'auto' shows on wide terminals, 'show' always visible, 'hide' always hidden"),
})

export const Server = z
Expand Down
49 changes: 49 additions & 0 deletions packages/opencode/test/cli/tui/sidebar-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { test, expect, describe } from "bun:test"

// Tests for TUI sidebar config behavior
// The sidebar uses: sync.data.config.tui?.sidebar ?? kv.get("sidebar", "auto")
// Priority: config.tui.sidebar > KV store > "auto"
// This allows per-workspace config to override global KV preferences

describe("TUI Sidebar Config Behavior", () => {
test("config takes precedence over KV store for per-workspace settings", () => {
// Workspace config set to "hide" overrides global KV preference of "show"
const configValue = "hide"
const kvValue = "show"
const defaultValue = "auto"

const result = configValue ?? kvValue ?? defaultValue

expect(result).toBe("hide")
})

test("KV store is used when no workspace config is set", () => {
// No workspace config, use global KV preference
const configValue = undefined
const kvValue = "show"
const defaultValue = "auto"

const result = configValue ?? kvValue ?? defaultValue

expect(result).toBe("show")
})

test("default is used when neither KV nor config is set", () => {
const kvValue = undefined
const configValue = undefined
const defaultValue = "auto"

const result = kvValue ?? configValue ?? defaultValue

expect(result).toBe("auto")
})

test("all valid sidebar values are accepted", () => {
const validValues = ["auto", "show", "hide"] as const

for (const value of validValues) {
const result = value
expect(["auto", "show", "hide"]).toContain(result)
}
})
})
74 changes: 74 additions & 0 deletions packages/opencode/test/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,80 @@ test("merges legacy tools with existing permission config", async () => {
})
})

// TUI sidebar config tests
// The TUI uses: sync.data.config.tui?.sidebar ?? kv.get("sidebar", "auto")
// Priority: Config > KV store > "auto"
// Config provides per-workspace default, KV provides global fallback

test("sidebar is undefined when not set in config", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
tui: {},
}),
)
},
})
await Instance.provide({
directory: tmp.path,
fn: async () => {
const config = await Config.get()
expect(config.tui?.sidebar).toBeUndefined()
},
})
})

test("loads TUI sidebar config with custom value", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
tui: {
sidebar: "hide",
},
}),
)
},
})
await Instance.provide({
directory: tmp.path,
fn: async () => {
const config = await Config.get()
expect(config.tui?.sidebar).toBe("hide")
},
})
})

test("TUI sidebar config accepts all valid values", async () => {
for (const value of ["auto", "show", "hide"] as const) {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
tui: {
sidebar: value,
},
}),
)
},
})
await Instance.provide({
directory: tmp.path,
fn: async () => {
const config = await Config.get()
expect(config.tui?.sidebar).toBe(value)
},
})
}
})

test("permission config preserves key order", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
Expand Down
4 changes: 4 additions & 0 deletions packages/sdk/js/src/v2/gen/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1563,6 +1563,10 @@ export type Config = {
* Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column
*/
diff_style?: "auto" | "stacked"
/**
* Default sidebar visibility: 'auto' shows on wide terminals, 'show' always visible, 'hide' always hidden
*/
sidebar?: "auto" | "show" | "hide"
}
server?: ServerConfig
/**
Expand Down
5 changes: 5 additions & 0 deletions packages/sdk/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -9112,6 +9112,11 @@
"description": "Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column",
"type": "string",
"enum": ["auto", "stacked"]
},
"sidebar": {
"description": "Control sidebar visibility: 'show' always shows sidebar, 'hide' always hides it, 'auto' adapts to terminal width",
"type": "string",
"enum": ["show", "hide", "auto"]
}
}
},
Expand Down
10 changes: 9 additions & 1 deletion packages/web/src/content/docs/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,24 @@ You can configure TUI-specific settings through the `tui` option.
"scroll_acceleration": {
"enabled": true
},
"diff_style": "auto"
"diff_style": "auto",
"sidebar": "hide"
}
}
```

Set `sidebar` to control the initial state of the session sidebar:

- `"auto"` (default) shows the sidebar when there is enough horizontal space.
- `"show"` always shows the sidebar on launch.
- `"hide"` keeps the sidebar hidden until toggled.

Available options:

- `scroll_acceleration.enabled` - Enable macOS-style scroll acceleration. **Takes precedence over `scroll_speed`.**
- `scroll_speed` - Custom scroll speed multiplier (default: `1`, minimum: `1`). Ignored if `scroll_acceleration.enabled` is `true`.
- `diff_style` - Control diff rendering. `"auto"` adapts to terminal width, `"stacked"` always shows single column.
- `sidebar` - Initial sidebar visibility. `"auto"` shows on wide terminals, `"show"` always visible, `"hide"` always hidden.

[Learn more about using the TUI here](/docs/tui).

Expand Down
4 changes: 3 additions & 1 deletion packages/web/src/content/docs/tui.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,8 @@ You can customize TUI behavior through your OpenCode config file.
"scroll_speed": 3,
"scroll_acceleration": {
"enabled": true
}
},
"sidebar": "hide"
}
}
```
Expand All @@ -359,6 +360,7 @@ You can customize TUI behavior through your OpenCode config file.

- `scroll_acceleration` - Enable macOS-style scroll acceleration for smooth, natural scrolling. When enabled, scroll speed increases with rapid scrolling gestures and stays precise for slower movements. **This setting takes precedence over `scroll_speed` and overrides it when enabled.**
- `scroll_speed` - Controls how fast the TUI scrolls when using scroll commands (minimum: `1`). Defaults to `1` on Unix and `3` on Windows. **Note: This is ignored if `scroll_acceleration.enabled` is set to `true`.**
- `sidebar` - Sets the initial sidebar behavior (default: `"auto"` uses the responsive layout, `"show"` forces it visible, `"hide"` keeps it hidden until toggled)

---

Expand Down
Loading