[object Object]

← back to Homesonspec

HARD RULE: demo/synthetic inventory NEVER surfaces live — isDemo:false filter on search + all consumer queries, isDemo 404-guards on detail pages, banner drops demo language (real builder data only)

f895480f95535134ef95175e65db941b95df29ef · 2026-07-22 13:33:26 -0700 · Steve Abrams

Files touched

Diff

commit f895480f95535134ef95175e65db941b95df29ef
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jul 22 13:33:26 2026 -0700

    HARD RULE: demo/synthetic inventory NEVER surfaces live — isDemo:false filter on search + all consumer queries, isDemo 404-guards on detail pages, banner drops demo language (real builder data only)
---
 apps/web/src/app/[state]/[city]/page.tsx     |  2 +-
 apps/web/src/app/builders/[slug]/page.tsx    |  6 ++--
 apps/web/src/app/communities/[slug]/page.tsx |  4 +--
 apps/web/src/app/homes/[id]/page.tsx         |  2 +-
 apps/web/src/app/layout.tsx                  |  9 +++--
 apps/web/src/app/page.tsx                    |  5 +--
 apps/web/src/app/plans/[id]/page.tsx         |  2 +-
 packages/search/src/index.ts                 |  3 +-
 retarget-domain.sh                           | 50 ++++++++++++++++++++++++++++
 9 files changed, 67 insertions(+), 16 deletions(-)

diff --git a/apps/web/src/app/[state]/[city]/page.tsx b/apps/web/src/app/[state]/[city]/page.tsx
index c66ddd3..477bdd1 100644
--- a/apps/web/src/app/[state]/[city]/page.tsx
+++ b/apps/web/src/app/[state]/[city]/page.tsx
@@ -17,7 +17,7 @@ export default async function CityLandingPage({
   const cityName = decodeURIComponent(city).replace(/-/g, " ");
 
   const homes = await prisma.inventoryHome.findMany({
-    where: { state: stateUpper, city: { equals: cityName, mode: "insensitive" }, status: "PUBLISHED" },
+    where: { state: stateUpper, city: { equals: cityName, mode: "insensitive" }, status: "PUBLISHED", isDemo: false },
     include: { community: { select: { slug: true, name: true } }, builder: { select: { name: true } } },
     orderBy: { publishedAt: "desc" },
     take: 24,
diff --git a/apps/web/src/app/builders/[slug]/page.tsx b/apps/web/src/app/builders/[slug]/page.tsx
index fdda37a..0ce758b 100644
--- a/apps/web/src/app/builders/[slug]/page.tsx
+++ b/apps/web/src/app/builders/[slug]/page.tsx
@@ -11,12 +11,12 @@ export default async function BuilderPage({ params }: { params: Promise<{ slug:
     where: { slug },
     include: {
       divisions: true,
-      communities: { include: { _count: { select: { homes: { where: { status: "PUBLISHED" } } } } } },
+      communities: { include: { _count: { select: { homes: { where: { status: "PUBLISHED", isDemo: false } } } } } },
     },
   });
-  if (!builder) notFound();
+  if (!builder || builder.isDemo) notFound();
 
-  const homeCount = await prisma.inventoryHome.count({ where: { builderId: builder.id, status: "PUBLISHED" } });
+  const homeCount = await prisma.inventoryHome.count({ where: { builderId: builder.id, status: "PUBLISHED", isDemo: false } });
 
   return (
     <div className="mx-auto max-w-5xl px-4 py-8">
diff --git a/apps/web/src/app/communities/[slug]/page.tsx b/apps/web/src/app/communities/[slug]/page.tsx
index daa47c1..5a9d9cf 100644
--- a/apps/web/src/app/communities/[slug]/page.tsx
+++ b/apps/web/src/app/communities/[slug]/page.tsx
@@ -14,10 +14,10 @@ export default async function CommunityPage({ params }: { params: Promise<{ slug
       floorPlans: true,
       salesOffices: true,
       incentives: true,
-      homes: { where: { status: "PUBLISHED" }, orderBy: { publishedAt: "desc" } },
+      homes: { where: { status: "PUBLISHED", isDemo: false }, orderBy: { publishedAt: "desc" } },
     },
   });
-  if (!community) notFound();
+  if (!community || community.isDemo) notFound();
 
   return (
     <div className="mx-auto max-w-6xl px-4 py-8">
diff --git a/apps/web/src/app/homes/[id]/page.tsx b/apps/web/src/app/homes/[id]/page.tsx
index 9aafc01..df8c462 100644
--- a/apps/web/src/app/homes/[id]/page.tsx
+++ b/apps/web/src/app/homes/[id]/page.tsx
@@ -25,7 +25,7 @@ export default async function HomeDetailPage({ params }: { params: Promise<{ id:
       incentives: true,
     },
   });
-  if (!home || home.status !== "PUBLISHED") notFound();
+  if (!home || home.status !== "PUBLISHED" || home.isDemo) notFound();
 
   const [communityIncentives, evidence] = await Promise.all([
     prisma.incentive.findMany({ where: { communityId: home.communityId, scope: "COMMUNITY" } }),
diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx
index 9184828..90045b9 100644
--- a/apps/web/src/app/layout.tsx
+++ b/apps/web/src/app/layout.tsx
@@ -12,11 +12,10 @@ export default function RootLayout({ children }: { children: React.ReactNode })
   return (
     <html lang="en">
       <body className="min-h-screen bg-white text-neutral-900 antialiased">
-        {/* Early-access honesty: real builder-website data + labeled synthetic demo data coexist. */}
-        <div className="bg-violet-700 px-4 py-1.5 text-center text-xs font-medium text-white">
-          Early preview — listings labeled “Verified from builder website” come from live builder sites;
-          listings labeled “Demonstration data” are synthetic samples. Every listing shows its verification
-          label and last-verified time.
+        {/* Live inventory only — every listing is real builder data with a verification label. */}
+        <div className="bg-teal-800 px-4 py-1.5 text-center text-xs font-medium text-white">
+          Early preview — every listing is verified from the builder’s own website and shows its verification
+          label and last-verified time. New builders are being added continuously.
         </div>
         <header className="border-b border-neutral-200">
           <nav className="mx-auto flex max-w-7xl items-center justify-between px-4 py-3">
diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx
index eddf60d..5641d3c 100644
--- a/apps/web/src/app/page.tsx
+++ b/apps/web/src/app/page.tsx
@@ -6,10 +6,11 @@ export const dynamic = "force-dynamic";
 export default async function HomePage() {
   const markets = await prisma.community.groupBy({
     by: ["metro", "state"],
+    where: { isDemo: false, homes: { some: { status: "PUBLISHED", isDemo: false } } },
     _count: { _all: true },
   });
-  const homeCount = await prisma.inventoryHome.count({ where: { status: "PUBLISHED" } });
-  const builderCount = await prisma.builder.count();
+  const homeCount = await prisma.inventoryHome.count({ where: { status: "PUBLISHED", isDemo: false } });
+  const builderCount = await prisma.builder.count({ where: { isDemo: false } });
 
   return (
     <div>
diff --git a/apps/web/src/app/plans/[id]/page.tsx b/apps/web/src/app/plans/[id]/page.tsx
index 8aa6670..e28625b 100644
--- a/apps/web/src/app/plans/[id]/page.tsx
+++ b/apps/web/src/app/plans/[id]/page.tsx
@@ -12,7 +12,7 @@ export default async function PlanPage({ params }: { params: Promise<{ id: strin
     include: {
       community: true,
       builder: true,
-      homes: { where: { status: "PUBLISHED" } },
+      homes: { where: { status: "PUBLISHED", isDemo: false } },
     },
   });
   if (!plan) notFound();
diff --git a/packages/search/src/index.ts b/packages/search/src/index.ts
index 90e1366..5b72fa8 100644
--- a/packages/search/src/index.ts
+++ b/packages/search/src/index.ts
@@ -79,6 +79,7 @@ export function buildWhere(params: SearchParams, bbox?: BBox): Prisma.InventoryH
 
   return {
     status: "PUBLISHED",
+    isDemo: false, // HARD RULE: demonstration/synthetic inventory NEVER surfaces on the live site
     ...(bbox
       ? {
           lat: { gte: bbox.minLat, lte: bbox.maxLat },
@@ -169,7 +170,7 @@ export async function runSearch(params: SearchParams): Promise<SearchResult> {
     const [homesRaw, total] = await Promise.all([
       prisma.$queryRaw<{ id: string }[]>(Prisma.sql`
         SELECT id FROM "InventoryHome"
-        WHERE status = 'PUBLISHED'
+        WHERE status = 'PUBLISHED' AND "isDemo" = false
           AND lat BETWEEN ${bbox!.minLat} AND ${bbox!.maxLat}
           AND lon BETWEEN ${bbox!.minLon} AND ${bbox!.maxLon}
         ORDER BY 2 * 3958.8 * asin(sqrt(
diff --git a/retarget-domain.sh b/retarget-domain.sh
new file mode 100644
index 0000000..46b46f2
--- /dev/null
+++ b/retarget-domain.sh
@@ -0,0 +1,50 @@
+#!/usr/bin/env bash
+# Retarget the live Kamatera origin to homesonspec.com (from spechomes.com).
+#   ! bash ~/Projects/spechomes/retarget-domain.sh
+# Rewrites the nginx vhost server_names + regenerates the self-signed origin cert.
+# App/DB/pm2 unchanged (nginx matches by server_name only).
+set -euo pipefail
+KH=root@45.61.58.125
+WEB_PORT=9975
+ADMIN_PORT=9976
+DOMAIN=homesonspec.com
+
+ssh $KH "
+mkdir -p /etc/nginx/ssl
+openssl req -x509 -nodes -newkey rsa:2048 -days 3650 \
+  -keyout /etc/nginx/ssl/homesonspec.key -out /etc/nginx/ssl/homesonspec.crt \
+  -subj '/CN=$DOMAIN' 2>/dev/null
+cat > /etc/nginx/sites-available/spechomes.com <<'NGINX'
+server {
+    listen 45.61.58.125:80;
+    listen 45.61.58.125:443 ssl;
+    server_name homesonspec.com www.homesonspec.com;
+    ssl_certificate     /etc/nginx/ssl/homesonspec.crt;
+    ssl_certificate_key /etc/nginx/ssl/homesonspec.key;
+    location / {
+        proxy_pass http://127.0.0.1:$WEB_PORT;
+        proxy_set_header Host \$host;
+        proxy_set_header X-Real-IP \$remote_addr;
+        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto \$scheme;
+    }
+}
+server {
+    listen 45.61.58.125:80;
+    listen 45.61.58.125:443 ssl;
+    server_name admin.homesonspec.com;
+    ssl_certificate     /etc/nginx/ssl/homesonspec.crt;
+    ssl_certificate_key /etc/nginx/ssl/homesonspec.key;
+    location / {
+        proxy_pass http://127.0.0.1:$ADMIN_PORT;
+        proxy_set_header Host \$host;
+        proxy_set_header X-Real-IP \$remote_addr;
+        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto \$scheme;
+    }
+}
+NGINX
+nginx -t && systemctl reload nginx
+echo -n '  origin HTTPS (real SNI) → '; curl -sk --resolve $DOMAIN:443:45.61.58.125 -o /dev/null -w '%{http_code}\n' https://$DOMAIN/
+"
+echo "✅  nginx now serves $DOMAIN"

← 5c85769 resilience: live adapters skip per-URL fetch failures (404/b  ·  back to Homesonspec  ·  clean-relaunch script for homesonspec.com (ship demo-filtere af67cea →