Skip to content

V1 interest list — homepage subscribe form, Pages Function, D1 - #26

Open
unforced wants to merge 1 commit into
mainfrom
feat/interest-list-v1
Open

V1 interest list — homepage subscribe form, Pages Function, D1#26
unforced wants to merge 1 commit into
mainfrom
feat/interest-list-v1

Conversation

@unforced

@unforced unforced commented May 4, 2026

Copy link
Copy Markdown
Contributor

Closes #25.

Replaces Substack as the entry point with a direct subscribe path on parachute.computer. Email captures from the homepage POST to a Cloudflare Pages Function that writes to D1.

What's in the PR

  • functions/api/subscribe.ts — Pages Function. POST handler, validates email (lowercase + trim, basic regex, 254-char cap), captures source_path from Referer, inserts into D1, returns a 303 redirect to /subscribe/thanks/. Bounces back to /?subscribe_error=1 on validation/insert failure (a tiny inline script on index.njk toggles a discreet error message). No Resend, no de-dup, no admin UI — V2 territory.
  • migrations/0001_interests.sqlinterests table. Matches LVB's post-feat(site): /deploy/render/ page + homepage surface (closes #42) #44 schema; user_id and resend_contact_id are reserved (NULL in V1) so V2/V3 don't need a migration.
  • wrangler.toml[[d1_databases]] binding (DB), pages_build_output_dir = "_site", compatibility_flags = ["nodejs_compat"]. database_id is a PLACEHOLDER that Aaron fills in after wrangler d1 create.
  • index.njk — inline subscribe section sits between the Notes aside and the Principles row. Mirrors the hero rhythm: serif lead heading, muted palette, accent-green button. Plain browser POST → 303 redirect, no JS for the happy path.
  • subscribe/thanks.njk — success page extending _includes/base.njk.
  • CLAUDE.md — new "Interest list / backend" section documenting the new pieces.
  • INFRASTRUCTURE.md — step-by-step CF setup for Aaron (project create, D1 create, migration apply, binding, DNS swap, GH Pages workflow archive).
  • eleventy.config.js — ignores functions/, migrations/, wrangler.toml, INFRASTRUCTURE.md so the static build doesn't try to copy or template them.
  • package.json — adds wrangler + @cloudflare/workers-types as devDeps; adds dev:pages, db:migrate:local, db:migrate:remote scripts.

Worth a closer look

  • Form placement — went between the Notes aside and Principles row (rather than directly under the hero) so the page still leads with the value prop and product story. The issue said "below the value prop" — happy to move it higher into the hero column if you'd rather.
  • No de-dup is intentional — matches LVB/RegenHub V1, preserves return-visit signal. Easy to add a UNIQUE on email later if it becomes noise.
  • Error handling — homepage shows a quiet "that didn't go through" line via inline JS reading ?subscribe_error=1. The form itself uses native required + type="email" validation so most bad inputs never POST.
  • Substack link on /blog/ — left untouched in this PR (blog/index.njk still says "Subscribe on Substack"). Updating that link to point at the new subscribe section is a small but separate change; happy to do as a follow-up so this PR stays one-concern.

Build sanity

npx @11ty/eleventy builds cleanly. _site/index.html contains the form posting to /api/subscribe; _site/subscribe/thanks/index.html exists; backend files are not copied into the static output.

What's left for Aaron

(See INFRASTRUCTURE.md for the full version.)

  1. Create the Cloudflare Pages project, point at this repo, build = npx @11ty/eleventy, output = _site.
  2. wrangler d1 create parachute-db → paste the database_id into wrangler.toml and push.
  3. wrangler d1 migrations apply parachute-db --remote to create the interests table (pause-and-confirm prompt).
  4. Bind D1 → DB to the Pages project in the dashboard (production scope).
  5. Custom domain parachute.computer on Pages → DNS swap from GitHub Pages.
  6. Disable / delete .github/workflows/deploy.yml once CF deploys are healthy.
  7. Smoke-test: submit a real email, confirm row in D1.

🤖 Generated with Claude Code

