Skip to content

fix: valuation run shows progress and stops prefetching the alphabet - #59

Merged
sweetmantech merged 1 commit into
mainfrom
fix/1902-valuation-progress
Jul 31, 2026
Merged

fix: valuation run shows progress and stops prefetching the alphabet#59
sweetmantech merged 1 commit into
mainfrom
fix/1902-valuation-progress

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Part of recoupable/chat#1902 (web UX conversion audit, item M5). Two defects on /valuation (also the homepage hero, which renders the same CatalogValuation):

1. The valuation run looked broken for ~60 seconds

Clicking "Value my catalog" fires one synchronous POST /api/valuation that measured ~60s on a 238-track catalog (Mac DeMarco) while every control went disabled, with the only feedback buried in the input's placeholder text. Users assumed the page broke.

This PR keeps the synchronous POST (async/workflow conversion is out of scope) and adds a visible progress state that appears immediately on click:

  • ValuationProgress: a card below the search box with a subtle pulsing dot, the existing staged status line ("Finding your releases…" → "Measuring play counts…" → "Computing your valuation…"), a live elapsed-time ticker ("42s", "1m 15s"), and a persistent expectation line: "Measuring live play counts across your catalog. Large catalogs can take a minute or two." Announced via role="status" / aria-live="polite".
  • ValuationError: the error state is now a readable card with a "Try again" button that re-fires the run for the same picked artist (previously a bare red line with no recovery path). Error copy rewritten without dashes.
  • New logic helpers, TDD'd: formatElapsedSeconds (+ unit tests) and a useElapsedSeconds hook.

2. Page load fired 26 search requests

Mounting the search box prefetched GET /api/spotify/search?q=<letter> for every letter a-z to warm the autocomplete cache: 26 requests on every fresh page load before the user touched anything. Removed lib/spotify/prefetchLetters.ts and its mount effect; the cache now seeds lazily from the existing debounced fetch on the first keystroke (getCachedApprox still narrows from previously cached prefixes). A fresh page load now fires zero search requests.

How to test

  1. Open /valuation with the network panel filtered to spotify/search: zero requests on load. Type a letter: the first (debounced) request fires.
  2. Pick an artist with a large catalog and click "Value my catalog": within a second a progress card appears with the staged status line, a ticking elapsed counter, and the expectation copy; the send button keeps its spinner. The card stays until the result renders.
  3. Force a failure (e.g. an artist with no releases): a readable error card appears with a "Try again" button; clicking it re-runs the same valuation.

Preview verification + screenshots to follow in a PR comment.

🤖 Generated with Claude Code


Summary by cubic

Show progress during valuation runs and remove alphabet prefetch in artist search to fix broken-looking UX and prevent unnecessary requests on load. Addresses recoupable/chat#1902 (M5).

  • New Features

    • Added a progress card with a pulsing dot, staged status, elapsed timer, and aria-live status.
    • Added a readable error card with improved copy and a Try again action.
  • Bug Fixes

    • Removed the a–z search prefetch; artist search now fetches lazily on first keystroke (zero requests on initial load; previously 26).

Written for commit d456486. Summary will update on new commits.

Review in cubic

