Full Day — June 17

ForgeKit · 2026-06-17 · Forge · PilotLight · forgekit-website

Forge Phase 2 shipped. PilotLight got a safety gate and 58 tests. The ForgeKit website went from nothing to fully deployed — 7 pages, community hub, real screenshots, live flame. One day.

8 – 11 AM
Forge v3
Twilio SMS · Photos · Dashboard
11 AM – 3 PM
Forge v3 cont.
Mobile fixes · Inngest · Live SMS
3:30 – 5 PM
Website launch
forgekits.build · Screenshots
5 – 8 PM
Community hub
3 pages · Forms · Resend email
8 – 9:30 PM
PilotLight + Visual
Safety gate · Tests · Brand images
Chapter 1

Forge v3 — Phase 2 Complete

8:00 AM – 3:00 PM

Twilio SMS — Live

  • Switched from auth token → API key auth
  • Missed call → TwiML Reject → SMS in same request
  • Inbound replies logged to message_log
  • Org looked up by twilio_number field
  • Confirmed "SMS sent" in Vercel logs

Photo Upload + AI Captions

  • job_photos table → Neon (migrated)
  • Upload → Vercel Blob (private store)
  • Claude vision caption via base64 encode
  • Server-side proxy: /api/deals/photos/serve
  • Delete on hover (trash icon)

Deal Detail Page

  • /deals/[id] — contact header, stage badge
  • Before/after photo grid with captions
  • Notes section
  • Pipeline cards now link to deal page

Dashboard Home

  • 4 stat cards: estimates, scheduled, payment, revenue
  • Open estimates list with values
  • Awaiting payment list (yellow highlight)
  • Recent messages feed (last 5)

Inngest Missed-Call Flow

  • 90-second delay before SMS fires
  • Cancel window if call is answered
  • leadStatus field on deals
  • "Mark Contacted / Not a Lead" quick actions

lib/ai.ts

  • Reusable photo caption function
  • Base64 encode pattern extracted
  • Model from env var (CLAUDE_MODEL)

vercel env pull doesn't pull app vars

drizzle-kit push failed with url: undefined. Root cause: vercel env pull only downloads Vercel system vars (VERCEL_ENV, TURBO_CACHE) — not DATABASE_URL or any app secrets.

Manually add DATABASE_URL to .env.local. Never rely on vercel env pull for secrets.

Vercel Blob private store rejects public access flag

put(..., { access: 'public' }) threw "Cannot use public access on a private store." Private stores require private access on every upload.

Changed to access: 'private' + built server-side proxy route. Bonus: photos now org-scoped.

Claude vision can't fetch private blob URLs

Passing a private Vercel Blob URL to Claude's source: { type: 'url' } returns 400 — Claude's API can't download authenticated URLs.

Fetch bytes server-side with the blob token, encode as base64, send as source: { type: 'base64', media_type, data }.

BOM on CLAUDE_MODEL env var

Caption generation failed with model: claude-sonnet-4-6 — a byte-order mark prepended the model string. Caused by pasting into Vercel's env var field instead of typing.

Delete the var and manually retype the value. Never paste model IDs into Vercel.

Twilio webhook: CallStatus was "ringing" not empty

Code checked !callStatus to detect the initial call webhook. Twilio sends CallStatus: ringing on the initial POST — not an empty string.

Check for callStatus === 'ringing' || !callStatus explicitly.

Twilio webhook returning JSON instead of TwiML

Returning { ok: true } caused Twilio error 12300 "Invalid Content-Type." Voice webhook must return XML.

Return new NextResponse(twiml, { headers: { 'content-type': 'text/xml' } }).

TWILIO_ACCOUNT_SID missing from Vercel

"accountSid must start with AC" — env var was in .env.local but never added to Vercel dashboard (same vercel env pull problem).

Manually add all Twilio vars to Vercel dashboard. Never assume pull gets them.

A2P 10DLC — SMS blocked at carrier (Error 30799)

