Skip to content

Commit a4da942

Browse files
author
DavidQ
committed
Disable duplicate new-session saves and clarify Session Library load semantics - PR_11_269
1 parent 9a066d1 commit a4da942

8 files changed

Lines changed: 314 additions & 13 deletions
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# PR_11_269 Workspace V2 Session Library Save Guard + Load Explanation UX Report
2+
3+
## Scope
4+
Workspace V2 Session Library only.
5+
6+
## Files Changed
7+
- tools/workspace-v2/index.html
8+
- tools/workspace-v2/index.js
9+
- tests/runtime/V2SessionLibrarySaveGuard.test.mjs
10+
- tests/runtime/V2SessionLibraryActionCleanup.test.mjs
11+
- tests/runtime/V2SessionLibraryActions.test.mjs
12+
- docs/pr/PLAN_PR_11_269_WORKSPACE_V2_SESSION_LIBRARY_SAVE_GUARD_AND_LOAD_EXPLANATION_UX.md
13+
- docs/pr/BUILD_PR_11_269_WORKSPACE_V2_SESSION_LIBRARY_SAVE_GUARD_AND_LOAD_EXPLANATION_UX.md
14+
- docs/dev/reports/PR_11_269_workspace_v2_session_library_save_guard_load_explanation_report.md
15+
16+
## Implementation Summary
17+
- Added Save Session guard logic in state model and UI wiring.
18+
19+
### Save Session enablement
20+
Save is enabled only when all are true:
21+
- active Workspace V2 session exists (`currentHostContextId` + valid payload)
22+
- New Session ID is valid (non-empty, pattern-validated)
23+
- New Session ID does not already exist in saved library
24+
25+
Save is disabled when:
26+
- ID is empty
27+
- ID is invalid
28+
- ID already exists
29+
- no active Workspace V2 session exists
30+
31+
### Existing saved-session behavior
32+
- Existing saved sessions remain managed from Saved Session cards.
33+
- Load from card path continues to:
34+
- load saved session as active Workspace V2 session
35+
- avoid creating duplicate saved entries
36+
- avoid mutating saved library copy
37+
- recompute UI state through model refresh after load
38+
39+
### UX helper text
40+
- Updated Session Library helper text to explain:
41+
- Save creates new saved copy from active Workspace V2 session
42+
- Load makes saved session active
43+
- Overwrite (when available on cards) updates saved copy
44+
- Delete removes saved copy only
45+
46+
### Status text
47+
- Added exact required duplicate-ID message in current-state feedback:
48+
- `That session ID already exists. Use the saved session card to Load, Overwrite, or Delete it.`
49+
- Library state updates now set current-state guidance text and avoid stale messages.
50+
51+
## Validation Commands
52+
1. `node --check tools/workspace-v2/index.js`
53+
- PASS
54+
2. `node --check tests/runtime/V2SessionLibrarySaveGuard.test.mjs`
55+
- PASS
56+
3. `node --check tests/runtime/V2SessionLibraryActionCleanup.test.mjs`
57+
- PASS
58+
4. `node --check tests/runtime/V2SessionLibraryActions.test.mjs`
59+
- PASS
60+
5. `node tests/runtime/V2SessionLibrarySaveGuard.test.mjs`
61+
- PASS
62+
- Results: `tmp/v2-session-library-save-guard-results.json`
63+
6. `node tests/runtime/V2SessionLibraryActionCleanup.test.mjs`
64+
- PASS
65+
- Results: `tmp/v2-session-library-action-cleanup-results.json`
66+
7. `node tests/runtime/V2SessionLibraryActions.test.mjs`
67+
- PASS
68+
- Results: `tmp/v2-session-library-actions-results.json`
69+
70+
## Full Samples Smoke Decision
71+
- Skipped full samples smoke test.
72+
- Reason: changes are scoped to Workspace V2 Session Library save guard and UX copy, with targeted runtime test coverage.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# BUILD_PR_11_269_WORKSPACE_V2_SESSION_LIBRARY_SAVE_GUARD_AND_LOAD_EXPLANATION_UX
2+
3+
## Purpose
4+
Enforce save-button guard conditions for new session IDs and improve Session Library Load explanation UX.
5+
6+
## Files
7+
- tools/workspace-v2/index.html
8+
- tools/workspace-v2/index.js
9+
- tests/runtime/V2SessionLibrarySaveGuard.test.mjs
10+
- tests/runtime/V2SessionLibraryActionCleanup.test.mjs
11+
- tests/runtime/V2SessionLibraryActions.test.mjs
12+
- docs/dev/reports/PR_11_269_workspace_v2_session_library_save_guard_load_explanation_report.md
13+
14+
## Implementation
15+
1. Add save-guard state helpers:
16+
- active-session availability
17+
- new session ID validity
18+
- duplicate saved-session ID detection
19+
2. Extend state model with save-guard fields and wire Save button enablement to `libraryCanSave`.
20+
3. Keep top hidden non-save buttons disabled.
21+
4. Add duplicate-ID current-state status text exactly as required.
22+
5. Ensure load-from-card path retains existing non-duplicate/non-mutation behavior and recomputes UI state from loaded payload.
23+
6. Update Session Library helper text to explain Save/Load/Overwrite/Delete behavior.
24+
7. Update/add targeted runtime tests.
25+
26+
## Acceptance
27+
- Save button guard conditions enforced exactly.
28+
- Duplicate-ID message shown exactly.
29+
- Card-level load/delete/overwrite authority unchanged.
30+
- No stale/misleading Session Library status text.
31+
32+
## Validation
33+
- node --check tools/workspace-v2/index.js
34+
- node --check tests/runtime/V2SessionLibrarySaveGuard.test.mjs
35+
- node --check tests/runtime/V2SessionLibraryActionCleanup.test.mjs
36+
- node --check tests/runtime/V2SessionLibraryActions.test.mjs
37+
- node tests/runtime/V2SessionLibrarySaveGuard.test.mjs
38+
- node tests/runtime/V2SessionLibraryActionCleanup.test.mjs
39+
- node tests/runtime/V2SessionLibraryActions.test.mjs
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# PLAN_PR_11_269_WORKSPACE_V2_SESSION_LIBRARY_SAVE_GUARD_AND_LOAD_EXPLANATION_UX
2+
3+
## Purpose
4+
Add Workspace V2 Session Library new-session save guard rules and clarify load/save UX messaging.
5+
6+
## Scope
7+
- tools/workspace-v2/index.html
8+
- tools/workspace-v2/index.js
9+
- tests/runtime/V2SessionLibrarySaveGuard.test.mjs
10+
- tests/runtime/V2SessionLibraryActionCleanup.test.mjs
11+
- tests/runtime/V2SessionLibraryActions.test.mjs
12+
- docs/report only
13+
14+
## Goals
15+
- Save Session enabled only when:
16+
- active Workspace V2 session exists
17+
- new session ID is valid
18+
- ID does not already exist in saved sessions
19+
- Save Session disabled for empty, invalid, or duplicate IDs.
20+
- Existing saved session management remains card-level authoritative.
21+
- Load from saved card loads active session without mutating or duplicating saved copy.
22+
- Add concise helper text explaining Save/Load/Overwrite/Delete behavior.
23+
- Show required duplicate-ID status text.
24+
25+
## Out of Scope
26+
- No schema changes
27+
- No cross-tool changes
28+
- No merge/diff behavior changes beyond state recompute wiring
29+
30+
## Validation
31+
- node --check tools/workspace-v2/index.js
32+
- node --check tests/runtime/V2SessionLibrarySaveGuard.test.mjs
33+
- node --check tests/runtime/V2SessionLibraryActionCleanup.test.mjs
34+
- node --check tests/runtime/V2SessionLibraryActions.test.mjs
35+
- node tests/runtime/V2SessionLibrarySaveGuard.test.mjs
36+
- node tests/runtime/V2SessionLibraryActionCleanup.test.mjs
37+
- node tests/runtime/V2SessionLibraryActions.test.mjs

