@@ -7,6 +7,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
77const __filename = fileURLToPath ( import . meta. url ) ;
88const __dirname = path . dirname ( __filename ) ;
99const repoRoot = path . resolve ( __dirname , ".." , ".." ) ;
10+ const workspaceHtmlPath = path . join ( repoRoot , "tools" , "workspace-v2" , "index.html" ) ;
1011const workspaceJsPath = path . join ( repoRoot , "tools" , "workspace-v2" , "index.js" ) ;
1112const testPath = path . join ( repoRoot , "tests" , "runtime" , "V2CurrentSessionExport.test.mjs" ) ;
1213const resultsPath = path . join ( repoRoot , "tmp" , "v2-current-session-export-results.json" ) ;
@@ -23,7 +24,7 @@ function checkSyntax(filePath) {
2324 }
2425}
2526
26- function simulateExport ( activePayload , selectedToolId , currentHostContextId , library , history , selection , mergeAuditLog ) {
27+ function simulateToolModeExport ( activePayload , currentHostContextId ) {
2728 if ( ! activePayload || typeof activePayload !== "object" || Array . isArray ( activePayload ) ) {
2829 return {
2930 ok : false ,
@@ -33,112 +34,195 @@ function simulateExport(activePayload, selectedToolId, currentHostContextId, lib
3334 } ;
3435 }
3536 const payloadToolId = typeof activePayload . toolId === "string" ? activePayload . toolId . trim ( ) : "" ;
36- const filenameToolId = payloadToolId || selectedToolId || "workspace-v2" ;
37- const filenameSessionId = typeof currentHostContextId === "string" && currentHostContextId . trim ( )
37+ const toolIdForFile = payloadToolId || "workspace-v2" ;
38+ const sessionIdForFile = typeof currentHostContextId === "string" && currentHostContextId . trim ( )
3839 ? currentHostContextId . trim ( )
3940 : "session" ;
40- const exportedContainer = {
41+ return {
42+ ok : true ,
43+ status : "Exported current workspace session JSON." ,
44+ filename : `${ toolIdForFile } -${ sessionIdForFile } .json` ,
45+ serialized : JSON . stringify ( activePayload , null , 2 )
46+ } ;
47+ }
48+
49+ function simulateWorkspaceModeExport ( activePayload , currentHostContextId , library ) {
50+ if ( ! activePayload || typeof activePayload !== "object" || Array . isArray ( activePayload ) ) {
51+ return {
52+ ok : false ,
53+ status : "No active Workspace V2 session is available to export." ,
54+ filename : "" ,
55+ serialized : ""
56+ } ;
57+ }
58+ const activeToolId = typeof activePayload . toolId === "string" && activePayload . toolId . trim ( )
59+ ? activePayload . toolId . trim ( )
60+ : "workspace-v2" ;
61+ const activeHostContextId = typeof currentHostContextId === "string" && currentHostContextId . trim ( )
62+ ? currentHostContextId . trim ( )
63+ : "" ;
64+ const toolSessions = { } ;
65+ if ( activeHostContextId ) {
66+ toolSessions [ activeToolId ] = {
67+ [ activeHostContextId ] : activePayload
68+ } ;
69+ }
70+ Object . keys ( library ) . forEach ( ( sessionId ) => {
71+ const payload = library [ sessionId ] ;
72+ if ( ! payload || typeof payload !== "object" || Array . isArray ( payload ) ) {
73+ return ;
74+ }
75+ const payloadToolId = typeof payload . toolId === "string" && payload . toolId . trim ( )
76+ ? payload . toolId . trim ( )
77+ : activeToolId ;
78+ if ( ! Object . prototype . hasOwnProperty . call ( toolSessions , payloadToolId ) ) {
79+ toolSessions [ payloadToolId ] = { } ;
80+ }
81+ toolSessions [ payloadToolId ] [ sessionId ] = payload ;
82+ } ) ;
83+ const container = {
4184 version : "v2" ,
4285 toolId : "workspace-v2" ,
4386 workspaceSession : {
4487 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
88+ workspaceSessionId : activeHostContextId ,
89+ defaultToolId : "palette-manager-v2" ,
90+ activeToolId,
91+ activeHostContextId,
92+ toolSessions,
93+ savedSessions : library ,
94+ exportedAt : "2026-05-02T00:00:00.000Z"
5395 }
5496 } ;
5597 return {
5698 ok : true ,
5799 status : "Exported current workspace session JSON." ,
58- filename : `workspace-v2-${ filenameToolId } -${ filenameSessionId } .json` ,
59- serialized : JSON . stringify ( exportedContainer , null , 2 )
100+ filename : `workspace-v2-${ activeToolId } -${ activeHostContextId || "session" } .json` ,
101+ serialized : JSON . stringify ( container , null , 2 )
60102 } ;
61103}
62104
63105export function run ( ) {
64106 const failures = [ ] ;
107+ const workspaceHtmlExists = fs . existsSync ( workspaceHtmlPath ) ;
65108 const workspaceJsExists = fs . existsSync ( workspaceJsPath ) ;
109+ const workspaceHtml = workspaceHtmlExists ? fs . readFileSync ( workspaceHtmlPath , "utf8" ) : "" ;
66110 const workspaceJs = workspaceJsExists ? fs . readFileSync ( workspaceJsPath , "utf8" ) : "" ;
67111 const workspaceJsSyntax = checkSyntax ( workspaceJsPath ) ;
68112 const testSyntax = checkSyntax ( testPath ) ;
69113
114+ if ( ! workspaceHtmlExists ) failures . push ( "Missing tools/workspace-v2/index.html." ) ;
70115 if ( ! workspaceJsExists ) failures . push ( "Missing tools/workspace-v2/index.js." ) ;
71116 if ( ! workspaceJsSyntax . ok ) failures . push ( "tools/workspace-v2/index.js failed syntax check." ) ;
72117 if ( ! testSyntax . ok ) failures . push ( "tests/runtime/V2CurrentSessionExport.test.mjs failed syntax check." ) ;
73118
74- const requiredTokens = [
75- "readActiveSessionPayloadForLibraryActions()" ,
76- "No active Workspace V2 session is available to export." ,
77- "Exported current workspace session JSON." ,
78- "URL.createObjectURL" ,
79- "downloadLink.download" ,
80- "workspaceSession" ,
81- "sessionLibrary" ,
82- "sessionHistory" ,
83- "activeSessionPayload"
119+ const requiredHtmlTokens = [
120+ 'id="workspaceV2NavModeSelect"' ,
121+ 'id="workspaceV2NavToolsActions"' ,
122+ 'id="workspaceV2NavWorkspaceActions"' ,
123+ 'id="workspaceV2ImportWorkspaceButton"' ,
124+ 'id="workspaceV2ExportWorkspaceButton"'
125+ ] ;
126+ requiredHtmlTokens . forEach ( ( token ) => {
127+ if ( ! workspaceHtml . includes ( token ) ) {
128+ failures . push ( `Missing nav mode separation HTML token: ${ token } ` ) ;
129+ }
130+ } ) ;
131+
132+ const requiredJsTokens = [
133+ "currentNavMode()" ,
134+ "renderNavModeActions()" ,
135+ "importWorkspaceSessionJson()" ,
136+ "exportWorkspaceSessionJson()" ,
137+ "buildPortableWorkspaceSessionContainer()" ,
138+ "if (!this.inToolsNavMode())" ,
139+ "if (!this.inWorkspaceNavMode())" ,
140+ "toolSessions" ,
141+ "savedSessions" ,
142+ "runtime-only fields are not allowed in portable workspace payload"
84143 ] ;
85- requiredTokens . forEach ( ( token ) => {
144+ requiredJsTokens . forEach ( ( token ) => {
86145 if ( ! workspaceJs . includes ( token ) ) {
87- failures . push ( `Missing export token: ${ token } ` ) ;
146+ failures . push ( `Missing nav/ export contract JS token: ${ token } ` ) ;
88147 }
89148 } ) ;
90- if ( workspaceJs . includes ( "No current session payload to export. Load fixture or import JSON first." ) ) {
91- failures . push ( "Legacy export-empty message is still present." ) ;
92- }
93149
94150 const activePayload = {
95151 version : "v2" ,
96152 toolId : "palette-manager-v2" ,
97- payloadJson : { swatches : [ "#000000 " , "#ffffff " ] }
153+ payloadJson : { swatches : [ "#112233 " , "#445566 " ] }
98154 } ;
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- ) ;
108- if ( ! activeResult . ok ) failures . push ( "Active session export should be allowed." ) ;
109- if ( activeResult . status !== "Exported current workspace session JSON." ) {
110- failures . push ( "Active session export status mismatch." ) ;
111- }
112- if ( activeResult . filename !== "workspace-v2-palette-manager-v2-palette-manager-v2-1234567890123-abcd1234.json" ) {
113- failures . push ( "Export filename should include tool/session identity." ) ;
155+ const library = {
156+ "asset-browser-v2-saved" : {
157+ version : "v2" ,
158+ toolId : "asset-browser-v2" ,
159+ payloadJson : { assetCatalog : { entries : [ { id : "asset-1" } ] } }
160+ }
161+ } ;
162+
163+ const toolExport = simulateToolModeExport ( activePayload , "palette-manager-v2-1234567890123-abcd1234" ) ;
164+ if ( ! toolExport . ok ) failures . push ( "Tool mode export should succeed when active payload exists." ) ;
165+ if ( toolExport . filename !== "palette-manager-v2-palette-manager-v2-1234567890123-abcd1234.json" ) {
166+ failures . push ( "Tool mode export filename mismatch." ) ;
114167 }
115- const parsedActive = activeResult . serialized ? JSON . parse ( activeResult . serialized ) : null ;
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." ) ;
168+ const toolExportParsed = toolExport . serialized ? JSON . parse ( toolExport . serialized ) : null ;
169+ if ( JSON . stringify ( toolExportParsed ) !== JSON . stringify ( activePayload ) ) {
170+ failures . push ( "Tool mode export should preserve active tool payload exactly." ) ;
122171 }
123172
124- const noActiveResult = simulateExport ( null , "asset-browser -v2" , "" , { } , [ ] , { sessionA : "" , sessionB : "" } , [ ] ) ;
125- if ( noActiveResult . ok ) failures . push ( "Export should be blocked when no active session exists." ) ;
126- if ( noActiveResult . status !== "No active Workspace V2 session is available to export. " ) {
127- failures . push ( "No-active export status mismatch." ) ;
173+ const workspaceExport = simulateWorkspaceModeExport ( activePayload , "palette-manager -v2-1234567890123-abcd1234 " , library ) ;
174+ if ( ! workspaceExport . ok ) failures . push ( "Workspace mode export should succeed when active payload exists." ) ;
175+ if ( workspaceExport . filename !== "workspace-v2-palette-manager-v2-palette-manager-v2-1234567890123-abcd1234.json " ) {
176+ failures . push ( "Workspace mode export filename mismatch." ) ;
128177 }
178+ const workspaceExportParsed = workspaceExport . serialized ? JSON . parse ( workspaceExport . serialized ) : null ;
179+ if ( ! workspaceExportParsed || typeof workspaceExportParsed !== "object" || Array . isArray ( workspaceExportParsed ) ) {
180+ failures . push ( "Workspace mode export should be an object container." ) ;
181+ } else if ( ! workspaceExportParsed . workspaceSession || typeof workspaceExportParsed . workspaceSession !== "object" ) {
182+ failures . push ( "Workspace mode export should include workspaceSession container." ) ;
183+ } else {
184+ if ( Object . prototype . hasOwnProperty . call ( workspaceExportParsed . workspaceSession , "sessionHistory" ) ) {
185+ failures . push ( "Workspace export must not include sessionHistory runtime field." ) ;
186+ }
187+ if ( Object . prototype . hasOwnProperty . call ( workspaceExportParsed . workspaceSession , "sessionSelection" ) ) {
188+ failures . push ( "Workspace export must not include sessionSelection runtime field." ) ;
189+ }
190+ if ( Object . prototype . hasOwnProperty . call ( workspaceExportParsed . workspaceSession , "mergeAuditLog" ) ) {
191+ failures . push ( "Workspace export must not include mergeAuditLog runtime field." ) ;
192+ }
193+ if ( Object . prototype . hasOwnProperty . call ( workspaceExportParsed . workspaceSession , "activeSessionPayload" ) ) {
194+ failures . push ( "Workspace export must not include lone activeSessionPayload runtime field." ) ;
195+ }
196+ if ( JSON . stringify ( workspaceExportParsed . workspaceSession . savedSessions ) !== JSON . stringify ( library ) ) {
197+ failures . push ( "Workspace export should preserve savedSessions payload map." ) ;
198+ }
199+ const activeEntry =
200+ workspaceExportParsed . workspaceSession . toolSessions ?. [ "palette-manager-v2" ] ?. [ "palette-manager-v2-1234567890123-abcd1234" ] ;
201+ if ( JSON . stringify ( activeEntry ) !== JSON . stringify ( activePayload ) ) {
202+ failures . push ( "Workspace export should preserve active payload under toolSessions by tool/session id." ) ;
203+ }
204+ }
205+
206+ const noActiveToolExport = simulateToolModeExport ( null , "" ) ;
207+ if ( noActiveToolExport . ok ) failures . push ( "Tool export should fail when active payload is missing." ) ;
208+ const noActiveWorkspaceExport = simulateWorkspaceModeExport ( null , "" , { } ) ;
209+ if ( noActiveWorkspaceExport . ok ) failures . push ( "Workspace export should fail when active payload is missing." ) ;
129210
130211 fs . mkdirSync ( path . dirname ( resultsPath ) , { recursive : true } ) ;
131212 fs . writeFileSync ( resultsPath , `${ JSON . stringify ( {
132213 generatedAt : new Date ( ) . toISOString ( ) ,
133214 failures,
134215 checks : {
216+ workspaceHtmlExists,
135217 workspaceJsExists,
136218 workspaceJsSyntax,
137219 testSyntax
138220 } ,
139221 scenarios : {
140- activeResult,
141- noActiveResult
222+ toolExport,
223+ workspaceExport,
224+ noActiveToolExport,
225+ noActiveWorkspaceExport
142226 }
143227 } , null , 2 ) } \n`, "utf8" ) ;
144228
0 commit comments