diff --git a/ui/src/components/IssueDocumentsSection.tsx b/ui/src/components/IssueDocumentsSection.tsx
index c3f0a9d783f..44de75192af 100644
--- a/ui/src/components/IssueDocumentsSection.tsx
+++ b/ui/src/components/IssueDocumentsSection.tsx
@@ -12,6 +12,7 @@ import { useLocation } from "@/lib/router";
import { ApiError } from "../api/client";
import { issuesApi } from "../api/issues";
import { useAutosaveIndicator } from "../hooks/useAutosaveIndicator";
+import { deriveDocumentRevisionState } from "../lib/document-revisions";
import { queryKeys } from "../lib/queryKeys";
import { cn, relativeTime } from "../lib/utils";
import { MarkdownBody } from "./MarkdownBody";
@@ -787,7 +788,9 @@ export function IssueDocumentsSection({
const activeDraft = draft?.key === doc.key && !draft.isNew ? draft : null;
const activeConflict = documentConflict?.key === doc.key ? documentConflict : null;
const isFolded = foldedDocumentKeys.includes(doc.key);
- const revisionHistory = getDocumentRevisions(doc.key);
+ const revisionState = deriveDocumentRevisionState(doc, getDocumentRevisions(doc.key));
+ const revisionHistory = revisionState.revisions;
+ const currentRevision = revisionState.currentRevision;
const selectedRevisionId = selectedRevisionIds[doc.key] ?? null;
const selectedHistoricalRevision = selectedRevisionId
? revisionHistory.find((revision) => revision.id === selectedRevisionId) ?? null
@@ -795,10 +798,10 @@ export function IssueDocumentsSection({
const isHistoricalPreview = Boolean(selectedHistoricalRevision);
const displayedTitle = selectedHistoricalRevision
? selectedHistoricalRevision.title ?? ""
- : activeDraft?.title ?? doc.title ?? "";
- const displayedBody = selectedHistoricalRevision?.body ?? activeDraft?.body ?? doc.body;
- const displayedRevisionNumber = selectedHistoricalRevision?.revisionNumber ?? doc.latestRevisionNumber;
- const displayedUpdatedAt = selectedHistoricalRevision?.createdAt ?? doc.updatedAt;
+ : activeDraft?.title ?? currentRevision.title ?? "";
+ const displayedBody = selectedHistoricalRevision?.body ?? activeDraft?.body ?? currentRevision.body;
+ const displayedRevisionNumber = selectedHistoricalRevision?.revisionNumber ?? currentRevision.revisionNumber;
+ const displayedUpdatedAt = selectedHistoricalRevision?.createdAt ?? currentRevision.createdAt;
const showTitle = !isPlanKey(doc.key) && !!displayedTitle.trim() && !titlesMatchKey(displayedTitle, doc.key);
const canVoteOnDocument = Boolean(doc.latestRevisionId && doc.updatedByAgentId && !doc.updatedByUserId && onVote);
@@ -848,9 +851,9 @@ export function IssueDocumentsSection({
{revisionMenuOpenKey === doc.key && isFetchingDocumentRevisions && revisionHistory.length === 0 ? (
Loading revisions...
) : revisionHistory.length > 0 ? (
-
+
{revisionHistory.map((revision) => {
- const isCurrentRevision = revision.id === doc.latestRevisionId;
+ const isCurrentRevision = revision.id === currentRevision.id;
return (
= {}): IssueDocument {
+ return {
+ id: "document-1",
+ companyId: "company-1",
+ issueId: "issue-1",
+ key: "plan",
+ title: "Plan",
+ format: "markdown",
+ body: "# Current plan",
+ latestRevisionId: "revision-2",
+ latestRevisionNumber: 2,
+ createdByAgentId: "agent-1",
+ createdByUserId: null,
+ updatedByAgentId: "agent-1",
+ updatedByUserId: null,
+ createdAt: new Date("2026-04-10T15:00:00.000Z"),
+ updatedAt: new Date("2026-04-10T16:00:00.000Z"),
+ ...overrides,
+ };
+}
+
+function createRevision(overrides: Partial = {}): DocumentRevision {
+ return {
+ id: "revision-1",
+ companyId: "company-1",
+ documentId: "document-1",
+ issueId: "issue-1",
+ key: "plan",
+ revisionNumber: 1,
+ title: "Plan",
+ format: "markdown",
+ body: "# Revision body",
+ changeSummary: null,
+ createdByAgentId: "agent-1",
+ createdByUserId: null,
+ createdAt: new Date("2026-04-10T15:00:00.000Z"),
+ ...overrides,
+ };
+}
+
+describe("deriveDocumentRevisionState", () => {
+ it("sorts fetched revisions newest-first even when the API payload is out of order", () => {
+ const state = deriveDocumentRevisionState(createDocument(), [
+ createRevision({ id: "revision-1", revisionNumber: 1, createdAt: new Date("2026-04-10T15:00:00.000Z") }),
+ createRevision({ id: "revision-2", revisionNumber: 2, body: "# Current plan", createdAt: new Date("2026-04-10T16:00:00.000Z") }),
+ ]);
+
+ expect(state.currentRevision.id).toBe("revision-2");
+ expect(state.revisions.map((revision) => revision.id)).toEqual(["revision-2", "revision-1"]);
+ });
+
+ it("keeps the latest document revision current when the revision history cache is stale", () => {
+ const state = deriveDocumentRevisionState(createDocument(), [
+ createRevision({ id: "revision-1", revisionNumber: 1, body: "# Original plan" }),
+ ]);
+
+ expect(state.currentRevision.id).toBe("revision-2");
+ expect(state.currentRevision.body).toBe("# Current plan");
+ expect(state.revisions.map((revision) => revision.id)).toEqual(["revision-2", "revision-1"]);
+ });
+
+ it("trusts the fetched revision history when it is newer than the document summary cache", () => {
+ const staleDocument = createDocument({
+ body: "# Original plan",
+ latestRevisionId: "revision-1",
+ latestRevisionNumber: 1,
+ updatedAt: new Date("2026-04-10T15:00:00.000Z"),
+ });
+
+ const state = deriveDocumentRevisionState(staleDocument, [
+ createRevision({ id: "revision-2", revisionNumber: 2, body: "# Current plan", createdAt: new Date("2026-04-10T16:00:00.000Z") }),
+ createRevision({ id: "revision-1", revisionNumber: 1, body: "# Original plan", createdAt: new Date("2026-04-10T15:00:00.000Z") }),
+ ]);
+
+ expect(state.currentRevision.id).toBe("revision-2");
+ expect(state.currentRevision.body).toBe("# Current plan");
+ expect(state.revisions.map((revision) => revision.id)).toEqual(["revision-2", "revision-1"]);
+ });
+});
diff --git a/ui/src/lib/document-revisions.ts b/ui/src/lib/document-revisions.ts
new file mode 100644
index 00000000000..5759d4af300
--- /dev/null
+++ b/ui/src/lib/document-revisions.ts
@@ -0,0 +1,72 @@
+import type { DocumentRevision, IssueDocument } from "@paperclipai/shared";
+
+type DocumentRevisionState = {
+ currentRevision: DocumentRevision;
+ revisions: DocumentRevision[];
+};
+
+function toTimestamp(value: Date | string | null | undefined) {
+ if (!value) return 0;
+ const timestamp = new Date(value).getTime();
+ return Number.isNaN(timestamp) ? 0 : timestamp;
+}
+
+function sortRevisionsDescending(revisions: DocumentRevision[]) {
+ return [...revisions].sort((a, b) => {
+ if (a.revisionNumber !== b.revisionNumber) {
+ return b.revisionNumber - a.revisionNumber;
+ }
+ const createdAtDelta = toTimestamp(b.createdAt) - toTimestamp(a.createdAt);
+ if (createdAtDelta !== 0) return createdAtDelta;
+ return b.id.localeCompare(a.id);
+ });
+}
+
+function createCurrentRevisionSnapshot(document: IssueDocument): DocumentRevision {
+ return {
+ id: document.latestRevisionId ?? `${document.id}-latest`,
+ companyId: document.companyId,
+ documentId: document.id,
+ issueId: document.issueId,
+ key: document.key,
+ revisionNumber: document.latestRevisionNumber,
+ title: document.title,
+ format: document.format,
+ body: document.body,
+ changeSummary: null,
+ createdByAgentId: document.updatedByAgentId ?? document.createdByAgentId,
+ createdByUserId: document.updatedByUserId ?? document.createdByUserId,
+ createdAt: document.updatedAt,
+ };
+}
+
+export function deriveDocumentRevisionState(
+ document: IssueDocument,
+ revisions: DocumentRevision[],
+): DocumentRevisionState {
+ const sortedRevisions = sortRevisionsDescending(revisions);
+ const currentSnapshot = createCurrentRevisionSnapshot(document);
+ const highestFetchedRevision = sortedRevisions[0] ?? null;
+ const documentAppearsStale = Boolean(
+ highestFetchedRevision && highestFetchedRevision.revisionNumber > document.latestRevisionNumber,
+ );
+
+ const currentRevision = documentAppearsStale
+ ? highestFetchedRevision!
+ : sortedRevisions.find((revision) => revision.id === document.latestRevisionId) ?? currentSnapshot;
+
+ const revisionsWithCurrent = sortRevisionsDescending([currentRevision, ...sortedRevisions]);
+ const dedupedRevisions: DocumentRevision[] = [];
+ const seenRevisionIds = new Set();
+
+ for (const revision of revisionsWithCurrent) {
+ if (seenRevisionIds.has(revision.id)) continue;
+ seenRevisionIds.add(revision.id);
+ dedupedRevisions.push(revision);
+ }
+
+ return {
+ currentRevision,
+ revisions: dedupedRevisions,
+ };
+}