Skip to content

Commit 297da66

Browse files
author
DavidQ
committed
Add explicit feedback for saved-session delete misses - PR 11.243
1 parent ec77fe9 commit 297da66

3 files changed

Lines changed: 236 additions & 5 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# PR_11_243 — Saved Session Delete Feedback (Workspace V2)
2+
3+
## Summary
4+
Updated Workspace V2 Session Library delete UX so `Delete Saved Session` always returns explicit feedback and never silently no-ops when a session is missing from Session Library.
5+
6+
## Scope Confirmation
7+
- Workspace V2 Session Library UI logic only.
8+
- No schema changes.
9+
- No sample changes.
10+
- No game changes.
11+
- No Diff/Merge behavior changes.
12+
- No Recent Session delete behavior changes.
13+
14+
## Files Changed
15+
- `tools/workspace-v2/index.js`
16+
- `tests/runtime/V2SavedSessionDeleteFeedback.test.mjs`
17+
18+
## Implemented Behavior
19+
`Delete Saved Session` now returns exact required outcomes:
20+
21+
1. Empty input:
22+
- `Enter a saved session ID before deleting.`
23+
2. Empty library:
24+
- `No saved sessions exist. Use Delete on Recent Sessions to remove temporary sessions.`
25+
3. Recent-only ID (exists in Recent Sessions, not in Session Library):
26+
- `Session ID is not saved in Session Library. Use Delete on Recent Sessions to remove temporary sessions.`
27+
4. Unknown ID (exists nowhere):
28+
- `Saved session not found.`
29+
5. Saved ID exists:
30+
- deletes library entry only
31+
- `Saved session deleted.`
32+
33+
Additional guarantees:
34+
- `Delete Saved Session` does not remove Recent Session entries.
35+
- `Delete Saved Session` does not remove `sessionStorage` payloads.
36+
- Session Library render refresh remains after successful delete.
37+
38+
## Validation Commands Run
39+
```powershell
40+
node --check tools/workspace-v2/index.js
41+
node --check tests/runtime/V2SavedSessionDeleteFeedback.test.mjs
42+
node tests/runtime/V2SavedSessionDeleteFeedback.test.mjs
43+
```
44+
45+
## Validation Results
46+
- `node --check tools/workspace-v2/index.js` -> PASS
47+
- `node --check tests/runtime/V2SavedSessionDeleteFeedback.test.mjs` -> PASS
48+
- `node tests/runtime/V2SavedSessionDeleteFeedback.test.mjs` -> PASS
49+
- Output: `tmp/v2-saved-session-delete-feedback-results.json`
50+
- Failures: `[]`
51+
52+
## Full Samples Smoke
53+
Skipped.
54+
55+
Reason: this PR is a narrowly scoped Workspace V2 Session Library feedback update with targeted executable validation; no shared sample infrastructure was changed.
56+
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
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 resultsPath = path.join(repoRoot, "tmp", "v2-saved-session-delete-feedback-results.json");
12+
13+
function readText(filePath) {
14+
return fs.readFileSync(filePath, "utf8");
15+
}
16+
17+
function checkSyntax(jsFilePath) {
18+
try {
19+
execFileSync(process.execPath, ["--check", jsFilePath], {
20+
cwd: repoRoot,
21+
stdio: ["ignore", "pipe", "pipe"]
22+
});
23+
return { ok: true, error: "" };
24+
} catch (error) {
25+
return { ok: false, error: (error?.stderr || error?.stdout || error?.message || "").toString().trim() };
26+
}
27+
}
28+
29+
function evaluateDeleteFeedback(sessionName, library, history) {
30+
const trimmedSessionName = typeof sessionName === "string" ? sessionName.trim() : "";
31+
if (!trimmedSessionName) {
32+
return { message: "Enter a saved session ID before deleting.", libraryDeleted: false };
33+
}
34+
const libraryMap = library && typeof library === "object" && !Array.isArray(library) ? { ...library } : {};
35+
const recentMatch = Array.isArray(history)
36+
? history.some((entry) => entry && entry.hostContextId === trimmedSessionName)
37+
: false;
38+
const librarySessionNames = Object.keys(libraryMap);
39+
if (librarySessionNames.length === 0) {
40+
return {
41+
message: recentMatch
42+
? "Session ID is not saved in Session Library. Use Delete on Recent Sessions to remove temporary sessions."
43+
: "No saved sessions exist. Use Delete on Recent Sessions to remove temporary sessions.",
44+
libraryDeleted: false
45+
};
46+
}
47+
if (!Object.prototype.hasOwnProperty.call(libraryMap, trimmedSessionName)) {
48+
return {
49+
message: recentMatch
50+
? "Session ID is not saved in Session Library. Use Delete on Recent Sessions to remove temporary sessions."
51+
: "Saved session not found.",
52+
libraryDeleted: false
53+
};
54+
}
55+
delete libraryMap[trimmedSessionName];
56+
return { message: "Saved session deleted.", libraryDeleted: true, nextLibrary: libraryMap };
57+
}
58+
59+
export function run() {
60+
const failures = [];
61+
const jsExists = fs.existsSync(jsPath);
62+
const js = jsExists ? readText(jsPath) : "";
63+
const syntax = checkSyntax(jsPath);
64+
65+
if (!jsExists) failures.push("Missing tools/workspace-v2/index.js.");
66+
if (!syntax.ok) failures.push("tools/workspace-v2/index.js failed syntax check.");
67+
68+
const requiredMessages = [
69+
"Enter a saved session ID before deleting.",
70+
"No saved sessions exist. Use Delete on Recent Sessions to remove temporary sessions.",
71+
"Session ID is not saved in Session Library. Use Delete on Recent Sessions to remove temporary sessions.",
72+
"Saved session not found.",
73+
"Saved session deleted."
74+
];
75+
requiredMessages.forEach((message) => {
76+
if (!js.includes(message)) {
77+
failures.push(`Missing required message: ${message}`);
78+
}
79+
});
80+
81+
const deleteNamedStart = js.indexOf("deleteNamedSession() {");
82+
const deleteNamedEnd = deleteNamedStart >= 0
83+
? js.indexOf(" createSessionAndLaunch() {", deleteNamedStart)
84+
: -1;
85+
if (deleteNamedStart < 0 || deleteNamedEnd < 0 || deleteNamedEnd <= deleteNamedStart) {
86+
failures.push("Could not locate deleteNamedSession() block.");
87+
} else {
88+
const deleteBlock = js.slice(deleteNamedStart, deleteNamedEnd);
89+
if (deleteBlock.includes("sessionStorage.removeItem(")) {
90+
failures.push("Delete Saved Session must not remove sessionStorage payloads.");
91+
}
92+
if (deleteBlock.includes("this.deleteRecentSessionEntry(")) {
93+
failures.push("Delete Saved Session must not delete Recent Sessions entries.");
94+
}
95+
}
96+
97+
const history = [{ hostContextId: "recent-only", tool: "asset-browser-v2", timestamp: "2026-05-01T00:00:00.000Z", payload: { toolId: "asset-browser-v2", version: "v2" } }];
98+
const recentBefore = JSON.stringify(history);
99+
const storageBefore = { "recent-only": "{\"toolId\":\"asset-browser-v2\",\"version\":\"v2\"}" };
100+
const storageAfter = { ...storageBefore };
101+
102+
const emptyInputCase = evaluateDeleteFeedback("", {}, history);
103+
if (emptyInputCase.message !== "Enter a saved session ID before deleting.") {
104+
failures.push("Empty input did not produce required explicit message.");
105+
}
106+
107+
const emptyLibraryCase = evaluateDeleteFeedback("unknown-id", {}, history);
108+
if (emptyLibraryCase.message !== "No saved sessions exist. Use Delete on Recent Sessions to remove temporary sessions.") {
109+
failures.push("Empty library did not produce required explicit message.");
110+
}
111+
112+
const recentOnlyCase = evaluateDeleteFeedback("recent-only", {}, history);
113+
if (recentOnlyCase.message !== "Session ID is not saved in Session Library. Use Delete on Recent Sessions to remove temporary sessions.") {
114+
failures.push("Recent-only id did not produce required explicit message.");
115+
}
116+
117+
const unknownIdCase = evaluateDeleteFeedback("unknown-id", { "saved-id": { toolId: "palette-manager-v2", version: "v2" } }, history);
118+
if (unknownIdCase.message !== "Saved session not found.") {
119+
failures.push("Unknown id did not produce required explicit message.");
120+
}
121+
122+
const savedDeleteCase = evaluateDeleteFeedback("saved-id", { "saved-id": { toolId: "palette-manager-v2", version: "v2" }, "saved-2": { toolId: "asset-browser-v2", version: "v2" } }, history);
123+
if (savedDeleteCase.message !== "Saved session deleted.") {
124+
failures.push("Saved session delete did not produce success message.");
125+
}
126+
if (!savedDeleteCase.libraryDeleted || !savedDeleteCase.nextLibrary || Object.prototype.hasOwnProperty.call(savedDeleteCase.nextLibrary, "saved-id")) {
127+
failures.push("Saved session id was not deleted from library map.");
128+
}
129+
130+
if (JSON.stringify(history) !== recentBefore) {
131+
failures.push("Delete Saved Session should not delete Recent Sessions entries.");
132+
}
133+
if (!Object.prototype.hasOwnProperty.call(storageAfter, "recent-only")) {
134+
failures.push("Delete Saved Session should not remove sessionStorage payloads.");
135+
}
136+
137+
fs.mkdirSync(path.dirname(resultsPath), { recursive: true });
138+
fs.writeFileSync(resultsPath, `${JSON.stringify({
139+
generatedAt: new Date().toISOString(),
140+
failures,
141+
checks: {
142+
jsExists,
143+
syntax,
144+
requiredMessagesPresent: requiredMessages.every((message) => js.includes(message))
145+
},
146+
cases: {
147+
emptyInputCase,
148+
emptyLibraryCase,
149+
recentOnlyCase,
150+
unknownIdCase,
151+
savedDeleteCase
152+
}
153+
}, null, 2)}\n`, "utf8");
154+
155+
console.log(`v2 saved-session delete feedback results: ${resultsPath}`);
156+
assert.equal(failures.length, 0, `V2 saved-session delete feedback failures: ${failures.join(" | ")}`);
157+
return { failures };
158+
}
159+
160+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
161+
try {
162+
const summary = run();
163+
console.log(JSON.stringify(summary, null, 2));
164+
} catch (error) {
165+
console.error(error);
166+
process.exitCode = 1;
167+
}
168+
}

tools/workspace-v2/index.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,25 +1734,32 @@ class WorkspaceV2SessionProducer {
17341734
deleteNamedSession() {
17351735
const sessionName = this.selectedSessionName();
17361736
if (!sessionName) {
1737-
this.statusNode.textContent = "Session name is required to delete.";
1737+
this.statusNode.textContent = "Enter a saved session ID before deleting.";
17381738
return;
17391739
}
17401740
const library = this.readSessionLibrary();
17411741
if (library === null) {
17421742
return;
17431743
}
1744+
const history = this.readSessionHistory();
1745+
const recentMatch = history.some((entry) => entry.hostContextId === sessionName);
1746+
const librarySessionNames = Object.keys(library);
1747+
if (librarySessionNames.length === 0) {
1748+
this.statusNode.textContent = recentMatch
1749+
? "Session ID is not saved in Session Library. Use Delete on Recent Sessions to remove temporary sessions."
1750+
: "No saved sessions exist. Use Delete on Recent Sessions to remove temporary sessions.";
1751+
return;
1752+
}
17441753
if (!Object.prototype.hasOwnProperty.call(library, sessionName)) {
1745-
const history = this.readSessionHistory();
1746-
const recentMatch = history.some((entry) => entry.hostContextId === sessionName);
17471754
this.statusNode.textContent = recentMatch
17481755
? "Session ID is not saved in Session Library. Use Delete on Recent Sessions to remove temporary sessions."
1749-
: `Session '${sessionName}' was not found in library.`;
1756+
: "Saved session not found.";
17501757
return;
17511758
}
17521759
delete library[sessionName];
17531760
this.writeSessionLibrary(library);
17541761
this.renderSessionLibrary();
1755-
this.statusNode.textContent = `Session '${sessionName}' deleted from library.`;
1762+
this.statusNode.textContent = "Saved session deleted.";
17561763
}
17571764

17581765
createSessionAndLaunch() {

0 commit comments

Comments
 (0)