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 @@ -294,6 +294,14 @@ already in use` and no way forward short of finding the file in a temporary
selected at all — you can clear it by clicking the selected one — the list is
empty rather than everything at once.
([#300](https://github.com/quickbeard/codev-code/pull/300))
- **Starting the app returns you to what you were doing** — every launch opened
a **New session** tab, even when you had sessions open and were plainly in the
middle of something. Now a window with anything open comes back to the session
it was last in, on whichever side you were using — Code and Work each remember
their own — and a new session opens only when there is nothing to come back
to. If the session you were last in has since been deleted, or its server is
gone, the window settles on a tab that is already open instead of a dead one.
([#301](https://github.com/quickbeard/codev-code/pull/301))

- **Typing `@` lists your files again** — the `@` menu was meant to offer files
first, but on a bare `@` it showed only agents and references: it filled the
Expand Down
2 changes: 1 addition & 1 deletion DIVERGENCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ from the squash message first, or use a plain merge.
| `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. |
| `packages/app/src/components/titlebar.tsx` | Four changes, all 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. (4) `openNewTab` routes all five of its project-resolution branches through a local `openDraftFor(...)`, which selects the draft already open for that server + directory — via the fork-only `findOpenDraft` in `pages/home-draft-reuse.ts` — and only calls `tabs.newDraft` when there is none. The `+` button gains `data-action="titlebar-new-tab"` so e2e can address it without colliding with the sidebar row, which carries the same accessible name. The model read off the active tab still seeds a *new* draft only; a reused one keeps its own, as selecting its tab by hand would. | 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, which is now the rule for every new-session affordance rather than a Home-only quirk (see the `openNewTab` change and `pages/home-draft-reuse.ts`). 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). |
| `packages/app/src/context/tabs.tsx` | `newDraft` inserts with `tabs.unshift(tab)` instead of `tabs.push(tab)`, so a draft opens at the head of the tab strip. Session tabs (`addSessionTab`) still append, and nothing else about draft handling changes. | A draft is where you start a session, so it belongs at the front of the strip rather than behind every session already open — with several tabs open the New session tab was scrolling off the right-hand end. | Keep the `unshift`. On conflict take upstream's `newDraft` and swap the insert back. Note the knock-on: `promoteDraft` replaces the draft **at its own index**, so a promoted session keeps the head position and sessions accumulate newest-first. That is intended — the tab you are working in should not jump across the strip the moment you send the first message — but if it is ever unwanted, move the tab in `promoteDraft` rather than reverting this, or the New session tab goes back to the end. |
| `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. |
| `packages/app/src/pages/layout-new.tsx` (again) | Mounts the fork-only `<LaunchNewSession />`, which on desktop decides one time per boot where the window lands: a window with tabs open on the showing side resumes that side's last session (`layout.home.lastSession`, validated with `sessionAlive` the way the Code/Work switch does), falling back to a session tab already open when that history is unusable; only a window with nothing open lands on a new-session draft (reusing an existing empty draft rather than stacking one up per launch). The chain lives in the exported pure `launchLanding`, tested in the fork-only `packages/app/test-browser/launch-landing.test.ts`, because the desktop-only path cannot be driven from the web e2e harness — the same split `workspace/side.ts`'s `folderEntry` uses. 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. |
| `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_SECTION_LABEL` is consumed one level down, by the fork-only `src/components/sidebar-section.tsx`). `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. |
| `packages/app/src/app.tsx` | `AppInterface`'s `ServerShell` no longer wraps its children in a second `<QueryProvider>`; it renders `<SharedProviders>` directly. `QueryProvider` itself is unchanged and still installed once, by `AppBaseProviders`. | `QueryProvider` builds a fresh `QueryClient` on every render, and it was mounted twice: once in `AppBaseProviders` and again inside `ServerShell`. `GlobalProvider` sits above `ServerShell`, so `ServerSync` — and the home session index cache it owns (`context/global-sync/home-session-index.ts`) — resolved the outer client, while everything under the router, including the sidebar and the home screen, resolved the inner one. The index that `session.created`/`session.updated` events write to therefore had no readers, and the list those events exist to keep fresh could only change when its own query refetched. In this fork that list is the persistent sidebar's Recents section, so a session stayed missing from Recents until something remounted the observer — switching Code/Work and back reassigns the observer's key, which is why that looked like the fix. Both `AppInterface` call sites (`packages/app/src/entry.tsx`, `packages/desktop/src/renderer/index.tsx`) already wrap it in `AppBaseProviders`, so the nested client was always redundant. | Keep the single provider. If upstream reworks the shell, the invariant to preserve is that `ServerSync` and the components reading its queries resolve the *same* `QueryClient` — verify by checking that `homeSessions.apply` and the sidebar's `sessionLoad` land in one cache, not that this exact JSX survives. |
| `packages/app/src/context/settings.tsx`, `packages/app/src/components/settings-general.tsx`, `packages/app/src/components/settings-v2/general.tsx`, `packages/app/src/context/settings.test.ts`, `packages/app/src/pages/session/composer/session-revert-dock.stories.tsx`, deleted `packages/app/src/components/settings-v2/interface-transition.tsx` + `interface-transition.stories.tsx`, deleted `packages/app/e2e/regression/legacy-new-session.spec.ts`, `packages/app/e2e/regression/session-timeline-projection.spec.ts`, `packages/app/e2e/performance/timeline-stability/fixture.ts` | **The legacy layout is retired: the fork ships one layout and no way back.** Upstream runs a timed migration — `oldInterfaceSunset` (a hardcoded date), a per-profile `layoutTransitionEligible` flag, a `newLayoutDesigns` preference and a Settings → General **New layout** switch — so existing users can flip between layouts until the sunset passes. All of it goes. `newLayoutDesigns` becomes `createMemo(() => true)`: reading a constant rather than the persisted value is what makes a profile carrying `newLayoutDesigns: false` (older build, another device via sync, hand-edited store) still come up on the new layout, which suppressing the toggle alone would not. `setNewLayoutDesigns`, `layoutTransitionAvailable`, `newInterfaceNoticeVisible` and `dismissNewInterfaceNotice` are **removed from the context rather than stubbed** — a no-op setter would let a caller look like it was switching layouts, so re-introducing the toggle has to be a compile error. Both Settings screens (v1 `settings-general.tsx` and v2 `settings-v2/general.tsx`) lose their `InterfaceSection` + `InterfaceNoticeSection` and the `<Show>` blocks that mounted them; `interface-transition.tsx` and its story are deleted with their last caller. The sunset machinery in `settings.tsx` goes with it — `oldInterfaceSunset`, `legacyNewLayoutDesignsDefault`, `newLayoutDesignsDefault`, `newLayoutDesignsUpgradeCutoff`, `shouldEnableNewLayout`, `layoutTransitionState`, `maximumSunsetTimeout`, `nextSunsetCheckDelay`, `resolveNewLayoutDesigns`, the retirement `setTimeout` and the two `createEffect`s that wrote `newLayoutDesigns` back to the store — along with their tests; `settings.test.ts` keeps only the `isAppUpgrade` / `shouldDisplayTabsToast` block. **Deliberately kept**: `layoutTransitionEligible`, `layoutTransitionClassified` and `setOldLayoutEligible` (with the desktop `isOldLayoutEligible` IPC that feeds them), because the tabs toast still uses eligibility to tell a fresh profile from an existing install — they no longer gate any layout. Also kept: every `newLayoutDesigns()` **read** in `app.tsx` and the `LegacyServerLayout` / `LegacyHome` code behind them. That shell is still mounted on `/:dir` routes, and leaving the branches in place keeps upstream's edits to them applying cleanly; deleting the legacy layout outright is a far larger diff for no user-visible gain. On the test side `legacy-new-session.spec.ts` is deleted (it asserted a redirect to a route that no longer resolves), `session-timeline-projection.spec.ts` drops its one `settings: { newLayoutDesigns: false }` pin, and the timeline-stability fixture drops the `newLayoutDesigns === false` branch that back-dated `app-version.v1`. The many specs that set `newLayoutDesigns: true` are untouched — the value is simply ignored now. The fork-only `packages/app/e2e/regression/layout-theme-lock.spec.ts` covers both locks from a seeded profile; each of its four cases was checked to fail when the matching source file is reverted to upstream's. | The fork ships one layout. Leaving a switch that drops users into a layout the fork does not design for, style-test or screenshot means two UIs to maintain and support, and the upstream sunset date would have retired it anyway. | Keep ours. On conflict take upstream's file and re-apply: the constant `newLayoutDesigns`, the four removed context members, the two deleted Settings sections. If upstream adds a new layout-preference surface, remove it too rather than wiring it up. Do **not** "restore" `setNewLayoutDesigns` to make a new upstream caller compile — delete the caller. Do not delete the `LegacyServerLayout` branches in `app.tsx`; they are load-bearing on `/:dir` and deliberately left as upstream's. |
Expand Down
Loading
Loading