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
1 change: 1 addition & 0 deletions DIVERGENCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ from the squash message first, or use a plain merge.
| `packages/app/src/pages/session/timeline/message-timeline.tsx` | **Session actions menu extracted.** The ⋯ menu beside the session title — both renderings, the legacy `DropdownMenu` and the v2 `MenuV2`, ~145 lines — and the `DialogDeleteSession` closure move to the fork-only `packages/app/src/components/session-actions-menu.tsx` (verbatim at extraction; the v2 Delete item has since dropped its ellipsis and taken the danger colour, so do not diff the two expecting a match), and this file renders `<SessionActionsMenu>` instead, passing its existing `openTitleEditor` / share / `archiveSession` / `deleteSession` as callbacks and its trigger element out through `triggerRef` (the share popover still anchors to it). The extracted dialog takes a resolved `title` and an `onConfirm` rather than a `sessionID`, so the caller supplies `sessionTitle(sync().session.get(id)?.title)`. The `title` store loses `pendingRename`/`pendingShare`: deferring Rename and Share until the menu's `onCloseAutoFocus` is now the component's job, since every caller needs it (a list row's inline editor is blurred by the returning focus exactly as the header's was). Also gains one fork-only `createEffect` that consumes `consumeSessionShareRequest(sessionID())` and opens the share popover — the session rows' Share routes through it. | The sidebar's Recents rows and the home session list open the same menu; three copies of four items would drift. Only this file rendered the menu, so extraction is the smallest way to share it. | Keep the extraction. On conflict, take upstream's `message-timeline.tsx` wholesale and re-apply: delete both inline menu blocks and the local `DialogDeleteSession`, mount `<SessionActionsMenu>` with the same callbacks, drop the two `pending*` store fields, and re-add the share-request effect. If upstream adds a menu item, add it to `session-actions-menu.tsx` (both variants) rather than back here. Do not "simplify" the component's deferred rename/share into a plain `onSelect` — focus return is what it exists to survive. |
| `packages/app/src/pages/home.tsx` | `HomeSessionRow` gains the same `<SessionActionsMenu>` (hover/focus/menu-open revealed, vertical dots) and inline rename: while renaming, the row renders a `div` with `InlineEditor` instead of the `button`, because an input inside a button is invalid markup and swallows keystrokes into the open-session handler. `NewHome` holds one `createInlineEditorController()` for the list and passes it plus the model down. Its local `archiveSession` is deleted — `createHomeModel()` owns `archiveSession`/`renameSession`/`deleteSession` now, so the sidebar and this screen run one implementation (extends the "state both screens need moves to home-model.tsx" split in the persistent-sidebar row above); the call site becomes `model.archiveSession`. Upstream's `SHOW_HOME_SESSION_ARCHIVE` block is left exactly as-is — the flag is `false`, so it renders nothing, and touching it would widen the diff for no gain. `data-component="home-session-row"` and every other selector are untouched (`packages/app/e2e` binds to them); the menu adds `data-action="session-menu"`. | The Recents rows and "View all" are the same list at two sizes; a menu on one and not the other makes "View all" a dead end. | Keep our row structure. On conflict take upstream's row and re-apply the menu, the editing branch, and the `model.*` action wiring. Never let a `data-component`/`data-action` hook drift. If upstream re-enables `SHOW_HOME_SESSION_ARCHIVE`, drop that block instead — its absolutely-positioned archive button would sit on top of the menu. |
| `packages/app/src/pages/home.tsx` (again) | **Session lists keyed by id.** The "View all" list iterates `groups().map((group) => group.id)` and, within each group, `group.sessions.map((record) => record.session.id)`, resolving each back through a `createMemo` + `<Show>` rather than handing the objects to `<For>`. `groupSessions` mints new group objects on every call and `createHomeModel`'s records memo mints a new `{session, project, projectName}` wrapper per session on every recompute — the `projects` memo alone returns a fresh array on a pointerdown — and `<For>` keys on reference, so the rows were torn down and rebuilt on recomputes that changed nothing visible. When that landed between a pointerdown and its mouseup the browser had no surviving common ancestor to fire `click` on, so a row's ⋮ menu ignored its first click; the same rebuild could close an open menu or drop an in-progress rename. `data-component="home-session-row"`, `data-action="session-menu"` and the group header refs / opacity keys are unchanged. The fork-only `packages/app/src/components/app-sidebar.tsx` carries the same shape for the Recents list. | A menu that swallows the first click reads as broken, and the cause is structural rather than incidental: any refresh of the session index rebuilt every row. | Keep the id keying. On conflict take upstream's list body and re-apply both `<For>` wrappers. Do not "simplify" back to `<For each={groups()}>` / `<For each={group.sessions}>` — that is the bug. If upstream ever makes groups or records referentially stable this can go, but prove it with `packages/app/e2e/regression/home-session-menu-first-click.spec.ts` before removing it. |
| `packages/app/e2e/regression/remote-session-settings.spec.ts` | Both tests pick the settings-shortcut modifier by asking the page — `/(Mac|iPod|iPhone|iPad)/.test(navigator.platform)` — through a local `openSettingsDialog(page)` helper, instead of branching on the Node-side `process.platform`. | Upstream's version pressed `Meta+,` whenever the *host* was macOS, but the app resolves `mod` from `navigator.platform` (`IS_MAC` in `src/context/command.tsx`) and Playwright's `devices["Desktop Chrome"]` profile ships a Windows user agent, so the page always reports `Win32`. On a macOS machine the suite therefore pressed Meta while the app listened for Control: the settings dialog never opened and both tests failed, while Linux CI stayed green because there the host branch happened to pick Control anyway. A real macOS browser reports `MacIntel`, so the app is right and only the test was wrong. | Keep our helper. On conflict take upstream's spec and re-apply it. If upstream fixes this itself, drop the row. Worth submitting upstream — it breaks the suite for every macOS developer. |
| `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. |
| `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`). |
| `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. |
Expand Down
16 changes: 14 additions & 2 deletions packages/app/e2e/regression/remote-session-settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,26 @@ const sessionA = session("ses_server_a", directoryA, "Server A session")
const childSessionA = { ...session("ses_server_a_child", directoryA, "Server A child session"), parentID: sessionA.id }
const sessionB = session("ses_server_b", directoryB, "Server B session")

