From 5a14614d774bc6e878bda688211fd3c308017409 Mon Sep 17 00:00:00 2001 From: kate bonner Date: Wed, 8 Jul 2026 18:08:33 -0400 Subject: [PATCH] amicode(home): fix silent dead-end on 'Open chat' with no tracked projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a fresh browser profile against a bare `opencode serve`, the home page's primary CTA (Meet-Amico card / "Open chat") did nothing: the persisted client-side project list is empty, so startWithPrompt fell through to openNewSession(), which needs the same newSessionProject() that just came back empty and silently returns. Fall back to the focused server's own working directory, synced from GET /path (.directory; "" until loaded, so the guard holds). Open and touch it as a project — self-healing: the home page tracks it from then on — and start the draft with the prompt preserved. Deliberately not sync.data.project: the server's "global" record has worktree "/". Regression spec drives the real UI against a mocked server with no localStorage seed; verified failing on the unfixed code and passing with the fix. tsgo -b clean; bun test:unit 376 pass. Co-Authored-By: Claude Fable 5 --- AMICODE-PATCHES.md | 20 +++++++++++ .../home-open-chat-empty-projects.spec.ts | 33 +++++++++++++++++++ packages/app/src/pages/home.tsx | 18 ++++++++-- 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 packages/app/e2e/regression/home-open-chat-empty-projects.spec.ts diff --git a/AMICODE-PATCHES.md b/AMICODE-PATCHES.md index e89ce2c289..0834bb663e 100644 --- a/AMICODE-PATCHES.md +++ b/AMICODE-PATCHES.md @@ -225,3 +225,23 @@ Rebuilt with the exact T3 recipe (`OPENCODE_VERSION=1.17.3 bun run script/build. - index.css: @font-face for both (JuliaMono full glyph set — Julia Unicode; Racing Sans One latin subset, font-display swap). logo.tsx + wordmark-v2.tsx: font-family 'Racing Sans One' first, weight 700→400. settings.tsx: monoDefault/monoFallback lead with JuliaMono. theme.css: --font-family-mono leads with JuliaMono. Terminal font DELIBERATELY unchanged (JetBrainsMono Nerd Font Mono via separate terminalFallback). - New assets (git-added — build breaks without them): public/assets/RacingSansOne-Regular.woff2 (21 KB) + JuliaMono-Regular.woff2 (946 KB). - Font build sha256: `2a15da111be516516fb1fbd1a4fb5ae02ad9bddd6373ede08cdf7b28c19d163a` (dist/opencode-local + vendored path, write-temp + mv -f swap; SUPERSEDES #13's a73d8583… — same code, fonts now committed). Verify (scratch port 14099): `GET /assets/RacingSansOne-Regular.woff2` → 200 font/woff2 21804 B; `GET /assets/JuliaMono-Regular.woff2` → 200 font/woff2 946516 B; "Racing Sans One" in built css + index/new-session chunks, "JuliaMono" in `index-Dwtxigfs.css`; `GET /amicode/problems` → 200; `GET /` → 200 `Amicode`; ui `bun test src` → 70 pass; typecheck green ui+app (no snapshots assert fonts, per Aaron — confirmed nothing went red). Bonus confirmation: KaTeX\_\* woff2 assets now in dist — the entity view's katex import (#13) pulls its font set into the embed. +9. (home CTA fallback) — amicode(home): "Open chat" works on a fresh profile. + - BUG: `startWithPrompt` (fork wiring for the Meet-Amico card, patch 5ef6b7e0e) dead-ended + silently when the persisted client-side project list was empty (fresh browser profile + against a bare `opencode serve`): the `!project` branch called `openNewSession()`, which + needs the SAME empty `newSessionProject()` and hits `if (!conn || !project) return`. + Primary home CTA did nothing, no error. Hit live 2026-07-08 (web UI on a scratch dir). + - FIX (packages/app/src/pages/home.tsx, `startWithPrompt` only): when no project is + tracked, fall back to the focused server's own working directory — + `focusedSync().data.path.directory` (synced from GET /path; "" until loaded, so the + falsy guard holds) — open+touch it as a project (self-heals the home page), then + `tabs.newDraft` with the prompt preserved. Deliberately NOT `sync.data.project`: + the server's "global" project record has worktree "/". + - Regression spec: packages/app/e2e/regression/home-open-chat-empty-projects.spec.ts — + fresh profile (NO localStorage seed), mocked server, click the CTA (`exact: true` — + the whole card is also a button whose accessible name contains "Open chat"), expect + navigation to `/new-session?draftId=` + the cwd persisted as a tracked project. + Verified failing on the unfixed code, passing with the fix. Playwright note: config + reuses any server on port 3000 (`reuseExistingServer`) — run with `PLAYWRIGHT_PORT=` + if something else (e.g. the harmoniqs website dev server) holds 3000. + - Checks: `tsgo -b` clean; `bun run test:unit` 376 pass / 0 fail. diff --git a/packages/app/e2e/regression/home-open-chat-empty-projects.spec.ts b/packages/app/e2e/regression/home-open-chat-empty-projects.spec.ts new file mode 100644 index 0000000000..39ee2ca9cc --- /dev/null +++ b/packages/app/e2e/regression/home-open-chat-empty-projects.spec.ts @@ -0,0 +1,33 @@ +import { expect, test } from "@playwright/test" +import { fixture, pageMessages } from "../smoke/session-timeline.fixture" +import { mockOpenCodeServer } from "../utils/mock-server" + +// Regression: on a fresh profile (no tracked projects in localStorage) the +// home "Open chat" CTA dead-ended silently — startWithPrompt fell through to +// openNewSession(), which needs the same newSessionProject() that just came +// back empty. It must instead fall back to the server's own working +// directory (GET /path → .directory) and start a draft there, tracking the +// directory as a project so the rest of the home page works from then on. +test("home 'Open chat' falls back to the server cwd on a fresh profile", async ({ page }) => { + await mockOpenCodeServer(page, { + sessions: [], + provider: fixture.provider, + directory: fixture.directory, + project: fixture.project, + pageMessages, + }) + + // Deliberately NO localStorage seed — an empty tracked-project list is the + // regression condition (contrast: session-list-path-loading.spec.ts seeds it). + await page.goto("/") + // exact: true — the whole Meet-Amico card is also a button whose accessible + // name contains "Open chat"; we want the CTA inside it. + await page.getByRole("button", { name: "Open chat", exact: true }).click() + + // Navigates to a new-session draft instead of doing nothing. + await expect(page).toHaveURL(/\/new-session\?draftId=/) + + // And the server cwd is now a tracked project (the self-healing part). + const persisted = await page.evaluate(() => localStorage.getItem("opencode.global.dat:server") ?? "") + expect(persisted).toContain(fixture.directory) +}) diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx index aa0d177af6..cb199904d3 100644 --- a/packages/app/src/pages/home.tsx +++ b/packages/app/src/pages/home.tsx @@ -341,11 +341,23 @@ function HomeDesign() { const [sessionsExpanded, setSessionsExpanded] = createSignal(false) function startWithPrompt(prompt: string) { const project = newSessionProject() - if (!project) { - openNewSession() + if (project) { + tabs.newDraft({ server: server.key, directory: project.worktree }, prompt) return } - tabs.newDraft({ server: server.key, directory: project.worktree }, prompt) + // No tracked projects (fresh profile against a bare `opencode serve`): + // openNewSession() would dead-end silently here — it needs the same + // newSessionProject() that just came back empty. Fall back to the server's + // own working directory (path.directory, synced from GET /path; "" until + // loaded) and start tracking it, so the home CTAs work on first visit. + // Deliberately NOT sync.data.project: its "global" record has worktree "/". + const conn = focusedServer() + const directory = focusedSync().data.path.directory + if (!conn || !directory) return + const ctx = global.createServerCtx(conn) + ctx.projects.open(directory) + ctx.projects.touch(directory) + tabs.newDraft({ server: ServerConnection.key(conn), directory }, prompt) } function setSelection(next: HomeProjectSelection) {