summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.opencode/opencode.jsonc6
-rw-r--r--STATS.md1
-rw-r--r--bun.lock34
-rw-r--r--flake.lock6
-rw-r--r--nix/hashes.json2
-rw-r--r--package.json2
-rw-r--r--packages/console/app/package.json2
-rw-r--r--packages/console/core/package.json2
-rw-r--r--packages/console/function/package.json2
-rw-r--r--packages/console/mail/package.json2
-rw-r--r--packages/desktop/package.json2
-rw-r--r--packages/desktop/src/pages/session.tsx4
-rw-r--r--packages/enterprise/package.json2
-rw-r--r--packages/enterprise/src/routes/share/[shareID].tsx11
-rw-r--r--packages/extensions/zed/extension.toml12
-rw-r--r--packages/function/package.json2
-rw-r--r--packages/opencode/package.json2
-rw-r--r--packages/opencode/src/cli/cmd/tui/context/theme.tsx48
-rw-r--r--packages/plugin/package.json2
-rw-r--r--packages/sdk/js/package.json2
-rw-r--r--packages/slack/package.json2
-rw-r--r--packages/tauri/package.json2
-rw-r--r--packages/ui/package.json4
-rw-r--r--packages/ui/src/components/code.tsx29
-rw-r--r--packages/ui/src/components/diff-ssr.tsx75
-rw-r--r--packages/ui/src/components/diff.tsx98
-rw-r--r--packages/ui/src/components/message-part.tsx32
-rw-r--r--packages/ui/src/components/message-progress.tsx28
-rw-r--r--packages/ui/src/components/session-review.tsx8
-rw-r--r--packages/ui/src/components/session-turn.tsx37
-rw-r--r--packages/ui/src/pierre/index.ts (renamed from packages/ui/src/components/pierre.ts)11
-rw-r--r--packages/ui/src/pierre/worker.ts5
-rw-r--r--packages/util/package.json2
-rw-r--r--packages/web/package.json2
-rw-r--r--sdks/vscode/package.json2
35 files changed, 339 insertions, 144 deletions
diff --git a/.opencode/opencode.jsonc b/.opencode/opencode.jsonc
index 68097c8d9..ce4a6658b 100644
--- a/.opencode/opencode.jsonc
+++ b/.opencode/opencode.jsonc
@@ -1,9 +1,9 @@
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-openai-codex-auth"],
- "enterprise": {
- "url": "https://enterprise.dev.opencode.ai",
- },
+ // "enterprise": {
+ // "url": "https://enterprise.dev.opencode.ai",
+ // },
"provider": {
"opencode": {
"options": {
diff --git a/STATS.md b/STATS.md
index 1279367f4..a9807ddf5 100644
--- a/STATS.md
+++ b/STATS.md
@@ -157,3 +157,4 @@
| 2025-11-29 | 908,689 (+6,948) | 863,361 (+6,879) | 1,772,050 (+13,827) |
| 2025-11-30 | 916,116 (+7,427) | 870,194 (+6,833) | 1,786,310 (+14,260) |
| 2025-12-01 | 925,898 (+9,782) | 876,500 (+6,306) | 1,802,398 (+16,088) |
+| 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) |
diff --git a/bun.lock b/bun.lock
index bdadce801..f8e711a7b 100644
--- a/bun.lock
+++ b/bun.lock
@@ -20,7 +20,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
- "version": "1.0.126",
+ "version": "1.0.127",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -48,7 +48,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
- "version": "1.0.126",
+ "version": "1.0.127",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -75,7 +75,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
- "version": "1.0.126",
+ "version": "1.0.127",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -99,7 +99,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
- "version": "1.0.126",
+ "version": "1.0.127",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -123,7 +123,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
- "version": "1.0.126",
+ "version": "1.0.127",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -164,7 +164,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
- "version": "1.0.126",
+ "version": "1.0.127",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -192,7 +192,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
- "version": "1.0.126",
+ "version": "1.0.127",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "22.0.0",
@@ -208,7 +208,7 @@
},
"packages/opencode": {
"name": "opencode",
- "version": "1.0.126",
+ "version": "1.0.127",
"bin": {
"opencode": "./bin/opencode",
},
@@ -297,7 +297,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
- "version": "1.0.126",
+ "version": "1.0.127",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -317,7 +317,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
- "version": "1.0.126",
+ "version": "1.0.127",
"devDependencies": {
"@hey-api/openapi-ts": "0.81.0",
"@tsconfig/node22": "catalog:",
@@ -328,7 +328,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
- "version": "1.0.126",
+ "version": "1.0.127",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -341,7 +341,7 @@
},
"packages/tauri": {
"name": "@opencode-ai/tauri",
- "version": "1.0.126",
+ "version": "1.0.127",
"dependencies": {
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-opener": "^2",
@@ -354,7 +354,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
- "version": "1.0.126",
+ "version": "1.0.127",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -386,7 +386,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
- "version": "1.0.126",
+ "version": "1.0.127",
"dependencies": {
"zod": "catalog:",
},
@@ -397,7 +397,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
- "version": "1.0.126",
+ "version": "1.0.127",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -443,7 +443,7 @@
"@hono/zod-validator": "0.4.2",
"@kobalte/core": "0.13.11",
"@openauthjs/openauth": "0.0.0-20250322224806",
- "@pierre/precision-diffs": "0.5.7",
+ "@pierre/precision-diffs": "0.6.0-beta.3",
"@solidjs/meta": "0.29.4",
"@solidjs/router": "0.15.4",
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
@@ -1218,7 +1218,7 @@
"@petamoriken/float16": ["@petamoriken/[email protected]", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
- "@pierre/precision-diffs": ["@pierre/[email protected]", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/transformers": "3.15.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.15.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-Y+e4kJ9pT2I4NS5fE39KdoiXtwMkVPRvrwLM6O2IqO7PDCRWLBS7CYxcSgSyngEndccUll2krx66I2QnfO0Ovg=="],
+ "@pierre/precision-diffs": ["@pierre/[email protected]", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/transformers": "3.15.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.15.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-1FBm9jhLWZvs7BqN3yG2Wh9SpGuO1us2QsKZlQqSwyCctMr9DRGzYQJ9lF6yR03LHzXs3fuIzO++d9sCObYzrQ=="],
"@pkgjs/parseargs": ["@pkgjs/[email protected]", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
diff --git a/flake.lock b/flake.lock
index 107fb6a00..2ef89f661 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
- "lastModified": 1764587062,
- "narHash": "sha256-hdFa0TAVQAQLDF31cEW3enWmBP+b592OvHs6WVe3D8k=",
+ "lastModified": 1764611609,
+ "narHash": "sha256-yU9BNcP0oadUKupw0UKmO9BKDOVIg9NStdJosEbXf8U=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "c1cb7d097cb250f6e1904aacd5f2ba5ffd8a49ce",
+ "rev": "8c29968b3a942f2903f90797f9623737c215737c",
"type": "github"
},
"original": {
diff --git a/nix/hashes.json b/nix/hashes.json
index 734e47928..e34d21200 100644
--- a/nix/hashes.json
+++ b/nix/hashes.json
@@ -1,3 +1,3 @@
{
- "nodeModules": "sha256-9BfJ3dFq/UYyhsnK3Sfx6rb6CT8bCvFOFOqD2+W1WQE="
+ "nodeModules": "sha256-HyH219sZn4gOPyVg/bij7K3mfZ0MnBSM/7NmsOyrD4o="
}
diff --git a/package.json b/package.json
index e85f08e4e..a5e7c1462 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
"@tsconfig/bun": "1.0.9",
"@cloudflare/workers-types": "4.20251008.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
- "@pierre/precision-diffs": "0.5.7",
+ "@pierre/precision-diffs": "0.6.0-beta.3",
"@tailwindcss/vite": "4.1.11",
"diff": "8.0.2",
"ai": "5.0.97",
diff --git a/packages/console/app/package.json b/packages/console/app/package.json
index 500129094..ade902b71 100644
--- a/packages/console/app/package.json
+++ b/packages/console/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
- "version": "1.0.126",
+ "version": "1.0.127",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
diff --git a/packages/console/core/package.json b/packages/console/core/package.json
index 25cf96616..8e1e690b2 100644
--- a/packages/console/core/package.json
+++ b/packages/console/core/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
- "version": "1.0.126",
+ "version": "1.0.127",
"private": true,
"type": "module",
"dependencies": {
diff --git a/packages/console/function/package.json b/packages/console/function/package.json
index dc474827b..7a7f5fa70 100644
--- a/packages/console/function/package.json
+++ b/packages/console/function/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
- "version": "1.0.126",
+ "version": "1.0.127",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json
index 423f23da3..f58563edf 100644
--- a/packages/console/mail/package.json
+++ b/packages/console/mail/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
- "version": "1.0.126",
+ "version": "1.0.127",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
diff --git a/packages/desktop/package.json b/packages/desktop/package.json
index e47e27af7..0e84c10a7 100644
--- a/packages/desktop/package.json
+++ b/packages/desktop/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/desktop",
- "version": "1.0.126",
+ "version": "1.0.127",
"description": "",
"type": "module",
"scripts": {
diff --git a/packages/desktop/src/pages/session.tsx b/packages/desktop/src/pages/session.tsx
index 281b6765a..ddc2a60c5 100644
--- a/packages/desktop/src/pages/session.tsx
+++ b/packages/desktop/src/pages/session.tsx
@@ -30,6 +30,7 @@ import { useSync } from "@/context/sync"
import { useSession } from "@/context/session"
import { useLayout } from "@/context/layout"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
+import { Diff } from "@opencode-ai/ui/diff"
export default function Page() {
const layout = useLayout()
@@ -357,6 +358,7 @@ export default function Page() {
content: "pb-20",
container: "w-full " + (wide() ? "max-w-146 mx-auto px-6" : "pr-6 pl-18"),
}}
+ diffComponent={Diff}
/>
</div>
</Match>
@@ -405,6 +407,7 @@ export default function Page() {
container: "px-6",
}}
diffs={session.diffs()}
+ diffComponent={Diff}
actions={
<Tooltip value="Open in tab">
<IconButton
@@ -436,6 +439,7 @@ export default function Page() {
container: "px-6",
}}
diffs={session.diffs()}
+ diffComponent={Diff}
split
/>
</div>
diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json
index 982d95499..d615d3dab 100644
--- a/packages/enterprise/package.json
+++ b/packages/enterprise/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
- "version": "1.0.126",
+ "version": "1.0.127",
"private": true,
"type": "module",
"scripts": {
diff --git a/packages/enterprise/src/routes/share/[shareID].tsx b/packages/enterprise/src/routes/share/[shareID].tsx
index b95004749..f579bfa03 100644
--- a/packages/enterprise/src/routes/share/[shareID].tsx
+++ b/packages/enterprise/src/routes/share/[shareID].tsx
@@ -18,6 +18,10 @@ import z from "zod"
import NotFound from "../[...404]"
import { Tabs } from "@opencode-ai/ui/tabs"
import { preloadMultiFileDiff, PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
+import { Diff } from "@opencode-ai/ui/diff-ssr"
+import { clientOnly } from "@solidjs/start"
+
+const ClientOnlyDiff = clientOnly(() => import("@opencode-ai/ui/diff").then((m) => ({ default: m.Diff })))
const SessionDataMissingError = NamedError.create(
"SessionDataMissingError",
@@ -230,6 +234,7 @@ export default function () {
"flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
container: "px-4",
}}
+ diffComponent={ClientOnlyDiff}
/>
)}
</For>
@@ -299,6 +304,7 @@ export default function () {
content: "flex flex-col justify-between items-start",
container: "w-full pb-20 " + (wide() ? "max-w-146 mx-auto px-6" : "pr-6 pl-18"),
}}
+ diffComponent={ClientOnlyDiff}
>
<div classList={{ "w-full flex items-center justify-center pb-8 shrink-0": true }}>
<Logo class="w-58.5 opacity-12" />
@@ -311,6 +317,7 @@ export default function () {
<SessionReview
class="@4xl:hidden"
diffs={diffs()}
+ diffComponent={Diff}
classes={{
root: "pb-20",
header: "px-6",
@@ -318,9 +325,10 @@ export default function () {
}}
/>
<SessionReview
- class="hidden @4xl:flex"
split
+ class="hidden @4xl:flex"
diffs={splitDiffs()}
+ diffComponent={Diff}
classes={{
root: "pb-20",
header: "px-6",
@@ -352,6 +360,7 @@ export default function () {
<div class="relative h-full pt-8 overflow-y-auto no-scrollbar">
<SessionReview
diffs={diffs()}
+ diffComponent={Diff}
classes={{
root: "pb-20",
header: "px-4",
diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml
index 577943242..2b15573df 100644
--- a/packages/extensions/zed/extension.toml
+++ b/packages/extensions/zed/extension.toml
@@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The AI coding agent built for the terminal"
-version = "1.0.126"
+version = "1.0.127"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/sst/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
-archive = "https://github.com/sst/opencode/releases/download/v1.0.126/opencode-darwin-arm64.zip"
+archive = "https://github.com/sst/opencode/releases/download/v1.0.127/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
-archive = "https://github.com/sst/opencode/releases/download/v1.0.126/opencode-darwin-x64.zip"
+archive = "https://github.com/sst/opencode/releases/download/v1.0.127/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
-archive = "https://github.com/sst/opencode/releases/download/v1.0.126/opencode-linux-arm64.zip"
+archive = "https://github.com/sst/opencode/releases/download/v1.0.127/opencode-linux-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
-archive = "https://github.com/sst/opencode/releases/download/v1.0.126/opencode-linux-x64.zip"
+archive = "https://github.com/sst/opencode/releases/download/v1.0.127/opencode-linux-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
-archive = "https://github.com/sst/opencode/releases/download/v1.0.126/opencode-windows-x64.zip"
+archive = "https://github.com/sst/opencode/releases/download/v1.0.127/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
diff --git a/packages/function/package.json b/packages/function/package.json
index 46053e5bf..5aa956ac1 100644
--- a/packages/function/package.json
+++ b/packages/function/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
- "version": "1.0.126",
+ "version": "1.0.127",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index 8652b0d64..d2580661f 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
- "version": "1.0.126",
+ "version": "1.0.127",
"name": "opencode",
"type": "module",
"private": true,
diff --git a/packages/opencode/src/cli/cmd/tui/context/theme.tsx b/packages/opencode/src/cli/cmd/tui/context/theme.tsx
index 88b9616b0..ed77e04b4 100644
--- a/packages/opencode/src/cli/cmd/tui/context/theme.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/theme.tsx
@@ -169,6 +169,9 @@ function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
throw new Error(`Color reference "${c}" not found in defs or theme`)
}
}
+ if (typeof c === "number") {
+ return ansiToRgba(c)
+ }
return resolveColor(c[mode])
}
@@ -203,6 +206,51 @@ function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
} as Theme
}
+function ansiToRgba(code: number): RGBA {
+ // Standard ANSI colors (0-15)
+ if (code < 16) {
+ const ansiColors = [
+ "#000000", // Black
+ "#800000", // Red
+ "#008000", // Green
+ "#808000", // Yellow
+ "#000080", // Blue
+ "#800080", // Magenta
+ "#008080", // Cyan
+ "#c0c0c0", // White
+ "#808080", // Bright Black
+ "#ff0000", // Bright Red
+ "#00ff00", // Bright Green
+ "#ffff00", // Bright Yellow
+ "#0000ff", // Bright Blue
+ "#ff00ff", // Bright Magenta
+ "#00ffff", // Bright Cyan
+ "#ffffff", // Bright White
+ ]
+ return RGBA.fromHex(ansiColors[code] ?? "#000000")
+ }
+
+ // 6x6x6 Color Cube (16-231)
+ if (code < 232) {
+ const index = code - 16
+ const b = index % 6
+ const g = Math.floor(index / 6) % 6
+ const r = Math.floor(index / 36)
+
+ const val = (x: number) => (x === 0 ? 0 : x * 40 + 55)
+ return RGBA.fromInts(val(r), val(g), val(b))
+ }
+
+ // Grayscale Ramp (232-255)
+ if (code < 256) {
+ const gray = (code - 232) * 10 + 8
+ return RGBA.fromInts(gray, gray, gray)
+ }
+
+ // Fallback for invalid codes
+ return RGBA.fromInts(0, 0, 0)
+}
+
export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
name: "Theme",
init: (props: { mode: "dark" | "light" }) => {
diff --git a/packages/plugin/package.json b/packages/plugin/package.json
index 84691617c..58232a539 100644
--- a/packages/plugin/package.json
+++ b/packages/plugin/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
- "version": "1.0.126",
+ "version": "1.0.127",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json
index 2ea5d4cc8..168159934 100644
--- a/packages/sdk/js/package.json
+++ b/packages/sdk/js/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
- "version": "1.0.126",
+ "version": "1.0.127",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
diff --git a/packages/slack/package.json b/packages/slack/package.json
index fb995a0f6..8ae877164 100644
--- a/packages/slack/package.json
+++ b/packages/slack/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
- "version": "1.0.126",
+ "version": "1.0.127",
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",
diff --git a/packages/tauri/package.json b/packages/tauri/package.json
index 2233f6fca..dccecada5 100644
--- a/packages/tauri/package.json
+++ b/packages/tauri/package.json
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/tauri",
"private": true,
- "version": "1.0.126",
+ "version": "1.0.127",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 5a6da5268..8d54d752b 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,10 +1,10 @@
{
"name": "@opencode-ai/ui",
- "version": "1.0.126",
+ "version": "1.0.127",
"type": "module",
"exports": {
"./*": "./src/components/*.tsx",
- "./pierre": "./src/components/pierre.ts",
+ "./pierre": "./src/pierre/index.ts",
"./hooks": "./src/hooks/index.ts",
"./context": "./src/context/index.ts",
"./context/*": "./src/context/*.tsx",
diff --git a/packages/ui/src/components/code.tsx b/packages/ui/src/components/code.tsx
index 788baf549..b4b772816 100644
--- a/packages/ui/src/components/code.tsx
+++ b/packages/ui/src/components/code.tsx
@@ -1,6 +1,22 @@
import { type FileContents, File, FileOptions, LineAnnotation } from "@pierre/precision-diffs"
import { ComponentProps, createEffect, splitProps } from "solid-js"
-import { createDefaultOptions, styleVariables } from "./pierre"
+import { createDefaultOptions, styleVariables } from "../pierre"
+import { getOrCreateWorkerPoolSingleton } from "@pierre/precision-diffs/worker"
+import { workerFactory } from "../pierre/worker"
+
+const workerPool = getOrCreateWorkerPoolSingleton({
+ poolOptions: {
+ workerFactory,
+ // poolSize defaults to 8. More workers = more parallelism but
+ // also more memory. Too many can actually slow things down.
+ // poolSize: 8,
+ },
+ highlighterOptions: {
+ theme: "OpenCode",
+ // Optionally preload languages to avoid lazy-loading delays
+ // langs: ["typescript", "javascript", "css", "html"],
+ },
+})
export type CodeProps<T = {}> = FileOptions<T> & {
file: FileContents
@@ -14,10 +30,13 @@ export function Code<T>(props: CodeProps<T>) {
const [local, others] = splitProps(props, ["file", "class", "classList", "annotations"])
createEffect(() => {
- const instance = new File<T>({
- ...createDefaultOptions<T>("unified"),
- ...others,
- })
+ const instance = new File<T>(
+ {
+ ...createDefaultOptions<T>("unified"),
+ ...others,
+ },
+ workerPool,
+ )
container.innerHTML = ""
instance.render({
diff --git a/packages/ui/src/components/diff-ssr.tsx b/packages/ui/src/components/diff-ssr.tsx
new file mode 100644
index 000000000..800aa3730
--- /dev/null
+++ b/packages/ui/src/components/diff-ssr.tsx
@@ -0,0 +1,75 @@
+import { FileDiff } from "@pierre/precision-diffs"
+import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
+import { onCleanup, onMount, Show, splitProps } from "solid-js"
+import { isServer } from "solid-js/web"
+import { createDefaultOptions, styleVariables, type DiffProps } from "../pierre"
+
+export type SSRDiffProps<T = {}> = DiffProps<T> & {
+ preloadedDiff: PreloadMultiFileDiffResult<T>
+}
+
+export function Diff<T>(props: SSRDiffProps<T>) {
+ let container!: HTMLDivElement
+ let fileDiffRef!: HTMLElement
+ const [local, others] = splitProps(props, ["before", "after", "class", "classList", "annotations"])
+
+ let fileDiffInstance: FileDiff<T> | undefined
+ const cleanupFunctions: Array<() => void> = []
+
+ onMount(() => {
+ if (isServer || !props.preloadedDiff) return
+ fileDiffInstance = new FileDiff<T>({
+ ...createDefaultOptions(props.diffStyle),
+ ...others,
+ ...props.preloadedDiff,
+ })
+ // @ts-expect-error - fileContainer is private but needed for SSR hydration
+ fileDiffInstance.fileContainer = fileDiffRef
+ fileDiffInstance.hydrate({
+ oldFile: local.before,
+ newFile: local.after,
+ lineAnnotations: local.annotations,
+ fileContainer: fileDiffRef,
+ containerWrapper: container,
+ })
+
+ // Hydrate annotation slots with interactive SolidJS components
+ // if (props.annotations.length > 0 && props.renderAnnotation != null) {
+ // for (const annotation of props.annotations) {
+ // const slotName = `annotation-${annotation.side}-${annotation.lineNumber}`;
+ // const slotElement = fileDiffRef.querySelector(
+ // `[slot="${slotName}"]`
+ // ) as HTMLElement;
+ //
+ // if (slotElement != null) {
+ // // Clear the static server-rendered content from the slot
+ // slotElement.innerHTML = '';
+ //
+ // // Mount a fresh SolidJS component into this slot using render().
+ // // This enables full SolidJS reactivity (signals, effects, etc.)
+ // const dispose = render(
+ // () => props.renderAnnotation!(annotation),
+ // slotElement
+ // );
+ // cleanupFunctions.push(dispose);
+ // }
+ // }
+ // }
+ })
+
+ onCleanup(() => {
+ // Clean up FileDiff event handlers and dispose SolidJS components
+ fileDiffInstance?.cleanUp()
+ cleanupFunctions.forEach((dispose) => dispose())
+ })
+
+ return (
+ <div data-component="diff" style={styleVariables} ref={container}>
+ <file-diff ref={fileDiffRef} id="ssr-diff">
+ <Show when={isServer}>
+ <template shadowrootmode="open" innerHTML={props.preloadedDiff.prerenderedHTML} />
+ </Show>
+ </file-diff>
+ </div>
+ )
+}
diff --git a/packages/ui/src/components/diff.tsx b/packages/ui/src/components/diff.tsx
index bd2134515..8e19c3172 100644
--- a/packages/ui/src/components/diff.tsx
+++ b/packages/ui/src/components/diff.tsx
@@ -1,17 +1,22 @@
-import { type FileContents, FileDiff, type DiffLineAnnotation, FileDiffOptions } from "@pierre/precision-diffs"
-import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
-import { ComponentProps, createEffect, onCleanup, onMount, Show, splitProps } from "solid-js"
-import { isServer } from "solid-js/web"
-import { createDefaultOptions, styleVariables } from "./pierre"
-
-export type DiffProps<T = {}> = FileDiffOptions<T> & {
- preloadedDiff?: PreloadMultiFileDiffResult<T>
- before: FileContents
- after: FileContents
- annotations?: DiffLineAnnotation<T>[]
- class?: string
- classList?: ComponentProps<"div">["classList"]
-}
+import { FileDiff } from "@pierre/precision-diffs"
+import { getOrCreateWorkerPoolSingleton } from "@pierre/precision-diffs/worker"
+import { createEffect, onCleanup, splitProps } from "solid-js"
+import { createDefaultOptions, type DiffProps, styleVariables } from "../pierre"
+import { workerFactory } from "../pierre/worker"
+
+const workerPool = getOrCreateWorkerPoolSingleton({
+ poolOptions: {
+ workerFactory,
+ // poolSize defaults to 8. More workers = more parallelism but
+ // also more memory. Too many can actually slow things down.
+ // poolSize: 8,
+ },
+ highlighterOptions: {
+ theme: "OpenCode",
+ // Optionally preload languages to avoid lazy-loading delays
+ // langs: ["typescript", "javascript", "css", "html"],
+ },
+})
// interface ThreadMetadata {
// threadId: string
@@ -21,21 +26,21 @@ export type DiffProps<T = {}> = FileDiffOptions<T> & {
export function Diff<T>(props: DiffProps<T>) {
let container!: HTMLDivElement
- let fileDiffRef!: HTMLElement
const [local, others] = splitProps(props, ["before", "after", "class", "classList", "annotations"])
let fileDiffInstance: FileDiff<T> | undefined
const cleanupFunctions: Array<() => void> = []
createEffect(() => {
- if (props.preloadedDiff) return
container.innerHTML = ""
if (!fileDiffInstance) {
- fileDiffInstance = new FileDiff<T>({
- ...createDefaultOptions(props.diffStyle),
- ...others,
- ...(props.preloadedDiff ?? {}),
- })
+ fileDiffInstance = new FileDiff<T>(
+ {
+ ...createDefaultOptions(props.diffStyle),
+ ...others,
+ },
+ workerPool,
+ )
}
fileDiffInstance.render({
oldFile: local.before,
@@ -45,60 +50,11 @@ export function Diff<T>(props: DiffProps<T>) {
})
})
- onMount(() => {
- if (isServer || !props.preloadedDiff) return
- fileDiffInstance = new FileDiff<T>({
- ...createDefaultOptions(props.diffStyle),
- ...others,
- ...(props.preloadedDiff ?? {}),
- })
- // @ts-expect-error - fileContainer is private but needed for SSR hydration
- fileDiffInstance.fileContainer = fileDiffRef
- fileDiffInstance.hydrate({
- oldFile: local.before,
- newFile: local.after,
- lineAnnotations: local.annotations,
- fileContainer: fileDiffRef,
- containerWrapper: container,
- })
-
- // Hydrate annotation slots with interactive SolidJS components
- // if (props.annotations.length > 0 && props.renderAnnotation != null) {
- // for (const annotation of props.annotations) {
- // const slotName = `annotation-${annotation.side}-${annotation.lineNumber}`;
- // const slotElement = fileDiffRef.querySelector(
- // `[slot="${slotName}"]`
- // ) as HTMLElement;
- //
- // if (slotElement != null) {
- // // Clear the static server-rendered content from the slot
- // slotElement.innerHTML = '';
- //
- // // Mount a fresh SolidJS component into this slot using render().
- // // This enables full SolidJS reactivity (signals, effects, etc.)
- // const dispose = render(
- // () => props.renderAnnotation!(annotation),
- // slotElement
- // );
- // cleanupFunctions.push(dispose);
- // }
- // }
- // }
- })
-
onCleanup(() => {
// Clean up FileDiff event handlers and dispose SolidJS components
fileDiffInstance?.cleanUp()
cleanupFunctions.forEach((dispose) => dispose())
})
- return (
- <div data-component="diff" style={styleVariables} ref={container}>
- <file-diff ref={fileDiffRef} id="ssr-diff">
- <Show when={isServer && props.preloadedDiff}>
- {(preloadedDiff) => <template shadowrootmode="open" innerHTML={preloadedDiff().prerenderedHTML} />}
- </Show>
- </file-diff>
- </div>
- )
+ return <div data-component="diff" style={styleVariables} ref={container} />
}
diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx
index 807e56db0..a0e6e91b6 100644
--- a/packages/ui/src/components/message-part.tsx
+++ b/packages/ui/src/components/message-part.tsx
@@ -1,4 +1,4 @@
-import { Component, createMemo, For, Match, Show, Switch } from "solid-js"
+import { Component, createMemo, For, Match, Show, Switch, ValidComponent } from "solid-js"
import { Dynamic } from "solid-js/web"
import {
AssistantMessage,
@@ -13,7 +13,6 @@ import { GenericTool } from "./basic-tool"
import { Card } from "./card"
import { Icon } from "./icon"
import { Checkbox } from "./checkbox"
-import { Diff } from "./diff"
import { DiffChanges } from "./diff-changes"
import { Markdown } from "./markdown"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
@@ -23,12 +22,14 @@ import { unwrap } from "solid-js/store"
export interface MessageProps {
message: MessageType
parts: PartType[]
+ diffComponent: ValidComponent
sanitize?: RegExp
}
export interface MessagePartProps {
part: PartType
message: MessageType
+ diffComponent: ValidComponent
hideDetails?: boolean
sanitize?: RegExp
}
@@ -53,6 +54,7 @@ export function Message(props: MessageProps) {
message={assistantMessage() as AssistantMessage}
parts={props.parts}
sanitize={props.sanitize}
+ diffComponent={props.diffComponent}
/>
)}
</Match>
@@ -60,7 +62,12 @@ export function Message(props: MessageProps) {
)
}
-export function AssistantMessageDisplay(props: { message: AssistantMessage; parts: PartType[]; sanitize?: RegExp }) {
+export function AssistantMessageDisplay(props: {
+ message: AssistantMessage
+ parts: PartType[]
+ sanitize?: RegExp
+ diffComponent: ValidComponent
+}) {
const filteredParts = createMemo(() => {
return props.parts?.filter((x) => {
if (x.type === "reasoning") return false
@@ -68,7 +75,11 @@ export function AssistantMessageDisplay(props: { message: AssistantMessage; part
})
})
return (
- <For each={filteredParts()}>{(part) => <Part part={part} message={props.message} sanitize={props.sanitize} />}</For>
+ <For each={filteredParts()}>
+ {(part) => (
+ <Part part={part} message={props.message} sanitize={props.sanitize} diffComponent={props.diffComponent} />
+ )}
+ </For>
)
}
@@ -87,7 +98,13 @@ export function Part(props: MessagePartProps) {
const part = createMemo(() => sanitizePart(unwrap(props.part), props.sanitize))
return (
<Show when={component()}>
- <Dynamic component={component()} part={part()} message={props.message} hideDetails={props.hideDetails} />
+ <Dynamic
+ component={component()}
+ part={part()}
+ message={props.message}
+ diffComponent={props.diffComponent}
+ hideDetails={props.hideDetails}
+ />
</Show>
)
}
@@ -96,6 +113,7 @@ export interface ToolProps {
input: Record<string, any>
metadata: Record<string, any>
tool: string
+ diffComponent: ValidComponent
output?: string
hideDetails?: boolean
}
@@ -162,6 +180,7 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) {
component={render}
input={input}
tool={part.tool}
+ diffComponent={props.diffComponent}
metadata={metadata}
output={part.state.status === "completed" ? part.state.output : undefined}
hideDetails={props.hideDetails}
@@ -361,7 +380,8 @@ ToolRegistry.register({
>
<Show when={props.metadata.filediff}>
<div data-component="edit-content">
- <Diff
+ <Dynamic
+ component={props.diffComponent}
before={{
name: getFilename(props.metadata.filediff.path),
contents: props.metadata.filediff.before,
diff --git a/packages/ui/src/components/message-progress.tsx b/packages/ui/src/components/message-progress.tsx
index efe4dfd8f..afd7f754a 100644
--- a/packages/ui/src/components/message-progress.tsx
+++ b/packages/ui/src/components/message-progress.tsx
@@ -1,10 +1,27 @@
-import { For, JSXElement, Match, Show, Switch, createEffect, createMemo, createSignal, onCleanup } from "solid-js"
+import {
+ For,
+ JSXElement,
+ Match,
+ Show,
+ Switch,
+ ValidComponent,
+ createEffect,
+ createMemo,
+ createSignal,
+ onCleanup,
+} from "solid-js"
import { Part } from "./message-part"
import { Spinner } from "./spinner"
import { useData } from "../context/data"
import type { AssistantMessage as AssistantMessageType, ToolPart } from "@opencode-ai/sdk"
-export function MessageProgress(props: { assistantMessages: () => AssistantMessageType[]; done?: boolean }) {
+export interface MessageProgressProps {
+ assistantMessages: () => AssistantMessageType[]
+ diffComponent: ValidComponent
+ done?: boolean
+}
+
+export function MessageProgress(props: MessageProgressProps) {
const data = useData()
const sanitizer = createMemo(() => (data.directory ? new RegExp(`${data.directory}/`, "g") : undefined))
const parts = createMemo(() => props.assistantMessages().flatMap((m) => data.store.part[m.id]))
@@ -155,7 +172,12 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
)
return (
<div data-slot="message-progress-item">
- <Part message={message()!} part={part} sanitize={sanitizer()} />
+ <Part
+ message={message()!}
+ part={part}
+ sanitize={sanitizer()}
+ diffComponent={props.diffComponent}
+ />
</div>
)
}}
diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx
index 376317e1b..ea5871b95 100644
--- a/packages/ui/src/components/session-review.tsx
+++ b/packages/ui/src/components/session-review.tsx
@@ -1,15 +1,15 @@
import { Accordion } from "./accordion"
import { Button } from "./button"
-import { Diff } from "./diff"
import { DiffChanges } from "./diff-changes"
import { FileIcon } from "./file-icon"
import { Icon } from "./icon"
import { StickyAccordionHeader } from "./sticky-accordion-header"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
-import { For, Match, Show, Switch, type JSX } from "solid-js"
+import { For, Match, Show, Switch, ValidComponent, type JSX } from "solid-js"
import { createStore } from "solid-js/store"
import { type FileDiff } from "@opencode-ai/sdk"
import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
+import { Dynamic } from "solid-js/web"
export interface SessionReviewProps {
split?: boolean
@@ -18,6 +18,7 @@ export interface SessionReviewProps {
classes?: { root?: string; header?: string; container?: string }
actions?: JSX.Element
diffs: (FileDiff & { preloaded?: PreloadMultiFileDiffResult<any> })[]
+ diffComponent: ValidComponent
}
export const SessionReview = (props: SessionReviewProps) => {
@@ -96,7 +97,8 @@ export const SessionReview = (props: SessionReviewProps) => {
</Accordion.Trigger>
</StickyAccordionHeader>
<Accordion.Content data-slot="session-review-accordion-content">
- <Diff
+ <Dynamic
+ component={props.diffComponent}
preloadedDiff={diff.preloaded}
diffStyle={props.split ? "split" : "unified"}
before={{
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index c61b23068..963713a22 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/packages/ui/src/components/session-turn.tsx
@@ -2,7 +2,18 @@ import { AssistantMessage } from "@opencode-ai/sdk"
import { useData } from "../context"
import { Binary } from "@opencode-ai/util/binary"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
-import { createEffect, createMemo, createSignal, For, Match, onMount, ParentProps, Show, Switch } from "solid-js"
+import {
+ createEffect,
+ createMemo,
+ createSignal,
+ For,
+ Match,
+ onMount,
+ ParentProps,
+ Show,
+ Switch,
+ ValidComponent,
+} from "solid-js"
import { DiffChanges } from "./diff-changes"
import { Typewriter } from "./typewriter"
import { Message } from "./message-part"
@@ -11,10 +22,10 @@ import { Accordion } from "./accordion"
import { StickyAccordionHeader } from "./sticky-accordion-header"
import { FileIcon } from "./file-icon"
import { Icon } from "./icon"
-import { Diff } from "./diff"
import { Card } from "./card"
import { MessageProgress } from "./message-progress"
import { Collapsible } from "./collapsible"
+import { Dynamic } from "solid-js/web"
export function SessionTurn(
props: ParentProps<{
@@ -25,6 +36,7 @@ export function SessionTurn(
content?: string
container?: string
}
+ diffComponent: ValidComponent
}>,
) {
const data = useData()
@@ -117,7 +129,7 @@ export function SessionTurn(
</div>
</div>
<div data-slot="session-turn-message-content">
- <Message message={msg()} parts={parts()} sanitize={sanitizer()} />
+ <Message message={msg()} parts={parts()} sanitize={sanitizer()} diffComponent={props.diffComponent} />
</div>
{/* Summary */}
<Show when={completed()}>
@@ -167,7 +179,8 @@ export function SessionTurn(
</Accordion.Trigger>
</StickyAccordionHeader>
<Accordion.Content data-slot="session-turn-accordion-content">
- <Diff
+ <Dynamic
+ component={props.diffComponent}
before={{
name: diff.file!,
contents: diff.before!,
@@ -193,7 +206,11 @@ export function SessionTurn(
<div data-slot="session-turn-response-section">
<Switch>
<Match when={!completed()}>
- <MessageProgress assistantMessages={assistantMessages} done={!messageWorking()} />
+ <MessageProgress
+ assistantMessages={assistantMessages}
+ done={!messageWorking()}
+ diffComponent={props.diffComponent}
+ />
</Match>
<Match when={completed() && hasToolPart()}>
<Collapsible variant="ghost" open={detailsExpanded()} onOpenChange={setDetailsExpanded}>
@@ -224,10 +241,18 @@ export function SessionTurn(
message={assistantMessage}
parts={parts().filter((p) => p?.id !== last()?.id)}
sanitize={sanitizer()}
+ diffComponent={props.diffComponent}
/>
)
}
- return <Message message={assistantMessage} parts={parts()} sanitize={sanitizer()} />
+ return (
+ <Message
+ message={assistantMessage}
+ parts={parts()}
+ sanitize={sanitizer()}
+ diffComponent={props.diffComponent}
+ />
+ )
}}
</For>
<Show when={error()}>
diff --git a/packages/ui/src/components/pierre.ts b/packages/ui/src/pierre/index.ts
index 5821697c7..26e902d05 100644
--- a/packages/ui/src/components/pierre.ts
+++ b/packages/ui/src/pierre/index.ts
@@ -1,4 +1,13 @@
-import { FileDiffOptions } from "@pierre/precision-diffs"
+import { DiffLineAnnotation, FileContents, FileDiffOptions } from "@pierre/precision-diffs"
+import { ComponentProps } from "solid-js"
+
+export type DiffProps<T = {}> = FileDiffOptions<T> & {
+ before: FileContents
+ after: FileContents
+ annotations?: DiffLineAnnotation<T>[]
+ class?: string
+ classList?: ComponentProps<"div">["classList"]
+}
export function createDefaultOptions<T>(style: FileDiffOptions<T>["diffStyle"]) {
return {
diff --git a/packages/ui/src/pierre/worker.ts b/packages/ui/src/pierre/worker.ts
new file mode 100644
index 000000000..de5fd625a
--- /dev/null
+++ b/packages/ui/src/pierre/worker.ts
@@ -0,0 +1,5 @@
+import ShikiWorkerUrl from "@pierre/precision-diffs/worker/shiki-worker.js?worker&url"
+
+export function workerFactory(): Worker {
+ return new Worker(ShikiWorkerUrl, { type: "module" })
+}
diff --git a/packages/util/package.json b/packages/util/package.json
index dcdda3b51..83dd25c22 100644
--- a/packages/util/package.json
+++ b/packages/util/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/util",
- "version": "1.0.126",
+ "version": "1.0.127",
"private": true,
"type": "module",
"exports": {
diff --git a/packages/web/package.json b/packages/web/package.json
index 528f8abcb..38ba08b4f 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/web",
"type": "module",
- "version": "1.0.126",
+ "version": "1.0.127",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json
index edf3f7274..ffd493859 100644
--- a/sdks/vscode/package.json
+++ b/sdks/vscode/package.json
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
- "version": "1.0.126",
+ "version": "1.0.127",
"publisher": "sst-dev",
"repository": {
"type": "git",