feat: TASK-21460 setup flow on the URL stepper, kills the redux setup slice - #2949
Conversation
The step cursor is a named screen id in the URL (?screen=signup) on the
shared useFlowStepper — replacing the redux numeric index into the
runtime-filtered step array whose next/previous clamps silently
dead-ended (TASK-21404). history:'push' keeps the deliberate setup
contract: the browser/hardware Back button walks the steps; a step with
showBackButton=false is a point of no return — earlier screens' guards
bounce a history pop back. Entry stays owned by resolveSetupEntryStep
and REPLACES any stale ?screen= on fresh load. The pushState mirror
(useSetupStepUrlSync) is retired; its analytics live on in
useSetupStepAnalytics.
The setup slice dissolves by field:
- steps/direction/isLoading/username/residence → SetupFlowProvider,
mounted at the (setup) layout (flow-scoped, TASK-21816 pattern).
- inviteCode/inviteType → cookies (invite-stash) — they are written from
payment/claim/invite surfaces and read at registration, and the cookie
is the copy that already survived the PWA-install hop.
- showIosPwaInstallScreen → useIosPwaInstallGate (sessionStorage) — the
cross-layout latch (setup arms it, mobile-ui reads it).
- telegramHandle was dead state: no writer anywhere; dropped.
- useResidenceRestrictions reads the during-signup answer via
useOptionalSetupFlow (Profile/Home consumers have no provider).
useFlowStepper grows a history option ('replace' default per design.md;
'push' for this flow) and a per-call history override on goTo (entry
replaces). TASK-21460.
…460-setup-url-stepper
…460-setup-url-stepper
…460-setup-url-stepper
…460-setup-url-stepper
…460-setup-url-stepper
…460-setup-url-stepper
…460-setup-url-stepper
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code-analysis diffPainscore total: 7127.63 → 7187.1 (+59.47) 🆕 New findings (263)
…and 243 more. ✅ Resolved (251)
…and 231 more. 📈 Painscore deltas (top movers)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
|
/chip review |
1 similar comment
|
/chip review |
|
/chip review |
There was a problem hiding this comment.
Chip review — changes requested
The URL-stepper migration reintroduces native back-stack bounce behavior and can separate persisted invite codes from their invite type.
Findings
-
BLOCKING · src/hooks/useSetupFlow.ts:60 · Keep native setup transitions out of browser history
Every setup transition now pushes a WebView history entry, including on native. Native hardware Back is already consumed by useSetupBackHandler, which calls handleBack directly, so these entries are not consumed during the flow; after advancing, backing, and completing setup, stale /setup entries remain and Back from the app can re-enter completed onboarding and bounce to /home. The deleted URL mirror explicitly used replace when isNativeBridge() for this reason. Use replace history on native and push only on web, with a native-history regression test. -
MAJOR · src/utils/invite-stash.ts:18 · Keep invite code and type on one lifecycle
The new helper stores inviteCode and inviteType separately, but live paths such as /setup?code, PublicProfile, native/deferred links, and JoinWaitlist cleanup still update only inviteCode; the failed-accept retry also extends only inviteCode for 30 days. Start a PAYMENT_LINK signup, then let the session type cookie expire or replace the code through a direct/native link: readInviteType() defaults to or retains the wrong type and /invites/accept persists a misclassified invite edge. Route every write, clear, and retry through one helper (or one structured cookie), keep both fields on the same lifetime, and test overwrite plus restart-retry cases. -
MINOR · src/utils/invite-stash.ts:25 · [claude-opus] New persistent inviteType cookie has no test; it can now outlive its flow
src/utils/invite-stash.tsis new shared, persistent state that feeds invite attribution:readInviteType()supplies thetypesent toPOST /invites/accept(useZeroDev.ts:72,129), which peanut-api-ts persists on the Invites row (siblings/peanut-api-ts/src/routes/invite.ts:265) and emits on theinvite.acceptedevent (:305). CONTRIBUTING.md:506 — "if code moves money or mutates shared state, it needs a test before merge". There is no test for this module: every spec that touches it (InvitesPage.test.tsx, JoinWaitlistPage.test.tsx, useZeroDev-invite-onboarding.test.tsx, useZeroDev-login-failure.test.tsx)jest.mock('@/utils/invite-stash', ...), so the real read/write path is never exercised.
The exact untested case: invite TYPE used to live in per-session redux and defaulted to DIRECT on every fresh session; it is now a cookie that only stashInvite writes, while five call sites still write the code cookie alone — deferred-link.ts:342, useNativeAppLinks.ts:135, general.utils.ts:787 (inviteFlowUrl on native), PublicProfile.tsx:111, setup/page.tsx:161. So: visit a payment-link invite page (type cookie = PAYMENT_LINK), don't sign up; later arrive through a deferred/deep link that writes only inviteCode, then register — registration now sends type: PAYMENT_LINK for an invite that has nothing to do with that earlier payment link, where the old code sent DIRECT. Nothing catches this today.
Fix: add src/utils/__tests__/invite-stash.test.ts covering the round trip (stash → readInviteCode/readInviteType through the JSON-serialising cookie helpers), the DIRECT fallback for a missing or unrecognised inviteType cookie, and clearInvite() blanking both — and decide whether a code-only cookie write should reset the type (e.g. have the deep-link/deferred writers call stashInvite(code, EInviteType.DIRECT)).
Checked clean
- Verified the detached worktree head and merge base against the supplied SHAs.
- Checked exact-head CI; all substantive checks completed successfully.
- Checked URL entry replacement, invalid-screen fallback, point-of-no-return guards, and setup step analytics.
- Checked setup context migration for username and residence state and the cross-layout iOS install latch.
- Checked the sibling API invite contract; the supplied invite type is persisted on the invite edge while inviter validity and app access remain backend-controlled.
Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.
Third opinion by claude-opus: 1 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.
Exact head: 30bf4a38eef4 · Context: repo, sibling · Took 9m (queued 25m)
…d 1) Native setup transitions now replace instead of push: hardware Back is consumed by useSetupBackHandler, so pushed WebView entries were never used during the flow and Back after completing setup popped through stale /setup screens back into onboarding — the contract the deleted useSetupStepUrlSync mirror kept via replaceState. Web keeps push. Regression tests pin both history modes through the nuqs adapter. The invite code and its type are one value on one lifecycle now: every write goes through stashInvite (bare-code writers — deferred links, native app links, /setup?code=, PublicProfile — stash DIRECT, which is what the old per-session redux default made them mean, and it RESETS a stale type from an earlier flow), cleanup goes through clearInvite (both cookies), and the failed-accept retry extends both fields for 30 days instead of the code alone. inviteFlowUrl is a pure URL builder — its native code-only cookie write was the drift source, and every caller already stashes with the true type first. New invite-stash test file runs the real cookie round trip: stash/read, DIRECT fallback, overwrite-resets-type, retry extension, clear.
|
/chip review |
handleNext voids goTo's promise, so the assertion could outrun nuqs's throttle queue — passed locally, failed on the slower CI runner. rateLimitFactor 0 + one macrotask makes the write land deterministically.
|
/chip review |
There was a problem hiding this comment.
Chip review — changes requested
One blocking setup-entry regression remains: a stale final-screen URL can become locked before entry initialization resets it.
Findings
-
BLOCKING · src/hooks/useSetupFlow.ts:72 · Do not lock a step hidden behind entry loading
Open/setup?screen=sign-test-transactionin a fresh logged-out session, or reload a copied stale URL. Once the layout suppliessteps, this effect records the URL-selected final step as rendered even thoughSetupPageContentis still returning its loading screen. The entry resolver then tries to replace it with landing/signup, but the newly built point-of-no-return guard rejects every earlier target and rewrites the URL back tosign-test-transaction, leaving the visitor on the terminal screen without prerequisite state or a usable back path. Record the irreversible lock only after the page confirms the step is actually visible (stepRendered), or clear/bypass it during initial entry resolution, and add a stale-final-screen load test. -
MINOR · src/hooks/useIosPwaInstallGate.ts:25 · [claude-opus] New sessionStorage install-gate latch has no test, and its lifetime changed
useIosPwaInstallGatereplaces the reduxshowIosPwaInstallScreenfield with a sessionStorage latch (peanut.showIosPwaInstallScreen) shared across the (setup) and (mobile-ui) route groups. That is shared mutable state gating a full-screen takeover:src/app/(mobile-ui)/layout.tsx:193returns<ForceIOSPWAInstall />instead of the app for every non-public route while the latch is true. The module has no test of its own —src/components/ForceIOSPWAInstall/__tests__/ForceIOSPWAInstall.test.tsxmocks the hook out entirely, so nothing exercises the real read/write.
This is not a pure rename: the storage medium changes the lifetime. Previously the flag lived in redux and was false again on any full page load, so only a live /setup mount could arm it. Now it survives reloads for the whole tab session. The exact untested case: arm the gate from the (setup) layout on iOS/non-PWA (setShowIosPwaInstallScreen(true) in src/app/(setup)/layout.tsx:75), then do a full page load of a (mobile-ui) route — the gate still reads true and the wall renders, where the redux version rendered the app. The complementary case is equally uncovered: setShowIosPwaInstallScreen(false) (ForceIOSPWAInstall's "continue in the browser", and the existing-session disarm at src/app/(setup)/setup/page.tsx:120) must keep the user past the wall for the rest of the session.
Fix: add a small jsdom test over the real module — arm, assert read() is true after a simulated remount, disarm, assert it stays false; plus the private-mode branch where sessionStorage throws and the wall must never arm.
Checked clean
- P1 is fixed: setup transitions use push history on web and replace history on the native bridge, with exact-head regression tests.
- P2 is fixed: invite code and type writers, retry extension, and terminal cleanup now use the shared lifecycle helpers.
- P3 is fixed: invite-stash tests exercise real cookie round trips, overwrite/reset behavior, retry lifetime, and cleanup.
- Setup context migrations, filtered-step indexing, residence state, iOS PWA latch consumers, and invite-flow call sites were checked for additional regressions.
- Exact-head CI passed unit, typecheck, eslint, format, design-system lint, and the aggregate ci-success gate.
- A focused local Jest run could not start because the detached worktree has no installed Jest binary; exact-head CI completed successfully instead.
Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.
Third opinion by claude-opus: 1 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.
Exact head: f9af1c03be22 · Context: repo · Took 7m (queued 7m)
The lock derived itself from 'the URL selected this no-back step' as soon as the layout supplied steps — so a stale terminal URL (/setup?screen=sign-test-transaction in a fresh session) got locked while the page still showed its loading screen, and the entry resolver's replacement bounced off the guard forever. The lock now lives in SetupFlowContext (one fact across the several useSetupFlow instances) and only the page arms it, gated on stepRendered — entry resolution done, no interstitial, no modals. resetSetupFlow disarms it. Tests: armed lock still bounces backward moves, a stale terminal URL stays replaceable, reset disarms. Also adds the real-module test for useIosPwaInstallGate: cross-layout remount hand-off, live-subscriber notify, disarm stickiness, the deliberate survives-reload lifetime, the documented storage key, and the private-mode throw branch where the wall must never arm.
|
/chip review |
There was a problem hiding this comment.
Chip review — no blocking findings — this is not an approval
All five supplied prior findings are fixed at this head. One minor import-graph regression remains: the shared invite stash reconnects globally mounted native-link handling to general.utils and its heavy catalog graph. Exact-head CI is green.
Findings
-
MINOR · src/utils/invite-stash.ts:2 · Keep the invite stash on the lightweight cookie helper
ClientProviders mounts useNativeAppLinks on every route, and that hook now imports this stash. Importing the cookie helpers through general.utils reconnects this global/native-startup path to token-registry.consts, wagmi/chains, and the rest of the app utility graph, undoing the explicit cookie-url split that kept the roughly 189 KB catalog graph out. Import getFromCookie and saveToCookie directly from cookie-url.utils. -
MINOR · src/features/setup/useSetupStepAnalytics.ts:17 · [claude-opus] Signup-funnel analytics lost its only test in the move
useSetupStepUrlSync.test.tsxwas deleted (correctly — the URL-mirroring half is gone), but the analytics half it also covered survives verbatim asuseSetupStepAnalyticsand now has no test at all:SIGNUP_STEP_VIEWEDhas only 5 references in the repo, none in a spec. The newuseSetupFlow.test.tsxcovers the stepper/history/lock contracts, not the capture. The untested case is thenav_typederivation: enteringsignup→residencemust emitnav_type: 'forward',residence→signupmust emit'back', a skip (landing→residence) must emit'jump', the first render must emit'initial', and a re-render of the samescreenIdmust emit nothing — the deleted spec asserted every one of these (including the no-double-fire case) and nothing asserts them now. Since the whole flow is a single pageview, this event is the only source for per-screen signup funnels, so a regression here fails silently in PostHog rather than in CI. Fix: port the four analytics assertions from the deleted spec intosrc/features/setup/__tests__/useSetupStepAnalytics.test.ts. Flagging this as a coverage regression from a deleted passing test, not a new test demand on moved code.
Checked clean
- P1 is fixed: web setup transitions push history while native bridge transitions replace it, with exact platform-mode regression tests.
- P2 and P5 are fixed: every invite writer now updates code and type together, retry extends both, terminal cleanup clears both, and the real cookie lifecycle is tested.
- P3 is fixed: the point-of-no-return lock is context-owned and arms only after the page confirms the no-back step is visible; stale terminal URLs remain replaceable.
- P4 is fixed: the real sessionStorage install-gate module now covers cross-layout remount, subscriber notification, disarm persistence, and storage-unavailable behavior.
- Correctness and adversarial passes covered setup entry resolution, filtered-step navigation, point-of-no-return behavior, logout/reset, invite acceptance retry, and iOS install-wall handoff.
- Security pass found no new auth, secret-exposure, unsafe-navigation, or money-handling defect requiring a dedicated review.
- Exact-head CI completed green: unit, typecheck, eslint, format, design-system lint, backend baseline, and aggregate ci-success all passed.
- Focused local execution passed 26 tests across useFlowStepper, useIosPwaInstallGate, and invite-stash; the setup-flow suite could not load from the borrowed dependency tree because lucide-react was absent, while the exact-head CI unit job passed.
Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.
Third opinion by claude-opus: 1 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.
Exact head: 2c9c1072eee1 · Context: repo · Took 15m
…atalog graph (Chip round 3) useSetupStepAnalytics regains the coverage the deleted URL-sync spec carried: initial/forward/back/jump nav_type derivation and the no-double-fire case — SIGNUP_STEP_VIEWED is the only per-screen signal in the single-pageview flow. invite-stash imports the cookie helpers from cookie-url.utils directly so the globally mounted native-link handler stays off general.utils' token-catalog graph.
|
/chip review |
There was a problem hiding this comment.
Chip review — no blocking findings — this is not an approval
The setup-flow, invite-stash, analytics, and iOS gate changes resolve the six supplied findings. Exact-head unit CI still fails because PublicProfile's invite handoff tests were not migrated to the new stashInvite contract.
Findings
- MINOR · src/components/Profile/components/PublicProfile.tsx:113 · Update the PublicProfile invite-handoff tests
Theunitcheck fails in all three crediting-door cases because this path now callsstashInvite(code, EInviteType.DIRECT), whilePublicProfile.test.tsxstill mocks and expectssaveToCookie('inviteCode', code). Update that suite to mock@/utils/invite-stashand assert the code/type pair so the changed contract is covered and exact-head CI passes.
Checked clean
- P1 fixed: setup transitions use push history on web and replace history with a native bridge; focused adapter tests cover both modes.
- P2 fixed: invite code/type writes, retry extension, and cleanup share the invite-stash lifecycle; no production bare-cookie writers remain.
- P3 fixed: the no-back lock is armed only after the selected step is visibly rendered, and resetSetupFlow disarms it.
- P4 fixed: invite-stash imports the lightweight cookie-url helper directly and avoids the general-utils token-catalog graph.
- P5 fixed: useSetupStepAnalytics has real tests for initial, forward, back, jump, and duplicate suppression.
- P6 fixed: useIosPwaInstallGate has real tests for remount handoff, live notification, disarm persistence, storage lifetime, and unavailable storage.
- Reviewed entry-step replacement, runtime-filtered step ordering, browser/native back behavior, point-of-no-return guards, Redux setup-state rehoming, and invite consumers.
- Exact-head typecheck, ESLint, format, design-system lint, backend baseline, preview, and ownership gates passed; unit failed only in three PublicProfile guest-door assertions tied to this PR's writer migration.
- The detached worktree had no installed dependencies, so local focused Jest execution was unavailable; exact-head GitHub Actions logs supplied the test evidence.
Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.
Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.
Exact head: 6519955346ff · Context: repo · Took 11m
…general.utils spy
Round 3 moved invite-stash onto cookie-url.utils directly, so the suite's
general.utils saveToCookie spy stopped seeing the write (caught by CI, not
locally — my full-run summary got truncated). The assertions now target the
semantic API: stashInvite('satoshi','DIRECT') on the crediting paths, not
called on the three no-credit paths.
|
/chip review |
There was a problem hiding this comment.
Chip review — no blocking findings — this is not an approval
The six supplied findings are fixed at this head. One minor duplicate cleanup remains in logout.
Findings
- MINOR · src/context/authContext.tsx:300 · Clear the invite stash only once on logout
Every logout already clears both invite cookies at line 287. Replacing the removed setup reset with another clearInvite() here makes the same two cookie writes a second time, and leaves the surrounding Redux-reset block pretending setup state still exists. Remove this second call and update the block comment to cover only the user and ZeroDev slices.
Checked clean
- P1 fixed: web setup transitions push while native transitions replace, with per-platform adapter coverage.
- P2 fixed: production invite-code writers, retry extension, and cleanup now keep code and type on one cookie lifecycle.
- P3 fixed: the no-back lock is armed by the page only after the selected step is visibly rendered; reset disarms it.
- P4 fixed: invite-stash imports the lightweight cookie helper directly.
- P5 fixed: PublicProfile guest-door tests now assert stashInvite for both normal and store-handoff paths.
- P6 fixed: signup step analytics now has initial, forward, back, jump, and duplicate-suppression coverage.
- Setup context consumers remain inside the setup provider; the residence hook uses the optional context outside setup.
- Exact-head unit, typecheck, lint, format, design-system lint, backend-baseline, and bot-approval checks passed; aggregate reporting was still in progress when checked.
- No new security, privacy, secret, authorization, or money-handling surface was introduced.
Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.
Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.
Exact head: 35d18d815c61 · Context: repo · Took 9m
The early clear at the top of logout already wipes both cookies; the second call was the leftover slot where the setup-slice reset used to live. Comment now says what the block actually resets.
|
/chip review |
There was a problem hiding this comment.
Chip review — no blocking findings — this is not an approval
All six supplied prior findings are fixed at this head, exact-head CI is green, and no new review findings remain.
Checked clean
- P1 is fixed: setup transitions push browser history on web and replace it on the native bridge, with per-platform adapter coverage.
- P2 is fixed: invite code and type now share one writer, retry lifetime, and cleanup lifecycle across all production handoff paths.
- P3 is fixed: the point-of-no-return lock arms only after a no-back step is visibly rendered, and resetSetupFlow disarms it.
- P4 is fixed: invite-stash imports the lightweight cookie helper directly and keeps the globally mounted native-link path off the token-catalog graph.
- P5 is fixed: PublicProfile tests mock the invite stash and cover both crediting doors plus all no-credit outcomes.
- P6 is fixed: logout clears the invite stash once and the Redux-reset comment now matches the surviving user and ZeroDev slices.
- Correctness and adversarial passes covered entry-step replacement, runtime-filtered step ordering, web/native back behavior, no-back guard recovery, cross-layout iOS install state, invite acceptance retries, and context consumers.
- The paired API contract remains compatible: telegramHandle is optional and invite acceptance still uses the existing DIRECT and PAYMENT_LINK enum values.
- Exact-head unit, typecheck, ESLint, format, design-system lint, backend baseline, report, aggregate ci-success, preview, and ownership checks completed successfully.
- The detached worktree has no installed dependencies, so focused local Jest execution was unavailable; exact-head GitHub Actions supplied the test evidence.
Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.
Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.
Exact head: 82f70823cc40 · Context: repo, peanut-api-ts · Took 9m
Lands via the
tech-debtintegration train (dev is merge-frozen). STACKED on #2917: base is temporarily that PR's branch so this diff shows only the setup delta (the shared stepper and Field are born in #2917). When #2917 merges into tech-debt (delete its branch), GitHub retargets this PR to tech-debt automatically — verify the base after that merge.Summary
The setup flow's cursor was a redux numeric index into a runtime-filtered step array (PostHog sunset flag filters steps in
(setup)/layout.tsx), andpreviousStep/nextStepclamped silently — the dead-end bug (TASK-21404) and the broken-back bug were the same defect. This PR puts the cursor in the URL as a named screen id and deletes the redux setup slice.Setup on the shared stepper (TASK-21460):
useSetupFlowrebuilt onuseFlowStepperfrom feat: TASK-21816 withdraw on a URL-backed stepper + TASK-21454 Field #2917:?screen=signup|residence|passkey|…— named ids, never indexes.?screen=, not?step=:?step=signupat /setup entry is an existing contract that skips the invite gate (determineInitialStep), preserved as-is.history: 'push'— a deliberate deviation from the replace default: the signup funnel wants the hardware Back button to walk the steps, which is the contract the olduseSetupStepUrlSyncpushState mirror established. That hook (132 LoC + 169 LoC of tests) is deleted; the stepper owns back-nav.showBackButton: falserenders (e.g. sign-test-transaction — the passkey is already registered), guards refuse every earlier step and bounce forward, so a history pop cannot re-enter the forms.handleNext; an unknown target id is a no-op, not an index write.SetupFlowProvidermounts at the(setup)layout — flow-scoped, same shape as the withdraw provider in feat: TASK-21816 withdraw on a URL-backed stepper + TASK-21454 Field #2917. It holds what the URL cannot: the filtered step list (a runtime decision), the typed username/residence answers, loading/animation direction.Redux setup slice deleted (the only slice with real structure — 8 read sites re-homed):
SetupFlowContext.showIosPwaInstallScreen→useIosPwaInstallGate: a sessionStorage latch behinduseSyncExternalStore, because the writer ((setup)layout) and the reader ((mobile-ui)layout) live in different route groups with separate providers. Session-scoped on purpose; storage-unavailable fails closed (wall never arms).inviteCode/inviteType→src/utils/invite-stash.ts: the cookie was already the durable store (the hand-off must survive the PWA-install hop); the redux mirror is gone, the cookie is now the single owner. Consumers: claim/pot/invite CTAs stash,useZeroDevregistration reads,authContextclears.useSetupStepAnalytics(new,src/features/setup/): the step-view PostHog events the old URL-sync hook fired, extracted unchanged.Task
TASK-21460 (setup rebuild on the URL stepper, absorbs TASK-21404's dead-end/back bugs). The full redux deletion (store, remaining trivial slices, deps) is TASK-21462 — next PR, not this one.
Deleted
src/redux/slices/setup-slice.ts,src/redux/types/setup.types.ts, thesetupentries instore.ts/hooks.ts/constants.src/hooks/useSetupStepUrlSync.ts+ its test file (the stepper owns the URL now).Risks / breaking changes
resolveSetupEntryStep), the existing-session interstitial, invite-gate skip via?step=signup, and the sunset-flag filtering are preserved; what changed is who owns the cursor.?screen=is a new URL param; deep-linking into a mid-flow screen without the flow's memory bounces to a live screen via the stepper guards instead of rendering a dead one.QA
npm run typecheckclean · prettier clean ·npm run buildgreen.add-money-states › loaded EVM depositfailure (fails identically on pristine dev; see feat: TASK-21816 withdraw on a URL-backed stepper + TASK-21454 Field #2917's QA note).useSetupFlow(guard bounces, explicit end-of-list, point-of-no-return, direction),useIosPwaInstallGate(cross-layout latch + storage-unavailable), invite stash round-trip viauseZeroDevinvite-onboarding tests,Residence/SignTestTransactionview tests moved off the redux store,InvitesPage/JoinWaitlistPage/ForceIOSPWAInstallre-pointed./setuproutes this PR preserves; classifications unchanged.