Decision record
ADR-0009: A page opens to read
ADR-0009: A page opens to read; editing is a state a person enters
Status: accepted · 2026-09-11
Context
An editor who opened a page got the editor at once. The server load signed a
collab ticket for anyone with page.edit, the client joined the WebSocket
room on mount, and the Tiptap bundle loaded — whether the person had come to
write or to read. Viewers, who cannot edit, got rendered HTML with the
comments, threads and decision controls all working. Read state existed; an
editor was simply never shown it.
Three properties of this product make “which mode opens first” a question with a right answer, not a matter of taste.
- A page in a live room refuses agent writes. ADR-0003, and it is right. But the room opened the moment an editor looked at a page. Every reader was blocking every agent for as long as their tab was open, and the agent was told the page was being edited when nobody was editing it.
- Autosave overwrote API writes that landed while someone read. On 2026-09-10 two section updates to a plan were silently lost: the page was open in a tab, nobody was typing, and the editor flushed its older copy over them. The live-room guard exists to prevent exactly this and was bypassed that day — but the deeper cause was that reading opened a room.
- Any revision withdraws an approval. An approver who opened a brief to read it and brushed a key had retracted the approval they came to check. The person whose job is to decide was the person most exposed to an accidental edit.
Each is fixed by the same change: nobody holds a room until they ask to.
Decision
A page opens to read. Editing is a state a person enters and leaves.
- The page renders server-side for everyone. No collab ticket is signed in the load; no editor bundle is fetched. Presence marks the person as reading.
- Edit — a button, or the
ekey — fetches a ticket from a small endpoint with the same membership check the load used to make, imports the editor, and joins the room. Presence flips to editing. - Done persists what is on screen, leaves the room, and re-renders the page from the stored document so what the person sees is what they just wrote.
- The URL does not change. Edit is a state, not a place; a link always opens to read.
Two exceptions, both narrow:
- An empty page opens in edit. There is nothing to read.
- A person who pressed Edit keeps editing across pages for the session. Navigating between siblings while working should not cost a click each time. This is a session flag, never a per-page memory: a state that depends on which page you last touched is unpredictable, and the point is that a link opens to read.
Two things ride on the change because they are what it was for:
- Comments are the primary read-state action. They already worked there. Every heading and every task in the rendered view now carries its own comment button, using ADR-0007’s anchors, so “comment on this” is one click from the thing itself. Every heading also carries “edit this section”, which opens the editor scrolled to that anchor.
- Editing an approved brief warns first. One line, once per page per session, with Continue: the first keystroke withdraws the approval. The rule itself is untouched.
And one consequence that turned out to be a design point rather than a side effect: the workspace-change notice — “this page changed, refresh?” — was for the page you are in, so a reload could not discard what you were typing. With read by default, in means editing. A page being read now refreshes itself when someone else writes to it, which is exactly what a reader wants when the writer is an agent.
Alternatives rejected
Keep opening in edit, and make the room guard smarter — treat a room with no recent edits as inactive. Rejected: it turns “is anyone editing” into a heuristic with a window, and the failure mode is the one from 2026-09-10, silently.
Open in edit but hold off joining the room until the first keystroke. Closer, but the first keystroke is precisely the one that withdraws an approval, and the editor bundle still loads for every reader.
Per-page memory of the last mode. Rejected: a link that opens differently depending on what the reader did last time is a link that cannot be relied on.
Auto-exit edit after idle. Tempting for the agent-write case. Rejected: a person who stepped away mid-sentence and returns to find their page rewritten by an agent has a worse problem than the one this solves. Done is explicit.
Consequences
- A reader costs no WebSocket and no editor bundle. The common case is faster and lighter.
- An agent can write to a page while people read it, and is refused only
while someone is actually editing. The
editingcount in presence is the number that tells an agent whether a write will land. - The collab service, the document schema and every use case are untouched. This changed when the editor is asked for, not what it does.
- Thirteen end-to-end specs that typed into the editor gained one call to a shared helper. One new spec proves the property that matters: an agent’s write lands while a person reads, is refused once they press Edit, and lands again after Done.
- Presence records carry a
mode. Nothing else about presence changes.
Not decided here
Suggested edits — a reviewer proposing replacement text — are a different feature. Section-level edit, shipped here, is where they would attach.