why-hydration 0.1.5 → 0.2.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,55 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - The overlay now speaks Arabic, and renders right-to-left data correctly in any language.
8
+
9
+ **Arabic overlay**
10
+
11
+ - On a page whose `<html lang>` is Arabic, the whole panel is in Arabic and
12
+ laid out right-to-left — title, buttons, category names, explanations, fixes,
13
+ and the scroll hint, with correct Arabic plurals. Every other page, including
14
+ right-to-left pages in languages the overlay does not translate, gets the
15
+ English panel as before.
16
+ - New `overlay={{ locale: 'auto' | 'en' | 'ar' }}` option. `'auto'` is the
17
+ default and follows `<html lang>`.
18
+ - The console output and the `explanation`/`suggestion` strings in `onReport`
19
+ stay English and are byte-identical to 0.1.5. Every built-in cause now also
20
+ carries `messageId` and `params`, which is how the overlay finds the
21
+ translation.
22
+
23
+ **Right-to-left rendering fixes**
24
+
25
+ - Arabic values no longer have their sentence punctuation moved to the wrong
26
+ end. Each value cell takes its direction from its own content, in either
27
+ panel language.
28
+ - Arabic values are drawn in a proportional face instead of a monospace
29
+ fallback, which rendered them with their letters disconnected.
30
+ - Selectors, component names, file paths and attribute names are isolated
31
+ left-to-right, and quoted values inside explanations are isolated one by one,
32
+ so brackets cannot mirror and tokens cannot swap places.
33
+ - The panel declares its own `lang`. Previously an English panel on an Arabic
34
+ page inherited `lang="ar"`, so screen readers voiced it with an Arabic voice.
35
+
36
+ **Other fixes**
37
+
38
+ - Invisible bidi marks (LRM, RLM, isolates…) are drawn as labelled badges in
39
+ the value cells, so the bidi-mark case no longer shows two identical-looking
40
+ values. Value cells also preserve whitespace, so whitespace-only mismatches
41
+ are visible.
42
+ - Component attribution from a React component stack no longer names a DOM tag
43
+ such as `<p>`; it names the nearest component you wrote.
44
+ - React's bare "hydration failed" message no longer adds an empty card next to
45
+ a real mismatch. When it is the only signal it is still reported, with text
46
+ that says React did not name the node, instead of "inspect the values above".
47
+ - React 19 hydration errors no longer produce junk reports. React 19 prints a
48
+ bulleted list of possible causes above its diff tree, and each bullet was
49
+ read as a removed ("server") diff line — five bogus "Unknown" reports per
50
+ mismatch, with the real client value paired against the first bullet. Only
51
+ the tree after React's hydration-mismatch link is read as a diff now.
52
+
3
53
  ## 0.1.5
4
54
 
5
55
  ### Patch Changes
package/README.md CHANGED
@@ -60,9 +60,10 @@ 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
- - 🔤 **RTL-safe overlay** — the overlay always renders left-to-right, even on
64
- pages with `<html dir="rtl">`, since its content (paths, values, code) is
65
- English.
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
66
+ marks are shown instead of hidden.
66
67
  - 🫧 **Zero production cost** — every code path is gated behind
67
68
  `process.env.NODE_ENV`, and CI fails the build if the production bundle for
68
69
  any entry point isn't tree-shaken to a no-op.
@@ -339,6 +340,7 @@ A page with no mismatches renders **nothing** — no overlay, no console output.
339
340
  | **✕** on the hint bar | Closes just the "scroll to see all" hint; the panel stays. |
340
341
  | `overlay={false}` | Never mounts it at all — `onReport` and the console output still work. |
