fix(ratewise): 修復 persisted v2 冷載觸發 React #418 hydration mismatch - #664
Merged
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…e 653) - SingleConverter 改 two-pass render:hydration commit 前 client snapshot 一律沿用 server snapshot(legacy),與 SSG 輸出一致,杜絕 rehydrate/遷移期間強制 client render 的 418 - commit 後以 layout effect(paint 前)切換讀取 persisted 偏好;base 的 uSES 一致性切換為 passive effect paint 後,本修法切換更早且無多餘可見 legacy 幀;SPA remount 首幀直接依偏好渲染 - converter-v2-flag.ts SSOT 未動,flag off 零暴露行為不變 - 契約測試(審查 R1 強化):hydration 錯誤改以 hydrateRoot onRecoverableError 收集; 新增 client-only 首幀契約——以 callback ref 於首次 commit layout 階段取樣, base 還原版與 hydrated 初值 true 壞版均實證必紅,修法版綠 - jsdom 無法以 hydration 錯誤鑑別壞版(uSES hydration render 恆讀 getServerSnapshot、 訂閱 commit 後才建立),runtime mismatch 防回歸由 converter-v2 e2e 冷載 console 斷言承擔 測試:vitest 全套 4859 passed;converter-v2 e2e 16 passed;SSG 255 頁正規化零 diff; Playwright persisted v2 冷載(含舊 profile 遷移、CPU/網路節流、SW 受控)console 零 418 Co-authored-by: Cursor <cursoragent@cursor.com>
s123104
force-pushed
the
fix/653-v2-hydration
branch
from
July 7, 2026 00:56
b22254e to
7c0c992
Compare
Contributor
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Contributor
|
✅ SEO 審計通過!所有 2026 標準驗證項目都符合要求。
|
This was referenced Jul 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #653(refs #666:Settings 頁
getConverterV2Variant同構破口由 PM 另立 issue 追蹤,本 PR 不擴 scope)根因
persisted
singleConverterVariant: v2(zustandratewise-converter)使SingleConverter的useSyncExternalStoreclient snapshot 在 hydration 期間即回傳 v2。converterStore rehydrate/__migrateFromLegacy遷移在 hydration 窗口內觸發 store 更新時,React 會對尚未完成 hydration 的邊界強制改走 client render——此時 client 內容為 v2,與 SSG snapshot(一律 legacy)不符 → React #418(間歇、舊 profile 因遷移路徑必現)。Two-pass render 設計
hydrated=false時,uSES 的 client snapshot 一律沿用getConverterV2ServerSnapshot(legacy)。hydration 期間任何強制 re-render 的輸出都與 SSG HTML 一致,結構性杜絕 chore(seo): 每日更新匯差範例數據 #418 破口。useLayoutEffect(isomorphic,對齊RememberedHomeRoute/LanguagePreferenceSync同族慣例)設hydrated=true,uSES 改讀 persisted 偏好切 v2。時序精確化:base 的 uSES 一致性切換由 passive effect 於 paint 後觸發,本修法 layout effect 於 paint 前——切換更早,無多餘可見 legacy 幀。converter-v2-flag.tsSSOT 未修改;flag off 零暴露行為不變(v2 chunk 不載入)。單元測試宣稱範圍與鑑別力(審查 R1 修正)
jsdom 不可重演 hydration 錯誤的證據(結構+實測):React 的 uSES 在 hydration render 一律讀取
getServerSnapshot(修法版/壞版共用常數 legacy),且 store 訂閱在 commit 後才建立——hydration 窗口內的 store 更新(本測試已於hydrateRoot同一 act 窗口內模擬 legacy→v2 set)不構成 React 可見事件。實測壞版 A(base 還原)/壞版 C(hydrated初值 true)在 hydration 測試下onRecoverableError均收 0 筆,無法以 hydration 錯誤鑑別。runtime mismatch 防回歸由 e2econverter-v2.spec.ts「設 v2 冷啟動重載」(寫入 persisted v2 → reload → console error 斷言為空)承擔。壞版鑑別(實證必紅):新增「client-only 首幀契約」測試——真正的破口在 client render 路徑(hydration de-opt/早期更新 #423/SSG fallback guard 清 root 後重渲染),該路徑首次 render 直讀 client snapshot。以 callback ref 於首次 commit 的 layout 階段(layout effect 切換 re-render 之前)同步取樣 DOM:
legacy)expected [ 'v2' ] to deeply equal [ 'legacy' ]hydrated初值 true)expected [ 'v2' ] to deeply equal [ 'legacy' ]hydration 錯誤收集同步改用
hydrateRoot的onRecoverableErrorcallback(正確 API 鉤點;jsdom+React 19 的 mismatch 不走console.error,原 console spy 為空斷言已移除)。驗收證據
persisted-v2與old-profile-v2(wave-A legacy key 觸發遷移)各 6 輪、含 CPU 6x+Fast 3G 節流與 SW 受控重載情境,console error 全程為空renderToString單元測試斷言 persisted v2 下 SSG 輸出仍為 legacyscreenshots/653-cold-load-frame-*.png):SSG 靜態 legacy HTML → v2 骨架 → v2,幀序與 base 分支對照組完全一致(frame 0 的 legacy 為 SSG 靜態 HTML 本身,hydration 前即存在,非本修法引入;paint 前切換不新增 legacy 幀)vitest run全套 184 檔 4859 passed(rebase 後含 #663)converter-v2.spec.ts16 passed(rebase 後 dist 重建重跑)node scripts/verify-002-log.mjs通過)與 pre-push(typecheck+test+build:ratewise)全過變更檔案
apps/ratewise/src/features/ratewise/components/SingleConverter.tsx:two-pass gate(唯一行為變更點)apps/ratewise/src/features/ratewise/components/__tests__/SingleConverter.flag.test.tsx:hydration 契約+client-only 首幀契約測試.changeset/persisted-v2-hydration-418.md:patchdocs/dev/002_development_reward_penalty_log.md:+1 → 累計 +178(rebase 後依 base +177 重算)