Skip to content

fix(app): stabilize virtual session timeline interactions#28422

Merged
Hona merged 14 commits into
anomalyco:devfrom
Hona:fix/timeline-virtua-stability
May 25, 2026
Merged

fix(app): stabilize virtual session timeline interactions#28422
Hona merged 14 commits into
anomalyco:devfrom
Hona:fix/timeline-virtua-stability

Conversation

@Hona

@Hona Hona commented May 20, 2026

Copy link
Copy Markdown
Member

Summary

  • Preserve manual tool expand/collapse state when active assistant content streams and changes virtual row shape.
  • Add a small virtua@0.49.1 Solid patch exposing VirtualizerHandle.measure() for synchronous mounted-item remeasurement.
  • Use that measure hook for recent context group expansion so following rows reflow without visible overlap.
  • Keep non-virtualized timeline diffs mounted across sibling/count-only updates; disable nested Pierre virtualization inside the already virtualized timeline.
  • Parse patch-backed diffs through Pierre as partial metadata, including legacy headerless hunks, while preserving complete before/after inputs as complete Pierre diffs.
  • Respect pinned Pierre beta lifecycle constraints: retained plain timeline diffs update in place, while virtualized viewers reset on changed target or construction-bound inputs.

Data-driven repros fixed

  • A manually collapsed edit tool reopened when later assistant content streamed.
  • Expanding a recent context group overlapped the following row before virtualizer measurement settled.
  • A sparse multi-hunk patch could render omitted context as repeated undefined text.
  • Headerless stored patches could render as empty diffs after direct Pierre parsing.

Scope

  • Normal completed edit, apply_patch, snapshot, and VCS diffs are patch-backed and use the direct Pierre parsing path.
  • Complete before/after payloads remain compatibility input and preserve Pierre's isPartial: false invariant; this PR does not optimize pathological patchless full rewrites.
  • Upgrading @pierre/diffs and evaluating its new CodeView API for the dedicated review surface are separate follow-ups.

Tests

  • bun test src from packages/ui
  • bun typecheck from packages/ui
  • bun typecheck from packages/app
  • bun test --preload ./happydom.ts src/utils/diffs.test.ts from packages/app
  • bun run test:e2e e2e/regression/session-timeline-collapse-state.spec.ts e2e/regression/session-timeline-context-resize.spec.ts e2e/smoke/session-timeline.spec.ts from packages/app
  • bun turbo typecheck

Follow-up

  • OpenCode currently pins @pierre/diffs@1.1.0-beta.18; stable 1.2.x adds CodeView and improved virtualized lifecycle/layout handling. Upgrade separately once scoped and eligible under the dependency release-age policy.

@Hona Hona requested a review from adamdotdevin as a code owner May 20, 2026 02:58
Copilot AI review requested due to automatic review settings May 20, 2026 02:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR stabilizes the session message timeline’s virtualized row behavior by persisting tool expand/collapse state across remounts and adding a synchronous “measure mounted items” hook to Virtua so recent context group expansions can reflow before the next paint.

Changes:

  • Adds a small virtua@0.49.1 patch exposing VirtualizerHandle.measure() and uses it to synchronously remeasure mounted items.
  • Makes tool open/close state controllable (open/onOpenChange) in BasicTool and ToolErrorCard, and wires controlled state through MessagePart / MessageTimeline.
  • Adds Playwright regression tests covering (1) tool collapse-state persistence under streaming updates and (2) context-group expansion reflow/overlap.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
patches/virtua@0.49.1.patch Adds measure() to the Solid Virtualizer handle by synchronously remeasuring mounted items.
packages/ui/src/components/tool-error-card.tsx Adds controlled open state (open, onOpenChange) for error tool cards.
packages/ui/src/components/message-part.tsx Plumbs controlled tool open state; triggers timeline remeasure on context group expand/collapse.
packages/ui/src/components/basic-tool.tsx Adds controlled open state support and routes open changes via a unified setter.
packages/app/src/pages/session/message-timeline.tsx Persists tool open state in the timeline; calls virtualizer.measure() on context group size changes; refactors row rendering around accessors.
packages/app/e2e/regression/session-timeline-context-resize.spec.ts New regression test asserting no visible overlap/flicker on context group expansion.
packages/app/e2e/regression/session-timeline-collapse-state.spec.ts New regression test ensuring manual tool collapse persists when later assistant parts stream in.
package.json Registers the Virtua patch in patchedDependencies.
bun.lock Adds the Virtua patch entry.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1231 to +1233
const row = () => timelineRowByKey().get(props.rowKey)!

return renderTimelineRow(row)
ms: pace(640),
})
const [toolOpen, setToolOpen] = createStore<Record<string, boolean | undefined>>({})

const samples = await sampleExpansion(page)
const visibleOverlap = samples.filter((sample) => sample.frame >= 1 && sample.overlap > 0.5)

console.log("context resize samples", JSON.stringify(samples, null, 2))
@lele872

lele872 commented May 20, 2026

Copy link
Copy Markdown

Regressione from PR #28334 — renderer crash with TypeError: Cannot read properties of undefined (reading 'group')

I'm hitting a renderer crash after updating that maps directly to the refactor in PR #28334 (merged yesterday, May 19).

Stack trace:

TypeError: Cannot read properties of undefined (reading 'group')
at Object.fn (oc://renderer/assets/session-Cj87sRUZ.js:22281:26)
at runComputation (oc://renderer/assets/styles-BzRX2rgK.js:486:22)
at oc://renderer/assets/styles-BzRX2rgK.js:475:9
at runUpdates (oc://renderer/assets/styles-BzRX2rgK.js:582:17)
at oc://renderer/assets/styles-BzRX2rgK.js:472:7
Analysis:

The crash occurs in renderAssistantPartGroup inside message-timeline.tsx (~line 984), where row.group.type is accessed but row is undefined.

The root cause is that PR #28334 removed the Accessor wrapping, switching to direct values. The internal createMemo calls capture row in a stale closure when session state changes (new messages, streaming, session switch), the memo re-executes but row references a stale value that becomes undefined during SolidJS reactive transitions.

Before the refactor, row() was a reactive function that always returned the current value, avoiding this issue.

Question: Does this PR (#28422) cover this specific crash? From the modified files (message-timeline.tsx and message-timeline.data.ts) it looks like it should, but I wanted to confirm since the crash is a hard blocker — the app becomes completely unusable and requires a forced restart.

Thanks for the work!

@Hona Hona merged commit f023c63 into anomalyco:dev May 25, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants