6 Commits

Author SHA1 Message Date
0c686a2e40 Document how to add and release a blog post
docs/blog-workflow.md: frontmatter/voice conventions, the draft+pubDate gate,
the daily deploy cron, publish checklists (scheduled vs immediate), and gotchas.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:44:12 +02:00
6776b1f337 Merge pull request 'Publish first post: Compose for widgets, RemoteViews underneath' (#3) from release-widgets-post into main 2026-07-01 19:39:21 +00:00
5da1615fa8 Publish "Compose for widgets, RemoteViews underneath"
First post released through the scheduling pipeline: draft:false with pubDate
today so the date gate passes. The other five stay draft/future-dated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:39:10 +02:00
6d8df36557 Merge pull request 'Target shared docker runners in scheduled-deploy' (#2) from fix-runner-label into main
Reviewed-on: #2
2026-07-01 19:32:38 +00:00
6a577c14ce Target shared docker runners in scheduled-deploy
The instance runners advertise `dind`/`docker` labels, not `ubuntu-latest`, so
the job sat queued unmatched. Point runs-on at `docker`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:31:11 +02:00
8d1126b5dc Merge pull request 'Add date-based post scheduling + daily deploy cron, and six draft posts' (#1) from blog-release-pipeline into main 2026-07-01 14:42:28 +00:00
3 changed files with 122 additions and 6 deletions

View File

@@ -7,9 +7,8 @@
# pings the deploy hook. # pings the deploy hook.
# #
# Setup (once): # Setup (once):
# - A Gitea Actions runner must be registered and online for this repo/org, # - Uses the shared `docker`-labelled runners. If those labels change, update
# advertising a label that matches `runs-on` below (default act_runner maps # `runs-on` below to match a runner that is online.
# `ubuntu-latest`; change it if your runner uses a different label).
# - Add a repo secret COOLIFY_DEPLOY_HOOK = the app's deploy URL from Coolify # - Add a repo secret COOLIFY_DEPLOY_HOOK = the app's deploy URL from Coolify
# (app → Webhooks → "Deploy Webhook", e.g. # (app → Webhooks → "Deploy Webhook", e.g.
# https://coolify.example.com/api/v1/deploy?uuid=<uuid>&force=false). # https://coolify.example.com/api/v1/deploy?uuid=<uuid>&force=false).
@@ -31,7 +30,7 @@ on:
jobs: jobs:
trigger-deploy: trigger-deploy:
runs-on: ubuntu-latest runs-on: docker
steps: steps:
- name: Ping Coolify deploy hook - name: Ping Coolify deploy hook
env: env:

117
docs/blog-workflow.md Normal file
View File

@@ -0,0 +1,117 @@
# Writing and releasing a blog post
How posts live, how they're scheduled, and how they go live. The short version:
**a post is public when it is `draft: false` *and* its `pubDate` has arrived**
and a daily job rebuilds the site so "arrived" keeps advancing on its own.
## 1. Add the post
Create a Markdown file in `src/content/blog/`, e.g.
`src/content/blog/my-post.md`. The filename (minus `.md`) is the URL slug:
`/blog/my-post/`.
Frontmatter is validated at build time against `src/content.config.ts`:
```markdown
---
title: Short, specific title
description: >-
One or two sentences. Shown in listings, the RSS feed, and social previews.
pubDate: 2026-07-08 # ISO date. The publish gate (see below).
# updatedDate: 2026-07-10 # optional; shown as "updated"
tags: [android, calendula] # see the tag vocabulary below
draft: true # true = never public; flip to false when ready
---
Body in Markdown. ~80-char wrapped to match the other posts.
```
Voice conventions (match the existing posts): first-person, "constraint as
design", link to standards/APIs inline, one punchy closing line. Reference other
posts with root-relative links like `/blog/open-standards`.
Established **tag vocabulary** — reuse these rather than inventing one-offs:
`android`, `architecture`, `calendula`, `caldav`, `open-standards`,
`open-source`, `accessibility`, `localization`, `meta`.
## 2. The three states
The gate lives in `src/utils/posts.ts` (`getPublishedPosts()`), which every
listing, the tag pages, the post routes, and `rss.xml` use.
| `draft` | `pubDate` | Dev server | Production |
| --- | --- | --- | --- |
| `true` | any | visible (preview) | **hidden** |
| `false` | future | visible (preview) | **hidden until the date** |
| `false` | today / past | visible | **live** |
So:
- **`draft: true`** — work in progress or not yet approved. Never public.
- **`draft: false` + future `pubDate`** — approved and *scheduled*. Publishes
itself on that day (see §3).
- **`draft: false` + past `pubDate`** — live now.
Preview anything (including drafts and scheduled posts) locally with
`npm run dev` — the dev server lifts the gate entirely.
## 3. How release actually happens
The site is static, so "now" is frozen at each build. Two things trigger a
rebuild:
1. **Push to `main`** → Coolify redeploys (the normal deploy).
2. **The daily cron**`.gitea/workflows/scheduled-deploy.yml` runs at
**06:15 Europe/Berlin**, pings the Coolify deploy hook, and the rebuild
re-evaluates the date gate. This is what makes a future-dated post appear on
its day without anyone touching the repo.
The cron runs on the shared **`docker`** runner and needs two repo secrets set
once (Gitea → Settings → Actions → Secrets): `COOLIFY_DEPLOY_HOOK` (the app's
deploy URL) and `COOLIFY_TOKEN` (a Coolify API token, Bearer).
## 4. Publish checklist
To schedule a post for its date (the normal path):
1. Set `draft: false`, leave the intended future `pubDate`.
2. Commit and get it onto `main` (branch → PR → merge; PRs via `tea pr create`
/ `tea pr merge`). Merging deploys, but the post stays hidden until its date.
3. Done — the daily cron publishes it on `pubDate`.
To release a post **immediately**:
1. Set `draft: false` **and** `pubDate` to today (or a past date), so the gate
passes now.
2. Merge to `main`.
3. Trigger a rebuild right away instead of waiting for the cron:
```sh
tea actions workflows dispatch scheduled-deploy.yml --ref main
```
(`tea` may print `unexpected end of JSON input` — that's it mis-reading
Gitea's empty success response; the run still starts. Check it with
`tea actions runs list`.)
4. Coolify rebuilds; confirm at `https://jeanlucmakiola.de/blog/<slug>/`.
## 5. Handy commands
```sh
npm run dev # preview everything locally
npm run build # production build (applies the gate)
tea actions workflows list # see the deploy workflow
tea actions workflows dispatch scheduled-deploy.yml --ref main # manual deploy
tea actions runs list # watch run status/conclusion
```
## 6. Gotchas
- **Don't** put non-post `.md` files under `src/content/blog/` — the collection
glob will try to parse them as posts and fail the build. Docs like this one
live in `docs/`.
- `pubDate` with no time resolves to `00:00 UTC` that day, so a post becomes
eligible at UTC midnight; the morning cron then publishes it. Adjust the cron
time in the workflow if you want a different local hour.
- Timezone for the cron is pinned in the workflow (`TZ=Europe/Berlin`); the
gate comparison itself uses the build machine's clock (UTC in CI).

View File

@@ -4,9 +4,9 @@ description: >-
Jetpack Glance lets you write home-screen widgets in Compose. But it compiles Jetpack Glance lets you write home-screen widgets in Compose. But it compiles
down to RemoteViews — so the old constraints still bite through the nice API. down to RemoteViews — so the old constraints still bite through the nice API.
Three Calendula bugs that proved it. Three Calendula bugs that proved it.
pubDate: 2026-07-02 pubDate: 2026-07-01
tags: [android, architecture, calendula] tags: [android, architecture, calendula]
draft: true draft: false
--- ---
Calendula's home-screen widgets are written in Calendula's home-screen widgets are written in