Skip to content

Copy + UX: fear hook on FindJobs no-scan state, WinStatsBanner zero-state, TrustCenter accuracy - #504

Merged
manazoid4 merged 25 commits into
mainfrom
nightly/2026-08-24-copy-ux
Aug 24, 2026
Merged

manazoid4 merged 25 commits into
mainfrom
nightly/2026-08-24-copy-ux

Conversation

@manazoid4

Copy link
Copy Markdown
Owner

Summary

  • FindJobsPage — no-scan state: Replaced generic SVG map illustration and corporate "CHECK THE CURRENT PUBLIC-TENDER FEED" headline with a fear→proof→control hook: "Every week you don't scan is work your competitors are pricing" + specific proof line naming planning, energy, and contract signals. Removes decorative noise, adds urgency that a tradesman reads in under 2 seconds.

  • WinStatsBanner — zero-state: Component previously returned null when wonCount === 0, so users never saw the banner after a scan in an area with no logged wins. Now shows a subdued placeholder: "No wins logged in your area yet — be the first to track a job won." This surfaces the concept of win-tracking to users who haven't seen it before.

  • TrustCenterPage — accuracy fix: guaranteeFeatures listed "One exclusive territory lock — your patch, nobody else in it" which implies no other tradesperson can scan the same postcode — that's not true and would damage trust if a user investigated. Changed to "Your scan is private — other trades scanning the same postcode see nothing of yours" which is accurate and still strong.

Test plan

  • Load /find-jobs without scanning — confirm fear-hook headline shows, SVG map is gone
  • Enter a postcode with no wins and scan — confirm WinStatsBanner shows zero-state placeholder
  • Check /trust — confirm guarantee features no longer mention "exclusive territory lock"
  • npm run build — passes
  • npx tsc --noEmit — passes (0 errors)

Generated by Claude Code

…Banner zero-state, TrustCenter accuracy fix

- FindJobsPage: Replace SVG map + "CHECK THE CURRENT PUBLIC-TENDER FEED" with fear→proof→control hook ("Every week you don't scan is work your competitors are pricing")
- WinStatsBanner: Add zero-state placeholder ("No wins logged in your area yet — be first to track a job won") so the component shows after a scan even with no wins data yet
- TrustCenterPage: Fix misleading "One exclusive territory lock — your patch, nobody else in it" claim → accurate "Your scan is private — other trades scanning the same postcode see nothing of yours"
@supabase

supabase Bot commented Aug 24, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project nfjwuwsuaapufmkppoeo because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@vercel

vercel Bot commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
job-filter-v1 Ready Ready Preview Aug 24, 2026 8:36am

@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: b69e70aac4

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/WinStatsBanner.tsx Outdated
… on postcode change

Add AbortController to cancel previous fetch when postcode prop updates. Prevents
a late-resolving response from an old postcode overwriting the current result now
that zero-win state is also committed (not silently dropped).

@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: e0ead09a8f

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/WinStatsBanner.tsx Outdated
Reset stats and loaded state when postcode is empty, so the banner hides
immediately rather than showing the previous area's zero-win message.

@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: 92dd758fb4

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/WinStatsBanner.tsx
…ro-state

readOutcomeRows() applied a LIMIT 1000 globally before postcode filtering,
so older area wins could be excluded and WinStatsBanner would falsely show
"no wins logged". New readWonRowsByArea() queries with status=won and
ilike postcode filter pushed to Supabase, no truncation risk.

@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: fa20291428

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/WinStatsBanner.tsx Outdated
Editing from one non-empty postcode to another left the previous area's
result visible during the pending request. Now resets stats/loaded at
the start of every new fetch so the banner hides until fresh data arrives.

@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: fbcffc3a3e

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/routes/outcomeReport.ts Outdated
readWonRowsByArea fetched rows client-side, still subject to PostgREST's
server-side 1000-row cap. New readWonStatsByArea uses count()+won_value.sum()
in the select, so aggregation happens in the DB and a single response returns
exact totals regardless of the number of matching rows.

