V1 interest list — homepage subscribe form, Pages Function, D1 - #26
Open
unforced wants to merge 1 commit into
Open
V1 interest list — homepage subscribe form, Pages Function, D1#26unforced wants to merge 1 commit into
unforced wants to merge 1 commit into
Conversation
…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>
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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), capturessource_pathfromReferer, inserts into D1, returns a 303 redirect to/subscribe/thanks/. Bounces back to/?subscribe_error=1on validation/insert failure (a tiny inline script onindex.njktoggles a discreet error message). No Resend, no de-dup, no admin UI — V2 territory.migrations/0001_interests.sql—intereststable. Matches LVB's post-feat(site): /deploy/render/ page + homepage surface (closes #42) #44 schema;user_idandresend_contact_idare 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_idis aPLACEHOLDERthat Aaron fills in afterwrangler 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— ignoresfunctions/,migrations/,wrangler.toml,INFRASTRUCTURE.mdso the static build doesn't try to copy or template them.package.json— addswrangler+@cloudflare/workers-typesas devDeps; addsdev:pages,db:migrate:local,db:migrate:remotescripts.Worth a closer look
emaillater if it becomes noise.?subscribe_error=1. The form itself uses nativerequired+type="email"validation so most bad inputs never POST./blog/— left untouched in this PR (blog/index.njkstill 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/eleventybuilds cleanly._site/index.htmlcontains the form posting to/api/subscribe;_site/subscribe/thanks/index.htmlexists; backend files are not copied into the static output.What's left for Aaron
(See
INFRASTRUCTURE.mdfor the full version.)npx @11ty/eleventy, output =_site.wrangler d1 create parachute-db→ paste thedatabase_idintowrangler.tomland push.wrangler d1 migrations apply parachute-db --remoteto create theintereststable (pause-and-confirm prompt).DBto the Pages project in the dashboard (production scope).parachute.computeron Pages → DNS swap from GitHub Pages..github/workflows/deploy.ymlonce CF deploys are healthy.🤖 Generated with Claude Code