diff --git a/CHANGELOG.md b/CHANGELOG.md index 239f0efdcc..3939ab954d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`/`b` only affects the current session. + ## 1.18.2-1 ### Sessions diff --git a/DIVERGENCES.md b/DIVERGENCES.md index 8c938fc194..639f1a467b 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -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... ""` 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`/`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`/`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 `` (all additive); take upstream's surrounding changes. If upstream starts passing its own `hint` to this ``, 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. | diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index 5c64e735fb..88740121f4 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -1,5 +1,4 @@ import { - batch, createContext, createEffect, createMemo, @@ -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() @@ -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) @@ -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() }, }, diff --git a/packages/tui/test/sidebar-hint.view.test.tsx b/packages/tui/test/sidebar-hint.view.test.tsx index 5609673fc5..3241f52a5d 100644 --- a/packages/tui/test/sidebar-hint.view.test.tsx +++ b/packages/tui/test/sidebar-hint.view.test.tsx @@ -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([])