@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: 8559879721

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/routes/outcomeReport.ts Outdated
PostgREST returned count()/sum() under keys 'count'/'sum', not the names
the reader expected — so wonCount and totalValue always fell back to 0.
Aliased the aggregates in the select as won_count and won_value_sum so
the response key matches what the reader unwraps.
@alwaysmeticulous

alwaysmeticulous Bot commented Aug 24, 2026 •

Copy link
Copy Markdown

✅ Meticulous spotted 0 visual differences across 169 screens tested: view results.

Meticulous evaluated ~4 hours of user flows against your PR.

Expected differences? Click here. Last updated for commit cbe6a37 fix(wins-stats): anchor area query to digit boundary, not bare prefix. This comment will update as new commits are pushed.

@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: 9109c33377

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/WinStatsBanner.tsx
… slice

Prior slice(0,2) turned B14 into B1 (wrong: matches B10-B19, misses B2-B9,
B20+), so a Birmingham user could get a false zero-state when wins exist
elsewhere in area B. Now uses outwardFromPostcode + /^[A-Z]+/ to extract
the actual area, matching how server/utils/postcode.ts already handles it.

@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: caf166b0fe

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/routes/outcomeReport.ts Outdated
The prior `ilike '${area}%'` bled into neighbouring areas whose codes
start with the same letter — a B14 request also counted BT (Belfast), BA
(Bath), BD, BH, BL, BN, BR, BS wins; E1 counted EC/EH/EN/EX; S1 counted
SA/SE/SG/SK/SL/SM/SN/SO/SP/SR/SS/ST/SW/SY. UK outward codes always follow
letters with a digit, so switching from ilike to a POSIX regex
(`^${area}[0-9]`) via PostgREST's imatch operator enforces that boundary
and confines the aggregate to the actual postcode area.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012cMAaJrqSu6Bbrtyn9gCPU

@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: a318d8b273

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/routes/outcomeReport.ts
Add .not('user_id', 'is', null) to the aggregate query in
readWonStatsByArea so it operates on the same population as
countDistinctContributors. Prevents legacy null-user_id rows from
inflating the released aggregate under a k=3 privacy claim built from
identified contributors only.

@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: 5455580acf

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/routes/outcomeReport.ts Outdated
Filter .not('won_value', 'is', null) on both the aggregate and the
distinct-contributor gate in readWonStatsByArea so k=3 measures
distinct users who recorded a value, and the released totalValue
covers exactly those users' values. Prevents a k=3 release from
publishing a single user's exact recorded won_value when the other
two contributors saved wins without an amount.

@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: 015ebe4834

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/routes/outcomeReport.ts Outdated
Comment thread server/routes/outcomeReport.ts Outdated
Add distinctValueContributors as a second gate scoped to wins with a
recorded won_value. distinctWinContributors gates suppression and the
wonCount publish; distinctValueContributors gates the totalValue
publish separately. An area whose wins were all logged without an
amount now surfaces the count with a no-amount message rather than
falsely claiming no wins are logged.

@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: 74dbb5eb43

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/routes/outcomeReport.ts Outdated
toMoneyInt preserves explicit 0 rather than nulling it, so
won_value = 0 rows pass .not('won_value', 'is', null). An
area with three contributors where only one supplied a
positive amount could satisfy the k=3 value threshold and
publish that single user's exact sum as totalValue.

Switch both the distinct-value-contributor gate and the sum
aggregate from IS NOT NULL to > 0. Zero-value rows no longer
count toward the value cohort, and the sum stays k=3
protected against exact-amount inference.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012cMAaJrqSu6Bbrtyn9gCPU

@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: 4b4a08c64b

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/WinStatsBanner.tsx Outdated
Every keystroke changed `postcode` and reran the effect, firing
a fresh /api/wins/stats request even when the parsed outward was
identical. Aborting fetch on the client does not cancel the
server-side Express handler or its Supabase queries, so a full
"SW1A 1AA" entry triggered ~7 wasted request cascades — each up
to eight sequential Supabase queries via countDistinctContributors
+ aggregate.

Derive `outward` with useMemo and depend the effect on outward,
so the request only fires when the parsed area actually changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012cMAaJrqSu6Bbrtyn9gCPU

@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: 9b9c53e4fe

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/WinStatsBanner.tsx Outdated
split(' ')[0] produces successive outward strings for compact
postcodes — M11AA → M1, M11, M11AA as the user types, firing
three requests. Use the same UK postcode regex as the server's
outwardFromPostcode: clean non-alphanumerics, match the leading
outward pattern. M11AA now produces '' → M1 → M11 → '' → M1,
so the effect only fires when a structurally valid outward is
derivable, not on every intermediate character.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012cMAaJrqSu6Bbrtyn9gCPU

@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: 989d605fd1

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/WinStatsBanner.tsx Outdated
Comment thread server/routes/outcomeReport.ts
Two fixes for the unauthenticated /api/wins/stats endpoint:

1. Add rateLimit middleware to /api/wins/stats — consistent with
   all other public DB-backed routes (/api/waitlist/count etc).
   Previously unthrottled despite triggering up to 8 Supabase
   queries per call via countDistinctContributors + aggregates.

2. Key the WinStatsBanner effect on the postcode area (leading
   letters) rather than the outward. B1, B14, B147 all share
   area B — the server queries ^B[0-9] regardless of which
   outward is sent. Changing the effect dep from outward to area
   means typing B14 7QH fires one request (when area first
   becomes B on B1), never again until the area changes. Send
   ${area}1 as a minimal valid outward so the server can parse it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012cMAaJrqSu6Bbrtyn9gCPU

@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: 8af8310d75

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/routes/outcomeReport.ts Outdated
The shared rateLimit middleware keys only on IP, so banner
requests against /api/wins/stats consumed the same 20-req/min
bucket as /api/leads/search. Twenty page loads or area queries
could 429 a subsequent scan even though the user never hit the
scan limit.

Add makeRateLimit(limit) factory to server/middleware/rateLimit.ts
— each call returns a middleware with its own isolated Map and
configurable limit. Preserve the original rateLimit export
(including its scan-specific 429 body) for backward compatibility.

Wire /api/wins/stats to rateLimitStats = makeRateLimit(60): a
generous separate bucket (banner fires once per area) that never
interferes with scan quota.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012cMAaJrqSu6Bbrtyn9gCPU

@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: 121949eb61

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/pages/FindJobsPage.tsx Outdated
Remove the unverifiable claim that JobFilter surfaces jobs before
Checkatrade/Bark/MyBuilder. Replace with factually accurate copy:
"from official sources, before the job reaches the quote sites."

Addresses Codex r3841840108.
@manazoid4
manazoid4 merged commit 5489192 into main Aug 24, 2026
4 checks passed
@manazoid4
manazoid4 deleted the nightly/2026-08-24-copy-ux branch August 24, 2026 08:37

@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: 6e9aa273b3

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.from('lead_outcomes')
.select('user_id')
.eq('status', 'won')
.filter('postcode_outward', 'imatch', areaFilter)

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 Avoid asserting zero when wins lack outward postcodes

When a win comes from a feed without an exact postcode, deriveOutward() stores UK (leadEngine/normaliser.ts:150-158), and FindJobsPage forwards that value unchanged when recording the outcome (src/pages/FindJobsPage.tsx:1405-1418). This area regex excludes every such win from the contributor check, so an area whose tracked wins came from those supported contract feeds receives distinctWinContributors === 0 and the new banner states “No wins logged,” even though the database contains wins that cannot be geographically attributed. Preserve the user's searched outward when logging the outcome, or treat unlocalized outcome data as unavailable rather than an exhaustive zero.

Useful? React with 👍 / 👎.

This branch was successfully deployed

1 active deployment
Preview — 6e9aa273 Deployed Aug 24, 2026 by vercel[bot]
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.

2 participants