Stop rank tracking, Site Audit and Local SEO reporting failures as absences - #26
Merged
Merged
Conversation
Pressing "Look Up" set `runKeyword`, which hid the analyze prompt, while
`profile` stayed undefined until the response landed. Neither branch
matched in between, so the body went EMPTY for the length of a metered
DataForSEO call — on the one page where the user has just spent money and
most wants to see something happening. The only feedback was a spinner
inside the submit button.
The result area is now a QueryStateBoundary with a real loading card. Two
judgement calls in it are worth stating, because both are easy to get
backwards:
`isFetching`, not `isPending`. This query is disabled until a lookup is
authorized, and a disabled react-query stays pending forever — so
`isPending` would render "loading" on a page nobody has run yet. Verified
in the browser: the idle state still shows the analyze prompt and no
spinner.
`rowCount` is the presence of a RESPONSE, never `profile.found`. A provider
that looked and found nothing is a successful answer, and it carries
spelling/city guidance ("Try adding the city or checking the spelling").
Treating it as empty would have replaced that specific advice with a
generic shrug — the boundary would have made the page worse.
The standalone error alert is gone; the boundary owns that state, so an
error can no longer render as an alert above a blank body.
The idle analyze prompt stays OUTSIDE the boundary: it is the state before
any query exists, not a state of the query.
ci:check clean, 2,122 tests passing. Browser-verified on the idle path;
the loading and ready paths need a paid lookup this machine cannot make.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five defects found by a Codex audit of Lane 3, all one shape: a query that FAILED rendered as a confident statement that nothing EXISTS. Four of the five then invite the user to spend money fixing a problem they do not have. **A failed config read said the domain was deleted.** `$configId.tsx` destructured only `data` and `isLoading`, never `isError`, so a rejected `getRankTrackingConfigs` left `config = null` and printed "Domain configuration not found." Now the error is shown with a free retry, before the not-found branch. **A failed results read said you had never tracked anything.** `RankTrackingDomainDetail` fell through to `rows = []`, and the table announced 'No rank data yet. Click "Check Now" to run your first check.' — a first-run message shown to someone with months of history, and an invitation to buy a check they do not need. **A failed matrix read silently deleted the History tab.** `historyAvailable` was `countMatrixRuns(matrixCells ?? []) >= 2`, so a rejected request looked identical to "only one run exists" and the tab vanished with no explanation. The tab now stays and the view reports its own failure. **A failed audit-history read said "No audits yet."** Same `data ?? []` shape, same false first-run claim, on a page whose call to action is a paid audit. **A completed audit whose results failed rendered a blank page.** The condition required `resultsQuery.data`, so a rejection produced empty space under a "complete" header — no error, no retry. Re-reading a finished audit is free. Not migrated to QueryStateBoundary: these are not-found and first-run states with their own affordances (a Back button, a Check Now call to action) that a generic empty state would flatten. The defect was the missing error branch, not the empty copy, so the fix is the error branch. Two new props, `loadFailed`, on the history matrix and the audit history section — deliberately separate from `isLoading` and from emptiness, because "could not be read" and "has not happened yet" are different facts and were being conflated. ci:check clean, 2,122 tests passing. Not browser-verified: reproducing each one means making a server call fail against a project that has real data. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
flyrocketseo | 5d87706 | Jul 31 2026, 05:06 AM |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lane 3. Six defects, found by a Codex audit and confirmed by reading each one.
Five share one shape: a query that FAILED rendered as a confident statement
that nothing EXISTS. Four of those then invite the user to spend money fixing
a problem they don't have.
The false absences
$configId.tsx:20RankTrackingDomainDetail.tsx:98RankTrackingDomainDetail.tsx:110LaunchView.tsx:63audit/index.tsx:174Every one is the same root cause:
data ?? []or?? null, withisErrorneverdestructured. The absence and the failure were indistinguishable, so the UI
picked the confident one.
The rank-tracking and audit cases are the worst, because the "nothing here"
copy is attached to a paid call to action. A user whose request failed is
told to buy a check or an audit to fix it.
Plus: Local SEO went blank during a paid lookup
Pressing Look Up set
runKeyword, hiding the analyze prompt, whileprofilestayed undefined until the response landed. Neither branch matched in between,
so the body was empty for the length of a metered DataForSEO call — on the
one page where the user has just spent money and most wants to see progress.
Two judgement calls there, both easy to get backwards:
isFetching, notisPending. The query is disabled until a lookup isauthorized, and a disabled react-query stays pending forever —
isPendingwould render "loading" on a page nobody has run. Browser-verified: the idle
state still shows the analyze prompt and no spinner.
rowCountis the presence of a RESPONSE, neverprofile.found. A providerthat looked and found nothing is a successful answer carrying spelling/city
guidance. Treating it as empty would have replaced that specific advice with a
generic shrug — the boundary would have made the page worse.
Why most of these aren't QueryStateBoundary migrations
These are not-found and first-run states with their own affordances — a Back
button, a Check Now call to action — that a generic empty state would flatten.
The defect was the missing error branch, not the empty copy, so the fix is
the error branch.
Two new
loadFailedprops (history matrix, audit history section), deliberatelyseparate from
isLoadingand from emptiness: "could not be read" and "has nothappened yet" are different facts and were being conflated.
Verification
pnpm ci:checkclean, 2,122 tests passing. Local SEO browser-verified on theidle path.
Not browser-verified: reproducing each false absence means forcing a server
call to fail against a project that has real data.
🤖 Generated with Claude Code