Skip to content

M6: History, diff, revert, changes, feeds#15

Merged
simonbc merged 6 commits into
mainfrom
m6-history
May 15, 2026
Merged

M6: History, diff, revert, changes, feeds#15
simonbc merged 6 commits into
mainfrom
m6-history

Conversation

@simonbc
Copy link
Copy Markdown
Owner

@simonbc simonbc commented May 15, 2026

Summary

Port the history-flavored UI: per-page revision list, between-revision diff, revert / undelete actions, site-wide changes feed, and RSS 2.0 + JSON Feed endpoints.

Decisions

  • Diff URL shape: ?r=A&r=B multi-value (matches the 2007 site for URL compatibility).
  • List page size: 20 (matches original).
  • Feed formats: RSS 2.0 + JSON Feed (jsonfeed.org). Atom skipped.

Test plan

  • Step 1: DB helpers (get_revisions, get_revisions_count, get_changes)
  • Step 2: GET /<page>?m=history
  • Step 3: GET /<page>?m=diff
  • Step 4: POST /<page>?m=revert + POST /<page>?m=undelete
  • Step 5: GET /site/changes
  • Step 6: RSS 2.0 + JSON Feed (?m=history_rss / ?m=history_json per-page; /site/changes.rss + /site/changes.json site-wide)

🤖 Generated with Claude Code

Three additions:
- get_revisions(page_id, before, limit) — paginated list of a page's
  revisions, newest first. The `before` cursor takes a revision number
  and returns the next page back through history.
- get_revisions_count(page_id) — total non-sentinel revisions; used
  for the history page-count math.
- get_changes(site_id, before, limit) — joined site-wide activity feed
  across all pages. The 2007 helper returned `[]` with an "impossible
  query" comment; the query itself works fine on modern Postgres.

`before` for get_changes is keyed by revision id (not revision number)
because the order is by created-time across many pages, where revision
numbers reset per page.

First commit of M6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@simonbc simonbc changed the title M6: History, diff, revert, changes, atom feeds M6: History, diff, revert, changes, feeds May 15, 2026
simonbc and others added 5 commits May 15, 2026 17:03
Adds the page-history view: a paginated list of revisions (20 per
page, newest first) with revision number, timestamp, and the diff
summary stored on each revision. The "Older →" link is cursor-based
on the oldest revision in the current page rather than offset-based,
so pages stay stable as new revisions land.

m=history slots into the existing mode dispatch in page.view and maps
onto the auth-matrix `view_revision` action — same as `?r=N` — so
public sites gate it behind a sign-in while open sites allow anonymous
browsing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renders a between-revision diff using the better_diff renderer from
jottit/diff.py. Revision selection follows the 2007 URL shape:

- ?m=diff → latest vs previous
- ?m=diff&r=N → revision N vs N-1
- ?m=diff&r=A&r=B → between A and B (ordering normalized so ?r=3&r=1
  and ?r=1&r=3 produce the same output)

More than two `r` values, non-integer values, and unknown revision
numbers all return 400. Like history, the action maps to
view_revision so public sites gate it behind a sign-in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Revert restores a page's content to an earlier revision by writing a
new revision (so history stays linear). If the page is deleted, the
revert path also undeletes it. Reverting to a revision whose content
already matches the latest is a no-op.

Undelete restores a soft-deleted page and appends a "<em>Delete
undone.</em>" revision that re-promotes the pre-delete content as the
new latest. The deleted sentinel revision stays in history.

Both POST endpoints go through the auth matrix as `edit` and the
unified mode dispatch in page.view.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Site-wide activity feed: every revision across every page on the site,
newest first, paginated 20 at a time. The "Older →" link uses the
revision id as the cursor since revisions are ordered by created-time
across pages (revision numbers reset per page so they can't double as
a global cursor).

Deleted pages still appear in the feed with an "(page since deleted)"
mark so the timeline remains continuous after a delete.

Auth maps to view_revision — public sites gate it behind a sign-in,
open sites allow anonymous reads.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four feeds:
- /<page>?m=history_rss + /<page>?m=history_json (per-page history)
- /site/changes.rss + /site/changes.json (site-wide recent changes)

RSS templates live under jottit/templates/feeds/ as `.rss.xml`. JSON
Feed is built as a dict and jsonify'd — no template needed. Both
content types are returned with the right MIME (`application/rss+xml`,
`application/feed+json`).

Self-link URLs are absolute and respect the current request: per-page
links live under the site's subdomain or secret-URL prefix as
appropriate.

Auth-wise feeds map to the loose "view" action since feed readers
typically don't authenticate — that means private sites still gate
them behind a sign-in, but public sites expose them anonymously
(matches the original 2007 behavior for history_atom / changes_atom).

The replaced `/site/changes.atom` route is gone; `changes_atom` view
stub is removed.

Closes out M6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@simonbc simonbc merged commit ce5860b into main May 15, 2026
1 check passed
@simonbc simonbc deleted the m6-history branch May 15, 2026 15:16
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.

1 participant