← 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
M apps/web/src/app/[state]/[city]/page.tsxM apps/web/src/app/builders/[slug]/page.tsxM apps/web/src/app/communities/[slug]/page.tsxM apps/web/src/app/homes/[id]/page.tsxM apps/web/src/app/layout.tsxM apps/web/src/app/page.tsxM apps/web/src/app/plans/[id]/page.tsxM packages/search/src/index.tsA retarget-domain.sh
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 →