Skip to content

fix: persist post-auth valuation intent to sessionStorage so the run resumes after the OTP#45

Open
sweetmantech wants to merge 1 commit into
mainfrom
fix/valuation-intent-resume
Open

fix: persist post-auth valuation intent to sessionStorage so the run resumes after the OTP#45
sweetmantech wants to merge 1 commit into
mainfrom
fix/valuation-intent-resume

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Ships the post-auth intent drop item of recoupable/chat#1850: "Value my catalog" did not resume after the OTP for fresh signups; the lead had to click it a second time.

Root cause

Per the 2026-07-08 audit: a resume mechanism already existed. useCatalogValuation stored the picked artist in a pendingRun ref and auto-fired doRun on the authenticated flip. That works when the component instance survives auth (the 07-06 walkthrough auto-resumed through it), but fresh-signup auth flows churn enough state to remount the component mid-auth, and a ref dies with its instance: the intent is silently dropped, leaving the idle homepage observed in the 07-07 walkthrough.

Fix

Persist the intent to sessionStorage at the auth gate and resume from storage. Storage is the source of truth; the ref remains as the fast path.

  • Gate (run() while signed out): savePendingIntent(picked) writes {artist, savedAt} under the versioned key recoupable-valuation-pending-intent:v1 before login().
  • Resume: the authenticated effect now also runs on mount when already authenticated, so it covers the remount AND full-reload cases. It takes pendingRun.current ?? readPendingIntent(), clears both (consume-once, never re-fires on later mounts), restores picked (a remount lost it, and the result card renders the artist), and fires doRun.
  • Staleness guard: intents older than 15 minutes (PENDING_INTENT_MAX_AGE_MS) are ignored, so a stale tab never auto-runs a valuation.
  • clearPick() clears the persisted copy along with the ref, preserving the existing drop-the-deferred-run semantic.

New helpers in lib/valuation/ (one exported function per file): savePendingIntent, readPendingIntent, clearPendingIntent, plus pendingIntent.ts (shared key/max-age constants + type). All storage access is SSR-guarded and try/caught (private-mode failures fall back to the ref path).

Tests

TDD, RED first: 13 new logic-only vitest cases (3 save, 8 read, 2 clear) against a Map-backed sessionStorage stub. Full suite: 80 passed (was 67). pnpm build green.

Verification

  • pnpm test — 80/80
  • pnpm build — production build green
  • Live funnel: search artist → "Value my catalog" → OTP (fresh signup) → valuation runs with no further clicks. Pending — requires a fresh funnel signup on recoupable.dev; will be recorded as a PR comment per the "Done when" on chat#1850.

🤖 Generated with Claude Code


Summary by cubic