341
342
  | `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). |
342
344
 
343
345
  The overlay is a *view* over the collected reports, not the collector itself:
344
346
  dismissing it does not stop detection, and `onReport` keeps firing. If a
@@ -373,19 +375,47 @@ both how the overlay renders and what the engine can actually detect.
373
375
 
374
376
  ### The overlay
375
377
 
376
- - The overlay's own layout **always renders left-to-right**, on any page. Its
377
- content — file paths, DOM selectors, code values, category names — is
378
- English, so keeping it LTR keeps it readable regardless of the host page's
379
- direction.
380
- - This is automatic and needs no configuration. The overlay lives in an
381
- isolated Shadow DOM, sets `direction: ltr` on both `:host` and the panel, and
382
- carries a `dir="ltr"` attribute as well — belt and braces, because the CSS
383
- `all` shorthand deliberately excludes `direction` (per spec), so
384
- `:host { all: initial }` alone would still let `direction: rtl` leak in and
385
- flip the server/client diff columns.
386
- - Values are rendered as **text**, so Arabic, Hebrew and mixed bidi content
387
- display intact inside the LTR panel without reordering the surrounding
388
- layout.
378
+ **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
380
+ 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' }}`.
386
+
387
+ What stays English: the console output, and `cause.explanation` /
388
+ `cause.suggestion` in the report `onReport` receives. Those are what people log,
389
+ grep and paste into issues, so they do not change with the page. Each report
390
+ also carries `cause.messageId` and `cause.params`, which is how the overlay
391
+ finds the translation — and how your own tooling can, too. The "Learn more"
392
+ links point at this README, which is in English, and say so.
393
+
394
+ **Page data renders in its own direction, in either panel.** A mismatched value
395
+ can be in any script, so each value cell takes its direction from its own first
396
+ strong character. `السعر: ١٬٤٠٠ د.ك.` reads right-to-left with its full stop at
397
+ the end even inside the English panel, and `Price: 1,400 KWD.` reads
398
+ left-to-right inside the Arabic one. Selectors, component names, file paths and
399
+ attribute names are code, so they are always isolated left-to-right — otherwise
400
+ a right-to-left line would mirror their brackets and reorder their segments.
401
+ Class tokens and other values quoted inside a sentence are isolated one by one,
402
+ so two Arabic tokens cannot swap places. Arabic is drawn in a proportional face
403
+ (a monospace fallback renders it with its letters disconnected), and Arabic
404
+ labels are never letter-spaced or uppercased, which breaks their joining.
405
+
406
+ **What differs is what you see.** Value cells preserve whitespace, so a
407
+ whitespace-only mismatch no longer looks like two identical strings. Invisible
408
+ characters that change layout — `LRM`, `RLM`, `ALM`, the bidi embeddings and
409
+ isolates, zero-width space, BOM — are drawn as small labelled badges rather than
410
+ applied, so the [bidi-mark case](#cause-locale-format) shows a visible
411
+ difference instead of two cells that look the same.
412
+
413
+ **It is isolated from the page.** The panel lives in a Shadow DOM, and its
414
+ direction is set inside it, so nothing in the host page's CSS can flip it — the
415
+ `all` shorthand deliberately excludes `direction` (per spec), so
416
+ `:host { all: initial }` alone would not be enough. The panel also declares its
417
+ own `lang`, so a screen reader voices it in its own language: before this, an
418
+ English panel on an Arabic page was read aloud with an Arabic voice.
389
419
 
390
420
  ### Detection
391
421
 
@@ -490,6 +520,7 @@ effect setup → cleanup → setup.
490
520
  ```ts
491
521
  interface OverlayOptions {
492
522
  position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'; // default 'bottom-right'
523
+ locale?: 'auto' | 'en' | 'ar'; // default 'auto' — follows <html lang>
493
524
  }
494
525
  ```
495
526
 
@@ -538,9 +569,11 @@ interface HydrationReport {
538
569
  cause: {
539
570
  category: HydrationCauseCategory; // one of the categories below, or "unknown"
540
571
  confidence: number; // 0–1
541
- explanation: string;
542
- suggestion: string;
572
+ explanation: string; // always English
573
+ suggestion: string; // always English
543
574
  docsUrl?: string;
575
+ messageId?: string; // which message produced the text, e.g. "attribute-mismatch.class"
576
+ params?: Record<string, string | string[]>; // the values in it, e.g. { added: ['wide'], removed: [] }
544
577
  };
545
578
  raw?: { reactMessage?: string }; // the original React console message, if any
546
579
  }
@@ -701,6 +734,13 @@ drive the visual change with a CSS media query instead of a JS class toggle.
701
734
  A mismatch was detected but didn't match any of the rules above. The report
702
735
  still shows the exact server vs. client values and node path so you can
703
736
  diagnose it directly.
737
+
738
+ React also logs a bare "hydration failed" message that names no node at all.
739
+ When the DOM diff or another React message has already located the mismatch,
740
+ that message adds nothing and is not shown. When it is the *only* signal —
741
+ typically because the snapshot script is missing — it is reported once, with an
742
+ explanation that says React did not name the node and a fix that points at the
743
+ snapshot script, rather than asking you to inspect values that do not exist.
704
744
  **Fix:** compare the two values — the cause is usually one of the categories
705
745
  above. If you find a reliable signal for it, add a custom rule via the
706
746
  `classify` option (see [Contributing](#contributing)).
@@ -729,9 +769,12 @@ above. If you find a reliable signal for it, add a custom rule via the
729
769
  budget (`npm run size`) against a real production bundle built with webpack
730
770
  and terser — the same toolchain Next.js and CRA use for production — and
731
771
  fails the build if the tree-shaken output for any entry point isn't reduced
732
- to a near-empty stub. Verified independently against Rollup, which is what
733
- Vite uses for production builds: **99 B** under webpack, **166 B** under
734
- Rollup, versus ~40 KB for the same entry built for development.
772
+ to a near-empty stub. The budgets sit just above today's output, so even a
773
+ small leak fails CI. Verified independently against Rollup, which is what
774
+ Vite uses for production builds: **99 B** minified + gzipped under webpack,
775
+ and **156 B** minified (not gzipped) under Rollup — versus ~59 KB minified
776
+ for the same entry built for development. The Arabic translations exist only
777
+ in that development build.
735
778
  - `<HydrationSnapshotScript>` also renders `null` outside development, so no
736
779
  snapshot script is emitted into your production HTML.
737
780
 
@@ -802,6 +845,11 @@ to the rebuilt inspector rather than lost.
802
845
  suspend `requestAnimationFrame` on hidden pages, so scheduling races a frame
803
846
  against a 50 ms timer and no longer depends on the page being painted.
804
847
 
848
+ **The component name in a report was a tag like `<p>`.** Fixed. React's
849
+ component stacks list host elements first (`at p`, `at span`), and those were
850
+ being taken as the component. The nearest component you wrote is used instead,
851
+ and when there is none the report names no component rather than a tag.
852
+
805
853
  **The "Learn more →" link 404s.** The links point at this README on GitHub
806
854
  (`github.com/razan-aboushi/why-hydration#cause-…`). If you've forked the
807
855
  package under a different name or repository, update `DOCS_BASE` in
@@ -823,16 +871,24 @@ production. It's dev-only, runs a bounded diff across roughly 1.5 seconds
823
871
  right after hydration, then stops.
824
872
 
825
873
  **Does it work with `<html dir="rtl">`?** Yes — see
826
- [RTL and Arabic support](#rtl-and-arabic-support). The overlay stays
827
- left-to-right on purpose; this is not a bug. Detection covers Arabic-script
828
- formatting mismatches on both sides, not just Arabic-vs-Latin.
829
-
830
- **My Arabic app shows two identical-looking values as a mismatch.** They differ
831
- by invisible bidirectional control characters — `Intl` adds LRM/RLM/isolate
874
+ [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.
879
+
880
+ **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.
883
+
884
+ **My Arabic app reports a mismatch between two values that look the same.** They
885
+ differ by invisible bidirectional control characters — `Intl` adds LRM/RLM/isolate
832
886
  marks around numbers and dates in RTL locales, and Node's ICU and the browser's
833
887
  ICU do not always agree on which. The report names this explicitly under
834
- [`locale-format`](#cause-locale-format). Format the value in one place and pass
835
- the string down, or add `suppressHydrationWarning` if the marks are harmless.
888
+ [`locale-format`](#cause-locale-format), and the overlay draws each invisible
889
+ mark as a small labelled badge (`RLM`, `LRM`, …) in the value cell, so you can
890
+ see which side has it. Format the value in one place and pass the string down,
891
+ or add `suppressHydrationWarning` if the marks are harmless.
836
892
 
837
893
  ---
838
894