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
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