tests/runtime/V2SessionLibraryActionCleanup.test.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ export function run() {
4444
'<button id="workspaceV2OverwriteSessionButton" type="button" hidden>Overwrite Session</button>',
4545
'<button id="workspaceV2LoadSessionButton" type="button" hidden>Load Session</button>',
4646
'<button id="workspaceV2DeleteSessionButton" type="button" hidden>Delete Saved Session</button>',
47-
'Use Save Session to create a new saved entry. Manage existing saved sessions from their Saved Sessions cards.'
47+
'Save creates a new saved copy from the active Workspace V2 session. Load from a saved card makes that saved session the active Workspace V2 session. Overwrite (when available on cards) updates an existing saved copy, and Delete removes the saved copy only.'
4848
];
4949
requiredHtmlTokens.forEach((token) => {
5050
if (!html.includes(token)) failures.push(`Missing expected Session Library cleanup HTML token: ${token}`);
5151
});
5252

5353
const requiredJsTokens = [
54-
'this.setLibraryStatus("Saved session already exists. Manage it from its Saved Sessions card.");',
54+
'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";',
5757
'deleteSavedButton.textContent = "Delete Saved";',

tests/runtime/V2SessionLibraryActions.test.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ function evaluateSaveAction(inputId, activePayload, library) {
3737
return { message: "No active Workspace V2 session is available to save.", library: libraryMap };
3838
}
3939
if (Object.prototype.hasOwnProperty.call(libraryMap, sessionId)) {
40-
return { message: "Saved session already exists. Manage it from its Saved Sessions card.", library: libraryMap };
40+
return { message: "That session ID already exists. Use the saved session card to Load, Overwrite, or Delete it.", library: libraryMap };
4141
}
4242
libraryMap[sessionId] = activePayload;
4343
return { message: "Saved session created. Manage this session from its Saved Sessions card.", library: libraryMap };
@@ -131,7 +131,7 @@ export function run() {
131131
const requiredMessages = [
132132
"Enter a session ID before saving.",
133133
"No active Workspace V2 session is available to save.",
134-
"Saved session already exists. Manage it from its Saved Sessions card.",
134+
"That session ID already exists. Use the saved session card to Load, Overwrite, or Delete it.",
135135
"Saved session created. Manage this session from its Saved Sessions card.",
136136
"Enter a session ID before overwriting.",
137137
"No active Workspace V2 session is available to overwrite from.",
@@ -179,7 +179,7 @@ export function run() {
179179
}
180180

181181
const duplicateSave = evaluateSaveAction("saved-a", activePayload, { "saved-a": { toolId: "palette-manager-v2", version: "v2" } });
182-
if (duplicateSave.message !== "Saved session already exists. Manage it from its Saved Sessions card.") {
182+
if (duplicateSave.message !== "That session ID already exists. Use the saved session card to Load, Overwrite, or Delete it.") {
183183
failures.push("Duplicate save should be blocked with overwrite guidance.");
184184
}
185185

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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", "V2SessionLibrarySaveGuard.test.mjs");
12+
const resultsPath = path.join(repoRoot, "tmp", "v2-session-library-save-guard-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 canSaveSession({ hasActiveSession, newId, idValid, idExists }) {
27+
return Boolean(Boolean(newId) && idValid && !idExists && hasActiveSession);
28+
}
29+
30+
export function run() {
31+
const failures = [];
32+
const jsExists = fs.existsSync(jsPath);
33+
const js = jsExists ? fs.readFileSync(jsPath, "utf8") : "";
34+
const jsSyntax = checkSyntax(jsPath);
35+
const testSyntax = checkSyntax(testPath);
36+
37+
if (!jsExists) failures.push("Missing tools/workspace-v2/index.js.");
38+
if (!jsSyntax.ok) failures.push("tools/workspace-v2/index.js failed syntax check.");
39+
if (!testSyntax.ok) failures.push("tests/runtime/V2SessionLibrarySaveGuard.test.mjs failed syntax check.");
40+
41+
const requiredTokens = [
42+
"hasActiveWorkspaceSessionForSave()",
43+
"isValidNewSessionId(sessionId)",
44+
"savedSessionIdExists(sessionId)",
45+
"libraryCanSave",
46+
"this.saveSessionButton.disabled = !model.libraryCanSave;",
47+
"That session ID already exists. Use the saved session card to Load, Overwrite, or Delete it.",
48+
"loadSavedSessionById(sessionId)",
49+
"this.loadNamedSession();",
50+
"this.syncDiffAndMergeSelectionSlotsFromContextId(sessionId.trim());"
51+
];
52+
requiredTokens.forEach((token) => {
53+
if (!js.includes(token)) failures.push(`Missing required save-guard token: ${token}`);
54+
});
55+
56+
const scenarios = {
57+
emptyIdDisabled: canSaveSession({ hasActiveSession: true, newId: "", idValid: false, idExists: false }),
58+
invalidIdDisabled: canSaveSession({ hasActiveSession: true, newId: "bad id", idValid: false, idExists: false }),
59+
duplicateIdDisabled: canSaveSession({ hasActiveSession: true, newId: "session-a", idValid: true, idExists: true }),
60+
noActiveSessionDisabled: canSaveSession({ hasActiveSession: false, newId: "session-a", idValid: true, idExists: false }),
61+
validEnabled: canSaveSession({ hasActiveSession: true, newId: "session-a", idValid: true, idExists: false })
62+
};
63+
64+
if (scenarios.emptyIdDisabled) failures.push("Save should be disabled for empty ID.");
65+
if (scenarios.invalidIdDisabled) failures.push("Save should be disabled for invalid ID.");
66+
if (scenarios.duplicateIdDisabled) failures.push("Save should be disabled for duplicate ID.");
67+
if (scenarios.noActiveSessionDisabled) failures.push("Save should be disabled without active session.");
68+
if (!scenarios.validEnabled) failures.push("Save should be enabled only for valid new ID with active session.");
69+
70+
fs.mkdirSync(path.dirname(resultsPath), { recursive: true });
71+
fs.writeFileSync(resultsPath, `${JSON.stringify({
72+
generatedAt: new Date().toISOString(),
73+
failures,
74+
checks: { jsExists, jsSyntax, testSyntax },
75+
scenarios
76+
}, null, 2)}
77+
`, "utf8");
78+
79+
console.log(`v2 session-library-save-guard results: ${resultsPath}`);
80+
assert.equal(failures.length, 0, `V2 session-library-save-guard failures: ${failures.join(" | ")}`);
81+
return { failures };
82+
}
83+
84+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
85+
try {
86+
const summary = run();
87+
console.log(JSON.stringify(summary, null, 2));
88+
} catch (error) {
89+
console.error(error);
90+
process.exitCode = 1;
91+
}
92+
}

tools/workspace-v2/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ <h2>Share Session Link</h2>
5959
<h2>Session Library</h2>
6060
<label for="workspaceV2SessionName">New Session ID (Save Session)</label>
6161
<input id="workspaceV2SessionName" type="text" placeholder="session-id" />
62-
<p>Use Save Session to create a new saved entry. Manage existing saved sessions from their Saved Sessions cards.</p>
62+
<p>Save creates a new saved copy from the active Workspace V2 session. Load from a saved card makes that saved session the active Workspace V2 session. Overwrite (when available on cards) updates an existing saved copy, and Delete removes the saved copy only.</p>
6363
<div>
6464
<button id="workspaceV2SaveSessionButton" type="button">Save Session</button>
6565
<button id="workspaceV2OverwriteSessionButton" type="button" hidden>Overwrite Session</button>

0 commit comments

Comments
 (0)