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
20 changes: 20 additions & 0 deletions AMICODE-PATCHES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<title>Amicode</title>`; 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=<free>`
if something else (e.g. the harmoniqs website dev server) holds 3000.
- Checks: `tsgo -b` clean; `bun run test:unit` 376 pass / 0 fail.
33 changes: 33 additions & 0 deletions packages/app/e2e/regression/home-open-chat-empty-projects.spec.ts
Original file line number Diff line number Diff line change
@@ -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)
})
18 changes: 15 additions & 3 deletions packages/app/src/pages/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading