Skip to content
Merged
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ file is the human-readable summary of what changed and why.

## Unreleased

### Sidebar

- **Sidebar always starts closed** — the TUI right sidebar no longer remembers
its open/closed state across sessions. Previously, toggling it open persisted
the choice in `kv.json`, so a new session on a wide terminal (>120 columns)
started with the sidebar open. Now every session starts closed and
`ctrl+b`/`<leader>b` only affects the current session.

## 1.18.2-1

### Sessions
Expand Down
2 changes: 1 addition & 1 deletion DIVERGENCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ from the squash message first, or use a plain merge.
| `packages/opencode/src/cli/cmd/*.ts` (tui, run, serve, attach, pr, upgrade, uninstall, web) | Help `describe` strings "opencode" → "codev" | `--help` is the first thing users read. | Keep our strings. |
| `packages/tui/src/app.tsx` | Terminal title "OpenCode"/"OC \|" → "CoDev Code"/"CC \|"; the configure-gate effect is a stateless forced-setup invariant — while `sync.status === "complete" && !useConnected() && dialog.stack.length === 0` it (re)opens the fork's `DialogConfigure` (component/dialog-configure.tsx, new file), so escaping any gate-launched flow or a credential-only save bounces back to the gate instead of an unconfigured chat (gates on `!useConnected()`, not `provider.length === 0` — hosted Zen free models always register); added the `codev.configure` command (`/configure`) and a `CODEV_CONFIGURE=1` mount hook for `codev configure`; home screen skipped — plain startup seeds the `dummy` session route and auto-opens a new empty session (reusing a leftover default-titled empty one via `openNewSession`/`findEmptySession`), `session.new` and the current-session `session.deleted` handler open a new session instead of navigating home, and the `-c` resolver prefers non-default-titled sessions so it lands on the last real conversation; added the `app.exit.confirm` command + `EXIT_CONFIRM_WINDOW` const (double-press Ctrl+C to quit: first press shows a toast, second within 1.5 s exits) and gathered its `app_exit_confirm` binding alongside `app_exit` | Tab title branding; first-run setup is a forced gate the user can't escape into an unconfigured chat (the invariant reopens it); `codev` starts directly in the session layout instead of the home screen; a stray Ctrl+C no longer quits on the first press. | Keep our strings, the invariant gate effect, dialog swap + command block, route seed, `openNewSession`/`findEmptySession` + call sites, the `-c` default-title filter, and the `app.exit.confirm` command + binding; take upstream's surrounding changes. |
| `packages/tui/src/routes/session/index.tsx` | Flow transcript: the scrollbox opens with a home-like banner (`Logo` + `home_bottom` tips slot, `placement="start"`) as its first entry for root sessions (subagent sessions keep a plain 1-row pad), its `maxHeight` is clamped to the content height (a `transcriptHeight` signal read off the content's yoga node in the renderer root's `layout-changed` handler, which then re-runs `calculateLayout` so the clamp lands in the same frame — `scrollHeight` is a frame stale there, and painting with a stale clamp flickers during streaming) so the `Prompt` rides directly below the last message, and a trailing flexGrow spacer absorbs the leftover space until content fills the screen (then the prompt is effectively bottom-anchored and the banner scrolls away with history); the session `Prompt` gets home's `placeholder` pool when the session has no messages so the input shows an `Ask anything... "<suggestion>"` placeholder like home | Startup lands in an empty session instead of the home screen; the transcript flows top-down like terminal scrollback — logo top-left, input following the conversation down — instead of a bottom-anchored prompt with a void above it. | Keep our banner block inside the scrollbox, the `transcriptHeight` clamp + `layout-changed` listener, the trailing spacer, and the `placeholders` prop; take upstream's surrounding changes. |
| `packages/tui/src/routes/session/index.tsx` | Sidebar kv default `"auto"` → `"hide"` (the `kv.signal<"auto" \| "hide">("sidebar", …)` initial value) | The right sidebar starts hidden regardless of terminal width; `ctrl+b`/`<leader>b` opens it, and the choice persists in kv as before. | Keep our `"hide"` default; take upstream's surrounding changes. |
| `packages/tui/src/routes/session/index.tsx` | Sidebar visibility is session-local: removed upstream's persisted `kv.signal<"auto" \| "hide">("sidebar", "auto")` (and the `sidebar() === "auto" && wide()` branch of `sidebarVisible`); visibility is driven only by the non-persisted `sidebarOpen` signal, which the `session.sidebar.toggle` command flips (supersedes the earlier `"auto"` → `"hide"` default change) | The right sidebar starts hidden in every session regardless of terminal width or what the user did last session; `ctrl+b`/`<leader>b` only affects the current session. A stale `sidebar` key in existing kv.json files is simply never read. | Keep the kv signal removed and `sidebarVisible` reading only `parentID` + `sidebarOpen`; take upstream's surrounding changes. |
| `packages/tui/src/routes/session/index.tsx` | Sidebar hint in the prompt footer: a `sidebarHint` memo (active-todo count from `sync.data.todo`, modified-file count from `sync.data.session_diff`, only when the sidebar is hidden and the session isn't a subagent; the label text comes from `sidebarHintLabel` in the fork-only `routes/session/sidebar-hint.ts`) plus a `sidebarShortcut` (`useCommandShortcut("session.sidebar.toggle")`), passed as the `Prompt`'s previously unused `hint` prop — e.g. `ctrl+b 3 todos · 5 files` | The sidebar starts hidden in this fork (see the row above), so users would otherwise never learn there are todos/modified files or how to reveal them. | Keep our `sidebarShortcut`/`sidebarHint` memos, the `sidebar-hint` import, and the `hint={…}` prop on `<Prompt>` (all additive); take upstream's surrounding changes. If upstream starts passing its own `hint` to this `<Prompt>`, merge the two under one `Show`. |
| `packages/plugin/src/tui.ts` | `home_bottom` slot props widened from `{}` to `{ placement?: "center" \| "start" }` | The empty-session banner asks `home_bottom` contributors to left-align; optional prop keeps the home route and third-party plugins (no prop → centered) unchanged. | Keep our `placement` prop; take upstream's other slot-map changes. |
| `packages/tui/src/feature-plugins/home/tips.tsx` | `home_bottom` slot fn reads the new `placement` prop; with `"start"` the tips box left-aligns (`flex-start`) and drops its `paddingTop` 3 | The top-left session banner needs the tip flush under the logo; the centered home route keeps upstream spacing. | Keep our `placement` threading + conditional alignItems/paddingTop; take upstream's surrounding changes. |
Expand Down
12 changes: 2 additions & 10 deletions packages/tui/src/routes/session/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
batch,
createContext,
createEffect,
createMemo,
Expand Down Expand Up @@ -249,7 +248,6 @@ export function Session() {
})

const dimensions = useTerminalDimensions()
const [sidebar, setSidebar] = kv.signal<"auto" | "hide">("sidebar", "hide")
const [sidebarOpen, setSidebarOpen] = createSignal(false)
const [conceal, setConceal] = createSignal(true)
const thinking = useThinkingMode()
Expand All @@ -266,9 +264,7 @@ export function Session() {
const wide = createMemo(() => dimensions().width > 120)
const sidebarVisible = createMemo(() => {
if (session()?.parentID) return false
if (sidebarOpen()) return true
if (sidebar() === "auto" && wide()) return true
return false
return sidebarOpen()
})
const showTimestamps = createMemo(() => timestamps() === "show")
const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 42 : 0) - 4)
Expand Down Expand Up @@ -707,11 +703,7 @@ export function Session() {
value: "session.sidebar.toggle",
category: "Session",
run: () => {
batch(() => {
const isVisible = sidebarVisible()
setSidebar(() => (isVisible ? "hide" : "auto"))
setSidebarOpen(!isVisible)
})
setSidebarOpen(!sidebarVisible())
dialog.clear()
},
},
Expand Down
2 changes: 1 addition & 1 deletion packages/tui/test/sidebar-hint.view.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ test("prompt footer hints hidden-sidebar todos and files, live and toggle-aware"
const state = await fs.mkdtemp(path.join(os.tmpdir(), "tui-hint-kv-"))
const events = createEventSource()
const calls = createFetch((url) => {
// Sandbox kv.json writes (sidebar toggling persists the kv signal).
// Sandbox kv.json writes.
if (url.pathname === "/path") return json({ home: "", state, config: "", worktree, directory })
if (url.pathname === "/session") return json([session])
if (url.pathname.startsWith(`/session/${session.id}/message`)) return json([])
Expand Down
Loading