Skip to content

fix: reconnect the live plumbing (contact form, Writing, analytics) - #4

Merged
dommango merged 4 commits into
mainfrom
fix/reconnect-live-plumbing
Aug 31, 2026
Merged

dommango merged 4 commits into
mainfrom
fix/reconnect-live-plumbing

Conversation

@dommango

Copy link
Copy Markdown
Owner

Summary

Implements docs/plans/01-reconnect-live-plumbing.md. Three things are dead on the live site today because the deploy build never receives the secrets they need:

  • Contact form can't send — NEXT_PUBLIC_EMAILJS_* isn't in the build env, so every submission gets "Email service not configured."
  • Writing section never appears — POSTS was empty; a real post ("The game had already started", 2026-08-04) now ships committed, since the CI fetch is blocked by Substack (403 on GitHub's IP range).
  • No analytics — NEXT_PUBLIC_GOATCOUNTER_SITE wasn't wired into the build either.

A fourth item (chat widget) gets a stopgap here — plan 06 replaces it properly.

Changes

  • .github/workflows/deploy.yml — pass EmailJS/reCAPTCHA/GoatCounter secrets into a single Build Next.js step; scripts/check-build-env.js fails the build loudly if a required var is missing (all in one env block, so guard and build can't drift).
  • lib/services/emailjs.ts — drop the misleading to_name/to_email params (the EmailJS template's own "To email" field controls the real recipient).
  • app/layout.tsx / new components/chat/ChatBotGate.tsx — chat widget only mounts when NEXT_PUBLIC_CHAT_API_URL is set, via a client-only dynamic import so the stale offline-answer copy is excluded from the shipped bundle entirely, not just unrendered.
  • scripts/fetch-substack.js + new scripts/lib/parse-substack-json.js — browser-like User-Agent, plus a JSON-API fallback source for when the RSS route is challenged. Fixed an entity-decoding bug found in review (numeric/named HTML entities like — weren't decoded, so the live post's em dash would have rendered as literal text).
  • lib/content/writing.ts — committed the real fetched post (fetched from a residential connection; GitHub Actions' IP range is blocked).
  • e2e/not-found.spec.ts / __tests__/not-found-redirects.test.ts — updated for the now-true hasPosts() state (plan 07's branded-404 tests assumed no posts existed).

Before merging

This PR's own CI doesn't need secrets (by design — ci.yml builds without them so PRs from forks work). But merging to main will fail the deploy until these exist, because of the new guard step:

  1. Set the required secrets from .env.local (I can't read that file myself):
    for k in NEXT_PUBLIC_EMAILJS_SERVICE_ID NEXT_PUBLIC_EMAILJS_TEMPLATE_ID NEXT_PUBLIC_EMAILJS_PUBLIC_KEY NEXT_PUBLIC_RECAPTCHA_SITE_KEY; do
      v=$(grep "^$k=" .env.local | cut -d= -f2-)
      if [ -n "$v" ]; then printf '%s' "$v" | gh secret set "$k"; echo "set $k"; else echo "SKIP $k (empty)"; fi
    done
    gh secret list
  2. In the EmailJS dashboard, confirm the template's "To email" field is hard-coded to your address (not {{to_email}}) — this PR removes the params that made that field work today. If it's still {{to_email}}, wire it to your address before merging, or the form will 422 for every visitor instead of just mis-routing.
  3. Also worth confirming while you're there: EmailJS allowed-domains restricted to dommango.github.io, since this PR is the first time the service/template/public-key triple ships in a public bundle.

Test plan

  • npx tsc --noEmit, npm run lint (2 pre-existing, unrelated errors in TravelMap.tsx/vitest.setup.ts — confirmed unchanged by this diff)
  • npm test -- --run — 27/27 pass
  • npm run build — confirmed out/index.html has id="writing" and the real post text; confirmed the chat chunk is built but not referenced from index.html
  • npx playwright test — 25/25 pass
  • After merge + secrets set: send a real message through the live contact form, confirm it lands in the inbox
  • After merge: curl -s https://dommango.github.io | grep -c goatcounter → should be ≥ 1
  • Check /dashboard-m7x9k2 in a couple days for non-zero page views

🤖 Generated with Claude Code

The deploy build never received EMAILJS/reCAPTCHA/GoatCounter env vars, so
the live contact form and analytics have been dead since deploy. Passes the
existing GitHub secrets into the build step and fails loudly via a new
check-build-env.js guard if any required one is missing. Also removes the
misleading to_name/to_email EmailJS params (the template controls the real
recipient) and stops rendering the chat widget when no chat API is
configured, so it can't keep giving wrong offline answers until plan 06
replaces it. fetch-substack.js gets a browser-like User-Agent and a JSON
API fallback for when the RSS route is challenged.
The CI fetch gets a 403 from Substack's GitHub Actions IP range, so the
committed POSTS array is the reliable path — it's what ships whenever the
cron/build fetch fails, which is currently always. Fetched from a
residential connection per docs/plans/01-reconnect-live-plumbing.md.
Plan 07's test assumed hasPosts() was false; plan 01 commits the first
real post, so /blog is now a genuine redirect target instead of the
dangling-anchor case the old test guarded against.
- Decode HTML entities (numeric + named) in the RSS subtitle, not just
    — the live feed's em dashes were rendering as literal "—"
  on the one post this plan turns on.
- Treat a bare [] from the JSON fallback as inconclusive (null), same as
  the RSS parser does for a body that isn't a real feed — this source has
  no way to confirm "genuinely zero posts" and shouldn't be allowed to
  empty the committed POSTS on a shape drift.
- Collapse the guard + build into one env block in deploy.yml so a var
  added to only one of them can't silently pass the guard while missing
  from the actual build; add NEXT_PUBLIC_GOATCOUNTER_SITE to the
  required list (analytics was one of the three things this plan exists
  to fix).
- Pair each fetch source with its parser directly (SOURCES tuples)
  instead of dispatching on a url.includes() check that could drift.
- Route ChatBot through a client-only ChatBotGate using next/dynamic, so
  the stale offline-answer copy it ships is excluded from the bundle
  entirely while chat is unconfigured, not just unrendered.
- Add the missing positive-branch test for hasPosts() in
  not-found-redirects.test.ts, and correct CLAUDE.md's ChatBot line.
@dommango
dommango merged commit 91f05ec into main Aug 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant