From a1a6b8077c03b455f1edf81105da03476a82337c Mon Sep 17 00:00:00 2001 From: Naya Fytali <125997387+nayafyt@users.noreply.github.com> Date: Tue, 11 Aug 2026 09:20:18 +0000 Subject: [PATCH] fix(tui): prevent sidebar selection copy bleed --- packages/tui/src/routes/session/sidebar.tsx | 14 +++++++ .../tui/test/util/sidebar-selection.test.ts | 37 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 packages/tui/test/util/sidebar-selection.test.ts diff --git a/packages/tui/src/routes/session/sidebar.tsx b/packages/tui/src/routes/session/sidebar.tsx index 0c5d2b313967..e7016a460668 100644 --- a/packages/tui/src/routes/session/sidebar.tsx +++ b/packages/tui/src/routes/session/sidebar.tsx @@ -5,10 +5,21 @@ import { useTheme } from "../../context/theme" import { useTuiConfig } from "../../config" import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version" import { usePluginRuntime } from "../../plugin/runtime" +import type { Renderable } from "@opentui/core" import { getScrollAcceleration } from "../../util/scroll" import { WorkspaceLabel } from "../../component/workspace-label" +export function disableSelectionSubtree(root: Renderable) { + root.selectable = false + const stack = [...root.getChildren()] + while (stack.length > 0) { + const node = stack.pop()! + node.selectable = false + stack.push(...node.getChildren()) + } +} + export function Sidebar(props: { sessionID: string; overlay?: boolean }) { const pluginRuntime = usePluginRuntime() const project = useProject() @@ -26,6 +37,9 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { return ( MockRenderable[] +} + +function mockRenderable(children: MockRenderable[] = []): MockRenderable { + return { + selectable: true, + children, + getChildren() { + return this.children + }, + } +} + +function asRenderable(node: MockRenderable) { + return node as unknown as Renderable +} + +test("disableSelectionSubtree disables selection for all descendants", () => { + const leaf = mockRenderable() + const nested = mockRenderable([leaf]) + const sibling = mockRenderable() + const root = mockRenderable([nested, sibling]) + + disableSelectionSubtree(asRenderable(root)) + + expect(root.selectable).toBe(false) + expect(nested.selectable).toBe(false) + expect(leaf.selectable).toBe(false) + expect(sibling.selectable).toBe(false) +})