Skip to content

Commit c2e9eed

Browse files
authored
fix: resolve a2ui-root Strict Mode error (CopilotKit#3607)
## Summary Fixes the `NotSupportedError: Failed to execute 'define' on 'CustomElementRegistry': the name "a2ui-root" has already been used` error reported by a customer under React Strict Mode. ### Root cause `@a2ui/lit` registers the `a2ui-root` custom element via Lit's `@customElement` decorator as a side effect when its UI module loads. Several files in `@copilotkit/a2ui-renderer` used value imports (`import { v0_8 } from "@a2ui/lit"`) when only types were needed — this pulled in the full module tree including the custom element registration. React Strict Mode double-mounts, and since `customElements.define()` is irreversible, the second mount throws. ### Fix - Changed to `import type` where only types are needed (erased at compile time, no side effects) - Switched remaining value imports to the narrower `@a2ui/lit/0.8` subpath (core.js only, no UI/custom element registration) Credit to Alem for identifying React Strict Mode as the likely cause. ## Test plan - [x] All pre-commit hooks pass (tests, publint, attw) - [ ] Verify no `a2ui-root` error in React Strict Mode https://claude.ai/code/session_01CUPfWAF8EmLieEasaepAgp
2 parents 8a66ff6 + f2c860a commit c2e9eed

5 files changed

Lines changed: 40 additions & 23 deletions

File tree

.oxlintrc.json

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,27 @@
1010
"react/react-in-jsx-scope": "off",
1111
"import/no-unassigned-import": "off",
1212
"unicorn/no-array-sort": "off",
13-
"copilotkit/require-cpk-prefix": "off"
13+
"copilotkit/require-cpk-prefix": "off",
14+
"no-restricted-imports": "off"
1415
},
1516
"overrides": [
17+
{
18+
"files": ["packages/**/*.{ts,tsx}"],
19+
"rules": {
20+
"no-restricted-imports": [
21+
"error",
22+
{
23+
"paths": [
24+
{
25+
"name": "@a2ui/lit",
26+
"message": "Do not use value imports from '@a2ui/lit' — it registers custom elements as a side effect, which breaks React Strict Mode. Use `import type` for types, or import from '@a2ui/lit/0.8' for value access (core only, no UI side effects).",
27+
"allowTypeImports": true
28+
}
29+
]
30+
}
31+
]
32+
}
33+
},
1634
{
1735
"files": ["packages/react-ui/src/**/*.{ts,tsx}"],
1836
"rules": {

packages/a2ui-renderer/src/A2UIViewer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import React, { useId, useMemo, useEffect, useRef } from "react";
44
import type { Types } from "@a2ui/lit/0.8";
5-
import { v0_8 } from "@a2ui/lit";
5+
import type { v0_8 } from "@a2ui/lit";
66
import {
77
A2UIProvider,
88
useA2UIActions,
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { v0_8 } from "@a2ui/lit";
1+
import { Data, type Types } from "@a2ui/lit/0.8";
22

3-
export type Theme = v0_8.Types.Theme;
4-
export type A2UIClientEventMessage = v0_8.Types.A2UIClientEventMessage;
5-
export const DEFAULT_SURFACE_ID =
6-
v0_8.Data.A2uiMessageProcessor.DEFAULT_SURFACE_ID;
3+
export type Theme = Types.Theme;
4+
export type A2UIClientEventMessage = Types.A2UIClientEventMessage;
5+
export const DEFAULT_SURFACE_ID = Data.A2uiMessageProcessor.DEFAULT_SURFACE_ID;

packages/a2ui-renderer/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export type { A2UIViewerProps } from "./A2UIViewer.js";
2222
export { theme as viewerTheme } from "./theme/viewer-theme.js";
2323

2424
// Re-export v0_8 types namespace for consumers
25-
import { v0_8 } from "@a2ui/lit";
25+
import type { v0_8 } from "@a2ui/lit";
2626
export type ComponentInstance = v0_8.Types.ComponentInstance;
2727
export type UserAction = v0_8.Types.UserAction;
2828
export type Action = v0_8.Types.Action;

packages/a2ui-renderer/src/theme/viewer-theme.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
limitations under the License.
1515
*/
1616

17-
import { v0_8 } from "@a2ui/lit";
17+
import { Styles, type Types } from "@a2ui/lit/0.8";
1818

1919
/** Elements */
2020

@@ -159,27 +159,27 @@ const video = {
159159
"layout-el-cv": true,
160160
};
161161

162-
const aLight = v0_8.Styles.merge(a, { "color-c-n5": true });
163-
const inputLight = v0_8.Styles.merge(input, { "color-c-n5": true });
164-
const textareaLight = v0_8.Styles.merge(textarea, { "color-c-n5": true });
165-
const buttonLight = v0_8.Styles.merge(button, { "color-c-n100": true });
166-
const h1Light = v0_8.Styles.merge(h1, { "color-c-n5": true });
167-
const h2Light = v0_8.Styles.merge(h2, { "color-c-n5": true });
168-
const h3Light = v0_8.Styles.merge(h3, { "color-c-n5": true });
169-
const bodyLight = v0_8.Styles.merge(body, { "color-c-n5": true });
170-
const pLight = v0_8.Styles.merge(p, { "color-c-n35": true });
171-
const preLight = v0_8.Styles.merge(pre, { "color-c-n35": true });
172-
const orderedListLight = v0_8.Styles.merge(orderedList, {
162+
const aLight = Styles.merge(a, { "color-c-n5": true });
163+
const inputLight = Styles.merge(input, { "color-c-n5": true });
164+
const textareaLight = Styles.merge(textarea, { "color-c-n5": true });
165+
const buttonLight = Styles.merge(button, { "color-c-n100": true });
166+
const h1Light = Styles.merge(h1, { "color-c-n5": true });
167+
const h2Light = Styles.merge(h2, { "color-c-n5": true });
168+
const h3Light = Styles.merge(h3, { "color-c-n5": true });
169+
const bodyLight = Styles.merge(body, { "color-c-n5": true });
170+
const pLight = Styles.merge(p, { "color-c-n35": true });
171+
const preLight = Styles.merge(pre, { "color-c-n35": true });
172+
const orderedListLight = Styles.merge(orderedList, {
173173
"color-c-n35": true,
174174
});
175-
const unorderedListLight = v0_8.Styles.merge(unorderedList, {
175+
const unorderedListLight = Styles.merge(unorderedList, {
176176
"color-c-n35": true,
177177
});
178-
const listItemLight = v0_8.Styles.merge(listItem, {
178+
const listItemLight = Styles.merge(listItem, {
179179
"color-c-n35": true,
180180
});
181181

182-
export const theme: v0_8.Types.Theme = {
182+
export const theme: Types.Theme = {
183183
additionalStyles: {
184184
Button: {
185185
background: "var(--primary, oklch(0.205 0 0))",

0 commit comments

Comments
 (0)