Skip to content

Commit 05422d6

Browse files
quickbeardclaude
andcommitted
feat(app): register sidebar.toggle so View → Toggle Sidebar works
The app menu dispatches command id "sidebar.toggle", and only LegacyLayout ever registered it — so on the new layout the item did nothing. AppSidebarProvider now registers it with no keybind, since mod+b belongs to Home here rather than to the sidebar as it does in the legacy layout. It lives on the provider rather than on AppSidebar so that collapsing the sidebar does not unregister the one command that brings it back. Verified through the command registry, which is the path the menu item takes: opened the palette with mod+k, typed "toggle sid", got "Toggle sidebar" as a match, and selecting it collapsed the sidebar. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent cacc558 commit 05422d6

3 files changed

Lines changed: 24 additions & 3 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ file is the human-readable summary of what changed and why.
1616
sidebar, so switching project means opening it again. The second, next to
1717
Home, opens the recent-sessions list exactly as **View all** does and drops the
1818
cursor straight into its search field. Neither has a keyboard shortcut, so
19-
nothing you already press has changed meaning.
19+
nothing you already press has changed meaning. **View → Toggle Sidebar** in the
20+
app menu now works too — it had no effect on this layout, since the command it
21+
dispatches was only ever registered by the old one.
2022

2123
### Desktop / Web UI
2224

