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) {