Commit Graph

100 Commits

Author SHA1 Message Date
Atai Barkai 77b9c6b76f deprecate headless chat hook 2026-06-25 17:16:32 -07:00
Atai Barkai 67802cca3f fix(showcase): add headless resolver demo 2026-06-20 13:51:06 -07:00
Atai Barkai c947bd1dbb feat(react-core): add tool rendering resolver 2026-06-20 11:17:05 -07:00
Martha Schumann 1a61be49dc refactor(FOR-75): replace useJsonStable with useShallowStableRef; add unit tests and changeset
- Kill useJsonStable in CopilotChatConfigurationProvider — useShallowStableRef
  is now the single stabilization primitive for both messageView props and labels
- Harden useShallowStableRef with isPlainObject guard so arrays, Dates, and
  class instances are never shallow-compared (reference-only for non-plain objects)
- Remove redundant stableParentLabels — parentConfig?.labels is already stabilized
  by the parent provider's own useShallowStableRef
- Uninstall ts-deepmerge from react-core dependencies (no longer imported anywhere)
- Add unit tests for useShallowStableRef in slots.test.ts (5 tests)
- Add changeset (patch) describing the FOR-75 performance fix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 11:00:19 -07:00
Martha Schumann 56cc194782 refactor(react-core): extract useShallowStableRef; fix Test 2 to guard labels regression
- Extract useShallowStableRef<T> into slots.tsx alongside shallowEqual —
  replaces the 20-line inline stabilization block in CopilotChat with two
  readable call sites, and covers suggestionView for free
- Apply useShallowStableRef to both messageView and suggestionView in CopilotChat
- Use post-hoc assignment for messageView to avoid an empty object allocation
  on every render when messageView is undefined
- Rewrite Test 2 (labels) to use LabelConsumerMessage which calls
  useCopilotChatConfiguration() directly — context consumers re-render when
  their context changes regardless of parent memo boundaries, making this a
  genuine regression guard for the labels fix (previous version tracked
  assistantRenderCount which was already protected by messageView stabilization)
- Replace `as any` on messageView with scoped cast to preserve structural
  type checking on the surrounding object
- Fix text/testid collision: CountingAssistantMessage now renders data-testid
  instead of a hardcoded string that matched the agent reply text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 10:41:46 -07:00
Martha Schumann 35481074bd refactor(react-core): replace eslint-disable comments with useJsonStable hook
Replaces the manual JSON.stringify dep-key pattern (which required three
eslint-disable-next-line comments) with a useJsonStable utility that
stabilizes object references using JSON comparison. The dep array for
mergedLabels is now honest — it references the exact variables used inside
the useMemo callback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 10:30:26 -07:00
Martha Schumann cdbc3457cf fix(react-core): stabilize messageView/labels props to prevent message re-renders on keystroke
Inline `messageView` and `labels` objects passed to CopilotChat caused all
completed assistant messages to re-render on every keystroke because:

1. `ts-deepmerge.merge()` deep-clones its inputs, producing a new `messageView`
   reference on every render even when content is identical — defeating
   `MemoizedSlotWrapper`'s shallow equality check.

2. An inline `labels` object (new reference each render) invalidated the
   `mergedLabels` useMemo in `CopilotChatConfigurationProvider`, causing
   every `useCopilotChatConfiguration()` consumer to re-render on every keystroke.

3. The inline `onAddFile` arrow function was a new reference each render.

Fixes:
- Replace `merge()` with shallow spread; stabilize `messageView` via a ref +
  `shallowEqual` guard so the same object reference is returned when props
  are shallowly equal across renders.
- Use `useCallback` for `handleAddFile`.
- Add JSON.stringify-based dep keys in `CopilotChatConfigurationProvider` so
  `mergedLabels` is only recomputed when label values actually change.

