fix(ratewise): persisted 欄位 hydration 暴露面盤點與 Settings 同構破口修復(#666) - #692
Merged
s123104 merged 1 commit intoJul 8, 2026
Merged
Conversation
- Settings 套用 issue 664 同款 two-pass gate:variant、rateMode、theme、customPrimary、 splash 首幀恆用 SSG 預設,layout effect 於 paint 前切回 persisted 偏好,remount 不重演 - 修復 converterStore onRehydrateStorage TDZ 死路:同步 storage 下後置回呼於 create() 內觸發時模組綁定仍在 TDZ,改用回呼參數 state 走閉包,wave-A 遷移與 sanitize 恢復生效 - 新增 Settings 首幀契約測試(client-only 首幀取樣,壞版突變必紅)與 converterStore rehydrate 契約測試(模組綁定壞版必紅) - 新增 e2e settings-hydration.spec.ts:三組高風險 persisted 組合冷載 console 零錯誤 測試:vitest run 全綠(9331)、settings-hydration 與 converter-v2 e2e 34 passed、typecheck 過 Co-authored-by: Cursor <cursoragent@cursor.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Contributor
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Contributor
|
✅ SEO 審計通過!所有 2026 標準驗證項目都符合要求。
|
Contributor
Author
|
PM 收棒補註(審查 4 條文件級 Should-fix 歸檔):1) 盤點表漏列 december-theme 獨立 key——併入 #666 關閉留言補記;2) 補救結論如實記錄:legacy key 未刪、wave-A 偏好於修復後冷載可自動找回(遷移重新生效);3) /multi /favorites 豁免依據為本地旗標非 store hasHydrated,自洽成立;4) TDZ 存在於 production main 約 4 個月(2026-03-08 起)——main hotfix 決策已另立 P1 issue 待使用者裁決。 |
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 #666
摘要
依 issue #666 完成三件事:
ratewise-converterpartialize 欄位+5 個獨立 localStorage key × 預渲染頁 × server snapshot × 破口判定。/settings為預渲染頁,APP_ONLY_PRERENDER_PATHS)。converterStore的onRehydrateStorage後置回呼經模組綁定useConverterStore.getState()呼叫——localStorage 為同步 storage,回呼於create()內同步觸發,此刻模組綁定仍在 TDZ,ReferenceError被 persist middleware 靜默吞掉:wave-A 遷移與 sanitize 於冷載全滅、persist.hasHydrated()恆 false(真瀏覽器 production build 實證:wave-A key 冷載後偏好未併入、legacy key 未刪)。改用回呼參數state(閉包 get/set)修復。盤點表(欄位 × 預渲染頁消費點 × server snapshot × 破口判定)
判定基準:React uSES 於 hydration render 恆讀
getServerSnapshot(zustand hook 的 server snapshot=getInitialState=預設值),故 hydration render 天然安全;破口類別為 client render 路徑(hydration de-opt/早期更新 #423/SSG fallback guard)首幀直讀 client snapshot,輸出偏離 SSG HTML 即 #418 同族。zustand
ratewise-converter(partialize 13 欄)lastConverterView/(RememberedHomeRoute redirect)'single'singleConverterVariant/(SingleConverter 分流);/settings(aria-pressed+說明文字+override badge)'legacy'/已修(#664 gate);/settings破口 → 本 PR 修fromCurrency/(converter props)'TWD'toCurrency/(converter props)'JPY'rateMode/(換算數值);/settings(aria-pressed+說明文字)'auto'/settings破口 → 本 PR 修;/見附註 ①rateType/(RateSelector pills+數值)'spot'rateSource/(來源 badge+數值)'bank'providerPreference/(effectiveRateSource 推導)favorites/(FavoritesList,md+);/favorites/favorites豁免(isHydrated skeleton gate:SSG 與 client 首幀同為 skeleton);/見附註 ①history/favorites[]baseCurrency/multi(multiAmounts initializer 讀getState())'TWD'/multiisHydrated skeleton gate;首頁不渲染 multi 元素)cardRateEnabled/(uSES → hasCardRate pill)falsegetCardRateServerSnapshot;Phase 1 無 UI 寫入路徑(僅 URL override,不持久化),persisted true 僅能手改 localStorage;殘餘面同附註 ①cardFeePercent/(CardEstimateInfo,僅 hasCardRate=true 渲染)1.5cardRateEnabled零暴露 gate 連動,SSG 恆不渲染 card UI獨立 localStorage key(
/settings消費)ratewise-theme.style/settings(7 風格卡 aria-pressed/ring)'zen'<html data-style>由 index.html pre-paint bootstrap 處理,非 hydration 比對範圍)ratewise-theme.customPrimary/settings(自訂卡 ring/裝飾圓/勾選色)#3182F6ratewise-theme.customBackgroundTone/settings(僅 CustomThemeSheet 開啟後消費)'pure'ratewise-splash-enabled/settings(switch aria-checked)trueratewise-language'zh-TW'(i18n 固定 init)changeLanguage,首幀恆 zh-TW=SSG統計:盤點 18 欄位;破口 6(Settings 5 個消費點+1 個 TDZ 系統性缺陷);修復 6;豁免 12(均附理由)。
修復點清單
apps/ratewise/src/pages/Settings.tsx:two-pass gate(比照 fix(ratewise): 修復 persisted v2 冷載觸發 React #418 hydration mismatch #664 慣例)——variant/rateMode/style/customPrimary/splash顯示值一律用 SSG 預設;override badge 的 uSES client snapshot 在hydrated=false時固定走 server snapshot。apps/ratewise/src/stores/converterStore.ts:onRehydrateStorage回呼改用參數state(閉包 get/set),修復 TDZ 死路——wave-A 遷移、legacy key 清理、sanitize、hasHydrated()冷載恢復生效。合約測試清單(壞版必紅實證)
Settings.first-frame.test.tsxhydrated初值 true)3 紅(實測)onRecoverableError零錯誤+窗口內 store 更新(#653 情境)hasHydrated=true)converterStore.rehydrate.test.ts宣稱範圍(誠實標注,同 #664):jsdom 中 React hydration render 對 uSES 恆讀 server snapshot、store 訂閱 commit 後才建立,壞版無法以 hydration 錯誤重演;壞版鑑別由 client-only 首幀取樣承擔,runtime mismatch 防回歸由 e2e 承擔。
E2E(3 組高風險 persisted 組合冷載,chromium desktop+mobile)
singleConverterVariant=v2+rateMode=sell(issue 核心破口)ratewise-themestyle=nitro+customPrimaryaria-pressed恆 false——修復前後對照實證)驗證
pnpm --filter @app/ratewise exec vitest run:195 檔 9328 passed(3 skipped)settings-hydration.spec.ts+converter-v2.spec.ts:34 passed(desktop+mobile)/settings輸出契約:legacy/zen/auto/splash-on 全為預設、無 override badge(build 後實測)Made with Cursor