diff --git a/app/priv/blog/release-notes/2026/04-01-scheduled-publishing.md b/app/priv/blog/release-notes/2026/04-01-scheduled-publishing.md new file mode 100644 index 0000000..0c34971 --- /dev/null +++ b/app/priv/blog/release-notes/2026/04-01-scheduled-publishing.md @@ -0,0 +1,72 @@ +%{ + title: "Scheduled Publishing & Author Dashboard", + author: "Willem van den Ende", + tags: ~w(release features), + description: "Future-dated posts stay hidden until their publish date, authors get a dashboard to track drafts and scheduled content, and registration is locked down to invited emails only." +} +--- + +Posts in Firehose are markdown files with a date in the filename. Until now, every published post was immediately visible. That changes today: posts with a future date are now hidden from public views until their date arrives. + +This was built in a single session using an agentic dev team -- 12 issues tracked in beads, executed in three parallel phases, producing 232 tests across the blogex library and Phoenix app. + +## What changed + +### Future-dated posts are hidden from public views + +The blog index, tag pages, RSS feeds, and Atom feeds now filter out posts where the date is after today. If you schedule a post for next Tuesday, readers won't see it until then. + +![Blog index showing only past-dated posts](/images/scheduled-publishing/scheduled-blog-index.png) + +But here's the key design choice: **direct URL access still works**. If you know the slug, you can view the post. This lets authors share preview links with reviewers before the publish date. + +![Future post accessible by direct URL](/images/scheduled-publishing/scheduled-direct-access.png) + +### Status banners for authors + +When you're logged in, draft and scheduled posts show a status banner so you always know what state a post is in. Unauthenticated visitors see nothing -- no clue the post isn't "live" yet. + +**Scheduled posts** show a blue banner with the target date: + +![Scheduled banner showing target publish date](/images/scheduled-publishing/scheduled-banner-future.png) + +**Draft posts** (unpublished) show an amber banner: + +![Draft banner on unpublished post](/images/scheduled-publishing/scheduled-banner-draft.png) + +### Editor dashboard + +A new LiveView at `/editor/dashboard` gives authors a unified view of all non-live content across every blog. Two tabs: drafts and scheduled posts. Scheduled posts show a "days until live" countdown. + +![Dashboard drafts tab](/images/scheduled-publishing/scheduled-dashboard.png) + +![Dashboard scheduled tab with countdown](/images/scheduled-publishing/scheduled-dashboard-scheduled.png) + +The dashboard requires authentication -- unauthenticated users are redirected to the login page. + +### Authentication and registration gating + +We added `mix phx.gen.auth` for session-based authentication with magic links and password login. Login and registration pages are accessible by direct URL only -- they're intentionally not linked from the public navigation. + +Registration is gated to a single email via the `ALLOWED_REGISTRATION_EMAIL` environment variable. Anyone else gets a polite rejection: + +![Registration rejected for non-matching email](/images/scheduled-publishing/scheduled-registration-rejected.png) + +When the environment variable isn't set, registration is disabled entirely. A demo user (`demo@example.com`) is seeded in dev for local testing. + +## How it was built + +The feature was planned as an [Allium specification](https://github.com/your-org/allium) with surfaces, rules, and domain entities, then broken into 12 beads (issues) across three phases: + +1. **Scheduled posts** (5 beads): date filtering in blogex, unfiltered direct access, feed/router verification +2. **Authentication** (3 beads): phx.gen.auth scaffolding, registration gating, dev seed +3. **Dashboard** (4 beads): post visibility helpers, unfiltered registry access, LiveView dashboard, status banners + +All 12 beads were executed with parallel agentic workers in isolated git worktrees, then merged and integrated on main. The demo caught one bug (auth check using `current_user` instead of `current_scope`) which was fixed before this post. + +## By the numbers + +- **232 tests** passing (89 blogex + 143 Phoenix app) +- **12 beads** planned, executed, and closed +- **3 phases** run with parallel workers +- **0 compiler warnings** diff --git a/app/priv/static/images/scheduled-publishing/scheduled-banner-draft.png b/app/priv/static/images/scheduled-publishing/scheduled-banner-draft.png new file mode 100644 index 0000000..6bd2488 Binary files /dev/null and b/app/priv/static/images/scheduled-publishing/scheduled-banner-draft.png differ diff --git a/app/priv/static/images/scheduled-publishing/scheduled-banner-future.png b/app/priv/static/images/scheduled-publishing/scheduled-banner-future.png new file mode 100644 index 0000000..3f5f203 Binary files /dev/null and b/app/priv/static/images/scheduled-publishing/scheduled-banner-future.png differ diff --git a/app/priv/static/images/scheduled-publishing/scheduled-blog-index.png b/app/priv/static/images/scheduled-publishing/scheduled-blog-index.png new file mode 100644 index 0000000..ad0a37d Binary files /dev/null and b/app/priv/static/images/scheduled-publishing/scheduled-blog-index.png differ diff --git a/app/priv/static/images/scheduled-publishing/scheduled-dashboard-scheduled.png b/app/priv/static/images/scheduled-publishing/scheduled-dashboard-scheduled.png new file mode 100644 index 0000000..6426a2d Binary files /dev/null and b/app/priv/static/images/scheduled-publishing/scheduled-dashboard-scheduled.png differ diff --git a/app/priv/static/images/scheduled-publishing/scheduled-dashboard.png b/app/priv/static/images/scheduled-publishing/scheduled-dashboard.png new file mode 100644 index 0000000..68401f0 Binary files /dev/null and b/app/priv/static/images/scheduled-publishing/scheduled-dashboard.png differ diff --git a/app/priv/static/images/scheduled-publishing/scheduled-direct-access.png b/app/priv/static/images/scheduled-publishing/scheduled-direct-access.png new file mode 100644 index 0000000..9cc0fc5 Binary files /dev/null and b/app/priv/static/images/scheduled-publishing/scheduled-direct-access.png differ diff --git a/app/priv/static/images/scheduled-publishing/scheduled-registration-rejected.png b/app/priv/static/images/scheduled-publishing/scheduled-registration-rejected.png new file mode 100644 index 0000000..22d8947 Binary files /dev/null and b/app/priv/static/images/scheduled-publishing/scheduled-registration-rejected.png differ