You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Wrapped sidebar todos overlap and consume the next section gap #57529
Long todo descriptions can overlap in the sidebar and consume the blank row before the next section. The Todo container can shrink below the height of its wrapped text.
Plugins
Reproduced with the native Todo component and a synthetic following section; no third-party plugin code is required.
OpenCode version
2.1.1; also reproduced with the component on current dev.
Steps to reproduce
Use a narrow sidebar with four long todo descriptions that wrap onto multiple lines.
Place another section with a long list after Todo in sidebar.content.
Observe overlapping todo text and no blank row before the following section. Short, unwrapped descriptions retain the gap.
The regression test in the linked PR renders the native component at widths 40 and 50 with 3, 4, and 8 todos, including active and completed states.
Screenshot
Before (left), after preserving the Todo container's height (right):
Rendered from an isolated OpenCode 2.1.1 terminal recording with synthetic text, not an OS screenshot. No personal sessions or configuration are included.
Terminal
Isolated xterm-256color PTY; also reproduced with the OpenTUI test renderer.
Thanks for the detailed report. I reproduced this on 2.1.1, and the same script shows it on the latest dev (3fdb13bc).
Steps:
Turn on experimental.todo. Have the model write four long todos that wrap in the 42-column sidebar.
Add a local TUI plugin that appends a section after Todo in sidebar.content. The section is a box that can grow and shrink (flexGrow={1} minHeight={0}) with a header, a "search…" line and a scroll box of 67 rows, similar to your "Sessions (67)" example.
The Todo section then shrinks below the height of its wrapped text. The first todo loses its last line, stray letters land inside the next todo ("Addrintegrationetestsrcovering"), the next section's header is drawn over "search…", and the blank row before it disappears. The screenshot and recording below show this, and the script below reproduces it.
In my runs it only happened when the following section could shrink. With a plain long list after Todo, or with only the built-in sections, the todos wrapped correctly and the blank row was kept. Your proposed fix is in #57530.
recording.mp4
Reproduction script (drive.ts)
// Repro for anomalyco/opencode#57529: wrapped sidebar todos overlap.import{Effect}from"effect"import{Llm,OpenCodeDriver}from"opencode-drive"consttodos=[{content: "Refactor the authentication middleware so that tokens are refreshed before expiry",status: "completed"},{content: "Add integration tests covering the new session restore flow across reconnects",status: "in_progress"},{content: "Update the configuration documentation to describe every new sidebar option",status: "pending"},{content: "Audit the error handling in the provider adapters for consistent retry behaviour",status: "pending"},]// A synthetic sidebar section placed after Todo: a flexible section with a long, scrollable list// (like a session list plugin). With it, the native Todo section shrinks below its wrapped text.constfollowingPlugin=`/** @jsxImportSource @opentui/solid */import { Plugin } from "@opencode/plugin/tui"export default Plugin.define({ id: "repro.following", setup(context) { context.ui.slot({ append: "sidebar.content", render: () => ( <box flexGrow={1} minHeight={0}> <text fg={context.theme.text.base}><b>Sessions (67)</b></text> <text fg={context.theme.text.muted}>search…</text> <scrollbox flexGrow={1} minHeight={0}> {Array.from({ length: 67 }, (_, i) => <text fg={context.theme.text.muted}>Session {i}</text>)} </scrollbox> </box> ), }) },})`exportdefaultOpenCodeDriver.use({project: {files: {".opencode/plugins/following/tui.tsx": followingPlugin}},
...(process.env.OC_DEV ? {opencode: {dev: process.env.OC_DEV}} : {}),config: {experimental: {todo: true}},tui: {recording: true,viewport: {cols: 130,rows: 40}},},({ ui, llm })=>Effect.gen(function*(){yield*llm.queue(Llm.toolCall({index: 0,id: "call_todo",name: "todowrite",input: { todos }}),Llm.finish("tool-calls"),)constupdated=todos.map((t,i)=>({ ...t,status: i===0 ? "in_progress" : "pending"}))yield*llm.queue(Llm.toolCall({index: 0,id: "call_todo2",name: "todowrite",input: {todos: updated}}),Llm.finish("tool-calls"),)yield*llm.queue(Llm.text("Todo list created."))yield*ui.submit("Plan the work")yield*ui.waitFor("Todo list created.").pipe(Effect.ignore)yield*Effect.sleep("1500 millis")yield*ui.screenshot("sidebar-todos")constframe: any=yield*ui.capture()yield*Effect.log("\n"+frame.lines.map((l: any)=>l.spans.map((s: any)=>s.text).join("")).join("\n"))}),)
Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.
Activity
opencode-agent commentedon Oct 20, 2026
Thanks for the detailed report. I reproduced this on 2.1.1, and the same script shows it on the latest
dev(3fdb13bc).Steps:
experimental.todo. Have the model write four long todos that wrap in the 42-column sidebar.sidebar.content. The section is a box that can grow and shrink (flexGrow={1} minHeight={0}) with a header, a "search…" line and a scroll box of 67 rows, similar to your "Sessions (67)" example.The Todo section then shrinks below the height of its wrapped text. The first todo loses its last line, stray letters land inside the next todo ("Addrintegrationetestsrcovering"), the next section's header is drawn over "search…", and the blank row before it disappears. The screenshot and recording below show this, and the script below reproduces it.
In my runs it only happened when the following section could shrink. With a plain long list after Todo, or with only the built-in sections, the todos wrapped correctly and the blank row was kept. Your proposed fix is in #57530.
recording.mp4
Reproduction script (drive.ts)
Run it with opencode-drive:
opencode-drive run ./drive.ts. It drivesopencodefrom yourPATH; setOC_DEVto the path of an OpenCode checkout to run that from source instead.