why-hydration 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,81 @@
1
+ # Changelog
2
+
3
+ ## 0.1.3
4
+
5
+ Stability + accuracy overhaul from running on a large production Next.js app.
6
+ Detection is now **deterministic** and reports **every** mismatch at once.
7
+
8
+ - **LCS child alignment.** Children are matched by an LCS (tag+id) instead of by
9
+ index, so a node the client injects mid-tree (react-toastify's
10
+ `<section class="Toastify">`, a portal, a modal, an ad) is treated as an
11
+ insertion — it no longer shifts every sibling and cascade into a different set
12
+ of false positives on each refresh. This makes results **consistent across
13
+ refreshes**.
14
+ - **Collect all mismatches.** The diff now returns every divergence on the page
15
+ (deduped by value), so the overlay shows them together instead of one-per-
16
+ refresh. The panel is scrollable and shows a "↓ N issues — scroll to see all"
17
+ hint (with a ✕ close button) when there are more than fit.
18
+ - **Skip client-injected containers** — toasts, modals, portals, overlays,
19
+ tooltips, consent banners, chat/analytics widgets (by class/role/`aria-live`)
20
+ are never reported and never mask a real mismatch.
21
+ - **Skip pending Suspense fallbacks** — content inside a streaming `<!--$?-->`
22
+ boundary (server `Loading…` vs client content) is expected, not a mismatch.
23
+ - **No duplicates** — value-based dedup means the same mismatch (e.g. two links
24
+ with the same conditional class) appears once.
25
+ - **All class/style mismatches captured.** React can emit several changes in one
26
+ hydration message; the parser now extracts all of them (not just the first).
27
+ - Adjacent delete+insert is coalesced into a single `structure` report.
28
+ - License: removed MIT (now `UNLICENSED`); added the author's LinkedIn.
29
+
30
+ ## 0.1.2
31
+
32
+ Accuracy overhaul from running on a large production Next.js app — fixes real
33
+ false positives and misclassifications.
34
+
35
+ - **New `attribute-mismatch` category.** A `class`/`style` difference (e.g. a
36
+ conditional `forceHide` class) is now reported correctly with the exact
37
+ changed tokens — previously misclassified as "locale-format" because the class
38
+ list contained digits.
39
+ - **Component name + source file:line.** Reports now name the offending
40
+ component (e.g. `<PriceTag>`) and, where React exposes it, the source
41
+ `file:line`, read from React's fiber and its hydration diff.
42
+ - **Modern React message parsing.** React 18.3+/19 print a JSX diff tree
43
+ (`+`/`-` lines) instead of "Prop X did not match"; the parser now understands
44
+ it and extracts the attribute/value and component. This is required because
45
+ React does **not** patch mismatched attributes, so the DOM diff alone can't
46
+ see them.
47
+ - **Third-party noise is skipped.** Hidden ads/consent/analytics iframes (e.g.
48
+ Google Funding Choices `googlefcInactive`, `about:blank`) no longer produce
49
+ false "browser-only API" reports and no longer mask your real mismatch.
50
+ - **Stricter locale detection** — number-format matching now requires actual
51
+ numeric values, so class lists / ids with digits no longer false-match.
52
+ - **Reliable timing.** Detection re-checks across a short settling window
53
+ because React applies client values to mismatched subtrees a few hundred ms
54
+ after hydration; value-based dedup prevents double reports.
55
+ - Docs: detection scope (full load vs client navigation), `attribute-mismatch`
56
+ category, refreshed screenshots showing component names.
57
+
58
+ ## 0.1.1
59
+
60
+ Fixes found by end-to-end testing in a real Next.js app (App Router + Pages
61
+ Router, React 18 & 19). **0.1.0 is broken in Next.js — use 0.1.1 or later.**
62
+
63
+ - **`'use client'` directive** is now injected into the `react` and `next`
64
+ bundles. Without it, `<HydrationInspector>` crashed in a Server Component
65
+ layout (`useRef only works in Client Components`). `why-hydration/next` is now
66
+ client-only; import `HydrationSnapshotScript` from `why-hydration/next/script`.
67
+ - **Diff skips framework comment markers** (React `<!--$-->`, RSC payload) that
68
+ were reported as false `viewport-branching` mismatches.
69
+ - **Inline `style` is normalized through the CSSOM**, so the server snapshot and
70
+ the browser-normalized live DOM (`#hex` → `rgb()`, spacing) no longer produce
71
+ a false `unknown` mismatch.
72
+ - **`typesVersions`** added so subpath types resolve under `moduleResolution:
73
+ "node"` (fixes `next build` type errors in apps using classic resolution).
74
+
75
+ Verified live: locale-format, non-deterministic-value, date-time, and
76
+ browser-only-api classify correctly in App Router and Pages Router; the tool is
77
+ a zero-code no-op in a production `next build`.
78
+
79
+ ## 0.1.0
80
+
81
+ Initial release.
package/README.md CHANGED
@@ -3,14 +3,18 @@
3
3
  [![npm version](https://img.shields.io/npm/v/why-hydration.svg)](https://www.npmjs.com/package/why-hydration)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/why-hydration.svg)](https://www.npmjs.com/package/why-hydration)
5
5
  [![minzipped size](https://img.shields.io/bundlephobia/minzip/why-hydration.svg)](https://bundlephobia.com/package/why-hydration)
6
- [![license: MIT](https://img.shields.io/npm/l/why-hydration.svg)](LICENSE)
7
6
 
8
7
  📦 **npm:** https://www.npmjs.com/package/why-hydration
9
8
 
10
9
  **Tells you which component broke hydration, what differed, and how to fix it — in dev, with zero production cost.**
11
10
 
11
+ A **React hydration error / hydration mismatch debugger** for **Next.js** (App
12
+ Router & Pages Router), **Remix**, **Vite**, and any React SSR app. It points at
13
+ the exact **component, file, and value** behind "Hydration failed because the
14
+ server rendered HTML didn't match the client" — and tells you how to fix it.
15
+
12
16
  <p align="center">
13
- <img src="docs/screenshots/overlay-hero.png" alt="why-hydration overlay showing three classified hydration mismatches" width="440">
17
+ <img src="docs/screenshots/overlay-hero.png" alt="why-hydration overlay showing classified React hydration mismatches in a Next.js app with component names" width="440">
14
18
  </p>
15
19
 
16
20
  React's hydration warnings tell you _that_ something mismatched, rarely _which
@@ -28,10 +32,13 @@ the client DOM, classifies the root cause, and hands you a specific fix.
28
32
  or format the value after mount.
29
33
  ```
30
34
 
31
- - 🔍 **Where** — component (best-effort) + exact DOM node + selector path.
35
+ - 🔍 **Where** — the **component name** (e.g. `<PriceTag>`) and **source file:line**,
36
+ read from React's own fiber/diff, plus the exact DOM node + selector path.
32
37
  - 🔀 **What** — server value vs client value, side by side.
33
38
  - 🧠 **Why** — the cause, classified into a known category with a confidence score.
34
39
  - 🛠️ **Fix** — a specific, actionable suggestion with a docs link.
40
+ - 🧹 **Low noise** — skips third-party/extension injections (ads, consent, chat,
41
+ Grammarly) so you see _your_ bug, not their DOM.
35
42
  - 🫧 **Zero prod cost** — everything is gated on `process.env.NODE_ENV` and
36
43
  tree-shakes to a **no-op** (~0 B, size-budgeted in CI).
37
44
  - 🌐 **Arabic-first** — digit-script mismatches (٠١٢ vs 012) are a first-class cause.
@@ -156,6 +163,25 @@ A clean page with no mismatches shows **nothing** — no overlay, no logs.
156
163
 
157
164
  ---
158
165
 
166
+ ## When it detects (full load vs client navigation)
167
+
168
+ Hydration mismatches happen **only during the initial server render + hydration**
169
+ — i.e. a **full page load, refresh, or direct URL entry** to an SSR'd route.
170
+ `why-hydration` detects those.
171
+
172
+ **Client-side navigation** (Next.js `<Link>` / `router.push`, React Router) does
173
+ **not** re-hydrate — the destination is rendered entirely on the client, so there
174
+ is no server HTML to diverge from and **no hydration mismatch can occur**. This is
175
+ React's design, not a limitation. To reproduce a mismatch on a specific route,
176
+ **refresh that route** (or open its URL directly).
177
+
178
+ Detection covers both React's console warning path and a real server-vs-client
179
+ DOM diff, and re-checks across a short settling window (React applies client
180
+ values to mismatched subtrees a few hundred ms after hydration), so time- and
181
+ render-order-dependent mismatches are caught reliably.
182
+
183
+ ---
184
+
159
185
  ## Options
160
186
 
161
187
  ```tsx
@@ -311,6 +337,19 @@ early third-party script before hydration.
311
337
  third-party init to post-hydration.
312
338
  **Reference:** [React `suppressHydrationWarning`](https://react.dev/reference/react-dom/components/common#suppressing-unavoidable-hydration-mismatch-errors).
313
339
 
340
+ <a id="cause-attribute-mismatch"></a>
341
+
342
+ ### attribute-mismatch
343
+
344
+ A `class`, `style`, or other attribute differs between server and client — the
345
+ report lists the exact tokens (e.g. _added on client: `forceHide`_). Usually a
346
+ class/style applied by a client-only conditional (viewport, media query, theme,
347
+ or feature flag) during the first render. React does **not** patch mismatched
348
+ attributes, so `why-hydration` reads these from React's own diff.
349
+ **Fix:** render the same attribute on the server and the first client paint —
350
+ move the condition into `useEffect`/a mounted flag, or use CSS media queries.
351
+ **Reference:** [React — different client/server content](https://react.dev/reference/react-dom/client/hydrateRoot#handling-different-client-and-server-content).
352
+
314
353
  <a id="cause-unknown"></a>
315
354
 
316
355
  ### unknown
@@ -388,14 +427,24 @@ field in `package.json` to match your URL.
388
427
  **Can I send reports to my logging?** Yes — pass `onReport`; you receive the full
389
428
  `HydrationReport`.
390
429
 
391
- **Does it slow my app down?** No. It's dev-only, runs the diff once after
392
- hydration (plus once per real React signal), and holds no standing observers.
430
+ **Why do results now show all at once and stay the same on refresh?** The diff
431
+ aligns children with an LCS and collects every mismatch deterministically, so
432
+ injected nodes (toasts, portals, ads) can't shift the comparison and change the
433
+ results between refreshes.
434
+
435
+ **Does it slow my app down?** No. It's dev-only, diffs across a short settling
436
+ window right after hydration (React applies client values a few hundred ms
437
+ later), then stops — no standing observers.
393
438
 
394
439
  ## Contributing
395
440
 
396
441
  Adding a cause category is a self-contained change — see
397
442
  [CONTRIBUTING.md](CONTRIBUTING.md).
398
443
 
399
- ## License
444
+ ## Author
445
+
446
+ Built by **[Razan Aboushi](https://www.linkedin.com/in/razan-aboushi/)** ·
447
+ [GitHub](https://github.com/razan-aboushi) ·
448
+ [LinkedIn](https://www.linkedin.com/in/razan-aboushi/)
400
449
 
401
- [MIT](LICENSE) © [Razan Aboushi](https://github.com/razan-aboushi)
450
+ © 2026 Razan Aboushi. All rights reserved.