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
tui: vertical session tabs render the working pulse only 10 columns wide #50426
With vertical session tabs (tabs.layout: "vertical"), the animated "session is running" pulse is hardcoded to 10 columns instead of the width of the tab. On a ~40-column rail it looks like a stray short dark stripe that crosses only part of the tab — both the separators above/below the tab and the sweep inside its title/detail rows. Horizontal tabs are fine, which makes the vertical rail look broken/inconsistent.
Confirmed against the current v2 sources: VerticalSessionTabs in packages/tui/src/component/session-tabs.tsx defines const indicatorWidth = 10 and passes it as width to all four of its TabPulse call sites (separator above, separator below, title row, detail row), while the horizontal tab layout passes no width and therefore falls back to props.width ?? "100%" in packages/tui/src/component/tab-pulse.tsx. That matches the reported symptom exactly — only the vertical rail is clipped.
Plan: drop the hardcoded width at those four call sites so the strips span the rail the same way they do in the horizontal layout, and cover it with a render test alongside the existing packages/tui/test/component/session-tabs-*.tsx tests.
passed as width at all four TabPulse sites in VerticalSessionTabs: :964 (edge="above"), :991 (edge="below"), :1012 (title row), :1097 (detail row)
the horizontal TabPulse (:1660) passes no width, so it takes props.width ?? "100%" (packages/tui/src/component/tab-pulse.tsx:792)
What changed my reading is the change that introduced the constant: c894f87771 — feat(tui): refine session tab activity indicators (#42144, merged 2026-08-12). Its description names this exact behaviour as the intent:
vertical tabs use a compact 10-cell running sweep instead of washing the full row
vertical flashes radiate from the shortcut with a feathered falloff
and its scope section says:
This does not change the horizontal tab background pulse geometry.
That same commit added the flash/glow tails on these four sites (flashTail={8}, glowTail={8}, detail glowTail={10}), and TabPulseRenderable clamps every tail to width - 2 (tab-pulse.tsx:675-684) — 8 is the largest falloff a 10-cell strip can express. So the strip width and the tails were sized together, and the horizontal layout keeping "100%" is the difference that PR says it chose to keep.
If the intent now is that the strip should follow the rail width, the change itself is small — drop width={indicatorWidth} so the four sites fall back to "100%" like the horizontal one, or pass the rail width explicitly — but I would expect the tails to need retuning in the same change: an 8-cell falloff near the shortcut does not reach across a 40-column rail, which is the shape those constants were picked for.
So I am not going to open a PR that reverts #42144 on my own reading of it. I am happy to implement whichever direction is wanted, including a middle option (keep the compact sweep on the title/detail rows, let the above/below separators span the row) — just say which.
Thanks for the detailed report! I reproduced this on 2.0.12 and on the latest dev (fa1260d).
Setup: tabs: { enabled: true, layout: "vertical" } in cli.json, a 200-column terminal (so the rail is about 42 columns), and a session kept running. In every frame, the pulse in the active tab's title row, detail row and separator below stays within the first 10 columns; the rest of the tab never animates. The recording below shows the whole run. The attached contrast-boosted crop of the rail makes the faint pulse visible and shows where it stops, and the script below reproduces it.
The cause looks like the one you pointed out: VerticalSessionTabs passes a fixed indicatorWidth = 10 to its four TabPulse calls, while horizontal tabs use the full width. Maintainers will need to confirm whether the short indicator was meant to look this way. Until then, "animations": false remains the workaround.
recording.mp4
Reproduction script (drive.ts)
// Repro for anomalyco/opencode#50426: vertical session tabs clip the "running" pulse to 10 columns.import{Effect}from"effect"import{Llm,OpenCodeDriver}from"opencode-drive"exportdefaultOpenCodeDriver.use({
...(process.env.OC_DEV ? {opencode: {dev: process.env.OC_DEV}} : {}),tuiConfig: {tabs: {enabled: true,layout: "vertical"}},tui: {recording: true,viewport: {cols: 200,rows: 45}},},({ ui, llm })=>Effect.gen(function*(){yield*ui.screenshot("vertical-idle")// Keep the session running for a while so the pulse animates.yield*llm.queue(Llm.pause(12000),Llm.text("Done thinking."))yield*ui.submit("Think about something for a while")for(leti=0;i<8;i++){yield*Effect.sleep(700)yield*ui.screenshot(`vertical-running-${i}`)}// Columns 10+ of the ~42-column rail never get the pulse; compare screenshots.yield*ui.waitFor("Done thinking.",{timeout: 30000}).pipe(Effect.catchTag("UiWaitTimeoutError",()=>Effect.void))}),)
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
argszero commentedon Sep 22, 2026
I'd like to work on this.
Confirmed against the current
v2sources:VerticalSessionTabsinpackages/tui/src/component/session-tabs.tsxdefinesconst indicatorWidth = 10and passes it aswidthto all four of itsTabPulsecall sites (separator above, separator below, title row, detail row), while the horizontal tab layout passes nowidthand therefore falls back toprops.width ?? "100%"inpackages/tui/src/component/tab-pulse.tsx. That matches the reported symptom exactly — only the vertical rail is clipped.Plan: drop the hardcoded width at those four call sites so the strips span the rail the same way they do in the horizontal layout, and cover it with a render test alongside the existing
packages/tui/test/component/session-tabs-*.tsxtests.argszero commentedon Sep 22, 2026
Update — the 10-cell strip looks deliberate, so this reads as a design decision rather than a regression
I read the code and its history before starting. The facts in the report are exact on current
v2:const indicatorWidth = 10—packages/tui/src/component/session-tabs.tsx:826widthat all fourTabPulsesites inVerticalSessionTabs::964(edge="above"),:991(edge="below"),:1012(title row),:1097(detail row)TabPulse(:1660) passes nowidth, so it takesprops.width ?? "100%"(packages/tui/src/component/tab-pulse.tsx:792)What changed my reading is the change that introduced the constant:
c894f87771— feat(tui): refine session tab activity indicators (#42144, merged 2026-08-12). Its description names this exact behaviour as the intent:and its scope section says:
That same commit added the flash/glow tails on these four sites (
flashTail={8},glowTail={8}, detailglowTail={10}), andTabPulseRenderableclamps every tail towidth - 2(tab-pulse.tsx:675-684) — 8 is the largest falloff a 10-cell strip can express. So the strip width and the tails were sized together, and the horizontal layout keeping"100%"is the difference that PR says it chose to keep.If the intent now is that the strip should follow the rail width, the change itself is small — drop
width={indicatorWidth}so the four sites fall back to"100%"like the horizontal one, or pass the rail width explicitly — but I would expect the tails to need retuning in the same change: an 8-cell falloff near the shortcut does not reach across a 40-column rail, which is the shape those constants were picked for.So I am not going to open a PR that reverts #42144 on my own reading of it. I am happy to implement whichever direction is wanted, including a middle option (keep the compact sweep on the title/detail rows, let the above/below separators span the row) — just say which.
opencodejr commentedon Oct 28, 2026
Thanks for the detailed report! I reproduced this on 2.0.12 and on the latest
dev(fa1260d).Setup:
tabs: { enabled: true, layout: "vertical" }incli.json, a 200-column terminal (so the rail is about 42 columns), and a session kept running. In every frame, the pulse in the active tab's title row, detail row and separator below stays within the first 10 columns; the rest of the tab never animates. The recording below shows the whole run. The attached contrast-boosted crop of the rail makes the faint pulse visible and shows where it stops, and the script below reproduces it.The cause looks like the one you pointed out:
VerticalSessionTabspasses a fixedindicatorWidth = 10to its fourTabPulsecalls, while horizontal tabs use the full width. Maintainers will need to confirm whether the short indicator was meant to look this way. Until then,"animations": falseremains the workaround.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.