Skip to content

Commit 5ce7908

Browse files
author
DavidQ
committed
Add clear merge result status and summary feedback - PR 11.258
1 parent 1469bbc commit 5ce7908

4 files changed

Lines changed: 317 additions & 0 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# PR_11_258 Merge Result Summary Report
2+
3+
## Scope
4+
Workspace V2 Session Merge UI only.
5+
6+
## Files Changed
7+
- tools/workspace-v2/index.html
8+
- tools/workspace-v2/index.js
9+
- tests/runtime/V2MergeResultSummary.test.mjs
10+
11+
## Implementation Summary
12+
- Added a compact merge summary region in the Session Merge panel: `#workspaceV2MergeResultSummary`.
13+
- Added merge summary wiring in Workspace V2 merge flow:
14+
- Preview success summary: source id, target id, toolId, added/updated/unchanged/conflict counts.
15+
- Confirm summary: `Preview confirmed. Apply Merge is ready.`
16+
- Apply success summary: merged session id, toolId, timestamp, added/updated/unchanged counts.
17+
- Undo success summary: removed merged session id.
18+
- Added stale/blocked summary handling:
19+
- Blocked preview paths replace stale success summary with the blocking reason.
20+
- Selection changes that stale a preview mark summary stale and keep existing enable-state guards.
21+
- Kept raw merge JSON preview output visible in `#workspaceV2MergeOutput`.
22+
23+
## Validation Commands
24+
1. `node --check tools/workspace-v2/index.js`
25+
- Result: PASS
26+
2. `node --check tests/runtime/V2MergeResultSummary.test.mjs`
27+
- Result: PASS
28+
3. `node tests/runtime/V2MergeResultSummary.test.mjs`
29+
- Result: PASS
30+
- Output file: `tmp/v2-merge-result-summary-results.json`
31+
- Failures: `[]`
32+
33+
## Executable Validation Coverage
34+
- preview summary appears after preview
35+
- confirm status appears after confirm
36+
- apply summary appears after apply
37+
- undo summary appears after undo
38+
- blocked preview clears stale success summary
39+
- selection changes mark stale old preview summary
40+
- raw JSON preview remains visible
41+
42+
## Full Samples Smoke Decision
43+
- Skipped full samples smoke test.
44+
- Reason: this PR is scoped to Workspace V2 merge UI/status wiring and a targeted runtime test; no shared sample infrastructure or sample files were modified.
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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-merge-result-summary-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 previewSummary(preview) {
27+
const addedCount = Object.keys(preview.changes.added).length;
28+
const updatedCount = Object.keys(preview.changes.updated).length;
29+
const unchangedCount = Object.keys(preview.changes.unchanged).length;
30+
const conflictCount = preview.conflictCount;
31+
return [
32+
"Merge Preview Summary",
33+
`Source Session ID: ${preview.source.id}`,
34+
`Target Session ID: ${preview.target.id}`,
35+
`Tool ID: ${preview.selectedToolId}`,
36+
`Added: ${addedCount}`,
37+
`Updated: ${updatedCount}`,
38+
`Unchanged: ${unchangedCount}`,
39+
`Conflicts: ${conflictCount}`
40+
].join("\n");
41+
}
42+
43+
function applySummary(applied) {
44+
return [
45+
"Merge Apply Summary",
46+
`Merged Session ID: ${applied.hostContextId}`,
47+
`Tool ID: ${applied.toolId}`,
48+
`Timestamp: ${applied.timestamp}`,
49+
`Added: ${applied.addedCount}`,
50+
`Updated: ${applied.updatedCount}`,
51+
`Unchanged: ${applied.unchangedCount}`
52+
].join("\n");
53+
}
54+
55+
export function run() {
56+
const failures = [];
57+
const htmlExists = fs.existsSync(htmlPath);
58+
const jsExists = fs.existsSync(jsPath);
59+
const html = htmlExists ? fs.readFileSync(htmlPath, "utf8") : "";
60+
const js = jsExists ? fs.readFileSync(jsPath, "utf8") : "";
61+
const jsSyntax = checkSyntax(jsPath);
62+
const testSyntax = checkSyntax(path.join(repoRoot, "tests", "runtime", "V2MergeResultSummary.test.mjs"));
63+
64+
if (!htmlExists) failures.push("Missing tools/workspace-v2/index.html.");
65+
if (!jsExists) failures.push("Missing tools/workspace-v2/index.js.");
66+
if (!jsSyntax.ok) failures.push("tools/workspace-v2/index.js failed syntax check.");
67+
if (!testSyntax.ok) failures.push("tests/runtime/V2MergeResultSummary.test.mjs failed syntax check.");
68+
69+
const requiredHtml = [
70+
"id=\"workspaceV2MergeResultSummary\"",
71+
"No merge summary yet.",
72+
"id=\"workspaceV2MergeOutput\""
73+
];
74+
requiredHtml.forEach((token) => {
75+
if (!html.includes(token)) failures.push(`Missing merge summary/output HTML token: ${token}`);
76+
});
77+
78+
const requiredJs = [
79+
"setMergeResultSummary(message)",
80+
"setMergePreviewSummary(preview)",
81+
"setMergeApplySummary(hostContextId, toolId, timestamp, changes)",
82+
"Merge Preview Summary",
83+
"Preview confirmed. Apply Merge is ready.",
84+
"Merge Apply Summary",
85+
"Removed Session ID:",
86+
"Preview summary is stale because Session A or Session B changed. Run Preview Merge again.",
87+
"this.setMergeResultSummary(\"Merge preview blocked. Session A and Session B selections are missing.\");"
88+
];
89+
requiredJs.forEach((token) => {
90+
if (!js.includes(token)) failures.push(`Missing merge summary JS token/text: ${token}`);
91+
});
92+
93+
const preview = {
94+
source: { id: "history:asset-browser-v2-a1" },
95+
target: { id: "history:asset-browser-v2-b2" },
96+
selectedToolId: "asset-browser-v2",
97+
changes: {
98+
added: { "payload.layers.2": {} },
99+
updated: { "payload.zoom": { from: 1, to: 2 } },
100+
unchanged: { "payload.toolId": "asset-browser-v2" }
101+
},
102+
conflictCount: 0
103+
};
104+
const previewSummaryText = previewSummary(preview);
105+
if (!previewSummaryText.includes("Merge Preview Summary")) failures.push("Preview summary missing heading.");
106+
if (!previewSummaryText.includes("Source Session ID: history:asset-browser-v2-a1")) failures.push("Preview summary missing source id.");
107+
if (!previewSummaryText.includes("Target Session ID: history:asset-browser-v2-b2")) failures.push("Preview summary missing target id.");
108+
if (!previewSummaryText.includes("Tool ID: asset-browser-v2")) failures.push("Preview summary missing tool id.");
109+
if (!previewSummaryText.includes("Added: 1") || !previewSummaryText.includes("Updated: 1") || !previewSummaryText.includes("Unchanged: 1")) {
110+
failures.push("Preview summary missing added/updated/unchanged counts.");
111+
}
112+
113+
const confirmSummaryText = "Preview confirmed. Apply Merge is ready.";
114+
if (confirmSummaryText !== "Preview confirmed. Apply Merge is ready.") failures.push("Confirm summary mismatch.");
115+
116+
const applied = {
117+
hostContextId: "asset-browser-v2-merged-1777777777777-abc123xy",
118+
toolId: "asset-browser-v2",
119+
timestamp: "2026-05-02T00:00:00.000Z",
120+
addedCount: 2,
121+
updatedCount: 1,
122+
unchangedCount: 3
123+
};
124+
const applySummaryText = applySummary(applied);
125+
if (!applySummaryText.includes("Merge Apply Summary")) failures.push("Apply summary missing heading.");
126+
if (!applySummaryText.includes(`Merged Session ID: ${applied.hostContextId}`)) failures.push("Apply summary missing merged session id.");
127+
if (!applySummaryText.includes(`Timestamp: ${applied.timestamp}`)) failures.push("Apply summary missing timestamp.");
128+
129+
const undoSummaryText = `Last merged session removed.\nRemoved Session ID: ${applied.hostContextId}`;
130+
if (!undoSummaryText.includes("Last merged session removed.")) failures.push("Undo summary missing success message.");
131+
if (!undoSummaryText.includes(`Removed Session ID: ${applied.hostContextId}`)) failures.push("Undo summary missing removed session id.");
132+
133+
const blockedReason = "Merge preview blocked. Session A and Session B selections are missing.";
134+
const staleSummary = "Preview summary is stale because Session A or Session B changed. Run Preview Merge again.";
135+
if (!blockedReason.startsWith("Merge preview blocked.")) failures.push("Blocked preview reason mismatch.");
136+
if (!staleSummary.startsWith("Preview summary is stale")) failures.push("Stale preview summary mismatch.");
137+
138+
const rawPreviewJson = JSON.stringify({
139+
source: preview.source,
140+
target: preview.target,
141+
changes: preview.changes,
142+
conflicts: {},
143+
conflictCount: 0,
144+
confirmed: false,
145+
canApply: true
146+
}, null, 2);
147+
if (!rawPreviewJson.includes("\"source\"") || !rawPreviewJson.includes("\"target\"") || !rawPreviewJson.includes("\"changes\"")) {
148+
failures.push("Raw preview JSON is not available after summary updates.");
149+
}
150+
151+
fs.mkdirSync(path.dirname(resultsPath), { recursive: true });
152+
fs.writeFileSync(resultsPath, `${JSON.stringify({
153+
generatedAt: new Date().toISOString(),
154+
failures,
155+
checks: { htmlExists, jsExists, jsSyntax, testSyntax },
156+
samples: {
157+
previewSummaryText,
158+
confirmSummaryText,
159+
applySummaryText,
160+
undoSummaryText,
161+
blockedReason,
162+
staleSummary,
163+
rawPreviewJsonVisible: Boolean(rawPreviewJson)
164+
}
165+
}, null, 2)}\n`, "utf8");
166+
167+
console.log(`v2 merge-result-summary results: ${resultsPath}`);
168+
assert.equal(failures.length, 0, `V2 merge-result-summary failures: ${failures.join(" | ")}`);
169+
return { failures };
170+
}
171+
172+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
173+
try {
174+
const summary = run();
175+
console.log(JSON.stringify(summary, null, 2));
176+
} catch (error) {
177+
console.error(error);
178+
process.exitCode = 1;
179+
}
180+
}

tools/workspace-v2/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ <h2>Session Merge</h2>
112112
<button id="workspaceV2UndoLastMergeButton" type="button" disabled>Undo Last Merge</button>
113113
</div>
114114
<p id="workspaceV2MergeEnableState">Select two different sessions to enable Preview Merge.</p>
115+
<pre id="workspaceV2MergeResultSummary">No merge summary yet.</pre>
115116
<p id="workspaceV2MergeEmptyState">Need at least two valid sessions to merge.</p>
116117
<label for="workspaceV2MergedSessionId">Merged Session ID</label>
117118
<input id="workspaceV2MergedSessionId" type="text" placeholder="tool-v2-merged-0000000000000" />

0 commit comments

Comments
 (0)