From 67aec2212010d67775c35e696d8b8b54902eb338 Mon Sep 17 00:00:00 2001 From: opencode Date: Thu, 25 Jun 2026 12:09:14 +0000 Subject: [PATCH 1/3] release: v1.17.11 --- bun.lock | 54 ++++++++++----------- packages/app/package.json | 2 +- packages/cli/package.json | 2 +- 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/console/support/package.json | 2 +- packages/core/package.json | 2 +- packages/desktop/package.json | 2 +- packages/effect-drizzle-sqlite/package.json | 2 +- packages/effect-sqlite-node/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/function/package.json | 2 +- packages/http-recorder/package.json | 2 +- packages/llm/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/server/package.json | 2 +- packages/session-ui/package.json | 2 +- packages/slack/package.json | 2 +- packages/stats/app/package.json | 2 +- packages/stats/core/package.json | 2 +- packages/stats/server/package.json | 2 +- packages/tui/package.json | 2 +- packages/ui/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 29 files changed, 55 insertions(+), 55 deletions(-) diff --git a/bun.lock b/bun.lock index 5bdd890d1384..c75067cab1b4 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", @@ -87,7 +87,7 @@ }, "packages/cli": { "name": "@opencode-ai/cli", - "version": "1.17.10", + "version": "1.17.11", "bin": { "lildax": "./bin/lildax.cjs", }, @@ -135,7 +135,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -171,7 +171,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -198,7 +198,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/openai": "3.0.48", @@ -220,7 +220,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -244,7 +244,7 @@ }, "packages/console/support": { "name": "@opencode-ai/console-support", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@opencode-ai/console-core": "workspace:*", @@ -264,7 +264,7 @@ }, "packages/core": { "name": "@opencode-ai/core", - "version": "1.17.10", + "version": "1.17.11", "bin": { "opencode": "./bin/opencode", }, @@ -357,7 +357,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@zip.js/zip.js": "2.7.62", "effect": "catalog:", @@ -411,7 +411,7 @@ }, "packages/effect-drizzle-sqlite": { "name": "@opencode-ai/effect-drizzle-sqlite", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -425,7 +425,7 @@ }, "packages/effect-sqlite-node": { "name": "@opencode-ai/effect-sqlite-node", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "effect": "catalog:", }, @@ -437,7 +437,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@hono/standard-validator": "catalog:", "@opencode-ai/core": "workspace:*", @@ -469,7 +469,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -485,7 +485,7 @@ }, "packages/http-recorder": { "name": "@opencode-ai/http-recorder", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@effect/platform-node": "4.0.0-beta.83", "@effect/platform-node-shared": "4.0.0-beta.83", @@ -516,7 +516,7 @@ }, "packages/llm": { "name": "@opencode-ai/llm", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@opencode-ai/schema": "workspace:*", "@smithy/eventstream-codec": "4.2.14", @@ -535,7 +535,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.17.10", + "version": "1.17.11", "bin": { "opencode": "./bin/opencode", }, @@ -665,7 +665,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/sdk": "workspace:*", @@ -741,7 +741,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "cross-spawn": "catalog:", }, @@ -756,7 +756,7 @@ }, "packages/server": { "name": "@opencode-ai/server", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@opencode-ai/core": "workspace:*", "@opencode-ai/protocol": "workspace:*", @@ -771,7 +771,7 @@ }, "packages/session-ui": { "name": "@opencode-ai/session-ui", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", @@ -815,7 +815,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -828,7 +828,7 @@ }, "packages/stats/app": { "name": "@opencode-ai/stats-app", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@ibm/plex": "6.4.1", "@opencode-ai/stats-core": "workspace:*", @@ -861,7 +861,7 @@ }, "packages/stats/core": { "name": "@opencode-ai/stats-core", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@aws-sdk/client-athena": "3.933.0", "@planetscale/database": "1.19.0", @@ -880,7 +880,7 @@ }, "packages/stats/server": { "name": "@opencode-ai/stats-server", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@aws-sdk/client-firehose": "3.933.0", "@effect/platform-node": "catalog:", @@ -921,7 +921,7 @@ }, "packages/tui": { "name": "@opencode-ai/tui", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@opencode-ai/core": "workspace:*", "@opencode-ai/plugin": "workspace:*", @@ -948,7 +948,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@kobalte/core": "catalog:", "@pierre/diffs": "catalog:", @@ -995,7 +995,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index 2fcba573d8b1..53c01ea64e57 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.17.10", + "version": "1.17.11", "description": "", "type": "module", "exports": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 0b76906fe981..1ea11f0864c6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/cli", - "version": "1.17.10", + "version": "1.17.11", "type": "module", "license": "MIT", "bin": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index a227ce5fc165..1011a5a59af8 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.17.10", + "version": "1.17.11", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 1adbeae9d1ac..bc7c31cc0b65 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.17.10", + "version": "1.17.11", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 42c66bad252e..3cb120ed6976 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.17.10", + "version": "1.17.11", "$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 56d435a588ac..da8c06a6110a 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.17.10", + "version": "1.17.11", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/console/support/package.json b/packages/console/support/package.json index 89d1226ce25b..5c081f7f4d49 100644 --- a/packages/console/support/package.json +++ b/packages/console/support/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-support", - "version": "1.17.10", + "version": "1.17.11", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 19854c7a62b8..139d116a7816 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.10", + "version": "1.17.11", "name": "@opencode-ai/core", "type": "module", "license": "MIT", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 1fcf77e06462..5a239863a8ef 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.17.10", + "version": "1.17.11", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/effect-drizzle-sqlite/package.json b/packages/effect-drizzle-sqlite/package.json index 24a1ec24a631..0e66036f0f94 100644 --- a/packages/effect-drizzle-sqlite/package.json +++ b/packages/effect-drizzle-sqlite/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.10", + "version": "1.17.11", "name": "@opencode-ai/effect-drizzle-sqlite", "type": "module", "license": "MIT", diff --git a/packages/effect-sqlite-node/package.json b/packages/effect-sqlite-node/package.json index 4623b109f19d..0bf7116aa64a 100644 --- a/packages/effect-sqlite-node/package.json +++ b/packages/effect-sqlite-node/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.10", + "version": "1.17.11", "name": "@opencode-ai/effect-sqlite-node", "type": "module", "license": "MIT", diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index aaaf67df5c95..03183d33a53c 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.17.10", + "version": "1.17.11", "private": true, "type": "module", "license": "MIT", diff --git a/packages/function/package.json b/packages/function/package.json index f4548da355e0..ba0867556735 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.17.10", + "version": "1.17.11", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/http-recorder/package.json b/packages/http-recorder/package.json index f1ec73e9662a..09cf673afc4c 100644 --- a/packages/http-recorder/package.json +++ b/packages/http-recorder/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.10", + "version": "1.17.11", "name": "@opencode-ai/http-recorder", "description": "Record and replay Effect HTTP client traffic with deterministic cassettes", "type": "module", diff --git a/packages/llm/package.json b/packages/llm/package.json index a3cb50422709..d5b370c7d46c 100644 --- a/packages/llm/package.json +++ b/packages/llm/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.10", + "version": "1.17.11", "name": "@opencode-ai/llm", "type": "module", "license": "MIT", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 281fe449aa2e..fba8d9b5bee4 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.17.10", + "version": "1.17.11", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 5ef302b24299..6e3c59bea3b8 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.17.10", + "version": "1.17.11", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 7ba155fd1aaa..476b2cc5c0ab 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.17.10", + "version": "1.17.11", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 3bffa03590e1..b0c4d948cb31 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/server", - "version": "1.17.10", + "version": "1.17.11", "private": true, "type": "module", "license": "MIT", diff --git a/packages/session-ui/package.json b/packages/session-ui/package.json index 98fa4c80cffc..f6c70cf5ae7d 100644 --- a/packages/session-ui/package.json +++ b/packages/session-ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/session-ui", - "version": "1.17.10", + "version": "1.17.11", "private": true, "type": "module", "license": "MIT", diff --git a/packages/slack/package.json b/packages/slack/package.json index 1b0fad5aff78..a815936e5d5e 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.17.10", + "version": "1.17.11", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/stats/app/package.json b/packages/stats/app/package.json index c4b2184d14a5..d3b83ac4b32f 100644 --- a/packages/stats/app/package.json +++ b/packages/stats/app/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/stats-app", - "version": "1.17.10", + "version": "1.17.11", "private": true, "type": "module", "license": "MIT", diff --git a/packages/stats/core/package.json b/packages/stats/core/package.json index e807306f53ce..3d93cba551fd 100644 --- a/packages/stats/core/package.json +++ b/packages/stats/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/stats-core", - "version": "1.17.10", + "version": "1.17.11", "private": true, "type": "module", "license": "MIT", diff --git a/packages/stats/server/package.json b/packages/stats/server/package.json index 6c508a47086a..86ad57f337eb 100644 --- a/packages/stats/server/package.json +++ b/packages/stats/server/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/stats-server", - "version": "1.17.10", + "version": "1.17.11", "private": true, "type": "module", "license": "MIT", diff --git a/packages/tui/package.json b/packages/tui/package.json index 3f4b6a2d7d0d..ee3972a2c602 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/tui", - "version": "1.17.10", + "version": "1.17.11", "private": true, "type": "module", "license": "MIT", diff --git a/packages/ui/package.json b/packages/ui/package.json index c4d6deb54d8f..93e4fc3483c5 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.17.10", + "version": "1.17.11", "type": "module", "license": "MIT", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index ab1240259cca..4c924ea504b0 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.17.10", + "version": "1.17.11", "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 bba23c00b7de..8a151aae1e19 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.17.10", + "version": "1.17.11", "publisher": "sst-dev", "repository": { "type": "git", From 9f1186b1da7b05ff79b437ce8589b9c230a5b20f Mon Sep 17 00:00:00 2001 From: Max schwenk Date: Thu, 2 Jul 2026 13:51:53 -0400 Subject: [PATCH 2/3] fix(app): remeasure timeline after message hydration --- .../app/src/context/server-session.test.ts | 47 ++++++++++++++++++- packages/app/src/context/server-session.ts | 33 +++++++++++-- .../session/timeline/message-timeline.tsx | 10 +++- 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/packages/app/src/context/server-session.test.ts b/packages/app/src/context/server-session.test.ts index 8986b63cddf0..fdec4d49ead4 100644 --- a/packages/app/src/context/server-session.test.ts +++ b/packages/app/src/context/server-session.test.ts @@ -1,5 +1,5 @@ -import { describe, expect, test } from "bun:test" -import type { OpencodeClient, Session } from "@opencode-ai/sdk/v2/client" +import { describe, expect, mock, test } from "bun:test" +import type { Message, OpencodeClient, Part, Session } from "@opencode-ai/sdk/v2/client" import { createServerSession } from "./server-session" const session = (id: string, parentID?: string): Session => ({ @@ -55,6 +55,49 @@ describe("server session", () => { expect(ctx.store.data.message.root).toEqual([]) }) + test("retries transient message hydration failures", async () => { + const message: Message = { + id: "message", + sessionID: "root", + role: "user", + time: { created: 1 }, + summary: { diffs: [] }, + agent: "build", + model: { providerID: "anthropic", modelID: "claude" }, + } + const part: Extract = { + id: "part", + sessionID: "root", + messageID: message.id, + type: "text", + text: "hello", + } + const messages = mock(async () => { + if (messages.mock.calls.length === 1) { + throw new Error("GET /session/root/message -> 503 Service Unavailable", { + cause: { status: 503 }, + }) + } + + return { + data: [{ info: message, parts: [part] }], + response: { headers: new Headers() }, + } + }) + const store = createServerSession({ + session: { + get: mock(async () => ({ data: session("root") })), + messages, + }, + } as unknown as OpencodeClient) + + await store.sync("root") + + expect(messages).toHaveBeenCalledTimes(2) + expect(store.data.message.root?.map((message) => message.id)).toEqual(["message"]) + expect(store.data.part.message?.map((part) => part.id)).toEqual(["part"]) + }) + test("applies events without a directory store", () => { const ctx = setup({}) ctx.store.apply({ type: "session.created", properties: { info: session("root") } }) diff --git a/packages/app/src/context/server-session.ts b/packages/app/src/context/server-session.ts index 7bad4088628a..30fbe2664903 100644 --- a/packages/app/src/context/server-session.ts +++ b/packages/app/src/context/server-session.ts @@ -22,6 +22,7 @@ const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"]) const initialMessagePageSize = 2 const historyMessagePageSize = 200 const sessionInfoLimit = 2_048 +const retryableMessageLoadStatuses = new Set([408, 409, 425, 429, 500, 502, 503, 504]) type OptimisticItem = { message: Message @@ -75,6 +76,24 @@ function merge(a: readonly T[], b: readonly T[]) { return [...items.values()].sort((x, y) => cmp(x.id, y.id)) } +function isRetryableMessageLoadError(error: unknown) { + if (error instanceof Error) { + const status = (error.cause as { status?: unknown } | undefined)?.status + if (typeof status === "number" && retryableMessageLoadStatuses.has(status)) return true + return [ + "load failed", + "network connection was lost", + "network request failed", + "failed to fetch", + "econnreset", + "econnrefused", + "etimedout", + "socket hang up", + ].some((message) => error.message.toLowerCase().includes(message)) + } + return false +} + export function createServerSession(client: OpencodeClient) { const [data, setData] = createStore({ info: {} as Record, @@ -92,6 +111,7 @@ export function createServerSession(client: OpencodeClient) { }) const requests = new Map>() const inflight = new Map>() + const messageLoads = new Map>() const inflightDiff = new Map>() const inflightTodo = new Map>() const optimistic = new Map>() @@ -202,6 +222,7 @@ export function createServerSession(client: OpencodeClient) { clearOptimistic(sessionID) requests.delete(sessionID) inflight.delete(sessionID) + messageLoads.delete(sessionID) inflightDiff.delete(sessionID) inflightTodo.delete(sessionID) }) @@ -248,7 +269,9 @@ export function createServerSession(client: OpencodeClient) { ) const fetchMessages = async (sessionID: string, limit: number, before?: string) => { - const response = await retry(() => client.session.messages({ sessionID, limit, before })) + const response = await retry(() => client.session.messages({ sessionID, limit, before }), { + retryIf: isRetryableMessageLoadError, + }) const items = (response.data ?? []).filter((item) => !!item?.info?.id) return { session: items.map((item) => cleanMessage(item.info)).sort((a, b) => cmp(a.id, b.id)), @@ -262,10 +285,11 @@ export function createServerSession(client: OpencodeClient) { } const loadMessages = async (sessionID: string, limit: number, before?: string, mode?: "replace" | "prepend") => { - if (meta.loading[sessionID]) return + const pending = messageLoads.get(sessionID) + if (pending) return pending const generation = generations.get(sessionID) ?? 0 setMeta("loading", sessionID, true) - await fetchMessages(sessionID, limit, before) + const request = fetchMessages(sessionID, limit, before) .then((page) => { if ((generations.get(sessionID) ?? 0) !== generation) return const next = mergeOptimisticPage(page, [...(optimistic.get(sessionID)?.values() ?? [])]) @@ -284,8 +308,11 @@ export function createServerSession(client: OpencodeClient) { }) }) .finally(() => { + if (messageLoads.get(sessionID) === request) messageLoads.delete(sessionID) if ((generations.get(sessionID) ?? 0) === generation) setMeta("loading", sessionID, false) }) + messageLoads.set(sessionID, request) + await request } const sync = (sessionID: string, options?: { force?: boolean; messageLimit?: number }) => { diff --git a/packages/app/src/pages/session/timeline/message-timeline.tsx b/packages/app/src/pages/session/timeline/message-timeline.tsx index 5d1bc9f86455..7e44e5861a98 100644 --- a/packages/app/src/pages/session/timeline/message-timeline.tsx +++ b/packages/app/src/pages/session/timeline/message-timeline.tsx @@ -507,11 +507,13 @@ export function MessageTimeline(props: { } let measuredSessionKey = sessionKey() + let measuredRowCount = timelineRows().length createEffect(() => { const key = sessionKey() - timelineRows().length - if (measuredSessionKey !== key) { + const rowCount = timelineRows().length + if (measuredSessionKey !== key || measuredRowCount !== rowCount) { measuredSessionKey = key + measuredRowCount = rowCount virtualizer.measure() } maybeAnchorBottom() @@ -549,6 +551,10 @@ export function MessageTimeline(props: { if (root === listRoot()) return setListRoot(root) props.setScrollRef(root) + requestAnimationFrame(() => { + virtualizer.measure() + maybeAnchorBottom() + }) } const handleListWheel = (event: WheelEvent & { currentTarget: HTMLDivElement }) => { From 7f8c6e832ebd0de9377dae3bb28c2ab663c3e4ac Mon Sep 17 00:00:00 2001 From: Max schwenk Date: Thu, 2 Jul 2026 14:16:58 -0400 Subject: [PATCH 3/3] fix(app): render short timelines when virtual range is empty Co-authored-by: Cursor --- .../session/timeline/message-timeline.tsx | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/packages/app/src/pages/session/timeline/message-timeline.tsx b/packages/app/src/pages/session/timeline/message-timeline.tsx index 7e44e5861a98..5e82762f8701 100644 --- a/packages/app/src/pages/session/timeline/message-timeline.tsx +++ b/packages/app/src/pages/session/timeline/message-timeline.tsx @@ -463,7 +463,15 @@ export function MessageTimeline(props: { const virtualItemByKey = createMemo( () => new Map(virtualizer.getVirtualItems().map((item) => [item.key, item] as const)), ) - const virtualRowKeys = createMemo(() => virtualizer.getVirtualItems().map((item) => item.key as string)) + const virtualRowKeys = createMemo(() => { + const items = virtualizer.getVirtualItems() + if (items.length > 0) return items.map((item) => item.key as string) + + const rows = timelineRows() + if (rows.length === 0 || rows.length > 20) return [] + + return rows.map(TimelineRow.key) + }) createEffect(() => { props.setRevealMessage?.((id) => { const index = messageRowIndex().get(id) @@ -557,6 +565,17 @@ export function MessageTimeline(props: { }) } + const bindVirtualContent = (element: HTMLDivElement) => { + virtualContent = element + props.setContentRef(element) + + requestAnimationFrame(() => { + const root = element.parentElement + if (!(root instanceof HTMLDivElement)) return + bindListRoot(root) + }) + } + const handleListWheel = (event: WheelEvent & { currentTarget: HTMLDivElement }) => { if (!prependLoading) clearPrependAnchor() const root = event.currentTarget @@ -1160,9 +1179,24 @@ export function MessageTimeline(props: { function VirtualTimelineRow(props: { rowKey: string }) { let element: HTMLDivElement - const initialItem = virtualItemByKey().get(props.rowKey)! + const fallbackItem = createMemo(() => { + const index = Math.max( + 0, + timelineRows().findIndex((row) => TimelineRow.key(row) === props.rowKey), + ) + const start = index * timelineFallbackItemSize + return { + end: start + timelineFallbackItemSize, + index, + key: props.rowKey, + lane: 0, + size: timelineFallbackItemSize, + start, + } + }) + const initialItem = virtualItemByKey().get(props.rowKey) ?? fallbackItem() const initialRow = timelineRowByKey().get(props.rowKey)! - const item = createMemo(() => virtualItemByKey().get(props.rowKey) ?? initialItem) + const item = createMemo(() => virtualItemByKey().get(props.rowKey) ?? fallbackItem()) const row = createMemo(() => timelineRowByKey().get(props.rowKey) ?? initialRow) const asyncFile = () => { const value = row() @@ -1549,10 +1583,7 @@ export function MessageTimeline(props: {
{ - virtualContent = element - props.setContentRef(element) - }} + ref={bindVirtualContent} style={{ height: `${virtualizer.getTotalSize()}px`, position: "relative",