fair: a Go CLI that publishes markdown to ATProto as standard.site
A single-author static-site publisher built around two ideas:
1. The PDS is the source of truth. Posts live as
site.standard.publication + site.standard.document records on your
ATProto PDS; the rendered site mirrors them. Markdown files on disk
are working copies. External standard.site readers (Leaflet, pckt,
indexers) see the same records your own site does.
2. The output is plain HTML. No CSS, no JavaScript, no client-side
rendering. Native browser widgets carry the load — <details>,
<progress>, <meter>, <figure>, semantic <article>/<aside>/<time>,
GFM tables, footnotes, definition lists. A demo article in
blog-posts/html-feature-demo/ exercises every feature.
Architecture (see docs/design-plans/2026-04-29-blog-v2026.md):
- Profile mechanism: --profile required (sandbox vs prod), no default,
Levenshtein typo suggestion, profile-isolated state + sessions.
- ATProto OAuth (loopback + DPoP + PAR) via haileyok/atproto-oauth-golang.
Auto-refresh wired into every authenticated subcommand.
- Idempotent publish: SHA-256 over canonical inputs (markdown source,
not goldmark output) gates re-puts; --dry-run / --force escapes.
- ULID identity in frontmatter — written back on first publish, drives
rename detection.
- Build flow has a cold-start contract: works with no auth, no records,
emits well-known + a stub site so the OAuth metadata document is
reachable before first login.
- Outputs: index, per-post, archive, tag pages, Atom + RSS + JSON Feed
+ index.json, sitemap, robots.txt, .well-known/oauth-client-metadata
+ .well-known/site.standard.publication, favicon from publication.icon
blob. Atomic dist.tmp -> dist promote.
- HTML enhancements: OG/Twitter cards, canonical, rel=me, prev/next
link headers, atproto+json alternate, bsky discuss link from
bskyPostRef, image-paragraph -> figure rewrite, Bluesky blockquote
-> "Discuss on Bluesky" footer link.
Sandbox: bluesky-social/pds Docker container behind Caddy + mkcert at
pds.localtest.me (workaround for the upstream PDS rejecting loopback
hostnames in OAuth issuer URLs). Profile-isolated session storage
prevents cross-contamination with prod.
Subcommands: auth login/logout/status, init publication,
emit-client-metadata, publish (--dry-run/--force), build, ls (PDS-vs-
local diff), doctor (5-check health report), unpublish,
migrate cleanup-whtwnd (post-cutover legacy lexicon cleanup),
config show.
Operator runbook for the WhtWnd -> standard.site cutover lives at
docs/runbook-cutover.md. CLAUDE.md captures the non-obvious rules.
Replaces the Next.js + WhiteWind blog at ~/code/pds-blog.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>