diff --git a/DIVERGENCES.md b/DIVERGENCES.md index d98eab69af..748c6d364d 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -36,7 +36,7 @@ upstream maintainers — see README → Syncing with upstream for why and how. | `packages/tui/src/util/presentation.ts` | Local logo copy replaced by the shared `ansiWordmark` (brand teal + drop shadow) | Presentation-mode wordmark was still upstream-branded. | Keep our wordmark lines; take upstream's surrounding changes. | | `packages/tui/src/feature-plugins/sidebar/footer.tsx` | Sidebar footer wordmark: upstream's two-tone "Open"+"Code" spans → a single "CoDev" span (text color, bold) | Upstream splits the wordmark across two spans, so it never matched `opencode` greps and survived earlier rebrand passes. | Keep our span; take upstream's surrounding changes. | | `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 \|"; startup dialog effect gates on `!useConnected()` (not `provider.length === 0` — hosted Zen free models always register) and opens the fork's `DialogConfigure` (component/dialog-configure.tsx, new file); added the `codev.configure` command (`/configure`) and a `CODEV_CONFIGURE=1` mount hook for `codev configure` | Tab title branding; first-run setup routes to the CoDev gateway configure flow (upstream's `/connect` stays as the escape hatch). | Keep our strings, gate condition, dialog swap + command block; take upstream's surrounding changes. | +| `packages/tui/src/app.tsx` | Terminal title "OpenCode"/"OC \|" → "CoDev Code"/"CC \|"; startup dialog effect gates on `!useConnected()` (not `provider.length === 0` — hosted Zen free models always register) and opens the fork's `DialogConfigure` (component/dialog-configure.tsx, new file); 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 | Tab title branding; first-run setup routes to the CoDev gateway configure flow (upstream's `/connect` stays as the escape hatch); `codev` starts directly in the session layout instead of the home screen. | Keep our strings, gate condition, dialog swap + command block, route seed, `openNewSession`/`findEmptySession` + call sites, and the `-c` default-title filter; take upstream's surrounding changes. | | `packages/tui/src/routes/session/permission.tsx`, `packages/opencode/src/cli/cmd/run/permission.shared.ts`, `packages/opencode/test/cli/run/permission.shared.test.ts`, `packages/tui/src/component/error-component.tsx`, `packages/opencode/src/cli/cmd/run/splash.ts` | Visible sentences/badges "OpenCode"/"opencode" → "CoDev Code"/"codev-code" (incl. the permission always-allow copy and its test expectations) | User-facing copy. | Keep our strings. | | `packages/opencode/src/installation/index.ts` | Updater detects/queries/installs `codev-code` (npm-family); GitHub fallback → quickbeard/codev-code | `codev-code upgrade` and the update notification target OUR npm package, never stock opencode. brew/choco/scoop probes never match (no such packages). | Keep our package names. | | `packages/opencode/src/config/config.ts` | `share` defaults to `"disabled"` when unset | Sharing publishes sessions to upstream's share service; internal users must opt in via config. | Keep our default block. | diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index bbe0efccdc..02a1c39a14 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -286,7 +286,15 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) { Promise; pluginHost: TuiPlugi }) }) + // Fork: home screen is skipped — plain startup opens a new empty session + // directly (reusing a leftover empty one to avoid accumulating rows). + async function openNewSession() { + const created = await sdk.client.session.create({}) + if (!created.data?.id) { + toast.show({ message: "Failed to create session", variant: "error" }) + route.navigate({ type: "home" }) + return + } + route.navigate({ type: "session", sessionID: created.data.id }) + } + + async function findEmptySession() { + const list = await sdk.client.session.list({ start: Date.now() - 30 * 24 * 60 * 60 * 1000 }) + const match = (list.data ?? []) + .filter((x) => !x.parentID && !x.time.archived && isDefaultTitle(x.title)) + .filter((x) => !sdk.directory || x.directory === sdk.directory) + .toSorted((a, b) => b.time.updated - a.time.updated) + .at(0) + if (!match) return + const messages = await sdk.client.session.messages({ sessionID: match.id, limit: 1 }) + if (messages.data?.length !== 0) return + return match.id + } + + onMount(() => { + if (args.sessionID || args.continue || args.fork || args.prompt) return + // An explicit CODEV_ROUTE already picked the destination. + if (startup.initialRoute) return + void findEmptySession() + .catch(() => undefined) + .then((existing) => { + if (existing) { + route.navigate({ type: "session", sessionID: existing }) + return + } + return openNewSession() + }) + }) + let continued = false createEffect(() => { // When using -c, session list is loaded in blocking phase, so we can navigate at "partial" if (continued || sync.status === "loading" || !args.continue) return - const match = sync.data.session + // Skip default-titled sessions: those are the empty ones auto-created at + // startup, and -c should land on the last real conversation. + const sorted = sync.data.session .toSorted((a, b) => b.time.updated - a.time.updated) - .find((x) => x.parentID === undefined)?.id + .filter((x) => x.parentID === undefined) + const match = (sorted.find((x) => !isDefaultTitle(x.title)) ?? sorted[0])?.id if (match) { continued = true if (args.fork) { @@ -598,9 +649,7 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi slashName: "new", slashAliases: ["clear"], run: () => { - route.navigate({ - type: "home", - }) + void openNewSession() dialog.clear() }, }, @@ -1029,7 +1078,7 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi event.on("session.deleted", (evt) => { if (route.data.type === "session" && route.data.sessionID === evt.properties.info.id) { - route.navigate({ type: "home" }) + void openNewSession() toast.show({ variant: "info", message: "The current session was deleted", @@ -1136,7 +1185,13 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi - + {/* "dummy" is the pre-navigation placeholder (--continue and the + fork's startup auto-session); mounting Session with it would + fetch a nonexistent ID and toast a server error. */} + {(_) => }