refactor(web): migrate usePushNotifications off raw localStorage (Item #6 round 8)#1723
Conversation
Replace direct `localStorage.{getItem,setItem,removeItem}` calls with
`safeReadStringLS` / `safeWriteLS` / `safeRemoveLS` for the
`hub_push_subscribed` cache key (1 read + 2 writes + 4 removes total).
Item 6 (localStorage allowlist burndown) round-8 follow-up — drops the
production budget from 15 → 14 (headroom 0). Adds 3 RTL hardening tests
that stub `Storage.prototype.{getItem,setItem,removeItem}` to throw
SecurityError / QuotaExceededError, locking in the no-crash semantics
in Safari Private Mode and on full-quota devices.
Refs: docs/diagnostics/2026-05-03-web-deep-dive/02-architecture-and-state.md §2.2
Co-Authored-By: Ка А <dmytro.s.stakhov@gmail.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThis PR migrates the ChangeslocalStorage Hardening Migration for usePushNotifications
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Review rate limit: 7/10 reviews remaining, refill in 17 minutes. Comment |
⏱️ CI Pipeline Duration ReportBased on the last 50 successful runs on the default branch. Overall Pipeline
Trend (last 20 runs): Per-Job Breakdown
|
Документує round-8 — четверту хвилю follow-up-PR-ів за топ-4 ROI з roadmap-таблиці web deep-dive (Items #6 / #8 / #15 / #16). Той самий паттерн, що в round-6 / round-7. Без зміни roadmap-чисел items. Підсумок раунду 8: - Item #6 — localStorage burndown 15 → 14 (#1723). - Item #8 — useApiForm rollout: ChangePasswordSection (#1724). - Item #15 — noUncheckedIndexedAccess: api-client (#1727). - Item #16 — Storybook 12 → 16 компонентів (#1732). Файли: - 00-overview.md — Update-block для round 8 + 4 рядки в roadmap. - 01-frontend-ergonomics.md — follow-up #3 для Item #8. - 02-architecture-and-state.md — follow-up #3 для #15 + #6. - 04-security-observability-testing-devx.md — follow-up #3 для #16. Co-Authored-By: Ка А <dmytro.s.stakhov@gmail.com>
Документує round-8 — четверту хвилю follow-up-PR-ів за топ-4 ROI з roadmap-таблиці web deep-dive (Items #6 / #8 / #15 / #16). Той самий паттерн, що в round-6 / round-7. Без зміни roadmap-чисел items. Підсумок раунду 8: - Item #6 — localStorage burndown 15 → 14 (#1723). - Item #8 — useApiForm rollout: ChangePasswordSection (#1724). - Item #15 — noUncheckedIndexedAccess: api-client (#1727). - Item #16 — Storybook 12 → 16 компонентів (#1732). Файли: - 00-overview.md — Update-block для round 8 + 4 рядки в roadmap. - 01-frontend-ergonomics.md — follow-up #3 для Item #8. - 02-architecture-and-state.md — follow-up #3 для #15 + #6. - 04-security-observability-testing-devx.md — follow-up #3 для #16. Co-Authored-By: Ка А <dmytro.s.stakhov@gmail.com>
Summary
Web deep-dive Item #6 (localStorage allowlist burndown) round-8 follow-up. Replaces direct
localStorage.{getItem,setItem,removeItem}calls inapps/web/src/shared/hooks/usePushNotifications.tswithsafeReadStringLS/safeWriteLS/safeRemoveLSfor thehub_push_subscribedcache key (1 read + 2 writes + 4 removes total — covers native Capacitor + web push branches symmetrically).Drops the production allowlist budget 15 → 14 (headroom 0). The hook is removed from the
sergeant-design/no-raw-local-storageallowlist ineslint.config.js; budget rationale in.tech-debt/localstorage-allowlist-budget.jsonupdated to cite this round.Also adds 3 RTL hardening tests that stub
Storage.prototype.{getItem,setItem,removeItem}to throwSecurityError/QuotaExceededError, locking in the no-crash semantics in Safari Private Mode and on full-quota devices (the behavior the safe-helpers already guarantee, now with explicit coverage).Governing Skill
sergeant-web-uiPlaybook
docs/diagnostics/2026-05-03-web-deep-dive/02-architecture-and-state.md§2.2) — pattern proven across 3 prior PRs (refactor(web): migrate perf.ts + useDarkMode.ts off raw localStorage (Item 6 follow-up) #1674, feat(web): migrate useActiveFizrukWorkout off raw localStorage (Item 6 follow-up) #1692).Verification
Additional checks:
Docs and Governance
AGENTS.mdneeded an update.Updated docs:
.tech-debt/localstorage-allowlist-budget.json— production: 15 → 14 + rationale.docs/diagnostics/2026-05-03-web-deep-dive/00-overview.mdround-8 summary lands in a separate docs PR after all 4 round-8 follow-ups merge, matching the round-7 sequence.)Risk and Rollout
safe*LShelpers are call-compatible with the previous direct access on success; they additionally swallow throws on Safari Private Mode and quota-exceeded paths instead of crashing the hook on mount or mid-subscribe.localStorageshape is byte-equivalent on the happy path.Hard Rule #15
AGENTS.mdbefore coding.--no-verify.Reviewer Notes
The two
getItempaths feedinguseStateinitializers were collapsed into a singlesafeReadStringLS(PUSH_SUB_KEY) === "1"lazy initializer — the priortry/catchreturnedfalseon any throw, which is exactly whatsafeReadStringLS's default fallback produces, so the observable behavior is identical.Summary by cubic
Migrated
usePushNotificationsoff rawlocalStoragetosafeReadStringLS/safeWriteLS/safeRemoveLSfor thehub_push_subscribedflag to prevent crashes in Safari Private Mode and quota-full cases. Removed the hook from thesergeant-design/no-raw-local-storageallowlist and reduced the production allowlist budget from 15 to 14; added 3 tests that stub Storage throws to lock in the behavior.Written for commit 3840b3a. Summary will update on new commits.
Summary by CodeRabbit
Tests
Chores