From d556143e3bb0b33ac71d531a9666923fde7280e3 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Tue, 16 Dec 2025 14:35:42 -0600 Subject: [PATCH 001/393] ci: fix branch name --- .github/workflows/release-github-action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-github-action.yml b/.github/workflows/release-github-action.yml index 491f66f6d..3f5caa55c 100644 --- a/.github/workflows/release-github-action.yml +++ b/.github/workflows/release-github-action.yml @@ -3,7 +3,7 @@ name: release-github-action on: push: branches: - - main + - dev paths: - "github/**" From 984f17ddd7c420a19f98dae2cd3e8b2173ec52e2 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Tue, 16 Dec 2025 14:37:33 -0600 Subject: [PATCH 002/393] ci: include desktop & tauri in release notes --- script/publish.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/publish.ts b/script/publish.ts index 121da8baa..7b5256312 100755 --- a/script/publish.ts +++ b/script/publish.ts @@ -17,7 +17,7 @@ if (!Script.preview) { .then((data: any) => data.version) const log = - await $`git log v${previous}..HEAD --oneline --format="%h %s" -- packages/opencode packages/sdk packages/plugin`.text() + await $`git log v${previous}..HEAD --oneline --format="%h %s" -- packages/opencode packages/sdk packages/plugin packages/tauri packages/desktop`.text() const commits = log .split("\n") From 1aee8b49e1d7158533652fc611fdb811b2ff59fb Mon Sep 17 00:00:00 2001 From: matvey Date: Tue, 16 Dec 2025 23:43:14 +0300 Subject: [PATCH 003/393] feat: add experimental oxfmt formatter (#5620) --- packages/opencode/src/flag/flag.ts | 1 + packages/opencode/src/format/formatter.ts | 20 ++++++++++ packages/web/src/content/docs/cli.mdx | 1 + packages/web/src/content/docs/formatters.mdx | 39 ++++++++++---------- 4 files changed, 42 insertions(+), 19 deletions(-) diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index 544188d4e..b49d19615 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -26,6 +26,7 @@ export namespace Flag { truthy("OPENCODE_ENABLE_EXA") || OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EXA") export const OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH = number("OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH") export const OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS = number("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS") + export const OPENCODE_EXPERIMENTAL_OXFMT = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_OXFMT") function truthy(key: string) { const value = process.env[key]?.toLowerCase() diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index c4e7c9ee8..954940f8d 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -2,6 +2,7 @@ import { readableStreamToText } from "bun" import { BunProc } from "../bun" import { Instance } from "../project/instance" import { Filesystem } from "../util/filesystem" +import { Flag } from "@/flag/flag" export interface Info { name: string @@ -74,6 +75,25 @@ export const prettier: Info = { }, } +export const oxfmt: Info = { + name: "oxfmt", + command: [BunProc.which(), "x", "oxfmt", "$FILE"], + environment: { + BUN_BE_BUN: "1", + }, + extensions: [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"], + async enabled() { + if (!Flag.OPENCODE_EXPERIMENTAL_OXFMT) return false + const items = await Filesystem.findUp("package.json", Instance.directory, Instance.worktree) + for (const item of items) { + const json = await Bun.file(item).json() + if (json.dependencies?.oxfmt) return true + if (json.devDependencies?.oxfmt) return true + } + return false + }, +} + export const biome: Info = { name: "biome", command: [BunProc.which(), "x", "@biomejs/biome", "format", "--write", "$FILE"], diff --git a/packages/web/src/content/docs/cli.mdx b/packages/web/src/content/docs/cli.mdx index 6fd7307b2..393046c97 100644 --- a/packages/web/src/content/docs/cli.mdx +++ b/packages/web/src/content/docs/cli.mdx @@ -326,3 +326,4 @@ These environment variables enable experimental features that may change or be r | `OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH` | number | Max output length for bash commands | | `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | Default timeout for bash commands in ms | | `OPENCODE_EXPERIMENTAL_FILEWATCHER` | boolean | Enable file watcher for entire dir | +| `OPENCODE_EXPERIMENTAL_OXFMT` | boolean | Enable oxfmt formatter | diff --git a/packages/web/src/content/docs/formatters.mdx b/packages/web/src/content/docs/formatters.mdx index 052138f68..c2c01836b 100644 --- a/packages/web/src/content/docs/formatters.mdx +++ b/packages/web/src/content/docs/formatters.mdx @@ -11,25 +11,26 @@ OpenCode automatically formats files after they are written or edited using lang OpenCode comes with several built-in formatters for popular languages and frameworks. Below is a list of the formatters, supported file extensions, and commands or config options it needs. -| Formatter | Extensions | Requirements | -| -------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | -| gofmt | .go | `gofmt` command available | -| mix | .ex, .exs, .eex, .heex, .leex, .neex, .sface | `mix` command available | -| prettier | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://prettier.io/docs/en/index.html) | `prettier` dependency in `package.json` | -| biome | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://biomejs.dev/) | `biome.json(c)` config file | -| zig | .zig, .zon | `zig` command available | -| clang-format | .c, .cpp, .h, .hpp, .ino, and [more](https://clang.llvm.org/docs/ClangFormat.html) | `.clang-format` config file | -| ktlint | .kt, .kts | `ktlint` command available | -| ruff | .py, .pyi | `ruff` command available with config | -| uv | .py, .pyi | `uv` command available | -| rubocop | .rb, .rake, .gemspec, .ru | `rubocop` command available | -| standardrb | .rb, .rake, .gemspec, .ru | `standardrb` command available | -| htmlbeautifier | .erb, .html.erb | `htmlbeautifier` command available | -| air | .R | `air` command available | -| dart | .dart | `dart` command available | -| ocamlformat | .ml, .mli | `ocamlformat` command available and `.ocamlformat` config file | -| terraform | .tf, .tfvars | `terraform` command available | -| gleam | .gleam | `gleam` command available | +| Formatter | Extensions | Requirements | +| -------------------- | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| gofmt | .go | `gofmt` command available | +| mix | .ex, .exs, .eex, .heex, .leex, .neex, .sface | `mix` command available | +| prettier | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://prettier.io/docs/en/index.html) | `prettier` dependency in `package.json` | +| biome | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://biomejs.dev/) | `biome.json(c)` config file | +| zig | .zig, .zon | `zig` command available | +| clang-format | .c, .cpp, .h, .hpp, .ino, and [more](https://clang.llvm.org/docs/ClangFormat.html) | `.clang-format` config file | +| ktlint | .kt, .kts | `ktlint` command available | +| ruff | .py, .pyi | `ruff` command available with config | +| uv | .py, .pyi | `uv` command available | +| rubocop | .rb, .rake, .gemspec, .ru | `rubocop` command available | +| standardrb | .rb, .rake, .gemspec, .ru | `standardrb` command available | +| htmlbeautifier | .erb, .html.erb | `htmlbeautifier` command available | +| air | .R | `air` command available | +| dart | .dart | `dart` command available | +| ocamlformat | .ml, .mli | `ocamlformat` command available and `.ocamlformat` config file | +| terraform | .tf, .tfvars | `terraform` command available | +| gleam | .gleam | `gleam` command available | +| oxfmt (Experimental) | .js, .jsx, .ts, .tsx | `oxfmt` dependency in `package.json` and an [experiental env variable flag](/docs/cli/#experimental) | So if your project has `prettier` in your `package.json`, OpenCode will automatically use it. From a2c91ebc32dba43004aba31adad2b54efcf1b34a Mon Sep 17 00:00:00 2001 From: Eric Guo Date: Wed, 17 Dec 2025 04:50:33 +0800 Subject: [PATCH 004/393] feat(desktop): Loading more session number per project by button (#5616) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- packages/desktop/src/context/global-sync.tsx | 6 +++--- packages/desktop/src/pages/layout.tsx | 20 +++++++++++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/desktop/src/context/global-sync.tsx b/packages/desktop/src/context/global-sync.tsx index 7d8186a68..53b891065 100644 --- a/packages/desktop/src/context/global-sync.tsx +++ b/packages/desktop/src/context/global-sync.tsx @@ -99,19 +99,19 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple } async function loadSessions(directory: string) { + const [store, setStore] = child(directory) globalSDK.client.session.list({ directory }).then((x) => { const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000 const nonArchived = (x.data ?? []) .slice() .filter((s) => !s.time.archived) .sort((a, b) => a.id.localeCompare(b.id)) - // Include at least 5 sessions, plus any updated in the last hour + // Include sessions up to the limit, plus any updated in the last hour const sessions = nonArchived.filter((s, i) => { - if (i < 5) return true + if (i < store.limit) return true const updated = new Date(s.time.updated).getTime() return updated > fourHoursAgo }) - const [, setStore] = child(directory) setStore("session", sessions) }) } diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index 6cf7a2b0e..aba435332 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -497,7 +497,7 @@ export default function Layout(props: ParentProps) { const sortable = createSortable(props.project.worktree) const slug = createMemo(() => base64Encode(props.project.worktree)) const name = createMemo(() => getFilename(props.project.worktree)) - const [store] = globalSync.child(props.project.worktree) + const [store, setProjectStore] = globalSync.child(props.project.worktree) const sessions = createMemo(() => store.session ?? []) const rootSessions = createMemo(() => sessions().filter((s) => !s.parentID)) const childSessionsByParent = createMemo(() => { @@ -511,6 +511,11 @@ export default function Layout(props: ParentProps) { } return map }) + const hasMoreSessions = createMemo(() => store.session.length >= store.limit) + const loadMoreSessions = async () => { + setProjectStore("limit", (limit) => limit + 10) + await globalSync.project.loadSessions(props.project.worktree) + } const [expanded, setExpanded] = createSignal(true) return ( // @ts-ignore @@ -583,6 +588,19 @@ export default function Layout(props: ParentProps) { + +
+ +
+
From 5eeba76bc52668e7d184249d5a2fbd7ad36c8ea7 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:34:22 -0600 Subject: [PATCH 005/393] fix: defensive audio init --- packages/desktop/src/context/notification.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/desktop/src/context/notification.tsx b/packages/desktop/src/context/notification.tsx index d2c5bae4c..5ca813448 100644 --- a/packages/desktop/src/context/notification.tsx +++ b/packages/desktop/src/context/notification.tsx @@ -68,7 +68,9 @@ export const { use: useNotification, provider: NotificationProvider } = createSi const match = Binary.search(syncStore.session, sessionID, (s) => s.id) const isChild = match.found && syncStore.session[match.index].parentID if (isChild) break - idlePlayer?.play() + try { + idlePlayer?.play() + } catch {} setStore("list", store.list.length, { ...base, type: "turn-complete", @@ -84,7 +86,9 @@ export const { use: useNotification, provider: NotificationProvider } = createSi const isChild = match.found && syncStore.session[match.index].parentID if (isChild) break } - errorPlayer?.play() + try { + errorPlayer?.play() + } catch {} setStore("list", store.list.length, { ...base, type: "error", From 7e682a95c4c77ba187b07a90d8ed2eac5238d06b Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:46:07 -0600 Subject: [PATCH 006/393] fix: prompt input multi line input --- .../desktop/src/components/prompt-input.tsx | 64 ++++++++++++------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/packages/desktop/src/components/prompt-input.tsx b/packages/desktop/src/components/prompt-input.tsx index 6e147242d..6bd3127be 100644 --- a/packages/desktop/src/components/prompt-input.tsx +++ b/packages/desktop/src/components/prompt-input.tsx @@ -380,31 +380,47 @@ export const PromptInput: Component = (props) => { const parseFromDOM = (): Prompt => { const newParts: Prompt = [] let position = 0 - editorRef.childNodes.forEach((node) => { - if (node.nodeType === Node.TEXT_NODE) { - if (node.textContent) { - const content = node.textContent - newParts.push({ type: "text", content, start: position, end: position + content.length }) - position += content.length - } - } else if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).dataset.type) { - switch ((node as HTMLElement).dataset.type) { - case "file": - const content = node.textContent! - newParts.push({ - type: "file", - path: (node as HTMLElement).dataset.path!, - content, - start: position, - end: position + content.length, - }) - position += content.length - break - default: - break - } - } + + const pushText = (content: string) => { + if (!content) return + newParts.push({ type: "text", content, start: position, end: position + content.length }) + position += content.length + } + + const rangeText = (range: Range) => { + const fragment = range.cloneContents() + const container = document.createElement("div") + container.append(fragment) + return container.innerText + } + + const files = Array.from(editorRef.querySelectorAll("[data-type=file]")) + let last: HTMLElement | undefined + + files.forEach((file) => { + const before = document.createRange() + before.selectNodeContents(editorRef) + if (last) before.setStartAfter(last) + before.setEndBefore(file) + pushText(rangeText(before)) + + const content = file.textContent ?? "" + newParts.push({ + type: "file", + path: file.dataset.path!, + content, + start: position, + end: position + content.length, + }) + position += content.length + last = file }) + + const after = document.createRange() + after.selectNodeContents(editorRef) + if (last) after.setStartAfter(last) + pushText(rangeText(after)) + if (newParts.length === 0) newParts.push(...DEFAULT_PROMPT) return newParts } From 96e4dcb521ed6c1d42ce31e1a603abd0016b39a7 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 16 Dec 2025 14:09:33 -0600 Subject: [PATCH 007/393] fix: working logic --- packages/ui/src/components/session-turn.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index ade9a04ab..213d72160 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -48,7 +48,7 @@ export function SessionTurn( type: "idle", }, ) - const working = createMemo(() => status()?.type !== "idle") + const working = createMemo(() => status()?.type !== "idle" && message()?.id === userMessages().at(-1)?.id) const retry = createMemo(() => { const s = status() if (s.type !== "retry") return @@ -306,7 +306,7 @@ export function SessionTurn(
- + From b4014e5baabfa0a404464cf71c153c8dbbe7ac65 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 16 Dec 2025 14:39:45 -0600 Subject: [PATCH 008/393] fix: auto-scroll --- packages/ui/src/components/session-turn.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index 213d72160..14e4055ca 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -75,6 +75,7 @@ export function SessionTurn( let scrollRef: HTMLDivElement | undefined const [state, setState] = createStore({ + contentRef: undefined as HTMLDivElement | undefined, stickyTitleRef: undefined as HTMLDivElement | undefined, stickyTriggerRef: undefined as HTMLDivElement | undefined, userScrolled: false, @@ -101,10 +102,18 @@ export function SessionTurn( function scrollToBottom() { if (!scrollRef || state.userScrolled || !working()) return requestAnimationFrame(() => { - scrollRef?.scrollTo({ top: scrollRef.scrollHeight, behavior: "instant" }) + scrollRef?.scrollTo({ top: scrollRef.scrollHeight, behavior: "smooth" }) }) } + createResizeObserver( + () => state.contentRef, + ({ height }) => { + console.log(height) + scrollToBottom() + }, + ) + createEffect(() => { if (!working()) { setState("userScrolled", false) @@ -232,11 +241,6 @@ export function SessionTurn( }) } - createEffect(() => { - lastPart() - scrollToBottom() - }) - const [store, setStore] = createStore({ status: rawStatus(), stepsExpanded: props.stepsExpanded ?? working(), @@ -296,6 +300,7 @@ export function SessionTurn( return (
setState("contentRef", el)} data-message={message().id} data-slot="session-turn-message-container" class={props.classes?.container} From 9aa5460a0e1292dbfa6abcf944e8f5982618f3ca Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 16 Dec 2025 15:10:38 -0600 Subject: [PATCH 009/393] fix(desktop): prompt history navigation --- packages/desktop/src/components/prompt-input.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/desktop/src/components/prompt-input.tsx b/packages/desktop/src/components/prompt-input.tsx index 6bd3127be..a04211a98 100644 --- a/packages/desktop/src/components/prompt-input.tsx +++ b/packages/desktop/src/components/prompt-input.tsx @@ -593,23 +593,24 @@ export const PromptInput: Component = (props) => { if (event.key === "ArrowUp" || event.key === "ArrowDown") { if (event.altKey || event.ctrlKey || event.metaKey) return - const { collapsed, cursorPosition, textLength } = getCaretState() + const { collapsed } = getCaretState() if (!collapsed) return + + const cursorPosition = getCursorPosition(editorRef) + const textLength = promptLength(prompt.current()) const inHistory = store.historyIndex >= 0 - const atAbsoluteStart = cursorPosition === 0 - const atAbsoluteEnd = cursorPosition === textLength - const allowUp = (inHistory && atAbsoluteEnd) || atAbsoluteStart - const allowDown = (inHistory && atAbsoluteStart) || atAbsoluteEnd + const atStart = cursorPosition <= 0 + const atEnd = cursorPosition >= textLength if (event.key === "ArrowUp") { - if (!allowUp) return + if (!atStart && !(inHistory && atEnd)) return if (navigateHistory("up")) { event.preventDefault() } return } - if (!allowDown) return + if (!atEnd && !(inHistory && atStart)) return if (navigateHistory("down")) { event.preventDefault() } From 99680baf83cd494a97b4db115ba2f1a2a4f7ce06 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 16 Dec 2025 15:25:00 -0600 Subject: [PATCH 010/393] fix(desktop): focus prompt input after dialog close --- packages/desktop/src/components/prompt-input.tsx | 1 + packages/ui/src/components/session-turn.tsx | 3 +-- packages/ui/src/context/dialog.tsx | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/desktop/src/components/prompt-input.tsx b/packages/desktop/src/components/prompt-input.tsx index a04211a98..b152ff0f5 100644 --- a/packages/desktop/src/components/prompt-input.tsx +++ b/packages/desktop/src/components/prompt-input.tsx @@ -850,6 +850,7 @@ export const PromptInput: Component = (props) => {
{ editorRef = el props.ref?.(el) diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index 14e4055ca..cab6a7af3 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -108,8 +108,7 @@ export function SessionTurn( createResizeObserver( () => state.contentRef, - ({ height }) => { - console.log(height) + () => { scrollToBottom() }, ) diff --git a/packages/ui/src/context/dialog.tsx b/packages/ui/src/context/dialog.tsx index 71fc63806..56be9ee47 100644 --- a/packages/ui/src/context/dialog.tsx +++ b/packages/ui/src/context/dialog.tsx @@ -33,6 +33,10 @@ function init() { }, close() { active()?.onClose?.() + if (!active()?.onClose) { + const promptInput = document.querySelector("[data-component=prompt-input]") as HTMLElement + promptInput?.focus() + } setActive(undefined) }, show(element: DialogElement, owner: Owner, onClose?: () => void) { @@ -48,7 +52,6 @@ function init() { open={true} onOpenChange={(open) => { if (!open) { - console.log("closing") result.close() } }} From 1755a3fe0778ca9021fe7b59218c1830826502bc Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 16 Dec 2025 15:32:14 -0600 Subject: [PATCH 011/393] fix(desktop): auto-scroll --- packages/ui/src/components/session-turn.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index cab6a7af3..fa435b402 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -106,12 +106,7 @@ export function SessionTurn( }) } - createResizeObserver( - () => state.contentRef, - () => { - scrollToBottom() - }, - ) + createResizeObserver(() => state.contentRef, scrollToBottom) createEffect(() => { if (!working()) { From 5f57cee8e46abc6b1368c10991a22b2d676cf2a4 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:42:21 -0800 Subject: [PATCH 012/393] =?UTF-8?q?fix:=20user=20invoked=20subtasks=20caus?= =?UTF-8?q?ing=20tool=5Fuse=20or=20missing=20thinking=20signa=E2=80=A6=20(?= =?UTF-8?q?#5650)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .opencode/command/commit.md | 1 + packages/opencode/src/session/prompt.ts | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/.opencode/command/commit.md b/.opencode/command/commit.md index c318ed54b..8e9346ebc 100644 --- a/.opencode/command/commit.md +++ b/.opencode/command/commit.md @@ -1,6 +1,7 @@ --- description: git commit and push model: opencode/glm-4.6 +subtask: true --- commit and push diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 3be4c45fd..4ae7469a3 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -396,6 +396,30 @@ export namespace SessionPrompt { }, } satisfies MessageV2.ToolPart) } + + // Add synthetic user message to prevent certain reasoning models from erroring + // If we create assistant messages w/ out user ones following mid loop thinking signatures + // will be missing and it can cause errors for models like gemini for example + const summaryUserMsg: MessageV2.User = { + id: Identifier.ascending("message"), + sessionID, + role: "user", + time: { + created: Date.now(), + }, + agent: lastUser.agent, + model: lastUser.model, + } + await Session.updateMessage(summaryUserMsg) + await Session.updatePart({ + id: Identifier.ascending("part"), + messageID: summaryUserMsg.id, + sessionID, + type: "text", + text: "Summarize the task tool output above and continue with your task.", + synthetic: true, + } satisfies MessageV2.TextPart) + continue } From f07d4b933c015310bbb7703c02b2672595a2aef6 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 16 Dec 2025 15:42:31 -0600 Subject: [PATCH 013/393] fix(desktop): prompt history nav --- packages/desktop/src/components/prompt-input.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/desktop/src/components/prompt-input.tsx b/packages/desktop/src/components/prompt-input.tsx index b152ff0f5..aea9f4e23 100644 --- a/packages/desktop/src/components/prompt-input.tsx +++ b/packages/desktop/src/components/prompt-input.tsx @@ -598,19 +598,24 @@ export const PromptInput: Component = (props) => { const cursorPosition = getCursorPosition(editorRef) const textLength = promptLength(prompt.current()) + const textContent = editorRef.textContent ?? "" + const isEmpty = textContent.trim() === "" || textLength <= 1 + const hasNewlines = textContent.includes("\n") const inHistory = store.historyIndex >= 0 - const atStart = cursorPosition <= 0 - const atEnd = cursorPosition >= textLength + const atStart = cursorPosition <= (isEmpty ? 1 : 0) + const atEnd = cursorPosition >= (isEmpty ? textLength - 1 : textLength) + const allowUp = isEmpty || atStart || (!hasNewlines && !inHistory) || (inHistory && atEnd) + const allowDown = isEmpty || atEnd || (!hasNewlines && !inHistory) || (inHistory && atStart) if (event.key === "ArrowUp") { - if (!atStart && !(inHistory && atEnd)) return + if (!allowUp) return if (navigateHistory("up")) { event.preventDefault() } return } - if (!atEnd && !(inHistory && atStart)) return + if (!allowDown) return if (navigateHistory("down")) { event.preventDefault() } From ef0fa2007b658ca49d4a19ab4234553b9380ee80 Mon Sep 17 00:00:00 2001 From: opencode Date: Tue, 16 Dec 2025 21:47:41 +0000 Subject: [PATCH 014/393] release: v1.0.164 --- bun.lock | 30 +++++++++++++------------- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/desktop/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +++++------ packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 4 ++-- packages/sdk/js/package.json | 4 ++-- packages/slack/package.json | 2 +- packages/tauri/package.json | 2 +- packages/ui/package.json | 2 +- packages/util/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 18 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index b30f89dd3..5718408a1 100644 --- a/bun.lock +++ b/bun.lock @@ -21,7 +21,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.0.163", + "version": "1.0.164", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -49,7 +49,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.0.163", + "version": "1.0.164", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -76,7 +76,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.0.163", + "version": "1.0.164", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -100,7 +100,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.0.163", + "version": "1.0.164", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -124,7 +124,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.0.163", + "version": "1.0.164", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -171,7 +171,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.0.163", + "version": "1.0.164", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -200,7 +200,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.0.163", + "version": "1.0.164", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -216,7 +216,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.0.163", + "version": "1.0.164", "bin": { "opencode": "./bin/opencode", }, @@ -308,7 +308,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.0.163", + "version": "1.0.164", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -328,7 +328,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.0.163", + "version": "1.0.164", "devDependencies": { "@hey-api/openapi-ts": "0.88.1", "@tsconfig/node22": "catalog:", @@ -339,7 +339,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.0.163", + "version": "1.0.164", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -352,7 +352,7 @@ }, "packages/tauri": { "name": "@opencode-ai/tauri", - "version": "1.0.163", + "version": "1.0.164", "dependencies": { "@opencode-ai/desktop": "workspace:*", "@tauri-apps/api": "^2", @@ -377,7 +377,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.0.163", + "version": "1.0.164", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -412,7 +412,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.0.163", + "version": "1.0.164", "dependencies": { "zod": "catalog:", }, @@ -423,7 +423,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.0.163", + "version": "1.0.164", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 7607f03a4..ca24a5d26 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.163", + "version": "1.0.164", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 7704332a7..b37dc0294 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.163", + "version": "1.0.164", "private": true, "type": "module", "dependencies": { diff --git a/packages/console/function/package.json b/packages/console/function/package.json index e62078f9a..7a88069bb 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.163", + "version": "1.0.164", "$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 5fc887fe4..6c718fee5 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.163", + "version": "1.0.164", "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 026fd4ec4..7f28ecc10 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/desktop", - "version": "1.0.163", + "version": "1.0.164", "description": "", "type": "module", "exports": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 36e8ef01c..edb2fa605 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.0.163", + "version": "1.0.164", "private": true, "type": "module", "scripts": { diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 8d7a72cb9..9f375b720 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.0.163" +version = "1.0.164" 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.163/opencode-darwin-arm64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.164/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.163/opencode-darwin-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.164/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.163/opencode-linux-arm64.tar.gz" +archive = "https://github.com/sst/opencode/releases/download/v1.0.164/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.163/opencode-linux-x64.tar.gz" +archive = "https://github.com/sst/opencode/releases/download/v1.0.164/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.163/opencode-windows-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.164/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 7fdd342ce..95d22ed34 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.0.163", + "version": "1.0.164", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index d65b1dec2..003c3677c 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.163", + "version": "1.0.164", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index c94dffabe..0febe1a10 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.163", + "version": "1.0.164", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", @@ -24,4 +24,4 @@ "typescript": "catalog:", "@typescript/native-preview": "catalog:" } -} +} \ No newline at end of file diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 2ea45ea7b..e596b9d6e 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.163", + "version": "1.0.164", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", @@ -29,4 +29,4 @@ "publishConfig": { "directory": "dist" } -} +} \ No newline at end of file diff --git a/packages/slack/package.json b/packages/slack/package.json index 07412a5ff..afa90acc3 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.0.163", + "version": "1.0.164", "type": "module", "scripts": { "dev": "bun run src/index.ts", diff --git a/packages/tauri/package.json b/packages/tauri/package.json index 9568e279d..e0c6b177a 100644 --- a/packages/tauri/package.json +++ b/packages/tauri/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/tauri", "private": true, - "version": "1.0.163", + "version": "1.0.164", "type": "module", "scripts": { "typecheck": "tsgo -b", diff --git a/packages/ui/package.json b/packages/ui/package.json index 2db6c52a1..b2e7e331d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.0.163", + "version": "1.0.164", "type": "module", "exports": { "./*": "./src/components/*.tsx", diff --git a/packages/util/package.json b/packages/util/package.json index 8f4a10a8d..59b13aa35 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.0.163", + "version": "1.0.164", "private": true, "type": "module", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index e1a51be71..b7ca5ee89 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/web", "type": "module", - "version": "1.0.163", + "version": "1.0.164", "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 773143ef9..b9f638f4c 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.163", + "version": "1.0.164", "publisher": "sst-dev", "repository": { "type": "git", From 2f2ea989372393620b754604688d791978a7d7e1 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:06:28 -0600 Subject: [PATCH 015/393] fix(share): content wasn't centered --- packages/enterprise/src/routes/share/[shareID].tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/enterprise/src/routes/share/[shareID].tsx b/packages/enterprise/src/routes/share/[shareID].tsx index 236fbf26b..bc3ca9f08 100644 --- a/packages/enterprise/src/routes/share/[shareID].tsx +++ b/packages/enterprise/src/routes/share/[shareID].tsx @@ -352,7 +352,7 @@ export default function () { messageID={store.messageId ?? firstUserMessage()!.id!} classes={{ root: "grow", - content: "flex flex-col justify-between items-start", + content: "flex flex-col justify-between", container: "w-full pb-20 " + (wide() From fc940dfcfb8d265a3cb0f678fa5228fbe6ad61c2 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 16 Dec 2025 22:07:10 +0000 Subject: [PATCH 016/393] chore: format code --- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 0febe1a10..7ebdcbe1d 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -24,4 +24,4 @@ "typescript": "catalog:", "@typescript/native-preview": "catalog:" } -} \ No newline at end of file +} diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index e596b9d6e..fdae55022 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -29,4 +29,4 @@ "publishConfig": { "directory": "dist" } -} \ No newline at end of file +} From 29aaf4f0000d4f917557aad590a9cdbc4bf015d5 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Tue, 16 Dec 2025 17:17:06 -0500 Subject: [PATCH 017/393] ci: fix release draft configuration to prevent automatic draft flag --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index afc23a81f..6b26e4421 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -211,6 +211,6 @@ jobs: fetch-depth: 0 ref: ${{ needs.publish.outputs.tagName }} - - run: gh release edit ${{ steps.publish.outputs.tagName }} --draft=false + - run: gh release edit ${{ needs.publish.outputs.tagName }} --draft=false env: GH_TOKEN: ${{ github.token }} From 8a185aa67844ca74ef07ed424257f7fbbc55abe6 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Tue, 16 Dec 2025 16:31:46 -0600 Subject: [PATCH 018/393] ci: fix missing pkg issue --- .github/workflows/duplicate-issues.yml | 2 ++ .github/workflows/review.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/duplicate-issues.yml b/.github/workflows/duplicate-issues.yml index 5969d9d41..dc82d297b 100644 --- a/.github/workflows/duplicate-issues.yml +++ b/.github/workflows/duplicate-issues.yml @@ -16,6 +16,8 @@ jobs: with: fetch-depth: 1 + - uses: ./.github/actions/setup-bun + - name: Install opencode run: curl -fsSL https://opencode.ai/install | bash diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index d974e2a76..36f6df54f 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -29,6 +29,8 @@ jobs: with: fetch-depth: 1 + - uses: ./.github/actions/setup-bun + - name: Install opencode run: curl -fsSL https://opencode.ai/install | bash From a0f9f8dabb42979911107fc3630bda1867549415 Mon Sep 17 00:00:00 2001 From: David Hill Date: Tue, 16 Dec 2025 23:23:18 +0000 Subject: [PATCH 019/393] fix: load more button --- packages/desktop/src/pages/layout.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index aba435332..bed8950c7 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -589,12 +589,11 @@ export default function Layout(props: ParentProps) {
-
+
+ + + + + } + > + {iife(() => { + const [url] = createResource( + () => currentSession(), + async (session) => { + if (!session) return + let shareURL = session.share?.url + if (!shareURL) { + shareURL = await globalSDK.client.session + .share({ sessionID: session.id, directory: currentDirectory() }) + .then((r) => r.data?.share?.url) + } + return shareURL + }, + ) + return {(url) => } + })} + +
diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index bed8950c7..618b84840 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -25,9 +25,8 @@ import { SortableProvider, closestCenter, createSortable, - useDragDropContext, } from "@thisbeyond/solid-dnd" -import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd" +import type { DragEvent } from "@thisbeyond/solid-dnd" import { useProviders } from "@/hooks/use-providers" import { Toast } from "@opencode-ai/ui/toast" import { useGlobalSDK } from "@/context/global-sdk" @@ -37,6 +36,7 @@ import { Header } from "@/components/header" import { useDialog } from "@opencode-ai/ui/context/dialog" import { DialogSelectProvider } from "@/components/dialog-select-provider" import { useCommand } from "@/context/command" +import { ConstrainDragXAxis } from "@/utils/solid-dnd" export default function Layout(props: ParentProps) { const [store, setStore] = createStore({ @@ -301,28 +301,6 @@ export default function Layout(props: ParentProps) { setStore("activeDraggable", undefined) } - const ConstrainDragXAxis = (): JSX.Element => { - const context = useDragDropContext() - if (!context) return <> - const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context - const transformer: Transformer = { - id: "constrain-x-axis", - order: 100, - callback: (transform) => ({ ...transform, x: 0 }), - } - onDragStart((event) => { - const id = getDraggableId(event) - if (!id) return - addTransformer("draggables", id, transformer) - }) - onDragEnd((event) => { - const id = getDraggableId(event) - if (!id) return - removeTransformer("draggables", id, transformer.id) - }) - return <> - } - const ProjectAvatar = (props: { project: Project class?: string diff --git a/packages/desktop/src/pages/session.tsx b/packages/desktop/src/pages/session.tsx index d024d5047..3415d0c4e 100644 --- a/packages/desktop/src/pages/session.tsx +++ b/packages/desktop/src/pages/session.tsx @@ -23,9 +23,8 @@ import { SortableProvider, closestCenter, createSortable, - useDragDropContext, } from "@thisbeyond/solid-dnd" -import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd" +import type { DragEvent } from "@thisbeyond/solid-dnd" import type { JSX } from "solid-js" import { useSync } from "@/context/sync" import { useTerminal, type LocalPTY } from "@/context/terminal" @@ -42,6 +41,7 @@ import { AssistantMessage, UserMessage } from "@opencode-ai/sdk/v2" import { useSDK } from "@/context/sdk" import { usePrompt } from "@/context/prompt" import { extractPromptFromParts } from "@/utils/prompt" +import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd" export default function Page() { const layout = useLayout() @@ -324,19 +324,6 @@ export default function Page() { if ((document.activeElement as HTMLElement)?.dataset?.component === "terminal") return if (dialog.active) return - if (event.key === "PageUp" || event.key === "PageDown") { - const scrollContainer = document.querySelector('[data-slot="session-turn-content"]') as HTMLElement - if (scrollContainer) { - event.preventDefault() - const scrollAmount = scrollContainer.clientHeight * 0.8 - scrollContainer.scrollBy({ - top: event.key === "PageUp" ? -scrollAmount : scrollAmount, - behavior: "instant", - }) - } - return - } - const focused = document.activeElement === inputRef if (focused) { if (event.key === "Escape") inputRef?.blur() @@ -519,36 +506,6 @@ export default function Page() { ) } - const ConstrainDragYAxis = (): JSX.Element => { - const context = useDragDropContext() - if (!context) return <> - const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context - const transformer: Transformer = { - id: "constrain-y-axis", - order: 100, - callback: (transform) => ({ ...transform, y: 0 }), - } - onDragStart((event) => { - const id = getDraggableId(event) - if (!id) return - addTransformer("draggables", id, transformer) - }) - onDragEnd((event) => { - const id = getDraggableId(event) - if (!id) return - removeTransformer("draggables", id, transformer.id) - }) - return <> - } - - const getDraggableId = (event: unknown): string | undefined => { - if (typeof event !== "object" || event === null) return undefined - if (!("draggable" in event)) return undefined - const draggable = (event as { draggable?: { id?: unknown } }).draggable - if (!draggable) return undefined - return typeof draggable.id === "string" ? draggable.id : undefined - } - const wide = createMemo(() => layout.review.state() === "tab" || !diffs().length) return ( diff --git a/packages/desktop/src/utils/solid-dnd.tsx b/packages/desktop/src/utils/solid-dnd.tsx new file mode 100644 index 000000000..a634be4b4 --- /dev/null +++ b/packages/desktop/src/utils/solid-dnd.tsx @@ -0,0 +1,55 @@ +import { useDragDropContext } from "@thisbeyond/solid-dnd" +import { JSXElement } from "solid-js" +import type { Transformer } from "@thisbeyond/solid-dnd" + +export const getDraggableId = (event: unknown): string | undefined => { + if (typeof event !== "object" || event === null) return undefined + if (!("draggable" in event)) return undefined + const draggable = (event as { draggable?: { id?: unknown } }).draggable + if (!draggable) return undefined + return typeof draggable.id === "string" ? draggable.id : undefined +} + +export const ConstrainDragXAxis = (): JSXElement => { + const context = useDragDropContext() + if (!context) return <> + const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context + const transformer: Transformer = { + id: "constrain-x-axis", + order: 100, + callback: (transform) => ({ ...transform, x: 0 }), + } + onDragStart((event) => { + const id = getDraggableId(event) + if (!id) return + addTransformer("draggables", id, transformer) + }) + onDragEnd((event) => { + const id = getDraggableId(event) + if (!id) return + removeTransformer("draggables", id, transformer.id) + }) + return <> +} + +export const ConstrainDragYAxis = (): JSXElement => { + const context = useDragDropContext() + if (!context) return <> + const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context + const transformer: Transformer = { + id: "constrain-y-axis", + order: 100, + callback: (transform) => ({ ...transform, y: 0 }), + } + onDragStart((event) => { + const id = getDraggableId(event) + if (!id) return + addTransformer("draggables", id, transformer) + }) + onDragEnd((event) => { + const id = getDraggableId(event) + if (!id) return + removeTransformer("draggables", id, transformer.id) + }) + return <> +} diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index b8e8106e8..94d9544d6 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -52,6 +52,7 @@ const icons = { copy: ``, check: ``, photo: ``, + share: ``, } export interface IconProps extends ComponentProps<"svg"> { diff --git a/packages/ui/src/components/popover.css b/packages/ui/src/components/popover.css new file mode 100644 index 000000000..74d7f5a39 --- /dev/null +++ b/packages/ui/src/components/popover.css @@ -0,0 +1,95 @@ +[data-slot="popover-trigger"] { + display: inline-flex; +} + +[data-component="popover-content"] { + z-index: 50; + min-width: 200px; + max-width: 320px; + border-radius: var(--radius-md); + border: 1px solid var(--border-weak-base); + background-color: var(--surface-raised-stronger-non-alpha); + box-shadow: var(--shadow-md); + transform-origin: var(--kb-popover-content-transform-origin); + + &:focus-within { + outline: none; + } + + &[data-closed] { + animation: popover-close 0.15s ease-out; + } + + &[data-expanded] { + animation: popover-open 0.15s ease-out; + } + + [data-slot="popover-header"] { + display: flex; + padding: 12px; + padding-bottom: 0; + justify-content: space-between; + align-items: center; + gap: 8px; + + [data-slot="popover-title"] { + flex: 1; + color: var(--text-strong); + margin: 0; + + font-family: var(--font-family-sans); + font-size: var(--font-size-base); + font-style: normal; + font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); + letter-spacing: var(--letter-spacing-normal); + } + + [data-slot="popover-close-button"] { + flex-shrink: 0; + } + } + + [data-slot="popover-description"] { + padding: 0 12px; + margin: 0; + color: var(--text-base); + + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-style: normal; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-large); + letter-spacing: var(--letter-spacing-normal); + } + + [data-slot="popover-body"] { + padding: 12px; + } + + [data-slot="popover-arrow"] { + fill: var(--surface-raised-stronger-non-alpha); + } +} + +@keyframes popover-open { + from { + opacity: 0; + transform: scale(0.96); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes popover-close { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.96); + } +} diff --git a/packages/ui/src/components/popover.tsx b/packages/ui/src/components/popover.tsx new file mode 100644 index 000000000..3262098e5 --- /dev/null +++ b/packages/ui/src/components/popover.tsx @@ -0,0 +1,44 @@ +import { Popover as Kobalte } from "@kobalte/core/popover" +import { ComponentProps, JSXElement, ParentProps, Show, splitProps } from "solid-js" +import { IconButton } from "./icon-button" + +export interface PopoverProps extends ParentProps, Omit, "children"> { + trigger: JSXElement + title?: JSXElement + description?: JSXElement + class?: ComponentProps<"div">["class"] + classList?: ComponentProps<"div">["classList"] +} + +export function Popover(props: PopoverProps) { + const [local, rest] = splitProps(props, ["trigger", "title", "description", "class", "classList", "children"]) + + return ( + + + {local.trigger} + + + + {/* */} + +
+ {local.title} + +
+
+ + {local.description} + +
{local.children}
+
+
+
+ ) +} diff --git a/packages/ui/src/components/text-field.tsx b/packages/ui/src/components/text-field.tsx index 63ffb2594..77f014b6b 100644 --- a/packages/ui/src/components/text-field.tsx +++ b/packages/ui/src/components/text-field.tsx @@ -56,6 +56,10 @@ export function TextField(props: TextFieldProps) { setTimeout(() => setCopied(false), 2000) } + function handleClick() { + if (local.copyable) handleCopy() + } + return ( ) } - -/** @deprecated Use TextField instead */ -export const Input = TextField -/** @deprecated Use TextFieldProps instead */ -export type InputProps = TextFieldProps diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css index 3f8838a7a..c4302a4d3 100644 --- a/packages/ui/src/styles/index.css +++ b/packages/ui/src/styles/index.css @@ -27,6 +27,7 @@ @import "../components/markdown.css" layer(components); @import "../components/message-part.css" layer(components); @import "../components/message-nav.css" layer(components); +@import "../components/popover.css" layer(components); @import "../components/progress-circle.css" layer(components); @import "../components/resize-handle.css" layer(components); @import "../components/select.css" layer(components); From d7e133732cb4378190a68be256ff2a3fc336cff7 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 17 Dec 2025 03:58:16 -0600 Subject: [PATCH 032/393] chore: cleanup --- packages/ui/src/components/session-turn.css | 2 -- packages/ui/src/components/session-turn.tsx | 38 ++++++++++----------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css index c0408cb0c..3861312cc 100644 --- a/packages/ui/src/components/session-turn.css +++ b/packages/ui/src/components/session-turn.css @@ -1,6 +1,5 @@ [data-component="session-turn"] { /* flex: 1; */ - --scroll-y: 0px; height: 100%; min-height: 0; min-width: 0; @@ -28,7 +27,6 @@ align-self: stretch; min-width: 0; gap: 42px; - /* gap: clamp(8px, calc(42px - var(--scroll-y) * 0.48), 42px); */ overflow-anchor: none; } diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index fa435b402..722b02492 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -3,7 +3,7 @@ import { useData } from "../context" import { useDiffComponent } from "../context/diff" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { checksum } from "@opencode-ai/util/encode" -import { createEffect, createMemo, createSignal, For, Match, onCleanup, ParentProps, Show, Switch } from "solid-js" +import { createEffect, createMemo, For, Match, onCleanup, ParentProps, Show, Switch } from "solid-js" import { createResizeObserver } from "@solid-primitives/resize-observer" import { DiffChanges } from "./diff-changes" import { Typewriter } from "./typewriter" @@ -54,18 +54,27 @@ export function SessionTurn( if (s.type !== "retry") return return s }) - const [retrySeconds, setRetrySeconds] = createSignal(0) + + let scrollRef: HTMLDivElement | undefined + const [state, setState] = createStore({ + contentRef: undefined as HTMLDivElement | undefined, + stickyTitleRef: undefined as HTMLDivElement | undefined, + stickyTriggerRef: undefined as HTMLDivElement | undefined, + autoScrolled: false, + userScrolled: false, + stickyHeaderHeight: 0, + retrySeconds: 0, + }) createEffect(() => { const r = retry() if (!r) { - setRetrySeconds(0) + setState("retrySeconds", 0) return } - const updateSeconds = () => { const next = r.next - if (next) setRetrySeconds(Math.max(0, Math.round((next - Date.now()) / 1000))) + if (next) setState("retrySeconds", Math.max(0, Math.round((next - Date.now()) / 1000))) } updateSeconds() @@ -73,20 +82,9 @@ export function SessionTurn( onCleanup(() => clearInterval(timer)) }) - let scrollRef: HTMLDivElement | undefined - const [state, setState] = createStore({ - contentRef: undefined as HTMLDivElement | undefined, - stickyTitleRef: undefined as HTMLDivElement | undefined, - stickyTriggerRef: undefined as HTMLDivElement | undefined, - userScrolled: false, - stickyHeaderHeight: 0, - scrollY: 0, - }) - function handleScroll() { - if (!scrollRef) return + if (!scrollRef || state.autoScrolled) return const { scrollTop, scrollHeight, clientHeight } = scrollRef - setState("scrollY", scrollTop) const atBottom = scrollHeight - scrollTop - clientHeight < 50 if (!atBottom && working()) { setState("userScrolled", true) @@ -101,8 +99,10 @@ export function SessionTurn( function scrollToBottom() { if (!scrollRef || state.userScrolled || !working()) return + setState("autoScrolled", true) requestAnimationFrame(() => { scrollRef?.scrollTo({ top: scrollRef.scrollHeight, behavior: "smooth" }) + setState("autoScrolled", false) }) } @@ -131,7 +131,7 @@ export function SessionTurn( ) return ( -
+
@@ -346,7 +346,7 @@ export function SessionTurn( })()} - · retrying {retrySeconds() > 0 ? `in ${retrySeconds()}s ` : ""} + · retrying {state.retrySeconds > 0 ? `in ${state.retrySeconds}s ` : ""} (#{retry()?.attempt}) From b695d3b6bb4d1f48cfa75ab1c866a42418d9c81b Mon Sep 17 00:00:00 2001 From: David Hill Date: Wed, 17 Dec 2025 11:21:56 +0000 Subject: [PATCH 033/393] fix: website cta button --- packages/console/app/src/routes/brand/index.css | 5 ++++- packages/console/app/src/routes/enterprise/index.css | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/console/app/src/routes/brand/index.css b/packages/console/app/src/routes/brand/index.css index b7c76f5bb..1240b91a5 100644 --- a/packages/console/app/src/routes/brand/index.css +++ b/packages/console/app/src/routes/brand/index.css @@ -110,10 +110,13 @@ [data-slot="cta-button"] { background: var(--color-background-strong); color: var(--color-text-inverted); - padding: 8px 16px; + padding: 8px 16px 8px 10px; border-radius: 4px; font-weight: 500; text-decoration: none; + display: flex; + align-items: center; + gap: 8px; @media (max-width: 55rem) { display: none; diff --git a/packages/console/app/src/routes/enterprise/index.css b/packages/console/app/src/routes/enterprise/index.css index 496a886eb..7eebf16ce 100644 --- a/packages/console/app/src/routes/enterprise/index.css +++ b/packages/console/app/src/routes/enterprise/index.css @@ -110,10 +110,13 @@ [data-slot="cta-button"] { background: var(--color-background-strong); color: var(--color-text-inverted); - padding: 8px 16px; + padding: 8px 16px 8px 10px; border-radius: 4px; font-weight: 500; text-decoration: none; + display: flex; + align-items: center; + gap: 8px; @media (max-width: 55rem) { display: none; From 4375149e638eb1b0655aa38c4506b196ee11623f Mon Sep 17 00:00:00 2001 From: David Hill Date: Wed, 17 Dec 2025 11:43:04 +0000 Subject: [PATCH 034/393] wip: auto-detect OS and show desktop download button --- .../console/app/src/routes/download/index.tsx | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/packages/console/app/src/routes/download/index.tsx b/packages/console/app/src/routes/download/index.tsx index 7783a738a..d8d2b5bb0 100644 --- a/packages/console/app/src/routes/download/index.tsx +++ b/packages/console/app/src/routes/download/index.tsx @@ -8,6 +8,47 @@ import { Faq } from "~/component/faq" import desktopAppIcon from "../../asset/lander/opencode-desktop-icon.png" import { Legal } from "~/component/legal" import { config } from "~/config" +import { createSignal, onMount, Show, JSX } from "solid-js" + +type OS = "macOS" | "Windows" | "Linux" | null + +function detectOS(): OS { + if (typeof navigator === "undefined") return null + const platform = navigator.platform.toLowerCase() + const userAgent = navigator.userAgent.toLowerCase() + + if (platform.includes("mac") || userAgent.includes("mac")) return "macOS" + if (platform.includes("win") || userAgent.includes("win")) return "Windows" + if (platform.includes("linux") || userAgent.includes("linux")) return "Linux" + return null +} + +function getDownloadUrl(os: OS): string { + const base = "https://github.com/sst/opencode/releases/latest/download" + switch (os) { + case "macOS": + return `${base}/opencode-desktop-darwin-aarch64.dmg` + case "Windows": + return `${base}/opencode-desktop-windows-x64.exe` + case "Linux": + return `${base}/opencode-desktop-linux-amd64.deb` + default: + return `${base}/opencode-desktop-darwin-aarch64.dmg` + } +} + +function IconDownload(props: JSX.SvgSVGAttributes) { + return ( + + + + ) +} function CopyStatus() { return ( @@ -20,6 +61,12 @@ function CopyStatus() { export default function Download() { const downloadUrl = "https://github.com/sst/opencode/releases/latest/download" + const [detectedOS, setDetectedOS] = createSignal(null) + + onMount(() => { + setDetectedOS(detectOS()) + }) + const handleCopyClick = (command: string) => (event: Event) => { const button = event.currentTarget as HTMLButtonElement navigator.clipboard.writeText(command) @@ -44,6 +91,12 @@ export default function Download() {

Download OpenCode

Available in Beta for macOS, Windows, and Linux

+ + + + Download for {detectedOS()} + +
From 5da1c0087b7a0216bf633d12ff038422727ff235 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 17 Dec 2025 12:04:41 +0000 Subject: [PATCH 035/393] ignore: update download stats 2025-12-17 --- STATS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/STATS.md b/STATS.md index c29c4dde9..c08f2e4b7 100644 --- a/STATS.md +++ b/STATS.md @@ -172,3 +172,4 @@ | 2025-12-14 | 1,082,042 (+8,481) | 1,052,425 (+7,817) | 2,134,467 (+16,298) | | 2025-12-15 | 1,093,632 (+11,590) | 1,059,078 (+6,653) | 2,152,710 (+18,243) | | 2025-12-16 | 1,120,477 (+26,845) | 1,078,022 (+18,944) | 2,198,499 (+45,789) | +| 2025-12-17 | 1,151,067 (+30,590) | 1,097,661 (+19,639) | 2,248,728 (+50,229) | From 5c490c51edd5a8e3953fcc439d46c0e138294073 Mon Sep 17 00:00:00 2001 From: Amadeus Demarzi Date: Wed, 17 Dec 2025 05:33:46 -0800 Subject: [PATCH 036/393] Diffs Performance Improvements (#5653) Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com> --- packages/desktop/src/pages/layout.tsx | 4 +- packages/desktop/src/pages/session.tsx | 9 +- .../enterprise/src/routes/share/[shareID].tsx | 474 +++++++++--------- packages/ui/package.json | 1 + packages/ui/src/components/diff-ssr.tsx | 23 +- packages/ui/src/components/diff.css | 2 + packages/ui/src/components/session-review.css | 1 + packages/ui/src/context/worker-pool.tsx | 10 + packages/ui/src/custom-elements.d.ts | 5 +- packages/ui/src/pierre/worker.ts | 36 +- 10 files changed, 305 insertions(+), 260 deletions(-) create mode 100644 packages/ui/src/context/worker-pool.tsx diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index 618b84840..540c5d778 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -613,7 +613,7 @@ export default function Layout(props: ParentProps) { classList={{ "relative @container w-12 pb-5 shrink-0 bg-background-base": true, "flex flex-col gap-5.5 items-start self-stretch justify-between": true, - "border-r border-border-weak-base": true, + "border-r border-border-weak-base contain-strict": true, }} style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }} > @@ -755,7 +755,7 @@ export default function Layout(props: ParentProps) {
-
{props.children}
+
{props.children}
diff --git a/packages/desktop/src/pages/session.tsx b/packages/desktop/src/pages/session.tsx index 3415d0c4e..7d1392c20 100644 --- a/packages/desktop/src/pages/session.tsx +++ b/packages/desktop/src/pages/session.tsx @@ -578,7 +578,10 @@ export default function Page() {
- +
- +
import("@opencode-ai/ui/diff").then((m) => ({ default: m.Diff }))) const ClientOnlyCode = clientOnly(() => import("@opencode-ai/ui/code").then((m) => ({ default: m.Code }))) +const ClientOnlyWorkerPoolProvider = clientOnly(() => + import("@opencode-ai/ui/pierre/worker").then((m) => ({ + default: (props: { children: any }) => ( + {props.children} + ), + })), +) const SessionDataMissingError = NamedError.create( "SessionDataMissingError", @@ -197,256 +205,260 @@ export default function () { - - - - {iife(() => { - const [store, setStore] = createStore({ - messageId: undefined as string | undefined, - }) - const messages = createMemo(() => - data().sessionID - ? (data().message[data().sessionID]?.filter((m) => m.role === "user") ?? []).sort( - (a, b) => a.time.created - b.time.created, - ) - : [], - ) - const firstUserMessage = createMemo(() => messages().at(0)) - const activeMessage = createMemo( - () => messages().find((m) => m.id === store.messageId) ?? firstUserMessage(), - ) - function setActiveMessage(message: UserMessage | undefined) { - if (message) { - setStore("messageId", message.id) - } else { - setStore("messageId", undefined) + + + + + {iife(() => { + const [store, setStore] = createStore({ + messageId: undefined as string | undefined, + }) + const messages = createMemo(() => + data().sessionID + ? (data().message[data().sessionID]?.filter((m) => m.role === "user") ?? []).sort( + (a, b) => a.time.created - b.time.created, + ) + : [], + ) + const firstUserMessage = createMemo(() => messages().at(0)) + const activeMessage = createMemo( + () => messages().find((m) => m.id === store.messageId) ?? firstUserMessage(), + ) + function setActiveMessage(message: UserMessage | undefined) { + if (message) { + setStore("messageId", message.id) + } else { + setStore("messageId", undefined) + } } - } - const provider = createMemo(() => activeMessage()?.model?.providerID) - const modelID = createMemo(() => activeMessage()?.model?.modelID) - const model = createMemo(() => data().model[data().sessionID]?.find((m) => m.id === modelID())) - const diffs = createMemo(() => { - const diffs = data().session_diff[data().sessionID] ?? [] - const preloaded = data().session_diff_preload[data().sessionID] ?? [] - return diffs.map((diff) => ({ - ...diff, - preloaded: preloaded.find((d) => d.newFile.name === diff.file), - })) - }) - const splitDiffs = createMemo(() => { - const diffs = data().session_diff[data().sessionID] ?? [] - const preloaded = data().session_diff_preload_split[data().sessionID] ?? [] - return diffs.map((diff) => ({ - ...diff, - preloaded: preloaded.find((d) => d.newFile.name === diff.file), - })) - }) + const provider = createMemo(() => activeMessage()?.model?.providerID) + const modelID = createMemo(() => activeMessage()?.model?.modelID) + const model = createMemo(() => data().model[data().sessionID]?.find((m) => m.id === modelID())) + const diffs = createMemo(() => { + const diffs = data().session_diff[data().sessionID] ?? [] + const preloaded = data().session_diff_preload[data().sessionID] ?? [] + return diffs.map((diff) => ({ + ...diff, + preloaded: preloaded.find((d) => d.newFile.name === diff.file), + })) + }) + const splitDiffs = createMemo(() => { + const diffs = data().session_diff[data().sessionID] ?? [] + const preloaded = data().session_diff_preload_split[data().sessionID] ?? [] + return diffs.map((diff) => ({ + ...diff, + preloaded: preloaded.find((d) => d.newFile.name === diff.file), + })) + }) - const title = () => ( -
-
-
- -
v{info().version}
-
-
- -
{model()?.name ?? modelID()}
-
-
- {DateTime.fromMillis(info().time.created).toFormat("dd MMM yyyy, HH:mm")} -
-
-
{info().title}
-
- ) - - const turns = () => ( -
-
{title()}
-
- - {(message) => ( - ( +
+
+
+ +
v{info().version}
+
+
+ - )} - -
-
- -
-
- ) - - const wide = createMemo(() => diffs().length === 0) - - return ( -
-
-
- - - +
{model()?.name ?? modelID()}
+
+
+ {DateTime.fromMillis(info().time.created).toFormat("dd MMM yyyy, HH:mm")} +
-
- - +
{info().title}
+
+ ) + + const turns = () => ( +
+
{title()}
+
+ + {(message) => ( + + )} +
- -
-
+
+ +
+
+ ) + + const wide = createMemo(() => diffs().length === 0) + + return ( +
+
+
+ + + +
+
+ + +
+
+
1, - "px-6": !wide() && messages().length === 1, + "@container relative shrink-0 pt-14 flex flex-col gap-10 min-h-0 w-full": true, + "mx-auto max-w-200": !wide(), }} > - {title()} -
-
- - 1 - ? "pr-6 pl-18" - : "px-6"), +
1, + "px-6": !wide() && messages().length === 1, }} > -
- -
- -
-
- 0}> - -
- -
-
-
-
- - 0}> - - - - Session - - + + 1 + ? "pr-6 pl-18" + : "px-6"), + }} > - {diffs().length} Files Changed - - - - {turns()} - - - - - -
- {turns()} +
+ +
+ +
- - + 0}> + +
+ +
+
+
+
+ + 0}> + + + + Session + + + {diffs().length} Files Changed + + + + {turns()} + + + + + +
+ {turns()} +
+
+
+
-
- ) - })} - - - + ) + })} + + + + ) }} diff --git a/packages/ui/package.json b/packages/ui/package.json index b2e7e331d..618dbf1f0 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -5,6 +5,7 @@ "exports": { "./*": "./src/components/*.tsx", "./pierre": "./src/pierre/index.ts", + "./pierre/*": "./src/pierre/*.ts", "./hooks": "./src/hooks/index.ts", "./context": "./src/context/index.ts", "./context/*": "./src/context/*.tsx", diff --git a/packages/ui/src/components/diff-ssr.tsx b/packages/ui/src/components/diff-ssr.tsx index b38b4a34f..e367a4fbe 100644 --- a/packages/ui/src/components/diff-ssr.tsx +++ b/packages/ui/src/components/diff-ssr.tsx @@ -1,8 +1,9 @@ -import { FileDiff } from "@pierre/diffs" +import { DIFFS_TAG_NAME, FileDiff } from "@pierre/diffs" import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr" import { onCleanup, onMount, Show, splitProps } from "solid-js" -import { isServer } from "solid-js/web" +import { Dynamic, isServer } from "solid-js/web" import { createDefaultOptions, styleVariables, type DiffProps } from "../pierre" +import { useWorkerPool } from "../context/worker-pool" export type SSRDiffProps = DiffProps & { preloadedDiff: PreloadMultiFileDiffResult @@ -12,17 +13,21 @@ export function Diff(props: SSRDiffProps) { let container!: HTMLDivElement let fileDiffRef!: HTMLElement const [local, others] = splitProps(props, ["before", "after", "class", "classList", "annotations"]) + const workerPool = useWorkerPool() let fileDiffInstance: FileDiff | undefined const cleanupFunctions: Array<() => void> = [] onMount(() => { if (isServer || !props.preloadedDiff) return - fileDiffInstance = new FileDiff({ - ...createDefaultOptions(props.diffStyle), - ...others, - ...props.preloadedDiff, - }) + fileDiffInstance = new FileDiff( + { + ...createDefaultOptions(props.diffStyle), + ...others, + ...props.preloadedDiff, + }, + workerPool, + ) // @ts-expect-error - fileContainer is private but needed for SSR hydration fileDiffInstance.fileContainer = fileDiffRef fileDiffInstance.hydrate({ @@ -65,11 +70,11 @@ export function Diff(props: SSRDiffProps) { return (
- +