Skip to content

Commit 4ea928b

Browse files
fix(history): anchor "current" badge to newest run, not html equality
After Restore, commitBase appends a new version whose html equals the restored row's html, so a pure r.html === activeHtml check flagged both rows as current and disabled Restore on the genuinely-historical one. Anchor on runs[0].id (newest, per listRuns ordering) and keep the content guard so the badge still clears when the editor drifts. Extracted to isCurrentRun helper for unit coverage of the restore path, byte-identical older versions, editor drift, and empty runs.
1 parent c9d2618 commit 4ea928b

3 files changed

Lines changed: 78 additions & 1 deletion

File tree

next/src/components/history-pane.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
MAX_VERSIONS_PER_TASK,
1010
type RunRecord,
1111
} from "@/lib/history/db";
12+
import { isCurrentRun } from "@/lib/history/is-current";
1213
import { previewHtml } from "@/lib/extract-html";
1314
import { relativeTime, useMounted } from "@/lib/use-autosave";
1415
import { useT } from "@/lib/i18n";
@@ -214,7 +215,7 @@ function HistoryList({
214215
<HistoryCard
215216
key={r.id}
216217
run={r}
217-
isCurrent={r.html === activeHtml}
218+
isCurrent={isCurrentRun(r, runs, activeHtml)}
218219
mounted={mounted}
219220
onCompare={() => onCompare(r)}
220221
onRestore={() => onRestore(r)}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { describe, expect, it } from "vitest";
2+
import type { RunRecord } from "./db";
3+
import { isCurrentRun } from "./is-current";
4+
5+
function record(version: number, html: string): RunRecord {
6+
return {
7+
id: `task-a__${String(version).padStart(6, "0")}`,
8+
taskId: "task-a",
9+
version,
10+
html,
11+
content: `content ${version}`,
12+
ts: 1_700_000_000_000 + version,
13+
stats: { outputBytes: html.length, deltaCount: 0 },
14+
templateId: "article-magazine",
15+
};
16+
}
17+
18+
describe("isCurrentRun", () => {
19+
it("flags the newest run when its html matches the editor", () => {
20+
const v2 = record(2, "<main>v2</main>");
21+
const v1 = record(1, "<main>v1</main>");
22+
const runs = [v2, v1];
23+
24+
expect(isCurrentRun(v2, runs, v2.html)).toBe(true);
25+
expect(isCurrentRun(v1, runs, v2.html)).toBe(false);
26+
});
27+
28+
it("does not flag an older byte-identical run as current after restore", () => {
29+
// After restoring v1, commitBase appends a fresh v3 whose html equals v1's html.
30+
// The old isCurrent (pure content equality) wrongly flagged BOTH v3 and v1
31+
// as current and disabled Restore on the genuinely-historical v1.
32+
const sharedHtml = "<main>v1</main>";
33+
const v3 = record(3, sharedHtml);
34+
const v2 = record(2, "<main>v2</main>");
35+
const v1 = record(1, sharedHtml);
36+
const runs = [v3, v2, v1];
37+
38+
expect(isCurrentRun(v3, runs, sharedHtml)).toBe(true);
39+
expect(isCurrentRun(v1, runs, sharedHtml)).toBe(false);
40+
expect(isCurrentRun(v2, runs, sharedHtml)).toBe(false);
41+
});
42+
43+
it("drops the current flag when the editor html drifts from the newest run", () => {
44+
const v2 = record(2, "<main>v2</main>");
45+
const v1 = record(1, "<main>v1</main>");
46+
const runs = [v2, v1];
47+
48+
// User edited the editor after the last commit — no row is live anymore.
49+
expect(isCurrentRun(v2, runs, "<main>v2 edited</main>")).toBe(false);
50+
expect(isCurrentRun(v1, runs, "<main>v2 edited</main>")).toBe(false);
51+
});
52+
53+
it("returns false against an empty runs list", () => {
54+
const orphan = record(1, "<main>v1</main>");
55+
expect(isCurrentRun(orphan, [], orphan.html)).toBe(false);
56+
});
57+
});

next/src/lib/history/is-current.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { RunRecord } from "./db";
2+
3+
/**
4+
* A run is "current" only when it is the newest row *and* its html still
5+
* matches the editor. Anchoring on the newest id (not just html equality)
6+
* is what makes restore correct: `commitBase` appends a new row whose html
7+
* equals the restored row's html, so a pure content check would flag both
8+
* as current and disable Restore on the older identical version.
9+
*
10+
* `runs` must be the listRuns ordering (newest first); `runs[0]` is the
11+
* live version.
12+
*/
13+
export function isCurrentRun(
14+
run: RunRecord,
15+
runs: RunRecord[],
16+
activeHtml: string,
17+
): boolean {
18+
return run.id === runs[0]?.id && run.html === activeHtml;
19+
}

0 commit comments

Comments
 (0)