why-hydration 0.2.0 → 0.3.0

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 CHANGED
@@ -1,5 +1,71 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Hebrew and Persian overlays, live prop updates, one report per invalid nesting.
8
+
9
+ **Hebrew and Persian**
10
+
11
+ - Pages whose `<html lang>` is Hebrew (`he`, `iw`) or Persian (`fa`, `prs`,
12
+ `pes`) now get the overlay in that language, right-to-left, alongside the
13
+ Arabic one. `overlay={{ locale }}` accepts `'he'` and `'fa'` too. Other
14
+ right-to-left languages, such as Urdu, keep the English left-to-right panel.
15
+
16
+ **`<HydrationInspector>` props apply while the app runs**
17
+
18
+ - Props were read once, at mount, and later changes were ignored until a
19
+ reload. Now a new `onReport` receives the next report, a new `overlay`
20
+ (on/off, `position`, `locale`) rebuilds the panel with the reports so far,
21
+ and new `ignore`, `classify` and `maxReports` apply from then on. Inline
22
+ values re-created on every render do not rebuild anything.
23
+
24
+ **Invalid HTML nesting is one report**
25
+
26
+ - A `<div>` inside a `<p>` used to surface as three wrongly labelled reports
27
+ ("browser-only API", "viewport branching" twice) next to React's own
28
+ warning, and a real text change inside it was never reported as one. The
29
+ browser repairs the server's HTML while parsing it, but React builds the
30
+ client DOM node by node, so the client side is now repaired the same way
31
+ before comparing. The nesting is one report at the misplaced element, and a
32
+ text difference inside it is its own report. Covers block elements in `<p>`,
33
+ nested `<a>`/`<button>`/`<form>`, and table content outside its section or
34
+ row, which the parser moves out of the whole table.
35
+
36
+ **Other fixes**
37
+
38
+ - A `roots` selector that matches nothing on the page — usually a typo — is
39
+ warned about once the settling window closes, instead of being skipped
40
+ silently.
41
+ - Reports no longer show the wrong component. A report parsed from one of
42
+ React's messages borrowed the component of whichever error came last, and
43
+ a text node the client added pointed at nothing, so its lookup fell back
44
+ the same way (a `localStorage` read labelled `<InvalidNesting>`). Each
45
+ report now carries only its own context, and an added text node points at
46
+ its parent element — which also lets `ignore` match it.
47
+ - Adjacent text nodes (`{label}: ` in JSX) are kept apart when the client
48
+ side of an invalid nesting is repaired, matching React's server HTML, so
49
+ they no longer show up as a bogus text change.
50
+ - The Release workflow no longer fails on every merge when no `NPM_TOKEN` is
51
+ set: it skips publishing with a notice and still opens version PRs.
52
+ - A GitHub Pages site (`docs/`) with search metadata, structured data and a
53
+ sitemap. A test keeps its error list identical to the README's.
54
+
55
+ ### Patch Changes
56
+
57
+ - Make the package easier to find.
58
+
59
+ - The README has a "Searching for this error?" section quoting React's
60
+ hydration error messages exactly as React 18 and React 19 print them, so a
61
+ search for the error leads here. A test reads the list back out of the
62
+ README and checks the package recognizes every message on it.
63
+ - The npm description and keywords now cover the terms people search for:
64
+ Next.js, React 18 and 19, "hydration failed", "text content does not match",
65
+ `suppressHydrationWarning`, RTL and Arabic.
66
+
67
+ No code changes to this part.
68
+
3
69
  ## 0.2.0
4
70
 
5
71
  ### Minor Changes
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![node: >=18](https://img.shields.io/node/v/why-hydration)](#install)
8
8
  [![license: MIT](https://img.shields.io/npm/l/why-hydration.svg)](LICENSE)
9
9
 
10
- 📦 **npm:** https://www.npmjs.com/package/why-hydration &nbsp;·&nbsp; 🐙 **GitHub:** https://github.com/razan-aboushi/why-hydration &nbsp;·&nbsp; 💼 **Author:** [Razan Aboushi](https://www.linkedin.com/in/razan-aboushi/)
10
+ 🌐 **Site:** https://razan-aboushi.github.io/why-hydration/ &nbsp;·&nbsp; 📦 **npm:** https://www.npmjs.com/package/why-hydration &nbsp;·&nbsp; 🐙 **GitHub:** https://github.com/razan-aboushi/why-hydration &nbsp;·&nbsp; 💼 **Author:** [Razan Aboushi](https://www.linkedin.com/in/razan-aboushi/)
11
11
 
12
12
  **Tells you which component broke hydration, what differed, and how to fix it — in dev, with zero production cost.**
13
13
 
@@ -60,9 +60,9 @@ together.
60
60
  incidental page noise.
61
61
  - 🌐 **Arabic-first locale detection** — digit-script mismatches (٠١٢ vs 012)
62
62
  are a first-class cause, not an afterthought.
63
- - 🔤 **Speaks Arabic, right-to-left** — on an Arabic page (`<html lang="ar">`)
64
- the overlay is in Arabic and laid out right-to-left; everywhere else it is in
65
- English. Page data renders in its own direction in both, and invisible bidi
63
+ - 🔤 **Speaks Arabic, Hebrew and Persian, right-to-left** — on a page whose
64
+ `<html lang>` is one of those, the overlay is in that language and laid out
65
+ right-to-left; everywhere else it is in English. Page data renders in its own direction in both, and invisible bidi
66
66
  marks are shown instead of hidden.
67
67
  - 🫧 **Zero production cost** — every code path is gated behind
68
68
  `process.env.NODE_ENV`, and CI fails the build if the production bundle for
@@ -79,6 +79,7 @@ Supports **React 18 and 19**.
79
79
  - [Install](#install)
80
80
  - [Quick setup](#quick-setup)
81
81
  - [What is a hydration mismatch](#what-is-a-hydration-mismatch)
82
+ - [Searching for this error?](#searching-for-this-error)
82
83
  - [What why-hydration detects](#what-why-hydration-detects)
83
84
  - [How detection works](#how-detection-works)
84
85
  - [What you will see](#what-you-will-see)
@@ -238,6 +239,37 @@ resolved a different locale or timezone. React logs a warning to the console
238
239
  when it detects this and, depending on what mismatched, may or may not repair
239
240
  the DOM to match. `why-hydration` exists to make that warning actionable.
240
241
 
242
+ ### Searching for this error?
243
+
244
+ If you landed here from one of these messages, this is the tool for it. Each
245
+ is quoted exactly as React prints it, and `why-hydration` recognizes every one.
246
+ With the [snapshot script](#quick-setup) in place it also names the component,
247
+ shows the server and client values side by side — including for the messages
248
+ that carry no values of their own — and tells you the likely cause and fix.
249
+
250
+ **React 19** (and Next.js 15+, which uses it):
251
+
252
+ ```text
253
+ Hydration failed because the server rendered text didn't match the client. As a result this tree will be regenerated on the client.
254
+ Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client.
255
+ A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up.
256
+ In HTML, <div> cannot be a descendant of <p>. This will cause a hydration error.
257
+ ```
258
+
259
+ **React 18** (and Next.js 13–14):
260
+
261
+ ```text
262
+ Hydration failed because the initial UI does not match what was rendered on the server.
263
+ There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.
264
+ There was an error while hydrating this Suspense boundary. Switched to client rendering.
265
+ Text content does not match server-rendered HTML.
266
+ Warning: Text content did not match. Server: "…" Client: "…"
267
+ Warning: Prop `className` did not match. Server: "…" Client: "…"
268
+ Warning: Expected server HTML to contain a matching <div> in <div>.
269
+ Warning: Did not expect server HTML to contain a <div> in <div>.
270
+ Warning: validateDOMNesting(...): <div> cannot appear as a descendant of <p>.
271
+ ```
272
+
241
273
  ---
242
274
 
243
275
  ## What why-hydration detects
@@ -340,7 +372,7 @@ A page with no mismatches renders **nothing** — no overlay, no console output.
340
372
  | **✕** on the hint bar | Closes just the "scroll to see all" hint; the panel stays. |
341
373
  | `overlay={false}` | Never mounts it at all — `onReport` and the console output still work. |
342
374
  | `overlay={{ position }}` | `bottom-right` (default), `bottom-left`, `top-right`, `top-left`. |
343
- | `overlay={{ locale }}` | `'auto'` (default) follows `<html lang>`; `'en'` or `'ar'` pins the language. See [RTL and Arabic support](#rtl-and-arabic-support). |
375
+ | `overlay={{ locale }}` | `'auto'` (default) follows `<html lang>`; `'en'`, `'ar'`, `'he'` or `'fa'` pins the language. See [RTL and Arabic support](#rtl-and-arabic-support). |
344
376
 
345
377
  The overlay is a *view* over the collected reports, not the collector itself:
346
378
  dismissing it does not stop detection, and `onReport` keeps firing. If a
@@ -370,19 +402,27 @@ there are more than fit:
370
402
 
371
403
  ## RTL and Arabic support
372
404
 
373
- An Arabic app gets the same diagnosis quality as an English one. That covers
374
- both how the overlay renders and what the engine can actually detect.
405
+ An Arabic, Hebrew or Persian app gets the same diagnosis quality as an English
406
+ one. That covers both how the overlay renders and what the engine can actually
407
+ detect.
375
408
 
376
409
  ### The overlay
377
410
 
378
411
  **The overlay speaks the page's language.** On a page whose `<html lang>` is
379
- Arabic (`ar`, `ar-SA`, `ar-EG`, …) the whole panel is in Arabic and laid out
412
+ Arabic, Hebrew or Persian, the whole panel is in that language and laid out
380
413
  right-to-left: title, buttons, category names, the explanation, the fix, and
381
- the scroll hint, with correct Arabic plurals. Every other page gets the English
382
- panel — including right-to-left pages in languages the overlay does not
383
- translate, such as Hebrew or Persian, where it stays left-to-right rather than
384
- mirroring English text. Pin it either way with `overlay={{ locale: 'en' }}` or
385
- `overlay={{ locale: 'ar' }}`.
414
+ the scroll hint, with each language's plural rules.
415
+
416
+ | `<html lang>` | Overlay |
417
+ | ------------- | ------- |
418
+ | `ar`, `ar-SA`, `ar-EG`, … and regional varieties (`arz`, `ary`, …) | Arabic |
419
+ | `he`, `he-IL`, and the legacy `iw` | Hebrew |
420
+ | `fa`, `fa-IR`, `fa-AF`, and `prs` (Dari), `pes` | Persian |
421
+ | anything else — including other right-to-left languages such as Urdu | English, left-to-right |
422
+
423
+ An untranslated right-to-left page keeps the English panel left-to-right rather
424
+ than mirroring English text. Pin the language either way with
425
+ `overlay={{ locale: 'en' }}`, `'ar'`, `'he'` or `'fa'`.
386
426
 
387
427
  What stays English: the console output, and `cause.explanation` /
388
428
  `cause.suggestion` in the report `onReport` receives. Those are what people log,
@@ -490,6 +530,14 @@ Wrap your app (or, for Next.js, use the `why-hydration/next` re-export).
490
530
  | `classify` | `Classifier[]` | `[]` | Custom classification rules, run **before** the built-in ones. |
491
531
  | `maxReports` | `number` | `25` | Cap on the number of unique reports collected per page load. |
492
532
 
533
+ Props can change while the app is running, and take effect without a reload:
534
+ a new `onReport` receives the next report; a new `overlay` (switched on or off,
535
+ or a different `position` or `locale`) rebuilds the panel with the reports so
536
+ far; new `ignore`, `classify` or `maxReports` apply to everything reported from
537
+ then on. Reports already made are kept as they were. Inline values that are
538
+ re-created on every render, such as `overlay={{ position: 'top-left' }}`, do
539
+ not rebuild anything unless they actually change.
540
+
493
541
  ### `createHydrationInspector(options)`
494
542
 
495
543
  For Vite/CRA/Remix, where you call `hydrateRoot` yourself. Accepts the same
@@ -520,7 +568,7 @@ effect setup → cleanup → setup.
520
568
  ```ts
521
569
  interface OverlayOptions {
522
570
  position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'; // default 'bottom-right'
523
- locale?: 'auto' | 'en' | 'ar'; // default 'auto' — follows <html lang>
571
+ locale?: 'auto' | 'en' | 'ar' | 'he' | 'fa'; // default 'auto' — follows <html lang>
524
572
  }
525
573
  ```
526
574
 
@@ -695,6 +743,16 @@ JavaScript.
695
743
  A node was moved or ejected because the markup is invalid HTML (e.g. a `<div>`
696
744
  inside a `<p>`, or a nested `<a>`). The browser repairs the server-rendered DOM
697
745
  so it no longer matches what React expects.
746
+
747
+ It is reported **once**, at the element that is misplaced. The browser repairs
748
+ the server's HTML while parsing it, but React builds the client DOM node by
749
+ node, so nothing repairs that side — and compared as-is, one invalid `<div>`
750
+ used to surface as three unrelated reports. The client side is now put through
751
+ the same parser repair before comparing, so the nesting shows up as a single
752
+ report, and any real text difference inside it still shows up as its own.
753
+ Covered: block elements inside `<p>`, nested `<a>`, `<button>` and `<form>`,
754
+ and table content outside its section or row (which the parser moves out of
755
+ the whole table).
698
756
  **Fix:** correct the markup validity — block elements cannot live inside
699
757
  `<p>`, anchors cannot nest, etc.
700
758
  **Reference:** [MDN — `<p>` (permitted content)](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p).
@@ -831,9 +889,11 @@ snapshot script is present in `<head>` and runs before your app's hydration
831
889
  script. Without the snapshot script, the tool still reports mismatches it can
832
890
  parse from React's own console warning, but loses the precise DOM-level diff.
833
891
  If you passed `roots`, check the console for a
834
- `[why-hydration] Skipping root:` warning — a root with no captured server HTML
835
- can never produce a DOM-level report, and it says so rather than failing
836
- silently.
892
+ `[why-hydration] Skipping root:` warning. There are three: a selector that is
893
+ not valid CSS, a root with no captured server HTML (it can never produce a
894
+ DOM-level report), and a selector that matches nothing on the page — usually a
895
+ typo. The last is only raised once the settling window has closed, so a root
896
+ rendered a moment after hydration is not reported by mistake.
837
897
 
838
898
  **Does it work under `<React.StrictMode>`?** Yes. Strict Mode runs every mount
839
899
  effect setup → cleanup → setup in development, which tears the inspector down
@@ -872,14 +932,18 @@ right after hydration, then stops.
872
932
 
873
933
  **Does it work with `<html dir="rtl">`?** Yes — see
874
934
  [RTL and Arabic support](#rtl-and-arabic-support). The panel's language follows
875
- `<html lang>`, not `dir`: an Arabic page gets the Arabic right-to-left panel,
876
- and a right-to-left page in another language gets the English left-to-right
877
- one. Detection covers Arabic-script formatting mismatches on both sides, not
878
- just Arabic-vs-Latin.
935
+ `<html lang>`, not `dir`: an Arabic, Hebrew or Persian page gets the panel in
936
+ that language, right-to-left, and a right-to-left page in another language gets
937
+ the English left-to-right one. Detection covers Arabic-script formatting
938
+ mismatches on both sides, not just Arabic-vs-Latin.
879
939
 
880
940
  **I want the English panel on my Arabic site (or the reverse).** Pass
881
- `overlay={{ locale: 'en' }}` (or `'ar'`). The console and `onReport` are English
882
- either way.
941
+ `overlay={{ locale: 'en' }}` (or `'ar'`, `'he'`, `'fa'`). The console and
942
+ `onReport` are English either way.
943
+
944
+ **I changed a `<HydrationInspector>` prop and nothing happened.** Fixed —
945
+ props used to be read once, at mount. They now apply while the app runs; see
946
+ [`<HydrationInspector>`](#hydrationinspector).
883
947
 
884
948
  **My Arabic app reports a mismatch between two values that look the same.** They
885
949
  differ by invisible bidirectional control characters — `Intl` adds LRM/RLM/isolate