From 8658c6458e15711f336ebeeca900052e469de344 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Wed, 24 Dec 2025 02:49:05 -0500 Subject: [PATCH 01/10] feat: Add persistent sidebar overlay toggle in command palette Instead of a config file option, allow toggling sidebar overlay behavior directly from the command palette. The state persists to kv.json. Changes: - Add sidebarOverlayEnabled signal persisted to kv.json (defaults to true) - Add sidebarOverlay toggle command in System category - Add optional sidebar_overlay_toggle keybind config (default: none) This provides instant toggling without requiring restart/reload. --- .../src/cli/cmd/tui/routes/session/index.tsx | 20 ++++++++++++++++++- packages/opencode/src/config/config.ts | 1 + 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index c685d8c66ccb..cb5394286ee9 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -128,6 +128,7 @@ export function Session() { const [showDetails, setShowDetails] = createSignal(kv.get("tool_details_visibility", true)) const [showScrollbar, setShowScrollbar] = createSignal(kv.get("scrollbar_visible", false)) const [userMessageMarkdown, setUserMessageMarkdown] = createSignal(kv.get("user_message_markdown", true)) + const [sidebarOverlayEnabled, setSidebarOverlayEnabled] = createSignal(kv.get("sidebar_overlay", true)) const [diffWrapMode, setDiffWrapMode] = createSignal<"word" | "none">("word") const wide = createMemo(() => dimensions().width > 120) @@ -138,7 +139,10 @@ export function Session() { if (sidebar() === "auto" && wide()) return true return false }) - const sidebarOverlay = createMemo(() => sidebarVisible() && !wide()) + const sidebarOverlay = createMemo(() => { + if (!sidebarOverlayEnabled()) return false + return sidebarVisible() && !wide() + }) const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() && !sidebarOverlay() ? 42 : 0) - 4) const scrollAcceleration = createMemo(() => { @@ -489,6 +493,20 @@ export function Session() { dialog.clear() }, }, + { + title: sidebarOverlayEnabled() ? "Disable sidebar overlay" : "Enable sidebar overlay", + value: "sidebar_overlay", + keybind: "sidebar_overlay_toggle", + category: "System", + onSelect: (dialog) => { + setSidebarOverlayEnabled((prev) => { + const next = !prev + kv.set("sidebar_overlay", next) + return next + }) + dialog.clear() + }, + }, { title: usernameVisible() ? "Hide username" : "Show username", value: "session.username_visible.toggle", diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index ba9d19730255..c9fd55821edf 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -469,6 +469,7 @@ export namespace Config { .default("h") .describe("Toggle code block concealment in messages"), tool_details: z.string().optional().default("none").describe("Toggle tool details visibility"), + sidebar_overlay_toggle: z.string().optional().default("none").describe("Toggle sidebar overlay mode"), model_list: z.string().optional().default("m").describe("List available models"), model_cycle_recent: z.string().optional().default("f2").describe("Next recently used model"), model_cycle_recent_reverse: z.string().optional().default("shift+f2").describe("Previous recently used model"), From fcefdddee38f704613ea78b2212eb45573b9ac14 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Fri, 16 Jan 2026 02:03:31 -0500 Subject: [PATCH 02/10] fix: make sidebar overlay toggle functional The sidebarOverlay() memo was computed but never used in rendering. Changed the Switch/Match conditions from wide() to sidebarOverlay() so the toggle command actually controls overlay behavior. --- packages/opencode/src/cli/cmd/tui/routes/session/index.tsx | 4 ++-- packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 109c58c5adbc..0cb421825537 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -1086,10 +1086,10 @@ export function Session() { - + - + sync.session.get(props.sessionID)!) @@ -77,7 +77,7 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { paddingBottom={1} paddingLeft={2} paddingRight={2} - position={props.overlay ? "absolute" : "relative"} + position="relative" > From 8c4daef7b058f2600e930f511be692d1033ab885 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Fri, 16 Jan 2026 02:26:55 -0500 Subject: [PATCH 03/10] fix: remove redundant kv.set call from sidebar overlay toggle The explicit kv.set() inside the setter callback was causing a nested setStore that interfered with SolidJS reactive tracking. This made the toggle appear to work (value persisted) but the UI wouldn't update. --- packages/opencode/src/cli/cmd/tui/routes/session/index.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 0cb421825537..44f6098e2dc2 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -494,11 +494,7 @@ export function Session() { keybind: "sidebar_overlay_toggle", category: "System", onSelect: (dialog) => { - setSidebarOverlayEnabled((prev) => { - const next = !prev - kv.set("sidebar_overlay", next) - return next - }) + setSidebarOverlayEnabled((prev) => !prev) dialog.clear() }, }, From 1c48fe12499e3056c6edc627e9d6ab67616bd627 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Mon, 26 Jan 2026 00:16:17 -0500 Subject: [PATCH 04/10] fix: unslop --- .../src/cli/cmd/tui/routes/session/index.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index f0ceb21b4e77..450a3c2aee4f 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -519,20 +519,6 @@ export function Session() { dialog.clear() }, }, - { - title: usernameVisible() ? "Hide username" : "Show username", - value: "session.username_visible.toggle", - keybind: "username_toggle", - category: "Session", - onSelect: (dialog) => { - setUsernameVisible((prev) => { - const next = !prev - kv.set("username_visible", next) - return next - }) - dialog.clear() - }, - }, { title: "Toggle code concealment", value: "session.toggle.conceal", From 5f89774da66861d7c65260076bebd9f5ba718d90 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Mon, 26 Jan 2026 01:06:51 -0500 Subject: [PATCH 05/10] fix(app): make sidebar overlay command available before sending messages --- packages/opencode/src/cli/cmd/tui/app.tsx | 10 ++++++++++ .../opencode/src/cli/cmd/tui/routes/session/index.tsx | 10 ---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 4b177e292cf3..7081c69e94bb 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -570,6 +570,16 @@ function App() { dialog.clear() }, }, + { + title: kv.get("sidebar_overlay", true) ? "Disable sidebar overlay" : "Enable sidebar overlay", + value: "sidebar_overlay", + keybind: "sidebar_overlay_toggle", + category: "System", + onSelect: (dialog) => { + kv.set("sidebar_overlay", !kv.get("sidebar_overlay", true)) + dialog.clear() + }, + }, ]) createEffect(() => { diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 450a3c2aee4f..75210a396643 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -509,16 +509,6 @@ export function Session() { dialog.clear() }, }, - { - title: sidebarOverlayEnabled() ? "Disable sidebar overlay" : "Enable sidebar overlay", - value: "sidebar_overlay", - keybind: "sidebar_overlay_toggle", - category: "System", - onSelect: (dialog) => { - setSidebarOverlayEnabled((prev) => !prev) - dialog.clear() - }, - }, { title: "Toggle code concealment", value: "session.toggle.conceal", From 7105a86ea2b1ac057695b5cdc32f24f5bab31d45 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Tue, 27 Jan 2026 08:05:05 -0500 Subject: [PATCH 06/10] Merge dev into feat/persistant-sidebar-overlay-behaviour --- packages/opencode/src/cli/cmd/tui/app.tsx | 30 ----------------------- 1 file changed, 30 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 1965dc2d8ede..db7e473fd0b8 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -549,7 +549,6 @@ function App() { { title: kv.get("sidebar_overlay", true) ? "Disable sidebar overlay" : "Enable sidebar overlay", value: "sidebar_overlay", - keybind: "sidebar_overlay_toggle", category: "System", onSelect: (dialog) => { kv.set("sidebar_overlay", !kv.get("sidebar_overlay", true)) @@ -575,35 +574,6 @@ function App() { dialog.clear() }, }, - }, - { -<<<<<<< HEAD - title: kv.get("sidebar_overlay", true) ? "Disable sidebar overlay" : "Enable sidebar overlay", - value: "sidebar_overlay", - keybind: "sidebar_overlay_toggle", - category: "System", - onSelect: (dialog) => { - kv.set("sidebar_overlay", !kv.get("sidebar_overlay", true)) -======= - title: kv.get("animations_enabled", true) ? "Disable animations" : "Enable animations", - value: "app.toggle.animations", - category: "System", - onSelect: (dialog) => { - kv.set("animations_enabled", !kv.get("animations_enabled", true)) - dialog.clear() - }, - }, - { - title: kv.get("diff_wrap_mode", "word") === "word" ? "Disable diff wrapping" : "Enable diff wrapping", - value: "app.toggle.diffwrap", - category: "System", - onSelect: (dialog) => { - const current = kv.get("diff_wrap_mode", "word") - kv.set("diff_wrap_mode", current === "word" ? "none" : "word") ->>>>>>> dev - dialog.clear() - }, - }, ]) createEffect(() => { From 66a2a405f2de9914a410faf1f986cc089311caa3 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Thu, 29 Jan 2026 17:59:26 -0500 Subject: [PATCH 07/10] fix: hide Header when sidebar is open in inline mode Changed Header visibility condition from !sidebarVisible() || !wide() to !sidebarVisible() || sidebarOverlay() so that the title bar is hidden when the sidebar is displayed inline (not as an overlay), regardless of screen width. This ensures the title doesn't appear twice when overlay mode is disabled and the sidebar is open. --- packages/opencode/src/cli/cmd/tui/routes/session/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 1c8b17b9af5a..55884c7f4373 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -948,7 +948,7 @@ export function Session() { - +
Date: Sun, 8 Feb 2026 12:10:09 -0500 Subject: [PATCH 08/10] Remove unrelated user_message_markdown code from sidebar overlay branch --- packages/opencode/src/cli/cmd/tui/routes/session/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index b7238a5ed3af..0eaa0cc352eb 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -150,7 +150,6 @@ export function Session() { const [showDetails, setShowDetails] = kv.signal("tool_details_visibility", true) const [showAssistantMetadata, setShowAssistantMetadata] = kv.signal("assistant_metadata_visibility", true) const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", false) - const [userMessageMarkdown, setUserMessageMarkdown] = kv.signal("user_message_markdown", true) const [sidebarOverlayEnabled, setSidebarOverlayEnabled] = kv.signal("sidebar_overlay", true) const [diffWrapMode] = kv.signal<"word" | "none">("diff_wrap_mode", "word") const [animationsEnabled, setAnimationsEnabled] = kv.signal("animations_enabled", true) From 424f4f309ae16db9a52401d60ddd90ef66cc9947 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Sun, 8 Feb 2026 12:12:41 -0500 Subject: [PATCH 09/10] Remove unrelated username_visible code from sidebar overlay branch --- packages/opencode/src/cli/cmd/tui/routes/session/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 0eaa0cc352eb..e7430ea9eca2 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -146,7 +146,6 @@ export function Session() { const [conceal, setConceal] = createSignal(true) const [showThinking, setShowThinking] = kv.signal("thinking_visibility", true) const [timestamps, setTimestamps] = kv.signal<"hide" | "show">("timestamps", "hide") - const [usernameVisible, setUsernameVisible] = kv.signal("username_visible", true) const [showDetails, setShowDetails] = kv.signal("tool_details_visibility", true) const [showAssistantMetadata, setShowAssistantMetadata] = kv.signal("assistant_metadata_visibility", true) const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", false) From 836b359f29431b8211599ba792e0560e0fee34f1 Mon Sep 17 00:00:00 2001 From: Ariane Emory Date: Fri, 24 Apr 2026 07:09:48 -0400 Subject: [PATCH 10/10] fix: remove out-of-scope header component and restore sidebar position prop --- .../src/cli/cmd/tui/routes/session/header.tsx | 174 ------------------ .../src/cli/cmd/tui/routes/session/index.tsx | 14 -- .../cli/cmd/tui/routes/session/sidebar.tsx | 2 +- 3 files changed, 1 insertion(+), 189 deletions(-) delete mode 100644 packages/opencode/src/cli/cmd/tui/routes/session/header.tsx diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx deleted file mode 100644 index ca7e8f30fd31..000000000000 --- a/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import { type Accessor, createMemo, createSignal, Match, Show, Switch } from "solid-js" -import { useRouteData } from "@tui/context/route" -import { useProject } from "@tui/context/project" -import { useSync } from "@tui/context/sync" -import { pipe, sumBy } from "remeda" -import { useTheme } from "@tui/context/theme" -import { SplitBorder } from "@tui/component/border" -import type { AssistantMessage, Session } from "@opencode-ai/sdk/v2" -import { useCommandDialog } from "@tui/component/dialog-command" -import { useKeybind } from "../../context/keybind" -import { Flag } from "@/flag/flag" -import { useTerminalDimensions } from "@opentui/solid" - -const Title = (props: { session: Accessor }) => { - const { theme } = useTheme() - return ( - - # {props.session().title} - - ) -} - -const ContextInfo = (props: { context: Accessor; cost: Accessor }) => { - const { theme } = useTheme() - return ( - - - {props.context()} ({props.cost()}) - - - ) -} - -const WorkspaceInfo = (props: { workspace: Accessor }) => { - const { theme } = useTheme() - return ( - - - {props.workspace()} - - - ) -} - -export function Header() { - const route = useRouteData("session") - const sync = useSync() - const project = useProject() - const session = createMemo(() => sync.session.get(route.sessionID)!) - const messages = createMemo(() => sync.data.message[route.sessionID] ?? []) - - const cost = createMemo(() => { - const total = pipe( - messages(), - sumBy((x) => (x.role === "assistant" ? x.cost : 0)), - ) - return new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - }).format(total) - }) - - const context = createMemo(() => { - const last = messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage - if (!last) return - const total = - last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write - const model = sync.data.provider.find((x) => x.id === last.providerID)?.models[last.modelID] - let result = total.toLocaleString() - if (model?.limit.context) { - result += " " + Math.round((total / model.limit.context) * 100) + "%" - } - return result - }) - - const workspace = createMemo(() => { - const id = session()?.workspaceID - if (!id) return "Workspace local" - const info = project.workspace.get(id) - if (!info) return `Workspace ${id}` - return `Workspace ${id} (${info.type})` - }) - - const { theme } = useTheme() - const keybind = useKeybind() - const command = useCommandDialog() - const [hover, setHover] = createSignal<"parent" | "prev" | "next" | null>(null) - const dimensions = useTerminalDimensions() - const narrow = createMemo(() => dimensions().width < 80) - - return ( - - - - - - - {Flag.OPENCODE_EXPERIMENTAL_WORKSPACES ? ( - - - Subagent session - - - - ) : ( - - Subagent session - - )} - - - - - setHover("parent")} - onMouseOut={() => setHover(null)} - onMouseUp={() => command.trigger("session.parent")} - backgroundColor={hover() === "parent" ? theme.backgroundElement : theme.backgroundPanel} - > - - Parent {keybind.print("session_parent")} - - - setHover("prev")} - onMouseOut={() => setHover(null)} - onMouseUp={() => command.trigger("session.child.previous")} - backgroundColor={hover() === "prev" ? theme.backgroundElement : theme.backgroundPanel} - > - - Prev {keybind.print("session_child_cycle_reverse")} - - - setHover("next")} - onMouseOut={() => setHover(null)} - onMouseUp={() => command.trigger("session.child.next")} - backgroundColor={hover() === "next" ? theme.backgroundElement : theme.backgroundPanel} - > - - Next {keybind.print("session_child_cycle")} - - - - - - - - {Flag.OPENCODE_EXPERIMENTAL_WORKSPACES ? ( - - - <WorkspaceInfo workspace={workspace} /> - </box> - ) : ( - <Title session={session} /> - )} - <ContextInfo context={context} cost={cost} /> - </box> - </Match> - </Switch> - </box> - </box> - ) -} diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 2dcab4539c46..b03cd2b8cbac 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -54,7 +54,6 @@ import { useSDK } from "@tui/context/sdk" import { useCommandDialog } from "@tui/component/dialog-command" import type { DialogContext } from "@tui/ui/dialog" import { useKeybind } from "@tui/context/keybind" -import { Header } from "./header" import { useDialog } from "../../ui/dialog" import { TodoItem } from "../../component/todo-item" import { DialogMessage } from "./dialog-message" @@ -164,7 +163,6 @@ export function Session() { const [showAssistantMetadata, _setShowAssistantMetadata] = kv.signal("assistant_metadata_visibility", true) const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", false) const [sidebarOverlayEnabled, setSidebarOverlayEnabled] = kv.signal("sidebar_overlay", true) - const [showHeader, setShowHeader] = kv.signal("header_visible", true) const [diffWrapMode] = kv.signal<"word" | "none">("diff_wrap_mode", "word") const [_animationsEnabled, _setAnimationsEnabled] = kv.signal("animations_enabled", true) const [showGenericToolOutput, setShowGenericToolOutput] = kv.signal("generic_tool_output_visibility", false) @@ -680,15 +678,6 @@ export function Session() { dialog.clear() }, }, - { - title: showHeader() ? "Hide header" : "Show header", - value: "session.toggle.header", - category: "Session", - onSelect: (dialog) => { - setShowHeader((prev) => !prev) - dialog.clear() - }, - }, { title: showGenericToolOutput() ? "Hide generic tool output" : "Show generic tool output", value: "session.toggle.generic_tool_output", @@ -1071,9 +1060,6 @@ export function Session() { <box flexDirection="row"> <box flexGrow={1} paddingBottom={1} paddingTop={1} paddingLeft={2} paddingRight={2} gap={1}> <Show when={session()}> - <Show when={showHeader() && (!sidebarVisible() || sidebarOverlay())}> - <Header /> - </Show> <scrollbox ref={(r) => (scroll = r)} viewportOptions={{ diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx index 71ab73cf2ac4..6d92752efe36 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx @@ -38,7 +38,7 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { paddingBottom={1} paddingLeft={2} paddingRight={2} - position="relative" + position={props.overlay ? "absolute" : "relative"} > <scrollbox flexGrow={1}