…nction, D1

Replaces Substack as the entry point per issue #25. Adds an inline email
form to the homepage that POSTs to /api/subscribe (Cloudflare Pages
Function), inserts into the D1 `interests` table, and redirects to
/subscribe/thanks/. No Resend, no admin UI, no de-dup — V2 territory.

- functions/api/subscribe.ts — Pages Function (POST handler, email
  validation, source_path from Referer, 303 redirect)
- migrations/0001_interests.sql — interests table (matches LVB schema
  post-#44; user_id + resend_contact_id reserved for V2/V3)
- wrangler.toml — D1 binding (`DB`) + Pages config; database_id is a
  placeholder that Aaron fills in after `wrangler d1 create`
- index.njk — inline subscribe section between the Notes aside and
  Principles row, mirrors the hero rhythm (serif heading, muted palette)
- subscribe/thanks.njk — success page extending base.njk
- CLAUDE.md — new "Interest list / backend" section
- INFRASTRUCTURE.md — one-time CF Pages + D1 setup steps for Aaron

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
unforced added a commit that referenced this pull request Jun 24, 2026
) (#127)

* feat(subscribe): email capture via standalone Cloudflare Worker + D1

Adds a quiet "stay in the loop" interest-list form to the home's closing
section. The site stays on GitHub Pages (no apex DNS migration); only the
email-capture backend lives on Cloudflare, as a small standalone Worker
backed by D1.

- worker/subscribe.ts — Worker (export default { fetch }) repackaged from
  PR #26's Pages Function. Validates/normalizes the email (EMAIL_RE, 254
  cap), inserts into D1, 303-redirects to ABSOLUTE site URLs (thanks/ on
  success, /?subscribe_error=1 on failure). GET = health check.
- migrations/0001_interests.sql — interests table (verbatim from #26),
  two reserved columns (user_id, resend_contact_id) for later.
- wrangler.toml — Worker config (main + [[d1_databases]] DB binding,
  database_id PLACEHOLDER for Aaron).
- index.njk — native cross-origin <form method="POST"> (no CORS needed:
  browser follows the 303 itself) in the home-close section, endpoint via
  {{ site.subscribeEndpoint }}; reuses .home-cta + tokens; tasteful CSS;
  kept the inline ?subscribe_error=1 error script.
- _data/site.json — subscribeEndpoint constant (default
  https://subscribe.parachute.computer/).
- subscribe/thanks.njk — thank-you page (extends current base.njk).
- eleventy.config.js — ignore worker/, migrations/, wrangler.toml,
  DEPLOY-subscribe.md so the static build skips backend assets.
- package.json — wrangler + @cloudflare/workers-types devDeps;
  dev:worker + db:migrate scripts.
- DEPLOY-subscribe.md — "what's left for Aaron" Cloudflare checklist.

Known gap: no rate limiting yet (accepted simple-start). No secrets
beyond the D1 binding; parameterized insert.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(subscribe): fold reviewer nits — stale comment, column docs, source_path caveat

- migrations: fix stale "Pages Function" comment → "subscribe Worker";
  document the unpopulated `name` column + source_path-often-NULL behavior
- DEPLOY-subscribe.md: note source_path is usually NULL (cross-origin
  Referrer-Policy strips the path); emphasize committing the real
  database_id out of the placeholder

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* subscribe: wire to the deployed Worker + D1 (live)

Provisioned the Cloudflare side (Unforced Development account):
- D1 `parachute-interests` created; migration 0001 applied (remote).
- Worker `parachute-subscribe` deployed → https://parachute-subscribe.unforced.workers.dev
- wrangler.toml: real database_id + pinned account_id (non-interactive deploys).
- _data/site.json: subscribeEndpoint → the deployed workers.dev URL.

Smoke-tested end-to-end: POST → 303 /subscribe/thanks/ → row in D1 (then
deleted the test row; count=0). A custom domain (subscribe.parachute.computer)
can replace the workers.dev URL later via one site.json edit + a CF custom domain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

Interest list — direct subscribe, replacing Substack as the entry point

1 participant