Skip to content

Commit 5a2c820

Browse files
author
DavidQ
committed
Add diff summary counts for clear no-diff vs changed visibility in Diff Viewer - PR_11_274
1 parent 6ca539b commit 5a2c820

6 files changed

Lines changed: 228 additions & 5 deletions
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# PR_11_274 Workspace V2 Diff Viewer Summary Counts Report
2+
3+
## Scope
4+
Workspace V2 Diff Viewer only.
5+
6+
## Files Changed
7+
- tools/workspace-v2/index.html
8+
- tools/workspace-v2/index.js
9+
- tests/runtime/V2DiffViewerSummaryCounts.test.mjs
10+
- docs/pr/PLAN_PR_11_274_WORKSPACE_V2_DIFF_VIEWER_SUMMARY_COUNTS.md
11+
- docs/pr/BUILD_PR_11_274_WORKSPACE_V2_DIFF_VIEWER_SUMMARY_COUNTS.md
12+
- docs/dev/reports/PR_11_274_workspace_v2_diff_viewer_summary_counts_report.md
13+
14+
## Implementation Summary
15+
- Added a new diff summary line node above raw diff JSON output: `workspaceV2DiffSummary`.
16+
- Wired summary updates from existing diff result counts (presentation only):
17+
- `No differences (added: 0, removed: 0, changed: 0)`
18+
- `Differences detected (added: X, removed: Y, changed: Z)`
19+
- Kept raw JSON diff output unchanged below the summary.
20+
- Cleared summary text on diff output invalidation/reset paths to prevent stale summaries after selection changes.
21+
22+
## Validation Commands
23+
1. `node --check tools/workspace-v2/index.js`
24+
- PASS
25+
2. `node --check tests/runtime/V2DiffViewerSummaryCounts.test.mjs`
26+
- PASS
27+
3. `node --check tests/runtime/V2DiffViewerMessaging.test.mjs`
28+
- PASS
29+
4. `node tests/runtime/V2DiffViewerSummaryCounts.test.mjs`
30+
- PASS
31+
- Results: `tmp/v2-diff-viewer-summary-counts-results.json`
32+
5. `node tests/runtime/V2DiffViewerMessaging.test.mjs`
33+
- PASS
34+
- Results: `tmp/v2-diff-viewer-messaging-results.json`
35+
36+
## Full Samples Smoke Decision
37+
- Skipped full samples smoke test.
38+
- Reason: change is scoped to Workspace V2 Diff Viewer presentation messaging and covered by targeted runtime tests.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# BUILD_PR_11_274_WORKSPACE_V2_DIFF_VIEWER_SUMMARY_COUNTS
2+
3+
## Purpose
4+
Implement presentation-only Diff Viewer summary counts in Workspace V2.
5+
6+
## Files
7+
- tools/workspace-v2/index.html
8+
- tools/workspace-v2/index.js
9+
- tests/runtime/V2DiffViewerSummaryCounts.test.mjs
10+
- docs/dev/reports/PR_11_274_workspace_v2_diff_viewer_summary_counts_report.md
11+
12+
## Implementation
13+
1. Add `workspaceV2DiffSummary` element above diff JSON output in Workspace V2 Diff Viewer.
14+
2. Wire the summary node in `index.js`.
15+
3. Add summary formatting method based on existing diff result object counts:
16+
- no diff: `No differences (added: 0, removed: 0, changed: 0)`
17+
- diff exists: `Differences detected (added: X, removed: Y, changed: Z)`
18+
4. Clear summary whenever diff output is invalidated/cleared (selection changes and state resets).
19+
5. Keep existing diff JSON rendering and diff computation intact.
20+
21+
## Acceptance
22+
- Summary appears above JSON output.
23+
- Count values match keys in `added`, `removed`, `changed`.
24+
- JSON output remains unchanged below summary.
25+
- Stale summaries are cleared on selection/state change paths.
26+
- No diff algorithm behavior changes.
27+
28+
## Validation
29+
- node --check tools/workspace-v2/index.js
30+
- node --check tests/runtime/V2DiffViewerSummaryCounts.test.mjs
31+
- node --check tests/runtime/V2DiffViewerMessaging.test.mjs
32+
- node tests/runtime/V2DiffViewerSummaryCounts.test.mjs
33+
- node tests/runtime/V2DiffViewerMessaging.test.mjs
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# PLAN_PR_11_274_WORKSPACE_V2_DIFF_VIEWER_SUMMARY_COUNTS
2+
3+
## Purpose
4+
Add explicit Diff Viewer summary counts in Workspace V2 so results are understandable without parsing raw JSON.
5+
6+
## Scope
7+
- tools/workspace-v2/index.html
8+
- tools/workspace-v2/index.js
9+
- tests/runtime/V2DiffViewerSummaryCounts.test.mjs
10+
- tests/runtime/V2DiffViewerMessaging.test.mjs (validation run)
11+
- docs/report only
12+
13+
## Goals
14+
- Add summary line above Diff JSON output.
15+
- Show exact count summary for no-diff and diff-exists outcomes.
16+
- Keep JSON output unchanged.
17+
- Clear stale summary on selection/state changes.
18+
- Preserve existing diff computation behavior.
19+
20+
## Out of Scope
21+
- No diff algorithm changes.
22+
- No schema changes.
23+
- No non-Diff Viewer Workspace V2 behavior changes.
24+
25+
## Validation
26+
- node --check tools/workspace-v2/index.js
27+
- node --check tests/runtime/V2DiffViewerSummaryCounts.test.mjs
28+
- node --check tests/runtime/V2DiffViewerMessaging.test.mjs
29+
- node tests/runtime/V2DiffViewerSummaryCounts.test.mjs
30+
- node tests/runtime/V2DiffViewerMessaging.test.mjs
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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 testPath = path.join(repoRoot, "tests", "runtime", "V2DiffViewerSummaryCounts.test.mjs");
13+
const resultsPath = path.join(repoRoot, "tmp", "v2-diff-viewer-summary-counts-results.json");
14+
15+
function checkSyntax(filePath) {
16+
try {
17+
execFileSync(process.execPath, ["--check", filePath], {
18+
cwd: repoRoot,
19+
stdio: ["ignore", "pipe", "pipe"]
20+
});
21+
return { ok: true, error: "" };
22+
} catch (error) {
23+
return { ok: false, error: (error?.stderr || error?.stdout || error?.message || "").toString().trim() };
24+
}
25+
}
26+
27+
function buildDiffSummary(diff) {
28+
const addedCount = Object.keys(diff.added).length;
29+
const removedCount = Object.keys(diff.removed).length;
30+
const changedCount = Object.keys(diff.changed).length;
31+
if (addedCount === 0 && removedCount === 0 && changedCount === 0) {
32+
return "No differences (added: 0, removed: 0, changed: 0)";
33+
}
34+
return `Differences detected (added: ${addedCount}, removed: ${removedCount}, changed: ${changedCount})`;
35+
}
36+
37+
export function run() {
38+
const failures = [];
39+
const htmlExists = fs.existsSync(htmlPath);
40+
const jsExists = fs.existsSync(jsPath);
41+
const html = htmlExists ? fs.readFileSync(htmlPath, "utf8") : "";
42+
const js = jsExists ? fs.readFileSync(jsPath, "utf8") : "";
43+
const jsSyntax = checkSyntax(jsPath);
44+
const testSyntax = checkSyntax(testPath);
45+
46+
if (!htmlExists) failures.push("Missing tools/workspace-v2/index.html.");
47+
if (!jsExists) failures.push("Missing tools/workspace-v2/index.js.");
48+
if (!jsSyntax.ok) failures.push("tools/workspace-v2/index.js failed syntax check.");
49+
if (!testSyntax.ok) failures.push("tests/runtime/V2DiffViewerSummaryCounts.test.mjs failed syntax check.");
50+
51+
if (!html.includes('id="workspaceV2DiffSummary"')) {
52+
failures.push("Missing Diff summary node in Workspace V2 Diff Viewer.");
53+
}
54+
if (!js.includes("this.diffSummaryNode = document.getElementById(\"workspaceV2DiffSummary\");")) {
55+
failures.push("Diff summary node is not wired in JS.");
56+
}
57+
if (!js.includes("No differences (added: 0, removed: 0, changed: 0)")) {
58+
failures.push("Missing no-difference summary text.");
59+
}
60+
if (!js.includes("Differences detected (added: ${addedCount}, removed: ${removedCount}, changed: ${changedCount})")) {
61+
failures.push("Missing diff-exists summary text.");
62+
}
63+
if (!js.includes("this.diffSummaryNode.textContent = \"\";")) {
64+
failures.push("Diff summary clear path missing for stale summary reset.");
65+
}
66+
67+
const noDiffSummary = buildDiffSummary({ added: {}, removed: {}, changed: {} });
68+
const withDiffSummary = buildDiffSummary({
69+
added: { "a.b": 1, "a.c": 2 },
70+
removed: { old: true },
71+
changed: { "x.y": { from: 1, to: 2 } }
72+
});
73+
if (noDiffSummary !== "No differences (added: 0, removed: 0, changed: 0)") {
74+
failures.push("No-diff summary formatting mismatch.");
75+
}
76+
if (withDiffSummary !== "Differences detected (added: 2, removed: 1, changed: 1)") {
77+
failures.push("Diff summary count formatting mismatch.");
78+
}
79+
80+
fs.mkdirSync(path.dirname(resultsPath), { recursive: true });
81+
fs.writeFileSync(resultsPath, `${JSON.stringify({
82+
generatedAt: new Date().toISOString(),
83+
failures,
84+
checks: {
85+
htmlExists,
86+
jsExists,
87+
jsSyntax,
88+
testSyntax
89+
},
90+
scenarios: {
91+
noDiffSummary,
92+
withDiffSummary
93+
}
94+
}, null, 2)}
95+
`, "utf8");
96+
97+
console.log(`v2 diff-viewer summary-counts results: ${resultsPath}`);
98+
assert.equal(failures.length, 0, `V2 diff-viewer summary-counts failures: ${failures.join(" | ")}`);
99+
return { failures };
100+
}
101+
102+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
103+
try {
104+
const summary = run();
105+
console.log(JSON.stringify(summary, null, 2));
106+
} catch (error) {
107+
console.error(error);
108+
process.exitCode = 1;
109+
}
110+
}