SMS code confirmed working in Vercel logs. Carrier silently drops the message. New EINs take 2–4 weeks to propagate through carrier verification. Sole proprietor brand submitted — pending.

No code fix. Block: wait for A2P approval. Plan for this lead time before every demo.

Credentials shared in chat

Twilio Account SID, Auth Token, and API SID appeared in chat during debugging. Clerk and Anthropic keys visible in a .env.local screenshot.

Auth Token rotated immediately. All shared creds should be rotated before Gino goes live.

📋

Vercel function logs as primary debugging tool

When Twilio's own debugger only showed an error code, Vercel logs showed the actual values (toNumber, callSid, callStatus, "SMS sent successfully"). Faster and more specific than any other path.

🖼️

Base64 image → Claude vision pattern

Fetch private bytes server-side, encode to base64, send as source: { type: 'base64' }. Works for any private file store. Extracted to lib/ai.ts — reusable across all ForgeKit apps.

🔒

Server-side photo proxy

Storing blob pathnames in DB + serving through an authenticated API route means photos are fully org-scoped. No signed URLs, no expiring links, no token exposure to the browser.

🔑

Twilio API keys over auth token

TWILIO_API_KEY + TWILIO_API_SECRET is the right production pattern. Revocable per-project without affecting the whole Twilio account.

Chapter 2

ForgeKit Website — Built from Zero

3:36 PM – 8:00 PM

Homepage

  • "Imagine it. We'll build it." hero
  • Craftsman toolshed stock photo
  • 3 product cards with real copy
  • Platform capability strip
  • CTA section with worker-tool image

Forge Page

  • Product overview with origin story copy
  • Real Playwright-captured screenshots
  • Desktop + mobile gallery
  • Karen Stills demo data shown

PilotLight Page

  • "YOU DO THE THING. WE STAY."
  • Live flame SVG from the app in hero
  • Flicker animation (same as app)
  • Flame right / text left hero layout
  • 3 use-case cards

About Page

  • Founder story + brand philosophy
  • Pull quote: "If I can think it clearly enough…"
  • The Forge Loop (5 steps)
  • Values + full product suite
  • Town square hero image

Community Hub

  • /community — experiment board, 3 cards
  • /community/history — plat map bg, Flyback links
  • /community/play — RockGod bg, guitar sign-up
  • /community/build — workshop bg, session formats

Sign-up Forms + Email

  • /api/community-signup route
  • All 3 forms post to Resend
  • Email delivers to jungebez@gmail.com
  • Thank-you state on each form

Brand Images

  • 5 Unsplash stock photos (free license)
  • Plat map reused from Flyback
  • RockGod reused from FretHero
  • Each page has a unique background

PWA + Homescreen Icon

  • manifest.json with ForgeKit name
  • #FF6B00 theme color
  • forgekit-app-icon-dark-1024.png wired
  • apple-touch-icon set in layout

Email Routing

  • Cloudflare: zeb@forgekits.build
  • Forwards to jungebez@gmail.com
  • Community forms use this address

PilotLight flame pushed text down

Added flame above the headline in a single-column layout — it shoved everything down and made the hero too tall.

Moved to a flex row: text left (flex-1), flame right (hidden md:flex shrink-0). Hidden on mobile.

Background images too dark — multiple rounds per image

Every background image needed 2–3 opacity iterations. Pattern: low opacity + dark gradient layer compound into near-invisible. Started at 7–20% and had to rebuild up each time.

Rule going forward: start at 50%+ opacity and pull back. Don't build up from dark.

Editing hero when the problem was the CTA section

Screenshot showed "too dark" — edited the hero section. The actual dark section was the CTA further down the page.

Identify the section name in code before touching opacity. Read the screenshot context.

Vercel hit 100 deploys/day limit

Iterating one image opacity at a time burned through the free tier daily limit. About page fix and PWA manifest are committed but couldn't be deployed.

