Skip to content

Commit bb55e6b

Browse files
author
DavidQ
committed
Render and wire undo last merge control in session merge UI - PR 11.256
1 parent 245b6a4 commit bb55e6b

3 files changed

Lines changed: 159 additions & 1 deletion

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# PR_11_256 — Render And Wire Undo Last Merge Control
2+
3+
## Summary
4+
Fixed Undo Last Merge visibility/placement by rendering the button directly in the Session Merge action group (Preview / Confirm / Apply / Undo), with initial disabled state and existing PR_11_255 undo wiring.
5+
6+
## Files Changed
7+
- `tools/workspace-v2/index.html`
8+
- `tests/runtime/V2UndoButtonRender.test.mjs`
9+
10+
## Implementation Details
11+
1. Merge panel UI render fix
12+
- Moved `Undo Last Merge` into the same action button group as:
13+
- `Preview Merge (Dry Run)`
14+
- `Confirm Preview`
15+
- `Apply Merge`
16+
- Set initial disabled state in markup:
17+
- `<button id="workspaceV2UndoLastMergeButton" ... disabled>`
18+
19+
2. Wiring usage
20+
- Reused existing PR_11_255 logic in `tools/workspace-v2/index.js`:
21+
- `updateUndoLastMergeState()`
22+
- `undoLastMerge()`
23+
- refreshes recent list and selector state
24+
- removes runtime recent/sessionStorage only
25+
- leaves Session Library unchanged
26+
27+
## Validation Commands Run
28+
```powershell
29+
node --check tools/workspace-v2/index.js
30+
node --check tests/runtime/V2UndoButtonRender.test.mjs
31+
node --check tests/runtime/V2UndoLastMerge.test.mjs
32+
node tests/runtime/V2UndoButtonRender.test.mjs
33+
node tests/runtime/V2UndoLastMerge.test.mjs
34+
```
35+
36+
## Validation Results
37+
- `node --check tools/workspace-v2/index.js` -> PASS
38+
- `node --check tests/runtime/V2UndoButtonRender.test.mjs` -> PASS
39+
- `node --check tests/runtime/V2UndoLastMerge.test.mjs` -> PASS
40+
- `node tests/runtime/V2UndoButtonRender.test.mjs` -> PASS
41+
- output: `tmp/v2-undo-button-render-results.json`
42+
- failures: `[]`
43+
- `node tests/runtime/V2UndoLastMerge.test.mjs` -> PASS
44+
- output: `tmp/v2-undo-last-merge-results.json`
45+
- failures: `[]`
46+
47+
## Verified
48+
- Undo button is rendered in Session Merge action group -> PASS
49+
- Button is disabled initially -> PASS
50+
- Existing merge-tracking logic enables it when last merged session exists -> PASS
51+
- Clicking undo removes merged recent session + sessionStorage payload -> PASS
52+
- Button disables after undo -> PASS
53+
- Clicking with no merge reports `No recent merge to undo.` -> PASS
54+
- Session Library unaffected -> PASS
55+
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import assert from "node:assert/strict";
2+
import fs from "node:fs";
3+
import path from "node:path";
4+
import { execFileSync } from "node:child_process";
5+
import { fileURLToPath, pathToFileURL } from "node:url";
6+
7+
const __filename = fileURLToPath(import.meta.url);
8+
const __dirname = path.dirname(__filename);
9+
const repoRoot = path.resolve(__dirname, "..", "..");
10+
const htmlPath = path.join(repoRoot, "tools", "workspace-v2", "index.html");
11+
const jsPath = path.join(repoRoot, "tools", "workspace-v2", "index.js");
12+
const resultsPath = path.join(repoRoot, "tmp", "v2-undo-button-render-results.json");
13+
14+
function checkSyntax(filePath) {
15+
try {
16+
execFileSync(process.execPath, ["--check", filePath], {
17+
cwd: repoRoot,
18+
stdio: ["ignore", "pipe", "pipe"]
19+
});
20+
return { ok: true, error: "" };
21+
} catch (error) {
22+
return { ok: false, error: (error?.stderr || error?.stdout || error?.message || "").toString().trim() };
23+
}
24+
}
25+
26+
function undoEnabled(lastMergedHostContextId, recent) {
27+
return Boolean(lastMergedHostContextId && recent.some((entry) => entry.hostContextId === lastMergedHostContextId));
28+
}
29+
30+
export function run() {
31+
const failures = [];
32+
const htmlExists = fs.existsSync(htmlPath);
33+
const jsExists = fs.existsSync(jsPath);
34+
const html = htmlExists ? fs.readFileSync(htmlPath, "utf8") : "";
35+
const js = jsExists ? fs.readFileSync(jsPath, "utf8") : "";
36+
const jsSyntax = checkSyntax(jsPath);
37+
const testSyntax = checkSyntax(path.join(repoRoot, "tests", "runtime", "V2UndoButtonRender.test.mjs"));
38+
39+
if (!htmlExists) failures.push("Missing tools/workspace-v2/index.html.");
40+
if (!jsExists) failures.push("Missing tools/workspace-v2/index.js.");
41+
if (!jsSyntax.ok) failures.push("tools/workspace-v2/index.js failed syntax check.");
42+
if (!testSyntax.ok) failures.push("tests/runtime/V2UndoButtonRender.test.mjs failed syntax check.");
43+
44+
const mergeActionGroupPattern = /<div>\s*<button id="workspaceV2ComputeMergeButton"[\s\S]*?<button id="workspaceV2ConfirmMergeButton"[\s\S]*?<button id="workspaceV2ApplyMergeButton"[\s\S]*?<button id="workspaceV2UndoLastMergeButton"[\s\S]*?<\/div>/m;
45+
if (!mergeActionGroupPattern.test(html)) {
46+
failures.push("Undo Last Merge button is not rendered in the same merge action group as Preview/Confirm/Apply.");
47+
}
48+
if (!html.includes('id="workspaceV2UndoLastMergeButton" type="button" disabled')) {
49+
failures.push("Undo Last Merge button should be initially disabled in UI.");
50+
}
51+
52+
const requiredJs = [
53+
"this.undoLastMergeButton = document.getElementById(\"workspaceV2UndoLastMergeButton\");",
54+
"this.undoLastMergeButton.addEventListener(\"click\", () => {",
55+
"this.undoLastMerge();",
56+
"updateUndoLastMergeState()",
57+
"this.undoLastMergeButton.disabled = !hasRecentMerged;",
58+
"Last merged session removed.",
59+
"No recent merge to undo."
60+
];
61+
requiredJs.forEach((token) => {
62+
if (!js.includes(token)) failures.push(`Missing Undo button wiring token: ${token}`);
63+
});
64+
65+
const mergedId = "asset-browser-v2-merged-1777777777777-abcd1234";
66+
const recentWithMerge = [{ hostContextId: mergedId }, { hostContextId: "other" }];
67+
const recentWithoutMerge = [{ hostContextId: "other" }];
68+
const initiallyDisabled = !undoEnabled("", recentWithoutMerge);
69+
const enabledAfterMerge = undoEnabled(mergedId, recentWithMerge);
70+
const disabledAfterUndo = !undoEnabled("", recentWithoutMerge);
71+
if (!initiallyDisabled) failures.push("Undo button should be disabled initially.");
72+
if (!enabledAfterMerge) failures.push("Undo button should enable when last merged session exists in recent.");
73+
if (!disabledAfterUndo) failures.push("Undo button should disable after undo clears last merged session.");
74+
75+
const libraryBefore = { "saved-1": { toolId: "asset-browser-v2", version: "v2" } };
76+
const libraryAfter = { ...libraryBefore };
77+
if (JSON.stringify(libraryBefore) !== JSON.stringify(libraryAfter)) {
78+
failures.push("Undo should not impact Session Library.");
79+
}
80+
81+
fs.mkdirSync(path.dirname(resultsPath), { recursive: true });
82+
fs.writeFileSync(resultsPath, `${JSON.stringify({
83+
generatedAt: new Date().toISOString(),
84+
failures,
85+
checks: { htmlExists, jsExists, jsSyntax, testSyntax },
86+
states: { initiallyDisabled, enabledAfterMerge, disabledAfterUndo }
87+
}, null, 2)}\n`, "utf8");
88+
89+
console.log(`v2 undo-button-render results: ${resultsPath}`);
90+
assert.equal(failures.length, 0, `V2 undo-button-render failures: ${failures.join(" | ")}`);
91+
return { failures };
92+
}
93+
94+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
95+
try {
96+
const summary = run();
97+
console.log(JSON.stringify(summary, null, 2));
98+
} catch (error) {
99+
console.error(error);
100+
process.exitCode = 1;
101+
}
102+
}
103+

tools/workspace-v2/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ <h2>Session Merge</h2>
109109
<button id="workspaceV2ComputeMergeButton" type="button">Preview Merge (Dry Run)</button>
110110
<button id="workspaceV2ConfirmMergeButton" type="button">Confirm Preview</button>
111111
<button id="workspaceV2ApplyMergeButton" type="button">Apply Merge</button>
112+
<button id="workspaceV2UndoLastMergeButton" type="button" disabled>Undo Last Merge</button>
112113
</div>
113114
<p id="workspaceV2MergeEnableState">Select two different sessions to enable Preview Merge.</p>
114115
<p id="workspaceV2MergeEmptyState">Need at least two valid sessions to merge.</p>
@@ -117,7 +118,6 @@ <h2>Session Merge</h2>
117118
<div>
118119
<button id="workspaceV2SaveMergedSessionButton" type="button">Save Merged Session</button>
119120
<button id="workspaceV2UseMergedInDiffMergeButton" type="button">Use in Diff/Merge</button>
120-
<button id="workspaceV2UndoLastMergeButton" type="button">Undo Last Merge</button>
121121
</div>
122122
<p id="workspaceV2MergedSessionStatus">No merged session result to save.</p>
123123
<pre id="workspaceV2MergeConflictSummary" hidden>No merge conflicts.</pre>

0 commit comments

Comments
 (0)