diff --git a/bun.lock b/bun.lock
index e5892a7745..53d80630b8 100644
--- a/bun.lock
+++ b/bun.lock
@@ -22,7 +22,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
- "version": "1.1.25",
+ "version": "1.1.26",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -71,7 +71,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
- "version": "1.1.25",
+ "version": "1.1.26",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -105,7 +105,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
- "version": "1.1.25",
+ "version": "1.1.26",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -132,7 +132,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
- "version": "1.1.25",
+ "version": "1.1.26",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -156,7 +156,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
- "version": "1.1.25",
+ "version": "1.1.26",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -180,7 +180,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
- "version": "1.1.25",
+ "version": "1.1.26",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -209,7 +209,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
- "version": "1.1.25",
+ "version": "1.1.26",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -238,7 +238,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
- "version": "1.1.25",
+ "version": "1.1.26",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -254,7 +254,7 @@
},
"packages/opencode": {
"name": "opencode",
- "version": "1.1.25",
+ "version": "1.1.26",
"bin": {
"opencode": "./bin/opencode",
},
@@ -358,7 +358,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
- "version": "1.1.25",
+ "version": "1.1.26",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -378,7 +378,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
- "version": "1.1.25",
+ "version": "1.1.26",
"devDependencies": {
"@hey-api/openapi-ts": "0.90.4",
"@tsconfig/node22": "catalog:",
@@ -389,7 +389,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
- "version": "1.1.25",
+ "version": "1.1.26",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -402,7 +402,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
- "version": "1.1.25",
+ "version": "1.1.26",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -443,7 +443,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
- "version": "1.1.25",
+ "version": "1.1.26",
"dependencies": {
"zod": "catalog:",
},
@@ -454,7 +454,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
- "version": "1.1.25",
+ "version": "1.1.26",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
diff --git a/packages/app/index.html b/packages/app/index.html
index 450807a42e..1e516cbbb1 100644
--- a/packages/app/index.html
+++ b/packages/app/index.html
@@ -4,10 +4,10 @@
OpenCode
-
-
-
-
+
+
+
+
diff --git a/packages/app/package.json b/packages/app/package.json
index 2a754c9673..736a262504 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
- "version": "1.1.25",
+ "version": "1.1.26",
"description": "",
"type": "module",
"exports": {
diff --git a/packages/app/public/apple-touch-icon-v2.png b/packages/app/public/apple-touch-icon-v2.png
new file mode 120000
index 0000000000..c0d4353db4
--- /dev/null
+++ b/packages/app/public/apple-touch-icon-v2.png
@@ -0,0 +1 @@
+../../ui/src/assets/favicon/apple-touch-icon-v2.png
\ No newline at end of file
diff --git a/packages/app/public/favicon-96x96-v2.png b/packages/app/public/favicon-96x96-v2.png
new file mode 120000
index 0000000000..b3129f6bf9
--- /dev/null
+++ b/packages/app/public/favicon-96x96-v2.png
@@ -0,0 +1 @@
+../../ui/src/assets/favicon/favicon-96x96-v2.png
\ No newline at end of file
diff --git a/packages/app/public/favicon-v2.ico b/packages/app/public/favicon-v2.ico
new file mode 120000
index 0000000000..d8527270af
--- /dev/null
+++ b/packages/app/public/favicon-v2.ico
@@ -0,0 +1 @@
+../../ui/src/assets/favicon/favicon-v2.ico
\ No newline at end of file
diff --git a/packages/app/public/favicon-v2.svg b/packages/app/public/favicon-v2.svg
new file mode 120000
index 0000000000..2600394cea
--- /dev/null
+++ b/packages/app/public/favicon-v2.svg
@@ -0,0 +1 @@
+../../ui/src/assets/favicon/favicon-v2.svg
\ No newline at end of file
diff --git a/packages/app/src/components/dialog-edit-project.tsx b/packages/app/src/components/dialog-edit-project.tsx
index c870228607..2600bd8e24 100644
--- a/packages/app/src/components/dialog-edit-project.tsx
+++ b/packages/app/src/components/dialog-edit-project.tsx
@@ -27,7 +27,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
const [store, setStore] = createStore({
name: defaultName(),
color: props.project.icon?.color || "pink",
- iconUrl: props.project.icon?.url || "",
+ iconUrl: props.project.icon?.override || "",
saving: false,
})
@@ -79,7 +79,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
await globalSDK.client.project.update({
projectID: props.project.id,
name,
- icon: { color: store.color, url: store.iconUrl },
+ icon: { color: store.color, override: store.iconUrl },
})
setStore("saving", false)
dialog.close()
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index 81222709f2..1c30cee98f 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -300,7 +300,8 @@ export const PromptInput: Component = (props) => {
event.stopPropagation()
const items = Array.from(clipboardData.items)
- const imageItems = items.filter((item) => ACCEPTED_FILE_TYPES.includes(item.type))
+ const fileItems = items.filter((item) => item.kind === "file")
+ const imageItems = fileItems.filter((item) => ACCEPTED_FILE_TYPES.includes(item.type))
if (imageItems.length > 0) {
for (const item of imageItems) {
@@ -310,7 +311,16 @@ export const PromptInput: Component = (props) => {
return
}
+ if (fileItems.length > 0) {
+ showToast({
+ title: "Unsupported paste",
+ description: "Only images or PDFs can be pasted here.",
+ })
+ return
+ }
+
const plainText = clipboardData.getData("text/plain") ?? ""
+ if (!plainText) return
addPart({ type: "text", content: plainText, start: 0, end: 0 })
}
diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx
index a8da156092..d7d09aa399 100644
--- a/packages/app/src/context/layout.tsx
+++ b/packages/app/src/context/layout.tsx
@@ -33,8 +33,6 @@ type SessionTabs = {
type SessionView = {
scroll: Record
reviewOpen?: string[]
- terminalOpened?: boolean
- reviewPanelOpened?: boolean
}
export type LocalProject = Partial & { worktree: string; expanded: boolean }
@@ -78,9 +76,11 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
terminal: {
height: 280,
+ opened: false,
},
review: {
diffStyle: "split" as ReviewDiffStyle,
+ panelOpened: true,
},
session: {
width: 600,
@@ -172,7 +172,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
const current = store.sessionView[sessionKey]
const keep = meta.active ?? sessionKey
if (!current) {
- setStore("sessionView", sessionKey, { scroll: next, terminalOpened: false, reviewPanelOpened: true })
+ setStore("sessionView", sessionKey, { scroll: next })
prune(keep)
return
}
@@ -208,10 +208,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
})
})
- const usedColors = new Set()
+ const [colors, setColors] = createStore>({})
- function pickAvailableColor(): AvatarColorKey {
- const available = AVATAR_COLOR_KEYS.filter((c) => !usedColors.has(c))
+ function pickAvailableColor(used: Set): AvatarColorKey {
+ const available = AVATAR_COLOR_KEYS.filter((c) => !used.has(c))
if (available.length === 0) return AVATAR_COLOR_KEYS[Math.floor(Math.random() * AVATAR_COLOR_KEYS.length)]
return available[Math.floor(Math.random() * available.length)]
}
@@ -222,24 +222,15 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
const metadata = projectID
? globalSync.data.project.find((x) => x.id === projectID)
: globalSync.data.project.find((x) => x.worktree === project.worktree)
- return [
- {
- ...(metadata ?? {}),
- ...project,
- icon: { url: metadata?.icon?.url, color: metadata?.icon?.color },
+ return {
+ ...(metadata ?? {}),
+ ...project,
+ icon: {
+ url: metadata?.icon?.url,
+ override: metadata?.icon?.override,
+ color: metadata?.icon?.color,
},
- ]
- }
-
- function colorize(project: LocalProject) {
- if (project.icon?.color) return project
- const color = pickAvailableColor()
- usedColors.add(color)
- project.icon = { ...project.icon, color }
- if (project.id) {
- globalSdk.client.project.update({ projectID: project.id, icon: { color } })
}
- return project
}
const roots = createMemo(() => {
@@ -277,8 +268,37 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
})
})
- const enriched = createMemo(() => server.projects.list().flatMap(enrich))
- const list = createMemo(() => enriched().flatMap(colorize))
+ const enriched = createMemo(() => server.projects.list().map(enrich))
+ const list = createMemo(() => {
+ const projects = enriched()
+ return projects.map((project) => {
+ const color = project.icon?.color ?? colors[project.worktree]
+ if (!color) return project
+ const icon = project.icon ? { ...project.icon, color } : { color }
+ return { ...project, icon }
+ })
+ })
+
+ createEffect(() => {
+ const projects = enriched()
+ if (projects.length === 0) return
+
+ const used = new Set()
+ for (const project of projects) {
+ const color = project.icon?.color ?? colors[project.worktree]
+ if (color) used.add(color)
+ }
+
+ for (const project of projects) {
+ if (project.icon?.color) continue
+ if (colors[project.worktree]) continue
+ const color = pickAvailableColor(used)
+ used.add(color)
+ setColors(project.worktree, color)
+ if (!project.id) continue
+ void globalSdk.client.project.update({ projectID: project.id, icon: { color } })
+ }
+ })
onMount(() => {
Promise.all(
@@ -379,31 +399,31 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
touch(sessionKey)
scroll.seed(sessionKey)
const s = createMemo(() => store.sessionView[sessionKey] ?? { scroll: {} })
- const terminalOpened = createMemo(() => s().terminalOpened ?? false)
- const reviewPanelOpened = createMemo(() => s().reviewPanelOpened ?? true)
+ const terminalOpened = createMemo(() => store.terminal?.opened ?? false)
+ const reviewPanelOpened = createMemo(() => store.review?.panelOpened ?? true)
function setTerminalOpened(next: boolean) {
- const current = store.sessionView[sessionKey]
+ const current = store.terminal
if (!current) {
- setStore("sessionView", sessionKey, { scroll: {}, terminalOpened: next, reviewPanelOpened: true })
+ setStore("terminal", { height: 280, opened: next })
return
}
- const value = current.terminalOpened ?? false
+ const value = current.opened ?? false
if (value === next) return
- setStore("sessionView", sessionKey, "terminalOpened", next)
+ setStore("terminal", "opened", next)
}
function setReviewPanelOpened(next: boolean) {
- const current = store.sessionView[sessionKey]
+ const current = store.review
if (!current) {
- setStore("sessionView", sessionKey, { scroll: {}, terminalOpened: false, reviewPanelOpened: next })
+ setStore("review", { diffStyle: "split" as ReviewDiffStyle, panelOpened: next })
return
}
- const value = current.reviewPanelOpened ?? true
+ const value = current.panelOpened ?? true
if (value === next) return
- setStore("sessionView", sessionKey, "reviewPanelOpened", next)
+ setStore("review", "panelOpened", next)
}
return {
@@ -444,8 +464,6 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
if (!current) {
setStore("sessionView", sessionKey, {
scroll: {},
- terminalOpened: false,
- reviewPanelOpened: true,
reviewOpen: open,
})
return
diff --git a/packages/app/src/entry.tsx b/packages/app/src/entry.tsx
index 28741098c8..8c4662926a 100644
--- a/packages/app/src/entry.tsx
+++ b/packages/app/src/entry.tsx
@@ -37,7 +37,7 @@ const platform: Platform = {
.then(() => {
const notification = new Notification(title, {
body: description ?? "",
- icon: "https://opencode.ai/favicon-96x96.png",
+ icon: "https://opencode.ai/favicon-96x96-v2.png",
})
notification.onclick = () => {
window.focus()
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index 1f2f04fa3e..e7808501b0 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -543,7 +543,7 @@ export default function Layout(props: ParentProps) {
running: number
}
- const prefetchChunk = 200
+ const prefetchChunk = 600
const prefetchConcurrency = 1
const prefetchPendingLimit = 6
const prefetchToken = { value: 0 }
@@ -1433,10 +1433,11 @@ export default function Layout(props: ParentProps) {
getLabel={messageLabel}
onMessageSelect={(message) => {
if (!isActive()) {
- navigate(`${props.slug}/session/${props.session.id}#message-${message.id}`)
+ sessionStorage.setItem("opencode.pendingMessage", `${props.session.id}|${message.id}`)
+ navigate(`${props.slug}/session/${props.session.id}`)
return
}
- window.location.hash = `message-${message.id}`
+ window.history.replaceState(null, "", `#message-${message.id}`)
window.dispatchEvent(new HashChangeEvent("hashchange"))
}}
size="normal"
@@ -1970,7 +1971,7 @@ export default function Layout(props: ParentProps) {
transform: "translate3d(52px, 0, 0)",
}}
>
-
+
{project()?.worktree.replace(homedir(), "~")}
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index ca04c53760..29f79613d9 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -1,4 +1,4 @@
-import { For, onCleanup, onMount, Show, Match, Switch, createMemo, createEffect, on } from "solid-js"
+import { For, onCleanup, onMount, Show, Match, Switch, createMemo, createEffect, on, createSignal } from "solid-js"
import { createMediaQuery } from "@solid-primitives/media"
import { createResizeObserver } from "@solid-primitives/resize-observer"
import { Dynamic } from "solid-js/web"
@@ -167,6 +167,7 @@ export default function Page() {
const sdk = useSDK()
const prompt = usePrompt()
const permission = usePermission()
+ const [pendingMessage, setPendingMessage] = createSignal(undefined)
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const tabs = createMemo(() => layout.tabs(sessionKey()))
const view = createMemo(() => layout.view(sessionKey()))
@@ -530,7 +531,7 @@ export default function Page() {
title: "Cycle thinking effort",
description: "Switch to the next effort level",
category: "Model",
- keybind: "shift+mod+t",
+ keybind: "shift+mod+d",
onSelect: () => {
local.model.variant.cycle()
},
@@ -943,17 +944,30 @@ export default function Page() {
window.history.replaceState(null, "", `#${anchor(id)}`)
}
+ createEffect(() => {
+ const sessionID = params.id
+ if (!sessionID) return
+ const raw = sessionStorage.getItem("opencode.pendingMessage")
+ if (!raw) return
+ const parts = raw.split("|")
+ const pendingSessionID = parts[0]
+ const messageID = parts[1]
+ if (!pendingSessionID || !messageID) return
+ if (pendingSessionID !== sessionID) return
+
+ sessionStorage.removeItem("opencode.pendingMessage")
+ setPendingMessage(messageID)
+ })
+
const scrollToElement = (el: HTMLElement, behavior: ScrollBehavior) => {
const root = scroller
- if (!root) {
- el.scrollIntoView({ behavior, block: "start" })
- return
- }
+ if (!root) return false
const a = el.getBoundingClientRect()
const b = root.getBoundingClientRect()
const top = a.top - b.top + root.scrollTop
root.scrollTo({ top, behavior })
+ return true
}
const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => {
@@ -967,7 +981,15 @@ export default function Page() {
requestAnimationFrame(() => {
const el = document.getElementById(anchor(message.id))
- if (el) scrollToElement(el, behavior)
+ if (!el) {
+ requestAnimationFrame(() => {
+ const next = document.getElementById(anchor(message.id))
+ if (!next) return
+ scrollToElement(next, behavior)
+ })
+ return
+ }
+ scrollToElement(el, behavior)
})
updateHash(message.id)
@@ -975,10 +997,57 @@ export default function Page() {
}
const el = document.getElementById(anchor(message.id))
- if (el) scrollToElement(el, behavior)
+ if (!el) {
+ updateHash(message.id)
+ requestAnimationFrame(() => {
+ const next = document.getElementById(anchor(message.id))
+ if (!next) return
+ if (!scrollToElement(next, behavior)) return
+ })
+ return
+ }
+ if (scrollToElement(el, behavior)) {
+ updateHash(message.id)
+ return
+ }
+
+ requestAnimationFrame(() => {
+ const next = document.getElementById(anchor(message.id))
+ if (!next) return
+ if (!scrollToElement(next, behavior)) return
+ })
updateHash(message.id)
}
+ const applyHash = (behavior: ScrollBehavior) => {
+ const hash = window.location.hash.slice(1)
+ if (!hash) {
+ autoScroll.forceScrollToBottom()
+ return
+ }
+
+ const match = hash.match(/^message-(.+)$/)
+ if (match) {
+ const msg = visibleUserMessages().find((m) => m.id === match[1])
+ if (msg) {
+ scrollToMessage(msg, behavior)
+ return
+ }
+
+ // If we have a message hash but the message isn't loaded/rendered yet,
+ // don't fall back to "bottom". We'll retry once messages arrive.
+ return
+ }
+
+ const target = document.getElementById(hash)
+ if (target) {
+ scrollToElement(target, behavior)
+ return
+ }
+
+ autoScroll.forceScrollToBottom()
+ }
+
const getActiveMessageId = (container: HTMLDivElement) => {
const cutoff = container.scrollTop + 100
const nodes = container.querySelectorAll("[data-message-id]")
@@ -1019,31 +1088,47 @@ export default function Page() {
if (!sessionID || !ready) return
requestAnimationFrame(() => {
- const hash = window.location.hash.slice(1)
- if (!hash) {
- autoScroll.forceScrollToBottom()
- return
- }
-
- const hashTarget = document.getElementById(hash)
- if (hashTarget) {
- scrollToElement(hashTarget, "auto")
- return
- }
-
- const match = hash.match(/^message-(.+)$/)
- if (match) {
- const msg = visibleUserMessages().find((m) => m.id === match[1])
- if (msg) {
- scrollToMessage(msg, "auto")
- return
- }
- }
-
- autoScroll.forceScrollToBottom()
+ applyHash("auto")
})
})
+ // Retry message navigation once the target message is actually loaded.
+ createEffect(() => {
+ const sessionID = params.id
+ const ready = messagesReady()
+ if (!sessionID || !ready) return
+
+ // dependencies
+ visibleUserMessages().length
+ store.turnStart
+
+ const targetId =
+ pendingMessage() ??
+ (() => {
+ const hash = window.location.hash.slice(1)
+ const match = hash.match(/^message-(.+)$/)
+ if (!match) return undefined
+ return match[1]
+ })()
+ if (!targetId) return
+ if (store.messageId === targetId) return
+
+ const msg = visibleUserMessages().find((m) => m.id === targetId)
+ if (!msg) return
+ if (pendingMessage() === targetId) setPendingMessage(undefined)
+ requestAnimationFrame(() => scrollToMessage(msg, "auto"))
+ })
+
+ createEffect(() => {
+ const sessionID = params.id
+ const ready = messagesReady()
+ if (!sessionID || !ready) return
+
+ const handler = () => requestAnimationFrame(() => applyHash("auto"))
+ window.addEventListener("hashchange", handler)
+ onCleanup(() => window.removeEventListener("hashchange", handler))
+ })
+
createEffect(() => {
document.addEventListener("keydown", handleKeyDown)
})
@@ -1163,9 +1248,9 @@ export default function Page() {
-
-
-
No changes in this session yet.
+
+
+
No changes in this session yet
@@ -1438,9 +1523,9 @@ export default function Page() {
-
-
-
No changes in this session yet.
+
+
+
No changes in this session yet
diff --git a/packages/console/app/package.json b/packages/console/app/package.json
index 2f44637fc8..83ff605cff 100644
--- a/packages/console/app/package.json
+++ b/packages/console/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
- "version": "1.1.25",
+ "version": "1.1.26",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/console/core/package.json b/packages/console/core/package.json
index a9bb2706d4..eb86fb525b 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.1.25",
+ "version": "1.1.26",
"private": true,
"type": "module",
"license": "MIT",
diff --git a/packages/console/function/package.json b/packages/console/function/package.json
index 6ada8abb05..6cfdaab757 100644
--- a/packages/console/function/package.json
+++ b/packages/console/function/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
- "version": "1.1.25",
+ "version": "1.1.26",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
diff --git a/packages/console/function/src/auth.ts b/packages/console/function/src/auth.ts
index 082564b21c..ee68dffff5 100644
--- a/packages/console/function/src/auth.ts
+++ b/packages/console/function/src/auth.ts
@@ -35,7 +35,7 @@ export const subjects = createSubjects({
const MY_THEME: Theme = {
...THEME_OPENAUTH,
- logo: "https://opencode.ai/favicon.svg",
+ logo: "https://opencode.ai/favicon-v2.svg",
}
export default {
diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json
index 7fe57cc79a..ab2fd76f8b 100644
--- a/packages/console/mail/package.json
+++ b/packages/console/mail/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
- "version": "1.1.25",
+ "version": "1.1.26",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
diff --git a/packages/desktop/index.html b/packages/desktop/index.html
index d7d439ab8a..f03666d5e7 100644
--- a/packages/desktop/index.html
+++ b/packages/desktop/index.html
@@ -4,10 +4,10 @@
OpenCode
-
-
-
-
+
+
+
+
diff --git a/packages/desktop/package.json b/packages/desktop/package.json
index 9114ff5621..0b95f75487 100644
--- a/packages/desktop/package.json
+++ b/packages/desktop/package.json
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
- "version": "1.1.25",
+ "version": "1.1.26",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx
index 6cd77d7d55..a06270b13f 100644
--- a/packages/desktop/src/index.tsx
+++ b/packages/desktop/src/index.tsx
@@ -253,7 +253,7 @@ const createPlatform = (password: Accessor
): Platform => ({
.then(() => {
const notification = new Notification(title, {
body: description ?? "",
- icon: "https://opencode.ai/favicon-96x96.png",
+ icon: "https://opencode.ai/favicon-96x96-v2.png",
})
notification.onclick = () => {
const win = getCurrentWindow()
diff --git a/packages/docs/docs.json b/packages/docs/docs.json
index 4461f8253b..93dff10f8c 100644
--- a/packages/docs/docs.json
+++ b/packages/docs/docs.json
@@ -7,7 +7,7 @@
"light": "#07C983",
"dark": "#15803D"
},
- "favicon": "/favicon.svg",
+ "favicon": "/favicon-v2.svg",
"navigation": {
"tabs": [
{
diff --git a/packages/docs/favicon-v2.svg b/packages/docs/favicon-v2.svg
new file mode 100644
index 0000000000..b785c738bf
--- /dev/null
+++ b/packages/docs/favicon-v2.svg
@@ -0,0 +1,19 @@
+
diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json
index e80a58b2d7..1a40af1c79 100644
--- a/packages/enterprise/package.json
+++ b/packages/enterprise/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
- "version": "1.1.25",
+ "version": "1.1.26",
"private": true,
"type": "module",
"license": "MIT",
diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml
index dccac3f933..2327c61e66 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.1.25"
+version = "1.1.26"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/anomalyco/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.25/opencode-darwin-arm64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.26/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.25/opencode-darwin-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.26/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.25/opencode-linux-arm64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.26/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.25/opencode-linux-x64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.26/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.25/opencode-windows-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.26/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
diff --git a/packages/function/package.json b/packages/function/package.json
index ad83a519c2..e5b0f62b92 100644
--- a/packages/function/package.json
+++ b/packages/function/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
- "version": "1.1.25",
+ "version": "1.1.26",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index e191819347..297abc8598 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
- "version": "1.1.25",
+ "version": "1.1.26",
"name": "opencode",
"type": "module",
"license": "MIT",
diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
index e27c32dfb2..718929d445 100644
--- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
@@ -85,6 +85,7 @@ export function Autocomplete(props: {
index: 0,
selected: 0,
visible: false as AutocompleteRef["visible"],
+ input: "keyboard" as "keyboard" | "mouse",
})
const [positionTick, setPositionTick] = createSignal(0)
@@ -128,6 +129,14 @@ export function Autocomplete(props: {
return props.input().getTextRange(store.index + 1, props.input().cursorOffset)
})
+ // When the filter changes due to how TUI works, the mousemove might still be triggered
+ // via a synthetic event as the layout moves underneath the cursor. This is a workaround to make sure the input mode remains keyboard so
+ // that the mouseover event doesn't trigger when filtering.
+ createEffect(() => {
+ filter()
+ setStore("input", "keyboard")
+ })
+
function insertPart(text: string, part: PromptInfo["parts"][number]) {
const input = props.input()
const currentCursorOffset = input.cursorOffset
@@ -525,11 +534,13 @@ export function Autocomplete(props: {
const isNavDown = name === "down" || (ctrlOnly && name === "n")
if (isNavUp) {
+ setStore("input", "keyboard")
move(-1)
e.preventDefault()
return
}
if (isNavDown) {
+ setStore("input", "keyboard")
move(1)
e.preventDefault()
return
@@ -612,7 +623,17 @@ export function Autocomplete(props: {
paddingRight={1}
backgroundColor={index === store.selected ? theme.primary : undefined}
flexDirection="row"
- onMouseOver={() => moveTo(index)}
+ onMouseMove={() => {
+ setStore("input", "mouse")
+ }}
+ onMouseOver={() => {
+ if (store.input !== "mouse") return
+ moveTo(index)
+ }}
+ onMouseDown={() => {
+ setStore("input", "mouse")
+ moveTo(index)
+ }}
onMouseUp={() => select()}
>
diff --git a/packages/opencode/src/cli/cmd/tui/component/tips.tsx b/packages/opencode/src/cli/cmd/tui/component/tips.tsx
index fe2e7ca216..3f0318e269 100644
--- a/packages/opencode/src/cli/cmd/tui/component/tips.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/tips.tsx
@@ -106,7 +106,7 @@ const TIPS = [
"Use plugins to send OS notifications when sessions complete",
"Create a plugin to prevent OpenCode from reading sensitive files",
"Use {highlight}opencode run{/highlight} for non-interactive scripting",
- "Use {highlight}opencode run --continue{/highlight} to resume the last session",
+ "Use {highlight}opencode --continue{/highlight} to resume the last session",
"Use {highlight}opencode run -f file.ts{/highlight} to attach files via CLI",
"Use {highlight}--format json{/highlight} for machine-readable output in scripts",
"Run {highlight}opencode serve{/highlight} for headless API access to OpenCode",
diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx
index 0edc911344..392cfb7f12 100644
--- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx
@@ -241,9 +241,27 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
event.properties.info.sessionID,
produce((draft) => {
draft.splice(result.index, 0, event.properties.info)
- if (draft.length > 100) draft.shift()
}),
)
+ const updated = store.message[event.properties.info.sessionID]
+ if (updated.length > 100) {
+ const oldest = updated[0]
+ batch(() => {
+ setStore(
+ "message",
+ event.properties.info.sessionID,
+ produce((draft) => {
+ draft.shift()
+ }),
+ )
+ setStore(
+ "part",
+ produce((draft) => {
+ delete draft[oldest.id]
+ }),
+ )
+ })
+ }
break
}
case "message.removed": {
diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
index 5c37a493df..618bf3b3cb 100644
--- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
+++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
@@ -52,6 +52,7 @@ export function DialogSelect(props: DialogSelectProps) {
const [store, setStore] = createStore({
selected: 0,
filter: "",
+ input: "keyboard" as "keyboard" | "mouse",
})
createEffect(
@@ -83,6 +84,14 @@ export function DialogSelect(props: DialogSelectProps) {
return result
})
+ // When the filter changes due to how TUI works, the mousemove might still be triggered
+ // via a synthetic event as the layout moves underneath the cursor. This is a workaround to make sure the input mode remains keyboard
+ // that the mouseover event doesn't trigger when filtering.
+ createEffect(() => {
+ filtered()
+ setStore("input", "keyboard")
+ })
+
const grouped = createMemo(() => {
const result = pipe(
filtered(),
@@ -157,12 +166,15 @@ export function DialogSelect(props: DialogSelectProps) {
const keybind = useKeybind()
useKeyboard((evt) => {
+ setStore("input", "keyboard")
+
if (evt.name === "up" || (evt.ctrl && evt.name === "p")) move(-1)
if (evt.name === "down" || (evt.ctrl && evt.name === "n")) move(1)
if (evt.name === "pageup") move(-10)
if (evt.name === "pagedown") move(10)
if (evt.name === "home") moveTo(0)
if (evt.name === "end") moveTo(flat().length - 1)
+
if (evt.name === "return") {
const option = selected()
if (option) {
@@ -259,11 +271,20 @@ export function DialogSelect(props: DialogSelectProps) {
{
+ setStore("input", "mouse")
+ }}
onMouseUp={() => {
option.onSelect?.(dialog)
props.onSelect?.(option)
}}
onMouseOver={() => {
+ if (store.input !== "mouse") return
+ const index = flat().findIndex((x) => isDeepEqual(x.value, option.value))
+ if (index === -1) return
+ moveTo(index)
+ }}
+ onMouseDown={() => {
const index = flat().findIndex((x) => isDeepEqual(x.value, option.value))
if (index === -1) return
moveTo(index)
@@ -337,6 +358,7 @@ function Option(props: {
fg={props.active ? fg : props.current ? theme.primary : theme.text}
attributes={props.active ? TextAttributes.BOLD : undefined}
overflow="hidden"
+ wrapMode="none"
paddingLeft={3}
>
{Locale.truncate(props.title, 61)}
diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts
index 834cbee1ed..0d18173565 100644
--- a/packages/opencode/src/file/ripgrep.ts
+++ b/packages/opencode/src/file/ripgrep.ts
@@ -162,34 +162,32 @@ export namespace Ripgrep {
})
}
if (config.extension === "zip") {
- if (config.extension === "zip") {
- const zipFileReader = new ZipReader(new BlobReader(new Blob([await Bun.file(archivePath).arrayBuffer()])))
- const entries = await zipFileReader.getEntries()
- let rgEntry: any
- for (const entry of entries) {
- if (entry.filename.endsWith("rg.exe")) {
- rgEntry = entry
- break
- }
+ const zipFileReader = new ZipReader(new BlobReader(new Blob([await Bun.file(archivePath).arrayBuffer()])))
+ const entries = await zipFileReader.getEntries()
+ let rgEntry: any
+ for (const entry of entries) {
+ if (entry.filename.endsWith("rg.exe")) {
+ rgEntry = entry
+ break
}
-
- if (!rgEntry) {
- throw new ExtractionFailedError({
- filepath: archivePath,
- stderr: "rg.exe not found in zip archive",
- })
- }
-
- const rgBlob = await rgEntry.getData(new BlobWriter())
- if (!rgBlob) {
- throw new ExtractionFailedError({
- filepath: archivePath,
- stderr: "Failed to extract rg.exe from zip archive",
- })
- }
- await Bun.write(filepath, await rgBlob.arrayBuffer())
- await zipFileReader.close()
}
+
+ if (!rgEntry) {
+ throw new ExtractionFailedError({
+ filepath: archivePath,
+ stderr: "rg.exe not found in zip archive",
+ })
+ }
+
+ const rgBlob = await rgEntry.getData(new BlobWriter())
+ if (!rgBlob) {
+ throw new ExtractionFailedError({
+ filepath: archivePath,
+ stderr: "Failed to extract rg.exe from zip archive",
+ })
+ }
+ await Bun.write(filepath, await rgBlob.arrayBuffer())
+ await zipFileReader.close()
}
await fs.unlink(archivePath)
if (!platformKey.endsWith("-win32")) await fs.chmod(filepath, 0o755)
diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts
index 2cec78623d..40ebb21ea5 100644
--- a/packages/opencode/src/project/project.ts
+++ b/packages/opencode/src/project/project.ts
@@ -25,6 +25,7 @@ export namespace Project {
icon: z
.object({
url: z.string().optional(),
+ override: z.string().optional(),
color: z.string().optional(),
})
.optional(),
@@ -190,6 +191,7 @@ export namespace Project {
if (!existing.sandboxes) existing.sandboxes = []
if (Flag.OPENCODE_EXPERIMENTAL_ICON_DISCOVERY) discover(existing)
+
const result: Info = {
...existing,
worktree,
@@ -213,6 +215,7 @@ export namespace Project {
export async function discover(input: Info) {
if (input.vcs !== "git") return
+ if (input.icon?.override) return
if (input.icon?.url) return
const glob = new Bun.Glob("**/{favicon}.{ico,png,svg,jpg,jpeg,webp}")
const matches = await Array.fromAsync(
@@ -293,6 +296,7 @@ export namespace Project {
...draft.icon,
}
if (input.icon.url !== undefined) draft.icon.url = input.icon.url
+ if (input.icon.override !== undefined) draft.icon.override = input.icon.override || undefined
if (input.icon.color !== undefined) draft.icon.color = input.icon.color
}
draft.time.updated = Date.now()
diff --git a/packages/opencode/src/tool/batch.ts b/packages/opencode/src/tool/batch.ts
index 8bffbd54a2..ba34eb48f5 100644
--- a/packages/opencode/src/tool/batch.ts
+++ b/packages/opencode/src/tool/batch.ts
@@ -33,8 +33,8 @@ export const BatchTool = Tool.define("batch", async () => {
const { Session } = await import("../session")
const { Identifier } = await import("../id/id")
- const toolCalls = params.tool_calls.slice(0, 10)
- const discardedCalls = params.tool_calls.slice(10)
+ const toolCalls = params.tool_calls.slice(0, 25)
+ const discardedCalls = params.tool_calls.slice(25)
const { ToolRegistry } = await import("./registry")
const availableTools = await ToolRegistry.tools({ modelID: "", providerID: "" })
@@ -139,14 +139,14 @@ export const BatchTool = Tool.define("batch", async () => {
state: {
status: "error",
input: call.parameters,
- error: "Maximum of 10 tools allowed in batch",
+ error: "Maximum of 25 tools allowed in batch",
time: { start: now, end: now },
},
})
results.push({
success: false as const,
tool: call.tool,
- error: new Error("Maximum of 10 tools allowed in batch"),
+ error: new Error("Maximum of 25 tools allowed in batch"),
})
}
diff --git a/packages/opencode/src/tool/batch.txt b/packages/opencode/src/tool/batch.txt
index b1b6a6010f..565eb4dd43 100644
--- a/packages/opencode/src/tool/batch.txt
+++ b/packages/opencode/src/tool/batch.txt
@@ -6,7 +6,7 @@ Payload Format (JSON array):
[{"tool": "read", "parameters": {"filePath": "src/index.ts", "limit": 350}},{"tool": "grep", "parameters": {"pattern": "Session\\.updatePart", "include": "src/**/*.ts"}},{"tool": "bash", "parameters": {"command": "git status", "description": "Shows working tree status"}}]
Notes:
-- 1–10 tool calls per batch
+- 1–20 tool calls per batch
- All calls start in parallel; ordering NOT guaranteed
- Partial failures do not stop other tool calls
- Do NOT use the batch tool within another batch tool.
diff --git a/packages/plugin/package.json b/packages/plugin/package.json
index 2734901808..ee7cf23b9f 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.1.25",
+ "version": "1.1.26",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json
index f3b12aa8c9..f69224d832 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.1.25",
+ "version": "1.1.26",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts
index 59b7f06963..706d0f9c22 100644
--- a/packages/sdk/js/src/v2/gen/sdk.gen.ts
+++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts
@@ -302,6 +302,7 @@ export class Project extends HeyApiClient {
name?: string
icon?: {
url?: string
+ override?: string
color?: string
}
},
diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts
index 75540f9072..b7e72fbad8 100644
--- a/packages/sdk/js/src/v2/gen/types.gen.ts
+++ b/packages/sdk/js/src/v2/gen/types.gen.ts
@@ -25,6 +25,7 @@ export type Project = {
name?: string
icon?: {
url?: string
+ override?: string
color?: string
}
time: {
@@ -2229,6 +2230,7 @@ export type ProjectUpdateData = {
name?: string
icon?: {
url?: string
+ override?: string
color?: string
}
}
diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json
index 08dd98fd9b..c1be820f26 100644
--- a/packages/sdk/openapi.json
+++ b/packages/sdk/openapi.json
@@ -231,6 +231,9 @@
"url": {
"type": "string"
},
+ "override": {
+ "type": "string"
+ },
"color": {
"type": "string"
}
@@ -5796,6 +5799,9 @@
"url": {
"type": "string"
},
+ "override": {
+ "type": "string"
+ },
"color": {
"type": "string"
}
diff --git a/packages/slack/package.json b/packages/slack/package.json
index d544b89e38..37b116ec2b 100644
--- a/packages/slack/package.json
+++ b/packages/slack/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
- "version": "1.1.25",
+ "version": "1.1.26",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 0b490591c3..7079384a54 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
- "version": "1.1.25",
+ "version": "1.1.26",
"type": "module",
"license": "MIT",
"exports": {
diff --git a/packages/ui/src/assets/favicon/apple-touch-icon-v2.png b/packages/ui/src/assets/favicon/apple-touch-icon-v2.png
new file mode 100644
index 0000000000..70fd01b0ea
Binary files /dev/null and b/packages/ui/src/assets/favicon/apple-touch-icon-v2.png differ
diff --git a/packages/ui/src/assets/favicon/favicon-96x96-v2.png b/packages/ui/src/assets/favicon/favicon-96x96-v2.png
new file mode 100644
index 0000000000..15266d28f1
Binary files /dev/null and b/packages/ui/src/assets/favicon/favicon-96x96-v2.png differ
diff --git a/packages/ui/src/assets/favicon/favicon-v2.ico b/packages/ui/src/assets/favicon/favicon-v2.ico
new file mode 100644
index 0000000000..34ca0b9c01
Binary files /dev/null and b/packages/ui/src/assets/favicon/favicon-v2.ico differ
diff --git a/packages/ui/src/assets/favicon/favicon-v2.svg b/packages/ui/src/assets/favicon/favicon-v2.svg
new file mode 100644
index 0000000000..157edc4d75
--- /dev/null
+++ b/packages/ui/src/assets/favicon/favicon-v2.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/components/favicon.tsx b/packages/ui/src/components/favicon.tsx
index 3462384d45..abb0e1f78c 100644
--- a/packages/ui/src/components/favicon.tsx
+++ b/packages/ui/src/components/favicon.tsx
@@ -3,9 +3,9 @@ import { Link, Meta } from "@solidjs/meta"
export const Favicon = () => {
return (
<>
-
-
-
+
+
+
>
diff --git a/packages/ui/src/components/list.tsx b/packages/ui/src/components/list.tsx
index 9bf395e2ae..88bda32809 100644
--- a/packages/ui/src/components/list.tsx
+++ b/packages/ui/src/components/list.tsx
@@ -59,6 +59,8 @@ export function List(props: ListProps & { ref?: (ref: ListRef) => void })
const searchProps = () => (typeof props.search === "object" ? props.search : {})
+ const moved = (event: MouseEvent) => event.movementX !== 0 || event.movementY !== 0
+
createEffect(() => {
if (props.filter !== undefined) {
onInput(props.filter)
@@ -234,7 +236,8 @@ export function List(props: ListProps & { ref?: (ref: ListRef) => void })
data-selected={item === props.current}
onClick={() => handleSelect(item, i())}
type="button"
- onMouseMove={() => {
+ onMouseMove={(event) => {
+ if (!moved(event)) return
setStore("mouseActive", true)
setActive(props.key(item))
}}
diff --git a/packages/ui/src/components/resize-handle.css b/packages/ui/src/components/resize-handle.css
index 088bf92157..e4c8d474e5 100644
--- a/packages/ui/src/components/resize-handle.css
+++ b/packages/ui/src/components/resize-handle.css
@@ -5,10 +5,8 @@
&::after {
content: "";
position: absolute;
- background-color: var(--color-border-strong-base);
opacity: 0;
transition: opacity 0.15s ease-in-out;
- border-radius: 2px;
}
&:hover::after,
diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css
index 5f8c0a16f6..034d302470 100644
--- a/packages/ui/src/components/session-turn.css
+++ b/packages/ui/src/components/session-turn.css
@@ -75,6 +75,17 @@
background-color: var(--background-stronger);
z-index: -1;
}
+
+ &::after {
+ content: "";
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ height: 32px;
+ background: linear-gradient(to bottom, var(--background-stronger), transparent);
+ pointer-events: none;
+ }
}
[data-slot="session-turn-response-trigger"] {
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index a918f0ae4f..360589f411 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/packages/ui/src/components/session-turn.tsx
@@ -6,7 +6,6 @@ import {
type PermissionRequest,
TextPart,
ToolPart,
- UserMessage,
} from "@opencode-ai/sdk/v2/client"
import { useData } from "../context"
import { useDiffComponent } from "../context/diff"
@@ -21,8 +20,6 @@ import { Accordion } from "./accordion"
import { StickyAccordionHeader } from "./sticky-accordion-header"
import { FileIcon } from "./file-icon"
import { Icon } from "./icon"
-import { IconButton } from "./icon-button"
-import { Tooltip } from "./tooltip"
import { Card } from "./card"
import { Dynamic } from "solid-js/web"
import { Button } from "./button"
@@ -352,7 +349,6 @@ export function SessionTurn(
const hasDiffs = createMemo(() => (data.store.session_diff?.[props.sessionID]?.length ?? 0) > 0)
const hideResponsePart = createMemo(() => !working() && !!responsePartId())
- const [responseCopied, setResponseCopied] = createSignal(false)
const [rootRef, setRootRef] = createSignal()
const [stickyRef, setStickyRef] = createSignal()
@@ -362,13 +358,6 @@ export function SessionTurn(
const next = Math.ceil(height)
root.style.setProperty("--session-turn-sticky-height", `${next}px`)
}
- const handleCopyResponse = async () => {
- const content = response()
- if (!content) return
- await navigator.clipboard.writeText(content)
- setResponseCopied(true)
- setTimeout(() => setResponseCopied(false), 2000)
- }
function duration() {
const msg = message()
@@ -589,15 +578,6 @@ export function SessionTurn(
{/* Response */}