The base class @ag-ui/langgraph creates an Observable that calls
runAgentStream() but ignores the returned Promise. Any async rejection
becomes an unhandled promise rejection that crashes the process.
Override run() to properly route async errors through the Observable
error channel so downstream catchError handlers can process them.
The thread naming creates an additional run, using a thread name that
was something like
"thread-naming:${originalthreadiduuid}:${newrandomuuid}" which
shockingly is not a valid uuid. This just drops the first 2 parts and
keeps the random uuid so langgraph can stop being mad.
Keep both defaultThrottleMs (our branch) and inspectorDefaultAnchor +
design-skill context registration (main) in CopilotKitProvider.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Core setter rejects invalid values (NaN, Infinity, negative) instead of
storing them, preventing garbage from reaching downstream consumers.
- Provider initializes defaultThrottleMs synchronously during instance
creation so child hooks see the correct value on their first render.
- Remove phantom notificationThrottle JSDoc reference (API does not exist).
- Remove dead "default" branch in error source detection.
- Fix misleading @default comments re: 0-vs-undefined semantics and
cascade direction.
- Replace React-specific "re-renders" language in framework-agnostic core.
- Add test for dynamic provider defaultThrottleMs changes.
- Add tests for core setter validation behavior.
- Add defaultThrottleMs to renderWithCopilotKit test helper.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace @a2ui/lit with @a2ui/web_core 0.9, introduce createCatalog() API for
custom component catalogs, add basic (18 components) and minimal (5 components)
built-in catalogs, CSS variable theming for dark mode, eliminate XSS vector
(dangerouslySetInnerHTML removed from Text component).
Addresses Tyler's review comment on #3657 — defaultThrottleMs is configuration
that belongs on the copilotkit instance, not as a sibling field in the React
context value.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract renderAndWarmCache() and installBoundingRectSpy() helpers to
eliminate duplicated setup across 4 tests. Add justifying comments on
all necessary type casts (MockResizeObserver, CSSStyleDeclaration,
TextMetrics, canvas getContext overloads).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Self-inflicted resizes (compact↔expanded) were clearing the cache before
the ignoreResizeRef guard, forcing a full re-measurement on the very next
keystroke. Move the guard ahead of invalidation so layout toggles keep the
cache warm — container dimensions don't change during these transitions.
Also reject compactWidth <= 0 from being cached, preventing a zero-width
entry from silently disabling text-width expansion until the next resize.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Merge two ResizeObserver instances into one; inspect entry.target to
decide cache invalidation. Eliminates the ignoreResizeRef race where
two observers could consume the guard independently.
- Move textarea font read out of the cache into evaluateLayout so CSS/
theme changes are picked up without a container resize.
- Return { compactWidth } | null from updateContainerCache; callers use
the value directly instead of the two-step "call then check ref" pattern.
- Hoist cache invalidation above the ignoreResizeRef guard to remove
duplicated containerCacheRef.current = null.
- Add zero-width guard in updateContainerCache to avoid caching
compactWidth: 0 when the container is hidden.
- Replace vi.fn() as any ResizeObserver mock with a class-based
MockResizeObserver that tracks observed targets, enabling per-target
resize triggers in tests.
- Fix empty-font and null-canvas tests to assert data-layout="compact"
(verifying the fallback path) instead of just non-null.
- Add tests: warm-cache keystroke perf (getBoundingClientRect not called),
textarea-only vs container resize invalidation, ignoreResizeRef + cache
invalidation during layout toggle.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract shared helpers (DEFAULT_LAYOUT_OPTIONS, getLayoutGrid,
renderTypeAndExpectLayout) to eliminate duplication, add explanatory
comments for unavoidable `as any` casts, and add missing test for
canvas.getContext returning null.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Split ResizeObserver into container vs textarea observers so textarea
height changes (typing, manual resize) don't invalidate the dimension
cache unnecessarily — only grid/button resizes invalidate it
- Invalidate cache on self-triggered resizes (compact↔expanded toggle)
even when skipping re-evaluation, preventing stale dimensions
- Validate fontSize and fontFamily before constructing font fallback
string, avoiding malformed CSS font values in Safari
- Add dev-mode console.warn when font resolution or canvas context
fails, making silent measurement path failures diagnosable
- Update test ResizeObserver mock to support multiple observers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Test canvas text measurement triggers expansion for long single-line text
- Test compact layout preserved when text fits within cached width
- Test cache invalidation on resize produces correct re-measurement
- Test empty font string is not cached (guard prevents invalid canvas state)
- Mock ResizeObserver to exercise cache invalidation path in jsdom
- Extend mockLayoutMetrics with getComputedStyle mocks for grid and textarea
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Store compactWidth (pre-padding) in cache, compute compactInnerWidth
at read time from live measurementsRef to avoid stale padding values
- Move cache invalidation after ignoreResize check so programmatic
layout transitions don't needlessly destroy the cache
- Guard against empty/invalid font strings before writing to cache
- Fix comment inaccuracies: 2x (not 3x) getComputedStyle, lazily
populated (not on mount), invalidated (not updated) on resize
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
evaluateLayout runs on every keystroke via useLayoutEffect. Previously it
performed 3x getComputedStyle + 2x getBoundingClientRect on every call to
measure grid/button dimensions and the textarea font — none of which change
between keystrokes.
Cache these measurements in a ref (containerCacheRef), populated lazily on
first evaluateLayout call and invalidated when the ResizeObserver fires.
Per-keystroke reflows drop from 3-4 to 1 (the unavoidable adjustTextareaHeight).
Also removes a stale console.log("FOOBAR") debug line.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
- 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>
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>
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>
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.
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".