Skip to content

Stop rank tracking, Site Audit and Local SEO reporting failures as absences - #26

Merged
ThinkingSpade merged 2 commits into
mainfrom
feat/phase2-lane3-local-rank-audit
Jul 31, 2026
Merged

Stop rank tracking, Site Audit and Local SEO reporting failures as absences#26
ThinkingSpade merged 2 commits into
mainfrom
feat/phase2-lane3-local-rank-audit

Conversation

@ThinkingSpade

Copy link
Copy Markdown
Owner

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

where what it said what was true
$configId.tsx:20 "Domain configuration not found" the config list request failed
RankTrackingDomainDetail.tsx:98 "No rank data yet. Click Check Now to run your first check." the results request failed
RankTrackingDomainDetail.tsx:110 History tab silently vanished the matrix request failed
LaunchView.tsx:63 "No audits yet" the history request failed
audit/index.tsx:174 blank page under a "complete" header the results request failed

Every one is the same root cause: data ?? [] or ?? null, with isError never
destructured. 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, while profile
stayed 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, not isPending. The query is disabled until a lookup is
    authorized, and a disabled react-query stays pending forever — isPending
    would render "loading" on a page nobody has run. Browser-verified: 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 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 loadFailed props (history matrix, audit history section), deliberately
separate from isLoading and from emptiness: "could not be read" and "has not
happened yet"
are different facts and were being conflated.

Verification

pnpm ci:check clean, 2,122 tests passing. Local SEO browser-verified on the
idle 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

ThinkingSpade and others added 2 commits July 31, 2026 00:56
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>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
flyrocketseo 5d87706 Jul 31 2026, 05:06 AM

@ThinkingSpade
ThinkingSpade merged commit 122ddac 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