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 numberDiff line numberDiff 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.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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 numberDiff line numberDiff 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.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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 numberDiff line numberDiff 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.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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 numberDiff line numberDiff 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.
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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