# Blog

2026-08-08. The fourth application, and the lightest of the ones still to
build — which is exactly why it goes before Events and Gallery: it is the first
honest test of whether the registries earn their keep.

---

## The job

Somebody writes an entry. It has a title, a body, an author and a date, it can
be commented on, and it lives at its own address. A site can have **one blog**
that staff write for, or **a blog per member**, and the difference is a setting
rather than two features.

## The question the roadmap left open, answered

> *"Whether Blog entries are a content type of their own or a Pages database
> with a preset. The second is tempting and probably wrong."*

**Their own.** A custom database could hold title, body and date — but a blog
wants four things a database record does not have and should not grow:

- **An owner, in the URL.** `/blog/ernest/why-we-rebuilt` is the shape people
  expect, and it makes a per-member blog navigable rather than a filter.
- **A feed.** Blogs are subscribed to. A database of records is not.
- **Comments.** A record has none, deliberately — the custom-database note left
  that open precisely because it belongs to whatever is being commented on.
- **Draft and schedule as first-class.** A record is published or it is not; a
  blog entry is written on Tuesday for Thursday.

Building it on records would mean bolting all four onto records, which makes
records worse for the thing they are actually for.

## Comments reuse posts, not a new table

🚨 The decision most likely to be got wrong, and the one worth arguing.

A comment on a blog entry is a post: written by a member, edited under the same
window, reported through the same queue, reacted to with the same reactions,
counted in the same member post count, and moderated by the same people using
the same tools.

A second table would mean a second implementation of editing, reporting,
reacting, hiding and deleting — five behaviours that already exist and are
already tested, each of which would drift.

So an entry gets a **hidden topic**, and comments are that topic's posts. The
entry owns the writing; the topic owns the conversation.

The cost, stated plainly: a blog entry's comments appear in the forum's data
model, and anything listing topics must exclude blog-owned ones.

### 🚨 Correcting this note: it is not "one filter in one place"

The first version of this paragraph said the cost was *one filter in one place*.
That was wrong, and it was wrong in a way this codebase had already
demonstrated the same day: deleting a topic missed `deleted_at` in four
separate listings, and the symptom was a moderator reporting that the delete
button did not work.

Counted properly, the queries that list topics without naming a forum are
**eight** — the REST API's list and lookup, the site statistics on the forum
index, the statistics widget, the recent-topics widget, quick search over
topics and over posts, activity streams, and topic surgery. Every one of them
is a place a blog comment could surface as though it were a forum topic.

So the filter is generalised rather than repeated. Topics gain an
**`owner_type`**: null means a forum topic, anything else names the application
that owns it. Global listings ask for `owner_type IS NULL` once, and the same
column serves Events, Gallery and the Knowledge Base when they want comments
too — which they will.

That is still cheaper than five reimplemented behaviours, but it is a bigger
number than the first draft of this note claimed, and pretending otherwise
would have been how the eighth listing got missed.

### 🚨 And the cost that is not small: Blog depends on Forums

Reusing topics and posts means `blog` declares `forum` in its manifest, so
**a site with Forums switched off cannot have blogs.** That is a step backwards
from the application model, which exists precisely so applications do not need
each other, and it should be recorded as a debt rather than discovered by
somebody who wanted a blog-only site.

It is still the right trade *today*, for one reason: the alternative is not
"write a comments table", it is **move topics and posts out of Forums and into
System**. Comments, forum replies and eventually club discussions are the same
thing, and the place that owns them should be the core — the same argument that
moved identity and settings there.

That is a real piece of work and it should not be done speculatively in the
middle of building Blog. What settles it is somebody actually wanting blogs
without forums; until then, one declared dependency is honest and one hidden
duplication would not be.

## What it registers

By now the pattern is the point:

- **Content type** so entries are searchable — and 🚨 filtered per viewer, since
  a member's blog can be limited to groups the same way everything else is.
- **Widget kinds** — "Latest blog entries" and "Blog entries by a member".
- **Widget areas** on the index and on an entry.
- **Personal data**, for entries and for the blogs themselves.

## Draft, scheduled, published

Resolved on read rather than by a job, the way Pages already does it: a
`published_at` in the future simply is not live yet. No scheduler, nothing to
fail overnight, and a site whose cron has been broken for a month still
publishes on time.

## What this note does not decide

- **Whether every member gets a blog automatically**, or asks for one. Leaning
  towards asking — a thousand empty blogs is not a feature — but it is a
  setting either way and the answer can wait for somebody to want it.
- **Categories or tags on entries.** Tags already exist and already attach to
  topics; whether an entry reuses them or has its own is a question for when
  somebody has more than twenty entries.
- **Cross-posting an entry to a forum.** Frequently asked for, and it needs a
  story for what happens when one side is edited or deleted.
- **The feed format.** RSS and JSON Feed are both trivial; which ones ship is
  not worth deciding before the entries exist.