// The app resolves `mod` from `navigator.platform` (see IS_MAC in
// src/context/command.tsx), and Playwright's Desktop Chrome profile ships a
// Windows user agent, so the page reports Win32 whatever the host is. Choosing
// the modifier from `process.platform` instead pressed Meta on a macOS machine
// while the app was listening for Control, so the settings dialog never opened
// and both tests here failed — on macOS only, which is why Linux CI stayed
// green. Ask the page the same question the app asks.
async function openSettingsDialog(page: Page) {
const mac = await page.evaluate(() => /(Mac|iPod|iPhone|iPad)/.test(navigator.platform))
await page.keyboard.press(mac ? "Meta+," : "Control+,")
}

test("session settings use the remote server context", async ({ page }) => {
const permissionRequests: string[] = []
await mockServers(page, permissionRequests)
await configureServers(page)

await page.goto(`/server/${base64Encode(serverB)}/session/${sessionB.id}`)
await expect(page.getByText(sessionB.title).first()).toBeVisible()
await page.keyboard.press(process.platform === "darwin" ? "Meta+," : "Control+,")
await openSettingsDialog(page)

const dialog = page.locator(".settings-v2-dialog")
const autoAccept = dialog.locator('[data-action="settings-auto-accept-permissions"]')
Expand Down Expand Up @@ -58,7 +70,7 @@ test("auto-accept responds for an unfocused server session", async ({ page }) =>
const hrefB = `/server/${base64Encode(serverB)}/session/${sessionB.id}`
await page.goto(`/server/${base64Encode(serverA)}/session/${sessionA.id}`)
await expect(page.getByText(sessionA.title).first()).toBeVisible()
await page.keyboard.press(process.platform === "darwin" ? "Meta+," : "Control+,")
await openSettingsDialog(page)
const autoAccept = page.locator(".settings-v2-dialog").locator('[data-action="settings-auto-accept-permissions"]')
await autoAccept.locator('[data-slot="switch-control"]').click()
await expect(autoAccept.getByRole("switch")).toBeChecked()
Expand Down
Loading