Skip to content

feat: remove options.document and reuse vnode as dom#1728

Draft
upupming wants to merge 21 commits intolynx-family:mainfrom
upupming:feat/simple-bsi
Draft

feat: remove options.document and reuse vnode as dom#1728
upupming wants to merge 21 commits intolynx-family:mainfrom
upupming:feat/simple-bsi

Conversation

@upupming
Copy link
Copy Markdown
Collaborator

@upupming upupming commented Sep 11, 2025

We removed options.document and reuse vnode tree as BackgroundSnapshotInstance(dom) tree

See preact changes at: https://github.com/hzy/preact/pull/2/files

Summary by CodeRabbit

  • New Features

    • Added setupDom for initializing background DOM nodes.
    • Introduced BackgroundDOM type and DOM-like operations.
    • Exposed tearDown utility for cleanup.
  • Refactor

    • Replaced BackgroundSnapshotInstance with a DOM-like model.
    • Updated hydrate and printSnapshot to accept BackgroundDOM.
    • Removed document adapter and the internal/document export.
  • Tests

    • Switched test harness to setupDom and options.setupDom; updated snapshots and assertions accordingly.
  • Chores

    • Updated Preact dependency.
    • Added changeset indicating a patch release.

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).
  • Changeset added, and when a BREAKING CHANGE occurs, it needs to be clearly marked (or not required).

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Sep 11, 2025

🦋 Changeset detected

Latest commit: 6d668a8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@lynx-js/react Patch
@lynx-js/react-umd Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Sep 11, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 859b9002-a3df-4be2-ab61-51653c4d1d4d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Refactors background rendering from BackgroundSnapshotInstance to a DOM-like BackgroundDOM with a new setupDom hook. Removes the document adapter, updates hydrate and cleanup paths, adjusts internal exports/types, switches tests and benchmarks to options.setupDom/vnode.setAttribute, updates package exports and dependencies, and revises testing utilities and global setups.

Changes

Cohort / File(s) Summary
Core runtime refactor: BackgroundSnapshotInstance → BackgroundDOM/setupDom
packages/react/runtime/src/backgroundSnapshot.ts, packages/react/runtime/src/document.ts, packages/react/runtime/src/internal.ts, packages/react/runtime/src/lynx.ts, packages/react/runtime/src/root.ts, packages/react/runtime/src/snapshot.ts, packages/react/runtime/src/debug/printSnapshot.ts, packages/react/runtime/src/lifecycle/patch/commit.ts, packages/react/runtime/src/lynx/suspense.ts, packages/react/runtime/src/lynx/tt.ts, packages/react/runtime/types/internal-preact.d.ts, packages/react/runtime/lazy/internal.js
Introduces BackgroundDOM interface and setupDom; removes BackgroundSnapshotInstance and document adapter; updates hydrate signature, teardown logic, exports/types, and runtime wiring (options.setupDom).
Tests migrated to setupDom/options.setupDom
packages/react/runtime/__test__/compat.test.jsx, .../__test__/compat/initData.test.jsx, .../__test__/debug/printSnapshot.test.jsx, .../__test__/debug/profile.test.jsx, .../__test__/element.test.jsx, .../__test__/hooks/useLynxGlobalEventListener.test.jsx, .../__test__/hydrate.test.jsx, .../__test__/lifecycle/isRendering.test.jsx, .../__test__/preact.test.jsx, .../__test__/render.test.jsx, .../__test__/renderToOpcodes.test.jsx, .../__test__/snapshot/spread.test.jsx, .../__test__/snapshotPatch.test.jsx, .../__test__/utils/debug.js, .../__test__/utils/envManager.ts, .../__test__/utils/setup.js
Replaces BackgroundSnapshotInstance constructions and document setup with setupDom and options.setupDom; adjusts assertions/paths and JSON serialization helpers; updates env manager to hook options.setupDom.
Benchmark hook update
benchmark/react/cases/004-various-update/index.tsx
Moves instrumentation from SnapshotInstance.prototype.setAttribute to options.setupDom by patching vnode.setAttribute; updates imports and preserves original setupDom chaining.
Package and exports
packages/react/package.json, .changeset/slick-pandas-flow.md
Removes ./internal/document export; updates preact dependency alias; adds changeset for patch release.
Testing library global setup
packages/react/testing-library/src/vitest-global-setup.js
Initializes background root via setupDom instead of BackgroundSnapshotInstance and drops custom document patching.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

framework:React

Suggested reviewers

  • hzy
  • Yradex
  • colinaaa

Poem

I hop through nodes where snapshots bloom,
From class to DOM, I sweep the room.
A gentle tap—setupDom!—I cheer,
Threads align, the paths grow clear.
With tiny paws, I stitch the tree,
And benchmark whispers: “Ready? Whee!” 🐇✨

Pre-merge checks and finishing touches and finishing touches and finishing touches and finishing touches and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat: remove options.document and reuse vnode as dom" is concise and accurately describes the primary change in the diff (removal of options.document/document.ts and introducing setupDom/BackgroundDOM to reuse vnodes as the DOM surface), and it is clear enough for a reviewer scanning history. It avoids noise and focuses on the main developer-visible change.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Sep 11, 2025

❌ 3 Tests Failed:

Tests completed Failed Passed Skipped
4971 3 4968 194
View the top 3 failed test(s) by shortest run time
packages/react/testing-library/src/__tests__/lazy-bundle/index.test.jsx > PreactSuspense with host element child
Stack Traces | 0.00763s run time
Error: Snapshot `PreactSuspense with host element child 5` mismatched

- Expected
+ Received

- [
-   {
-     "__id": 3,
-     "create": "function() {
-   const pageId = __vite_ssr_import_1__.__pageId;
-   const el = __CreateView(pageId);
-   __SetClasses(el, "lazy-wrapper");
-   const el1 = __CreateWrapperElement(pageId);
-   __AppendElement(el, el1);
-   const el2 = __CreateText(pageId);
-   __AppendElement(el, el2);
-   const el3 = __CreateRawText("Hello, ReactLynx");
-   __AppendElement(el2, el3);
-   const el4 = __CreateWrapperElement(pageId);
-   __AppendElement(el, el4);
-   return [
-     el,
-     el1,
-     el2,
-     el3,
-     el4
-   ];
- }",
-     "type": "__snapshot_fffe1_test_5",
-   },
-   {
-     "__id": 7,
-     "create": "function() {
-   const pageId = __vite_ssr_import_1__.__pageId;
-   const el = __CreateText(pageId);
-   __SetClasses(el, "loading");
-   const el1 = __CreateRawText("loading...");
-   __AppendElement(el, el1);
-   return [
-     el,
-     el1
-   ];
- }",
-     "type": "__snapshot_fffe1_test_4",
-   },
- ]
+ []

 ❯ .../__tests__/lazy-bundle/index.test.jsx:297:35
packages/react/testing-library/src/__tests__/lazy-bundle/index.test.jsx > LynxSuspense with host element child
Stack Traces | 0.0155s run time
Error: Snapshot `LynxSuspense with host element child 5` mismatched

- Expected
+ Received

- [
-   {
-     "__id": 8,
-     "create": "create () {
-                     /* v8 ignore start */ if (__JS__ && !__DEV__) return [];
-                     /* v8 ignore stop */ return [
-                         __CreateWrapperElement(__pageId)
-                     ];
-                 }",
-     "type": "wrapper",
-   },
- ]
+ []

 ❯ .../__tests__/lazy-bundle/index.test.jsx:341:35
tests/lynx.spec.ts > kitten-lynx testing framework > can navigate to a page and read the DOM
Stack Traces | 5.9s run time
AssertionError: promise rejected "Error: No response found for deviceId: em…" instead of resolving
 ❯ tests/lynx.spec.ts:42:37

Caused by: Caused by: Caused by: Error: No response found for deviceId: emulator-5554 port: 8901
 ❯ Connector.#sendMessageWithTransport ../...../devtool-connector/src/index.ts:433:11
 ❯ Connector.sendCDPMessage ../...../devtool-connector/src/index.ts:210:12
 ❯ CDPChannel.send src/CDPChannel.ts:158:12
 ❯ ElementNode.tap src/ElementNode.ts:93:5
 ❯ tests/lynx.spec.ts:42:7

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Sep 11, 2025

Web Explorer

#8265 Bundle Size — 385.21KiB (0%).

0b1076a(current) vs 726f600 main#8237(baseline)

Bundle metrics  no changes
                 Current
#8265
     Baseline
#8237
No change  Initial JS 155.59KiB 155.59KiB
No change  Initial CSS 35.1KiB 35.1KiB
No change  Cache Invalidation 0% 0%
No change  Chunks 8 8
No change  Assets 8 8
No change  Modules 238 238
No change  Duplicate Modules 16 16
No change  Duplicate Code 2.97% 2.97%
No change  Packages 4 4
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#8265
     Baseline
#8237
No change  JS 254.26KiB 254.26KiB
No change  Other 95.85KiB 95.85KiB
No change  CSS 35.1KiB 35.1KiB

Bundle analysis reportBranch upupming:feat/simple-bsiProject dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Sep 11, 2025

React Example

#5198 Bundle Size — 238.41KiB (-0.21%).

79713c3(current) vs f24eb4a main#5196(baseline)

Warning

Bundle introduced one new package: @upupming/preact – View changed packages

Bundle metrics  Change 4 changes Improvement 1 improvement
                 Current
#5198
     Baseline
#5196
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
Change  Cache Invalidation 38.99% 0%
No change  Chunks 0 0
No change  Assets 4 4
Change  Modules 159(-2.45%) 163
Improvement  Duplicate Modules 64(-4.48%) 67
Change  Duplicate Code 45.81%(-2.09%) 46.79%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  Change 1 change Improvement 1 improvement
                 Current
#5198
     Baseline
#5196
No change  IMG 145.76KiB 145.76KiB
Improvement  Other 92.65KiB (-0.53%) 93.14KiB

Bundle analysis reportBranch upupming:feat/simple-bsiProject dashboard


Generated by RelativeCIDocumentationReport issue

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Sep 11, 2025

Merging this PR will degrade performance by 27.52%

❌ 3 regressed benchmarks
✅ 69 untouched benchmarks
⏩ 3 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
006-static-raw-text-renderBackground 3.2 ms 3.7 ms -13.02%
002-hello-reactLynx-destroyBackground 678.2 µs 935.6 µs -27.52%
transform 1000 view elements 43.4 ms 47.3 ms -8.38%

Comparing upupming:feat/simple-bsi (0b1076a) with main (726f600)

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@upupming
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Sep 13, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/react/package.json (1)

117-119: Remove stale typesVersions path for deleted export

typesVersions["internal/document"] remains while the runtime export was removed. This misleads TS users into a type-only path that fails at runtime. Drop the mapping.

-      "internal/document": [
-        "./runtime/lib/document.d.ts"
-      ],
🧹 Nitpick comments (22)
packages/react/runtime/__test__/utils/debug.js (1)

12-12: Defensive: tolerate non-instance children in tests

Guard toJSON() to avoid brittle failures if a primitive sneaks into childNodes.

-    children: this.childNodes.map(child => child.toJSON()),
+    children: this.childNodes.map(child =>
+      typeof child?.toJSON === 'function' ? child.toJSON() : child
+    ),

Also applies to: 29-29

packages/react/runtime/__test__/lifecycle/isRendering.test.jsx (1)

45-45: Reduce brittleness: centralize access to background text value

The repeated __root.firstChild.firstChild.__values is fragile. Use a tiny helper to keep tests stable if the structure shifts again.

-    expect(__root.firstChild.firstChild.__values).toEqual(['Hello World']);
+    expect(rootTextValues()).toEqual(['Hello World']);
@@
-    expect(__root.firstChild.firstChild.__values).toEqual(['World']);
+    expect(rootTextValues()).toEqual(['World']);
@@
-    expect(__root.firstChild.firstChild.__values).toEqual(['World']);
+    expect(rootTextValues()).toEqual(['World']);

Add near imports:

const rootTextValues = () => __root.firstChild.firstChild.__values;

Also applies to: 90-90, 112-112

packages/react/runtime/__test__/render.test.jsx (1)

45-45: Same helper here to avoid hard-coding internals

Mirror the accessor helper to reduce churn across tests.

-    expect(__root.firstChild.firstChild.__values).toEqual(['Hello World']);
+    expect(rootTextValues()).toEqual(['Hello World']);
@@
-    expect(__root.firstChild.firstChild.__values).toEqual([88]);
+    expect(rootTextValues()).toEqual([88]);

Add near imports:

const rootTextValues = () => __root.firstChild.firstChild.__values;

Also applies to: 65-65

packages/react/runtime/src/lynx.ts (1)

56-56: Optional: don’t clobber host-provided setup — use nullish assignment

Replace direct assignment so a host-provided setup isn't overwritten.

-  options.setupDom = setupDom;
+  options.setupDom ??= setupDom;

Also: assignments to options.document remain in tests — packages/react/testing-library/src/vitest-global-setup.js:133,141. Remove or justify those usages.

packages/react/runtime/__test__/debug/printSnapshot.test.jsx (1)

10-10: Remove unused import

BackgroundSnapshotInstance isn’t used after the migration.

-import { BackgroundSnapshotInstance, setupDom } from '../../src/backgroundSnapshot';
+import { setupDom } from '../../src/backgroundSnapshot';
packages/react/runtime/__test__/debug/profile.test.jsx (1)

21-24: Add symmetric cleanup to avoid cross-test leakage.

Optional but safer to unmount after each test.

Add:

-import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
+import { beforeAll, beforeEach, afterEach, describe, expect, test, vi } from 'vitest';
@@
   beforeEach(() => {
     snapshotInstanceManager.clear();
     scratch = options.setupDom({ type: 'root' });
   });
+
+  afterEach(() => {
+    render(null, scratch);
+  });
packages/react/runtime/src/snapshot/spread.ts (1)

92-101: Nit: prefer Object.keys/entries to avoid inherited keys.

Current for...in is safe for plain objects but using Object.entries is clearer and avoids surprises.

- for (const key in newValue) {
-   const v = newValue[key];
+ for (const [key, v] of Object.entries(newValue)) {
@@
- for (const key in oldValue) {
+ for (const key of Object.keys(oldValue)) {

Also applies to: 192-201

packages/react/runtime/src/snapshot.ts (2)

9-15: Update docstring to match the new background type.

Comment still references BackgroundSnapshotInstance. Recommend renaming to BackgroundDOM.

- * 3. {@link BackgroundSnapshotInstance}: Runtime instance in the background thread
+ * 3. {@link BackgroundDOM}: Runtime instance in the background thread

170-193: Minor readability: avoid parsing index when addressing __extraProps.

Not an issue, but you can branch on res[1] === '__extraProps' before computing expIndex.

-    const expIndex = Number(res[1]);
-    const ctx = this.values.get(id);
+    const isExtra = res[1] === '__extraProps';
+    const expIndex = isExtra ? -1 : Number(res[1]);
+    const ctx = this.values.get(id);
@@
-    if (res[1] === '__extraProps') {
+    if (isExtra) {
packages/react/runtime/src/lynx/tt.ts (1)

118-123: Nit: fix stale comment type.

The nearby commented debug line still mentions BackgroundSnapshotInstance.

- // printSnapshotInstance(__root as BackgroundSnapshotInstance);
+ // printSnapshotInstance(__root as BackgroundDOM);
packages/react/runtime/__test__/compat/initData.test.jsx (1)

36-36: Prefer direct setupDom import for clarity in tests

If you need decoration, keep the wrapper but use a local alias to reduce coupling to Preact.options.

-  scratch = options.setupDom({ type: 'root' });
+  const createDom = options.setupDom;
+  scratch = createDom({ type: 'root' });
packages/react/runtime/src/internal.ts (1)

21-21: Prefer separate type re-export to avoid mixing with value exports

Minor ergonomics/readability.

-export { type BackgroundDOM, SnapshotInstance, snapshotCreateList, createSnapshot, snapshotManager };
+export type { BackgroundDOM } from './backgroundSnapshot.js';
+export { SnapshotInstance, snapshotCreateList, createSnapshot, snapshotManager };
packages/react/runtime/__test__/element.test.jsx (2)

3-3: Use the public path consistently

If other tests import from runtime/lib, keep imports aligned to avoid duplicate module instances under different paths.

-import { setupDom } from '../src/backgroundSnapshot';
+import { setupDom } from '../src/backgroundSnapshot';
+# or align with other tests if they use '../../runtime/lib/backgroundSnapshot.js'

28-31: Assert sibling invariants too

Add checks for nextSibling/previousSibling when removing to cover link maintenance.

   root.removeChild(child2);
-  expect(child2.parentNode).toEqual(undefined);
+  expect(child2.parentNode).toEqual(undefined);
+  expect(child1.nextSibling).toBe(child3);
+  expect(child3.previousSibling).toBe(child1);

Also applies to: 35-35

packages/react/runtime/src/root.ts (1)

36-36: Drop the double cast; make setupDom return BackgroundDOM.

The as unknown as BackgroundDOM is a smell and hides real typing issues. Prefer making setupDom’s return type BackgroundDOM and using it directly.

Apply this diff here once setupDom is typed correctly:

-  setRoot(setupDom({ type: 'root' } as unknown as BackgroundDOM));
+  setRoot(setupDom({ type: 'root' }));

Outside this hunk, ensure setupDom is declared as:

export function setupDom(config: { type: string }): BackgroundDOM;
packages/react/runtime/__test__/utils/envManager.ts (2)

43-49: Avoid proto; use Object.setPrototypeOf for clarity and perf.

Functionally equivalent, less magical, and avoids TS suppression.

-      // @ts-expect-error expected
-      vnode.__proto__ = SnapshotInstance.prototype;
+      Object.setPrototypeOf(vnode, SnapshotInstance.prototype);

61-63: Remove redundant cast; fix setupDom typing instead.

Casting to unknown as BackgroundDOM masks real type info and can hide bugs.

-      setRoot(setupDom({ type: 'root' } as unknown as BackgroundDOM));
+      setRoot(setupDom({ type: 'root' }));

If needed, adjust setupDom’s signature to return BackgroundDOM.

packages/react/runtime/src/lynx/suspense.ts (1)

20-24: Minor: initialize ref with null for explicitness.

Not required, but useRef<BackgroundDOM | null>(null) makes intent clearer in strict setups.

-  const childrenRef = useRef<BackgroundDOM>();
+  const childrenRef = useRef<BackgroundDOM | null>(null);

And check childrenRef.current accordingly.

packages/react/runtime/__test__/hooks/useLynxGlobalEventListener.test.jsx (1)

20-23: Fix JSDoc type for background root

scratch is a Background DOM vnode, not a SnapshotInstance. Update the type to improve editor/type tooling.

-  /** @type {SnapshotInstance} */
+  /** @type {import('../../src/backgroundSnapshot').BackgroundDOM} */
   let scratch;
   let switchToBackground = globalEnvManager.switchToBackground.bind(globalEnvManager);
packages/react/runtime/__test__/preact.test.jsx (3)

5-5: Remove unused import

setupDom isn’t used in this file; drop it to avoid lint noise.

-import { hydrate, setupDom } from '../src/backgroundSnapshot';
+import { hydrate } from '../src/backgroundSnapshot';

316-318: Correct background scratch type annotation

The background scratch is a BackgroundDOM vnode, not a SnapshotInstance.

-  /** @type {SnapshotInstance} */
+  /** @type {import('../src/backgroundSnapshot').BackgroundDOM} */
   let scratch;
@@
-    scratch = options.setupDom({ type: 'root' });
+    scratch = options.setupDom({ type: 'root' });

Also applies to: 340-342


624-639: DRY the setupDom-toJSON wrapping

This block duplicates the earlier wrapping logic. Consider extracting a small helper (e.g., withBackgroundToJSON) to apply the wrapper once per suite and restore it, reducing maintenance.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ad38e6 and 04eb7c9.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (33)
  • .changeset/slick-pandas-flow.md (1 hunks)
  • benchmark/react/cases/004-various-update/index.tsx (2 hunks)
  • packages/react/package.json (1 hunks)
  • packages/react/runtime/__test__/compat.test.jsx (2 hunks)
  • packages/react/runtime/__test__/compat/initData.test.jsx (2 hunks)
  • packages/react/runtime/__test__/debug/printSnapshot.test.jsx (3 hunks)
  • packages/react/runtime/__test__/debug/profile.test.jsx (1 hunks)
  • packages/react/runtime/__test__/element.test.jsx (2 hunks)
  • packages/react/runtime/__test__/hooks/useLynxGlobalEventListener.test.jsx (2 hunks)
  • packages/react/runtime/__test__/hydrate.test.jsx (7 hunks)
  • packages/react/runtime/__test__/lifecycle/isRendering.test.jsx (3 hunks)
  • packages/react/runtime/__test__/preact.test.jsx (17 hunks)
  • packages/react/runtime/__test__/render.test.jsx (2 hunks)
  • packages/react/runtime/__test__/renderToOpcodes.test.jsx (1 hunks)
  • packages/react/runtime/__test__/snapshot/spread.test.jsx (2 hunks)
  • packages/react/runtime/__test__/snapshotPatch.test.jsx (15 hunks)
  • packages/react/runtime/__test__/utils/debug.js (2 hunks)
  • packages/react/runtime/__test__/utils/envManager.ts (3 hunks)
  • packages/react/runtime/__test__/utils/setup.js (0 hunks)
  • packages/react/runtime/lazy/internal.js (0 hunks)
  • packages/react/runtime/src/backgroundSnapshot.ts (4 hunks)
  • packages/react/runtime/src/debug/printSnapshot.ts (1 hunks)
  • packages/react/runtime/src/document.ts (0 hunks)
  • packages/react/runtime/src/internal.ts (2 hunks)
  • packages/react/runtime/src/lifecycle/patch/commit.ts (3 hunks)
  • packages/react/runtime/src/lynx.ts (2 hunks)
  • packages/react/runtime/src/lynx/suspense.ts (2 hunks)
  • packages/react/runtime/src/lynx/tt.ts (2 hunks)
  • packages/react/runtime/src/root.ts (2 hunks)
  • packages/react/runtime/src/snapshot.ts (2 hunks)
  • packages/react/runtime/src/snapshot/spread.ts (2 hunks)
  • packages/react/runtime/types/internal-preact.d.ts (2 hunks)
  • packages/react/testing-library/src/vitest-global-setup.js (2 hunks)
💤 Files with no reviewable changes (3)
  • packages/react/runtime/lazy/internal.js
  • packages/react/runtime/test/utils/setup.js
  • packages/react/runtime/src/document.ts
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-08-27T12:42:01.095Z
Learnt from: upupming
PR: lynx-family/lynx-stack#1616
File: packages/webpack/cache-events-webpack-plugin/test/cases/not-cache-events/lazy-bundle/index.js:3-3
Timestamp: 2025-08-27T12:42:01.095Z
Learning: In webpack, properties like __webpack_require__.lynx_ce are injected during compilation/build time when webpack processes modules and generates bundles, not at runtime when dynamic imports execute. Tests for such properties don't need to wait for dynamic imports to complete.

Applied to files:

  • packages/react/runtime/src/internal.ts
  • packages/react/runtime/src/lynx.ts
📚 Learning: 2025-08-12T16:09:32.413Z
Learnt from: colinaaa
PR: lynx-family/lynx-stack#1497
File: packages/react/transform/tests/__swc_snapshots__/src/swc_plugin_snapshot/mod.rs/basic_full_static.js:9-10
Timestamp: 2025-08-12T16:09:32.413Z
Learning: In the Lynx stack, functions prefixed with `__` that are called in transformed code may be injected globally by the Lynx Engine at runtime rather than exported from the React runtime package. For example, `__CreateFrame` is injected globally by the Lynx Engine, not exported from lynx-js/react.

Applied to files:

  • packages/react/runtime/src/internal.ts
  • packages/react/runtime/src/lynx.ts
  • packages/react/runtime/src/root.ts
📚 Learning: 2025-09-12T09:43:04.810Z
Learnt from: gaoachao
PR: lynx-family/lynx-stack#1736
File: .changeset/spotty-experts-smoke.md:1-3
Timestamp: 2025-09-12T09:43:04.810Z
Learning: In the lynx-family/lynx-stack repository, empty changeset files (containing only `---\n\n---`) are used for internal changes that modify src/** files but don't require meaningful release notes, such as private package changes or testing-only modifications. This satisfies CI requirements without generating user-facing release notes.

Applied to files:

  • .changeset/slick-pandas-flow.md
📚 Learning: 2025-09-12T09:43:04.810Z
Learnt from: gaoachao
PR: lynx-family/lynx-stack#1736
File: .changeset/spotty-experts-smoke.md:1-3
Timestamp: 2025-09-12T09:43:04.810Z
Learning: In the lynx-family/lynx-stack repository, private packages (marked with "private": true in package.json) like lynx-js/react-transform don't require meaningful changeset entries even when their public APIs change, since they are not published externally and only affect internal development.

Applied to files:

  • .changeset/slick-pandas-flow.md
  • packages/react/runtime/src/lynx.ts
📚 Learning: 2025-07-22T09:23:07.797Z
Learnt from: colinaaa
PR: lynx-family/lynx-stack#1330
File: .changeset/olive-animals-attend.md:1-3
Timestamp: 2025-07-22T09:23:07.797Z
Learning: In the lynx-family/lynx-stack repository, changesets are only required for meaningful changes to end-users such as bugfixes and features. Internal/development changes like chores, refactoring, or removing debug info do not need changeset entries.

Applied to files:

  • .changeset/slick-pandas-flow.md
📚 Learning: 2025-07-22T09:26:16.722Z
Learnt from: colinaaa
PR: lynx-family/lynx-stack#1330
File: .changeset/olive-animals-attend.md:1-3
Timestamp: 2025-07-22T09:26:16.722Z
Learning: In the lynx-family/lynx-stack repository, CI checks require changesets when files matching the pattern "src/**" are modified (as configured in .changeset/config.json). For internal changes that don't need meaningful changesets, an empty changeset file is used to satisfy the CI requirement while not generating any release notes.

Applied to files:

  • .changeset/slick-pandas-flow.md
📚 Learning: 2025-08-19T11:25:36.127Z
Learnt from: colinaaa
PR: lynx-family/lynx-stack#1558
File: .changeset/solid-squids-fall.md:2-2
Timestamp: 2025-08-19T11:25:36.127Z
Learning: In the lynx-family/lynx-stack repository, changesets should use the exact package name from package.json#name, not generic or unscoped names. Each package has its own specific scoped name (e.g., "lynx-js/react-transform" for packages/react/transform).

Applied to files:

  • .changeset/slick-pandas-flow.md
📚 Learning: 2025-07-18T04:27:18.291Z
Learnt from: colinaaa
PR: lynx-family/lynx-stack#1238
File: packages/react/runtime/src/debug/component-stack.ts:70-90
Timestamp: 2025-07-18T04:27:18.291Z
Learning: The component-stack.ts file in packages/react/runtime/src/debug/component-stack.ts is a direct fork from https://github.com/preactjs/preact/blob/main/debug/src/component-stack.js. The team prefers to keep it aligned with the upstream Preact version and may contribute improvements back to Preact in the future.

Applied to files:

  • packages/react/runtime/__test__/debug/profile.test.jsx
  • packages/react/runtime/__test__/compat/initData.test.jsx
📚 Learning: 2025-08-11T05:57:18.212Z
Learnt from: upupming
PR: lynx-family/lynx-stack#1305
File: packages/testing-library/testing-environment/src/index.ts:255-258
Timestamp: 2025-08-11T05:57:18.212Z
Learning: In the ReactLynx testing environment (`packages/testing-library/testing-environment/src/index.ts`), the dual assignment pattern `target.console.method = console.method = () => {}` is required for rstest compatibility. This is because rstest provides `console` in an IIFE (Immediately Invoked Function Expression), and both the target and global console need to have these methods defined for proper test execution.

Applied to files:

  • packages/react/runtime/__test__/compat.test.jsx
  • packages/react/runtime/__test__/hooks/useLynxGlobalEventListener.test.jsx
📚 Learning: 2025-08-06T13:28:57.182Z
Learnt from: colinaaa
PR: lynx-family/lynx-stack#1453
File: vitest.config.ts:49-61
Timestamp: 2025-08-06T13:28:57.182Z
Learning: In the lynx-family/lynx-stack repository, the file `packages/react/testing-library/src/vitest.config.js` is source code for the testing library that gets exported for users, not a test configuration that should be included in the main vitest projects array.

Applied to files:

  • packages/react/runtime/__test__/compat.test.jsx
📚 Learning: 2025-08-11T05:59:28.530Z
Learnt from: upupming
PR: lynx-family/lynx-stack#1305
File: packages/react/testing-library/src/plugins/vitest.ts:4-6
Timestamp: 2025-08-11T05:59:28.530Z
Learning: In the lynx-family/lynx-stack repository, the `packages/react/testing-library` package does not have `vite` as a direct dependency. It relies on `vitest` being available from the monorepo root and accesses Vite types through re-exports from `vitest/node`. Direct imports from `vite` should not be suggested for this package.

Applied to files:

  • packages/react/runtime/__test__/compat.test.jsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (16)
  • GitHub Check: Playwright MULTI_THREAD-CSR (2/4) / check
  • GitHub Check: Playwright MULTI_THREAD-CSR (1/4) / check
  • GitHub Check: Playwright ALL_ON_UI-SSR (1/4) / check
  • GitHub Check: Playwright ALL_ON_UI-CSR (3/4) / check
  • GitHub Check: Playwright ALL_ON_UI-CSR (2/4) / check
  • GitHub Check: Playwright ALL_ON_UI-CSR (1/4) / check
  • GitHub Check: Playwright ALL_ON_UI-SSR (2/4) / check
  • GitHub Check: Playwright ALL_ON_UI-SSR (3/4) / check
  • GitHub Check: Playwright MULTI_THREAD-CSR (3/4) / check
  • GitHub Check: Playwright ALL_ON_UI-CSR (4/4) / check
  • GitHub Check: Vitest (Ubuntu) / check
  • GitHub Check: Vitest (Windows) / check
  • GitHub Check: test-publish / check
  • GitHub Check: lighthouse / check
  • GitHub Check: eslint / check
  • GitHub Check: benchmark / nodejs-benchmark
🔇 Additional comments (32)
packages/react/package.json (1)

173-173: Confirm forked preact pin, update deprecated usages, and check for conflicts

  • Pin published: npm shows @upupming/preact@10.24.0-simple-bsi.4.
  • Types include setupDom: packages/react/runtime/types/internal-preact.d.ts — Options.setupDom?: typeof setupDom.
  • Deprecated usage found: packages/react/testing-library/src/vitest-global-setup.js assigns options.document (≈lines 133, 141) — replace with setupDom or verify compatibility.
  • Verify no other package pins a conflicting preact alias/version across the monorepo (search package.json files for "preact" / "npm:@upupming/preact").
packages/react/runtime/src/lynx.ts (1)

9-9: LGTM: new DOM setup is correctly wired

Importing setupDom here aligns with the background migration.

packages/react/runtime/__test__/renderToOpcodes.test.jsx (1)

606-606: LGTM: using SnapshotInstance directly simplifies setup

Direct instantiation is clearer than the prior document shim and matches new APIs.

packages/react/runtime/__test__/debug/printSnapshot.test.jsx (1)

54-57: LGTM: migrated construction to setupDom

The switch to setupDom({ type }) matches the new Background DOM contract and keeps the test behavior intact.

Also applies to: 76-79

packages/react/runtime/__test__/debug/profile.test.jsx (1)

8-8: No action — options.setupDom is initialized in the Vitest setup.

packages/react/runtime/vitest.config.ts registers setupFiles: test/utils/globals.js then test/utils/setup.js; globals.js sets BACKGROUND = true and setup.js imports packages/react/runtime/src/lynx.ts, which assigns options.setupDom = setupDom when BACKGROUND is true.

packages/react/runtime/__test__/compat.test.jsx (1)

23-29: LGTM: switching test root to new SnapshotInstance('root').

This aligns with the new DOM model and keeps cleanup deterministic via render(null, scratch).

packages/react/runtime/__test__/snapshotPatch.test.jsx (2)

18-19: LGTM: using setupDom in place of BackgroundSnapshotInstance.

Import and usage match the new API.


81-83: Counters are already reset in the test setup — the “reset counters” concern is incorrect; optional: make snapshots ID‑agnostic.

Global setup (packages/react/testing-library/src/vitest-global-setup.js) calls snapshotInstanceManager.clear()/backgroundSnapshotInstanceManager.clear() and sets nextId = 0; runtime/test/utils/envManager.ts.resetEnv() does the same; many tests also reset these in beforeEach (e.g. spread.test.jsx, hydrate.test.jsx).

  • Optional: prefer pattern assertions for __id fields or add a small helper (e.g., const newDom = (type) => setupDom({ type })) to reduce snapshot brittleness.

Likely an incorrect or invalid review comment.

packages/react/runtime/src/snapshot/spread.ts (1)

14-15: Type shift to BackgroundDOM is correct and non-breaking.

transformSpread accepting BackgroundDOM | SnapshotInstance makes shared logic usable on both sides.

Also applies to: 298-301

packages/react/runtime/src/debug/printSnapshot.ts (1)

8-14: LGTM: BackgroundDOM union typing.

Printing works for both background DOM and main-thread instances without behavior change.

packages/react/runtime/src/snapshot.ts (1)

139-147: LGTM: background manager now tracks BackgroundDOM.

Type change is consistent with the refactor; methods (updateId, getValueBySign) remain compatible.

packages/react/runtime/src/lynx/tt.ts (1)

90-95: LGTM: hydrate now takes BackgroundDOM.

Matches the new hydrate(before, __root as BackgroundDOM) signature.

packages/react/runtime/src/lifecycle/patch/commit.ts (1)

139-141: Guarded teardown looks good

Using the map to resolve the root node and deferring teardown is consistent with prior flow.

packages/react/runtime/src/internal.ts (1)

10-10: Type-only import is correct — BackgroundDOM is exported as an interface.
packages/react/runtime/src/backgroundSnapshot.ts exports export interface BackgroundDOM, so import type { BackgroundDOM } from './backgroundSnapshot.js'; is valid.

packages/react/runtime/__test__/element.test.jsx (1)

9-12: Good migration to setupDom

Root/children creation via setupDom matches the new BackgroundDOM model.

packages/react/testing-library/src/vitest-global-setup.js (1)

18-18: Great: background root now uses setupDom

This aligns tests with the new DOM surface.

packages/react/runtime/__test__/hydrate.test.jsx (4)

3-3: Import swap to setupDom/hydrate looks correct.

Path and named imports align with the new API.


239-243: Root bootstrap via setupDom.

Good coverage for the empty-render path using a BackgroundDOM root.


295-303: Slot case migration to setupDom.

Looks consistent; keeps multi-children/slot behavior intact.


93-111: Parity confirmed — setupDom exposes SnapshotInstance API.

backgroundSnapshot.setupDom (packages/react/runtime/src/backgroundSnapshot.ts) attaches insertBefore/setAttribute to the vnode and the test env (packages/react/runtime/test/utils/envManager.ts) maps vnodes to SnapshotInstance.prototype; no change required.

packages/react/runtime/__test__/snapshot/spread.test.jsx (4)

6-6: Importing preact options/render is fine with the new hook.

No issues spotted.


9-10: Adoption of hydrate/setupDom.

Migration matches the new background DOM API.


13-18: Expanded snapshot imports are consistent with env changes.

OK to use SnapshotInstance alongside setupDom within the same test.


31-35: envManager already wires options.setupDom — no change required.

options.setupDom is assigned in packages/react/runtime/test/utils/envManager.ts (lines 43 and 68); adding a sanity assertion is optional.

packages/react/runtime/src/root.ts (2)

4-6: Type import + setupDom import align with new BackgroundDOM.

Looks good.


12-12: __root union updated to include BackgroundDOM.

This keeps types accurate across both environments.

packages/react/runtime/__test__/utils/envManager.ts (2)

13-15: Using preact options.setupDom is the right direction.

Imports are consistent with the new architecture.


68-69: Restore original options.setupDom after tests

Persisting a mutated global hook across suites can cause flaky tests. File: packages/react/runtime/test/utils/envManager.ts (around lines 68–69).

Suggested minimal change:

// add to class fields
private prevSetupDom?: typeof options.setupDom;

// when assigning:
this.prevSetupDom = options.setupDom;
options.setupDom = setupDom;

// in resetEnv():
if (this.prevSetupDom !== undefined) {
  options.setupDom = this.prevSetupDom;
  this.prevSetupDom = undefined;
}
packages/react/runtime/src/lynx/suspense.ts (2)

11-12: Type shift to BackgroundDOM is correct.

Keeps the wrapper refs aligned with the new background DOM abstraction.


32-33: Ref cleanup logic is intact after type change.

Accessing __id remains valid with BackgroundDOM.

packages/react/runtime/src/backgroundSnapshot.ts (2)

320-341: No change required — traverseSnapshotInstance accepts BackgroundDOM

BackgroundDOM declares childNodes: BackgroundDOM[] and traverseSnapshotInstance(si, ...) is structurally compatible, so removeChild’s traversal is type-safe (see snapshot.ts traverseSnapshotInstance and backgroundSnapshot.ts BackgroundDOM).


234-290: Cross-parent moves are already handled — no RemoveChild needed.

Verified: backgroundSnapshot.insertBefore emits SnapshotOperation.InsertBefore for moves (reconstructInstanceTree used only when node.__removed_from_tree). The main-thread consumers apply InsertBefore as a move: OffscreenElement.insertBefore calls node._remove() then emits OperationType.InsertBefore, and initOffscreenDocument.decodeOperation handles InsertBefore by calling target.insertBefore(kid, ref) (which moves the node). No code path requires emitting RemoveChild before InsertBefore.
Files checked: packages/react/runtime/src/backgroundSnapshot.ts, packages/web-platform/offscreen-document/src/webworker/OffscreenElement.ts, packages/web-platform/offscreen-document/src/main/initOffscreenDocument.ts.

Comment on lines +2 to +5
"@lynx-js/react": patch
---

Reuse vnode tree as dom tree in bts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Sep 13, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Clarify scope and bump version; this is a breaking surface change

Removing the public ./internal/document export and switching from options.document to options.setupDom is breaking for external consumers. Promote to a major (or at least minor if you consider internal/* non-semver) and spell out the migration in the note.

Apply:

-"@lynx-js/react": patch
+"@lynx-js/react": major
@@
-Reuse vnode tree as dom tree in bts
+feat(background): reuse VNode tree as Background DOM via options.setupDom
+BREAKING: remove options.document and the public export "./internal/document". Migrate any usage to options.setupDom and BackgroundDOM.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@lynx-js/react": patch
---
Reuse vnode tree as dom tree in bts
"@lynx-js/react": major
---
feat(background): reuse VNode tree as Background DOM via options.setupDom
BREAKING: remove options.document and the public export "./internal/document". Migrate any usage to options.setupDom and BackgroundDOM.
🤖 Prompt for AI Agents
In .changeset/slick-pandas-flow.md around lines 2–5, the changeset currently
lists this as a patch and omits migration guidance despite removing the public
./internal/document export and replacing options.document with options.setupDom;
update the changeset to a major (or at least minor) release, explicitly mark it
as a breaking change, and add a short migration note explaining: 1) consumers
must switch from options.document to options.setupDom and show the minimal
example of the new API call, 2) any code importing ./internal/document must be
replaced with the new public API (or migrated to internal usage if appropriate),
and 3) recommend steps to test the change; also update the summary line to
clearly state the scope of the breaking change.

Copy link
Copy Markdown
Collaborator Author

@upupming upupming Sep 13, 2025

Choose a reason for hiding this comment

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

@coderabbitai minor please, we are in 0.x stage

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.

@upupming You're absolutely right! In 0.x stage, breaking changes should go in minor releases. Here's the updated changeset:

---
"@lynx-js/react": minor
---

feat(background): reuse VNode tree as Background DOM via options.setupDom
BREAKING: remove options.document and the public export "./internal/document". Migrate any usage to options.setupDom and BackgroundDOM.

This properly reflects the 0.x semver convention where breaking changes are released as minor bumps rather than major ones.


🧠 Learnings used
Learnt from: gaoachao
PR: lynx-family/lynx-stack#1736
File: .changeset/spotty-experts-smoke.md:1-3
Timestamp: 2025-09-12T09:43:04.810Z
Learning: In the lynx-family/lynx-stack repository, empty changeset files (containing only `---\n\n---`) are used for internal changes that modify src/** files but don't require meaningful release notes, such as private package changes or testing-only modifications. This satisfies CI requirements without generating user-facing release notes.

Learnt from: gaoachao
PR: lynx-family/lynx-stack#1736
File: .changeset/spotty-experts-smoke.md:1-3
Timestamp: 2025-09-12T09:43:04.810Z
Learning: In the lynx-family/lynx-stack repository, private packages (marked with "private": true in package.json) like lynx-js/react-transform don't require meaningful changeset entries even when their public APIs change, since they are not published externally and only affect internal development.

Learnt from: colinaaa
PR: lynx-family/lynx-stack#1330
File: .changeset/olive-animals-attend.md:1-3
Timestamp: 2025-07-22T09:23:07.797Z
Learning: In the lynx-family/lynx-stack repository, changesets are only required for meaningful changes to end-users such as bugfixes and features. Internal/development changes like chores, refactoring, or removing debug info do not need changeset entries.

Learnt from: colinaaa
PR: lynx-family/lynx-stack#1558
File: .changeset/solid-squids-fall.md:2-2
Timestamp: 2025-08-19T11:25:36.127Z
Learning: In the lynx-family/lynx-stack repository, changesets should use the exact package name from package.json#name, not generic or unscoped names. Each package has its own specific scoped name (e.g., "lynx-js/react-transform" for packages/react/transform).

Learnt from: colinaaa
PR: lynx-family/lynx-stack#1330
File: .changeset/olive-animals-attend.md:1-3
Timestamp: 2025-07-22T09:26:16.722Z
Learning: In the lynx-family/lynx-stack repository, CI checks require changesets when files matching the pattern "src/**" are modified (as configured in .changeset/config.json). For internal changes that don't need meaningful changesets, an empty changeset file is used to satisfy the CI requirement while not generating any release notes.

Comment on lines +91 to 117
options,
'setupDom',
function(old: typeof options.setupDom, vnode: BackgroundDOM) {
old?.(vnode);
const oldSetAttribute = vnode.setAttribute;
vnode.setAttribute = function(this: BackgroundDOM, key, value) {
const values = value as unknown[];
if (
key === 'values'
&& values[values.length - 1] === 'stop-benchmark-true'
) {
// we only care about the update that stops the benchmark

Codspeed.startBenchmark();
const ret = oldSetAttribute.call(this, key, value);
Codspeed.stopBenchmark();
Codspeed.setExecutedBenchmark(
`${__REPO_FILEPATH__}::${__webpack_chunkname__}-setAttribute__BatchedValues`,
);
return ret;
}

return old!.call(this, key, value);
},
return oldSetAttribute.call(this, key, value);
};
return vnode;
} as never,
);
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.

⚠️ Potential issue

Preserve setupDom return value and type the setAttribute shim correctly

  • old?.(vnode) might return a (possibly replaced) vnode; reassign it.
  • Align setAttribute signature with BackgroundDOM (string | number, unknown).
   hook(
     options,
     'setupDom',
     function(old: typeof options.setupDom, vnode: BackgroundDOM) {
-      old?.(vnode);
-      const oldSetAttribute = vnode.setAttribute;
-      vnode.setAttribute = function(this: BackgroundDOM, key, value) {
+      vnode = (old?.(vnode) as BackgroundDOM | undefined) ?? vnode;
+      const oldSetAttribute = vnode.setAttribute;
+      vnode.setAttribute = function(this: BackgroundDOM, key: string | number, value: unknown) {
         const values = value as unknown[];
-        if (
-          key === 'values'
+        if (
+          key === 'values'
           && values[values.length - 1] === 'stop-benchmark-true'
         ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
options,
'setupDom',
function(old: typeof options.setupDom, vnode: BackgroundDOM) {
old?.(vnode);
const oldSetAttribute = vnode.setAttribute;
vnode.setAttribute = function(this: BackgroundDOM, key, value) {
const values = value as unknown[];
if (
key === 'values'
&& values[values.length - 1] === 'stop-benchmark-true'
) {
// we only care about the update that stops the benchmark
Codspeed.startBenchmark();
const ret = oldSetAttribute.call(this, key, value);
Codspeed.stopBenchmark();
Codspeed.setExecutedBenchmark(
`${__REPO_FILEPATH__}::${__webpack_chunkname__}-setAttribute__BatchedValues`,
);
return ret;
}
return old!.call(this, key, value);
},
return oldSetAttribute.call(this, key, value);
};
return vnode;
} as never,
);
options,
'setupDom',
function(old: typeof options.setupDom, vnode: BackgroundDOM) {
vnode = (old?.(vnode) as BackgroundDOM | undefined) ?? vnode;
const oldSetAttribute = vnode.setAttribute;
vnode.setAttribute = function(this: BackgroundDOM, key: string | number, value: unknown) {
const values = value as unknown[];
if (
key === 'values'
&& values[values.length - 1] === 'stop-benchmark-true'
) {
// we only care about the update that stops the benchmark
Codspeed.startBenchmark();
const ret = oldSetAttribute.call(this, key, value);
Codspeed.stopBenchmark();
Codspeed.setExecutedBenchmark(
`${__REPO_FILEPATH__}::${__webpack_chunkname__}-setAttribute__BatchedValues`,
);
return ret;
}
return oldSetAttribute.call(this, key, value);
};
return vnode;
} as never,
);
🤖 Prompt for AI Agents
In benchmark/react/cases/004-various-update/index.tsx around lines 91-117, the
shim calls old?.(vnode) but ignores its return (which may be a replaced vnode)
and the setAttribute shim signature is not aligned with BackgroundDOM; update
the code to reassign the vnode from old (e.g. vnode = old?.(vnode) as
BackgroundDOM || vnode) so you operate on and return the possibly replaced
vnode, and change the setAttribute signature to match BackgroundDOM (this:
BackgroundDOM, key: string | number, value: unknown) while preserving and
calling the original oldSetAttribute and returning its result.

Comment on lines +18 to +26
globalEnvManager.switchToBackground = () => {
switchToBackground();
const oldSetupDom = options.setupDom;
options.setupDom = (vnode) => {
vnode = oldSetupDom(vnode);
vnode.toJSON = backgroundSnapshotInstanceToJSON;
return vnode;
};
};
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.

⚠️ Potential issue

Avoid global leakage and double-wrapping options.setupDom

Wrap once and restore in afterAll; otherwise tests may stack wrappers and leak into other files.

Apply:

-  globalEnvManager.switchToBackground = () => {
-    switchToBackground();
-    const oldSetupDom = options.setupDom;
-    options.setupDom = (vnode) => {
-      vnode = oldSetupDom(vnode);
-      vnode.toJSON = backgroundSnapshotInstanceToJSON;
-      return vnode;
-    };
-  };
+  /** keep original to restore in afterAll */
+  /** @type {typeof options.setupDom | undefined} */
+  let originalSetupDom;
+  const DECORATED = Symbol.for('lynx.test.setupDomDecorated');
+  globalEnvManager.switchToBackground = () => {
+    switchToBackground();
+    if ((options.setupDom)[DECORATED]) return;
+    originalSetupDom = options.setupDom;
+    const base = originalSetupDom;
+    const wrapped = (vnode) => {
+      const dom = base(vnode);
+      dom.toJSON = backgroundSnapshotInstanceToJSON;
+      return dom;
+    };
+    wrapped[DECORATED] = true;
+    options.setupDom = wrapped;
+  };

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/react/runtime/__test__/compat/initData.test.jsx around lines 18-26,
the test overrides globalEnvManager.switchToBackground and replaces
options.setupDom on every call, causing double-wrapping and global leakage;
change this so you capture and store the original switchToBackground and
original options.setupDom once, perform the wrapper only once (not on every
call), and add an afterAll that restores both
globalEnvManager.switchToBackground and options.setupDom to their originals;
ensure the new switchToBackground simply delegates to the original switch
function without re-wrapping options.setupDom.

afterAll(() => {
delete BackgroundSnapshotInstance.prototype.toJSON;
globalEnvManager.switchToBackground = switchToBackground;
});
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.

🛠️ Refactor suggestion

Also restore options.setupDom

Restore the original setupDom to avoid global state bleed.

Apply:

 afterAll(() => {
   globalEnvManager.switchToBackground = switchToBackground;
+  if (typeof originalSetupDom === 'function') options.setupDom = originalSetupDom;
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
globalEnvManager.switchToBackground = switchToBackground;
afterAll(() => {
globalEnvManager.switchToBackground = switchToBackground;
if (typeof originalSetupDom === 'function') options.setupDom = originalSetupDom;
});
🤖 Prompt for AI Agents
In packages/react/runtime/__test__/compat/initData.test.jsx around line 32, the
test replaces globalEnvManager.setupDom but doesn't restore it, causing global
state bleed; capture the original setupDom before assigning switchToBackground,
and after the test (or in an afterEach/finally) reassign
globalEnvManager.setupDom back to the saved original value so the global
environment is restored after the test.

Comment on lines +22 to 37
let switchToBackground = globalEnvManager.switchToBackground.bind(globalEnvManager);
const ee = new EventEmitter();

beforeAll(() => {
setupBackgroundDocument();
setupPage(__CreatePage('0', 0));

BackgroundSnapshotInstance.prototype.toJSON = backgroundSnapshotInstanceToJSON;
globalEnvManager.switchToBackground = () => {
switchToBackground();
const oldSetupDom = options.setupDom;
options.setupDom = (vnode) => {
vnode = oldSetupDom(vnode);
vnode.toJSON = backgroundSnapshotInstanceToJSON;
return vnode;
};
};

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.

🛠️ Refactor suggestion

Avoid accumulating wrappers on options.setupDom; restore it after tests

Each call to switchToBackground wraps options.setupDom again, and afterAll only restores the switchToBackground function, not options.setupDom. Capture the original setupDom once and restore it; also wrap against that original to prevent wrapper stacking across tests.

-  let switchToBackground = globalEnvManager.switchToBackground.bind(globalEnvManager);
+  let switchToBackground = globalEnvManager.switchToBackground.bind(globalEnvManager);
+  let originalSetupDom = options.setupDom;

   beforeAll(() => {
     setupPage(__CreatePage('0', 0));

     globalEnvManager.switchToBackground = () => {
       switchToBackground();
-      const oldSetupDom = options.setupDom;
-      options.setupDom = (vnode) => {
-        vnode = oldSetupDom(vnode);
+      options.setupDom = (vnode) => {
+        vnode = originalSetupDom(vnode);
         vnode.toJSON = backgroundSnapshotInstanceToJSON;
         return vnode;
       };
     };
   });
@@
   afterAll(() => {
-    globalEnvManager.switchToBackground = switchToBackground;
+    globalEnvManager.switchToBackground = switchToBackground;
+    options.setupDom = originalSetupDom;
   });

Also applies to: 49-51

🤖 Prompt for AI Agents
In packages/react/runtime/__test__/hooks/useLynxGlobalEventListener.test.jsx
around lines 22 to 37 (and similarly at 49-51), the test replaces
globalEnvManager.switchToBackground with a wrapper that repeatedly rewraps
options.setupDom on every call causing wrapper accumulation; capture the
original options.setupDom once (e.g., const originalSetupDom = options.setupDom)
before modifying switchToBackground, have the replacement wrap originalSetupDom
(not the current options.setupDom) and assign the wrapped function to
options.setupDom, and ensure afterAll restores options.setupDom =
originalSetupDom as well as restoring switchToBackground so successive tests
don’t stack wrappers.

Comment on lines +350 to +369
if (__globalSnapshotPatch) {
const oldValues = this.__values;
if (oldValues) {
for (let index = 0; index < (value as unknown[]).length; index++) {
const { needUpdate, valueToCommit } = setAttributeImpl(
this,
(value as unknown[])[index],
oldValues[index],
index,
);
if (needUpdate) {
__globalSnapshotPatch.push(
SnapshotOperation.SetAttribute,
this.__id,
index,
valueToCommit,
);
}
}
} else {
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.

🛠️ Refactor suggestion

Handle removed trailing values when diffing the values array

When new values is shorter than the old, removed indices aren’t patched. Diff up to max length to send removals (undefined) as needed.

-  if (key === 'values') {
+  if (key === 'values') {
     if (__globalSnapshotPatch) {
-      const oldValues = this.__values;
-      if (oldValues) {
-        for (let index = 0; index < (value as unknown[]).length; index++) {
+      const oldValues = this.__values ?? [];
+      const newValues = value as unknown[]; // eslint-disable-line @typescript-eslint/no-unsafe-assignment
+      if (oldValues.length) {
+        const max = Math.max(oldValues.length, newValues.length);
+        for (let index = 0; index < max; index++) {
           const { needUpdate, valueToCommit } = setAttributeImpl(
             this,
-            (value as unknown[])[index],
-            oldValues[index],
+            newValues[index],
+            oldValues[index],
             index,
           );
           if (needUpdate) {
             __globalSnapshotPatch.push(
               SnapshotOperation.SetAttribute,
               this.__id,
               index,
               valueToCommit,
             );
           }
         }
       } else {
         const patch = [];
-        const length = (value as unknown[]).length;
-        for (let index = 0; index < length; ++index) {
-          const { valueToCommit } = setAttributeImpl(this, (value as unknown[])[index], null, index);
+        const length = newValues.length;
+        for (let index = 0; index < length; ++index) {
+          const { valueToCommit } = setAttributeImpl(this, newValues[index], null, index);
           patch[index] = valueToCommit;
         }
         __globalSnapshotPatch.push(
           SnapshotOperation.SetAttributes,
           this.__id,
           patch,
         );
       }
     } else {
@@
-    this.__values = value as unknown[];
+    this.__values = value as unknown[];

Also applies to: 394-399, 408-417

🤖 Prompt for AI Agents
packages/react/runtime/src/backgroundSnapshot.ts around lines 350-369: when
diffing arrays the loop only iterates new values length so removed trailing old
items aren’t emitted; change the iteration to run to Math.max(oldValues.length,
(value as unknown[]).length) and for indexes where the new value is missing
treat it as undefined (call setAttributeImpl or push
SnapshotOperation.SetAttribute with undefined) so removals are patched; apply
the same max-length iteration and undefined-removal logic to the other affected
ranges (around 394-399 and 408-417).

Comment on lines +510 to +516
export function setupDom(vnode: BackgroundDOM): BackgroundDOM {
const type = vnode.type;
vnode.__snapshot_def = snapshotManager.values.get(type)!;
const id = vnode.__id = backgroundSnapshotInstanceManager.nextId += 1;
__globalSnapshotPatch?.push(SnapshotOperation.CreateElement, type, id);

vnode.contains = contains;
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.

🛠️ Refactor suggestion

Guard against unknown vnode types in setupDom

Throw a descriptive error if the type is not registered; the non-null assertion can mask integration issues.

 export function setupDom(vnode: BackgroundDOM): BackgroundDOM {
   const type = vnode.type;
-  vnode.__snapshot_def = snapshotManager.values.get(type)!;
+  const def = snapshotManager.values.get(type);
+  if (!def) {
+    throw new Error(`setupDom: unknown vnode type "${type}"`);
+  }
+  vnode.__snapshot_def = def;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function setupDom(vnode: BackgroundDOM): BackgroundDOM {
const type = vnode.type;
vnode.__snapshot_def = snapshotManager.values.get(type)!;
const id = vnode.__id = backgroundSnapshotInstanceManager.nextId += 1;
__globalSnapshotPatch?.push(SnapshotOperation.CreateElement, type, id);
vnode.contains = contains;
export function setupDom(vnode: BackgroundDOM): BackgroundDOM {
const type = vnode.type;
const def = snapshotManager.values.get(type);
if (!def) {
throw new Error(`setupDom: unknown vnode type "${type}"`);
}
vnode.__snapshot_def = def;
const id = vnode.__id = backgroundSnapshotInstanceManager.nextId += 1;
__globalSnapshotPatch?.push(SnapshotOperation.CreateElement, type, id);
vnode.contains = contains;
}
🤖 Prompt for AI Agents
packages/react/runtime/src/backgroundSnapshot.ts around lines 510 to 516:
setupDom currently uses a non-null assertion when reading
snapshotManager.values.get(type), which can hide integration bugs; replace the
direct get+non-null with a guarded lookup: retrieve the snapshotDef via
get(type), if undefined throw a descriptive Error that includes the unknown type
(and optionally list registered types), otherwise assign vnode.__snapshot_def to
the retrieved value and proceed; ensure the thrown message makes it clear the
vnode type was unregistered so callers can trace the registration issue.

Comment on lines +95 to +102
export function tearDown(dom: BackgroundDOM): void {
traverseSnapshotInstance(dom, v => {
v.parentNode = undefined;
v.previousSibling = undefined;
v.nextSibling = undefined;
backgroundSnapshotInstanceManager.values.delete(v.__id);
});
}
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.

⚠️ Potential issue

tearDown mutates links during traversal; risk of skipping nodes

If traverseSnapshotInstance walks via nextSibling/child links, clearing them inside the callback can cut traversal short and leak nodes in the manager.

Apply a two-pass approach:

-export function tearDown(dom: BackgroundDOM): void {
-  traverseSnapshotInstance(dom, v => {
-    v.parentNode = undefined;
-    v.previousSibling = undefined;
-    v.nextSibling = undefined;
-    backgroundSnapshotInstanceManager.values.delete(v.__id);
-  });
-}
+export function tearDown(dom: BackgroundDOM): void {
+  const nodes: BackgroundDOM[] = [];
+  traverseSnapshotInstance(dom, v => { nodes.push(v); });
+  // Drop from manager first
+  for (const v of nodes) backgroundSnapshotInstanceManager.values.delete(v.__id);
+  // Then sever links
+  for (const v of nodes) {
+    v.parentNode = undefined;
+    v.previousSibling = undefined;
+    v.nextSibling = undefined;
+    // Best-effort extra cleanup to reduce retention
+    if ('firstChild' in (v as any)) (v as any).firstChild = undefined;
+    if ('lastChild' in (v as any)) (v as any).lastChild = undefined;
+    if (Array.isArray((v as any).childNodes)) (v as any).childNodes.length = 0;
+  }
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function tearDown(dom: BackgroundDOM): void {
traverseSnapshotInstance(dom, v => {
v.parentNode = undefined;
v.previousSibling = undefined;
v.nextSibling = undefined;
backgroundSnapshotInstanceManager.values.delete(v.__id);
});
}
export function tearDown(dom: BackgroundDOM): void {
const nodes: BackgroundDOM[] = [];
traverseSnapshotInstance(dom, v => { nodes.push(v); });
// Drop from manager first
for (const v of nodes) backgroundSnapshotInstanceManager.values.delete(v.__id);
// Then sever links
for (const v of nodes) {
v.parentNode = undefined;
v.previousSibling = undefined;
v.nextSibling = undefined;
// Best-effort extra cleanup to reduce retention
if ('firstChild' in (v as any)) (v as any).firstChild = undefined;
if ('lastChild' in (v as any)) (v as any).lastChild = undefined;
if (Array.isArray((v as any).childNodes)) (v as any).childNodes.length = 0;
}
}
🤖 Prompt for AI Agents
In packages/react/runtime/src/lifecycle/patch/commit.ts around lines 95 to 102,
tearDown currently mutates nextSibling/child links while
traverseSnapshotInstance is walking those links which can cause traversal to
skip nodes and leak entries; change to a two-pass approach: first traverse and
collect all snapshot instances (or their ids) into a temporary array without
mutating, then iterate that array to clear parent/previousSibling/nextSibling
and delete each id from backgroundSnapshotInstanceManager.values so traversal is
not affected by in-place mutations.

Comment on lines +5 to 6
import type { BackgroundSnapshotInstance, setupDom } from '../src/backgroundSnapshot.ts';

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.

💡 Verification agent

🧩 Analysis chain

Fix type drift: replace BackgroundSnapshotInstance with BackgroundDOM.

The rest of the codebase moved to BackgroundDOM. Keeping the old type here will break consumers and mis-type options.vnode.

-import type { BackgroundSnapshotInstance, setupDom } from '../src/backgroundSnapshot.ts';
+import type { BackgroundDOM, setupDom } from '../src/backgroundSnapshot.js';
@@
-    /** vnode */
-    vnode?(vnode: VNode & BackgroundSnapshotInstance): void;
+    /** vnode */
+    vnode?(vnode: VNode & BackgroundDOM): void;
     setupDom?: typeof setupDom;

If your build expects extensionless imports in .d.ts, use '../src/backgroundSnapshot' instead of .js.

Check for any remaining references to the old type:

Also applies to: 25-27


🏁 Script executed:

#!/bin/bash
rg -n "BackgroundSnapshotInstance" packages/react/runtime -g '!**/dist/**'

Length of output: 0


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== rg: BackgroundSnapshotInstance ==="
rg -n --hidden -S "BackgroundSnapshotInstance" -g '!**/dist/**' || true
echo

echo "=== rg: BackgroundDOM ==="
rg -n --hidden -S "BackgroundDOM" -g '!**/dist/**' || true
echo

echo "=== fd: backgroundSnapshot.* files ==="
fd -HI "backgroundSnapshot.*" || true
echo

echo "=== show: packages/react/runtime/types/internal-preact.d.ts ==="
if [ -f "packages/react/runtime/types/internal-preact.d.ts" ]; then
  sed -n '1,240p' packages/react/runtime/types/internal-preact.d.ts
else
  echo "file not found: packages/react/runtime/types/internal-preact.d.ts"
fi
echo

echo "=== rg: backgroundSnapshot under packages/react/runtime ==="
rg -n --hidden -S "backgroundSnapshot" packages/react/runtime -g '!**/dist/**' || true
echo

echo "=== cat any backgroundSnapshot.* files found under packages/react/runtime (first 240 lines each) ==="
while IFS= read -r f; do
  echo "----- $f -----"
  sed -n '1,240p' "$f"
done < <(fd -HI "backgroundSnapshot.*" packages/react/runtime || true)

Length of output: 1884


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== file exists ==="
[ -f packages/react/runtime/src/backgroundSnapshot.ts ] && echo "yes" || echo "no"
echo

echo "=== file contents (first 400 lines) ==="
sed -n '1,400p' packages/react/runtime/src/backgroundSnapshot.ts || true
echo

echo "=== lines with 'Background' in that file ==="
rg -n "Background" packages/react/runtime/src/backgroundSnapshot.ts || true
echo

echo "=== repo search: BackgroundDOM ==="
rg -n --hidden -S "BackgroundDOM" -g '!**/dist/**' || true
echo

echo "=== repo search: BackgroundSnapshotInstance ==="
rg -n --hidden -S "BackgroundSnapshotInstance" -g '!**/dist/**' || true
echo

echo "=== grep fallback: BackgroundDOM ==="
grep -RIn "BackgroundDOM" . --exclude-dir=dist || true
echo

echo "=== grep fallback: BackgroundSnapshotInstance ==="
grep -RIn "BackgroundSnapshotInstance" . --exclude-dir=dist || true

Length of output: 23717


Replace BackgroundSnapshotInstance with BackgroundDOM in packages/react/runtime/types/internal-preact.d.ts

internal-preact.d.ts still imports/uses BackgroundSnapshotInstance; src exports BackgroundDOM — update the import and the vnode typing:

-import type { BackgroundSnapshotInstance, setupDom } from '../src/backgroundSnapshot.ts';
+import type { BackgroundDOM, setupDom } from '../src/backgroundSnapshot.js';
@@
-    /** vnode */
-    vnode?(vnode: VNode & BackgroundSnapshotInstance): void;
+    /** vnode */
+    vnode?(vnode: VNode & BackgroundDOM): void;
     setupDom?: typeof setupDom;

If your build expects extensionless imports in .d.ts use '../src/backgroundSnapshot' instead of '.js'.

  • Also search for and update remaining references to BackgroundSnapshotInstance (e.g. tests under packages/react/runtime/test/*).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { BackgroundSnapshotInstance, setupDom } from '../src/backgroundSnapshot.ts';
import type { BackgroundDOM, setupDom } from '../src/backgroundSnapshot.js';
/** vnode */
vnode?(vnode: VNode & BackgroundDOM): void;
setupDom?: typeof setupDom;
🤖 Prompt for AI Agents
In packages/react/runtime/types/internal-preact.d.ts around lines 5 to 6, the
file imports BackgroundSnapshotInstance from '../src/backgroundSnapshot.ts' but
the source exports BackgroundDOM; update the import to import type {
BackgroundDOM, setupDom } from '../src/backgroundSnapshot' (use the
extensionless path), replace all type occurrences of BackgroundSnapshotInstance
with BackgroundDOM (including the vnode typing), and then search the repo (e.g.
packages/react/runtime/__test__/*) for remaining references to
BackgroundSnapshotInstance and update them to BackgroundDOM.

backgroundSnapshotInstanceManager.clear();
backgroundSnapshotInstanceManager.nextId = 0;
target.__root = new BackgroundSnapshotInstance('root');
target.__root = setupDom({ type: 'root' });
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.

⚠️ Potential issue

Remove remaining options.document usage and dead document shim

PR goal is to drop options.document. This file still sets it on thread switches and keeps a document shim; that’s inconsistent and can cause leakage across threads.

Apply:

@@
-  function setupDocument(document) {
-    document.createElement = function(type) {
-      return new SnapshotInstance(type);
-    };
-    document.createElementNS = function(_ns, type) {
-      return new SnapshotInstance(type);
-    };
-    document.createTextNode = function(text) {
-      const i = new SnapshotInstance(null);
-      i.setAttribute(0, text);
-      Object.defineProperty(i, 'data', {
-        set(v) {
-          i.setAttribute(0, v);
-        },
-      });
-      return i;
-    };
-    return document;
-  }
-
-  target._document = setupDocument({});
+  // Removed legacy document shim; main-thread tests should use SnapshotInstance directly.
@@
 globalThis.onSwitchedToMainThread = () => {
   if (onSwitchedToMainThread) {
     onSwitchedToMainThread();
   }
   setRoot(globalThis.__root);
-  options.document = globalThis._document;
 };
@@
 globalThis.onSwitchedToBackgroundThread = () => {
   if (onSwitchedToBackgroundThread) {
     onSwitchedToBackgroundThread();
   }
   setRoot(globalThis.__root);
-  options.document = globalThis._document;
 };

And drop the unused import if options is only used there.

-import { options } from 'preact';
+// import { options } from 'preact'; // no longer needed

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/react/testing-library/src/vitest-global-setup.js around line 90, the
code still assigns options.document (target.__root = setupDom({ type: 'root' })
implies a document shim is being set on thread switches) which reintroduces the
removed options.document API and keeps a dead document shim; remove the
assignment that sets options.document/document shim during thread switches,
delete the document shim code path entirely, and remove any imports that become
unused if options is only referenced for that assignment so no document state is
leaked across threads.

💡 Verification agent

🧩 Analysis chain

Verify no other references to options.document remain

Ensure full removal across repo.


🏁 Script executed:

#!/bin/bash
rg -n "options\.document" -g '!**/dist/**' -g '!**/build/**'

Length of output: 148


🏁 Script executed:

#!/bin/bash
# Search for any remaining references to options.document across the repo, excluding dist and build folders
rg -n "options\.document" -g '*' -g '!**/dist/**' -g '!**/build/**'

Length of output: 209


Remove remaining options.document references

packages/react/testing-library/src/vitest-global-setup.js — lines 133 and 141 contain options.document = globalThis._document; remove or update these assignments so options.document is fully removed across the repo.

🤖 Prompt for AI Agents
In packages/react/testing-library/src/vitest-global-setup.js around lines 90,
133 and 141, remove the leftover assignments that set options.document =
globalThis._document and any other references to options.document; update calls
and the options object so document is no longer passed or expected (e.g., stop
assigning or reading options.document, and remove related defaulting logic), and
ensure setupDom invocations and any downstream consumers use the global document
or explicitly passed nodes instead; run tests to confirm no remaining references
to options.document exist.

@upupming upupming force-pushed the feat/simple-bsi branch 2 times, most recently from 7818564 to ad4c7d2 Compare September 13, 2025 09:18
@github-actions
Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. If this pull request is still relevant, please leave any comment (for example, "bump").

@github-actions github-actions Bot added the stale Inactive for 30 days. Will be closed by bots. label Nov 14, 2025
@cla-assistant
Copy link
Copy Markdown

cla-assistant Bot commented Mar 19, 2026

CLA assistant check
All committers have signed the CLA.

@upupming upupming force-pushed the feat/simple-bsi branch 2 times, most recently from ba4ea2d to 3c752c1 Compare March 19, 2026 10:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

framework:React stale Inactive for 30 days. Will be closed by bots.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant