fair: comments, --announce, responsive images, security hardening
Builds on the initial publish/build pipeline:
- Comments: post pages render an inline-JS comments section that fetches
app.bsky.feed.getPostThread from the public Bluesky AppView at view
time. No-JS users get a "reply on Bluesky" link; empty-thread state
shows "be the first to reply"; replies render with avatar, profile
link, timestamp, body text, and a permalink ↗ to the specific reply.
- --announce flag on publish: optional auto-create of an
app.bsky.feed.post on the user's repo, pinned via bskyPostRef as the
comment-thread root. Adds repo:app.bsky.feed.post scope (existing
sessions need one-time re-login). Idempotent — writes the new at-uri
back to frontmatter as bsky_post: so re-runs short-circuit.
- bskyPostRef lexicon compliance: the field is a
com.atproto.repo.strongRef per the canonical standard.site lexicon —
fair now reads/writes both uri+cid. Frontmatter `bsky_post:` resolves
the CID via own-repo authenticated GetRecord (sandbox-friendly) or
the public AppView (federated posts).
- Responsive images: publish generates 480/960/1440/1920 variants
alongside each inline image (golang.org/x/image/draw CatmullRom);
build emits srcset/sizes/width/height + loading=lazy + decoding=async
on every <img>. New internal/images package.
- Home page: _index.md frontmatter gains description/subtitle/pinned/
hide_years for home-only customization; _outro.md is below-fold
chrome; year-grouped post index via <details>; tag cloud + h2
section headers; reading time per post; pdsls.dev link in the post
meta line.
- Polish: viewport meta, color-scheme light/dark, theme-color light/
dark, speculation-rules prerender, system font stack, line-height,
body width cap, centered prev/home/next nav, prev/next rel links.
- Security review fixes: path-traversal validation on PDS-controlled
path and tag fields; SSRF mitigation via no-redirect HTTP client
policy on the build's getRecord/listRecords calls; XSS escaping in
OAuth loopback callback page; at-uri prefix gate on htmltemplate.URL
casts; favicon download bounded with io.LimitReader (5 MiB); all
XRPC URL params now built via url.Values.Encode rather than
fmt.Sprintf.
- Cleanup: removed migrate cleanup-whtwnd subcommand and scope; new
docs/runbook.md replaces the migration-flavored runbook-cutover.md;
CLAUDE.md split between fair (project doc) and the operator's
blog-posts repo (deploy notebook).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>