Skip to content

fix: preserve hydration and suspended error recovery contracts - #43

Open
tannerlinsley wants to merge 3 commits into
mainfrom
taren/fix-hydration-and-suspense-recovery
Open

tannerlinsley wants to merge 3 commits into
mainfrom
taren/fix-hydration-and-suspense-recovery

Conversation

@tannerlinsley

@tannerlinsley tannerlinsley commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Hydration and suspended error recovery can alter already-correct DOM, trigger resource loads, suppress application scrolling, or render retained subscribers against stale props. This ports the fixes with standalone regressions using public React APIs.

  • Preserve case-sensitive SVG attributes through SSR, hydration, and updates.
  • Normalize hydration attributes in an inert document so validation does not fetch lazy images or construct custom elements.
  • Preserve matching opaque head content, while retaining mismatch recovery and suppression policies.
  • Remove the global hydration scrollTo wrapper so the application owns scroll restoration.
  • Queue document-root deletion before placement, while keeping mutations transactional.
  • Propagate errors thrown by a fallback to an ancestor, abandon stale captured errors after resets, and reconnect catch lifecycles in the right order after Suspense.
  • Retry already-queued retained subscribers through their Suspense boundary.
  • Keep noscript fallback markup opaque during hydration and skip client fallback children, preserving adjacent interactive DOM.

Validation

The original hydration/Suspense baseline passed the full local CI gate and 14 Chromium regressions. That evidence predates the noscript follow-up and is not a claim that the latest commit has completed every check. Shared-contract cases were checked against React, and the measurement report identifies Redact-specific policies excluded from that control.

For the noscript follow-up, CI run 38592959191 passed 1,653 unit tests, TypeScript, and build/export verification, then stopped at the size gate before Chromium ran. Local runtime tests were not rerun. The follow-up updates only exceeded ceilings to CI-measured maxima, with no added margin. The added regressions cover raw-text fallback retention, neighboring button identity and interactions, and skipping fallback components on client mount/update. The latest CI run is the remaining validation gate.

The full size table and reproduction commands are in benchmarks/HYDRATION_SUSPENSE_RECOVERY.md. The original baseline measured built combined client 28,060 to 28,634 gzip bytes and default Vite client 23,232 to 23,814. Follow-up CI measured built combined client at 28,668 bytes, 34 bytes over its previous ceiling, and default Vite client at 23,768, within its existing ceiling. This is a correctness cost, not a performance improvement claim.

Includes a patch changeset. No generated bundles or source maps are committed.

Summary by CodeRabbit

  • Bug Fixes
    • Improved hydration recovery for SVG attribute casing, document roots, and mismatched markup while preserving matching styles and existing page content.
    • Reduced unintended side effects when checking custom elements and lazy images during hydration.
    • Improved recovery when Suspense, error fallbacks, or external-store updates occur during rendering.
    • Preserved normal scroll behavior during hydration and kept noscript content unchanged.
  • Testing
    • Added browser-based regression coverage for hydration and recovery scenarios, including error fallbacks and scroll behavior.

@changeset-bot

changeset-bot Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4880924

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

This PR includes changesets to release 1 package
Name Type
@tanstack/redact 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

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f1932c2d-a99b-4a7a-953c-9d9e7416f5d4
📥 Commits

Reviewing files that changed from the base of the PR and between b1c945f and 4880924.

📒 Files selected for processing: 2
  • benchmarks/HYDRATION_SUSPENSE_RECOVERY.md
  • scripts/size-check.mjs
🚧 Files skipped from review as they are similar to previous changes: 1
  • benchmarks/HYDRATION_SUSPENSE_RECOVERY.md

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 0 remain after this review.

📝 Walkthrough

The PR changes SVG attribute handling, hydration validation and DOM adoption, and Suspense and error-boundary recovery. It adds regression tests, configures browser test execution, updates bundle-size budgets, and records a patch changeset.

Changes: Hydration and recovery

Layer / File(s) Summary
Hydration validation and DOM adoption
packages/redact/src/core/attributes.ts, packages/redact/src/dom/features/hydration/*, packages/redact/src/dom/reconcile.ts, tests/hydration-*.test.tsx
Hydration preserves listed SVG attribute casing and uses inert documents for validation probes. DOM adoption uses the validation result when recovering mismatched opaque HTML. The temporary scroll guard is removed. Client rendering and hydration treat noscript content as opaque. Tests cover SVG, custom elements, head styles, lazy images, opaque markup, and scroll ownership.
Suspense and error-boundary retries
packages/redact/src/dom/reconcile.ts, packages/redact/src/dom/features/class/full.ts, packages/redact/src/dom/features/suspense/full.ts, tests/error-fallback-recovery.test.tsx, tests/retained-store-departure.test.tsx
Reconciliation updates retained Suspense retries and child ordering. Error-boundary fallback rendering, capture reset handling, and deferred lifecycle callbacks are updated. Tests cover suspended fallbacks, reset props, document-root recovery, and queued store notifications.
Regression execution and release records
package.json, scripts/hydration-recovery*.config.mjs, .github/workflows/pr.yml, benchmarks/HYDRATION_SUSPENSE_RECOVERY.md, scripts/size-check.mjs, .changeset/calm-hydration-recovery.md
A browser recovery test script and CI steps are added. The benchmark records size measurements and regression commands. Size budgets and the patch changeset are updated.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant Hydration
  participant DOMAdoption
  participant PropValidation
  participant InertProbeDocument
  Hydration->>DOMAdoption: Adopt server host DOM
  DOMAdoption->>PropValidation: Validate host props
  PropValidation->>InertProbeDocument: Create validation probes
  PropValidation-->>DOMAdoption: Return recovery flag
  DOMAdoption-->>Hydration: Sync props according to validation result
Loading
Merge Risk: ⚪ Minimal · up to 48809

No confirmed issue blocks merging; complete the latest CI validation before release.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 5.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 35 functions across 12 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely summarizes the pull request's main changes to hydration and suspended error recovery behavior.
Full details: Docstring Coverage

Explanation

Docstring coverage is 5.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 35 functions across 12 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant