Realtor — "Show conditional listings as For sale"
Why
Team Beckett (tenant 00000000-0000-4000-a000-000000000c01, slug
terry-and-sheri, teambeckett.ca) asked that a listing with an accepted
conditional offer show "For sale" instead of "Conditional" on their public
site — "Conditional" was scaring buyers off. Built as a self-serve dashboard
toggle for ALL realtor customers, default OFF (today's behaviour unchanged),
turned ON for Team Beckett only after founder approval.
What must stay true no matter the setting
- The STORED listing
statusnever changes.mapResoStatus()(src/lib/real-estate/ddf/map.ts) keeps mapping the PropTx/DDF feed's "Active Under Contract" / "Sold Conditional" / "Leased Conditional" values to'conditional'— the 2026-09-11 P0 fix this depends on is unaffected. - Full-board search still includes conditional listings
(
server/listing-search.ts). - The listing DETAIL page (
/s/[slug]/listing/[mls],ListingDetailView.tsx) is unaffected by this setting — out of scope for v1 (only the card badge inui/ListingCard.tsx:statusBadge()reads the flag). - AI chat + voice (founder decision 2026-09-25, John: "it should say for sale... and have the agent followup with the info that there is an offer"):
- Flag OFF → unchanged ("Sale pending (conditional)" in chat, "sale-conditional" on voice).
- Flag ON → chat (
chat-knowledge.tsstatusLabel(), via/api/chatbot/demo) and the real-estate voice agent (voice-agent-livekit/verticals/real_estate/tools.pyformat_listing_facts()) present conditional listings as "For sale"/"For lease" and do not volunteer offer details. - HONESTY FLOOR (AI Bridge Principle): if a visitor/caller asks directly about offers, conditions, or whether the home is really available, the assistant NEVER denies it or says there are no offers — it says the agent will share the latest on offers for that home and offers to take their details (existing lead/callback tools).
- The chat
search_listingstool result carries no status field (before or after), so it needs no change. - PRs: churchwiseai-web#1723 (chat), #1722 (voice; deploy needs QA voice review + founder go).
States
1. Flag OFF (default — every tenant unless they opt in)
- A
status: 'conditional'sale listing's card badge reads "Conditional" (brass background) — byte-identical to pre-existing behaviour. - A
status: 'conditional'lease listing's card badge also reads "Conditional" (the lease/sale distinction only applies to the active-status default branch). - Applies identically on every surface that renders
ListingCard: homepage Featured Listings,/search,/sold(moot — sold listings never carryconditional),/coming-soon(moot — same reason),/my-homesfavourites.
2. Flag ON
- A
status: 'conditional'sale listing's card badge reads "For sale" — same green (theme.forest) background as an ordinary active listing. Visually indistinguishable from a genuinely active listing's badge. - A
status: 'conditional'lease listing's card badge reads "For lease", never "For sale" — the lease/sale distinction (isLeaseListing()) is preserved; a conditional lease must never be mislabeled as a sale (this repeats the 2026-08-19 Team Beckett rental mislabeling bug in the opposite direction if it regresses). - Sold / pending / coming_soon / active badges are completely unaffected —
only the
conditionalcase branches on the flag. - The price overlay, MLS number, brokerage attribution line, and every other card field are unaffected — only the status badge label/colour changes.
Dashboard
- Setting lives at
/realtor/app/settings/feeds-> "Listing display" panel (Tracking & Compliance tab region of the Website builder,WebsiteBuilderBody.tsx'sComplianceTab). - Checkbox label: "Show conditional listings as For sale".
- Help text: "When a listing has an accepted conditional offer, your website shows it as For sale until it's sold. Off: it shows as Conditional. Check that this fits your board's display rules."
- Save is immediate-publish (like the hero/videos editors) — no separate
"Publish" step; the public site reflects the new setting within the Data
Cache revalidation the save triggers (
revalidateRealEstateSiteConfig+revalidatePathon/s/[slug],/search,/sold,/coming-soon). - Gated
settings:compliance:editRBAC capability — available to every realtor tenant (NOT restricted to the Team Beckett/TeamMoelker demo floor the older bearer-token/api/real-estate/site-contenteditor carries). - Toggling OFF after being ON immediately reverts every conditional badge to "Conditional" (no caching lag beyond the same revalidation window).
Storage
local_businesses.metadata.listing_display = { conditional_shows_as_for_sale: boolean, updated_at: string }— additive jsonb key, no schema/DDL change. Same pattern asmetadata.ddf,metadata.brokerage_identity,metadata.vow.- Normalized via
src/lib/real-estate/listing-display-settings.ts(normalizeListingDisplaySettings— any absent/malformed value resolves tofalse, never throws). - Resolved into
RESiteConfig.conditionalShowsAsForSaleper-request inserver/site-content.ts:resolveRealEstateSiteConfigLive, before the listings-mode branches, so it applies uniformly to every render mode (homepage/search/sold/coming-soon/full).
API
GET /api/real-estate/website/listing-display?account_id=<id>returns{ settings: { conditionalShowsAsForSale } }.PUT /api/real-estate/website/listing-display?account_id=<id>with body{ value: { conditionalShowsAsForSale: boolean } }saves and returns the new settings. A malformedvalue(non-object/null/array) is a 400, never a silent no-op success.- Tenant is always resolved from the authenticated realtor membership
context (
resolveRealtorContext) — never trusted from the request body.
Turning it on for Team Beckett (tenant c01)
Requires founder approval before execution (no agent DB write). Either:
- Dashboard (preferred): sign in as a Team Beckett realtor member with
settings:compliance:edit, open/realtor/app/settings/feeds, toggle "Show conditional listings as For sale" on. - SQL (founder-run only):
UPDATE local_businesses
SET metadata = jsonb_set(
coalesce(metadata, '{}'::jsonb),
'{listing_display}',
jsonb_build_object(
'conditional_shows_as_for_sale', true,
'updated_at', now()::text
)
)
WHERE id = '00000000-0000-4000-a000-000000000c01';
Then either wait out the Data Cache TTL or call
revalidateRealEstateSiteConfig('00000000-0000-4000-a000-000000000c01')
(e.g. via a redeploy or the dashboard save path) so teambeckett.ca reflects
it immediately.
Verification checklist (run against the deployed URL before declaring done)
- With the flag OFF on a tenant with a real conditional listing (or a seeded fixture), the card badge reads "Conditional".
- Toggle ON via the dashboard; confirm the API response
ok: trueand the returnedsettings.conditionalShowsAsForSale === true. - Reload the public site (or the affected page) and confirm the same listing's badge now reads "For sale" (or "For lease" if it's a lease row).
- Confirm the listing DETAIL page is unchanged.
- Flag ON: ask the site chat (and, once #1722 is deployed, the voice line) about a conditional listing that is in its knowledge — it is described as for sale. Then ask "are there any offers on it?" — the answer must NOT deny an offer; it says the agent will share the latest and offers to take details.
- Toggle OFF; confirm the badge reverts.
- Confirm no OTHER tenant's badge changed during this test (the default stays OFF for everyone else).