← back to Wholivedthere
PLAN.md
432 lines
# PLAN — Three-Domain Home-History Ecosystem
**Domains** (all registered 2026-04-29, GoDaddy, 1-yr, privacy ON, auto-renew OFF):
| Domain | Role | Tone |
|---|---|---|
| **wholivedthere.com** | Flagship: address history + celebrity maps + records vault + insurance inventory | Editorial / archive |
| **claimmyaddress.com** | Onboarding: claim your home, sign up, upload docs | Action / utility |
| **bubbesblock.com** | Community: hyperlocal posts, cup-of-sugar requests, neighbor connections | Warm / family |
The three are surfaces of one product. Same Postgres, same auth, same MCP backend. Cross-linked, cross-promoted, but each surface tuned to a different first-touch use case.
---
## 1. Three-domain architecture
### Topology
```
┌─────────────────────────────────┐
│ Single backend (Express + PG) │
│ /api/address/{slug} │
│ /api/parcel/{apn} │
│ /api/permits/{apn} │
│ /api/community/{address-slug} │
│ /api/user/claims │
└─────────────────────────────────┘
▲
┌────────────────────┼────────────────────┐
│ │ │
┌────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ wholivedthere │ │ claimmyaddress │ │ bubbesblock │
│ (browse/read) │ │ (signup/upload) │ │ (community) │
└────────────────┘ └──────────────────┘ └──────────────────┘
```
### Cross-promotion strategy
Each surface promotes the other two through **contextual, non-spammy** placements that match the user's current intent. No banner ads — embedded calls-to-action that feel native.
**On `wholivedthere.com` (browse/discovery):**
- Address detail page → top-right card: *"Is this your home? Claim it on claimmyaddress.com to add private notes, photos, and receipts."*
- Address detail page → bottom card: *"What's happening on this block now? See live posts on bubbesblock.com →"*
- Search results → footer ribbon: *"Powered by claimmyaddress.com — sign in to upload your records"*
**On `claimmyaddress.com` (after claim):**
- Post-claim success page → *"See who lived here before you on wholivedthere.com →"*
- Dashboard sidebar → *"Connect with neighbors at bubbesblock.com →"*
- Email confirmation → footer links to both sister sites
**On `bubbesblock.com` (community):**
- Each post about a property → *"See this address's history on wholivedthere.com →"*
- Community sidebar → *"Got documents? Upload them on claimmyaddress.com to keep your home file in one place →"*
- Post-creation flow → *"Tag this post to a specific address → claim it on claimmyaddress.com"*
### Shared infrastructure
- **Auth**: single SSO across all three domains (cookie scoped to `.${root}` won't work cross-domain — use OAuth-style session token with `claimmyaddress.com` as identity provider)
- **Database**: single Postgres `homedb` with shared `users`, `addresses`, `parcels`, `permits`, `posts`, `claims`, `documents` tables
- **Backend API**: one Express server on Kamatera, served under each domain via nginx host routing
- **Static assets**: same CDN (Cloudflare) for shared imagery, fonts, JS bundles
---
## 2. Visual system — Gucci palette
Steve's call: every site uses Gucci's house palette, with each surface tuning the emphasis differently.
### Palette (drawn from Gucci's iconic GG-Web stripe + house colors)
```
--gucci-green: #1f4d40 (deep emerald — the "G" green)
--gucci-red: #c9292e (signature red — used sparingly as accent)
--gucci-gold: #c5a572 (Gucci gold for trim, dividers, hover)
--gucci-cream: #f3ebd9 (warm ivory page ground)
--gucci-bone: #e8dcc4 (deeper cream for cards/sections)
--gucci-ink: #1a1a1a (near-black text)
--gucci-stone: #6b6157 (muted body text)
--gucci-rule: #c9bfa9 (hairline divider)
```
**The Web stripe** (`green/red/green` — Gucci's signature horizontal band) appears once per page as a thin 4px ribbon under the header. Never as a background. Never repeated. It's a heritage-luxury cue.
### Typography
- **Display serif**: `'Cormorant Garamond', 'Playfair Display', Georgia, serif` (already in DW family — keeps cousin-brand consistency with `philipperomano.com`)
- **Body sans**: `'Inter', system-ui, sans-serif`
- **Display weights**: 500–700, ranges 18–96px, tight tracking (-0.01em on display, +0.04em on labels)
### Per-surface visual tuning
| Surface | Primary | Accent | Mood |
|---|---|---|---|
| `wholivedthere.com` | cream `#f3ebd9` | gucci-green | Editorial archive — generous whitespace, oversized serifs, lots of imagery |
| `claimmyaddress.com` | bone `#e8dcc4` | gucci-red (CTAs) | Utility/action — denser layout, prominent buttons, form-driven |
| `bubbesblock.com` | cream `#f3ebd9` | gucci-gold | Warm community — softer corners, more personality, hand-set headlines |
The Gucci-Web stripe (green/red/green) is the **only** visual element that appears identically across all three. It signals "you're in the same family of sites."
### Four-horsemen workflow for each site build
Per `/four-horsemen` skill, design pass goes:
1. **Paper** → layout brief + first artboard (1440×900 for desktop, 390×844 for mobile). Get the mood/palette/type fixed visually before any other tool.
2. **Figma** → high-fidelity component library + screen flows. Use Paper's exported tokens as the base.
3. **21st Dev** → live component preview, refine motion/interaction. Especially the map ↔ year-stack interaction.
4. **Canva** → marketing collateral derivatives — social-share cards, email headers, OG images for each domain.
Each site goes through all four before code lands.
---
## 3. LA public-records MCP — `mcp-la-records`
Custom MCP server at `~/Projects/wholivedthere/mcp-la-records/`, callable from Claude Code or directly from the backend. Gives a single tool: **`lookup_address`** that takes an address or APN and returns a unified record.
### Source priority order
1. **LA County GIS / ArcGIS Open Data** — parcels, APN, boundaries, situs address
- REST: `https://public.gis.lacounty.gov/public/rest/services/`
- Likely query: `LACounty_Cache/MapServer/query?where=SitusAddress%20LIKE%20'%25600%20BEDFORD%25'`
- No API key required (public layer)
2. **LA County Assessor** — owner, assessed values, year built, sqft, lot size, use code
- Portal: `https://portal.assessor.lacounty.gov/parceldetail/{APN}`
- May expose JSON via internal endpoints — investigate; otherwise scrape with Browserbase
- APN format: `4350-026-001` (10 digits with hyphens) or `4350026001` (10 digits no hyphens)
3. **City of LA ZIMAS** — zoning, council district, planning info
- Portal: `https://zimas.lacity.org/` — IFrame'd, no public API
- **Must** use Browserbase (headless cloud browser) to scrape per-parcel pages
4. **LADBS** — building permits, inspections, building history
- Portal: `https://www.ladbsservices2.lacity.org/onlineservices/` — search by address
- May have JSON endpoint behind the search form — investigate
5. **LA County Registrar-Recorder** — recorded documents, deeds, transfers, ownership history
- `https://www.lavote.gov/home/recorder` — lookup by APN
- Some data available on Socrata (`data.lacounty.gov`) — check first
### Existing MCP servers to evaluate (research first, build second)
Search for and evaluate:
- `mcp-arcgis` — generic ArcGIS REST wrapper (would handle source #1 and parcel queries directly)
- `mcp-socrata` — Socrata Open Data wrapper (some LA data lives here)
- Anything specifically for LA County — unlikely but check NPM and the MCP marketplace
If existing MCPs cover ArcGIS + Socrata cleanly, the custom MCP only needs to handle ZIMAS + LADBS + Assessor portal scraping.
### MCP tool surface
Single tool, single call resolves to one record:
```typescript
lookup_address({
query: "600 N Bedford Dr, Beverly Hills, CA"
// OR
apn: "4350-026-001"
})
→ {
apn: "4350-026-001",
address: { situs: "600 N BEDFORD DR", city: "BEVERLY HILLS", zip: "90210" },
owner: { name: "REDACTED FOR PRIVACY" | "LASTNAME, FIRSTNAME", since: "2018-04-12" },
assessment: { land: 4_500_000, improvement: 2_100_000, total: 6_600_000, year: 2025 },
characteristics: { yearBuilt: 1937, sqft: 4_280, lotSize: 14_500, useCode: "0100", useDescription: "Single Family Residence" },
zoning: { code: "BHR1", description: "Beverly Hills Single Family Residential", councilDistrict: "BH-3" },
permits: [
{ permitNumber: "21010-00000-12345", type: "Bldg-Alter/Repair", status: "Issued", filedDate: "2021-03-15", finalDate: "2021-09-22", description: "Kitchen remodel" }
],
recordedDocs: [
{ type: "GRANT DEED", recordedDate: "2018-04-12", documentNumber: "20180345678", parties: "JOHNSON FT TO SMITH RT" }
],
geometry: { lat: 34.0762, lng: -118.4029, parcelGeoJson: {...} },
sources: {
assessor: "https://portal.assessor.lacounty.gov/parceldetail/4350-026-001",
zimas: "https://zimas.lacity.org/parcel/4350-026-001",
ladbs: "https://...",
recorder: "https://..."
}
}
```
### Caching layer
Public records don't change daily. Cache aggressively:
- Postgres `parcels` table — 30-day TTL on assessor + GIS data
- Postgres `permits` table — 7-day TTL on LADBS
- Postgres `recorded_docs` table — 30-day TTL on Registrar-Recorder
- Cache key: APN (canonical form, 10 digits no hyphens)
This keeps the user-facing pages fast (sub-second) and the public APIs polite (well under their rate limits).
### Browserbase usage
For ZIMAS + LADBS + Assessor where no JSON API exists, use Browserbase headless cloud browser:
- Avoids local IP rate-limiting / IP-blocking from gov sites
- Persistent sessions for slow sites (ZIMAS takes ~5s to load a parcel)
- Pairs with `2captcha` skill if any of these services start gating with CAPTCHA
Pull data into Postgres on first lookup; subsequent lookups hit cache.
---
## 4. Functionality per surface
### `wholivedthere.com` — Flagship browse/discovery
**Routes:**
- `/` — full-bleed Mapbox map of LA County, search bar overlaid, "explore notable homes" curated list
- `/address/[slug]` — address detail page:
- Year-stack: vertical scroll-snap, one Street View capture per year (1955–2025)
- "Who lived here" panel: occupants ordered by tenure, with sources
- "Property facts" panel: APN, year built, sqft, lot size, zoning, assessed value
- "Permits" panel: chronological list with filed/final dates, descriptions
- "Owners over time" panel: deed-transfer history
- "Notable people associated" — if any occupant is a tagged celebrity, link to their page
- Cross-promo: claim CTA → claimmyaddress.com, community CTA → bubbesblock.com
- `/person/[slug]` — celebrity / notable figure: every known address, life timeline, photos
- `/era/[slug]` — "Hollywood 1940s", "Beverly Hills 1960s" — curated address lists, editorial intros
- `/blog/[slug]` — long-form editorial: "The Zukor estate at 1240 N Roxbury", "How Bedford Drive became Doctors' Row"
- `/collection/[slug]` — themed groups: "Mid-century modern", "Tudor revivals", "Frank Lloyd Wright LA"
**Data flow:**
- Search → geocode via Mapbox → APN lookup via mcp-la-records → cache → render
- Every address page hits cache first, falls through to live mcp-la-records if cold
**Map UX:**
- Mapbox-GL with custom style (cream ground, gucci-green parks, gold roads)
- Clustering at low zoom; individual parcels at high zoom
- Click parcel → side panel slides in with address summary + "see full history →"
- Filter chips: *Notable people only*, *Year range*, *Architectural style*, *Within X miles*
### `claimmyaddress.com` — Onboarding/upload
**Routes:**
- `/` — single hero page: address autocomplete + "Claim your home" button
- `/signup` — email/password or Google OAuth
- `/onboarding/[step]` — wizard:
1. Verify ownership (mailed postcard with code, or last 4 of SSN against assessor record)
2. Upload first document (deed, mortgage statement, or utility bill)
3. First note: "What do you love about this house?"
4. Connect Gmail for receipt parsing? (deferred feature)
- `/dashboard` — claimed properties, recent uploads, doc count, value timeline
- `/property/[address-slug]` — single-property dashboard:
- **Receipts vault**: dropzone for any doc, OCR auto-tagging (contractor, utility, tax, insurance), date-sorted timeline
- **Items inventory**: photo + description + serial + value of each major item, room-tagged, auto-export to insurance PDF
- **Notes**: timeline of personal notes ("repainted living room 2024-09"), tied to dates
- **Contractor log**: who did what, contact info, before/after photos
- **Insurance pack**: one-click export of inventory + receipts as a PDF formatted for claim filing
**Data flow:**
- Claim → match to mcp-la-records APN → if owner field matches user's verified identity, auto-confirm. If not, postcard verification.
- Upload → file goes to encrypted bucket (S3 or Cloudflare R2) → OCR pipeline tags it → result stored in `documents` table
**Security:**
- All uploaded docs encrypted at rest
- Per-user encryption key derived from password (or hardware-key on iOS)
- Owner verification before any data exchange between users
### `bubbesblock.com` — Community
**Routes:**
- `/` — feed of recent posts on user's claimed block(s), or LA-wide if not signed in
- `/block/[address-slug]` — block-specific feed (a "block" = the segment of a street between cross streets)
- `/feed` — your block + nearby blocks if claimed
- `/post/[id]` — single post with replies
- `/cup-of-sugar` — neighborhood marketplace: "borrowing", "lending", "lost & found", "free curb stuff"
- `/events` — block parties, garage sales, open houses, community meetings
- `/profile/[handle]` — public profile of a verified neighbor
**Post types:**
- **Update** — "Working on the front yard tomorrow, FYI on noise"
- **Cup of sugar** — "Anyone have eggs?"
- **For sale / free** — block-only marketplace
- **Recommendation** — "Used Mike for tree-trimming, great work"
- **Question** — "Anyone know who lived at 612 in the 80s?" — auto-pulls from wholivedthere.com if address tagged
- **Event** — block party, garage sale
**Data flow:**
- Posts tied to addresses → can pull metadata from mcp-la-records on render
- Privacy: public posts visible to verified neighbors of the same block; private posts opt-in
- Verification: must claim an address on claimmyaddress.com to post on a block (this is the cross-promotion engine — bubbesblock pushes signups to claimmyaddress)
---
## 5. Tech stack & build order
### Stack
| Layer | Tech |
|---|---|
| Frontend | Next.js 15 App Router (or single-file Express + SSR per `/dw-site-build` if scope stays simpler) |
| Backend | Node/Express on Kamatera, behind nginx + Cloudflare proxy + Let's Encrypt SSL |
| Database | Postgres (`homedb`) — own DB, separate from `dw_unified` |
| Map | Mapbox-GL with custom Gucci-palette style |
| Auth | Auth.js (formerly NextAuth) with Google OAuth + email magic link |
| Storage | Cloudflare R2 for uploaded documents (cheaper than S3, free egress) |
| MCP | Custom `mcp-la-records` Node MCP server |
| Headless browser | Browserbase (for ZIMAS + LADBS + Assessor scraping) |
| OCR | Tesseract for self-host, or Anthropic Files API + Claude vision for accuracy |
| Email | Resend for transactional, Mailgun for newsletters |
### Build order (~6-week MVP)
**Week 1 — Infrastructure**
- Set up Postgres `homedb` schema (users, addresses, parcels, permits, posts, claims, documents)
- Stand up Express backend on Kamatera
- Onboard all three domains to Cloudflare (per `/domain-setup` skill — same flow as philipperomano)
- Set up Auth.js with Google OAuth on `claimmyaddress.com` as identity provider
**Week 2 — MCP for LA records**
- Build `mcp-la-records` server
- Integrate ArcGIS REST for parcel lookup (no auth required)
- Integrate Browserbase for ZIMAS scraping
- Build `lookup_address(query)` tool, test on 50 known addresses
- Cache layer with 30-day TTL
**Week 3 — Four-horsemen design pass**
- Paper artboard for each of the three sites (Gucci palette + Cormorant + Web-stripe)
- Figma high-fi component library shared across all three
- 21st Dev component preview
- Canva social/OG cards
- Steve approval before any code
**Week 4 — wholivedthere.com**
- Map landing page with Mapbox + Gucci style
- Address detail page: year-stack, occupants, property facts, permits, owners
- Search → APN lookup → render flow
- Cross-promo CTAs (claim, community)
**Week 5 — claimmyaddress.com**
- Signup + Google OAuth
- Claim flow with assessor-record matching
- Upload pipeline → R2 + OCR
- Property dashboard with receipts, items, notes, contractor log, insurance-pack export
**Week 6 — bubbesblock.com**
- Block feed + post composer
- Address-tagging on posts (pulls from mcp-la-records)
- Cup-of-sugar marketplace
- Events
- Verification gate (must claim an address first → drives traffic to claimmyaddress.com)
**Post-MVP**
- Celebrity-mapping content layer (curated person/[slug] pages, era pages, editorial blog)
- Email newsletter from each domain
- Mobile apps (React Native shell over the same backend)
- Insurance-claim integration (partner with brokers to auto-file claims from the inventory pack)
- Expand beyond LA: SF Bay, NYC, Chicago — each adds a regional MCP for its public records
### Cost estimate (annual)
| Item | $ |
|---|---|
| Domain registrations (3, already paid) | $32 |
| Cloudflare (free tier — already have) | $0 |
| Mapbox (50K loads/mo free, ~$0.50/1K beyond) | ~$50 |
| Browserbase (~$50–200/mo depending on lookups) | $1,200 |
| Postgres on Kamatera (already running) | $0 |
| Resend / Mailgun | ~$25 |
| OCR (Anthropic API or Tesseract self-host) | ~$50 |
| **Year 1 infrastructure** | **~$1,300** |
---
## 6. What's locked, what's open
**Locked decisions:**
- Three-domain architecture
- Gucci palette + Cormorant/Inter typography
- Single backend, host-routed nginx
- LA-first, expand later
- Public records via custom MCP, no paid services (Zillow/Redfin/Regrid/ATTOM/CoreLogic are out unless explicitly approved later)
- Four-horsemen design pass before any code
- Privacy-on, auto-renew-off on all three domains
**Steve's decisions (2026-04-29):**
1. **Identity provider** → ✅ Subdomain `auth.wholivedthere.com`. `claimmyaddress.com` stays as marketing/onboarding-only.
2. **Tier** → ✅ **Free forever.** No paid tiers in plan. Optional voluntary support / sponsor model can come later if needed.
3. **Owner verification** → ✅ **Offer all options, user picks based on speed vs privacy preference:**
- **Option A — Utility bill upload** (recommended default): user uploads PDF/photo of recent gas, electric, or water bill showing matching name + address. OCR + heuristic match against assessor record. Free, fast (~minutes), low PII. **Bills accepted: gas, electric, water/sewer, internet, cable, phone landline. Cell-phone bills NOT accepted (mailing address often differs from service address).**
- **Option B — ID + assessor record match** (fast, no bill needed): user uploads driver's license; we OCR name + address and match against the assessor record. Free, fast.
- **Option C — Postcard mail** (highest trust, for absentee owners): physical postcard with code, user enters code. ~$1–2/claim cost, 3–7 day turnaround.
- **Option D — SSN last-4** (deferred — PII-sensitive, only if users specifically request it). Not offered at launch.
4. **Editorial content** → ✅ **AI-generated drafts.** Use Claude API (per `/claude-api` skill, with prompt caching) to generate person/era/blog pages from primary public sources (LA Times archive, Wikipedia, Curbed LA, public-domain newspapers). Each AI-drafted page is **flagged as AI-generated** in the byline for transparency. Human review only on flagship celebrity pages and SEO landing pages.
5. **Beverly Hills assessor** → ✅ **Both in MVP.** LA County records cover non-BH parcels; build a separate `mcp-bh-records` Browserbase scraper for the BH-only core city (~6 sq miles, but contains the celebrity-density that makes the product compelling). Worth the extra week of MVP scope.
---
## 7. Reflection back into `/dw-site-build` skill
Once any one site ships, the universal patterns get codified into `/dw-site-build/SKILL.md` per Steve's standing rule. Specifically:
- **Three-domain ecosystem pattern** — when one product needs multiple surfaces with shared backend
- **Gucci-palette section** — the actual hex values and Web-stripe usage
- **MCP-backed catalog pattern** — how custom MCPs feed product pages with cached data from public APIs
- **Cross-promotion CTA placement** — where in each surface the sister-site links go without feeling spammy
- **Owner-verification flow** — postcard / SSN-last-4 / ID upload trade-offs
---
## 8. Next steps (immediate, before any build)
1. Steve reviews this plan and resolves the **5 open questions** above
2. Steve approves the **Gucci palette + Cormorant/Inter** design baseline (or course-corrects)
3. I research existing MCP servers for ArcGIS + Socrata (web search, ~30 min)
4. I scaffold `~/Projects/wholivedthere/mcp-la-records/` with the tool surface + first ArcGIS query working end-to-end
5. I open three Paper artboards (one per domain, 1440×900) with the Gucci brief — Steve reviews
6. We move to Week 1 of the build plan above
**No code lands until #1 and #2 are done.**
---
## Appendix A — Existing MCP servers (research 2026-04-29)
Found via web search; evaluated for LA-public-records use:
| MCP | Repo | Covers | Use? |
|---|---|---|---|
| **ArcGISMCP** | [GarrickGarcia/ArcGISMCP](https://github.com/GarrickGarcia/ArcGISMCP) | ArcGIS Online feature layer queries — works against LA County's public ArcGIS REST | **Yes** — adopt directly for parcel lookup |
| **Socrata-MCP** | [Thomas-TyTech/Socrata-MCP](https://github.com/Thomas-TyTech/Socrata-MCP) | SoQL queries on Socrata Open Data — covers `data.lacounty.gov` and any LA dataset there | **Yes** — adopt for Open Data lookups |
| **gis-mcp** | [mahdin75/gis-mcp](https://github.com/mahdin75/gis-mcp) | Generic GIS operations (geometry transforms, projections) | Maybe — only if we need shapefile transforms |
| **MCP-Server-ArcGIS-Pro-AddIn** | nicogis/... | Desktop ArcGIS Pro — not relevant for web product | Skip |
### Custom MCPs still needed (build with Browserbase):
- `mcp-zimas` — scrapes `zimas.lacity.org` for zoning + council district
- `mcp-ladbs` — scrapes LADBS for permit history
- `mcp-la-assessor` — scrapes assessor portal for owner/values when not in ArcGIS layers
- `mcp-la-recorder` — scrapes Registrar-Recorder for deed transfers (Socrata covers some, not all)
**Net build estimate:** ~1 week for the four custom Browserbase MCPs (one per source) plus 2 days for the orchestration layer that combines all six sources into a single `lookup_address` response.