Skip to content

Document Activity history and Contents outline - #594

Merged
MaggieAppleton merged 2 commits into
mainfrom
activity-and-contents
Oct 16, 2026
Merged

MaggieAppleton merged 2 commits into
mainfrom
activity-and-contents

Conversation

@iamnbutler

@iamnbutler iamnbutler commented Oct 14, 2026 •

Copy link
Copy Markdown

Document History (version history in the spirit of Apple Notes' Activity panel) and Contents (an outline for navigating the document). Each History entry is one editing session; choosing one shows the document as that session left it, and writers can restore it.

Redesign pass (Maggie + Claude). The first version put Contents and Activity in one side panel inside the paper. After exploring alternatives in a design jig, Contents became margin ticks and History became a sidebar over the document. Squashed into one commit on rebase.

Screenshots

Contents: one tick per heading in the right margin. Hovering a tick peeks at that section's prose.

Contents peek

History: slides over the views bar and document from the right, under the title bar. One line per session with a terse age; spacing grows with the time between sessions.

History sidebar

Summaries arriving: rows show at once; missing summaries are skeletons until the model writes them.

Summaries loading

Viewing an earlier session: the views bar becomes Current, whose edits, the exact time, and Restore.

Earlier version

Contents

  • One quiet tick per H1–H3 heading in the document's right margin; the first sits level with the title, and the section being read is darker. No button and no panel.
  • Hover or focus a tick to peek at its heading and opening prose in a hairline card with a light shadow; the card follows the pointer down the ticks. Click scrolls there (instantly under reduced motion).
  • Comment markers own the lane beside the prose, so the ticks show only when the margin fits both (@container (width < 53rem) hides them): expanded documents and wide screens. History hides them while open.
  • Follows edits and an earlier version on show (MutationObserver). ContentsIcon and its design-contract exception are gone.

History

  • Trigger: the clock at the end of the views bar (now labelled History). The title bar stays as it is.
  • Sidebar: absolutely positioned under the title bar over the views bar and document, so nothing reflows. Slides in and out (fades under reduced motion). Its header stays put while the list scrolls under the shared scroll-edge fade ([data-history-scroll] joins the existing list in theme.css).
  • Rows: face, who, summary, terse age (now, 2m, 3h, 4d, 2w, then the date). Space before a row grows with log2 of the hours since the newer one, up to a cap. Lingering 0.9 s shows the full line and the exact time ("Oct 11 at 11:17 AM") just left of the row.
  • Earlier session: replaces the live document with a read-only copy; the views bar becomes Current, whose edits, and the exact time.
  • Keyboard: opening moves focus to the History heading; Escape closes it and returns focus to the clock. Escape also dismisses a Contents peek.

Restore

  • Writers get Restore in the version bar. POST /api/channels/:id/versions/:versionId/restore checks push access, refuses archived documents and active builds, and calls restoreDocumentVersion in main.ts.
  • edit.restoration(current, earlier) takes the earlier prose and carries today's Questionnaire, Decision, Research and Investigation projections across unchanged, each after the block it follows now (or after the projection before it), because the records own them and replace refuses to drop, alter or author them.
  • The result goes through the existing fenced Service.rewrite, credited to whoever pressed Restore. rewrite's MCP idempotency record is now optional for this path.
  • A restore always opens its own history session, so the session it replaced stays listed and restorable even within the 10-minute merge window.

Summaries

sequenceDiagram
  participant B as Browser (History)
  participant R as Routes
  participant J as Job runner
  participant M as HISTORY_MODEL
  participant S as document_versions
  B->>R: GET /versions
  R->>S: list (summary only where summary_hash = source_hash)
  B->>R: POST /version-summaries (if newest 20 lack some)
  R->>J: enqueue version-summary (idempotent per ids + hashes)
  R-->>B: pending | unavailable | complete
  J->>S: each session + the one before it
  J->>M: one turn: removed/added blocks per session
  M-->>J: one line per session
  J->>S: summarize (only if source_hash unchanged)
  B->>R: poll GET /versions every 2 s, up to 30 s
Loading
  • Rows render immediately; missing summaries show a breathing skeleton, then the line, or made edits when no Planner can write them (no owner, AGENT=off, background jobs off). A paused job resumes when an owner appears, so the next History open has them.
  • The version-summary job uses the active Planner's credential (opening History never claims ownership) and the new HISTORY_MODEL (defaults to MODEL; set a small fast model).
  • If twenty sessions' changes exceed the 48 KB prompt bound, each session's excerpt is halved until they fit. A job that already finished under the same key reports unavailable, not pending.
  • Migration 025_document_version_summaries adds summary and summary_hash. A session that grows keeps no stale summary.

Server (unchanged from the first pass)

  • Migration 024_document_versions and DocumentVersionStore (Postgres and memory, with a storage contract test) record the canonical MDX after a commit changes the source, via DocumentHistory in onDocumentPersisted.
  • Edits by the same author less than 10 minutes apart extend that author's latest session; another author, Chopin, or a longer pause starts a new one. Browser edits credit the member whose batch committed; MCP rewrites credit the caller through DocumentTarget.author; Planner and other server edits have no author.
  • Recording happens after acknowledgement and outside the fenced commit; failures are logged and never block an edit.
  • GET /api/channels/:id/versions (up to 200 sessions plus creation) and GET /api/channels/:id/versions/:versionId, with the same pull-access check as GET /api/channels/:id. The store gains previous and summarize.

Not done yet / questions

  • Diff highlighting between a session and the one before it.
  • Phone and compact layouts. The views bar only exists in split mode, so History has no trigger there yet; the ticks hide below the width that fits them.
  • Retention. Every session stores a full copy of the MDX.
  • History starts at deploy. No backfill.
  • Restore placement. A projection whose anchor block is gone in the earlier version lands at the start or after the previous projection.
  • Default HISTORY_MODEL. It falls back to MODEL; we should pick a small default per harness.

Review fixes (rebase onto main)

Rebased onto main (migrations renumbered to 024/025 after main's 021–023; StaticPlanEditor keeps main's className alongside label/presentation; MCP title receipts only finalize when a rewrite has a receipt). Then:

  • Restore no longer overwrites the author's previous session when it falls inside the 10-minute window (contract test added).
  • Summary prompts fit their bound instead of failing deterministically on busy documents (test added).
  • Finished summary jobs report unavailable, so History stops showing skeletons for rows the same key will never retry.
  • History's viewed version is tied to its document, panels remount per document, and a restore that settles after you move on is ignored.
  • Escape and focus handling for History and the Contents peek; restore errors use role="alert"; buttons use btn-sm (main retired btn-compact).

Still open: any reader can trigger a summary job billed to the Planner owner (idempotent per session set, no per-channel throttle).

Checks

  • bun run types, bun run ci (dprint, oxlint, tokens, design contract, design record, impeccable) pass.
  • bun test: 6,195 tests; the only failures are connector/installer tests that also fail on main from the same /tmp worktree.
  • bun run test:postgres passes, including the version contract (summaries, previous) and the migration list.
  • New tests: edit.test.ts (restoration), routes.test.ts (restore and summaries routes), version-summary.test.ts, document-history.test.ts (ages, exact times, gaps).
  • Verified in Chromium against a local server with fake GitHub: ticks and peek, sidebar open/close, scroll fade, delayed detail, restore end to end (decisions intact), stored summaries, and skeletons resolving (pending state simulated, since the fake Planner was unavailable).
  • Not run locally: the Chromium E2E suite.

🤖 Generated with Claude Code

@MaggieAppleton
MaggieAppleton marked this pull request as ready for review October 15, 2026 11:53
MaggieAppleton and others added 2 commits October 16, 2026 15:13
Contents shows margin ticks that peek each section's prose. History opens
a sidebar over the document listing editing sessions one line each, with
model-written summaries; choosing a session shows a read-only copy under a
version bar that can restore it.
A restore opens its own history session, so the session before it stays
restorable. Summary prompts trim each session's changes until twenty fit,
and a finished summary job reports unavailable instead of pending.
History's viewed version belongs to its document, a late restore result is
ignored, and History and the Contents peek close on Escape with focus kept.
Buttons use the shared btn-sm size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaggieAppleton
MaggieAppleton merged commit e45f0ed into main Oct 16, 2026
24 checks passed
@MaggieAppleton
MaggieAppleton deleted the activity-and-contents branch October 16, 2026 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants