Skip to content

feat: page version history UI (#622) - #705

Draft
NagariaHussain wants to merge 5 commits into
developfrom
feat/version-history-ui
Draft

feat: page version history UI (#622)#705
NagariaHussain wants to merge 5 commits into
developfrom
feat/version-history-ui

Conversation

@NagariaHussain

Copy link
Copy Markdown
Member

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:

  • View history in the page kebab menu (gated on contribute capability) opens /spaces/:spaceId/page/:pageId/history.
  • Left: a chronological list of every published revision that changed the page, newest first — change-type badge (added / edited / renamed / deleted), author, relative time, and a link to the originating Change Request (or the revision message for git-sync / bootstrap revisions).
  • Right: a content diff of the selected revision against its predecessor, with a split/unified toggle. Newest revision auto-selects.

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) and diff_page_revisions(page, revision, base_revision?), both gated on can_contribute_to_space.
  • History is reconstructed from the time-ordered published-revision set (is_working=0 AND is_overlay=0), not by walking parent_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_revisions returns the same shape as diff_change_request(scope="page"), so DiffViewer.vue consumes it unchanged.

Performance: composite index (wiki_space, is_working, is_overlay, created_at) on Wiki Revision plus a search_index on Wiki Revision Item.doc_key. EXPLAIN confirms 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

NagariaHussain and others added 5 commits July 7, 2026 11:50
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
@NagariaHussain

Copy link
Copy Markdown
Member Author

@greptile-apps

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

The history diff endpoint needs a scope check before merging.

  • The UI and history list paths are covered by the changed code and tests.
  • The diff API can materialize caller-supplied revisions outside the published history set.
  • The affected path can expose draft content through an editor-facing read endpoint.

wiki/frappe_wiki/doctype/wiki_revision/history.py

Security Review

The new diff endpoint accepts caller-supplied revision IDs without enforcing the published-revision scope, so draft revision content can be exposed through the history API.

Reviews (1): Last reviewed commit: "docs(history): reconcile spec — implemen..." | Re-trigger Greptile

Comment on lines +74 to +75
head = _resolve_page_at_revision(revision, doc_key)
base = _resolve_page_at_revision(base_revision, doc_key) if base_revision else None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Unscoped Revision Diff

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.

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