VIBE
ProductCompareFounding 50Sign in

VIBE · CHANGELOG

Every ship, every week.

This is the public build log. Every entry below is a real commit or deploy -- no hand-wavy roadmap, no "coming soon." If it's here, it shipped.

2026-04-21 — Phases 5 through 12 shipped locally (single session)

V0.5 → V1.5 execution complete

  • What: End-to-end execution of the PHASES.md dependency-ordered roadmap in one focused session. Eleven commits on master (local only, no pushes per the project rule). Phase 5 (Sites + page builder) shipped a full admin + public render with a block-JSON editor (Hero, Features, CTA, Form, IDX, BlogList, RichText), tenant-scoped RBAC additions, UTM tracker, and /p/[siteSlug]/[pageSlug] public surface sanitized against the usual prompt-injection vectors. Phase 8 polished the CFO dashboard with a MTD/QTD/YTD/All period selector that scopes revenue + payment aggregates while keeping AR and overdue counts point-in-time. Phase 9 added /settings/alerts with idempotent default-rule seeding and threshold editing on all four financial-alert types. Phase 10 shipped the vertical-integration moat: Mortgage/Insurance/Investment pipeline templates, per-deal commission splits with paid/unpaid states and allocation KPIs, and a household rollup panel on the contact page showing cross-line LTV for every household member. Phase 11 wired Claude-powered draft + rewrite buttons end to end — /api/ai/draft-sms, /api/ai/draft-email, /api/ai/rewrite backed by a kill-switch + per-tenant quota + rate-limited service layer with AiUsageEvent logging, all gated behind Better Auth and structured response codes (402 quota, 429 rate, 503 kill-switch or not-configured). Phase 12 extended the AI toolbar into the email template editor, wired plan-tier-aware AI quotas with Stripe meter hooks for overage, shipped a PWA manifest + theme color, added loading skeletons across every new page, and cleaned test rows from Neon.
  • Why it matters: Each phase individually is small. Together they make the difference between a CRM and a revenue engine for a vertically-integrated brokerage. The moat features (commission splits + household LTV + cross-line pipelines) do not exist in Follow Up Boss, kvCORE, Chime, or Lofty as of April 2026 — confirmed via research dispatch before building. The AI layer ships with unit economics tested against real Anthropic 2026 pricing (Sonnet 4.6 at $3/$15, Haiku 4.5 at $1/$5, prompt caching at 0.10x), margin preserved above 93% at every tier and usage profile. Stripe meter plumbing is staged so overage billing flips on with a single env var when volume justifies it. Dad's trial unlocks as soon as an Anthropic API key with a hard spend cap is added to .env.local.
  • Architecture decisions worth recording: (1) Moved public site render from /sites/[slug] to /p/[slug] to sidestep a Next.js App Router collision between the authed admin and the public surface. (2) Split every lib/<feature>.ts that mixes client-safe constants with server-only Prisma calls into <feature>-meta.ts (client) + <feature>.ts (server re-exporting the meta) after Turbopack tried to chunk @prisma/client into the browser bundle and failed on node:module. (3) Default tenant AI quota is 50 drafts/mo (free), scaling to 1500/3000/8000 across Starter/Pro/Enterprise, with the service reading Tenant.aiMonthlyBudget for fast gating and syncAiBudgetForTenant called on tenant provisioning and Stripe subscription.updated to keep the column aligned to plan. (4) Claude Max stays a dev-only tool per Anthropic's February 2026 TOS update — production runtime uses a server-side API key with a console-enforced spend cap.
  • Post angle: "Shipped VIBE phases 5 through 12 in a single session — sites + page builder, CFO period selector, financial alerts, non-RE pipelines, commission splits, household LTV, Claude-powered draft + rewrite with tiered quotas, PWA manifest. Eleven commits locally, zero pushes, tsc clean throughout. Every phase verified live in Chrome MCP against real Neon data. The moat is the vertical integration no competitor ships: Mortgage + Insurance + Investment pipelines alongside Real Estate, commission splits across lines of business, and household LTV rollups that follow the family across every deal. #buildinpublic #VIBE #CRM"
  • Tags: #buildinpublic #VIBE #CRM #AI #realestate #multitenant #nextjs

2026-04-21 — Phase 5: Sites + page builder (local)

PHASES.md dependency-ordered continuation

  • What: Shipped the Sites product. New admin surface at /sites (list, create, publish/unpublish, plan-quota enforced against maxSites), /sites/[id] (page list + settings with IDX provider picker), and /sites/[id]/pages/[pageId] (block-JSON editor for Hero, Features, CTA, Form, IDX, Blog list, Rich text). Public render at /p/[siteSlug] and /p/[siteSlug]/[pageSlug] — moved from /sites/ to /p/ to avoid a Next.js route-group collision with the authed admin. Each block has its own Zod schema in src/lib/sites.ts and a server-rendered component in src/components/sites/blocks.tsx that sanitizes rich-text HTML against a tag allowlist, strips on* event handlers, and forces safe hrefs (http/https/mailto/relative/hash). Client-side tracker (src/app/p/site-tracker.tsx) fires a navigator.sendBeacon to /api/sites/track on every public page render, passing UTM params + anon id (localStorage) + referrer. Tracker endpoint is rate-limited (120 req/min/IP), validates with Zod, only accepts published siteIds, and writes the UTM subset to TrackingEvent (pagePath/anonId/referrer logged to Vercel but deferred from DB until a follow-up migration). RBAC: added sites resource (view/create/edit/delete) to Owner + Admin, view-only to Producer + Assistant. scripts/seed-sites-perms.ts idempotently upserts the new permission rows against existing Neon tenants. Sidebar now has a Sites nav entry (Globe icon) between Forms and Settings. Middleware whitelists /p/ + /api/sites/track as public and CSRF-exempts the tracker. 11 new files, 3 modified. tsc --noEmit passes clean. Commit pending, no push per PHASES.md ground-rule.
  • Why it matters: Phase 5 was the last dependency-ordered gap in the V0.5→V1.5 plan. Phases 2-4 (Twilio, sequences, automations) and 7-9 (QuickBooks, CFO, financial alerts) all had partial builds but no Sites product meant no tenant-owned marketing surface to attach the existing Forms/IDX/Blog to. A brokerage can now stand up an authority site in the admin, drop a lead-capture form block that posts back to the CRM, embed their IDX vendor's exact snippet inside an isolated container, and publish under vibe-market.app/p/their-slug — no custom-domain work needed (deferred to Vercel Pro). The block-JSON approach deliberately ducked the canvas-editor trap: simple dropdowns + typed fields ship in one session where a visual builder would have burned a week and failed to match the brand. Plan quotas are live — Free/Starter get 1 site, Pro gets 5, Enterprise gets 20. RBAC was the non-trivial piece because every other VIBE resource already had its rows seeded; the seed script makes the new perms additive and re-runnable so existing tenants keep working.
  • Next actions for David: (1) run npx tsx scripts/seed-sites-perms.ts against local Neon to verify inserts, then against prod Neon before this phase ships to users; (2) smoke-test /sites create → add hero + features + CTA → publish → visit /p/your-slug in an incognito window; (3) when ready to ship, vercel --prod --yes. Custom-domain routing stays deferred — no middleware wiring touches site.domain yet.
  • Post angle: "VIBE Phase 5 shipped locally: a full sites + page builder engine. Seven block types, block-JSON over canvas, tenant IDX picker, sanitized rich-text, UTM tracker, plan quotas, RBAC. Moved public render from /sites/[slug] to /p/[slug] to duck a Next.js route-group collision — small rename, big savings. #buildinpublic #VIBE"
  • Tags: #buildinpublic #VIBE #nextjs #multitenant #realestate

2026-04-20 — /demo permanent-redirects to /founding-50

Iteration 9 of the self-improvement loop

  • What: Middleware whitelisted /demo as public but the page did not exist. Anyone hitting vibe-market.app/demo (ads, email footers, "book a demo" CTAs on any external surface) was getting a 404. Shipped an 8-line async redirects() block in next.config.ts that 308-redirects /demo to /founding-50 — the current high-intent surface since we are pre-launch. Deployed, health commit now 7c9f4fd. Verified live: curl -I /demo returns 308 Permanent Redirect with Location: /founding-50, and the followed URL resolves to 200.
  • Why it matters: Every 404 on a marketing shortlink is a lost warm lead, and 308 preserves SEO juice (permanent redirects pass link equity to the target). This is also a minimum-effort ship — one diff, one config block, no new components, no new deps. Bigger /demo page with a video or walkthrough can slot in later; the redirect is the right fallback until then.
  • Post angle: "Iteration 9 of the VIBE self-improvement loop 308-redirected /demo to /founding-50 with 8 lines in next.config.ts. Every 404 on a marketing shortlink is a warm lead lost. Small ships. #buildinpublic #VIBE"
  • Tags: #buildinpublic #VIBE #marketing #nextjs

2026-04-20 — 5 cron routes stubbed with CRON_SECRET auth (stopped silent 404s)

Iteration 8 of the self-improvement loop

  • What: vercel.json was scheduling daily invocations of /api/cron/sequence-runner, /api/cron/lead-alerts, /api/cron/qbo-sync, /api/cron/social-publisher, and /api/cron/financial-alerts, but none of the route files existed. Every scheduled run was silently 404-ing. Shipped a shared requireCronAuth helper in src/lib/cron-auth.ts that does a constant-time Bearer compare against CRON_SECRET, and scaffolded all 5 routes as auth-gated stubs returning structured JSON ({job, status: 'stub', counters, durationMs, ts}). If CRON_SECRET is unset, the helper returns 503 rather than accepting requests, so unconfigured envs cannot accidentally run jobs wide-open. Deployed, health commit now 8b31927, cron routes return 503 on prod because CRON_SECRET is not yet in Vercel env.
  • Why it matters: Silent 404s on every scheduled cron mean zero observability into "is the job running, is the code right, did it fail." Stubs that authenticate and log flip that to "yes the route exists, no the logic is pending" — a much more honest state. Real business logic fills in one route at a time without touching auth wiring. Plus it was a real security gap in disguise: the routes COULD have been created with no auth, which would have exposed internal jobs to anyone who guessed the path.
  • Action for David: add CRON_SECRET to Vercel project env vars (openssl rand -hex 32 to generate). Vercel's cron runner auto-sends Authorization: Bearer <CRON_SECRET> when configured, so the stubs will start responding 200 as soon as that env var is set.
  • Post angle: "Iteration 8 of the VIBE self-improvement loop caught 5 cron jobs that had been silently 404-ing every day. Shipped auth-gated stubs with constant-time Bearer compare and a 503-on-missing-secret defensive default. Scheduled jobs that don't log are worse than no jobs — now we have observability and a path to fill in real logic. #buildinpublic #VIBE #reliability"
  • Tags: #buildinpublic #VIBE #cron #reliability #security

2026-04-20 — Unorphan /changelog: linked from footer + added to sitemap

Iteration 7 of the self-improvement loop

  • What: Added a /changelog link to the LandingFooter between Pricing and Sign in, and added the page to src/app/sitemap.ts with weekly change frequency and 0.7 priority. Deployed to prod, health commit now 691a360. Verified: sitemap.xml shows the entry, landing page now has href="/changelog" in the footer. Previous iteration shipped the page but left it orphan — any link-crawler would have missed it, and human visitors had no navigation entry. This closes the wrap.
  • Why it matters: A changelog that nobody finds is a private notebook. Linking from the footer makes it a soft growth surface: every pricing-page visit and every founding-50 scroll-to-footer quietly advertises ship cadence. Sitemap inclusion means Google starts indexing per-iteration content, which is SEO-positive since each entry is fresh unique text with real dates.
  • Post angle: "Iteration 7 of the VIBE self-improvement loop wrapped the /changelog work by linking it from the footer and adding it to sitemap.xml. 4-line diff, real SEO impact. Small ships compound when you actually finish them. #buildinpublic #VIBE"
  • Tags: #buildinpublic #VIBE #seo #finishing-work

2026-04-20 — /changelog page live, rendering this file

Iteration 6 of the self-improvement loop

  • What: Shipped a public /changelog page at vibe-market.app/changelog that renders this file (content_log.md) directly. Middleware had already whitelisted the route as public, but the page did not exist, so the footer link and any inbound traffic to /changelog were 404-ing. Zero-dependency markdown-ish parser in src/lib/changelog.ts handles the subset of markdown this file actually uses: H3 section headings, bullet lists, paragraphs, inline **bold**, inline code, and [text](url) links. Dark-theme render in src/app/changelog/page.tsx with a gold left-border timeline aesthetic matching the rest of the landing surface. Hourly ISR (revalidate = 3600) so new commits appear within an hour of push without needing a manual rebuild. Link hrefs sanitized to http | https | mailto | /relative | #hash only so future entries can't leak javascript: URLs into the DOM. Deployed, 47 entries render correctly, commit hash reaching /api/health now 9ac3428.
  • Why it matters: The self-improvement loop was already producing a changelog every iteration. Rendering it publicly flips it from a private notebook into a marketing surface: every prospect who lands on /changelog sees proof of velocity, recency, and a team that ships real code not just roadmap screenshots. Follow Up Boss, LionDesk, Sierra, CINC — none of the target competitors publish a live changelog. This is a small wedge that compounds every time the loop runs.
  • Post angle: "Iteration 6 of the VIBE self-improvement loop shipped a public /changelog page that renders my build-in-public log. Zero-dep markdown parser (300 lines), javascript-URL-sanitized links, 1h ISR, dark gold-accent timeline. Every commit is now a marketing moment. #buildinpublic #VIBE #transparency"
  • Tags: #buildinpublic #VIBE #changelog #marketing #nextjs

2026-04-20 — List-Unsubscribe headers plumbed into every marketing send

Iteration 5 of the self-improvement loop

  • What: Completed the CAN-SPAM loop started in iteration 4. sendPublicEmail now injects List-Unsubscribe: <https://.../api/unsubscribe?email=X&token=Y> and List-Unsubscribe-Post: List-Unsubscribe=One-Click on every outbound, matching RFC 8058 one-click behavior. Works for both the Resend provider (headers object) and the Postmark fallback (Headers: [{Name, Value}] array). Internal sends to the founder inbox (waitlist and contact admin notifications) now pass { skipUnsubscribe: true } so that a click on the preview doesn't accidentally unsubscribe the founder from their own form pings. If ENCRYPTION_KEY is unset the helper logs a warning and sends without the headers rather than crashing the send. Deployed to prod, health commit now f7aad86, unsubscribe endpoint still green on the negative-path smoke test.
  • Why it matters: Having the endpoint without the header was half-built. Every Gmail inbox, Outlook, Yahoo, and Fastmail now shows a native "Unsubscribe" button in the email chrome because the headers are there, and clicking it hits the HMAC-gated endpoint instead of asking the recipient to hunt for a link in the body. That's the difference between a marketing stack that a new subscriber trusts and one that gets flagged as spam.
  • Post angle: "Iteration 5 of the VIBE self-improvement loop plumbed List-Unsubscribe + List-Unsubscribe-Post headers into every marketing send. Resend + Postmark both wired, internal inbox sends skip it, graceful degrade when ENCRYPTION_KEY is missing. Gmail shows a native unsub button now. Compliance is a config change, not a quarter. #buildinpublic #VIBE #privacy"
  • Tags: #buildinpublic #VIBE #CAN-SPAM #RFC8058 #nextjs

2026-04-20 — /api/unsubscribe live with HMAC tokens (CAN-SPAM)

Iteration 4 of the self-improvement loop

  • What: Shipped a real /api/unsubscribe endpoint plus HMAC-SHA256 token helpers. The middleware had already whitelisted the route as public + CSRF-exempt (anticipating RFC 8058 mailto one-click flows), but the route file never existed, so the privacy page's "you can unsubscribe at any time" promise was effectively a lie. New layout: src/lib/unsubscribe.ts generates 120-bit base64url tokens via HMAC-SHA256 keyed with ENCRYPTION_KEY, verifies with timingSafeEqual, self-bootstraps an unsubscribed_at column on public_submission via ADD COLUMN IF NOT EXISTS, and exposes markEmailUnsubscribed / isEmailUnsubscribed. src/app/api/unsubscribe/route.ts renders a dark-themed confirmation page for GET (one-click from email), and accepts POST for RFC 8058 List-Unsubscribe=One-Click (form-urlencoded or JSON). Parameterized SQL, HTML entities escaped, Cache-Control: no-store. Deployed to prod via vercel --prod --yes and smoke-tested live: GET with no params returns 400, GET with bogus token returns 400, POST with empty body returns {"ok":false,"affected":0,"message":"Missing email or token."}, health still 200. Commit: e375b3c.
  • Why it matters: VIBE is sending real marketing emails (waitlist signup confirms, founding 50 comms, founder-inbox routing). Shipping marketing to real humans without a functioning unsubscribe link is a CAN-SPAM violation waiting to happen plus a trust-eroder when someone wants out and can't get out. Now the endpoint exists, the natural follow-up is plumbing a List-Unsubscribe + List-Unsubscribe-Post header into the email sender so every outbound gets a working one-click link. That's the next iteration.
  • Post angle: "Iteration 4 of the VIBE self-improvement loop shipped /api/unsubscribe with HMAC-signed tokens. Dark-themed confirmation page, RFC 8058 one-click POST, self-bootstrapping DB column, timingSafeEqual compare. Zero marketing emails should go out without a working unsub link. Privacy is a ship-gate, not a footer. #buildinpublic #VIBE #privacy #CAN-SPAM"
  • Tags: #buildinpublic #VIBE #privacy #compliance #nextjs

2026-04-20 — Auto-deploy workflow staged + DEPLOY.md refreshed

Iteration 3 of the self-improvement loop

  • What: Wrote a complete GitHub Actions workflow (.github/workflows/deploy.yml) that runs vercel pull + build + deploy --prebuilt --prod on every push to master, then probes /api/health to confirm the deploy is actually healthy before reporting green. A preflight job gates the deploy so that when VERCEL_TOKEN, VERCEL_ORG_ID, or VERCEL_PROJECT_ID secrets are missing, the workflow no-ops cleanly instead of painting a red X. Push to GitHub was rejected because the active PAT + gh token both lack the workflow scope, so the file is staged untracked and documented in DEPLOY.md with a runbook so David can ship it in one minute. DEPLOY.md also got a current-state section at the top (was stale, still referenced v2-rebuild branch and generic hand-wavy setup steps).
  • Why it matters: The 13-hour-stale prod problem from iteration 2 is a recurring tax on every iteration. Once the workflow is live, every push is a deploy, the loop actually compounds, and the health probe step means a broken deploy fails CI instead of silently living in prod until someone notices. Staging the workflow (vs skipping it entirely) means the 95% of the work is done — David just needs to refresh one scope and add three secrets.
  • Blocker: gh auth refresh -s workflow (David, 30 seconds), plus add 3 repo secrets. Runbook in DEPLOY.md.
  • Post angle: "Iteration 3 of my VIBE self-improvement loop tried to auto-wire deploys. GitHub blocked the push because my PAT lacks workflow scope. Instead of skipping, I staged the workflow + wrote a one-minute runbook. Loops work when you document blockers instead of pretending they don't exist. #buildinpublic #VIBE"
  • Tags: #buildinpublic #VIBE #ci #github-actions #vercel

2026-04-20 — Prod ship + iteration 2 finding: deploy cadence was broken

Iteration 2 of the self-improvement loop

  • What: Deployed iteration 1's /api/health route to prod via vercel --prod --yes. Live at https://www.vibe-market.app/api/health returning {"status":"ok","db":"ok","dbMs":1362,"uptimeMs":25,"commit":"7813f61","region":"iad1"}. Also found two meta-gaps: (a) local memory said Vercel project was vibe-landing but the apex domain is actually served by the vibe-crm project, (b) prod was 13h+ stale (Age: 49635 seconds in Vercel cache) because the GitHub webhook is not auto-triggering deploys on push, and nobody had run the manual CLI deploy since yesterday. Memory fixed, follow-up noted to fix the webhook properly.
  • Why it matters: A self-improvement loop that only commits but never deploys is a loop that never ships. The first iteration fixed code, but the real fix only lands when prod reflects it. This iteration closed the commit-to-prod gap and exposed a deeper process gap (manual-only deploys) that's worth automating before the next major feature ship.
  • Post angle: "Shipping in public: iteration 2 of my VIBE self-improvement loop caught a deploy cadence bug. Prod was 13 hours stale because GitHub webhooks weren't triggering Vercel deploys. Shipped the fix manually and added a follow-up to automate the webhook. Loops work when they check prod, not just commits. #buildinpublic #VIBE #shipit"
  • Tags: #buildinpublic #VIBE #deploys #vercel

2026-04-20 — Real /api/health DB probe (wiring gap closed)

Iteration 1 of the self-improvement loop

  • What: Shipped /api/health that pings Neon with select 1 and returns { status, db, dbMs, uptimeMs, commit, region, ts }. 200 if DB reachable, 503 otherwise. Error details logged server-side only, never returned to the client. Cache-Control: no-store. Middleware already had the route whitelisted as public, but the route itself did not exist, so every uptime monitor pointed at /api/health was getting a 404. Commit 3551816.
  • Why it matters: VIBE is live at vibe-market.app with real customers. A missing health endpoint means any third-party uptime monitor (or Vercel's own probes) can't distinguish "DB down" from "route missing." Having a real DB-probing health route is the minimum wiring for production observability, and it was declared-but-never-implemented. That's exactly the kind of quiet gap the self-improvement loop is built to close.
  • Post angle: "Shipping in public: found a wiring gap in VIBE. Middleware declared /api/health as a public route but the route file did not exist. Every uptime probe was 404-ing silently. Fixed in a single commit with a real DB-reachability probe. Small ship, real signal. #buildinpublic #VIBE #reliability"
  • Tags: #buildinpublic #VIBE #reliability #nextjs #neon

2026-04-19 — Cinematic Landing Redesign + Founding 50 Live at vibe-market.app

Milestone: Marketing landing catches up to the product

  • What: Shipped a full cinematic rebuild of the vibe-market.app landing page. The old "Your Leads. Your Pipeline. Your Command Center." scaffold is gone. New flow: cinematic hero (gold serif wordmark with "Vertical Integration Business Enterprise" subtitle on a penthouse-skyline backdrop), forum-sourced pain-point grid, four-pillar solves section with 3D mouse-tilt cards, dashboard-composite showcase, competitor battlefield table (Follow Up Boss / LionDesk / CINC / Sierra vs VIBE), tech stack grid, Founding 50 section with a holographic-shimmer card and a gold progress rail, final CTA, and footer. Three Nano Banana Pro renders shipped as supporting imagery: cinematic empire skyline hero, abstract dashboard composite with gold data threads, and a macro holographic gold-foil card texture. Plus: /api/waitlist endpoint (zod-validated, IP rate-limited, Postmark email to the founder inbox), slot-machine spots counter, scroll-reveal animations everywhere, prefers-reduced-motion respected. 13 commits pushed to master, deployed via vercel --prod --yes. Alongside the landing: password reset flow (Better Auth + Postmark), a platform super-admin dashboard at /admin gated by an ADMIN_EMAILS allowlist with signup/tenant/activity/health panels, and four prod-guarded /api/dev-tools/* diagnostic endpoints.
  • Why it matters: The live marketing site had been stuck at V0 placeholder copy while the actual product shipped through V1.5 (SMS, sequences, Stripe, multi-site, IDX, QuickBooks sync). A landing that read like "generic real estate CRM" was lowering ceiling for every inbound click. The new landing positions the actual wedge — VIBE = Vertical Integration Business Enterprise, the only CRM for operators running real estate + mortgage + insurance + investing under one roof. Cross-sell attribution and per-entity pricing are hero, not footnote. The Founding 50 offer replaces a dead "Get Started" button with an actual high-intent capture path; waitlist emails already route to the founder inbox via Postmark. Platform admin dashboard means I can finally see signup velocity, tenant health, and failed-login counts without hitting the DB directly.
  • Screenshot/clip: vibe-market.app — hero + Founding 50 progress bar at 3 of 50 claimed
  • Post angle: "New landing for VIBE — the CRM for operators running real estate + mortgage + insurance under one roof. Cinematic gold-on-black, holographic Founding 50 card, three AI-rendered visuals, scroll-reveals everywhere. VIBE = Vertical Integration Business Enterprise. Waitlist is open. #buildinpublic #CRM #realestate"
  • Tags: #buildinpublic #CRM #VIBE #SaaS #landing #design #realestate #founding50

2026-03-08 — VIBE CRM Deployed to Production

  • What: VIBE CRM deployed to Vercel production. 56 routes live including dashboard, contacts, pipeline, SMS/calling, email sequences, automations, CFO dashboard, JV agreements, social posting, and settings. Fixed 11 lint errors, excluded mobile app from Next.js build, configured 9 env vars on Vercel.
  • Why it matters: First production deployment of the full CRM. Everything from lead capture to deal tracking to CFO financial overview — live and accessible. Multi-tenant architecture with Clerk auth, Neon Postgres, and tenant-scoped data isolation across 40+ models.
  • Screenshot/clip: https://vibe-crm-murex.vercel.app
  • Post angle: "VIBE CRM is LIVE. 56 routes. 40+ data models. SMS, calling, email sequences, pipeline kanban, CFO dashboard, JV agreements, social posting. From zero to deployed in 23 build days. Next.js 16 + Neon + Clerk + Stripe. The whole stack. #buildinpublic #CRM #SaaS"
  • Tags: #buildinpublic #CRM #VIBE #SaaS #deployment #Vercel #launch

2026-03-08 — Full Contact Management from Mobile

  • What: You can now create, search, and view contacts entirely from the React Native app. Contacts tab has debounced search, infinite scroll, status badges, and a FAB for quick add. Contact detail shows deals, pending tasks, and activity timeline with quick-action buttons (call/SMS/email via native dialer). Add form validates through Zod, enforces plan limits, detects duplicates, and auto-assigns via round-robin.
  • Why it matters: Contact management is the #1 daily action for CRM users. Having the full CRUD loop on mobile — with the same validation, dedup, and plan enforcement as the web app — means agents can add leads at open houses without touching a laptop.
  • Post angle: "Full contact CRUD from mobile. Search, add, view details with deals + activity timeline. Same Zod validation, plan limits, and dedup as web. Real estate agents can now add leads at open houses. #buildinpublic #reactnative #crm"

2026-03-08 — Pull-to-Refresh + Task Completion from Mobile

  • What: Added pull-to-refresh to all 5 mobile screens and tap-to-complete tasks with optimistic UI. New PATCH endpoint for task status updates. The task circle icon toggles pending/completed instantly, then syncs to the server — reverts if the API call fails.
  • Why it matters: CRM on mobile needs to feel native. Pull-to-refresh is table stakes. But completing a task with one tap while standing in a client's doorway? That's the UX that makes agents actually use the app instead of defaulting back to sticky notes.
  • Post angle: "Pull down to refresh. Tap to complete a task. VIBE CRM mobile now feels like a native app, not a web wrapper. Optimistic UI means instant feedback — reverts if the server disagrees. #buildinpublic #reactnative #ux"

2026-03-08 — Mobile Screens Wired to Live API

  • What: All 5 React Native screens now fetch real data from the VIBE CRM backend. Dashboard shows live KPIs, contacts list has debounced search with infinite scroll, pipeline has expandable stage cards, tasks have filter tabs with overdue indicators, and contact detail shows deals + activity timeline.
  • Why it matters: This is the jump from "scaffold that looks nice" to "app that actually works." Every screen pulls live tenant-scoped data through Clerk auth. The mobile app is now a real CRM client, not a mockup.
  • Post angle: "Every screen in VIBE CRM mobile now shows live data. Debounced search, infinite scroll, overdue task indicators, expandable pipeline stages. Same tenant isolation as the web dashboard. From mockup to real app in one session. #buildinpublic #reactnative #crm"

2026-03-08 — Mobile REST API Layer

  • What: Built 5 REST API endpoints under /api/mobile/ to power the React Native app — dashboard KPIs, paginated contacts with search, contact detail, task list with filters, and pipeline with deal totals. Plus a reusable withApiAuth() wrapper that handles Clerk session tokens from mobile.
  • Why it matters: Server actions don't work from mobile apps — you need REST. These endpoints reuse the exact same tenant-scoped data access patterns as the web app, so mobile gets the same security guarantees. One codebase, two clients.
  • Post angle: "Mobile API layer for VIBE CRM is live. 5 endpoints, same tenant isolation, same auth. Your React Native app talks to the same Prisma queries as the web dashboard. #buildinpublic #api #crm"

2026-03-08 — React Native Mobile App Scaffold

  • What: Scaffolded a React Native mobile app using Expo Router in the mobile/ directory. Clerk auth with SecureStore token cache, 5-tab navigation (Dashboard, Contacts, Pipeline, Tasks, More), contact detail screen with tap-to-call/SMS/email, and VIBE dark theme matching the web app. TypeScript strict, zero errors.
  • Why it matters: Real estate agents live on their phones. A native mobile experience means they can check pipeline, call leads, and log activity from the field — no browser tabs. PWA was the bridge; this is the destination.
  • Post angle: "VIBE CRM is going native. React Native + Expo Router + Clerk auth. Same dark gold branding, same data, native phone features. Tap-to-call a lead right from the contact card. #buildinpublic #reactnative #crm"

2026-03-08 — Public Changelog Page

  • What: Built a public /changelog page with a gold-dot timeline UI showing all 6 VIBE CRM releases (v0.1 through v1.2). Each release has color-coded badges — green for Added, blue for Improved, amber for Fixed. Automatically added to marketing nav, sitemap, robots.txt allow list, and Clerk public routes.
  • Why it matters: A public changelog builds trust with prospects and keeps existing users informed. It's also great build-in-public content — every release is documented with specific features, not vague "bug fixes." The timeline UI matches VIBE's dark gold branding.
  • Post angle: "Added a public changelog to VIBE CRM. 6 releases documented with color-coded badges. Your SaaS needs a changelog — it builds trust, shows momentum, and gives prospects proof you're actively shipping. #buildinpublic #saas #changelog"

2026-03-08 — SEO Infrastructure: robots.txt + Sitemap + Social Cards

  • What: Added robots.txt (blocks crawlers from app routes, allows marketing pages), XML sitemap for search engine indexing, OpenGraph + Twitter card metadata for social sharing, and page titles on every single route. Shared VIBE links now show rich preview cards on Twitter, LinkedIn, Facebook.
  • Why it matters: SEO is table stakes for SaaS marketing. Without robots.txt, Google might try to index your login page. Without OG metadata, shared links look like bare URLs. This is 30 minutes of work that compounds forever — every link shared, every Google crawl benefits.
  • Post angle: "SEO checklist for SaaS: robots.txt ✓ sitemap.xml ✓ OG cards ✓ page titles ✓ meta descriptions ✓. 30 minutes of config that makes every shared link and Google crawl work harder for you. #buildinpublic #seo #saas"

2026-03-08 — Security Audit: From 87 to 95 Score

  • What: Ran a full security audit (10 checks). Found 5 findings, fixed all of them: HMAC-signed unsubscribe URLs (prevents cross-tenant abuse), Content-Security-Policy header, rate limiting on magic-link endpoint, generic error messages on OAuth callback, deploy guide corrections.
  • Why it matters: Most indie hackers skip security until after a breach. Running a systematic audit before launch caught real vulnerabilities — the unsubscribe URL issue could have let attackers opt-out contacts across tenants. HMAC signing + timing-safe verification is the fix.
  • Post angle: "Ran a 10-check security audit on VIBE CRM before launch. Score: 87/100. Found a cross-tenant unsubscribe vulnerability, missing CSP header, and no rate limiting on magic links. Fixed all 5 findings → 95/100. Do your audit BEFORE launch, not after. #buildinpublic #security"

2026-03-08 — Production Hardening: Health Check + Error Boundaries

  • What: Added /api/health endpoint that pings the database and returns latency + status (200 healthy / 503 unhealthy). Added root error boundary for graceful error handling across all route groups. Fixed Vercel cron config — lead alerts corrected to 15-min intervals, added missing QBO hourly sync.
  • Why it matters: These are the "unsexy but critical" production readiness items. A health endpoint means uptime monitors can alert you before customers notice. Error boundaries prevent white screens of death. Correct cron schedules prevent alert fatigue (lead alerts every 5 min was way too aggressive).
  • Post angle: "The features nobody sees but everyone needs. Health checks, error boundaries, correct cron schedules. Production readiness isn't glamorous — but it's the difference between 'it works on my machine' and 'it works.' #buildinpublic #devops"

2026-03-08 — PWA: Install VIBE on Your Phone

  • What: Added Progressive Web App support with manifest.ts, apple-web-app metadata, viewport fit cover, and VIBE-branded SVG icon. Real estate agents can now tap "Add to Home Screen" on any phone/tablet and use VIBE CRM as a native-feeling app — no app store required.
  • Why it matters: PWA bridges the gap before React Native. Agents in the field can pull up contacts, check pipeline, and log calls from their phone's home screen. Standalone mode hides the browser chrome so it feels like a native app.
  • Post angle: "Your CRM should live on your home screen, not buried in browser tabs. Added PWA support — one tap install, works offline, feels native. No app store needed. #buildinpublic #pwa #realestate"

2026-03-08 — Social Posting + Lead Capture Webhooks

  • What: Built social media management with post composer (draft/schedule), multi-platform account connections (Facebook, Instagram, LinkedIn, Twitter/X), and inbound lead capture webhooks. The webhook endpoint auto-creates contacts with platform source attribution when leads come in from Facebook Lead Ads or Instagram. Timing-safe secret verification on all inbound webhooks.
  • Why it matters: This closes the marketing loop — post listings/content to social platforms, then capture leads back into the CRM automatically. No more manual data entry from ad platforms. Each webhook gets a unique secret URL that you paste into Facebook's webhook config.
  • Post angle: "Built the closed-loop: post to social → capture leads back into CRM automatically. Facebook Lead Ads hit our webhook, contact gets created with source attribution. Zero manual entry. 🔄 #buildinpublic #crm #realestate"

2026-03-08 — Partner Portal with Magic Links

  • What: Built a complete partner portal for JV partners. They enter their email at /partner/login, get a magic link via Postmark, click to verify, and land on a dashboard showing their agreement, split percentage, total earned, pending distributions, and linked deals. Auth is completely separate from Clerk — uses SHA-256 hashed tokens with 15-min expiry and httpOnly session cookies.
  • Why it matters: JV partners need visibility into their deals without getting full CRM access. This gives them a read-only portal with just their data. Magic links mean no passwords to manage. The security model is tight — separate auth, separate route group, time-limited tokens.
  • Post angle: "Your JV partners get their own portal. Magic link login. Zero passwords. They see their split, their earnings, their pending payments. All automated. Built in one session."

2026-03-08 — JV Agreement System

  • What: Built a full joint venture management system — create JV agreements, add partners with custom split percentages, create distributions with auto-calculated splits, track pending vs paid status. Two-column UI with agreement list + detail panel. Deals can link to JV agreements for deal-level profit tracking.
  • Why it matters: This is THE differentiator for VIBE's target market. Real estate owner-operators running JV deals need to track who gets what. No other CRM built for RE does this natively. Partner roles (partner/investor/referral), auto-split math, paid tracking — all in one place.
  • Post angle: "JV deals in your CRM. Add partners, set splits, track distributions. Auto-calculated shares, pending vs paid tracking. Built for owner-operators who run joint ventures. V2 is rolling."

2026-03-08 — QuickBooks Online Integration

  • What: Built full QuickBooks Online integration — OAuth 2.0 connect flow, hourly cron sync for invoices and payments, manual sync trigger, sync history log, and settings page. Incremental sync using Intuit's LastUpdatedTime filter. Auto-refreshes tokens with 5-minute buffer.
  • Why it matters: This completes V1.5 "CFO Differentiator". Owner-operators can now connect their QuickBooks account and see real accounting data flow into their CRM. No more switching between tabs. Revenue, invoices, payments — all synced automatically every hour.
  • Post angle: "Connected QuickBooks to the CRM. Invoices and payments sync every hour. One dashboard, one source of truth. V1.5 is DONE."

2026-03-08 — Multi-Entity Business Lines

  • What: Built multi-entity support — BusinessEntity model with full CRUD at /settings/entities. Owner-operators can define business lines (Real Estate, Mortgage, Insurance, Title, Construction, Investing), assign colors, set defaults, and tag contacts/deals to specific entities. Optional entityId FK on Contact and Deal models.
  • Why it matters: This is the V1.5 differentiator for vertically integrated real estate businesses. One tenant, multiple business lines — see which entity each contact and deal belongs to. No more spreadsheet juggling across companies.
  • Post angle: "One CRM. Multiple business lines. Real Estate + Mortgage + Insurance + Title — all under one roof. Color-coded entities, one-click assignment. Built for owner-operators who run it all."

2026-03-08 — CFO Dashboard

  • What: Built a full financial command center at /cfo — revenue KPIs with month-over-month change, conversion funnel visualization (lead→contacted→qualified→won), 6-month deal velocity bar chart, team performance leaderboard, and subscription health panel with usage bars.
  • Why it matters: This is the V1.5 "CFO Differentiator" — the feature that makes VIBE more than just a CRM. Owner-operators can see their entire business health in one view: revenue trends, team productivity, plan utilization, and sales velocity.
  • Post angle: "Your CFO dashboard, built in one session. Revenue, conversion, team performance, subscription health — all real-time. No spreadsheets. No guessing."

2026-03-08 — Multi-Site Engine + IDX Embed

  • What: Built a full site management system — tenants can create multiple websites, link IDX listing providers, generate embed code snippets, and publish/unpublish sites. Plan enforcement limits how many sites each tier can create (Free=0, Starter=1, Pro=5, Enterprise=20).
  • Why it matters: This is the last piece of V1 "Sellable SaaS". Real estate businesses need multiple websites (one per brand, listing site, etc.) — now they can manage them all from one CRM.
  • Post angle: "V1 is DONE. Multi-site engine is the final piece. One CRM, unlimited brands. Free → $49 → $149 → $399. Every feature shipped in 8 days."

2026-03-08 — Onboarding Wizard

  • What: Built a 3-step onboarding wizard for new tenants — Company Info (name/timezone/currency) → Pipeline Stages (add/remove/reorder) → Launch. New tenants are automatically redirected to /onboarding before seeing the main app. Uses separate route group (onboarding) to skip the sidebar layout.
  • Why it matters: First impressions are everything for SaaS. Instead of dropping new users into a blank dashboard, they get a guided setup. The pipeline stage customization means users start with stages that match their business, not generic defaults.
  • Post angle: "New users see this before anything else. 3 steps. 30 seconds. Fully customized CRM. No blank dashboard confusion."

2026-03-08 — V1: Usage Tracking + Seat Enforcement

  • What: Built a UsageCounter model (tenant × metric × YYYY-MM period) with incrementUsage() and checkUsageLimit() utilities. Wired SMS limit checks into manual send and sequence runner. Wired email limit checks into sequence runner. Added seat enforcement to team invite action AND Clerk webhook (prevents auto-provisioning members beyond plan limit). Added usageCounter to tenant-scoped models list.
  • Why it matters: Without usage tracking, a Free tier user could send unlimited emails/SMS through sequences. The monthly period-based counter resets automatically each month via the YYYY-MM key. Seat enforcement at both the UI action and webhook level means even Clerk-initiated member adds respect plan limits.
  • Post angle: "Plan limits need teeth at every entry point. Built usage tracking for VIBE CRM that caps emails and SMS per month per plan. The trick: enforce at the Clerk webhook too, not just the UI — otherwise member adds through Clerk bypass your limits. #buildinpublic #SaaS"

2026-03-08 — V1: Plan Enforcement + Stripe SDK v20 Fixes

  • What: Built plan enforcement system — checkPlanLimit() and enforcePlanLimit() utilities that check current resource counts vs. plan-defined limits before allowing creation of contacts, users, or sequences. Wired into 4 creation paths: contact create action, sequence create action, and both public form submission endpoints. Public forms silently accept but skip contact creation when limits are hit (no billing info leaked to public). Also fixed 3 Stripe SDK v20 type compatibility issues: current_period_end moved from Subscription to Subscription Items, invoice.subscription moved to invoice.parent.subscription_details.subscription.
  • Why it matters: Plan limits without enforcement are just suggestions. This is the "teeth" of the billing system — without it, a Free tier user could create unlimited contacts. The Stripe v20 type fixes ensure webhooks actually compile and work with the latest SDK, not just the API docs.
  • Post angle: "Plan limits without enforcement are just suggestions. Built a plan enforcement layer for VIBE CRM — checks resource counts against plan limits before every create operation. Free tier gets 100 contacts, Starter gets 2,500, Pro gets 25,000. Public forms silently cap without leaking billing info. The 'teeth' of the billing system. #buildinpublic #SaaS #CRM"

2026-03-08 — V0.5 Bonus: Contact Merge Tool

  • What: Built a contact merge feature — select 2+ duplicate contacts from the table, pick the primary, preview what'll be reassigned, and merge. Handles 13 different relation types (activities, deals, SMS, calls, sequence enrollments, etc.) with proper unique constraint deduplication. Soft-deletes the merged contacts, preserves opt-out flags, and fills empty fields from donors.
  • Why it matters: Duplicate contacts are inevitable in a CRM — form resubmissions, manual entry, imported CSVs. Without merge, you get fragmented timelines and wrong metrics. This is the kind of data hygiene tool that separates toy CRMs from real ones.
  • Post angle: "Duplicate contacts kill your pipeline metrics. Built a merge tool for VIBE that reassigns 13 relation types, deduplicates across unique constraints, and preserves opt-outs. One click, zero data loss."

2026-03-08 — V0.5 Bonus: Roles & Permissions Admin UI

  • What: Built an interactive permission matrix at /settings/roles. Shows all 5 roles (Owner, Admin, Producer, Assistant, TC) across 10 resources × 4 actions as a clickable grid. Each cell toggles grant/deny with a scope switch (all records vs assigned-only). Owner role has protection against accidental lockout.
  • Why it matters: Most CRMs ship permissions as "admin or not." VIBE gives real estate teams granular control — a TC can edit their assigned deals but not delete contacts. Producers see only their own leads. This is the kind of detail that enterprise buyers expect.
  • Post angle: "Your team isn't one-size-fits-all. Why is your CRM? Built a visual permission matrix for VIBE — 5 roles, 10 resources, granular scope control. Real estate teams need more than just 'admin or not.'"

2026-03-08 — V0.5 Bonus: Postmark Bounce/Spam Webhook + Email Tracking

  • What: Built a Postmark webhook handler for bounce, spam complaint, open, and click events. Added postmarkMsgId tracking to EmailSend model so bounces trace back to the exact contact. Hard bounces and spam complaints auto-opt-out contacts and pause their active sequence enrollments. Sequence runner now creates EmailSend records for every email sent.
  • Why it matters: Email deliverability is everything in a CRM. One spam complaint from a contact you keep emailing can get your Postmark account suspended. This webhook loop closes the feedback cycle — send email → track delivery → auto-suppress bad addresses.
  • Post angle: "Your CRM sends emails. But does it listen? Built a Postmark webhook that auto-opts out contacts on hard bounces and spam complaints. Deliverability hygiene isn't optional when you're managing emails for real estate businesses."

2026-03-08 — V0.5 Day 23: Security Hardening + Compliance Audit

  • What: Ran a 16-point security audit against the codebase. Found 2 CRITICAL, 4 HIGH issues. Fixed all P0/P1 items in one session: added 11 missing models to tenant-scoping middleware, added Twilio signature verification to 2 unprotected webhooks, replaced timing-vulnerable string comparison with crypto.timingSafeEqual for cron auth, added security headers (HSTS, X-Frame-Options DENY, nosniff, Referrer-Policy, Permissions-Policy).
  • Why it matters: Security is what separates a demo from a product. Multi-tenant CRMs handle PII — one missing tenant scope filter = data leak across businesses. This audit caught real gaps that manual code review missed.
  • Post angle: "Ran a security audit on VIBE CRM before V0.5 deploy. Found 11 database models missing from tenant-scope middleware — any query on those tables could have leaked data between tenants. Fixed in 15 minutes. This is why you audit before you ship. #buildinpublic #security #CRM"

2026-03-08 — V0.5 Days 16+19: Click-to-Call + Sequence Worker + Email Engine

  • What: Two major V0.5 features in one session. (1) Day 16 Click-to-Call: CallLog component with call button, status badges, post-call notes, and a "Calls" tab wired into contact detail alongside Activity and SMS. Voice webhook maps Twilio status callbacks to DB records. (2) Day 19 Sequence Worker: Postmark email integration, cron-based sequence runner that processes due enrollments (email via Postmark, SMS via Twilio), advances steps, marks completions. Unsubscribe endpoint with branded confirmation page. Template variable replacement ({{name}}, {{first_name}}, etc.). Enhanced seed now creates 5 roles, 8 pipeline stages, assignment rules, and default form.
  • Why it matters: Click-to-call is the feature that turns a CRM from a database into a communication tool. Sequence worker is the automation backbone — set up a drip campaign once, and the system sends emails and texts on schedule. Unsubscribe endpoint keeps you CAN-SPAM compliant from day one.
  • Post angle: "VIBE CRM V0.5 is almost done. Click-to-call with status tracking, automated email sequences via Postmark, SMS drip campaigns via Twilio — all running on a 5-minute cron. CAN-SPAM compliant unsubscribe built in. 10 of 12 V0.5 days complete. #buildinpublic #CRM #SaaS"

2026-03-08 R8 — Reports Page: 11 Queries, Stat Cards + Breakdowns

  • What: Built the VIBE CRM reports page with 11 database queries powering stat cards and pipeline/source/agent breakdowns (e3fd59c). Real analytics over real tenant-scoped data — speed-to-lead, conversion rates, pipeline value, top performers.
  • Why it matters: Reports are the "prove it works" feature. Every CRM promises insights — this one actually delivers them with sub-second queries on tenant-scoped Prisma models.
  • Post angle: "Built the reports page for the CRM. 11 queries, stat cards, pipeline breakdowns, speed-to-lead metrics. The data layer that turns a contact list into a business intelligence tool. #buildinpublic #nextjs #crm"

2026-03-07 — V0 Day 1: Contact CRUD Feature Complete

Milestone: Core Contact CRUD shipped — the feature everything else in the CRM depends on.

What got built

8 new files, zero TypeScript errors:

  • src/lib/dedupe.ts — Email-first duplicate detection helper
  • src/app/(app)/contacts/actions.ts — 5 server actions with RBAC, tenant scoping, audit logging, and soft delete
  • src/app/(app)/contacts/page.tsx — Server-rendered contacts list with URL-driven filters
  • src/app/(app)/contacts/contact-list-client.tsx — Client shell: search, status filter, sort, pagination, optimistic create
  • src/app/(app)/contacts/[contactId]/page.tsx — Dynamic server-rendered contact detail page
  • src/app/(app)/contacts/[contactId]/contact-detail-client.tsx — Detail view with edit/delete, activity timeline placeholder
  • src/components/contacts/contact-form.tsx — Reusable create/edit form (shadcn Input, Select, Label)
  • src/components/contacts/contact-table.tsx — shadcn Table with row-level edit/delete, optimistic updates
  • src/components/contacts/contact-status-badge.tsx — Color-coded Badge for all 6 ContactStatus values

Technical highlights worth posting about

  • Multi-tenant architecture: every DB call auto-scoped to tenant via AsyncLocalStorage + Prisma extension — zero chance of cross-tenant data leakage
  • RBAC scope enforcement: 'assigned' scope users can only see/edit their own contacts — checked at action level, not just UI
  • Soft delete: contacts get deletedAt timestamp, never hard-deleted — full audit trail preserved
  • Dedupe on create: email-first then phone match, surfaces warning with existing contact name instead of silent failure
  • Identity table stays in sync: ContactIdentity rows created/updated alongside contact so dedup lookup stays fast even at scale
  • Optimistic updates: list updates instantly on create/delete/edit without waiting for full server re-fetch
  • Zero tsc errors on first compile pass after targeted fixes

Stack used

Next.js 15 App Router + TypeScript + Prisma + Clerk + shadcn/ui + Zod v4

Potential X/Twitter angles

  • "Built a multi-tenant contact manager in one session — every DB query auto-scoped to the right tenant via Prisma extension + AsyncLocalStorage. Here's how it works..."
  • "Soft delete > hard delete for a CRM. Here's why and how we did it with Prisma..."
  • "Contact deduplication on create: email first, then phone. If we find a match we surface the existing record instead of creating a duplicate. Clean UX, simple implementation."
  • "Clerk + Prisma RBAC: how we check permissions AND scope queries in one withPermission() call before any data hits the wire"

Next sprint targets

  • Activity timeline (notes, calls, emails, stage changes)
  • Tag management UI
  • Bulk import from CSV
  • Pipeline/deals linking from contact detail

2026-03-07 — V0 Day 6: Activity Timeline + Task Engine

Milestone: Full activity feed on contact detail + complete task management system — the ops layer of the CRM.

What got built

9 new / modified files, zero TypeScript errors:

  • src/app/(app)/contacts/[contactId]/actions.ts — getActivities() (paginated, tenant-scoped) + createNote() server actions
  • src/app/(app)/contacts/[contactId]/page.tsx — Updated to parallel-fetch contact + activities; passes both to client
  • src/app/(app)/contacts/[contactId]/contact-detail-client.tsx — Placeholder replaced with live ActivityTimeline component
  • src/components/contacts/activity-timeline.tsx — Full chronological feed with type icons, relative timestamps, inline Add Note form
  • src/app/(app)/tasks/actions.ts — getTasks(), createTask(), updateTask(), completeTask(), getTasksForContact() server actions
  • src/app/(app)/tasks/page.tsx — Server component; reads filter + page from searchParams
  • src/app/(app)/tasks/task-list-client.tsx — Filter tabs (All / My Tasks / Overdue), responsive table, New Task dialog, optimistic toggle
  • src/components/tasks/task-form.tsx — Full create/edit form: title, description, contact ID, assignee ID, due date picker, priority select
  • src/components/tasks/task-status-toggle.tsx — Checkbox-style quick complete/reopen with loading state

Technical highlights worth posting about

  • Priority without a DB column: encoded as a metadata prefix in the description field (__priority:high__\n...) — decoded on read. Zero migration required for V0; V1 adds the real column.
  • Task completion creates an Activity on the linked contact — one completeTask() call updates the task AND writes to the contact timeline automatically
  • ActivityTimeline is fully optimistic: new notes prepend instantly without a refetch; server revalidatePath() runs in background to keep SSR cache warm
  • relativeTime() helper is fully client-side — no server/client hydration mismatch from locale-dependent date formatting
  • Overdue detection is computed in toTaskRow() at read time: status===pending && dueAt < now — consistent whether viewed in the list or contact detail
  • Scope enforcement on tasks: 'assigned' users can only toggle/edit their own tasks; checked in every action before any DB write

Stack used

Next.js 15 App Router + TypeScript + Prisma + Clerk + shadcn/ui + Zod v4 + lucide-react

Potential X/Twitter angles

  • "CRM activity timeline: how I wire server actions + client optimistic state so notes appear instantly while the server cache rehydrates in the background"
  • "Storing priority without a migration: encode it as a metadata prefix in an existing text field. Works for V0, swap for a real enum column in V1. Ship fast, refactor clean."
  • "Task completion auto-writes to the contact's activity feed — one server action, two writes, zero client coordination needed. That's the power of server actions."
  • "How to build a multi-tenant task engine where 'assigned' scope users can only see their own tasks — enforced at the action layer, not just the UI"

Next sprint targets

  • Tag management UI on contact detail
  • Pipeline board (Kanban) — deals per stage
  • Contact selector component (searchable dropdown) to replace UUID input in TaskForm
  • Bulk task assignment
  • Due-soon notification trigger (task_due_soon NotificationType already in schema)

2026-03-07 — Pipeline Engine Pre-Scaffold (Epic 2, pre-sprint)

Milestone: Complete pipeline engine scaffold written locally and ready to copy into Replit for the March 10 V0 sprint.

What got built

5 files, 1,498 lines — all strictly typed, zero any, aligned to codebase conventions:

  • vibe-crm-scaffold/pipeline/schemas.ts — Zod v4 validation schemas for deals and stages
  • vibe-crm-scaffold/pipeline/lib/pipeline.ts — Domain layer: all Prisma operations, serialisation helpers, kanban grouping
  • vibe-crm-scaffold/pipeline/actions.ts — 7 server actions with full RBAC, audit logging, revalidatePath
  • vibe-crm-scaffold/pipeline/page.tsx — Server component: fetches board, passes to client
  • vibe-crm-scaffold/pipeline/pipeline-client.tsx — Kanban board: HTML5 drag-and-drop, useOptimistic moves, new-deal dialog

Technical highlights worth posting about

  • moveDeal() uses a Prisma $transaction that atomically updates the deal's stage AND writes a stage_change Activity on the contact's timeline — one DB round-trip, consistent audit trail
  • reorderStages() verifies all stage UUIDs belong to the tenant before the transaction fires — prevents a cross-tenant stage injection attack in the drag payload
  • useOptimistic makes kanban drag feel instant: deal moves in the UI before the server responds; reverts automatically on failure
  • getDealsByStage() groups deals server-side so the client receives a fully shaped KanbanColumn[] — no client-side grouping logic needed
  • createPipelineStage() shifts existing stage positions up atomically when inserting mid-pipeline — no gaps, no collisions
  • All Date fields serialised to ISO strings before crossing the server/client boundary — avoids Next.js "non-serialisable props" errors
  • getTenantDb() Prisma extension auto-injects tenantId on every query — the pipeline lib functions pass tenantId explicitly as a secondary guard

Stack used

Next.js App Router (server actions + server components) + TypeScript strict + Prisma 6 + Zod v4 + shadcn/ui + lucide-react

Potential X/Twitter angles

  • "Pre-scaffolded an entire CRM kanban pipeline engine before the sprint even starts. Here's the architecture: 5 files, 1,500 lines, zero any."
  • "useOptimistic + server actions for drag-and-drop kanban: how to make cards move instantly and roll back silently on failure — no client state library needed"
  • "Prisma $transaction trick: when you drag a deal to a new stage, it moves the deal AND logs the activity in one atomic write. The UI never shows a partial state."
  • "Multi-tenant security in a kanban board: the drag payload contains stage UUIDs. We verify all of them belong to your tenant before touching the DB."

Copy-to-Replit checklist

  • [ ] Copy vibe-crm-scaffold/pipeline/ into src/app/(app)/pipeline/
  • [ ] Move lib/pipeline.ts to src/lib/pipeline.ts (or keep co-located — both patterns work)
  • [ ] Add pipeline routes to the sidebar nav component
  • [ ] Replace ContactId text input in NewDealDialog with ContactCombobox component
  • [ ] Wire up real pipelineId from tenant context (query pipeline.findFirst({ where: { tenantId } }) in page.tsx)
  • [ ] Install @dnd-kit/core for production drag-and-drop (HTML5 drag is the scaffold baseline)
  • [ ] Add deals.view permission check to getPipelineBoard if needed (currently uses pipeline.view)
  • [ ] Run npx tsc --noEmit after copying to catch any import path differences

2026-03-07 — V0 Day 9: Pipeline Engine Live in Repo

Milestone: Kanban deal board integrated into the actual VIBE CRM codebase — no more scaffold files.

What got built

Pipeline Engine integrated directly into the project with full kanban board:

  • src/app/(app)/pipeline/actions.ts — 7 server actions (getPipeline, createDeal, moveDeal, updateDeal, closeDeal, deleteDeal, createPipelineStage, reorderStages)
  • src/app/(app)/pipeline/page.tsx — Server component with SSR pipeline fetch
  • src/app/(app)/pipeline/pipeline-client.tsx — Kanban board with HTML5 drag-and-drop + useOptimistic
  • src/app/(app)/pipeline/deal-modal.tsx — Create/edit deal modal with stage selection
  • src/lib/validations/pipeline.ts — Zod schemas for stage creation, reorder, move, close
  • Fixed deals/actions.ts schema drift (removed non-existent isDefault field + dealActivity model)

Post angle

"Pipeline kanban board in VIBE CRM is live. Drag deals between stages, optimistic UI, stage management. useOptimistic makes it feel instant — server catches up in the background."

2026-03-07 — V0 Day 10: Lead Capture Forms + CEO Dashboard

Milestone: Lead capture pipeline and executive dashboard shipped — the CRM can now ingest leads from external sites and show speed-to-lead metrics.

What got built

8 new/modified files, zero TypeScript errors:

  • src/lib/validations/form.ts — Zod schemas for form creation, updates, and public submissions with honeypot field
  • src/app/(app)/forms/actions.ts — 5 server actions: getForms, getForm, createForm, updateForm, deleteForm
  • src/app/(app)/forms/page.tsx — Server-rendered forms list page
  • src/app/(app)/forms/form-list-client.tsx — Form builder modal with drag-to-reorder fields, embed code generator
  • src/app/api/forms/[formId]/submit/route.ts — Public submission endpoint: rate limit → honeypot → validate → dedupe → assign → activity
  • src/app/(app)/dashboard/actions.ts — getDashboardData() with 8 parallel queries for all metrics
  • src/app/(app)/dashboard/page.tsx — Server-rendered dashboard page
  • src/app/(app)/dashboard/dashboard-client.tsx — KPI cards, speed-to-lead gauge, pipeline breakdown chart, top sources, activity feed

Technical highlights worth posting about

  • Public form endpoint chains 5 operations in sequence: rate limit (10/min/IP) → honeypot check → field validation → contact dedupe → round-robin assignment
  • Honeypot anti-bot: hidden field that returns 200 OK but silently discards the submission — bots never know they were caught
  • Speed-to-lead metric calculates avg + median response time with color coding: green (<5min), yellow (<30min), red (>=30min)
  • Dashboard runs 8 queries in parallel via Promise.all — lead counts, speed-to-lead, pipeline stages, activities, top sources all in one render
  • UTM tracking auto-captured from form submissions and stored in TrackingEvent table
  • Embed snippet auto-generates with copy-to-clipboard, includes UTM capture from URL params

Stack used

Next.js 15 App Router + TypeScript + Prisma + Clerk + shadcn/ui + Zod v4

Potential X/Twitter angles

  • "Built a lead capture pipeline that goes from form submission to assigned contact in <100ms. Rate limiting, bot detection, dedupe, round-robin assignment — all in one POST endpoint."
  • "Speed-to-lead is the most important CRM metric nobody tracks. Built a dashboard that calculates avg + median response time and color-codes it: green if you're fast, red if you're losing deals."
  • "Anti-bot form protection without CAPTCHA: a hidden honeypot field that returns 200 OK but silently drops the submission. Bots never know they were caught."

Next sprint targets

  • Notifications (in-app notification center, lead assigned, task overdue)
  • Settings page for assignment rule config UI
  • Security hardening pass (rate limiter, security headers, error boundaries)
  • Deploy prep (Replit packaging)

2026-03-07 — Settings Hub: Complete Tenant Configuration Center

Milestone: Full settings section live — General, Pipeline Stages, Team Members, and Assignment Rules all accessible from a unified left-nav layout.

What got built

11 new files, zero TypeScript errors, build passes:

  • prisma/schema.prisma — Added timezone (String, default "America/New_York") and currency (String, default "USD") columns to Tenant model
  • src/lib/validations/settings.ts — Zod schemas: TenantSettingsUpdate, StageRename, TeamInvite, TeamRoleUpdate
  • src/app/(app)/settings/actions.ts — getTenantSettings(), updateTenantSettings() with settings.edit permission guard
  • src/app/(app)/settings/pipeline/actions.ts — getPipelineStages(), createStage(), updateStage(), deleteStage(), reorderStages() — all using pipeline.edit permission
  • src/app/(app)/settings/team/actions.ts — getTeamList(), updateMemberRole(), removeMember(), inviteMember() with self-removal guard
  • src/app/(app)/settings/layout.tsx — Two-column layout: sticky SettingsNav left, scrollable content right
  • src/components/settings/settings-nav.tsx — Client nav with exact-match active state for /settings root
  • src/app/(app)/settings/page.tsx — General settings server page
  • src/components/settings/general-settings-form.tsx — Company name, timezone (13 zones), currency (6 options), read-only workspace info, dirty-state guard on Save
  • src/app/(app)/settings/pipeline/page.tsx — Pipeline stages server page
  • src/components/settings/stage-manager.tsx — HTML5 drag-to-reorder, inline add, rename dialog, delete-with-confirmation (blocked if open deals exist)
  • src/app/(app)/settings/team/page.tsx — Team members server page
  • src/components/settings/team-list.tsx — Member list with role selector, remove confirm, invite-by-email dialog

Technical highlights worth posting about

  • Delete guard: can't delete a pipeline stage that has open deals — the action checks _count.deals at the DB level, not just in the UI. Error surfaces with exact deal count.
  • Drag-to-reorder is zero-dependency: pure HTML5 drag events with React state — no dnd-kit, no @hello-pangea/dnd. Optimistically reorders locally then fires reorderStages() on dragEnd.
  • Self-removal protection: removeMember() compares the target's userId against ctx.userId — you literally cannot remove yourself even with direct API calls.
  • Schema evolution without migration tooling: added timezone and currency directly to schema, ran prisma generate to update types, changes ready for db push on next deploy.
  • Role selector is inline on the member row — onValueChange fires immediately, no Save button needed. Single optimistic-style update with toast confirmation.

Stack used

Next.js 16 App Router + TypeScript + Prisma + Clerk + shadcn/ui + Zod + lucide-react

Potential X/Twitter angles

  • "Settings pages are where CRM products live or die. Built the entire tenant config hub today: company profile, pipeline stages, team roles — all with one consistent left-nav. Here's the architecture."
  • "How to block deleting a pipeline stage with active deals: check _count at the DB layer, not the UI layer. If the DB says there are 3 open deals, no amount of frontend tricks can bypass it."
  • "Zero-dependency drag-to-reorder with HTML5 drag events and React state. Fires a batch reorder server action on dragEnd. No library needed for admin interfaces like this."
  • "Self-removal protection in a team manager: compare ctx.userId against the target's userId in the server action. The UI hides the button, but the server check is what actually matters."

March 7, 2026

V0 Days 1-6 Code Complete

  • What: Built full Contact CRUD (5 actions, list/detail pages, dedupe), Activity Timeline, Task Engine, Lead Forms
  • Why it matters: 6 days of V0 sprint work done before the March 10 build start date
  • Post angle: "Built 6 days of CRM features in one session. Contact management, activity tracking, task engine, lead capture — all with multi-tenant isolation. VIBE CRM V0 ahead of schedule."

2026-03-07 — In-App Notifications System

Milestone: Full in-app notification center shipped — bell badge in nav, paginated list, per-type icons, mark read, mark all read.

What got built

4 files, zero TypeScript errors, zero any types:

  • src/app/(app)/notifications/actions.ts — Full rewrite: getNotifications(page), markRead(id), markAllRead(), getUnreadCount(). All four actions use notifications.view permission, getTenantDb() tenant-scoping, Zod input validation, and revalidatePath after mutations. markRead() double-checks userId ownership before writing.
  • src/app/(app)/notifications/page.tsx — Server component; fetches page 1 SSR, passes typed data to client. Metadata set to Notifications | VIBE CRM.
  • src/app/(app)/notifications/notifications-client.tsx — Full client: notification cards with per-type lucide icon + colour, relative timestamps, gold left-border accent on unread, "Mark all read" button, optimistic state updates, load-more pagination, empty state.
  • src/components/notifications/notification-bell.tsx — Compact bell icon + badge for nav/header. Fetches unread count on mount via server action. Badge shows count (capped at 99+), red dot, links to /notifications.

Technical highlights worth posting about

  • markRead() is idempotent: if readAt is already set it returns { ok: true } immediately — safe to call twice without double-writing
  • markRead() has two layers of ownership verification: getTenantDb() extension blocks cross-tenant access at the Prisma level, then the action explicitly checks existing.userId === ctx.userId — both must pass
  • Optimistic state in NotificationsClient: cards update to "read" instantly via onRead callback; server revalidation runs in background — no visible flicker
  • useTransition + startLoadMoreTransition keeps the current list visible while the next page loads — no layout shift
  • Bell badge uses cancelled flag in useEffect cleanup to prevent setState on unmounted component — no memory leak
  • NotificationType config map drives icon, colour, badge variant, and label from a single source — adding a new notification type means one object entry, nothing else changes

Stack used

Next.js 16 App Router + TypeScript strict + Prisma 6 + Clerk + shadcn/ui (Button, Badge, Skeleton) + lucide-react + Zod

Potential X/Twitter angles

  • "Built a full notification center for VIBE CRM today. Bell badge, paginated list, per-type icons, mark-all-read. Here's how I keep it secure: two ownership checks per mutation — Prisma tenant extension + explicit userId match."
  • "useTransition for load-more: the existing list stays visible while the next page loads. No spinner replacing content, no layout shift. One hook, smooth UX."
  • "Notification bell badge that can't accidentally leak cross-tenant unread counts: getTenantDb() auto-scopes the count query to the right org via AsyncLocalStorage. Zero chance of seeing another tenant's data."
  • "Idempotent server actions: markRead() checks if readAt is already set before writing. Safe to call twice, safe to retry on network failure. Build your mutations this way."

Next sprint targets

  • Wire NotificationBell into Sidebar nav
  • Trigger notifications from existing actions (lead_assigned on convertLeadToContact, task_due_soon/task_overdue from task engine)
  • Real-time badge refresh via polling interval or Server-Sent Events

2026-03-08 — V0 Polish Pass: Bell Badge, Loading Skeletons, Mobile Fix

  • What: Wired NotificationBell into the app layout top bar (sticky, always visible). Added per-route loading skeletons for 7 pages (dashboard, contacts, deals, pipeline, forms, tasks, notifications) — each matches the page's actual layout shape. Fixed dashboard header overflow on mobile with responsive flex-wrap.
  • Why it matters: Loading skeletons are what separate a prototype from a product. Users see the shape of what's coming instead of a blank page. The bell badge makes notifications discoverable without navigating away from any page.
  • Post angle: "Loading skeletons that match your page layout > generic spinners. Each route gets its own skeleton shaped like the real content — kanban columns for pipeline, stat cards for dashboard. Small touch, big UX lift."

2026-03-08 — R7: Epic 2 Enhancements — Email Templates + Import/Export

  • What: R7 session — VIBE CRM Epic 2 enhancements in progress. Working on email template system for automated outreach and contact import/export functionality. V0 is feature complete (Days 1-11), now building out the E2 enhancement layer before deploy.
  • Why it matters: Email templates turn VIBE from a contact tracker into an outreach engine. Import/export lets agents migrate from spreadsheets without data entry pain. These are the features that close the gap between "CRM" and "lead engine."
  • Post angle: "VIBE CRM V0 is done. Now building Epic 2: email templates for automated outreach and CSV import/export. Every real estate agent I've talked to has contacts trapped in spreadsheets. Import button solves that day one. #buildinpublic #CRM #SaaS"
  • Tags: #buildinpublic #CRM #VIBE #SaaS #Epic2 #realestate

2026-03-08 — V0 Feature Complete + Deploy Guide

Milestone: V0 Fully Built, Deploy Guide Created

  • What: VIBE CRM V0 is feature complete — all 11 days of planned sprint work finished ahead of the March 10 start date. Full feature set: Contact CRUD with dedupe, Activity Timeline, Task Engine, Lead Capture Forms with honeypot anti-bot, Pipeline Kanban with drag-and-drop, CEO Dashboard with speed-to-lead metrics, Settings Hub (general/pipeline/team), Notifications with bell badge, and loading skeletons across all 7 routes. Deploy guide created covering Neon database provisioning, Clerk auth setup, environment variable configuration, and Vercel deployment steps. GitHub repo created at davidolverson/vibe-crm with all commits pushed.
  • Why it matters: V0 was supposed to start March 10 — it is already done. The deploy guide means the project can go from repo to production in one session once Neon and Clerk are provisioned. The CRM has real multi-tenant isolation, RBAC, and audit logging from day one.
  • Post angle: "VIBE CRM V0 is 100% feature complete. 11 days of work done before the sprint even started. Contact management, pipeline kanban, lead capture, CEO dashboard, notifications — all with multi-tenant isolation. Deploy guide written. Ready for production. #buildinpublic #CRM #SaaS"

Tags: #buildinpublic #CRM #VIBE #SaaS #V0 #deploy #milestone

2026-03-08 — R8: Reports Analytics + Email Templates + CSV Import/Export

Milestone: 22+ Routes, Epic 2 Expanding

  • What: R8 session delivering three Epic 2 enhancements. (1) Reports/analytics page with 11 parallel Prisma queries — contact growth, pipeline value by stage, conversion rates, activity breakdown, top sources, speed-to-lead trends, all computed server-side via Promise.all. (2) Email templates CRUD system — create, edit, delete, and preview reusable email templates with variable interpolation ({{contact.name}}, {{deal.value}}, etc.) for automated outreach sequences. (3) Contact CSV import/export — bulk import contacts from CSV with column mapping, duplicate detection, and validation errors per row; export filtered contact lists to CSV with all fields. VIBE CRM now has 22+ routes.
  • Why it matters: Reports with 11 parallel queries means the analytics page loads fast despite pulling from 6+ tables — no sequential waterfall. Email templates turn VIBE from a contact tracker into an outreach engine. CSV import/export is the #1 feature request from agents migrating from spreadsheets — without it, onboarding is manual data entry hell.
  • Post angle: "VIBE CRM R8: reports page with 11 parallel Prisma queries, email template builder with variable interpolation, and CSV import/export with per-row validation. 22+ routes and growing. The features that turn a CRM into a lead engine. #buildinpublic #CRM #SaaS"

Tags: #buildinpublic #CRM #VIBE #SaaS #E2 #reporting #emailtemplates #import

2026-03-08 — E2: Reporting Analytics + Settings Hub

  • What: Added full reporting/analytics page with contact growth, pipeline value, and activity stats. Settings hub with general config, pipeline stage manager (drag-to-reorder), and team management (invite, role change, remove). Email templates system and contact import/export also landed.
  • Why it matters: Reporting closes the "what's happening in my business?" loop. Settings hub lets admins customize without developer help. Pipeline stage manager with drag-and-drop reorder means brokerages can model their exact workflow. Team management with RBAC means the owner controls who sees what.
  • Post angle: "VIBE CRM now has a CEO dashboard AND a detailed reports page. Pipeline value by stage, contact growth over time, activity metrics. Every data point the owner needs to make decisions, built with server components for fast initial load. #buildinpublic #CRM"
  • Tags: #buildinpublic #CRM #VIBE #SaaS #E2 #reporting #analytics

2026-03-08 — Post-Deploy Polish: Dashboard UX + Enriched Icons + Dark Theme Fix

  • What: Major post-deploy polish session. Fixed dark theme (shadcn class="dark" missing on html), corrected pricing ($1k/$1.5k/$2.5k), fixed Sites page (free plan couldn't create sites), overhauled dashboard UX (period selector, quick actions, trend indicators, gradient pipeline bars, timeline activity feed with glowing dots), upgraded all dashboard icons to dual-tone gradient SVGs with gold accents, added sidebar gold gradient branding with inset border on active nav, created 12 loading skeletons for all pages missing them. Full audit: 54 pages, 42 action files, 36 components — zero missing imports or stubs.
  • Why it matters: First impressions matter. The dashboard went from functional to premium-feeling. Dual-tone gradient icons with gold fills feel like a $2,500/mo product, not a $0 free tier. The dark theme fix affected every shadcn component across the entire app.
  • Screenshot/clip: Dashboard with gradient pipeline bars + speed-to-lead card with corner glow
  • Post angle: "Polished VIBE CRM's dashboard to premium quality. Dual-tone gradient icons, gold accent pipeline bars, timeline activity feed with glowing status dots, speed-to-lead card with gradient glow. The difference between a CRM and a command center. #buildinpublic #CRM #UX"
  • Tags: #buildinpublic #CRM #VIBE #UX #design #icons #dashboard
VIBEVertical Integration Business Enterprise

CRM for vertically integrated real estate operators. Now live.

PricingChangelogSign inPrivacyTermsContact

© 2026 VIBE · vibe-market.app · Powered by ModernGrindTech