Adds deterministic render-count regression tests (FOR-75) that fail on the
unfixed code and pass after the fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 09:55:34 -07:00
Martha Kelly Schumann 30b81395f6 Merge branch 'main' into feat/CPK-7190-chat-performance 2026-04-07 08:51:33 -07:00
Martha Schumann 82d58fae9b chore: merge main into feat/CPK-7190-chat-performance
Resolves conflict in CopilotChat.tsx — takes main's attachment-aware
messagesMemoKey fingerprint (scans all messages, avoids JSON.stringify
on base64 data) which subsumes our all-tool-calls fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 08:35:29 -07:00
Alem Tuzlak 39b5f671c0 Merge branch 'main' into claude/remove-endpoint-flag-xd3G1 2026-04-07 14:47:09 +02:00
Alem Tuzlak 4b11b995a2 feat: multimodal attachments — images, audio, video, documents in CopilotChat (#3604)
## Summary

Adds full multimodal attachment support to CopilotKit's v2 chat
components. Users can send images, audio, video, and documents alongside
text messages to any AG-UI-compatible agent.

### Frontend
- **`attachments` prop** on `CopilotChat` — `{ enabled, accept, maxSize,
onUpload, onUploadFailed }`
- **`useAttachments` hook** — reusable, referentially stable hook for
custom chat UIs (all callbacks memoized, zero unnecessary re-renders)
- **Attachment queue** with lightbox previews (View Transition API),
video player, PDF/text document viewer
- **Drag-and-drop**, **clipboard paste**, and **file picker** upload
flows
- **Drop zone overlay** with visual indicator
- **Filename preservation** via `InputContent` metadata
- **`onUploadFailed` callback** with typed error reasons
(`file-too-large`, `invalid-type`, `upload-failed`)
- **`AttachmentUploadResult` discriminated union** for type-safe
`onUpload` returns
- Memoized attachment renderer components (v1 and v2)

### Backend
- `BuiltInAgent` properly converts multimodal `InputContent[]` to Vercel
AI SDK format
- Handles image, audio, video, document, and legacy binary content parts

### Deprecation lifecycle
- `@deprecated` JSDoc on all legacy image upload APIs (9 symbols) with
`@since 1.56.0`
- **Codemod** at `codemods/migrate-attachments.ts` — transforms props +
imports (18 tests)
- Migration guide moved to new **Migration Guides** docs section

### Docs
- New guide: `docs/(root)/multimodal-attachments.mdx`
- Updated migration guide with codemod instructions, new `onUpload`
type, metadata
- Cross-links from prebuilt-components page
- New **Migration Guides** section (moved from troubleshooting)

### AG-UI
- Bumped `@ag-ui/client`, `@ag-ui/core`, `@ag-ui/encoder`,
`@ag-ui/proto` to 0.0.51

### Example
- React Router example with dual agent setup (BuiltInAgent + TanStack
AI)
- Multimodal content properly forwarded to TanStack AI `chat()`

## Test plan

- [x] `useAttachments` hook stability tests — 9 tests (referential
stability, re-render counting, state defaults)
- [x] `CopilotChat.attachments` tests — 5 tests (onUploadFailed for
invalid-type, file-too-large, upload-failed, multiple rejections, valid
files)
- [x] Codemod tests — 18 tests (prop transforms, import renames,
idempotency, edge cases)
- [x] Full react-core suite — 966 tests passing
- [x] Tool call streaming tests pass (messagesMemoKey fingerprint fix)
2026-04-07 14:26:03 +02:00
Ran Shemtov 0a262b3ad6 Merge branch 'main' into claude/remove-endpoint-flag-xd3G1 2026-04-07 12:55:33 +02:00
Alem Tuzlak 773a4a6415 fix: format package.json files 2026-04-07 12:29:29 +02:00
Alem Tuzlak 0aca4e88bc fix: address review feedback — concurrent-safe consumeAttachments & scope-aware codemod
1. consumeAttachments: read from a ref mirror instead of side-effecting
   out of a setState updater, avoiding reliance on synchronous updater
   execution under React concurrent mode.

2. Codemod: skip declaration positions (variable, function, class, type,
   interface) and non-reference positions (object keys, member accesses).
   When a local declaration shadows the import name, only rename
   unambiguous type-position references to avoid corrupting unrelated code.
2026-04-07 12:14:14 +02:00
Alem Tuzlak 1317e51302 fix: revert copy-dts script, use typesVersions for legacy TS consumers
Reverts the copy-dts.mjs approach from #3612 in favor of typesVersions
which is the standard Node/TS mechanism for resolving subpath types
under legacy moduleResolution: "node".
2026-04-07 11:39:15 +02:00
Alem Tuzlak 136bfc1182 Merge branch 'main' into feat/multimodal-builtin-agent 2026-04-07 09:51:46 +02:00
Alem Tuzlak 9ae7a97823 fix: address PR review feedback from Martha
Bugs fixed:
- v1 Chat.tsx: onUpload now reads result.type/result.value (was result.data/result.url)
- v1 Chat.tsx: metadata captured from onUpload and forwarded in InputContent parts
- v1 Chat.tsx: onUploadFailed callback now invoked at all three error paths
- useAttachments: consumeAttachments preserves in-flight uploads (was dropping them)

Should-fix addressed:
- Lightbox: vtCounter replaced with React.useId() (concurrent mode + SSR safe)
- Lightbox: SSR guard before createPortal(document.body)
- useBlobUrl: useEffect deps changed to scalar values (was object reference)
- Codemod: dynamic inputFileAccept expressions preserved (was silently dropped)
- Codemod: scope-awareness limitation documented in comment

Tests: 966/966 react-core, 19/19 codemod
2026-04-07 09:42:28 +02:00
Benjamin Taylor 3dc6963956 fix(threads): Improve useThreads DX with clearer names and direct type hints 2026-04-06 16:50:46 -07:00
Benjamin Taylor 9413015aa6 fix(react-core): clear copy button timeout on unmount
The CopyButton setTimeout fires after the jsdom test environment is
torn down, causing an unhandled ReferenceError on Node 20. Track the
timer in a ref and clear it on cleanup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:19:52 -07:00
Martha Schumann c89f42d311 fix(tests): type MCPAppsActivityRenderer test renderer with Zod schema
Replace `ReactActivityMessageRenderer<unknown>` + `(content as any).resourceUri`
with `ReactActivityMessageRenderer<z.infer<typeof MCPAppsActivityContentSchema>>`
so the renderer prop is properly typed and the `as any` cast is eliminated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 11:46:14 -07:00
Martha Schumann 8559608aeb style: fix oxfmt formatting in CopilotChatView 2026-04-06 10:59:37 -07:00
Martha Schumann e6539d7f24 fix(tests): update MockMCPProxyAgent.clone() to share isRunning and runAgent state
After the useRenderActivityMessage fix, MCPAppsActivityRenderer receives the
per-thread clone instead of the registry agent. The mock's clone() did not share
isRunning state, causing waitForAgentIdle(clone) to never resolve (emit() only
updated registry.isRunning). Also, tests that monkey-patch runAgent before
renderWithCopilotKit need the clone to delegate proxied MCP requests to the
registry (to pick up the monkey-patch) while running user-message flows on the
clone itself (so clone.messages is updated and rendered by CopilotKit).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 10:11:56 -07:00
Martha Schumann 1c156273a5 fix(react-core): address review feedback on perf PR
- Fingerprint all tool-call args on last message (not just last tool call)
  so parallel/non-last tool-call streaming is detected and triggers re-render
- Add dev warning when scroll container has clientHeight=0 so virtualization
  silently disabled (e.g. chat inside display:none tab) is surfaced early
- Use callback ref for non-autoScroll path in ScrollView to eliminate
  useLayoutEffect + eslint-disable; autoScroll path keeps useLayoutEffect
  since StickToBottom manages that ref internally

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 10:06:49 -07:00
Martha Schumann a92f43c1b3 test(react-core): add virtual path coverage to CopilotChatPerf tests
Adds a jsdom-compatible test that confirms the virtual code path activates
above VIRTUALIZE_THRESHOLD without requiring a real browser viewport. Mocks
clientHeight and getBoundingClientRect on the scroll element to pass both
CopilotChatMessageView's guard and TanStack Virtual's observeElementRect.
Asserts the virtual container div exists and rendered count < TOTAL.
Drains pending rAF callbacks before unmount to prevent spurious uncaught
exceptions after jsdom teardown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 09:54:34 -07:00
Martha Schumann 07b9ec13ec chore(a2ui): add /a2ui-demo page and DemoButtonAgent for manual fix validation
Adds a minimal demo at /a2ui-demo that uses the demo-button agent to
reproduce and validate the A2UI thread-clone bug fix. The agent renders
an A2UI surface with a Confirm button on first run; clicking it fires a
second run that emits a text confirmation message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 09:45:23 -07:00
Martha Kelly Schumann 90d99e857f Merge branch 'main' into feat/CPK-7190-chat-performance 2026-04-06 09:36:12 -07:00
Martha Schumann 0263b06e2a fix(chat): remove scrollToIndex length dep, fix stale toolResultMap comment
scrollToIndex was firing on every streaming chunk (deduplicatedMessages.length
dep), forcibly yanking users to the bottom even when scrolled up to read
history. Removed the dep — use-stick-to-bottom handles streaming auto-scroll
via content height growth on the virtualizer's total-size div, same as the
flat path. scrollToIndex now only fires on virtual mode activation and thread
switches.

Also removed a stale comment referencing toolResultMap (removed two commits
ago) and replaced it with an accurate description of the linear scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 09:15:26 -07:00
Alem Tuzlak 626c33bda2 fix: stabilize useAttachments hook with useCallback, add stability tests
- All callbacks (processFiles, handleFileUpload, handleDragOver, handleDragLeave,
  handleDrop, removeAttachment, consumeAttachments) are now wrapped in useCallback
  with empty deps — referentially stable across renders
- Config values read from configRef to avoid dep array changes
- consumeAttachments no-ops on empty queue (returns same state reference)
- 9 new tests: referential stability across re-renders, re-render counting
  (consumeAttachments on empty queue triggers zero re-renders), initial state,
  consumeAttachments behavior, removeAttachment no-op
2026-04-06 16:20:42 +02:00
Alem Tuzlak d5449f3b5d fix: restore JSON.stringify messages memo to fix streaming tool call re-renders 2026-04-06 16:15:13 +02:00
Martha Schumann c1a560ba85 fix(a2ui): pass per-thread clone to activity message renderers
useRenderActivityMessage was calling copilotkit.getAgent() directly,
always returning the registry agent. When a user clicked an A2UI button,
handleAction → runAgent executed on the registry agent — messages
accumulated there while CopilotChat displayed from the per-thread clone
(created by useAgent), so responses appeared to silently vanish.

Apply the same getThreadClone(registryAgent, threadId) ?? registryAgent
pattern already used in useRenderCustomMessages.

Adds a regression test that asserts the renderer receives the clone,
and confirms the test catches the bug when the fix is reverted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 07:01:22 -07:00
Alem Tuzlak d7578216d3 refactor: extract useAttachments hook, memoize attachment renderers
- Extract all attachment logic from CopilotChat into reusable useAttachments hook
  (state, processFiles, drag/drop, paste, consumeAttachments, removeAttachment)
- Export useAttachments from hooks/index.ts for custom chat UI builders
- Memoize v2 CopilotChatAttachmentRenderer sub-components
  (ImageAttachment, AudioAttachment, VideoAttachment, DocumentAttachment)
- Memoize v1 AttachmentRenderer sub-components to match v2 structure,
  align props (content → filename)
- Guard process.env in attachment-utils.ts for browser environments
- Remove debug logging (existing.debug = true) from use-agent.tsx
2026-04-06 15:17:56 +02:00
Alem Tuzlak 89d39d639b fix: address code review findings — error handling, codemod correctness, deprecation versions
- Restore console.error as safety net in processFiles catch blocks (errors no longer
  silently vanish when onUploadFailed callback is not provided)
- Add try/catch to useBlobUrl atob() — gracefully handles malformed base64 instead of
  crashing the component tree
- Fix DocumentLightboxContent: use blobUrl instead of undefined src variable
- Add .catch() to View Transition API transition.finished promise
- Codemod: preserve actual imageUploadsEnabled value (false, dynamic expressions)
  instead of hardcoding true — adds 3 new test cases (18 total)
- Replace all placeholder @deprecated versions (v1.x.0) with @since 1.56.0
- Add missing @since to ImageRenderer, ImageRendererProps, AIMessage.image tags
2026-04-06 14:55:08 +02:00
Alem Tuzlak 18c7a6001d feat: multimodal attachments — UI polish, deprecations, docs, codemod
Attachment queue & previews:
- Image lightbox with View Transition API morph animation
- Video lightbox with native controls and play button overlay
- Document lightbox (PDF via blob URL, text inline, info card fallback)
- Drop zone overlay with upload icon
- Filename preservation via InputContent metadata
- Proper video thumbnail sizing and play/pause indicator
- Fix attachment queue positioning (max-w-3xl constraint)
- Padding between X button and content for audio/document cards
- Document filenames wrap instead of truncating

Attachments config:
- onUploadFailed callback for validation/upload errors (file-too-large, invalid-type, upload-failed)
- onUpload accepts sync or async returns
- AttachmentUploadResult discriminated union with explicit interfaces
- Metadata field on Attachment and onUpload return type

AG-UI version bump:
- Bump @ag-ui/client, @ag-ui/core, @ag-ui/encoder, @ag-ui/proto to 0.0.51
- Remove process.env Vite workaround (fixed upstream in 0.0.51)

Deprecation lifecycle:
- @deprecated JSDoc on all legacy image upload APIs
- ImageRenderer, ImageRendererProps, ImageUpload type, imageUploadsEnabled prop,
  inputFileAccept prop, ImageRenderer prop, AIMessage.image, ImageData
- Codemod at codemods/migrate-attachments.ts (15 tests)
- Migration guide updated with codemod instructions and new type shapes

Docs:
- New guide: docs/(root)/multimodal-attachments.mdx
- Updated migration guide with onUpload return type, metadata, codemod section
- Cross-links from prebuilt-components and migration guide
- Label change: "Add photos or files" → "Add attachments"

Tests:
- CopilotChat.attachments.test.tsx — 5 tests for onUploadFailed
- migrate-attachments codemod — 15 tests
2026-04-06 14:55:06 +02:00
Alem Tuzlak 1603b63e33 style: format v2 multimodal changes 2026-04-06 14:54:46 +02:00
Alem Tuzlak cf0f8ad0b8 feat(react-core): export v2 attachment components and types 2026-04-06 14:54:45 +02:00
Alem Tuzlak c37d409bbf feat(react-core): add attachments prop, upload flow, drag-and-drop to v2 CopilotChat
Wire multimodal attachment support into the v2 CopilotChat component:
- Add AttachmentsConfig prop to CopilotChat for enabling file attachments
- Implement processFiles with accept filter, size validation, placeholder/ready lifecycle
- Support custom onUpload handlers and default base64 encoding
- Build InputContent[] when attachments accompany a message
- Add drag-and-drop handlers with visual feedback (dashed outline)
- Add clipboard paste handler for file items
- Render CopilotChatAttachmentQueue between scroll view and input
- Forward onAddFile through CopilotChatView to CopilotChatInput's AddMenuButton
- Omit internal attachment state props from CopilotChatProps public API
2026-04-06 14:54:44 +02:00
Alem Tuzlak ae1dc0ce4c feat(react-core): add v2 attachment renderer, update user message for multimodal, DRY shared utils 2026-04-06 14:54:43 +02:00
Alem Tuzlak 8f54f411ef feat(react-core): add v2 CopilotChatAttachmentQueue component 2026-04-06 14:54:42 +02:00
Alem Tuzlak 7de4bfa9cf chore: bump @ag-ui/core and @ag-ui/client to 0.0.50-alpha.0 2026-04-06 14:54:27 +02:00
Ran Shemtov 12838ee879 Merge branch 'main' into claude/remove-endpoint-flag-xd3G1 2026-04-06 14:43:14 +02:00
Claude ca2d081f8c fix: use preventScroll on focus calls so autoFocus opt-in doesn't scroll page
When users explicitly set autoFocus={true}, the textarea now focuses
with { preventScroll: true } so the page doesn't jump. Also applies
to the modal-open focus path. Added tests verifying default no-focus
behavior and that opt-in focus uses preventScroll.

https://claude.ai/code/session_01LPiJ8GcUNtWdS2W6MYSE4b
2026-04-05 00:39:36 +00:00
Claude f41133d356 fix: change autoFocus default to false in CopilotChatInput
Prevents the chat input from stealing focus on mount by default.
Users can opt-in via the autoFocus prop (e.g. input={{ autoFocus: true }}).

https://claude.ai/code/session_01LPiJ8GcUNtWdS2W6MYSE4b
2026-04-04 02:13:01 +00:00
Claude 7426486199 fix: remove scrollIntoView on chat input focus that forces page scroll
The v2 CopilotChatInput had a useEffect that called scrollIntoView()
whenever the textarea received focus. Combined with autoFocus defaulting
to true, this caused the entire page to scroll to the chat component on
load - the same bug that was previously fixed in v1.

https://claude.ai/code/session_01LPiJ8GcUNtWdS2W6MYSE4b
2026-04-04 02:07:38 +00:00
Martha Schumann 4bc77088b0 fix(chat): fix virtualization, slot memoization, and ToolMessage casts
- Change ScrollElementContext to carry HTMLElement | null state (not a ref)
  so CopilotChatMessageView re-renders reactively when the scroll container
  first mounts. Removes the one-shot useLayoutEffect that silently kept
  virtualization disabled in the default autoScroll path (hasMounted gate
  in ScrollView deferred the Provider past the one-shot read window).

- Lift resolveSlotComponent calls out of renderMessageBlock into useMemo.
  When a slot was passed as a CSS class string, resolveSlotComponent
  returned a new { className } object on every call, defeating
  MemoizedAssistantMessage's slotProps reference-equality check and
  causing all completed messages to re-render on every parent update.

- Add deduplicatedMessages.length to scrollToIndex deps so the virtualizer
  stays at the bottom as new messages arrive during streaming.

- Replace four as-any ToolMessage casts in the memoized comparator with
  typed inline type predicates using the ToolMessage import from @ag-ui/core.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 14:02:03 -07:00
Martha Schumann 95b3eabf0e chore(perf): remove perf page, bench file, and dev-only window globals
The /perf demo page and .bench.tsx file were useful during development but
add noise to the PR. Removing them keeps the diff focused on the library
changes. Also removes the __perfScrollEl / __perfMsgCount window globals
that only existed to serve the perf page's polling mechanism.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:24:33 -07:00
Martha Schumann 7184cfb855 Merge remote-tracking branch 'origin/main' into feat/CPK-7190-chat-performance 2026-04-03 13:21:52 -07:00
Martha Schumann 7b90bdb532 fix(chat): revert toolResultMap comparator — breaks HITL sequential tool-call rendering
The pre-computed toolResultMap in MemoizedAssistantMessage's comparator caused
HITL tests to fail: after responding to one tool call, the second tool call's
status would not transition from 'inProgress' to 'complete'.

Root cause: when executingToolCallIds context updates (removing the completed
tool call from the set), CopilotChatToolCallsView re-renders via useCopilotKit()
context subscription. At that point it holds stale `messages` props (MemoizedAssistantMessage
hasn't re-rendered yet), so messages.find(tc.id) returns undefined. The timing
window between the context update and the messages re-render caused the status
to briefly show inProgress. In the old code the same O(n) messages.filter() scan
was used, which is proven correct. Reverting to that approach.

Keeps all other perf improvements: deduplicatedMessages useMemo, virtualization,
resolveSlotComponent helper, NODE_ENV-gated dev globals.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:06:48 -07:00
Martha Schumann 848205f89b perf(chat): memoize dedup map, O(1) tool-result comparator, TanStack Virtual, perf page & tests
Addresses review findings from PR #3611:
- toolResultMap: pre-compute id→content Map via useMemo so MemoizedAssistantMessage
  comparator does O(k) lookups instead of O(n) linear scans
- resolveSlotComponent<T>() helper eliminates repeated slot-resolution boilerplate
- NODE_ENV gate on __perfMsgCount / __perfScrollEl (no production window pollution)
- hasScrolledToBottomRef → dep-based [shouldVirtualize, firstMessageId] in useLayoutEffect
- Merged duplicate virtual/flat render return paths; removed dead null-guards in comparator
- AssistantMessage type import + cast in fingerprint (CopilotChat.tsx)
- perf page: private → protected, lastScrollTop=NaN, STABLE_POLLS_REQUIRED constant,
  agent as any with version-mismatch comment (ag-ui 0.0.46 vs 0.0.48)
- snapshot test: act+tick flush instead of sentinel message (Observable subscription
  ends after RUN_FINISHED so new events are dropped)
- .bench.tsx extension comment: not picked up by vitest CI glob

Pre-existing test failures in use-human-in-the-loop.e2e.test.tsx (2 tests) and
@copilotkit/sqlite-runner + @copilotkit/runtime are on main; unrelated to this PR.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 12:42:47 -07:00
Maxim 208f9bc32f fix: restore .d.ts declaration files for legacy moduleResolution consumers
Copies .d.cts → .d.ts after each tsdown build so consumers on
moduleResolution "node" (e.g. DocuSign) can resolve types without
changing their tsconfig. Also points the "types" field back to .d.ts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 20:55:45 +02:00
Martha Schumann 468062c3c3 perf(react-core): add TanStack Virtual + two-phase animation measurement
- Add @tanstack/react-virtual to react-core deps; activate useVirtualizer
  in CopilotChatMessageView when message count exceeds 50 (VIRTUALIZE_THRESHOLD).
  Only visible items are mounted; total scrollable height is maintained via
  an absolutely-positioned container div.
- Add scroll-element-context.ts to pass the scroll container ref from
  CopilotChatView down to CopilotChatMessageView without circular imports.
- Expose window.__perfScrollEl (scroll container) and window.__perfMsgCount
  (committed message count) for programmatic perf measurement.
- Update /perf demo page with two-phase timing: phase 1 measures event emit
  time, phase 2 waits for all messages committed + scroll position stable
  (animation settled). PerfPanel now shows emit / render+anim / total.

Benchmark results (MacBook, dev build):
  Load 50  [anim]:    emit 305ms  render+anim 1433ms  total 1739ms
  Load 100 [anim]:    emit 752ms  render+anim 1578ms  total 2331ms
  Load 500 [anim]:    emit 14.2s  render+anim 1783ms  total 16.0s
  Load 50  [noAnim]:  emit 299ms  render+anim  182ms  total  481ms
  Load 100 [noAnim]:  emit 771ms  render+anim  161ms  total  931ms
  Load 500 [noAnim]:  emit 13.9s  render+anim  199ms  total 14.1s

Key findings:
- React render cost is now O(1) in message count (161-199ms flat).
  Virtualization fully decouples DOM work from history size.
- Animation (StickToBottom spring scroll-to-bottom) adds ~1.4-1.8s
  regardless of message count; this is a fixed cost on every history load.
- The remaining bottleneck for large histories is event emit time (~5.6ms
  per TEXT_MESSAGE_CHUNK through AbstractAgent). This is upstream of React
  and tracked separately.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 11:52:19 -07:00