feat: page version history UI (#622) - #705
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JdhTP8HYaiUka96hC2pXnE
Add get_page_history and diff_page_revisions on a new history module, gated on can_contribute_to_space. History is reconstructed from the time-ordered published-revision set (is_working=0, is_overlay=0), not a parent_revision walk, so concurrently-merged revisions are never skipped. Add a composite index (wiki_space, is_working, is_overlay, created_at) so the query is index-covered, plus a search_index on Wiki Revision Item.doc_key. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JdhTP8HYaiUka96hC2pXnE
Add PageHistory.vue (master-detail: revision list with change-type badges, author avatars, relative time, CR links; diff pane with split/unified toggle and empty/loading/error states), a nested PageHistory route, and a contribute-gated 'View history' item in the page kebab menu. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JdhTP8HYaiUka96hC2pXnE
Backend unit tests for classification, no-op dedup, the concurrent-merge guard (verified by temp-revert), diff base/predecessor resolution, and the contribute permission gate. Playwright e2e drives kebab -> history -> diff. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JdhTP8HYaiUka96hC2pXnE
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JdhTP8HYaiUka96hC2pXnE
Confidence Score: 4/5The history diff endpoint needs a scope check before merging.
wiki/frappe_wiki/doctype/wiki_revision/history.py
|
| head = _resolve_page_at_revision(revision, doc_key) | ||
| base = _resolve_page_at_revision(base_revision, doc_key) if base_revision else None |
There was a problem hiding this comment.
When diff_page_revisions is called directly, revision and base_revision can name working or overlay revisions because only page is checked against the resolved space. That exposes draft revision content through a published-history endpoint; validate both revision IDs against the same wiki_space with is_working=0 and is_overlay=0 before materializing items.
Why?
Issue #622 asks for a UI to browse a wiki page's version history. The v3 revision data model already records every published state, but there was no read endpoint or UI to surface it per page.
What?
An editor-facing, read-only history browser for a single page:
/spaces/:spaceId/page/:pageId/history.Read-only browse only — no restore/revert in this iteration.
How?
Two whitelisted endpoints in a new
wiki/frappe_wiki/doctype/wiki_revision/history.py:get_page_history(page)anddiff_page_revisions(page, revision, base_revision?), both gated oncan_contribute_to_space.is_working=0 AND is_overlay=0), not by walkingparent_revision— a three-way merge points its parent at the CR base, so a parent-walk would skip concurrently-merged revisions. An entry is emitted only when the page actually changed (content hash / tracked metadata), so no-op churn is deduped.diff_page_revisionsreturns the same shape asdiff_change_request(scope="page"), soDiffViewer.vueconsumes it unchanged.Performance: composite index
(wiki_space, is_working, is_overlay, created_at)onWiki Revisionplus asearch_indexonWiki Revision Item.doc_key.EXPLAINconfirms the history query is index-covered with no filesort.Tests: 7 backend unit tests (classification, no-op dedup, the concurrent-merge guard, diff/predecessor resolution, permission gate) and a Playwright e2e driving kebab → history → diff.
Spec:
specs/page_version_history_ui.md.🤖 Generated with Claude Code