diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml
index 3f06da5197..f53f20fcdb 100644
--- a/.github/actions/setup-bun/action.yml
+++ b/.github/actions/setup-bun/action.yml
@@ -3,14 +3,6 @@ description: "Setup Bun with caching and install dependencies"
runs:
using: "composite"
steps:
- - name: Cache Bun dependencies
- uses: actions/cache@v4
- with:
- path: ~/.bun/install/cache
- key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
- restore-keys: |
- ${{ runner.os }}-bun-
-
- name: Get baseline download URL
id: bun-url
shell: bash
@@ -31,6 +23,19 @@ runs:
bun-version-file: ${{ !steps.bun-url.outputs.url && 'package.json' || '' }}
bun-download-url: ${{ steps.bun-url.outputs.url }}
+ - name: Get cache directory
+ id: cache
+ shell: bash
+ run: echo "dir=$(bun pm cache)" >> "$GITHUB_OUTPUT"
+
+ - name: Cache Bun dependencies
+ uses: actions/cache@v4
+ with:
+ path: ${{ steps.cache.outputs.dir }}
+ key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-bun-
+
- name: Install setuptools for distutils compatibility
run: python3 -m pip install setuptools || pip install setuptools || true
shell: bash
diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx
index cb9c61918a..e64f5a7fd6 100644
--- a/packages/app/src/pages/session/message-timeline.tsx
+++ b/packages/app/src/pages/session/message-timeline.tsx
@@ -780,27 +780,31 @@ export function MessageTimeline(props: {
{(commentAccessor: () => MessageComment) => {
const comment = createMemo(() => commentAccessor())
return (
-
-
-
- {getFilename(comment().path)}
-
- {(selection) => (
-
- {selection().startLine === selection().endLine
- ? `:${selection().startLine}`
- : `:${selection().startLine}-${selection().endLine}`}
-
- )}
-
-
-
- {comment().comment}
-
-
+
+ {(c) => (
+
+
+
+ {getFilename(c().path)}
+
+ {(selection) => (
+
+ {selection().startLine === selection().endLine
+ ? `:${selection().startLine}`
+ : `:${selection().startLine}-${selection().endLine}`}
+
+ )}
+
+
+
+ {c().comment}
+
+
+ )}
+
)
}}
diff --git a/packages/desktop-electron/electron.vite.config.ts b/packages/desktop-electron/electron.vite.config.ts
index 80c1d6b704..6903d5ed20 100644
--- a/packages/desktop-electron/electron.vite.config.ts
+++ b/packages/desktop-electron/electron.vite.config.ts
@@ -27,7 +27,7 @@ export default defineConfig({
},
renderer: {
plugins: [appPlugin],
- publicDir: "../app/public",
+ publicDir: "../../../app/public",
root: "src/renderer",
build: {
rollupOptions: {
diff --git a/packages/desktop-electron/src/renderer/html.test.ts b/packages/desktop-electron/src/renderer/html.test.ts
new file mode 100644
index 0000000000..bd8281c2fb
--- /dev/null
+++ b/packages/desktop-electron/src/renderer/html.test.ts
@@ -0,0 +1,62 @@
+import { describe, expect, test } from "bun:test"
+import { join, dirname, resolve } from "node:path"
+import { existsSync } from "node:fs"
+import { fileURLToPath } from "node:url"
+
+const dir = dirname(fileURLToPath(import.meta.url))
+const root = resolve(dir, "../..")
+
+const html = async (name: string) => Bun.file(join(dir, name)).text()
+
+/**
+ * Electron loads renderer HTML via `win.loadFile()` which uses the `file://`
+ * protocol. Absolute paths like `src="/foo.js"` resolve to the filesystem root
+ * (e.g. `file:///C:/foo.js` on Windows) instead of relative to the app bundle.
+ *
+ * All local resource references must use relative paths (`./`).
+ */
+describe("electron renderer html", () => {
+ for (const name of ["index.html", "loading.html"]) {
+ describe(name, () => {
+ test("script src attributes use relative paths", async () => {
+ const content = await html(name)
+ const srcs = [...content.matchAll(/\bsrc=["']([^"']+)["']/g)].map((m) => m[1])
+ for (const src of srcs) {
+ expect(src).not.toMatch(/^\/[^/]/)
+ }
+ })
+
+ test("link href attributes use relative paths", async () => {
+ const content = await html(name)
+ const hrefs = [...content.matchAll(/]+href=["']([^"']+)["']/g)].map((m) => m[1])
+ for (const href of hrefs) {
+ expect(href).not.toMatch(/^\/[^/]/)
+ }
+ })
+
+ test("no web manifest link (not applicable in Electron)", async () => {
+ const content = await html(name)
+ expect(content).not.toContain('rel="manifest"')
+ })
+ })
+ }
+})
+
+/**
+ * Vite resolves `publicDir` relative to `root`, not the config file.
+ * This test reads the actual values from electron.vite.config.ts to catch
+ * regressions where the publicDir path no longer resolves correctly
+ * after the renderer root is accounted for.
+ */
+describe("electron vite publicDir", () => {
+ test("configured publicDir resolves to a directory with oc-theme-preload.js", async () => {
+ const config = await Bun.file(join(root, "electron.vite.config.ts")).text()
+ const pub = config.match(/publicDir:\s*["']([^"']+)["']/)
+ const rendererRoot = config.match(/root:\s*["']([^"']+)["']/)
+ expect(pub).not.toBeNull()
+ expect(rendererRoot).not.toBeNull()
+ const resolved = resolve(root, rendererRoot![1], pub![1])
+ expect(existsSync(resolved)).toBe(true)
+ expect(existsSync(join(resolved, "oc-theme-preload.js"))).toBe(true)
+ })
+})
diff --git a/packages/desktop-electron/src/renderer/index.html b/packages/desktop-electron/src/renderer/index.html
index 1756408196..dd8675ee6b 100644
--- a/packages/desktop-electron/src/renderer/index.html
+++ b/packages/desktop-electron/src/renderer/index.html
@@ -4,20 +4,19 @@
OpenCode
-
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
+