Skip to content

fix(onboarding): /setup/valuation polls its way out of measuring (chat#1912 row 9) - #1919

Merged
sweetmantech merged 3 commits into
mainfrom
fix/setup-valuation-poll
Jul 31, 2026
Merged

fix(onboarding): /setup/valuation polls its way out of measuring (chat#1912 row 9)#1919
sweetmantech merged 3 commits into
mainfrom
fix/setup-valuation-poll

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Closes row 9 of chat#1912.

Why

/setup/valuation renders MeasuringCatalogPanel"Measuring your catalog … this usually takes a minute" — and then never re-checks.

useCatalogMeasurements sets staleTime and refetchOnWindowFocus: false and has no refetchInterval. The only poller in the codebase lives in useCatalogReport, which chat#1915 built for /catalogs/{id}. So a signup who lands here while their seeded catalog is still measuring sits on static text until they refresh by hand.

That is the same dead end row 1 fixed, on a route row 1 did not cover — and this one is on the path the welcome email points at.

What changed

  • lib/onboarding/getSetupValuationStatus.ts — pure loading | redirect | measuring | ready. catalogsPending is checked first, because a pending list is not an empty one: useCatalogs is enabled: !!accountId && authenticated, and a disabled TanStack v5 query reports isPending true / isFetching false. Reading that as "no catalog" would redirect a signup away mid-load.
  • hooks/useSetupValuation.ts — composes the catalog and valuation reads, owns the redirect, and reuses the existing useMeasuringPoll rather than introducing a second poller.
  • SetupValuation renders what the status describes; its useEffect moved into the hook, per the OCP note on chat#1915.

Tests

  • getSetupValuationStatus — 6 cases including the one that matters, "prefers loading over redirect when both could apply", which is the trap the original comment in this file warned about.
  • useMeasuringPoll4 new cases, the first direct coverage this hook has had since row 1 introduced it: it polls while measuring, does not when idle, stops when measuring ends, and stops on unmount. The last two matter because a poll that outlives its state keeps hitting the api for as long as the tab is open.
  • The 5 existing SetupValuation tests pass unchanged through the new composition. They needed one added mock: the component now reaches useQueryClient through the hook, and those tests render it without a provider.

Full suite 347 passed / 83 files, tsc --noEmit and eslint clean.

Verification

Preview verification against row 9's Works-when — add a first artist on a fresh account, go straight to /setup/valuation, and do not touch the page; it should resolve to the baseline valuation on its own, and polling should stop once it does — will be posted as a PR comment.

🤖 Generated with Claude Code


Summary by cubic

Fixes onboarding /setup/valuation to poll catalogs and measurements and handle loading states correctly, so the page exits “Measuring your catalog” on its own and avoids redirects during seeding or while the roster is still loading.

  • Bug Fixes

    • Reuses useMeasuringPoll; stops when measuring ends or on unmount.
    • Treats pending useCatalogs and the artist roster as loading, not “no catalog”/“no artists” (@tanstack/react-query v5).
    • During seeding, shows measuring if there are artists but no catalog yet; redirects only when no artists and no catalog or the list failed.
    • Invalidates ["catalogs"] and ["catalog-measurements"] while polling.
  • Refactors

    • Added getSetupValuationStatus (loading | redirect | measuring | ready).
    • Added useSetupValuation to compose reads, own redirect, and drive polling.
    • SetupValuation now renders solely based on status.

Written for commit 1615d7f. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Improved valuation setup flow with clearer loading, measuring, ready, and redirect states.
    • Automatically guides accounts without a catalog to the catalog setup process.
    • Continues tracking measurements while valuation setup is in progress.
  • Bug Fixes

    • Improved handling of pending or failed catalog and valuation states.

…t#1912 row 9)

The route rendered MeasuringCatalogPanel and never re-checked.
useCatalogMeasurements has no refetchInterval, and the only poller lived in
useCatalogReport, which row 1 built for /catalogs/{id}. A signup landing here
while a seeded catalog was still measuring sat on static text until they
refreshed by hand — the same "nothing is happening, try again" pressure this
issue exists to remove, on a route row 1 did not cover.

Adds getSetupValuationStatus (loading | redirect | measuring | ready) and
useSetupValuation, which composes the catalog and valuation reads, owns the
redirect, and reuses the existing useMeasuringPoll rather than adding a second
poller. SetupValuation renders what the status describes.

Also covers useMeasuringPoll directly: it polls while measuring, not
otherwise, and stops both when measuring ends and on unmount.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

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 31, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
chat Ready Ready Preview Jul 31, 2026 1:24am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@sweetmantech, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5b27b04c-6a84-483d-87fd-a5bc15d005bd

📥 Commits

Reviewing files that changed from the base of the PR and between f79db01 and 1615d7f.

⛔ Files ignored due to path filters (2)
  • components/Onboarding/__tests__/SetupValuation.test.tsx is excluded by !**/*.test.* and included by components/**
  • lib/onboarding/__tests__/getSetupValuationStatus.test.ts is excluded by !**/*.test.* and included by lib/**
📒 Files selected for processing (2)
  • hooks/useSetupValuation.ts
  • lib/onboarding/getSetupValuationStatus.ts
📝 Walkthrough

Walkthrough

SetupValuation now delegates catalog and valuation orchestration to useSetupValuation, which derives setup status, redirects missing catalogs, polls measurements, and controls loading, measuring, and ready rendering states.

Changes

Setup valuation flow

Layer / File(s) Summary
Status resolution
lib/onboarding/getSetupValuationStatus.ts
Adds the status contract and resolver for loading, redirect, measuring, and ready states.
Hook orchestration
hooks/useSetupValuation.ts
Combines catalog and valuation state, performs catalog redirects, and polls measurements while setup is measuring.
Onboarding rendering
components/Onboarding/SetupValuation.tsx
Consumes the shared hook and renders skeleton, measuring, or valuation content from its derived state.

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

Sequence Diagram(s)

sequenceDiagram
  participant SetupValuation
  participant useSetupValuation
  participant CatalogQuery
  participant HomeValuation
  SetupValuation->>useSetupValuation: request status and valuation
  useSetupValuation->>CatalogQuery: read catalog state
  useSetupValuation->>HomeValuation: read valuation state
  useSetupValuation->>CatalogQuery: invalidate measurements while measuring
  useSetupValuation-->>SetupValuation: return status and valuation
Loading

Possibly related PRs

  • recoupable/chat#1895: Introduced the catalog and valuation flow now extracted into the shared hook.
  • recoupable/chat#1900: Introduced the measuring-state logic now centralized by the hook and status resolver.

Suggested reviewers: cubic-dev-ai

Poem

Catalogs load, valuations gleam,
A tidy hook now guides the stream.
Measuring hums while results grow,
Redirects point where they should go.
Ready at last—the numbers show!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Solid & Clean Code ✅ Passed Pure status logic is isolated, polling is reused, routing/poll orchestration is cohesive, and SetupValuation is presentational; no duplicated rules, deep nesting, or oversized classes were found.
✨ 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/setup-valuation-poll

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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f79db01fbf

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread hooks/useSetupValuation.ts Outdated
queryClient.invalidateQueries({ queryKey: ["catalog-measurements"] });
}, [queryClient]);

useMeasuringPoll(status === "measuring", refetchMeasurements);

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 Badge Stop polling for permanent no-valuation states

Only gate this interval on an actual in-progress measurement, not every hidden valuation. useHomeValuation() returns { show: false } for permanent/non-measuring cases too, such as measurementsFailed, a pre-v2 response without measured_song_count, or an artist-scope mismatch in getValuationHeroState; with a catalog present those all become status === "measuring" here, so /setup/valuation keeps invalidating and refetching catalog-measurements every 5 seconds for as long as the tab is open even though the state cannot resolve by polling.

Useful? React with 👍 / 👎.

@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 6 files

Confidence score: 3/5

  • In lib/onboarding/getSetupValuationStatus.ts, failed measurements are currently mapped to "measuring," which can leave /setup/valuation stuck on a perpetual loading state and trigger repeated refetches every 5 seconds even for 500/auth failures; this is the main user-impact risk and should be addressed by adding a distinct failed/error status and stopping the polling loop on terminal errors.
  • In components/Onboarding/SetupValuation.tsx, the || !valuation.show condition in the measuring branch is redundant with the status contract, which raises a small maintainability/readability risk and could cause future branching confusion—remove the extra guard to keep rendering logic aligned with status semantics.
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/onboarding/getSetupValuationStatus.ts">

<violation number="1" location="lib/onboarding/getSetupValuationStatus.ts:31">
P2: A failed measurements request is treated as an in-progress measurement, so `/setup/valuation` stays on “Measuring” and invalidates the failed query every 5 seconds (including 500/authorization failures). Add a valuation-error status/input and reserve `measuring` for an absent, non-failed valuation.</violation>
</file>

<file name="components/Onboarding/SetupValuation.tsx">

<violation number="1" location="components/Onboarding/SetupValuation.tsx:34">
P3: The `|| !valuation.show` guard in the measuring check is redundant because the status already encodes whether the valuation is ready.

When `status` is "measuring", `valuation.show` is always `false` by the definition of `getSetupValuationStatus`. When `status` is "ready", `valuation.show` is always `true`. The early return for "loading" and "redirect" already ensures we only reach this line with those two remaining statuses, so `!valuation.show` can never alter the branch outcome.

Removing the redundant check would make the intent clearer — the status model is the single source of truth for what to render.</violation>
</file>

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

Re-trigger cubic

}: SetupValuationStatusInput): SetupValuationStatus {
if (catalogsPending) return "loading";
if (catalogsFailed || !hasCatalog) return "redirect";
return valuationReady ? "ready" : "measuring";

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: A failed measurements request is treated as an in-progress measurement, so /setup/valuation stays on “Measuring” and invalidates the failed query every 5 seconds (including 500/authorization failures). Add a valuation-error status/input and reserve measuring for an absent, non-failed valuation.

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

<comment>A failed measurements request is treated as an in-progress measurement, so `/setup/valuation` stays on “Measuring” and invalidates the failed query every 5 seconds (including 500/authorization failures). Add a valuation-error status/input and reserve `measuring` for an absent, non-failed valuation.</comment>

<file context>
@@ -0,0 +1,32 @@
+}: SetupValuationStatusInput): SetupValuationStatus {
+  if (catalogsPending) return "loading";
+  if (catalogsFailed || !hasCatalog) return "redirect";
+  return valuationReady ? "ready" : "measuring";
+}
</file context>

// valuation is still measuring (chat#1889 row 8).
if (!valuation.show) return <MeasuringCatalogPanel />;
// valuation is still measuring (chat#1889 row 8). The hook polls it out.
if (status === "measuring" || !valuation.show) return <MeasuringCatalogPanel />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The || !valuation.show guard in the measuring check is redundant because the status already encodes whether the valuation is ready.

When status is "measuring", valuation.show is always false by the definition of getSetupValuationStatus. When status is "ready", valuation.show is always true. The early return for "loading" and "redirect" already ensures we only reach this line with those two remaining statuses, so !valuation.show can never alter the branch outcome.

Removing the redundant check would make the intent clearer — the status model is the single source of truth for what to render.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Onboarding/SetupValuation.tsx, line 34:

<comment>The `|| !valuation.show` guard in the measuring check is redundant because the status already encodes whether the valuation is ready.

When `status` is "measuring", `valuation.show` is always `false` by the definition of `getSetupValuationStatus`. When `status` is "ready", `valuation.show` is always `true`. The early return for "loading" and "redirect" already ensures we only reach this line with those two remaining statuses, so `!valuation.show` can never alter the branch outcome.

Removing the redundant check would make the intent clearer — the status model is the single source of truth for what to render.</comment>

<file context>
@@ -47,8 +30,8 @@ const SetupValuation = () => {
-  // valuation is still measuring (chat#1889 row 8).
-  if (!valuation.show) return <MeasuringCatalogPanel />;
+  // valuation is still measuring (chat#1889 row 8). The hook polls it out.
+  if (status === "measuring" || !valuation.show) return <MeasuringCatalogPanel />;
 
   return (
</file context>
Suggested change
if (status === "measuring" || !valuation.show) return <MeasuringCatalogPanel />;
if (status === "measuring") return <MeasuringCatalogPanel />;

Preview verification found the fix missed the case that actually happens.

Measured on a fresh signup: artist added 00:59:04, snapshot 00:59:07, catalog
created 00:59:19. createSnapshotCatalog runs only after the measurements land,
so for ~15s there is no catalog at all. Navigating to /setup/valuation in that
window read the empty list as "nothing to value" and redirected to /catalogs,
which renders "No catalogs found" — a worse dead end than the static panel
this row set out to fix, and the exact window a signup following the flow
arrives in.

The window the route was originally written for, catalog exists but not yet
measured, is about one second by comparison, because the measurements are
written first.

getSetupValuationStatus now takes hasArtists: an artist means seeding is in
flight and a catalog is coming, so that state is measuring rather than
redirect. Redirect is reserved for an account with neither. The poll
invalidates the catalogs query as well as measurements, since during seeding
it is the catalog that is missing.

Removes a test whose premise this changes: "no catalog" alone no longer means
redirect, and the two cases replacing it assert both halves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@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.

0 issues found across 4 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.

Re-trigger cubic

Second preview run still redirected. The roster loads asynchronously, so on a
fresh page load of /setup/valuation `sorted` is briefly empty, which read as
"no artists" and redirected before seeding could be detected.

That is the same trap the catalogs `isPending` comment in this file already
warns about, repeated for the roster: pending is not empty. getSetupValuationStatus
now takes artistsPending and reports loading until both reads resolve.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@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.

0 issues found across 4 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.

Re-trigger cubic

@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Preview verification — 2026-07-31

Preview https://chat-xk0csc8g2-recoup.vercel.app, built from head 1615d7fb (resolved by sha). Run on a fresh signup, because the seeding window exists exactly once per account.

Results

# Check Before Actual
1 Land on /setup/valuation right after adding the first artist redirected to /catalogs"No catalogs found" stays on /setup/valuation
2 Shows the measuring state while seeding runs n/a (redirected away) "Measuring your catalog" at 1s
3 Resolves untouched never; required a manual refresh hero renders $1.6K, range $1.1K to $2.3K, 24 tracks measured
4 Polling stops once resolved n/a request counts identical after a further 20s (catalogs: 4→4, measurements: 1→1)
5 Never redirects during the wait redirected: false across the whole observation

Timing for this account: artist added 01:26:37, catalog created 01:26:59 — a 22-second gap the route now sits through instead of bouncing out of.

resolved on route

Two defects this found that the unit tests passed straight through

Both needed a real signup; the tests were green throughout because they supply already-resolved state.

1. The fix was aimed at the wrong window. The first run still redirected to an empty /catalogs. The database showed why: createSnapshotCatalog runs only after the measurements land, so the catalog appears ~15-22s after the artist is added (measured twice: 00:59:04 → 00:59:19, and 01:26:37 → 01:26:59). For that entire window there is no catalog at all. The window this route was originally written for — catalog exists but is not yet measured — is about one second, because measurements are written first. So the original patch polled the narrow case and left the wide one redirecting away. An artist on the roster now means seeding is in flight, which is measuring, not redirect.

2. A loading roster is not an empty one. The second run still redirected. The roster loads asynchronously, so sorted is briefly empty on a fresh page load and read as "no artists". That is the same trap the comment already in this file warns about for the catalogs query — pending is not empty — which I then reproduced for the roster. artistsPending now holds the route in loading until both reads resolve.

A measurement error worth recording

My first "has it resolved?" probe matched /estimate goes stale|Set up your weekly report/. Set up your weekly report is also the CTA on the measuring panel, so the probe reported "resolved" while the measuring state was still on screen. Re-measured against estimate goes stale, which appears only in the ready branch. A detector that matches both states of the thing you are testing will confirm whatever you hoped.

Tests

getSetupValuationStatus 8 cases (including both traps above), useMeasuringPoll 4, SetupValuation 6. One pre-existing test was removed rather than adjusted: it asserted "no catalog → redirect", which this deliberately reverses; the two cases replacing it assert both halves — measuring when an artist exists, redirect when neither does.

Full suite 349 passed / 83 files, tsc --noEmit and eslint clean.

Residue

Three test accounts (sweetman+row9*@recoupable.com), one seeded catalog each. The first two were spent proving the defects above.

@sweetmantech
sweetmantech merged commit 9420679 into main Jul 31, 2026
4 checks passed
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