Skip to content

Commit 8637300

Browse files
author
DavidQ
committed
Add overwrite action to saved session cards and complete Session Library lifecycle - PR_11_270
1 parent a4da942 commit 8637300

7 files changed

Lines changed: 266 additions & 1 deletion
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# PR_11_270 Workspace V2 Session Library Overwrite Action Report
2+
3+
## Scope
4+
Workspace V2 Session Library only.
5+
6+
## Files Changed
7+
- tools/workspace-v2/index.js
8+
- tests/runtime/V2SessionLibraryCardOverwrite.test.mjs
9+
- tests/runtime/V2SessionLibraryActionCleanup.test.mjs
10+
- tests/runtime/V2SessionLibraryActions.test.mjs
11+
- docs/pr/PLAN_PR_11_270_WORKSPACE_V2_SESSION_LIBRARY_OVERWRITE_ACTION.md
12+
- docs/pr/BUILD_PR_11_270_WORKSPACE_V2_SESSION_LIBRARY_OVERWRITE_ACTION.md
13+
- docs/dev/reports/PR_11_270_workspace_v2_session_library_overwrite_action_report.md
14+
15+
## Implementation Summary
16+
- Added card-level `Overwrite` button for each saved session entry.
17+
- Overwrite button enablement is active-session-gated:
18+
- enabled only when active Workspace V2 session exists
19+
- disabled otherwise
20+
- Added `overwriteSavedSessionById(sessionId)` row-authoritative action:
21+
- requires existing saved session ID
22+
- requires active Workspace V2 session payload
23+
- replaces saved payload for that exact ID
24+
- does not create a new saved session ID
25+
- keeps saved session listed in place
26+
- emits confirmation: `Saved session updated.`
27+
- Save behavior remains NEW-only:
28+
- duplicate IDs blocked via Save
29+
- overwrite is now available on saved-session cards
30+
31+
## Validation Commands
32+
1. `node --check tools/workspace-v2/index.js`
33+
- PASS
34+
2. `node --check tests/runtime/V2SessionLibraryCardOverwrite.test.mjs`
35+
- PASS
36+
3. `node --check tests/runtime/V2SessionLibraryActionCleanup.test.mjs`
37+
- PASS
38+
4. `node --check tests/runtime/V2SessionLibraryActions.test.mjs`
39+
- PASS
40+
5. `node tests/runtime/V2SessionLibraryCardOverwrite.test.mjs`
41+
- PASS
42+
- Results: `tmp/v2-session-library-card-overwrite-results.json`
43+
6. `node tests/runtime/V2SessionLibraryActionCleanup.test.mjs`
44+
- PASS
45+
- Results: `tmp/v2-session-library-action-cleanup-results.json`
46+
7. `node tests/runtime/V2SessionLibraryActions.test.mjs`
47+
- PASS
48+
- Results: `tmp/v2-session-library-actions-results.json`
49+
50+
## Full Samples Smoke Decision
51+
- Skipped full samples smoke test.
52+
- Reason: changes are restricted to Workspace V2 Session Library row action behavior and covered by targeted runtime tests.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# BUILD_PR_11_270_WORKSPACE_V2_SESSION_LIBRARY_OVERWRITE_ACTION
2+
3+
## Purpose
4+
Implement card-level overwrite for existing saved sessions in Workspace V2 Session Library.
5+
6+
## Files
7+
- tools/workspace-v2/index.js
8+
- tests/runtime/V2SessionLibraryCardOverwrite.test.mjs
9+
- tests/runtime/V2SessionLibraryActionCleanup.test.mjs
10+
- tests/runtime/V2SessionLibraryActions.test.mjs
11+
- docs/dev/reports/PR_11_270_workspace_v2_session_library_overwrite_action_report.md
12+
13+
## Implementation
14+
1. Add `Overwrite` button to each saved session card.
15+
2. Gate row overwrite button enablement on active Workspace V2 session availability.
16+
3. Add `overwriteSavedSessionById(sessionId)` for row-authoritative overwrite behavior.
17+
4. Preserve existing session ID; replace saved payload only.
18+
5. Keep Save path new-only and duplicate-ID-blocked.
19+
6. Emit `Saved session updated.` on successful overwrite.
20+
21+
## Acceptance
22+
- Card-level overwrite exists and is active-session-gated.
23+
- Overwrite updates saved payload in place with no new ID creation.
24+
- Save remains new-only; duplicate IDs blocked on Save.
25+
- Status text is current-state and non-stale.
26+
27+
## Validation
28+
- node --check tools/workspace-v2/index.js
29+
- node --check tests/runtime/V2SessionLibraryCardOverwrite.test.mjs
30+
- node --check tests/runtime/V2SessionLibraryActionCleanup.test.mjs
31+
- node --check tests/runtime/V2SessionLibraryActions.test.mjs
32+
- node tests/runtime/V2SessionLibraryCardOverwrite.test.mjs
33+
- node tests/runtime/V2SessionLibraryActionCleanup.test.mjs
34+
- node tests/runtime/V2SessionLibraryActions.test.mjs
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# PLAN_PR_11_270_WORKSPACE_V2_SESSION_LIBRARY_OVERWRITE_ACTION
2+
3+
## Purpose
4+
Add a saved-session card overwrite action in Workspace V2 Session Library, aligned with new-only Save rules.
5+
6+
## Scope
7+
- tools/workspace-v2/index.js
8+
- tests/runtime/V2SessionLibraryCardOverwrite.test.mjs
9+
- tests/runtime/V2SessionLibraryActionCleanup.test.mjs
10+
- tests/runtime/V2SessionLibraryActions.test.mjs
11+
- docs/report only
12+
13+
## Goals
14+
- Add `Overwrite` on each saved session card.
15+
- Enable overwrite only when active Workspace V2 session exists.
16+
- Overwrite replaces payload for that saved session ID only.
17+
- Overwrite does not create new session ID or duplicate entry.
18+
- Show exact confirmation: `Saved session updated.`
19+
- Keep Save new-only and duplicate-save blocked.
20+
21+
## Out of Scope
22+
- No schema changes
23+
- No merge/diff algorithm changes
24+
- No cross-tool changes
25+
26+
## Validation
27+
- node --check tools/workspace-v2/index.js
28+
- node --check tests/runtime/V2SessionLibraryCardOverwrite.test.mjs
29+
- node --check tests/runtime/V2SessionLibraryActionCleanup.test.mjs
30+
- node --check tests/runtime/V2SessionLibraryActions.test.mjs
31+
- node tests/runtime/V2SessionLibraryCardOverwrite.test.mjs
32+
- node tests/runtime/V2SessionLibraryActionCleanup.test.mjs
33+
- node tests/runtime/V2SessionLibraryActions.test.mjs

tests/runtime/V2SessionLibraryActionCleanup.test.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export function run() {
5454
'this.setLibraryStatus("That session ID already exists. Use the saved session card to Load, Overwrite, or Delete it.");',
5555
'Saved session created. Manage this session from its Saved Sessions card.',
5656
'loadButton.textContent = "Load";',
57+
'overwriteButton.textContent = "Overwrite";',
5758
'deleteSavedButton.textContent = "Delete Saved";',
5859
'useInLibraryButton.textContent = "Use in Diff/Merge";'
5960
];

tests/runtime/V2SessionLibraryActions.test.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ export function run() {
137137
"No active Workspace V2 session is available to overwrite from.",
138138
"Saved session not found. Use Save Session to create it first.",
139139
"Saved session overwritten.",
140+
"Saved session updated.",
140141
"Enter a saved session ID before loading.",
141142
"Saved session loaded.",
142143
"Enter a saved session ID before deleting.",
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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 jsPath = path.join(repoRoot, "tools", "workspace-v2", "index.js");
11+
const testPath = path.join(repoRoot, "tests", "runtime", "V2SessionLibraryCardOverwrite.test.mjs");
12+
const resultsPath = path.join(repoRoot, "tmp", "v2-session-library-card-overwrite-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 overwriteSavedSession(library, sessionId, activePayload) {
27+
const next = JSON.parse(JSON.stringify(library));
28+
if (!sessionId || !Object.prototype.hasOwnProperty.call(next, sessionId)) {
29+
return { ok: false, message: "Saved session not found. Use Save Session to create it first.", library: next };
30+
}
31+
if (!activePayload || typeof activePayload !== "object" || Array.isArray(activePayload)) {
32+
return { ok: false, message: "No active Workspace V2 session is available to overwrite from.", library: next };
33+
}
34+
next[sessionId] = JSON.parse(JSON.stringify(activePayload));
35+
return { ok: true, message: "Saved session updated.", library: next };
36+
}
37+
38+
export function run() {
39+
const failures = [];
40+
const jsExists = fs.existsSync(jsPath);
41+
const js = jsExists ? fs.readFileSync(jsPath, "utf8") : "";
42+
const jsSyntax = checkSyntax(jsPath);
43+
const testSyntax = checkSyntax(testPath);
44+
45+
if (!jsExists) failures.push("Missing tools/workspace-v2/index.js.");
46+
if (!jsSyntax.ok) failures.push("tools/workspace-v2/index.js failed syntax check.");
47+
if (!testSyntax.ok) failures.push("tests/runtime/V2SessionLibraryCardOverwrite.test.mjs failed syntax check.");
48+
49+
const requiredTokens = [
50+
"overwriteButton.textContent = \"Overwrite\";",
51+
"overwriteButton.disabled = !canOverwriteFromActiveSession;",
52+
"overwriteSavedSessionById(sessionId)",
53+
"No active Workspace V2 session is available to overwrite from.",
54+
"Saved session updated."
55+
];
56+
requiredTokens.forEach((token) => {
57+
if (!js.includes(token)) failures.push(`Missing overwrite-card token/text: ${token}`);
58+
});
59+
60+
const initialLibrary = {
61+
"saved-1": { version: "v2", toolId: "palette-manager-v2", payloadJson: { original: true } },
62+
"saved-2": { version: "v2", toolId: "asset-browser-v2", payloadJson: { untouched: true } }
63+
};
64+
const activePayload = { version: "v2", toolId: "palette-manager-v2", payloadJson: { updated: true } };
65+
66+
const noActive = overwriteSavedSession(initialLibrary, "saved-1", null);
67+
if (noActive.ok || noActive.message !== "No active Workspace V2 session is available to overwrite from.") {
68+
failures.push("Overwrite should be blocked without active session.");
69+
}
70+
71+
const missingSaved = overwriteSavedSession(initialLibrary, "missing-id", activePayload);
72+
if (missingSaved.ok || missingSaved.message !== "Saved session not found. Use Save Session to create it first.") {
73+
failures.push("Overwrite should require an existing saved session ID.");
74+
}
75+
76+
const overwritten = overwriteSavedSession(initialLibrary, "saved-1", activePayload);
77+
if (!overwritten.ok || overwritten.message !== "Saved session updated.") {
78+
failures.push("Overwrite should succeed and return update confirmation.");
79+
}
80+
if (!overwritten.library["saved-1"] || overwritten.library["saved-1"].payloadJson?.updated !== true) {
81+
failures.push("Overwrite should replace saved payload with active payload.");
82+
}
83+
if (Object.keys(overwritten.library).length !== Object.keys(initialLibrary).length) {
84+
failures.push("Overwrite should not create a new saved session entry.");
85+
}
86+
if (overwritten.library["saved-2"].payloadJson?.untouched !== true) {
87+
failures.push("Overwrite should not mutate other saved session entries.");
88+
}
89+
90+
fs.mkdirSync(path.dirname(resultsPath), { recursive: true });
91+
fs.writeFileSync(resultsPath, `${JSON.stringify({
92+
generatedAt: new Date().toISOString(),
93+
failures,
94+
checks: { jsExists, jsSyntax, testSyntax },
95+
scenarios: { noActive, missingSaved, overwritten }
96+
}, null, 2)}
97+
`, "utf8");
98+
99+
console.log(`v2 session-library-card-overwrite results: ${resultsPath}`);
100+
assert.equal(failures.length, 0, `V2 session-library-card-overwrite failures: ${failures.join(" | ")}`);
101+
return { failures };
102+
}
103+
104+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
105+
try {
106+
const summary = run();
107+
console.log(JSON.stringify(summary, null, 2));
108+
} catch (error) {
109+
console.error(error);
110+
process.exitCode = 1;
111+
}
112+
}