Two /valuation defects from the web UX conversion audit
(recoupable/chat#1902, item M5):

1. The synchronous POST /api/valuation (~60s on a 238-track catalog)
   disabled every control with no visible feedback. Add a progress card
   that mounts with the run: pulsing dot, staged status line, live
   elapsed-time ticker, and expectation copy. Replace the bare error
   line with a readable error card and a Try again button.

2. Mounting the search box prefetched /api/spotify/search for every
   letter a-z (26 requests per fresh page load). Remove the prefetch;
   the cache now seeds lazily from the debounced fetch on the first
   keystroke.

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

cursor Bot commented Jul 29, 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 29, 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 29, 2026 3:03pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in: 57 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9225c4cb-a4b1-4348-be02-d88e3fff8ca1

📥 Commits

Reviewing files that changed from the base of the PR and between e1a67c7 and d456486.

📒 Files selected for processing (11)
  • components/valuation/CatalogValuation.tsx
  • components/valuation/ValuationError.tsx
  • components/valuation/ValuationProgress.tsx
  • hooks/useCatalogValuation.ts
  • hooks/useElapsedSeconds.ts
  • hooks/useSpotifyArtistSearch.ts
  • lib/spotify/prefetchLetters.ts
  • lib/spotify/searchCache.ts
  • lib/valuation/__tests__/formatElapsedSeconds.test.ts
  • lib/valuation/formatElapsedSeconds.ts
  • lib/valuation/runValuation.ts

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.

No issues found across 11 files

Re-trigger cubic

@sweetmantech

Copy link
Copy Markdown
Contributor Author

Preview verification — 2026-07-30

Preview https://marketing-o5jbp5lhp-recoup.vercel.app/valuation, built from head d456486f (deployment resolved by sha). Control measurements taken against production in an identical browser context.

Results

# Check Before (prod, measured) Actual on preview
1 Alphabet prefetch on a fresh /valuation load 26 requests — …/api/spotify/search?q=a&limit=10 through q=z 0
2 Any Spotify search on load 26 0
3 Search still works after removing the prefetch typing BennyJ504 returns matches, artist selectable
4 Artist selection enables the run Value my catalog enabled with the artist chip shown
5 Unit tests 23 passed / 6 files
6 Build gate pnpm build exit 0, 47 pages
7 Progress appears within 1s of the click ~60s frozen form, no indicator not exercised live — see below ⚠️

Row 1 is the headline, and I nearly reported it wrong. My first probe matched ?q=<letter> anchored to end-of-string, which missed the real &limit=10 suffix — production came back as 0 and looked already-fixed. Re-measured with the correct pattern, prod fires 26 and the preview fires 0. Worth flagging because a false negative there would have "verified" a change that had not happened.

Row 7: what I could and could not verify

Not exercised live. The run is behind the Privy gate — useCatalogValuation.run() calls login() and defers when signed out — so reaching the progress state needs an authenticated session on this preview origin, and the OTP did not arrive. Rather than claim a pass, here is exactly what is and is not established.

Established from the code, and it is a stronger guarantee than a stopwatch:

async function doRun(artist: Artist) {
  setPhase("running");   // ← synchronous
  setError("");
  startProgress();       // ← sets PROGRESS_STAGES[0] synchronously
  try {
    const token = await getAccessToken();   // first await comes after

ValuationProgress renders whenever phase === "running", and both the phase flip and the first progress line are set before the first await. So the progress block mounts in the same React commit as the click; the 1s criterion is structural, not a timing race. useElapsedSeconds starts at mount, so the ticker reads 0s in that same commit.

Not established: that it looks right in a browser — spacing, the pulsing dot, the elapsed ticker incrementing, and the stage copy advancing every 9s. That needs one authenticated run and I would like to do it before merge.

Pre-run state (artist picked, run enabled, no prefetch fired)

valuation with artist picked

One thing surfaced, deliberately not fixed here

The elapsed counter and the whole run are client-side React state with nothing persisted — no sessionStorage or localStorage anywhere in hooks/, lib/valuation/ or components/valuation/. A refresh mid-run resets the counter to 0s, drops the progress line, aborts the in-flight POST /api/valuation, and returns the page to idle as if nothing had happened.

That is a plausible route to the "I went through twice" report this row traces back to. It is out of scope for this PR — making a run survive a refresh needs the endpoint to become asynchronous and hand back an id the page can track, which is an architecture change, not a progress indicator. Two mitigations already landed today: this PR makes the wait legible, and api#800's 60-minute reuse window means a re-run no longer re-scrapes or re-bills.

Recommend tracking the resumable run as its own row rather than expanding this one.

Local build note

pnpm build fails locally without NEXT_PUBLIC_PRIVY_APP_ID (Error: Missing required NEXT_PUBLIC_PRIVY_APP_ID environment variable while prerendering /_not-found). With the variable set it exits 0. Environment gap, not a code defect — Vercel's build for this PR passes.

@sweetmantech

Copy link
Copy Markdown
Contributor Author

Happy path completed — 2026-07-30

Follow-up to the previous comment, which stopped at the Privy gate. Signed in as sweetman@recoupable.com on the preview and ran a real valuation for BennyJ504 (24 albums) end to end.

Results

# Check Before Actual
1 Progress appears after the click ~60s frozen form, every control disabled, no indicator 62 ms to Finding your releases…
2 Within the 1s criterion 62 ms, 16× inside budget
3 Elapsed ticker increments no ticker 0s at 62 ms → 1s at 1091 ms → 2s at 2115 ms → 3s at 3141 ms
4 Run completes to a real result $2K, range $1K – $2K, valued across 24 measured tracks
5 Result offers a next step Get the full report with Recoup →
6 Real work happened, not a stub playcount_snapshots row 4729c2f2, 24 albums, $0.0720, state done

Timings are performance.now() deltas from the click, sampled every 50 ms — not eyeballed.

Progress state, mid-run

progress running

Completed result

valuation result

One thing I could not capture

The stage copy advancing (Finding your releases…Measuring play counts…Computing your valuation… on a 9s cadence). This run finished before the second stage was due, so only the first line was ever displayed. That is the staged copy working as designed on a fast run, not a defect — but the later lines remain unobserved, and a large catalog is where they matter. Worth one run against a big artist before anyone treats the full sequence as proven.

Related and worth stating: those stages are advanced by a timer, not by server phases — PROGRESS_STAGES steps every 9s regardless of what the run is doing, and stalls on the last line until the call resolves. Accurate reporting needs the async conversion discussed on the issue.

Combined with the earlier comment

Full picture for this PR: alphabet prefetch 26 → 0 (measured against prod as a control), search and selection unaffected, 23 tests / 6 files, pnpm build exit 0, and now the happy path verified with hard timings and a real $0.072 valuation.

The refresh limitation from the previous comment still stands and is still out of scope: a mid-run refresh drops the counter, the progress line and the in-flight request. Recommend tracking the resumable run separately.

@sweetmantech
sweetmantech merged commit f366be5 into main Jul 31, 2026
3 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