Skip to content

Commit 0684dee

Browse files
author
DavidQ
committed
Export full Workspace V2 session container instead of single active tool payload - PR_11_276
1 parent 72c98a8 commit 0684dee

5 files changed

Lines changed: 175 additions & 13 deletions
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# PR_11_276 Workspace V2 Full-Session Export Contract Correction Report
2+
3+
## Scope
4+
Workspace V2 export path only.
5+
6+
## Files Changed
7+
- tools/workspace-v2/index.js
8+
- tests/runtime/V2CurrentSessionExport.test.mjs
9+
- docs/pr/PLAN_PR_11_276_WORKSPACE_V2_FULL_SESSION_EXPORT_CONTRACT_CORRECTION.md
10+
- docs/pr/BUILD_PR_11_276_WORKSPACE_V2_FULL_SESSION_EXPORT_CONTRACT_CORRECTION.md
11+
- docs/dev/reports/PR_11_276_workspace_v2_full_session_export_contract_correction_report.md
12+
13+
## Implementation Summary
14+
- Corrected `Export Current Session JSON` to export a full Workspace V2 container instead of only the active tool session payload.
15+
- Export still validates active session via `readActiveSessionPayloadForLibraryActions()` so export gate matches active-session source used by Save session flows.
16+
- Exported JSON now includes:
17+
- workspace/session identity
18+
- active tool identity
19+
- active hostContextId
20+
- active session payload
21+
- session library entries
22+
- recent session history entries
23+
- persisted session selection metadata
24+
- merge audit metadata
25+
- Export filename now includes workspace/tool/session identity (`workspace-v2-<toolId>-<hostContextId>.json`).
26+
- No fallback/default payload export path introduced.
27+
28+
## Validation Commands
29+
1. `node --check tools/workspace-v2/index.js`
30+
- PASS
31+
2. `node --check tests/runtime/V2CurrentSessionExport.test.mjs`
32+
- PASS
33+
3. `node tests/runtime/V2CurrentSessionExport.test.mjs`
34+
- PASS
35+
- Results: `tmp/v2-current-session-export-results.json`
36+
37+
## Full Samples Smoke Decision
38+
- Skipped full samples smoke test.
39+
- Reason: change is scoped to Workspace V2 export serialization and covered by targeted runtime validation.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# BUILD_PR_11_276_WORKSPACE_V2_FULL_SESSION_EXPORT_CONTRACT_CORRECTION
2+
3+
## Purpose
4+
Implement full Workspace V2 session-container export from the current export control path.
5+
6+
## Files
7+
- tools/workspace-v2/index.js
8+
- tests/runtime/V2CurrentSessionExport.test.mjs
9+
- docs/dev/reports/PR_11_276_workspace_v2_full_session_export_contract_correction_report.md
10+
11+
## Implementation
12+
1. Keep active-session export gate based on `readActiveSessionPayloadForLibraryActions()`.
13+
2. Build export payload as Workspace wrapper object (not single tool payload):
14+
- `version`, `toolId: workspace-v2`
15+
- `workspaceSession` container including identity, active session payload, sessionLibrary, sessionHistory, selection metadata, and merge audit metadata.
16+
3. Keep exact success/failure export statuses:
17+
- success: `Exported current workspace session JSON.`
18+
- no active session: `No active Workspace V2 session is available to export.`
19+
4. Block export when Session Library is invalid to prevent silent data loss.
20+
5. Keep JSON download behavior and include workspace/tool/session identity in filename.
21+
6. Update targeted runtime test to validate full-session-wrapper export contract.
22+
23+
## Acceptance
24+
- Export outputs full Workspace V2 container.
25+
- Active tool payload remains intact inside container.
26+
- Session metadata required by save/load/diff/merge context is included.
27+
- No export fallback/default data.
28+
29+
## Validation
30+
- node --check tools/workspace-v2/index.js
31+
- node --check tests/runtime/V2CurrentSessionExport.test.mjs
32+
- node tests/runtime/V2CurrentSessionExport.test.mjs
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# PLAN_PR_11_276_WORKSPACE_V2_FULL_SESSION_EXPORT_CONTRACT_CORRECTION
2+
3+
## Purpose
4+
Correct Workspace V2 export so it outputs the full Workspace session container instead of a single active-tool payload object.
5+
6+
## Scope
7+
- tools/workspace-v2/index.js
8+
- tests/runtime/V2CurrentSessionExport.test.mjs
9+
- docs/report only
10+
11+
## Goals
12+
- Export uses active-session validation gate.
13+
- Export serializes a Workspace V2 session wrapper preserving:
14+
- workspace/session identity
15+
- active/default tool identity
16+
- included session library payloads
17+
- included recent session history payloads
18+
- session selection metadata
19+
- merge audit metadata
20+
- Export filename includes workspace + tool + session identity when available.
21+
- Export does not flatten to a single tool payload object.
22+
- No fallback/default export payload path.
23+
24+
## Out of Scope
25+
- No schema file edits.
26+
- No unrelated tool changes.
27+
- No session library/diff/merge behavior-path changes beyond export-source consistency.
28+
29+
## Validation
30+
- node --check tools/workspace-v2/index.js
31+
- node --check tests/runtime/V2CurrentSessionExport.test.mjs
32+
- node tests/runtime/V2CurrentSessionExport.test.mjs

tests/runtime/V2CurrentSessionExport.test.mjs

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function checkSyntax(filePath) {
2323
}
2424
}
2525

26-
function simulateExport(activePayload, selectedToolId, currentHostContextId) {
26+
function simulateExport(activePayload, selectedToolId, currentHostContextId, library, history, selection, mergeAuditLog) {
2727
if (!activePayload || typeof activePayload !== "object" || Array.isArray(activePayload)) {
2828
return {
2929
ok: false,
@@ -37,11 +37,26 @@ function simulateExport(activePayload, selectedToolId, currentHostContextId) {
3737
const filenameSessionId = typeof currentHostContextId === "string" && currentHostContextId.trim()
3838
? currentHostContextId.trim()
3939
: "session";
40+
const exportedContainer = {
41+
version: "v2",
42+
toolId: "workspace-v2",
43+
workspaceSession: {
44+
workspaceToolId: "workspace-v2",
45+
workspaceSessionId: filenameSessionId === "session" ? "" : filenameSessionId,
46+
activeToolId: filenameToolId,
47+
activeHostContextId: filenameSessionId === "session" ? "" : filenameSessionId,
48+
activeSessionPayload: activePayload,
49+
sessionLibrary: library,
50+
sessionHistory: history,
51+
sessionSelection: selection,
52+
mergeAuditLog
53+
}
54+
};
4055
return {
4156
ok: true,
4257
status: "Exported current workspace session JSON.",
43-
filename: `${filenameToolId}-${filenameSessionId}.json`,
44-
serialized: JSON.stringify(activePayload, null, 2)
58+
filename: `workspace-v2-${filenameToolId}-${filenameSessionId}.json`,
59+
serialized: JSON.stringify(exportedContainer, null, 2)
4560
};
4661
}
4762

@@ -61,7 +76,11 @@ export function run() {
6176
"No active Workspace V2 session is available to export.",
6277
"Exported current workspace session JSON.",
6378
"URL.createObjectURL",
64-
"downloadLink.download"
79+
"downloadLink.download",
80+
"workspaceSession",
81+
"sessionLibrary",
82+
"sessionHistory",
83+
"activeSessionPayload"
6584
];
6685
requiredTokens.forEach((token) => {
6786
if (!workspaceJs.includes(token)) {
@@ -77,20 +96,32 @@ export function run() {
7796
toolId: "palette-manager-v2",
7897
payloadJson: { swatches: ["#000000", "#ffffff"] }
7998
};
80-
const activeResult = simulateExport(activePayload, "asset-browser-v2", "palette-manager-v2-1234567890123-abcd1234");
99+
const activeResult = simulateExport(
100+
activePayload,
101+
"asset-browser-v2",
102+
"palette-manager-v2-1234567890123-abcd1234",
103+
{ "saved-1": { version: "v2", toolId: "asset-browser-v2", payloadJson: { id: 1 } } },
104+
[{ hostContextId: "palette-manager-v2-1234567890123-abcd1234", tool: "palette-manager-v2", timestamp: "2026-05-02T12:00:00.000Z", payload: activePayload }],
105+
{ sessionA: "a", sessionB: "b" },
106+
[{ sourceSessionContextId: "a", targetSessionContextId: "b" }]
107+
);
81108
if (!activeResult.ok) failures.push("Active session export should be allowed.");
82109
if (activeResult.status !== "Exported current workspace session JSON.") {
83110
failures.push("Active session export status mismatch.");
84111
}
85-
if (activeResult.filename !== "palette-manager-v2-palette-manager-v2-1234567890123-abcd1234.json") {
112+
if (activeResult.filename !== "workspace-v2-palette-manager-v2-palette-manager-v2-1234567890123-abcd1234.json") {
86113
failures.push("Export filename should include tool/session identity.");
87114
}
88115
const parsedActive = activeResult.serialized ? JSON.parse(activeResult.serialized) : null;
89-
if (JSON.stringify(parsedActive) !== JSON.stringify(activePayload)) {
90-
failures.push("Exported JSON does not preserve active session payload exactly.");
116+
if (!parsedActive || typeof parsedActive !== "object" || Array.isArray(parsedActive)) {
117+
failures.push("Exported JSON should be a workspace container object.");
118+
} else if (!parsedActive.workspaceSession || typeof parsedActive.workspaceSession !== "object") {
119+
failures.push("Exported JSON should include workspaceSession container.");
120+
} else if (JSON.stringify(parsedActive.workspaceSession.activeSessionPayload) !== JSON.stringify(activePayload)) {
121+
failures.push("Exported workspace container does not preserve active session payload exactly.");
91122
}
92123

93-
const noActiveResult = simulateExport(null, "asset-browser-v2", "");
124+
const noActiveResult = simulateExport(null, "asset-browser-v2", "", {}, [], { sessionA: "", sessionB: "" }, []);
94125
if (noActiveResult.ok) failures.push("Export should be blocked when no active session exists.");
95126
if (noActiveResult.status !== "No active Workspace V2 session is available to export.") {
96127
failures.push("No-active export status mismatch.");

tools/workspace-v2/index.js

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2789,13 +2789,41 @@ class WorkspaceV2SessionProducer {
27892789
return;
27902790
}
27912791
try {
2792-
const serialized = JSON.stringify(activePayload, null, 2);
2793-
const payloadToolId = typeof activePayload.toolId === "string" ? activePayload.toolId.trim() : "";
2794-
const filenameToolId = payloadToolId || this.selectedToolId() || "workspace-v2";
2792+
const library = this.readSessionLibrary();
2793+
if (library === null) {
2794+
this.statusNode.textContent = "Workspace V2 export blocked: Session Library is invalid.";
2795+
return;
2796+
}
2797+
const history = this.readSessionHistory();
2798+
const persistedSelection = this.readPersistedSessionSelection();
2799+
const mergeAuditRaw = localStorage.getItem(this.mergeAuditStorageKey);
2800+
const mergeAuditParsed = this.safeParseJson(typeof mergeAuditRaw === "string" ? mergeAuditRaw : "");
2801+
const mergeAuditEntries = mergeAuditParsed.ok && Array.isArray(mergeAuditParsed.value) ? mergeAuditParsed.value : [];
2802+
const activeToolId = typeof activePayload.toolId === "string" && activePayload.toolId.trim()
2803+
? activePayload.toolId.trim()
2804+
: this.selectedToolId();
2805+
const workspaceContainer = {
2806+
version: "v2",
2807+
toolId: "workspace-v2",
2808+
workspaceSession: {
2809+
workspaceToolId: "workspace-v2",
2810+
workspaceSessionId: typeof this.currentHostContextId === "string" ? this.currentHostContextId.trim() : "",
2811+
activeToolId,
2812+
activeHostContextId: typeof this.currentHostContextId === "string" ? this.currentHostContextId.trim() : "",
2813+
activeSessionPayload: activePayload,
2814+
sessionLibrary: library,
2815+
sessionHistory: history,
2816+
sessionSelection: persistedSelection,
2817+
mergeAuditLog: mergeAuditEntries,
2818+
exportedAt: new Date().toISOString()
2819+
}
2820+
};
2821+
const serialized = JSON.stringify(workspaceContainer, null, 2);
2822+
const filenameToolId = activeToolId || "workspace-v2";
27952823
const filenameSessionId = typeof this.currentHostContextId === "string" && this.currentHostContextId.trim()
27962824
? this.currentHostContextId.trim()
27972825
: "session";
2798-
const downloadFileName = `${filenameToolId}-${filenameSessionId}.json`;
2826+
const downloadFileName = `workspace-v2-${filenameToolId}-${filenameSessionId}.json`;
27992827
const fileBlob = new Blob([serialized], { type: "application/json" });
28002828
const fileUrl = URL.createObjectURL(fileBlob);
28012829
const downloadLink = document.createElement("a");

0 commit comments

Comments
 (0)