‎DIVERGENCES.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ from the squash message first, or use a plain merge.
9898
| `packages/app/src/pages/home.tsx`, `packages/app/src/pages/error.tsx`, `packages/enterprise/src/routes/share/[shareID].tsx` | `Logo` width utilities retuned for the codev wordmark's 4:1 box (upstream's is 5.57:1): home `md:w-xl` → `md:w-104`, error `w-58.5` → `w-42`, share (both call sites) `w-58.5` → `w-42` | Each value is the width at which the new wordmark renders at exactly the height the upstream one did, so no surrounding layout shifts. | Keep our widths; take upstream's surrounding changes. Recompute rather than restore if upstream changes the size. |
9999
| `packages/app/src/components/titlebar.tsx` | Two changes, both to the **v2 titlebar branch only** (`useV2Titlebar()`); the legacy branch below it is untouched. (1) The Home button (and its `home.toggle` command, `mod+b`) opens a new session instead of toggling between `/` and the last active tab: `tabs.toggleHome(...)` becomes a `goHome` that selects an already-open draft if there is one and otherwise falls through to the existing `openNewTab()` project-resolution chain. Its pressed / `aria-pressed` state keys on `layout.route().type === "draft"` rather than `"home"`. The icon, label (`home.title`) and keybind are unchanged, as is `tabs.toggleHome` itself, which is simply no longer called. (2) `<ChannelIndicator>` is dropped from that row and the component gains an `export`, so the sidebar can render the DEV/BETA badge at its bottom instead; `Titlebar` keeps its `debugTools` prop because the legacy branch still renders the badge inline. (3) Two buttons added to the row: a sidebar collapse toggle (`sidebar-right` icon, `data-action="titlebar-sidebar-toggle"`, `pressed` while open) as its **first** child, ahead of the `windows() || linux()` `WindowsAppMenu`; and a Search button (`magnifying-glass`, `data-action="titlebar-search"`) immediately after Home, navigating to `/?focus=search`. The toggle reads the fork-only `useAppSidebar()` and is wrapped in `<Show when={appSidebar}>`, which is what keeps it out of the legacy shell. Labels reuse the existing `command.sidebar.toggle` and `home.sessions.search.placeholder` keys — no new i18n. | The home screen is a new session in this fork, so Home has to go there; toggling into the recent-sessions list contradicted that. Reuse-before-create keeps repeated presses from stacking up draft tabs — unlike the New session button, which is expected to always make a new one. Moving the badge lets Home lead the titlebar (measured: x 137 → 92 on macOS, the traffic-light inset). | Keep our `goHome` and the removed indicator. If upstream reworks the titlebar, re-apply both rather than restoring `toggleHome` or the inline badge. If upstream deletes `tabs.toggleHome`, nothing here breaks. Do not remove `Titlebar`'s `debugTools` prop — the legacy titlebar still needs it. Keep `useAppSidebar()` optional-by-design: `useV2Titlebar()` is just `newLayoutDesigns()`, so this titlebar also renders inside the legacy shell, which has no provider — swapping it for `createSimpleContext` would throw there (verified: with the new layout off, the legacy shell renders its own rail, no toggle, and no exception). |
100100
| `packages/app/src/pages/layout-new.tsx` (again) | Mounts the fork-only `<LaunchNewSession />`, which on desktop redirects one time per boot to a new-session draft (reusing an existing empty draft rather than stacking one up per launch). It pins its decision to the pathname the window came up on and disarms as soon as anything navigates, so it can never hijack a later visit to `/` — the sidebar's "View all" links there. Desktop-only: in the browser there is no "launch" distinct from "navigate to /", so redirecting would make the recents screen unreachable by URL and would break two `packages/app/e2e` specs that `goto("/")`. | Launching should put you in front of a prompt, the way Claude Desktop does, rather than on a list. This supersedes the desktop's restore-last-screen behaviour (`getLastActiveUrl`, `packages/desktop/src/renderer/index.tsx`); the previous session stays one click away in Recents and its tab is still restored. | Keep the mount. If upstream restructures this layout, re-add it. Do not "simplify" the boot-window guard into a plain `location.pathname === "/"` check — that is the bug it exists to prevent. |
101-
| `packages/app/src/pages/layout-new.tsx`, `packages/app/src/pages/home.tsx`, `packages/app/src/i18n/{en,es}.ts` | **Persistent app sidebar.** Upstream's project column is an `<aside>` owned by `NewHome`, so it exists only on `/`. It becomes app chrome: `layout-new.tsx` wraps its `<main>` in a row flex and renders the fork-only `<AppSidebar class="hidden lg:flex">` beside it, so the sidebar survives every navigation. **The row components stay in `home.tsx`** — `HomeUtilityNav`, `HomeServerRow`, `HomeProjectList`, `HomeProjectEmpty`, `HomeRecentlyClosedRow` and the classes `HOME_PROJECT_NAV_ROW` / `HOME_PROJECT_NAV_LABEL` / `HOME_SECTION_LABEL` gain an `export` and nothing else, so upstream edits to them still apply cleanly; the fork-only `src/components/app-sidebar.tsx` imports and composes them, adding only the column shell and the Recents section. `home.tsx` otherwise loses just `HomeProjectColumn` (dead once the sidebar owns that composition) and the `NewHome` selection/action block, and its grid collapses from `lg:grid-cols-[280px_minmax(0,720px)]` to one `max-w-[760px]` column. The sub-`lg` `HomeUtilityNav` mount is untouched, so the stacked mobile layout is upstream's. The state both screens need (`focusedServer`, `projects`, `selectedProject`, `newSessionProject`, `projectDirectories`, the session-index query, `openNewSession`, `openProjectNewSession`, `openSession`, `selectProject`, `addProjects`, `editProject`, `closeProject`, `unseenCount`, `clearNotifications`, `chooseProject`, `isBackgroundOpen`, `buildHomeSessionRecords`, `HomeSessionRecord`) moves to fork-only `src/pages/home-model.tsx`, which both callers instantiate — safe because the selection lives in `useLayout().home`, the index is one TanStack query deduped by key, and the server-normalizing effect is idempotent. New i18n keys `sidebar.recents`, `sidebar.recents.new`, `sidebar.recents.viewAll` in `en` + `es`. `layout-new.tsx` additionally wraps its shell in the fork-only `<AppSidebarProvider>` (so the titlebar toggle and the sidebar share one collapse state), `AppSidebar` renders nothing while collapsed, and `NewHome` consumes a `?focus=search` param — clearing it after use, the way `pages/new-session.tsx` handles `?prompt` — to focus the search field for the titlebar's Search button. That focus ref changed from a `let` to a signal because `HomeSessionSearch` binds it from `onMount`, which runs after `NewHome`'s effects, so a plain ref is still undefined when the param arrives. Every `data-component`/`data-action` hook is untouched (`home-project-row`, `home-add-project`, `home-session-row`, `home-session-search`, …) because `packages/app/e2e` selects on them. | The desktop app should navigate like Claude Desktop — one always-visible sidebar with Projects, Recents and pinned Settings/Help — instead of losing all navigation the moment a session opens. Exporting the row components in place rather than relocating them keeps `home.tsx` down to ~36 added lines: upstream changes to those components merge without a conflict, which relocating them would have forfeited. | Keep our structure. On conflict take upstream's `home.tsx` wholesale and re-apply the four edits: the `export` keywords, the `HomeProjectColumn` deletion, the `createHomeModel()` swap in `NewHome`, and the single-column grid. If upstream edits an exported row component, just take it. Never let a `data-component`/`data-action` hook drift — the e2e suite has no other handle on these rows. |
101+
| `packages/app/src/pages/layout-new.tsx`, `packages/app/src/pages/home.tsx`, `packages/app/src/i18n/{en,es}.ts` | **Persistent app sidebar.** Upstream's project column is an `<aside>` owned by `NewHome`, so it exists only on `/`. It becomes app chrome: `layout-new.tsx` wraps its `<main>` in a row flex and renders the fork-only `<AppSidebar class="hidden lg:flex">` beside it, so the sidebar survives every navigation. **The row components stay in `home.tsx`** — `HomeUtilityNav`, `HomeServerRow`, `HomeProjectList`, `HomeProjectEmpty`, `HomeRecentlyClosedRow` and the classes `HOME_PROJECT_NAV_ROW` / `HOME_PROJECT_NAV_LABEL` / `HOME_SECTION_LABEL` gain an `export` and nothing else, so upstream edits to them still apply cleanly; the fork-only `src/components/app-sidebar.tsx` imports and composes them, adding only the column shell and the Recents section. `home.tsx` otherwise loses just `HomeProjectColumn` (dead once the sidebar owns that composition) and the `NewHome` selection/action block, and its grid collapses from `lg:grid-cols-[280px_minmax(0,720px)]` to one `max-w-[760px]` column. The sub-`lg` `HomeUtilityNav` mount is untouched, so the stacked mobile layout is upstream's. The state both screens need (`focusedServer`, `projects`, `selectedProject`, `newSessionProject`, `projectDirectories`, the session-index query, `openNewSession`, `openProjectNewSession`, `openSession`, `selectProject`, `addProjects`, `editProject`, `closeProject`, `unseenCount`, `clearNotifications`, `chooseProject`, `isBackgroundOpen`, `buildHomeSessionRecords`, `HomeSessionRecord`) moves to fork-only `src/pages/home-model.tsx`, which both callers instantiate — safe because the selection lives in `useLayout().home`, the index is one TanStack query deduped by key, and the server-normalizing effect is idempotent. New i18n keys `sidebar.recents`, `sidebar.recents.new`, `sidebar.recents.viewAll` in `en` + `es`. `layout-new.tsx` additionally wraps its shell in the fork-only `<AppSidebarProvider>` (so the titlebar toggle and the sidebar share one collapse state), `AppSidebar` renders nothing while collapsed, and `NewHome` consumes a `?focus=search` param — clearing it after use, the way `pages/new-session.tsx` handles `?prompt` — to focus the search field for the titlebar's Search button. That focus ref changed from a `let` to a signal because `HomeSessionSearch` binds it from `onMount`, which runs after `NewHome`'s effects, so a plain ref is still undefined when the param arrives. `AppSidebarProvider` also registers the `sidebar.toggle` command (no keybind — `mod+b` is Home's here, unlike in the legacy layout), which is what the app menu's View → Toggle Sidebar dispatches; only `LegacyLayout` registered it before, so that item did nothing on this layout. It lives on the provider, not on `AppSidebar`, so collapsing does not unregister the command that undoes it. Both shells can be mounted at once on `/:dir` routes, so the id may be registered twice there; the command registry keeps the first entry, which is the outer (new-layout) one. Every `data-component`/`data-action` hook is untouched (`home-project-row`, `home-add-project`, `home-session-row`, `home-session-search`, …) because `packages/app/e2e` selects on them. | The desktop app should navigate like Claude Desktop — one always-visible sidebar with Projects, Recents and pinned Settings/Help — instead of losing all navigation the moment a session opens. Exporting the row components in place rather than relocating them keeps `home.tsx` down to ~36 added lines: upstream changes to those components merge without a conflict, which relocating them would have forfeited. | Keep our structure. On conflict take upstream's `home.tsx` wholesale and re-apply the four edits: the `export` keywords, the `HomeProjectColumn` deletion, the `createHomeModel()` swap in `NewHome`, and the single-column grid. If upstream edits an exported row component, just take it. Never let a `data-component`/`data-action` hook drift — the e2e suite has no other handle on these rows. |
102102
| `packages/desktop/icons/{dev,beta,prod}/` (all PNGs, `icon.icns`, `icon.ico`, `dock.png`), `packages/desktop/icons/README.md` | Regenerated from the fork-only `packages/desktop/icons/icon.svg` by the fork-only `packages/desktop/scripts/generate-icons.ts`: the brand-red `#ee0033` CoDev "C" — the `--mini` splash badge glyph, at the VS Code extension icon's proportions (unit = 120/1024, mark 480x600 centered) — on a white tile, replacing upstream's black/blueprint "o" mark. All three channels are byte-identical for now (chosen deliberately over upstream's per-channel treatments); the generator still writes them separately so a channel can diverge later. `icon.icns` members and `dock.png` use the macOS Big Sur grid (824/1024 inset + drop shadow); Windows, Linux and Store assets stay full-bleed. README rewritten from the Tauri-CLI + Image2Icon recipe to the generator. The stale Tauri-era `android/` and `ios/` subfolders are left untouched — nothing in the Electron build reads them. | Closes the "desktop app icons need design assets" rebrand gap: the Dock/taskbar icon was still upstream's mark. | Keep our assets and README (binary: take ours wholesale). Regenerate via the script rather than hand-editing; if upstream adds an icon size, add it to `FLAT_PNGS`/`ICONSET` and rerun. |
103103
| `packages/tui/src/theme/assets/codev.json` (new file), `packages/tui/src/theme/index.ts`, `packages/tui/src/context/theme.tsx` | Added the `codev` theme — upstream's `opencode.json` palette with `darkSecondary` → `#0891b2` (brand teal) — registered it in the theme index, and made it the default/fallback theme (`"opencode"` → `"codev"` in the store default, kv fallback, and load-failure fallbacks). `theme/assets/opencode.json` itself matches upstream again. | Brand accent lives in a fork-owned theme instead of edits to upstream's palette; the default theme carries the brand. | Keep our theme file, its index registration, and the fallback strings; when upstream changes the opencode palette, mirror it into `codev.json` (keeping our `darkSecondary`). |
104104
| `packages/opencode/src/cli/ui.ts` | `wordmark`/`logo()` render the lowercase codev wordmark via `ansiWordmark` (bold, no shadow, "dev" in brand red on TTY) | Help banner matches the TUI home banner. | Keep our wordmark + logo(); take upstream's surrounding changes. |

‎packages/app/src/context/app-sidebar.tsx‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { createContext, createMemo, createResource, useContext, type ParentProps } from "solid-js"
22
import { createStore } from "solid-js/store"
3+
import { useCommand } from "@/context/command"
4+
import { useLanguage } from "@/context/language"
35
import { Persist, persisted } from "@/utils/persist"
46

57
type AppSidebarState = {
@@ -13,6 +15,8 @@ const AppSidebarContext = createContext<AppSidebarState>()
1315
// with the titlebar's toggle button. Persisted globally rather than per window,
1416
// so a collapsed sidebar stays collapsed on the next launch.
1517
export function AppSidebarProvider(props: ParentProps) {
18+
const command = useCommand()
19+
const language = useLanguage()
1620
const [store, setStore, _, ready] = persisted(Persist.global("app-sidebar"), createStore({ collapsed: false }))
1721
// The stored value arrives asynchronously; reading it before then would render
1822
// the sidebar open for a frame and then yank it away.
@@ -21,12 +25,27 @@ export function AppSidebarProvider(props: ParentProps) {
2125
(promise) => promise.then(() => true),
2226
{ initialValue: false },
2327
)
28+
const toggle = () => setStore("collapsed", (value) => !value)
29+
30+
// The app menu's View → Toggle Sidebar dispatches this id, and only
31+
// LegacyLayout registered it, so the item was permanently greyed out in the
32+
// new layout. No keybind: mod+b belongs to the Home command here, unlike in
33+
// the legacy layout. Registered on the provider rather than on AppSidebar so
34+
// collapsing does not unregister the one command that undoes it.
35+
command.register("app-sidebar", () => [
36+
{
37+
id: "sidebar.toggle",
38+
title: language.t("command.sidebar.toggle"),
39+
category: language.t("command.category.view"),
40+
onSelect: toggle,
41+
},
42+
])
2443

2544
return (
2645
<AppSidebarContext.Provider
2746
value={{
2847
collapsed: createMemo(() => loaded() && store.collapsed),
29-
toggle: () => setStore("collapsed", (value) => !value),
48+
toggle,
3049
}}
3150
>
3251
{props.children}

0 commit comments

Comments
 (0)