fix(ui): preserve upstream behavior after performance rebase

This commit is contained in:
c_w_xiaohei
2026-08-26 00:42:36 +08:00
parent 9b9d7069c7
commit 532bdab5e8
34 changed files with 752 additions and 361 deletions
+1 -1
View File
@@ -65,7 +65,7 @@ The composer compares normalized attachment MIME types with the selected model's
### Layout-mounted session-list lifecycle
`MainLayout` and `VSCodeLayout` each call `useSessionListSync({ isVSCode })` directly and unconditionally, outside Sidebar visibility, responsive, editor, settings, and compact-view branches. The hook selects the real topology inputs, publishes complete directory bootstrap demand through `ChildStoreManager`, refreshes the global list once per layout mount, refreshes topology additions (including all VS Code directories on its first mount), coalesces OpenChamber control events for 500ms, and supplies a memoized complete global active+archived input to authoritative cleanup. MainLayout includes available worktrees; VS Code intentionally excludes them. Sidebar-local `session-created` worktree discovery is separate and full-app-only.
`MainLayout` and `VSCodeLayout` each call `useSessionListSync({ isVSCode })` directly and unconditionally, outside Sidebar visibility, responsive, editor, settings, and compact-view branches. The hook selects the real topology inputs, publishes complete directory bootstrap demand through `ChildStoreManager`, refreshes topology additions (including all VS Code directories on its first mount), coalesces OpenChamber control events for 500ms, and supplies a memoized complete global active+archived input to authoritative cleanup. The root-level global poller owns the initial global refresh. MainLayout includes available worktrees; VS Code intentionally excludes them. Sidebar-local `session-created` worktree discovery is separate and full-app-only.
### Directory bootstrap scheduling
@@ -27,6 +27,18 @@ describe("upsertSessionRecord", () => {
expect(result[1]).toBe(current[1])
})
test("replaces a same-ID record when an unlisted semantic field changes", () => {
const current = [session("a")]
// SAFETY: The runtime SDK payload may contain an additive field before the local Session type is updated.
const incoming = {
...session("a"),
// SDK records can gain fields independently of this synchronization boundary.
customField: "changed",
} as Session
expect(upsertSessionRecord(current, incoming)).not.toBe(current)
})
const changes: Array<[string, Partial<Session>, Partial<Session>]> = [
["scalars", { workspaceID: "one", path: "a", parentID: "p", cost: 1, agent: "a" }, { workspaceID: "two", path: "b", parentID: "q", cost: 2, agent: "b" }],
["tokens", { tokens: { input: 1, output: 2, reasoning: 3, cache: { read: 4, write: 5 } } }, { tokens: { input: 1, output: 2, reasoning: 3, cache: { read: 4, write: 6 } } }],
+2 -77
View File
@@ -1,83 +1,8 @@
import type { PermissionRuleset, Session, SnapshotFileDiff } from "@opencode-ai/sdk/v2"
import type { Session } from "@opencode-ai/sdk/v2"
import { Binary } from "./binary"
function areMetadataEqual(left: Session["metadata"], right: Session["metadata"]): boolean {
return JSON.stringify(left ?? null) === JSON.stringify(right ?? null)
}
function optionalEqual<T>(
left: T | undefined,
right: T | undefined,
equal: (left: T, right: T) => boolean,
): boolean {
return left === right || (left !== undefined && right !== undefined && equal(left, right))
}
const diffsEqual = (left: SnapshotFileDiff[], right: SnapshotFileDiff[]) => (
left.length === right.length
&& left.every((item, index) => {
const candidate = right[index]
return item.file === candidate.file
&& item.patch === candidate.patch
&& item.additions === candidate.additions
&& item.deletions === candidate.deletions
&& item.status === candidate.status
})
)
const permissionsEqual = (left: PermissionRuleset, right: PermissionRuleset) => (
left.length === right.length
&& left.every((item, index) => {
const candidate = right[index]
return item.permission === candidate.permission
&& item.pattern === candidate.pattern
&& item.action === candidate.action
})
)
function areSessionsEqual(left: Session, right: Session): boolean {
return left.id === right.id
&& left.slug === right.slug
&& left.projectID === right.projectID
&& left.workspaceID === right.workspaceID
&& left.directory === right.directory
&& left.path === right.path
&& left.parentID === right.parentID
&& left.cost === right.cost
&& left.title === right.title
&& left.agent === right.agent
&& left.version === right.version
&& areMetadataEqual(left.metadata, right.metadata)
&& optionalEqual(left.summary, right.summary, (a, b) => (
a.additions === b.additions
&& a.deletions === b.deletions
&& a.files === b.files
&& optionalEqual(a.diffs, b.diffs, diffsEqual)
))
&& optionalEqual(left.tokens, right.tokens, (a, b) => (
a.input === b.input
&& a.output === b.output
&& a.reasoning === b.reasoning
&& a.cache.read === b.cache.read
&& a.cache.write === b.cache.write
))
&& optionalEqual(left.share, right.share, (a, b) => a.url === b.url)
&& optionalEqual(left.model, right.model, (a, b) => (
a.id === b.id
&& a.providerID === b.providerID
&& a.variant === b.variant
))
&& left.time.created === right.time.created
&& left.time.updated === right.time.updated
&& left.time.compacting === right.time.compacting
&& left.time.archived === right.time.archived
&& optionalEqual(left.permission, right.permission, permissionsEqual)
&& optionalEqual(left.revert, right.revert, (a, b) => (
a.messageID === b.messageID
&& a.partID === b.partID
&& a.snapshot === b.snapshot
&& a.diff === b.diff
))
return JSON.stringify(left) === JSON.stringify(right)
}
export function upsertSessionRecord(current: Session[], incoming: Session): Session[] {