# News content authoring guide This page documents the HTML vocabulary the `/home` news perex and `/news` permalink page recognise. Authors should write content using only these allowed tags, attributes, and class names — anything outside the list is silently stripped on save. ## Where the content lives A single news entity (intro + content) is stored in DuckDB table `news_template`. Every save creates / updates a row with a monotonically increasing `version`. The latest row with `published = TRUE` is what `/home` and `/news` render. The admin can roll back by unpublishing a newer version (web falls back to the next-highest published version automatically). Drafts older than 30 days that were never published, and superseded published versions older than 30 days, are pruned on save. The currently-displayed published version is never pruned regardless of age. ## How to author Two equivalent surfaces: - Web admin UI: **`/admin/news`** — two textareas (intro + full content) with a sandboxed live preview, a "Format help" cheatsheet, and a versions table with `Unpublish` actions. - CLI: ```bash agnes admin news show # current published agnes admin news draft # active draft (or none) agnes admin news edit \ --intro '
Short HTML perex.
' \ --content 'Body.
' agnes admin news edit --from news.yaml # YAML/JSON {intro, content} agnes admin news publish # flip active draft → published agnes admin news unpublish 5 # roll back v5; web shows next-highest published agnes admin news versions agnes admin news export news.yaml # round-trip to a file ``` Both surfaces sanitize on save through `src/sanitize_news.py` (Rust-backed `nh3`). ## Allowed tags ``` p, br, hr, h1, h2, h3, h4, h5, h6, ul, ol, li, strong, em, b, i, u, s, code, pre, blockquote, a, img, span, div, section, table, thead, tbody, tr, th, td, details, summary, figure, figcaption, iframe (only with allowlisted src — see below) ``` Anything else (`