SPEC_BLOG.md

blog.siao.ai — personal technical blog spec

What this covers: a new, standalone personal technical blog at blog.siao.ai — its scope, stack, deployment, and its two-way linkage with SiaoHub (git.siao.ai). Lives in a new repo, not yet created (proposed name: blog-siao-ai, matching the apps-siao-ai / git-siao-ai naming convention — not finalized with the owner). Also covers a small addition to the existing siaohub repo (the backlink display on file pages).

No issue tracker exists for this project family/setup-matt-pocock-skills has not been run, this working directory is not itself a git repository, and the target repo for the blog does not exist yet. Following the precedent set by SPEC_APPS.md ("No issue tracker exists yet for this project — this file is the spec until one does"), this file is the spec of record. Status field below uses the same convention as the .scratch/*/issues/*.md tickets elsewhere in this project.

Status: ready-for-agent

For what is built or deployed, see ClaudeDocs/HANDOFF.md once work starts — status deliberately does not live here.

Problem Statement

The owner wants to publish long-form, narrative technical writing — documented experience, not Q&A content — somewhere of its own. AnswerHub (apps-siao-ai, at apps.siao.ai/answerhub) already has a working BlogPost feature (Prisma model, markdown rendering, a web form to publish), but it was already cut from AnswerHub's navigation (SPEC_ANSWERHUB_FRONTEND.md: "Blog and knowledge base cut from nav") and AnswerHub itself is expected to be deprecated later. Building the new blog as another AnswerHub feature would mean redoing this same move a second time when AnswerHub is retired. Separately, the owner wants technical posts to be able to reference real code living in SiaoHub (git.siao.ai) — showing an actual file inline rather than pasting a copy into the post — and wants that connection to run in both directions: a reader on the blog can see the referenced file, and a reader on SiaoHub can discover that a file has been written about.

Solution

Build blog.siao.ai as a new, independent, single-author, Markdown-only static site — no database, no login, no multi-user system — deployed to Cloudflare Pages from its own GitHub repository, the same deployment model already proven by the siao.ai homepage. Posts are plain Markdown files with frontmatter, written in the owner's own editor and published by git push. A post may embed a reference to a specific file in a SiaoHub-hosted repo; the referenced content is fetched at build time (never at reader-visit time) and rendered inline with a link back to the canonical file on git.siao.ai — this is the blog→SiaoHub half of the connection, automatic and requiring no separate authoring step. The reverse direction — SiaoHub showing that a file is referenced by a blog post — is driven by a small public manifest (backlinks.json) the blog's build emits, which SiaoHub fetches and checks when rendering a file page. To keep embedded previews from going stale, the owner manually registers a Forgejo webhook on any repo referenced by a post, pointed at the blog's Cloudflare Pages Deploy Hook, so a push to that repo triggers a blog rebuild.

This deliberately does not reuse AnswerHub's BlogPost model, its database, or its auth. AnswerHub's blog feature is left as-is and expected to be retired later, independently of this work.

User Stories

  1. As the blog owner, I want to write posts as plain Markdown files in my own editor, so that I can write long-form narrative content without a web form or a database.
  2. As the blog owner, I want git push to be the entire publish step, so that posting has no extra ceremony beyond writing.
  3. As the blog owner, I want the blog's source hosted on GitHub, so that Cloudflare Pages can build and redeploy automatically on every push without me wiring a custom webhook for my own commits.
  4. As the blog owner, I want blog.siao.ai added as a Cloudflare Pages custom domain, so that shipping it never touches the self-hosted Docker Compose / Cloudflare Tunnel / Caddy stack that runs SiaoHub, AnswerHub, and Logto.
  5. As the blog owner, I want to reference a specific file from one of my SiaoHub repos directly inside a post, so that I can show real code as part of a story instead of duplicating it into the post text.
  6. As a reader, I want an embedded file reference to show the file's actual content inline, so that I don't have to leave the post to see the code being discussed.
  7. As the blog owner, I want an embedded file's content to be pinned to the ref (branch/commit) I specified when I wrote the post, so that my narrative and the shown code stay in agreement even if the source file later changes.
  8. As the blog owner, I want to be able to register a webhook on a referenced repo pointing at the blog's Cloudflare Pages Deploy Hook, so that pushing to that repo can refresh the embedded preview without me manually rebuilding the blog.
  9. As the blog owner, I want this webhook registration to be a manual, one-time, per-repo action, so that neither system needs to hold a credential capable of modifying the other's configuration.
  10. As the blog owner, I do not want the blog to depend on SiaoHub being reachable at the moment a reader visits, so that a SiaoHub outage never breaks the blog for readers.
  11. As a reader browsing a file on SiaoHub, I want to see when that file is referenced by a blog post, so that I can discover the narrative context behind a piece of code.
  12. As the blog owner, I do not want to manually maintain the SiaoHub side of this backlink, so that adding a reference in a post is the only action required for the backlink to appear.
  13. As the blog owner, if the blog's backlinks manifest is briefly unreachable or malformed, I want SiaoHub's file pages to render normally with no backlink shown, so that a blog-side problem never breaks SiaoHub for its own visitors.
  14. As the blog owner, I want the blog to be single-author with no login, registration, or session system, so that there is no user-management surface to build or secure.
  15. As the blog owner, I want the blog to default to Traditional Chinese, so that I can write in the language I think in without translation overhead on every post.
  16. As the blog owner, I want to optionally provide a translated version of a specific post, so that select posts can reach other audiences without committing to translating everything I write.
  17. As a reader, I want a language switcher to appear on a post only when a translation actually exists for it, so that I never land on an empty or machine-stub page.
  18. As the blog owner, I want the blog to have no database and no server-rendered runtime, so that there is nothing to patch, scale, or keep alive beyond a static file host.
  19. As the blog owner, I want each post's frontmatter to carry at least a title, date, and excerpt, so that the build script can generate an index/listing page and page metadata without extra authoring.
  20. As a search engine or a social-media crawler, I want each post to expose correct title/description/canonical-URL metadata, so that shared links render meaningful previews.
  21. As the blog owner, I want the Markdown-to-HTML pipeline (GFM, math, syntax-highlighted code, sanitized output) to match AnswerHub's existing rendering choices, so that code blocks and math look consistent across my properties.
  22. As the blog owner, I want the renderer to still run content through an HTML sanitizer even though I am the only author, so that accidentally pasted untrusted HTML (e.g. copied from a browser) can't introduce a stray executable tag.
  23. As the blog owner, I want the build to fail loudly if a post references a SiaoHub file/repo/path that doesn't exist or isn't reachable, so that a broken reference is caught before it reaches readers, not shipped as a blank embed.
  24. As the blog owner, I want to render a post locally before pushing, so that I can catch a broken embed reference or a math/syntax mistake before it goes live.
  25. As the blog owner, I want post slugs to be stable and independent of where the source file happens to live, so that I can reorganize my source tree without breaking published links.
  26. As the blog owner, I want AnswerHub's existing /answerhub/blog feature to be treated as legacy, receiving no further investment, so that effort concentrates on this new blog instead of maintaining two parallel blog systems.
  27. As the blog owner, I want the repo name and exact Cloudflare Pages project/domain settings confirmed before implementation starts, so that they're created correctly the first time.

Implementation Decisions

Testing Decisions

Good tests here assert on observable output — generated HTML content, or the presence/absence of a rendered UI element — not on internal function calls, consistent with the testing style already used across this family.

Out of Scope

Further Notes