Batch image iterations into one deploy. The limit resets at midnight UTC.

About page orphaned quote mark

Pull quote had a standalone <p>"</p> element above the quote text, rendering as a lone hanging quotation mark on its own line.

Removed the element. Embedded opening quote directly into the quote text string.

♻️

Reusing assets from existing apps

Plat map from Flyback and RockGod from FretHero gave the community pages instant character. Zero sourcing cost. Tells the story of what's already built — the website sells the apps by showing the work.

🔥

Inlining the live flame SVG on the website

The PilotLight flame from the app is a pure SVG with CSS keyframe animation. Inlined directly in a Next.js server component with a <style> tag — no "use client", no extra component, identical to the app. One source of truth, two places.

🖼️

Unsplash sourcing workflow

WebSearch → WebFetch the photo page for the images.unsplash.com URL → PowerShell Invoke-WebRequest to download. Fully automated, free license, no manual steps. This is the pattern for every future image need.

📸

Playwright-captured real screenshots

Drove the live Forge app with Playwright to capture actual product screens. The Forge page shows real software, not mockups. Karen Stills job data makes it look like a real contractor account.

Chapter 3

PilotLight — Safety Gate + Test Suite

8:00 PM – 9:30 PM

Two-Tier Safety Gate

  • Tier 1: hardStopCheck() — 18 regex patterns
  • Gas/fire, electrical, medical, structural
  • Zero API cost — instant
  • Tier 2: Claude soft-stop (lightweight call)
  • Returns { emergency: true/false }
  • Fail-open: error → false, never blocks

Thumbs Feedback

  • Up/down UI on done screen
  • Posts { sessionId, thumbsUp } to /api/feedback
  • Resets on new session
  • One signal per completed workflow

Vitest — 58 Tests

  • safety-gate.test.js — 31 tests
  • guide-response.test.js — 27 tests
  • All 18 hard-stop patterns covered
  • Schema validation, detectCorrection
  • compressHistory, behavior contracts

First Test Suite in ForgeKit

  • Vitest configured (ESM, node env)
  • vitest.config.js added to repo
  • npm run test wired in package.json
  • Template for forgekit-testing package

Smoke regex missed "smoke is filling"

/smoke\s*(coming|pouring|filling)/i didn't match "smoke is filling" — "is" sits between "smoke" and "filling". The test suite caught it immediately before it could ever reach production.

// Before — didn't match "smoke is filling"
/smoke\s*(coming|pouring|filling)/i

// After — covers both forms
/smoke\s*(coming|pouring|filling|is\s+filling)/i

Extended the pattern. This is exactly what the test suite exists for.

vi.mock hoisting inside describe blocks

Tried to mock softStopCheck inside describe blocks. Vitest hoists vi.mock at compile time — mocks inside describe blocks don't work as expected. Same issue hit in Forge v2 tests.

Replaced mocked softStopCheck tests with behavior contract tests: no API key → false, empty input → false, error → false. Tests are more meaningful and don't fight Vitest mechanics.

🛡️

Two-tier fail-open safety gate

Hard regex stops the obvious cases (18 patterns, zero API cost). Claude handles the edges. Any error in either tier returns false — a network hiccup never blocks a real user. The safety gate is trustworthy precisely because it fails safe.

🧪

Test suite catching the smoke regex miss

The "smoke is filling" miss was found by Vitest immediately — before deployment. This is the whole point. 18 safety patterns need 18 test cases, not assumptions. Tests aren't bureaucracy here; they're the thing that makes the safety gate credible.

📋

Behavior contract tests over mocked modules

Instead of fighting Vitest's hoisting to mock softStopCheck, wrote tests for its contracts — what it should return given specific inputs and conditions. More robust, more meaningful, no mock mechanics to maintain.

12
Commits
8
Pages Built
58
Tests Written
18
Safety Patterns
5
Stock Images
3
Forms + Email
3
Apps Touched
~13h
8am – 9:30pm
App / Phase What it covers Status
Forge — Phase 0 Pipeline, contacts, auth, DB, demo data ✓ Done
Forge — Phase 1 Twilio, Inngest sequences, Automation Hub ✓ Done
Forge — Phase 2 Photos, AI captions, dashboard stats ✓ Done
Forge — A2P 10DLC Carrier SMS delivery for Gino's number ⚠ Pending (EIN verification, 2–4 wks)
Forge — Phase 3 Document Q&A, pgvector RAG, second vertical Not started
PilotLight — Core Guide flow, camera, schema, rate limiting ✓ Done
PilotLight — Trust Safety gate (2-tier), thumbs feedback, Vitest ✓ Done today
ForgeKit Website 7 pages, community hub, email, PWA icon ✓ Live at forgekits.build
Website — About page opacity Committed, pending Vercel deploy limit reset ⚠ Pending deploy
1

vercel env pull doesn't pull app vars

Only pulls Vercel system vars. Always manually populate DATABASE_URL and all app secrets in .env.local. Never rely on pull for secrets.

2

Always type model IDs, never paste

Pasting from clipboard introduces invisible BOM characters. Retype claude-sonnet-4-6 manually in Vercel env var fields every time.

3

Private Vercel Blob → Claude vision requires base64

Claude's API can't fetch authenticated URLs. Fetch bytes server-side, encode to base64, send as source: { type: 'base64' }.

4

A2P 10DLC required for all US business SMS — plan 4 weeks

New EINs take 2–4 weeks to propagate to carrier databases. Submit registration immediately when any client needs SMS. Never schedule a demo around unregistered SMS.

5

Rotate any credential that appeared in chat

Treat it as compromised. Rotate before going live with any client, regardless of how the chat was handled.

6

Start background image opacity at 50%, pull back

Building up from low opacity requires too many iterations. Dark overlay + low base opacity compound — the image disappears. Start high, reduce to taste in one or two passes.

7

Identify the exact section before editing opacity

When a screenshot shows "too dark," find the section name in the code before touching anything. Don't assume it's the hero.

8

Reuse app assets on the website first

Before sourcing stock images, check if existing apps already have on-brand visuals. Flyback has maps; FretHero has RockGod. Zero cost, tells a richer story.

9

Batch image iteration deploys

Free Vercel tier is 100 deploys/day. Don't deploy after every opacity tweak — batch into one deploy when the direction is clear.

10

vi.mock must be top-level — write behavior contracts instead

vi.mock doesn't work inside describe blocks. For AI utility functions, write behavior contract tests (no key → false, error → false) instead of fighting mock hoisting.

"If I can think it clearly enough, I can build it."

That used to be a wish. Now it's just how Tuesday goes. — Zeb Jungeberg, ForgeKit · Medina OH

Open Items

Blocker

A2P 10DLC approval — SMS code is live and confirmed working. Carrier blocks delivery until brand registration clears. New EIN submitted — check Twilio console for status. ETA: 2–4 weeks from submission.

Deploy

About page + PWA manifest — Committed, couldn't deploy (Vercel 100/day limit). Both changes are staged. Run vercel --prod from forgekit-website/ to push.

Security

Rotate credentials shared in chat — Twilio Auth Token, Clerk keys, Anthropic key appeared in session chat/screenshots. Rotate all before Gino goes live.

Forge

Inbound reply → stop sequence + note on deal — Last Phase 1 gap. Twilio inbound SMS webhook receives replies but doesn't yet match the sender to a contact, stop their active Inngest sequence, and write a Note on their deal.

PilotLight

Session feedback review — Thumbs feedback is now collecting. After a week of usage, review the signal: are thumbs-down sessions clustered around specific workflows or prompt types? Use that to tune the guide prompt.

Website

Forge page → real product walkthrough — The /forge page has screenshots. Next step: add a short narrative walkthrough (like "here's how Gino's morning changes") to make it sellable, not just visible.