Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions docs/plans/01-reconnect-live-plumbing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# Plan 01 — Reconnect the live plumbing

**Priority:** P0 (do first) · **Effort:** ~2–3 hours · **Value:** restores the two conversion paths that are dead today
**Depends on:** nothing · **Unblocks:** plans 03, 05

## Why

Three things are broken on https://dommango.github.io right now. All three were verified on 2026-08-31, not inferred:

1. **The contact form cannot send.** `lib/services/emailjs.ts` reads `NEXT_PUBLIC_EMAILJS_*` at build time. `.github/workflows/deploy.yml` passes **no** environment variables to `npm run build`, so the deployed bundle contains `"Email service not configured"` and no EmailJS service id. Every visitor who submits the form gets that error. (Checked by downloading the live chunks and grepping.)
2. **The Writing section never appears.** `scripts/fetch-substack.js` runs during the deploy build, and Substack answers GitHub Actions with HTTP 403. The job log for run 33308370829 says: `[substack] feed returned 403; keeping committed posts`. The committed `POSTS` array is empty, so the section (and its nav link) stay hidden even though a real post ("The game had already started", 2026-08-04) exists.
3. **No analytics are collected.** `NEXT_PUBLIC_GOATCOUNTER_SITE` is not in the build env, so `app/layout.tsx` never renders the GoatCounter script. `public/data/analytics.json` has been zeros since March. (The `GOATCOUNTER_SITE` secret already exists in the repo; it's just not wired to the build.)

A fourth P0 — the chat widget's wrong answers — is fixed properly in plan 06. This plan includes a **one-line stopgap** so the widget stops misinforming people today.

## Done when

- [ ] Sending the live contact form delivers an email to Dom's inbox (not to the sender).
- [ ] https://dommango.github.io shows a **Writing** section with the Aug 4 post and a **Writing** nav link.
- [ ] `curl -s https://dommango.github.io | grep -c goatcounter` prints `1` or more.
- [ ] The chat bubble is not rendered on the live site until plan 06 ships.
- [ ] The deploy workflow **fails loudly** if any required build secret is missing, so this can't silently regress.

## Files

- `.github/workflows/deploy.yml` — pass secrets into the build; add a guard step
- `lib/content/writing.ts` — regenerated by the fetch script (commit the result)
- `scripts/fetch-substack.js` — add a browser-like User-Agent and one fallback endpoint (best-effort)
- `app/layout.tsx` — don't render `<ChatBot />` when no chat API is configured
- `scripts/check-build-env.js` — new, tiny

## Steps

### 1. Put the EmailJS + reCAPTCHA values into GitHub secrets

The values live in `.env.local` (gitignored). Never paste them into a chat or a commit. Run this from the repo root; it reads the file and pipes each value straight into `gh` without echoing:

```bash
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
```

`GOATCOUNTER_SITE` already exists as a secret; reuse it below.

### 2. Wire the secrets into the build (`.github/workflows/deploy.yml`)

Replace the `Build Next.js` step with a guard + build:

```yaml
- name: Check required build env
env:
NEXT_PUBLIC_EMAILJS_SERVICE_ID: ${{ secrets.NEXT_PUBLIC_EMAILJS_SERVICE_ID }}
NEXT_PUBLIC_EMAILJS_TEMPLATE_ID: ${{ secrets.NEXT_PUBLIC_EMAILJS_TEMPLATE_ID }}
NEXT_PUBLIC_EMAILJS_PUBLIC_KEY: ${{ secrets.NEXT_PUBLIC_EMAILJS_PUBLIC_KEY }}
run: node scripts/check-build-env.js

- name: Build Next.js
env:
NEXT_PUBLIC_EMAILJS_SERVICE_ID: ${{ secrets.NEXT_PUBLIC_EMAILJS_SERVICE_ID }}
NEXT_PUBLIC_EMAILJS_TEMPLATE_ID: ${{ secrets.NEXT_PUBLIC_EMAILJS_TEMPLATE_ID }}
NEXT_PUBLIC_EMAILJS_PUBLIC_KEY: ${{ secrets.NEXT_PUBLIC_EMAILJS_PUBLIC_KEY }}
NEXT_PUBLIC_RECAPTCHA_SITE_KEY: ${{ secrets.NEXT_PUBLIC_RECAPTCHA_SITE_KEY }}
NEXT_PUBLIC_GOATCOUNTER_SITE: ${{ secrets.GOATCOUNTER_SITE }}
run: npm run build
```

Create `scripts/check-build-env.js` (CommonJS, like `fetch-substack.js`):

```js
// Fails the deploy build when a NEXT_PUBLIC_* value the site needs is missing.
// Local builds don't run this (see deploy.yml), so `npm run build` still works
// without a .env.local.
const REQUIRED = [
'NEXT_PUBLIC_EMAILJS_SERVICE_ID',
'NEXT_PUBLIC_EMAILJS_TEMPLATE_ID',
'NEXT_PUBLIC_EMAILJS_PUBLIC_KEY',
]

const missing = REQUIRED.filter((k) => !process.env[k])
if (missing.length > 0) {
console.error(`[env] missing required build env: ${missing.join(', ')}`)
console.error('[env] set them with `gh secret set <NAME>` — see docs/plans/01-reconnect-live-plumbing.md')
process.exit(1)
}
console.log('[env] all required build env present')
```

`ci.yml` does **not** run this step and should not — CI builds without secrets on purpose.

### 3. Verify the EmailJS template sends to Dom, not the visitor

`sendContactEmail` passes `to_name: fromName, to_email: fromEmail`. Whether that matters depends on the EmailJS template: if the template's "To email" field is `{{to_email}}`, the message goes to the **sender**. Open the template in the EmailJS dashboard and make sure "To email" is Dom's address (hard-coded), and `reply_to` is `{{reply_to}}`. Then remove the misleading params from `lib/services/emailjs.ts`:

```ts
{
from_name: fromName,
from_email: fromEmail,
reply_to: fromEmail,
message
}
```

### 4. Make the Writing section appear — commit the posts

Substack blocks GitHub's IP range, but not a residential connection. From the repo root:

```bash
node scripts/fetch-substack.js # expect: [substack] wrote 1 post(s) to lib/content/writing.ts
git diff lib/content/writing.ts # POSTS now has one entry
npm test -- --run # 13 tests pass
npx playwright test e2e/landing.spec.ts -g "writing section" # asserts section + nav link both present
```

Commit `lib/content/writing.ts`. This is the reliable path: the committed posts are what ship whenever the CI fetch fails, which is currently always.

**Recurring:** after each new post is published, run the two commands above and commit. (If `/content-publish` is used, add these two lines to the end of that skill so it happens automatically.)

### 5. Make the CI fetch a little more likely to succeed (best-effort)

In `scripts/fetch-substack.js`, change the request headers and add one fallback. Keep every existing guard.

```js
const FEED_URLS = [
'https://dommangonon.substack.com/feed',
// Substack's JSON API sometimes answers when the RSS route is challenged.
'https://dommangonon.substack.com/api/v1/posts?limit=6',
]
const HEADERS = {
'user-agent': 'Mozilla/5.0 (compatible; dommango.github.io build; +https://dommango.github.io)',
accept: 'application/rss+xml, application/xml, application/json;q=0.9, */*;q=0.8',
}
```

Loop over `FEED_URLS`; for the JSON endpoint map each item to `{ title, url: canonical_url, date: post_date, subtitle }` and apply the same placeholder filter (`title` of "Coming soon"). If both fail, keep the existing `keeping committed posts` behaviour. Add one unit test in `__tests__/parse-substack-feed.test.ts` only if you extract the JSON mapping into `scripts/lib/parse-substack-json.js`; otherwise no new tests — this path is best-effort by design.

### 6. Stopgap for the chat widget (`app/layout.tsx`)

```tsx
const CHAT_API_URL = process.env.NEXT_PUBLIC_CHAT_API_URL
...
{children}
{CHAT_API_URL && <ChatBot />}
```

With no API configured the widget — and its "Skills page" answers — disappears. Plan 06 replaces it.

### 7. Deploy and verify

```bash
git checkout -b fix/reconnect-live-plumbing
git add -A && git commit -m "fix: pass build secrets to the Pages deploy, commit Substack posts, hide unconfigured chat"
git push -u origin fix/reconnect-live-plumbing
gh pr create --fill
# after merge:
gh run watch # deploy.yml should pass the new "Check required build env" step
curl -s https://dommango.github.io | grep -c 'id="writing"' # 1
curl -s https://dommango.github.io | grep -c goatcounter # >= 1
```

Then send a real message through the live form and confirm it arrives in Dom's inbox. Check the dashboard at `/dashboard-m7x9k2` two days later: page views should be non-zero.

## Gotchas

- `NEXT_PUBLIC_*` values are inlined **at build time**. Setting a secret without re-running the deploy changes nothing; trigger `gh workflow run deploy.yml` after setting them.
- The reCAPTCHA site key is public by design (it's shipped in the page) but keep it in secrets anyway so the workflow reads one source.
- Don't add these env vars to `ci.yml`. CI must keep building without secrets so pull requests from forks work.
- `writing.ts` between the `GENERATED` markers is machine-written; never hand-edit inside the markers.
Loading
Loading