tools/workspace-v2/index.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,7 @@ class WorkspaceV2SessionProducer {
986986
this.sessionListNode.replaceChildren();
987987
this.libraryEmptyState.hidden = sessionNames.length > 0;
988988
this.libraryEmptyState.textContent = "No saved sessions in library.";
989+
const canOverwriteFromActiveSession = this.hasActiveWorkspaceSessionForSave();
989990
sessionNames.forEach((sessionName) => {
990991
const item = document.createElement("li");
991992
const payload = library[sessionName];
@@ -996,6 +997,7 @@ class WorkspaceV2SessionProducer {
996997
const copyIdButton = document.createElement("button");
997998
const useInLibraryButton = document.createElement("button");
998999
const loadButton = document.createElement("button");
1000+
const overwriteButton = document.createElement("button");
9991001
const deleteSavedButton = document.createElement("button");
10001002
const readableLabel = payload && typeof payload.toolId === "string" && payload.toolId.trim()
10011003
? payload.toolId.trim()
@@ -1020,12 +1022,18 @@ class WorkspaceV2SessionProducer {
10201022
loadButton.addEventListener("click", () => {
10211023
this.loadSavedSessionById(sessionName);
10221024
});
1025+
overwriteButton.type = "button";
1026+
overwriteButton.textContent = "Overwrite";
1027+
overwriteButton.disabled = !canOverwriteFromActiveSession;
1028+
overwriteButton.addEventListener("click", () => {
1029+
this.overwriteSavedSessionById(sessionName);
1030+
});
10231031
deleteSavedButton.type = "button";
10241032
deleteSavedButton.textContent = "Delete Saved";
10251033
deleteSavedButton.addEventListener("click", () => {
10261034
this.deleteSavedSessionById(sessionName);
10271035
});
1028-
item.append(label, idLine, copyIdButton, useInLibraryButton, loadButton, deleteSavedButton);
1036+
item.append(label, idLine, copyIdButton, useInLibraryButton, loadButton, overwriteButton, deleteSavedButton);
10291037
this.sessionListNode.appendChild(item);
10301038
});
10311039
this.renderSessionDiffInputs();
@@ -1073,6 +1081,30 @@ class WorkspaceV2SessionProducer {
10731081
this.renderSessionLibrary();
10741082
}
10751083

1084+
overwriteSavedSessionById(sessionId) {
1085+
if (typeof sessionId !== "string" || !sessionId.trim()) {
1086+
this.setLibraryStatus("Enter a saved session ID before overwriting.");
1087+
return;
1088+
}
1089+
const activePayload = this.readActiveSessionPayloadForLibraryActions();
1090+
if (!this.isValidSessionPayload(activePayload)) {
1091+
this.setLibraryStatus("No active Workspace V2 session is available to overwrite from.");
1092+
return;
1093+
}
1094+
const library = this.readSessionLibrary();
1095+
if (library === null) {
1096+
return;
1097+
}
1098+
if (!Object.prototype.hasOwnProperty.call(library, sessionId.trim())) {
1099+
this.setLibraryStatus("Saved session not found. Use Save Session to create it first.");
1100+
return;
1101+
}
1102+
library[sessionId.trim()] = activePayload;
1103+
this.writeSessionLibrary(library);
1104+
this.renderSessionLibrary();
1105+
this.setLibraryStatus("Saved session updated.");
1106+
}
1107+
10761108
deleteSavedSessionById(sessionId) {
10771109
if (typeof sessionId !== "string" || !sessionId.trim()) {
10781110
this.setLibraryStatus("Enter a saved session ID before deleting.");

0 commit comments

Comments
 (0)