Fixes the post-auth “Value my catalog” flow so the run resumes automatically after OTP, including fresh signup flows that remount the component (chat#1850). The deferred intent is now saved to sessionStorage and consumed on login.

  • Bug Fixes
    • Persist the picked artist at the auth gate under recoupable-valuation-pending-intent:v1.
    • Resume on the auth flip and on mount when already authenticated; clear after use and restore the pick for the result UI.
    • Ignore intents older than 15 minutes; gracefully handle SSR/private-mode by falling back to the in-memory ref.
    • clearPick() also removes the persisted intent. Added unit tests for save/read/clear helpers.

Written for commit 539e156. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Valuation now remembers a selected artist if sign-in is required, then resumes automatically after authentication.
    • Added support for saving, reading, and clearing the pending selection in a user’s session.
  • Bug Fixes

    • Prevents stale or cleared selections from re-running unexpectedly.
    • Handles missing storage, expired data, and invalid saved values safely.
  • Tests

    • Added coverage for session persistence, expiry handling, SSR behavior, and error resilience.

…resumes after the OTP

useCatalogValuation kept the deferred "run after login" artist in a React
ref, auto-fired on the authenticated flip. That works when the component
survives auth, but fresh-signup auth churn remounts it and the ref dies
with the instance, dropping the intent (chat#1850 2026-07-08 audit).

Persist the picked artist (+ timestamp) to a versioned sessionStorage key
at the auth gate and resume from storage on the authenticated effect,
which also covers a full page reload mid-auth. The ref stays as the
fast path; storage is the source of truth. Intents older than 15 minutes
are ignored; clearPick drops the persisted copy too. Helpers TDD'd
(RED first) in lib/valuation with logic-only vitest coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marketing Ready Ready Preview Jul 8, 2026 2:17am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0aa717cb-8aa8-4e0b-b550-b238126ae10e

📥 Commits

Reviewing files that changed from the base of the PR and between f9862ee and 539e156.

📒 Files selected for processing (9)
  • hooks/useCatalogValuation.ts
  • lib/valuation/__tests__/clearPendingIntent.test.ts
  • lib/valuation/__tests__/fakeSessionStorage.ts
  • lib/valuation/__tests__/readPendingIntent.test.ts
  • lib/valuation/__tests__/savePendingIntent.test.ts
  • lib/valuation/clearPendingIntent.ts
  • lib/valuation/pendingIntent.ts
  • lib/valuation/readPendingIntent.ts
  • lib/valuation/savePendingIntent.ts

📝 Walkthrough

Walkthrough

This change introduces a sessionStorage-backed pending intent mechanism for the catalog valuation flow. New pendingIntent.ts, savePendingIntent.ts, readPendingIntent.ts, and clearPendingIntent.ts modules define, persist, retrieve, and clear a selected artist across sign-in. useCatalogValuation is updated to save the pending selection before login, resume it after authentication, and clear it when the pick is cleared. Corresponding test suites and a fake sessionStorage test utility are added.

Changes

Persisted Pending Intent

Layer / File(s) Summary
Pending intent data contract
lib/valuation/pendingIntent.ts
Adds PENDING_INTENT_KEY, PENDING_INTENT_MAX_AGE_MS, and the PendingIntent type.
Save pending intent
lib/valuation/savePendingIntent.ts, lib/valuation/__tests__/savePendingIntent.test.ts
Persists artist and timestamp to sessionStorage with SSR/error-safe no-op behavior; tests cover normal, SSR, and error cases.
Read pending intent
lib/valuation/readPendingIntent.ts, lib/valuation/__tests__/readPendingIntent.test.ts
Reads and validates persisted intent, enforcing shape and staleness checks, returning null otherwise; tests cover round-trip, max-age boundary, malformed data, and SSR.
Clear pending intent
lib/valuation/clearPendingIntent.ts, lib/valuation/__tests__/clearPendingIntent.test.ts, lib/valuation/__tests__/fakeSessionStorage.ts
Removes persisted intent from sessionStorage safely; adds shared fakeSessionStorage() test utility and tests for removal and SSR no-op.
Hook integration
hooks/useCatalogValuation.ts
Wires save/read/clear helpers into run(), the post-auth effect, and clearPick() to persist and resume the deferred valuation run across remounts.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant useCatalogValuation
  participant sessionStorage
  participant AuthProvider

  User->>useCatalogValuation: run() while signed out
  useCatalogValuation->>sessionStorage: savePendingIntent(artist)
  useCatalogValuation->>AuthProvider: login()
  AuthProvider-->>useCatalogValuation: authenticated
  useCatalogValuation->>sessionStorage: readPendingIntent()
  sessionStorage-->>useCatalogValuation: artist
  useCatalogValuation->>sessionStorage: clearPendingIntent()
  useCatalogValuation->>useCatalogValuation: doRun(artist)
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: persisting valuation intent in sessionStorage so the flow resumes after auth/OTP.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/valuation-intent-resume

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 9 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/valuation/readPendingIntent.ts">

<violation number="1" location="lib/valuation/readPendingIntent.ts:26">
P2: Malformed timestamps can bypass the expiry guard and still resume a valuation intent. The `savedAt` check only verifies `number`, so `NaN`/`Infinity` pass; adding a finite-number guard keeps malformed storage data from being accepted.</violation>
</file>

<file name="hooks/useCatalogValuation.ts">

<violation number="1" location="hooks/useCatalogValuation.ts:87">
P2: Stale signed-out intents can still auto-run when the component instance survives auth, because `pendingRun.current` bypasses the 15-minute expiry enforced only by `readPendingIntent()`. Consider storing `{ artist, savedAt }` in the ref or validating the ref against the same max-age before preferring it.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

if (typeof artist?.id !== "string" || typeof artist.name !== "string") {
return null;
}
if (typeof savedAt !== "number") return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Malformed timestamps can bypass the expiry guard and still resume a valuation intent. The savedAt check only verifies number, so NaN/Infinity pass; adding a finite-number guard keeps malformed storage data from being accepted.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/valuation/readPendingIntent.ts, line 26:

<comment>Malformed timestamps can bypass the expiry guard and still resume a valuation intent. The `savedAt` check only verifies `number`, so `NaN`/`Infinity` pass; adding a finite-number guard keeps malformed storage data from being accepted.</comment>

<file context>
@@ -0,0 +1,32 @@
+    if (typeof artist?.id !== "string" || typeof artist.name !== "string") {
+      return null;
+    }
+    if (typeof savedAt !== "number") return null;
+    if (now - savedAt > PENDING_INTENT_MAX_AGE_MS) return null;
+    return artist;
</file context>

void doRun(artist);
}
if (!authenticated) return;
const artist = pendingRun.current ?? readPendingIntent();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Stale signed-out intents can still auto-run when the component instance survives auth, because pendingRun.current bypasses the 15-minute expiry enforced only by readPendingIntent(). Consider storing { artist, savedAt } in the ref or validating the ref against the same max-age before preferring it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At hooks/useCatalogValuation.ts, line 87:

<comment>Stale signed-out intents can still auto-run when the component instance survives auth, because `pendingRun.current` bypasses the 15-minute expiry enforced only by `readPendingIntent()`. Consider storing `{ artist, savedAt }` in the ref or validating the ref against the same max-age before preferring it.</comment>

<file context>
@@ -65,20 +71,25 @@ export function useCatalogValuation(): CatalogValuationState {
-      void doRun(artist);
-    }
+    if (!authenticated) return;
+    const artist = pendingRun.current ?? readPendingIntent();
+    pendingRun.current = null;
+    clearPendingIntent(); // consume-once: never re-fire on later mounts
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant