Forge v1 — Session Summary

ForgeKit · 2026-06-16 · app.forgekits.build

ForgeKit scaffolded, architecture locked, Forge built from scratch, deployed to app.forgekits.build in one session. 10 hours. Zero to live.

6:00 PM
ForgeKit scaffold — docs, rules, security fix
8:00 PM
Architecture — Forge platform, Armory, Gino intro
9:00 PM
Build — Next.js 16 app from scratch
11:00 PM
Deployment hell — BOM corruption, Clerk, Vercel
2:00 AM
Seed + polish — demo data, mobile, automations
4:00 AM
v1 shipped

ForgeKit Scaffolding

  • CLAUDE.md, PROJECT_INDEX.md, HOW_TO_WORK_WITH_ZEB.md
  • .claude/rules/ (ai-patterns, extraction, forge, security, testing)
  • docs/: app-inventory, reusable-patterns (13 patterns), extraction-backlog
  • Security fix: Happenstance moved Anthropic SDK off browser → server-side API route

Architecture Decisions

  • ForgeKit (company) → Forge (platform) → Armory (modules)
  • Capabilities-first, not products-first
  • Stack locked: Next.js 16, Tailwind + shadcn/ui, Neon, Drizzle, Clerk, Twilio, Inngest, Anthropic
  • First customer: Gino's Electric, Medina OH

Forge App — Built from Zero

  • Drizzle schema: orgs, stages, deals, contacts, notes, sequences
  • Kanban pipeline (New Lead → Estimate Sent → Scheduled → Complete → Paid)
  • Contacts list + contact detail pages
  • Dark sidebar layout with ForgeKit branding
  • Clerk auth with Next.js 16 proxy.ts pattern
  • Labels say "Jobs" — Gino's language

Domain + Infrastructure

  • forgekits.build ($25/yr on Cloudflare)
  • app.forgekits.build as Forge subdomain
  • Clerk production instance with Account Portal
  • GitHub repo: Jungebez/forge

Demo Seed Data

  • Gino's Electric with real-looking jobs
  • 7 deals across pipeline stages
  • Contacts with phone numbers
  • Notes on deals

Mobile Layout

  • Dark top bar + bottom tab nav (replaces sidebar on mobile)
  • Stacked stage view on small screens
  • Active nav highlight in orange

4 Automations

  • Missed-call SMS auto-reply (Inngest)
  • Estimate follow-up (day 3, 7, 14)
  • Appointment confirmation
  • Review request (24h post-complete)
  • Automation settings page with toggles

Test Suite — 48 Tests

  • 21 unit tests: render(), formatCents(), Twilio sig, config shape
  • 27 API tests against real Neon DB
  • E2E Playwright specs (stubs)
  • Fixed Vitest/dotenvx DATABASE_URL override

Splash Page

  • Public landing at /
  • Redirects authenticated users to /pipeline
  • ForgeKit identity imagery
10h
Single session
1
App built
48
Tests
4
Automations
9
Commits

Next.js 16 + Clerk v7 — middleware.ts vs proxy.ts

Clerk's clerkMiddleware expects the file Next.js 16 renamed to proxy.ts, and requires a named export. Multiple attempts with wrapped exports, no-op proxies, and trying to downgrade to Next.js 15 all failed — downgrade broke shadcn v4 which requires React 19 / Next.js 16.

500 errors on all authenticated routes in production.

proxy.ts with named export export { clerkMiddleware as proxy }. Rule: always check Next.js version before wiring Clerk middleware.

BOM corruption on Vercel env vars — root cause of 4+ hours of 500s

Windows PowerShell injected a U+FEFF byte-order mark when piping secrets via vercel env add. Keys arrived as pk_live_... instead of pk_live_.... Clerk and Neon both rejected them silently with cryptic errors.

Clerk: "isPublishableKey() failed". Neon: "connection string invalid".

Wrong keys, wrong Clerk instance, wrong env var names.

# What Windows CLI sent (invisible BOM prefix)
pk_live_xxxx

# What it should have been
pk_live_xxxx

Delete ALL env vars from Vercel dashboard. Re-enter by typing manually — never paste via CLI. Deleted and recreated the Vercel project to bust any cached state. 8+ deploys over ~3 hours before root cause found.

Turbopack stale chunk cache (false alarm)

The same chunk hash __0obi7ve._.js appeared across multiple deploys even after --force and project recreation. Looked like a stale build cache.

Same chunk hash surviving project deletion and forced redeploys.

Not a real problem. Chunk hash is deterministic from source content, not a build cache artifact. Same code = same hash. Stopped chasing it.

Clerk allowlist is a Pro-only feature

Restricting sign-ups to an approved email list requires the Clerk allowlist, which is gated behind the Pro plan. Free tier has no native way to limit who can register.

Couldn't restrict who could sign up on the free Clerk plan.

Switched to Account Portal hosted sign-in (Clerk-hosted at accounts.forgekits.build). Gives a clean auth flow without exposing the sign-up form publicly. Free tier workaround.

Seed script DATABASE_URL not loading

dotenvx wasn't overriding the system DATABASE_URL, so the seed script was pointing at the wrong database.

Seed data written to wrong Neon database.

Added --overload flag to the seed script. Forces dotenvx to override any already-set system variables.

Org ID null on first load — white screen

Clerk's auth() returned a valid userId but no orgId on the first session before the user had joined an org. The app assumed orgId was always present, causing a white screen when it was null.

Blank page immediately after sign-in for new users.

Used userId directly as the tenant identifier instead of requiring org membership. Org scoping can be layered in later when multi-tenant is needed.

🏗️

Capabilities-first architecture

Framing Forge as a capability platform (contacts, messaging, workflows, AI) rather than a product made it immediately extensible. Adding a second vertical = a new config file, not new code. The architecture absorbed every new requirement without structural changes.

Drizzle ORM + Neon

Schema-first development with drizzle-kit push caught type errors before runtime. Neon's serverless connection handled cold starts cleanly with no connection pooling setup required. The schema became the single source of truth from the first commit.

👤

Real customer as the north star

Having Gino the electrician as a concrete user (not a persona) shaped every decision — "Jobs" not "Pipeline", bottom nav not sidebar, mobile-first layout. The app felt real from day one because it was built for a real person in a real trade.

🔬

Build-debug-deploy loop in Vercel logs

Once the BOM issue was identified, Vercel function logs became the primary debugging surface. Every subsequent fix had observable evidence before the next deploy. The lesson: build a /api/debug route first, before fighting 500s in prod.

🚀

10-hour single session

Building v1 in one continuous session meant no context-switching cost, no "where were we" overhead, and a working app at the end. Momentum compounds — each piece made the next piece faster. Zero to live in one night.

1

Never use CLI to set long secrets on Windows

PowerShell injects a U+FEFF BOM when piping secrets via vercel env add. Always use the Vercel dashboard. Type, don't paste.

2

Next.js 16 uses proxy.ts, not middleware.ts

Named export required: export { clerkMiddleware as proxy }. v15 and v16 have different middleware conventions — check the version before wiring any auth middleware.

3

Build a /api/debug route before fighting 500s in prod

Expose env var names (never values) at runtime before assuming wrong keys. Evidence first, then fix. Without it, you're guessing.

4

dotenvx requires --overload to override a system DATABASE_URL

Without --overload, dotenvx silently skips variables that already exist in the environment. Always use --overload in seed and test scripts.

5

Clerk allowlist is Pro-only — plan for Account Portal on free tier

Account Portal (hosted at accounts.forgekits.build) gives a clean, controlled auth flow without exposing a public sign-up page. The right default until multi-tenant self-serve is ready.

6

Don't downgrade Next.js to fix a dependency — check the chain first

Next.js 15 breaks shadcn v4 (requires React 19 / Next.js 16). Before downgrading any major dependency, map the full version dependency chain to find the actual lowest compatible floor.

Phase What it covers Status
Phase 0 — Core CRM Pipeline, contacts, auth, DB, demo data ✓ Done
Phase 1 — Messaging Automations, sequences, Automation Hub ✓ Done (in this session)
Phase 2 — Files + AI Photo upload (Vercel Blob), AI captions, dashboard stats → Up next
Domain + Infra forgekits.build live, Clerk production, Vercel deployed ✓ Done
Phase 3 — Knowledge Document Q&A, pgvector RAG, second vertical Not started
Phase 4 — Platform Multi-tenant, Stripe, self-service onboarding Not started

discovery.md

  • Forge v1 full retro added

app-inventory.md

  • Forge section added (full)

reusable-patterns.md

  • 13 patterns documented
  • Kanban, Inngest, Drizzle, Clerk, vertical config, and more

extraction-backlog.md

  • Created with initial candidates

technical-roadmap.md

  • Phase 0 + Phase 1 roadmap written

build-playbook.md

  • Created with deployment and env var rules

Open Items

Required

Set Twilio env vars in Vercel — TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM_NUMBER. Without these, no SMS fires in production. Use Vercel dashboard only — never CLI (BOM corruption risk).

Required

Set Inngest env vars in Vercel — INNGEST_EVENT_KEY, INNGEST_SIGNING_KEY. Without these, Inngest runs in dev mode only and sequences won't persist between deploys.

Required

Test live SMS end-to-end with Gino's number — Verify the missed-call auto-reply actually fires from Twilio in production before showing the client.

Phase 2

Photo upload on deals (Vercel Blob) — Before/after job photos attached to a deal. Claude generates an AI caption on upload. Side-by-side display on deal detail page.

Phase 2

Dashboard stats screen — One screen showing Gino's business health: open estimates, jobs this week, unpaid count, revenue this month.

Phase 1 gap

Inbound SMS reply → note on deal + stop sequence — Twilio webhook receives inbound messages but doesn't yet match the caller to a contact, write a Note on the deal, or stop the running sequence.

"Service revenue now, product revenue later, magic always."
ForgeKit operating principle · Est. 2026-06-16