# Changelog All notable changes to **Volumen** are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [development] ### Added - ## [1.0.0] - 2026-09-29 ### Added - **First release** of the platform, the API, the admin and the command line. **Content** - Posts are Markdown files with a TOML frontmatter block: `title`, `slug`, `date`, `lang`, `author`, `tags`, `excerpt`, `cover` with alt text and caption, `series` with `series_order`, `draft`, `publish_at`, `all_langs`, `aliases`, `translations`, `fediverse_creator`, `doi`, `orcid`, `refs`, and any key of your own, preserved in the order you wrote it. - Mathematics: `$…$` and `$$…$$` in a post body render as MathML Core. The conversion is server-side and carried by scriptorium, whose symbol tables cover the standard TeX surface, so equations reach every consumer of the API as native HTML with no JavaScript and no external service. A display equation may span lines, the shape the papers in the corpus are written in. A construct no MathML element can carry is never guessed at: it stays visible as its verbatim TeX in the equation, marked as an error, exactly where the author wrote it. - Diagrams: a fenced `mermaid` code block renders server-side to an inline SVG, with flowcharts (including the historical `graph` spelling) and sequence diagrams carried in full: every node shape and edge kind, subgraphs, classes and styles on one side, participants, all arrow kinds, notes, activations, the block constructs, coloured rects, autonumbering and dividers on the other. A diagram type outside the two families stays the code block the author wrote, and a line that does not parse keeps it too, so nothing is half-drawn. - Scholarly identifiers are first-class: a post can carry `doi` and `orcid` in its frontmatter and the editor, and an account can carry its owner's ORCID, which pre-fills the field of new posts. A DOI is normalised from a doi.org URL or doi: prefix to the bare `10.…/…` form; an ORCID is checked to its ISO 7064 check digit. Validation is syntactic and offline: the engine never calls doi.org or orcid.org. - Bibliography is first-class: a post carries a `refs` array of tables in its frontmatter and marks where the list belongs with a `[[refs]]` line in the body. The engine renders a numbered reference section, links every inline `[n]` citation to its entry, and turns each entry's DOI, arXiv id and ORCID into resolver links; hand-written entries keep their verbatim text with their identifiers made live. A reference whose DOI belongs to another post of the same instance links to that post instead of leaving for the resolver. - Multi-language posts: one file per language, either in the content root or in a per-language subdirectory, linked by a `translations` map, with `all_langs` for a post that belongs to every language. - Scheduled publishing: `volumen publish-due` for cron or a systemd timer, and the in-process `[scheduler]`, which publishes the due posts at start-up and then every interval. - Post revisions: every save archives the previous version under `posts/.revisions//`, pruned to `revision_limit` versions; deleting a post moves it into that archive, so it stays undoable. - Post templates, an import of a `.md` file, and a download of any post with its frontmatter. A template pre-fills the new-post form: its name, title, slug, tags and body, plus TOML `key = value` lines for any editor input (`series`, `doi`, `orcid`, `author`, `lang`, `cover`, `excerpt`, …), stored in `templates.toml` under `[templates.fields]` and shown on the template's row, so a scientific volume or a short note starts with its series and author already in place. - A media library of uploaded images, and cover images per post. Uploads are stored under a UUID with the extension their bytes carry, WebP, AVIF and SVG being the recognised formats, an SVG recognised by its root element; each tile shows the pixel size read from the container headers or from the SVG root's size attributes and `viewBox`, and the library takes several uploads at once, filters by name, and offers copy-link, open and delete on every tile. **Public API** under `/api/volumen/` - Site metadata, paginated post lists with `lang`, `tag` and `q` filters, single posts with raw Markdown, rendered HTML and a table of contents, several posts in one request, tag and series listings, RSS 2.0, Atom 1.0, JSON Feed 1.1 and an XML sitemap. - The `q` search ranks its results by relevance across the title, the tags, the excerpt and the body: a title hit leads, and equal scores keep the date order. - Pagination in two shapes: page numbers with `has_next` and `has_prev`, or a cursor with `next_cursor`. - `ETag` on the list responses and on a post detail, `If-None-Match` answered with `304`, and `PUT` and `DELETE` honouring `If-Match`, so a write is refused with `412` instead of silently overwriting a change the client never saw. - A post's `doi` and `orcid` appear in the payload and in the JSON-LD block as resolvable identifiers, and a post with `refs` carries its `references` array, each entry's DOI, arXiv id and ORCID turned into a resolver link and the JSON-LD block carrying the `citation` objects beside them: the citation the web can hand to a reference manager. - Frontmatter keys the engine does not consume itself pass through in a `fields` object on a post detail, with nested tables, arrays and date-times intact. - A sliding-window rate limit per client address with `X-RateLimit-*` headers and a `429` carrying `Retry-After`. - Personal access tokens with the `write` and `delete` scopes for `POST`, `PUT` and `DELETE`, for publishing from scripts and CI. Only the SHA-256 digest is stored, and the raw token is shown once. - One error envelope for every failure, with a machine-readable `error` code and, where it helps, a `message` and a `field`. **Admin** under `/admin/` - A first-run wizard founds the installation in place of the login: it creates the administrator account, takes the interface language and the colour scheme with a live preview, shows a password strength meter against the configured policy, and ends with the operator signed in. The first account is created in one write and under a lock, so two visitors claiming a fresh installation cannot both open an identity. - Username and password login with the `admin` and `author` roles, CSRF tokens on every state-changing form, and a strict Content-Security-Policy with a per-request nonce. Sessions are bound to the password they were issued under, so a password change retires every session issued before it, and an admin can reset another account's password from Settings, Users. - An optional second factor for any account: time-based one-time passwords with the enrolment QR drawn by the server itself, one-time recovery codes shown exactly once and stored only as digests, a replay floor that refuses a code a second time, and the same lockout guarding code guesses as password guesses. Nothing changes for an account until its owner finishes the setup. - The interface is built on the author's website design system: a layered stylesheet with `oklch` neutrals and the Viridis, Plasma and Magma palettes from the exact matplotlib colour-map stops, self-hosted Ubuntu and Ubuntu Mono, a light/dark/system mode toggle, one shared icon sprite, Graphis, native dialogs for confirmations and prompts, and a sidebar that folds to an icon rail remembered on the device. On the post list the status and tag filters lead with a funnel and a tag glyph and the card's Edit action takes a pencil. The sheets and fonts are served under `/admin/assets/` and revalidated by a content ETag, so a visit fetches them once per change. The interface ships in English and Czech, both per-account choices, and the login screen follows the last one. - A dashboard with status counts, a tag cloud, search, status filters, bulk publish, draft and delete, and the next scheduled posts with their publish dates. The list shows one card per publication: the language versions are merged by their `translations` frontmatter, the card names the version in your interface language, and small language chips switch it to another version; a bulk selection acts on the whole publication, and the counters and the tag cloud count publications, not files. - A post editor with a Markdown source view and a visual view over the same document, live preview, toolbar and keyboard shortcuts, drag-and-drop and pasted image upload, slug generation, reading-time counters and autosave with restore. The bibliography has its own card below the editor, as the reference list sits below the body of a paper: it lists every entry the post carries, edits the verbatim citation or the structured fields (authors with ORCID, venue, year, volume, pages, DOI, arXiv, URL), reorders and removes entries, inserts the `[[refs]]` marker into the body, names its count in the heading and scrolls inside itself. A save writes the list back as `[[refs]]` tables with every other frontmatter key untouched and the identifiers checked on the way in: a DOI normalises from a doi.org URL, an ORCID checks its own digit. The live preview splices in the saved post's bibliography, so the author sees what the page will show. - Revision history per post, with download of any revision, a line-difference comparison against the current content and one-click restore, and an undo for the last delete. - Settings: account (password, username, display name, fediverse handle, profile photo), users and roles, post templates, the media library, API tokens, webhooks with a test delivery and per-endpoint enable and remove that apply without a restart, backup and restore, the version panel with a checksum-verified in-place self-update, and the audit log switch. - The surface reaches a phone: below 760 px a navigation sheet behind a hamburger button carries the sidebar links, the signed-in user and the logout button, and every control keeps a visible keyboard focus. **Command line** - `serve`, `status`, `doctor`, `check-update`, `export`, `import`, `publish-due`, `validate` and `version`, each with `-h`, and every operational failure reported with a non-zero exit code. A stray positional argument is a usage error. - A manual page, `man/volumen.1`, documents every subcommand and flag, and the README links it beside the command list. **Operations** - Installing is: copy the binary, run `volumen serve`, open `/admin`. With no `--config` the server reads `/etc/volumen/config.toml` if it exists, then `~/.config/volumen/config.toml`, and with neither it runs on per-user defaults whose state lives under `~/.local/share/volumen` (honouring `XDG_DATA_HOME`), so a plain start works without root and without writing any file first. The commented configuration template, `config.toml.example`, is committed at the repository root. - A fresh installation presents itself as Volumen: the default site title is `Volumen` and the description `Powered by Volumen.`, in the API, the feeds and the admin. - `volumen doctor` and `volumen validate` check an installation and its content, `volumen status` reports the installation's state, and a deployment with no accounts yet reports the first-run wizard as the next step (a warning, not a failure). `GET /healthz` reports readiness for a supervisor. - `volumen export` and `volumen import` move a whole deployment, or the admin Backup panel does, through the same archive: the posts, media and revisions under the content directory, plus the users, templates and tokens files. - The session secret is generated by the server: on first start it writes a 64-character key to `secret.key` beside the users file and reuses it across restarts, so an operator never edits a config to get sessions that survive. `[admin].session_key` remains as an explicit override. - Structured JSON logging with `[server].log_format = "json"`, an append-only audit log, and outgoing webhooks that notify a front end when a post changes. - Every request carries a 16-character id, answered in `X-Request-Id` and attached to each line the handlers write while serving it, and one access line per request records the method, the path, the status and the duration. - `[server].trusted_proxies` names the addresses whose `X-Forwarded-For` may be believed; an empty list never reads the header. - Read, header, write and idle timeouts on the server, and a shutdown on `SIGINT` or `SIGTERM` that drains the requests in flight. - `NOTICE.md` in the repository reproduces the licence of every Go module the binary is compiled from and the terms of the artwork embedded in it. ### Security - Post bodies are rendered by scriptorium and then sanitised by bluemonday against a strict allowlist, so a body is treated as untrusted even though its author is authenticated; `