tools/workspace-v2/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ <h2>Session Diff Viewer</h2>
9292
<button id="workspaceV2ComputeDiffButton" type="button">Compute Diff</button>
9393
<p id="workspaceV2DiffEnableState">Select two different sessions to enable Compute Diff.</p>
9494
<p id="workspaceV2DiffEmptyState">Need at least two valid sessions to compare.</p>
95+
<p id="workspaceV2DiffSummary"></p>
9596
<pre id="workspaceV2DiffOutput">No diff computed.</pre>
9697
</section>
9798

tools/workspace-v2/index.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class WorkspaceV2SessionProducer {
4141
this.computeDiffButton = document.getElementById("workspaceV2ComputeDiffButton");
4242
this.diffEnableStateNode = document.getElementById("workspaceV2DiffEnableState");
4343
this.diffEmptyState = document.getElementById("workspaceV2DiffEmptyState");
44+
this.diffSummaryNode = document.getElementById("workspaceV2DiffSummary");
4445
this.diffOutputNode = document.getElementById("workspaceV2DiffOutput");
4546
this.mergeLeftSelect = document.getElementById("workspaceV2MergeLeftSelect");
4647
this.mergeRightSelect = document.getElementById("workspaceV2MergeRightSelect");
@@ -566,6 +567,7 @@ class WorkspaceV2SessionProducer {
566567

567568
clearDiffOutputForStateChange(statusMessage, outputMessage) {
568569
this.diffOutputSelectionKey = "";
570+
this.diffSummaryNode.textContent = "";
569571
this.diffOutputNode.textContent = typeof outputMessage === "string" && outputMessage.trim()
570572
? outputMessage
571573
: "No diff computed.";
@@ -2286,7 +2288,16 @@ class WorkspaceV2SessionProducer {
22862288
return { added, removed, changed };
22872289
}
22882290

2291+
setDiffSummaryFromCounts(addedCount, removedCount, changedCount) {
2292+
if (addedCount === 0 && removedCount === 0 && changedCount === 0) {
2293+
this.diffSummaryNode.textContent = "No differences (added: 0, removed: 0, changed: 0)";
2294+
return;
2295+
}
2296+
this.diffSummaryNode.textContent = `Differences detected (added: ${addedCount}, removed: ${removedCount}, changed: ${changedCount})`;
2297+
}
2298+
22892299
computeSelectedSessionDiff() {
2300+
this.diffSummaryNode.textContent = "";
22902301
if (!Array.isArray(this.diffCandidates) || this.diffCandidates.length < 2) {
22912302
this.diffOutputSelectionKey = "";
22922303
this.diffOutputNode.textContent = "Create or reopen at least two Workspace V2 sessions before comparing.";
@@ -2334,13 +2345,13 @@ class WorkspaceV2SessionProducer {
23342345
return;
23352346
}
23362347
const diff = this.computeSessionDiff(left.payload, right.payload);
2348+
const addedCount = Object.keys(diff.added).length;
2349+
const removedCount = Object.keys(diff.removed).length;
2350+
const changedCount = Object.keys(diff.changed).length;
23372351
this.diffOutputSelectionKey = this.buildMergeSelectionKey(left.id, right.id);
2352+
this.setDiffSummaryFromCounts(addedCount, removedCount, changedCount);
23382353
this.diffOutputNode.textContent = JSON.stringify(diff, null, 2);
2339-
if (
2340-
Object.keys(diff.added).length === 0 &&
2341-
Object.keys(diff.removed).length === 0 &&
2342-
Object.keys(diff.changed).length === 0
2343-
) {
2354+
if (addedCount === 0 && removedCount === 0 && changedCount === 0) {
23442355
this.statusNode.textContent = "No differences. The selected sessions are identical.";
23452356
return;
23462357
}

0 commit comments

Comments
 (0)