A running log of the planning work for this project. The active plan is listed first; older plans are summarized at the bottom for context.
- The Active section is the source of truth for what's being worked on right now. There should usually be exactly one active plan.
- The Next up section is for plans we've decided on but haven't started.
- The Recent (shipped) section is for plans that landed within the last few months — useful for context when someone asks "wait, why is X built that way?"
- Older plans get pruned to a one-paragraph history at the bottom.
- Anything purely speculative belongs in
docs/IDEAS.md, not here.
Goal. Ship a working /admin/ dashboard on top of the existing static
site so non-developers can edit pages, posts, header, and footer through a
browser instead of a code editor. Tighten the surrounding scripts and docs
in the same effort.
Scope.
- Auth layer. Google OAuth + JWT cookie +
ADMIN_EMAILSallow-list. Single sign-in page; no separate accounts to manage. - Edit flow via PRs. Every save creates a fresh branch, commits the
override JSON or markdown, opens a PR back to
main. No direct writes. Merging the PR triggers the normal CF Pages rebuild. - Six admin capabilities — create page, visually edit page, hide page, draft page, edit header, edit footer. Plus "save as template" so the admin can build up a reusable library.
- Draft preview. A catch-all Cloudflare Pages Function that, on every request, checks for a draft override and serves the draft to signed-in admins at the real URL while public visitors get a 404.
- JSON-driven content. Header, footer, and per-page overrides live in
assets/data/andassets/page-overrides/, picked up byheader.js,footer.js, and a newpage-overrides.js. - Docs sweep (this folder). Eight files covering architecture, admin use, SEO, indexing, lifecycles, deployment, ideas backlog, and this plans file.
Build order.
- Empty Function scaffolding (
/functions/api/auth/*,/functions/api/pages/*, etc.) returning stubs. - Auth:
/api/auth/login,/api/auth/callback,/api/auth/me,/api/auth/logout. JWT helper,requireAdmin(). - Admin shell at
/admin/: sign-in page, dashboard tiles. - Pages module: list, create-from-template, edit-region, hide, draft.
- Posts module: list blog posts, edit frontmatter, edit markdown.
- Header / Footer modules: simple form UIs over the two JSON files.
- Template module: list, save-as, delete.
- Catch-all draft-preview Function.
- Doc set (this).
- Polish: stale-deploy banner, pending-PRs strip, optimistic UI.
Non-goals.
- Multi-author roles. Flat allow-list for v1.
- Image upload. Manual commit for now.
- Scheduled publish. Manual merge for now.
- Search / analytics / KV view counts. All in
IDEAS.md.
Risks.
- PR conflicts if two admins edit the same page at once. Mitigation: pre-flight check before save; show pending PRs strip.
- OAuth misconfig. Most-likely-bug is
redirect_uri_mismatch. Doc the exact URI inDEPLOYMENT.md. - Function size limits. CF Pages Functions have request/response limits; unlikely to hit them with JSON-only payloads but worth knowing.
Definition of done.
- An admin signs in, edits the home-page hero copy, sees a PR open, merges it, and the change is live in under three minutes.
- An admin creates a new page from a template, fills three fields, opens a PR, merges it, and the page appears at its slug.
- An admin toggles a page to Draft, saves, merges — the page is gone from public view and visible to them at the same URL.
Most editor friction in v1 will be "how do I add an image?" Spec is in
IDEAS.md. Likely the second thing we build after the dashboard ships.
The clinic's marketing person wants to queue posts for Tuesday mornings.
Small Function + a timestamp field in the override JSON. Spec in IDEAS.md.
Once the blog hits the second hundred posts, an in-site search becomes
worthwhile. Plain client-side fuzzy match over index.json. Spec in
IDEAS.md.
Four bundled improvements that landed together:
- Self-hosted fonts. Plus Jakarta Sans + Fraunces moved from Google
Fonts to
assets/fonts/(variable woff2, Latin subset). New scripts:setup-fonts.sh,selfhost-fonts.mjs. Removed three preconnects, added a single preload for the critical 600-weight Plus Jakarta Sans. - Skip-to-content link.
add-skip-link.mjsinjects a visually-hidden skip link as the first child of<body>on every page; reveals on focus. - Deferred lead-bot. Moved the chat widget into its own
leadbot.jsand madeapp.jslazy-load it on idle or first user gesture. Shaved hundreds of KB off the first paint. - Call CTA on form pages.
pre-intake-form.html,contact.html, andautism-testing.htmlgot a sticky "Prefer to talk?" sidebar next to the Jotform.
Replaced manual share images with gen-og-images.mjs: 1200x630 SVG per
post + per main page, regenerated at build time when index.json changes.
inject-seo.py auto-resolves the per-page SVG; blog/post.html does the
same at runtime.
Moved the deploy from a hand-rolled VPS to Cloudflare Pages. Settled the
Root directory: /website, Build command: bash build.sh, Build output: .
incantation. Added _redirects (pretty blog URLs) and _headers (security
- cache + correct MIME for sitemap/robots/key file). Wired IndexNow into the deploy pipeline.
161 WordPress posts scraped and rewritten as markdown with YAML
frontmatter under assets/blog/. Built blog/post.html as a single
runtime renderer using marked + DOMPurify. Added build-blog-index.py
to derive index.json from frontmatter on every build. Removed the
WordPress scraper from the repo once the markdown was in place.
Hand-rebuilt the marketing site against the Figma source of truth: new
palette (cream, ink, teal, coral, sun, sage), new typography (Fraunces
display + Plus Jakarta Sans body), bullseye / target-mark visual system
replacing the old puzzle-piece imagery. New nav structure with a
shared header.js / footer.js injection model so a nav edit is a
one-place change.
The original site lived on WordPress + Elementor with Fluent Forms,
Jotform, Hotjar, FB Pixel, and two Google Tag Manager containers
(GTM-N2RP5GST, GTM-W42536PM). In late 2025 the decision was made to
rebuild as a static site for performance, maintainability, and lower
ongoing cost. Cloudflare Pages was chosen over Vercel for cost and for
Pages Functions (which let the admin layer ship without a separate
backend). The Jotform IDs were preserved end-to-end so existing form
submissions continued to land in the same back-office workflow.
A few patterns to follow when adding a new plan to this file, so the running log stays useful as it grows.
- Goal. One or two sentences. What does "done" look like, in business terms?
- Scope. A bulleted list of in-scope deliverables. Be specific enough that a future reader can tell what was actually built.
- Build order. A numbered list of steps that minimize work-in-flight.
This is the working order, not necessarily the merge order — some
steps can ship to
mainindependently as long as feature flags hide them from users. - Non-goals. What we explicitly decided not to do this round. Equally important as the goals. Forgetting this section is how scope creeps.
- Risks. Two to four. Mitigations for each, if known.
- Definition of done. Specific, testable. "An admin can do X in under Y seconds." If you can't write this, the plan isn't ready.
When every Definition-of-done bullet is checked off in production. Not when the PR is merged — when the user-visible behavior actually matches.
If it's older than ~6 months and the codebase has moved on enough that reading it would mislead a new contributor, collapse it into the Archived paragraph at the bottom. The goal is for this file to be useful, not exhaustive.
Only when:
- The active plan has a clear off-ramp date or milestone.
- A specific decision (not just an idea) has been made about what's next.
If it's "we should do X eventually," that's IDEAS.md, not here.
- Implementation specifics live in
ARCHITECTURE.mdorHOW_IT_WORKS.md, not here. Plans describe intent and ordering; the other docs describe the resulting system. - Backlog items live in
IDEAS.md. Promote here only when the decision to do them is final and the work is sized. - Deployment / runbook details live in
DEPLOYMENT.md. Don't repeat "set this env var" inside a plan; reference the deployment section.