Просмотр исходного кода

fix(app): reject stale timeline range indexes (#33488)

Luke Parker 2 месяцев назад
Родитель
Сommit
f0849a697c

+ 5 - 1
packages/app/src/pages/session/timeline/message-timeline.tsx

@@ -73,6 +73,7 @@ import { makeTimer } from "@solid-primitives/timer"
 import { scheduleConnectedMeasure } from "./measure"
 import { createTimelineProjection } from "./projection"
 import { MessageComment, SummaryDiff, TimelineRow, TimelineRowMap } from "./rows"
+import { filterVirtualIndexes } from "./virtual-items"
 
 const emptyMessages: MessageType[] = []
 const emptyParts: PartType[] = []
@@ -452,7 +453,10 @@ export function MessageTimeline(props: {
       const id = activeMessageID()
       const active = id ? (messageLastRowIndex().get(id) ?? -1) : -1
       const indexes = defaultRangeExtractor({ ...range, overscan: renderOverscan() })
-      return [...new Set([...resizePinnedIndexes, ...indexes, ...(active < 0 ? [] : [active])])].sort((a, b) => a - b)
+      return filterVirtualIndexes(
+        [...new Set([...resizePinnedIndexes, ...indexes, ...(active < 0 ? [] : [active])])].sort((a, b) => a - b),
+        range.count,
+      )
     },
   })
   const resizeItem = virtualizer.resizeItem

+ 3 - 0
packages/app/src/pages/session/timeline/virtual-items.ts

@@ -0,0 +1,3 @@
+export function filterVirtualIndexes(indexes: number[], count: number) {
+  return indexes.filter((index) => index >= 0 && index < count)
+}

+ 25 - 1
packages/app/test-browser/solid-virtual.test.ts

@@ -1,6 +1,7 @@
 import { expect, test } from "bun:test"
-import { createVirtualizer } from "@tanstack/solid-virtual"
+import { createVirtualizer, defaultRangeExtractor } from "@tanstack/solid-virtual"
 import { createRoot, createSignal } from "solid-js"
+import { filterVirtualIndexes } from "@/pages/session/timeline/virtual-items"
 
 test("reactive count updates preserve measured row sizes", () => {
   createRoot((dispose) => {
@@ -44,3 +45,26 @@ test("logical scroll offset includes pending measurement adjustments", () => {
     dispose()
   })
 })
+
+test("stale pinned indexes do not produce missing virtual items after count shrinks", () => {
+  createRoot((dispose) => {
+    const [count, setCount] = createSignal(2)
+    const pinned = [1]
+    const virtualizer = createVirtualizer<HTMLDivElement, HTMLDivElement>({
+      get count() {
+        return count()
+      },
+      getScrollElement: () => null,
+      estimateSize: () => 60,
+      initialRect: { width: 800, height: 600 },
+      rangeExtractor: (range) =>
+        filterVirtualIndexes([...new Set([...defaultRangeExtractor(range), ...pinned])], range.count),
+    })
+
+    expect(virtualizer.getVirtualItems().map((item) => item.index)).toEqual([0, 1])
+    setCount(1)
+    expect(virtualizer.getVirtualItems().map((item) => item.index)).toEqual([0])
+    expect(() => new Map(virtualizer.getVirtualItems().map((item) => [item.key, item]))).not.toThrow()
+    dispose()
+  })
+})

+ 2 - 2
packages/opencode/test/session/prompt.test.ts

@@ -1242,7 +1242,7 @@ it.instance(
       }
     }),
   { git: true },
-  3_000,
+  10_000,
 )
 
 // Queue semantics
@@ -1669,7 +1669,7 @@ it.instance(
       expect(yield* llm.calls).toBe(1)
     }),
   { git: true },
-  3_000,
+  10_000,
 )
 
 unix(