Skip to content

fix: stop publishing aged-out listings and leaking verification vocabulary - #9

Merged
Asapteejo merged 3 commits into
mainfrom
fix/public-listing-visibility
Sep 18, 2026
Merged

Asapteejo merged 3 commits into
mainfrom
fix/public-listing-visibility

Conversation

@Asapteejo

Copy link
Copy Markdown
Owner

Correctness pass from the tenant public-site audit. The homepage restructure is deliberately not here — it is layout work, whereas this changes what is publicly visible and should be reviewable (and revertable) on its own.

1. Aged-out listings stayed public — the real bug

A live tenant's site showed a property card reading "Listing hidden", publicly.

The query filter is not at fault: it requires isPubliclyVisible: true and a VERIFIED/STALE status. But those are stored columns, written only when a property is mutated — updateVerificationState runs on mutations and in the presentation layer, and there is no sweep job that ages them.

The presented status is recomputed from lastVerifiedAt on every render. So a listing that crossed the hide threshold kept its stored "visible" flag, stayed on the site, and rendered the internal label. The policy was enforced in UI copy and not in the query — while the homepage promises "no stale or hidden inventory" directly above it.

The public entry points (listing, detail, brochure) now pass a cutoff of now - hideDays, taken from the company's own thresholds, so the rule the presentation applies is enforced in SQL. Stored flags are still honoured, so manual hiding still works; the cutoff parameter is optional, so non-public callers are unchanged.

Consequence worth knowing before merge: an aged-out listing leaves the public site until someone re-verifies it. For the audited tenant that is its only listing, so its listings page will be empty until it is re-verified. That is the intended policy — it is what the "Listing hidden" label already claimed was happening.

2. Internal vocabulary reaching buyers

The card rendered a "Stale" badge for anything not VERIFIED and printed verification.label verbatim ("Listing hidden", "Verification required"). Only the positive state is public now: a "Verified" badge and label when verified, nothing otherwise.

3. Footer links ran together — my regression

From the mobile touch-target pass: links moved from block to inline-flex for a 44px target, but their container still used space-y-2, a margin between block-level boxes. Inline-level boxes flow onto one line, so the footer rendered ListingsBuyer PortalAdmin Dashboard at every width. Containers are flex columns now, keeping inline-flex + min-h-11.

Regression test added and verified both directions: it fails against the old markup ("footer links in the same column share a line") and passes against the fix. It compares links per column, since separate columns legitimately share a line on desktop, and asserts the 44px height on phone viewports only — above sm the links deliberately return to desktop density.

Verification

npm run check 537 tests (2 new), typecheck, lint, build — green
E2E suite 63 passed; 2 admin-route timeouts under local load that pass in 11.3s in isolation — unrelated to these files
Footer test fails on old markup, passes on fix

🤖 Generated with Claude Code

Asapteejo and others added 3 commits September 18, 2026 11:37
A live tenant's public site showed a card labelled "Listing hidden". The
query filter was not at fault — it correctly requires isPubliclyVisible and
a VERIFIED/STALE status. Those columns are STORED values, though, written
only when a property is mutated, and nothing ages them: updateVerificationState
runs on mutations and in the presentation layer, and there is no sweep job.

The presented status, by contrast, is recomputed from lastVerifiedAt on every
render. So once a listing crossed the hide threshold it kept its stored
"publicly visible" flag, stayed on the site, and rendered the internal label
"Listing hidden" — the policy was enforced in UI copy but not in the query.
The homepage promises "no stale or hidden inventory" directly above it.

The public entry points (listing, detail, brochure) now pass a cutoff of
now - hideDays, using the company's own thresholds, so the same rule the
presentation applies is enforced in SQL. Stored flags are still honoured, so
manual hiding keeps working, and the cutoff is optional so non-public callers
are unaffected.

Consequence worth knowing: a listing that has aged out disappears from the
public site until someone re-verifies it. That is the intended policy — it is
what the "Listing hidden" label already claimed was happening.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The public property card rendered a "Stale" badge for anything not VERIFIED
and printed property.verification.label verbatim, which produces operator
strings such as "Listing hidden" and "Verification required" in front of
buyers. On a site whose pitch is verified listings, that reads as a warning
about the company rather than about the listing.

Only the positive state is public now: a "Verified" badge and its label when
the listing is verified, and nothing otherwise. Absence is quieter than a
warning, and the internal workflow stays internal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Regression I introduced in the mobile touch-target pass: the footer links
were changed from `block` to `inline-flex` to get a 44px tap target, but
their container still separated children with `space-y-2`, which is a margin
between BLOCK-level boxes. Inline-level boxes flow onto one line instead, so
the footer rendered "ListingsBuyer PortalAdmin Dashboard" and
"AboutTeamCareersContact" at every width.

The containers are now flex columns, so each link starts its own line while
keeping inline-flex + min-h-11 and the 44px target.

The E2E sweep now asserts this per column (separate columns legitimately share
a line on desktop) and checks the 44px height on phone viewports only, since
above `sm` the links deliberately return to desktop density. Verified the test
fails against the old markup and passes against the fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
estate-os Ready Ready Preview Sep 18, 2026 10:41am UTC

This branch was successfully deployed

1 active deployment
Preview — 185fe473 Deployed Sep 18, 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.

1 participant