@@ -33,7 +33,7 @@ function checkSyntax(filePath) {
3333 }
3434}
3535
36- function simulateWorkspaceSchemaExport ( activePayload , currentHostContextId , library ) {
36+ function simulateWorkspaceSchemaExport ( gamesSnapshot , activePayload , currentHostContextId , library ) {
3737 if ( ! activePayload || typeof activePayload !== "object" || Array . isArray ( activePayload ) ) {
3838 return {
3939 ok : false ,
@@ -56,45 +56,19 @@ function simulateWorkspaceSchemaExport(activePayload, currentHostContextId, libr
5656 serialized : ""
5757 } ;
5858 }
59- const games = [
60- {
61- id : activeHostContextId ,
62- level : "workspace-v2-active-session" ,
63- tool : activeToolId ,
64- tools : [ activeToolId ] ,
65- session : {
66- hostContextId : activeHostContextId ,
67- payload : activePayload
68- }
69- }
70- ] ;
71- Object . keys ( library ) . forEach ( ( sessionId ) => {
72- const payload = library [ sessionId ] ;
73- if ( ! payload || typeof payload !== "object" || Array . isArray ( payload ) ) {
74- return ;
75- }
76- const payloadToolId = typeof payload . toolId === "string" && payload . toolId . trim ( )
77- ? payload . toolId . trim ( )
78- : "" ;
79- if ( ! payloadToolId ) {
80- return ;
81- }
82- games . push ( {
83- id : sessionId ,
84- level : "workspace-v2-saved-session" ,
85- tool : payloadToolId ,
86- tools : [ payloadToolId ] ,
87- session : {
88- hostContextId : sessionId ,
89- payload
90- }
91- } ) ;
92- } ) ;
9359 const container = {
9460 documentKind : "workspace-manifest" ,
9561 schema : "html-js-gaming.workspace-v2-session-export/1" ,
9662 version : 1 ,
97- games
63+ games : Array . isArray ( gamesSnapshot ) ? JSON . parse ( JSON . stringify ( gamesSnapshot ) ) : [ ] ,
64+ workspaceSession : {
65+ schema : "html-js-gaming.workspace-v2-session/1" ,
66+ defaultToolId : "palette-manager-v2" ,
67+ activeToolId,
68+ activeHostContextId,
69+ activeSession : activePayload ,
70+ savedSessions : library
71+ }
9872 } ;
9973 return {
10074 ok : true ,
@@ -147,6 +121,31 @@ export function run() {
147121 }
148122 } ) ;
149123
124+ const requiredWorkspaceJsTokens = [
125+ "importWorkspaceSessionJson()" ,
126+ "exportWorkspaceSessionJson()" ,
127+ "buildWorkspaceSchemaDocument()" ,
128+ "validateWorkspaceSchemaDocument(" ,
129+ "workspaceManifestGames" ,
130+ "workspaceSession" ,
131+ "Diff requires sessions from the same tool."
132+ ] ;
133+ requiredWorkspaceJsTokens . forEach ( ( token ) => {
134+ if ( ! workspaceJs . includes ( token ) ) {
135+ failures . push ( `Missing required workspace contract JS token: ${ token } ` ) ;
136+ }
137+ } ) ;
138+
139+ const forbiddenWorkspaceJsTokens = [
140+ "workspace-v2-active-session" ,
141+ "workspace-v2-saved-session"
142+ ] ;
143+ forbiddenWorkspaceJsTokens . forEach ( ( token ) => {
144+ if ( workspaceJs . includes ( token ) ) {
145+ failures . push ( `Workspace V2 export/import should not model session snapshots in games[] (${ token } ).` ) ;
146+ }
147+ } ) ;
148+
150149 const forbiddenWorkspaceHtmlTokens = [
151150 'id="workspaceV2NavModeSelect"' ,
152151 'id="workspaceV2NavToolsActions"' ,
@@ -161,20 +160,6 @@ export function run() {
161160 }
162161 } ) ;
163162
164- const requiredWorkspaceJsTokens = [
165- "importWorkspaceSessionJson()" ,
166- "exportWorkspaceSessionJson()" ,
167- "buildWorkspaceSchemaDocument()" ,
168- "validateWorkspaceSchemaDocument(" ,
169- "tools/schemas/workspace.schema.json" ,
170- "Diff requires sessions from the same tool."
171- ] ;
172- requiredWorkspaceJsTokens . forEach ( ( token ) => {
173- if ( ! workspaceJs . includes ( token ) ) {
174- failures . push ( `Missing required workspace contract JS token: ${ token } ` ) ;
175- }
176- } ) ;
177-
178163 TOOL_IDS . forEach ( ( toolId ) => {
179164 const toolHtmlPath = path . join ( repoRoot , "tools" , toolId , "index.html" ) ;
180165 const toolHtmlExists = fs . existsSync ( toolHtmlPath ) ;
@@ -204,8 +189,17 @@ export function run() {
204189 failures . push ( `workspace.schema.json must require root.${ key } .` ) ;
205190 }
206191 } ) ;
207- if ( ! workspaceSchema . properties . games ?. items ?. properties ?. session ) {
208- failures . push ( "workspace.schema.json must allow games[].session for Workspace V2 session export/import." ) ;
192+ if ( workspaceSchema . properties . games ?. items ?. properties ?. session ) {
193+ failures . push ( "workspace.schema.json must not allow games[].session." ) ;
194+ }
195+ if ( ! workspaceSchema . properties . workspaceSession ) {
196+ failures . push ( "workspace.schema.json must define optional root.workspaceSession." ) ;
197+ } else {
198+ const requiredWorkspaceSessionKeys = workspaceSchema . properties . workspaceSession . required || [ ] ;
199+ const expectedWorkspaceSessionKeys = [ "schema" , "defaultToolId" , "activeToolId" , "activeHostContextId" , "activeSession" , "savedSessions" ] ;
200+ if ( JSON . stringify ( requiredWorkspaceSessionKeys ) !== JSON . stringify ( expectedWorkspaceSessionKeys ) ) {
201+ failures . push ( "workspace.schema.json workspaceSession required keys do not match minimal contract." ) ;
202+ }
209203 }
210204 }
211205
@@ -221,47 +215,64 @@ export function run() {
221215 payloadJson : { assetCatalog : { entries : [ { id : "asset-1" } ] } }
222216 }
223217 } ;
224- const workspaceExport = simulateWorkspaceSchemaExport ( activePayload , "palette-manager-v2-1234567890123-abcd1234" , library ) ;
218+ const projectGames = [
219+ {
220+ id : "0207" ,
221+ level : "phase-02" ,
222+ tool : "sprite-editor" ,
223+ tools : [ "sprite-editor" ] ,
224+ palette : "samples/phase-02/0207/sample.0207.palette.json"
225+ }
226+ ] ;
227+ const workspaceExport = simulateWorkspaceSchemaExport ( projectGames , activePayload , "palette-manager-v2-1234567890123-abcd1234" , library ) ;
225228 if ( ! workspaceExport . ok ) failures . push ( "Workspace export should succeed when active payload exists." ) ;
226229 const workspaceExportParsed = workspaceExport . serialized ? JSON . parse ( workspaceExport . serialized ) : null ;
227230 if ( ! workspaceExportParsed || typeof workspaceExportParsed !== "object" || Array . isArray ( workspaceExportParsed ) ) {
228231 failures . push ( "Workspace export should be an object matching workspace.schema.json." ) ;
229232 } else {
230233 const rootKeys = Object . keys ( workspaceExportParsed ) . sort ( ( left , right ) => left . localeCompare ( right ) ) ;
231- const expectedRootKeys = [ "documentKind" , "games" , "schema" , "version" ] ;
234+ const expectedRootKeys = [ "documentKind" , "games" , "schema" , "version" , "workspaceSession" ] ;
232235 if ( JSON . stringify ( rootKeys ) !== JSON . stringify ( expectedRootKeys ) ) {
233236 failures . push ( `Workspace export root keys mismatch. Expected ${ expectedRootKeys . join ( ", " ) } and received ${ rootKeys . join ( ", " ) } .` ) ;
234237 }
235- if ( ! Array . isArray ( workspaceExportParsed . games ) || workspaceExportParsed . games . length < 1 ) {
236- failures . push ( "Workspace export must include games array entries." ) ;
237- }
238- if ( Object . prototype . hasOwnProperty . call ( workspaceExportParsed , "workspaceSession" ) ) {
239- failures . push ( "Workspace export must not include workspaceSession wrapper." ) ;
238+ if ( ! Array . isArray ( workspaceExportParsed . games ) || workspaceExportParsed . games . length !== 1 ) {
239+ failures . push ( "Workspace export must preserve project games[] entries." ) ;
240+ } else {
241+ if ( Object . prototype . hasOwnProperty . call ( workspaceExportParsed . games [ 0 ] , "session" ) ) {
242+ failures . push ( "Workspace export games[] entries must not include session snapshots." ) ;
243+ }
244+ if ( workspaceExportParsed . games [ 0 ] . id !== "0207" || workspaceExportParsed . games [ 0 ] . tool !== "sprite-editor" ) {
245+ failures . push ( "Workspace export must keep original games[] entry values unchanged." ) ;
246+ }
240247 }
241248 if ( Object . prototype . hasOwnProperty . call ( workspaceExportParsed , "workspaceV2Session" ) ) {
242249 failures . push ( "Workspace export must not include workspaceV2Session wrapper." ) ;
243250 }
244251 if ( Object . prototype . hasOwnProperty . call ( workspaceExportParsed , "toolSessions" ) ) {
245252 failures . push ( "Workspace export must not include toolSessions wrapper." ) ;
246253 }
247- if ( Object . prototype . hasOwnProperty . call ( workspaceExportParsed , "savedSessions" ) ) {
248- failures . push ( "Workspace export must not include savedSessions wrapper." ) ;
254+ if ( Object . prototype . hasOwnProperty . call ( workspaceExportParsed , "savedSessions" ) && ! Object . prototype . hasOwnProperty . call ( workspaceExportParsed , "workspaceSession" ) ) {
255+ failures . push ( "Workspace export must not include root savedSessions wrapper." ) ;
249256 }
250257 if ( Object . prototype . hasOwnProperty . call ( workspaceExportParsed , "exportedAt" ) ) {
251258 failures . push ( "Workspace export must not include exportedAt root field." ) ;
252259 }
253- const activeEntry = workspaceExportParsed . games . find ( ( entry ) => entry . level === "workspace-v2-active-session" ) ;
254- if ( ! activeEntry ) {
255- failures . push ( "Workspace export must include one active session entry in games." ) ;
260+ if ( ! workspaceExportParsed . workspaceSession || typeof workspaceExportParsed . workspaceSession !== "object" ) {
261+ failures . push ( "Workspace export must include workspaceSession resume block." ) ;
256262 } else {
257- if ( activeEntry . tool !== "palette-manager-v2" ) {
258- failures . push ( "Active session games entry must preserve tool id." ) ;
263+ const workspaceSessionKeys = Object . keys ( workspaceExportParsed . workspaceSession ) . sort ( ( left , right ) => left . localeCompare ( right ) ) ;
264+ const expectedWorkspaceSessionKeys = [ "activeHostContextId" , "activeSession" , "activeToolId" , "defaultToolId" , "savedSessions" , "schema" ] ;
265+ if ( JSON . stringify ( workspaceSessionKeys ) !== JSON . stringify ( expectedWorkspaceSessionKeys ) ) {
266+ failures . push ( "workspaceSession keys do not match minimal allowed set." ) ;
267+ }
268+ if ( workspaceExportParsed . workspaceSession . activeHostContextId !== "palette-manager-v2-1234567890123-abcd1234" ) {
269+ failures . push ( "workspaceSession.activeHostContextId must preserve active hostContextId." ) ;
259270 }
260- if ( activeEntry . session ?. hostContextId !== "palette-manager-v2-1234567890123-abcd1234 " ) {
261- failures . push ( "Active session games entry must preserve hostContextId ." ) ;
271+ if ( workspaceExportParsed . workspaceSession . activeToolId !== "palette-manager-v2" ) {
272+ failures . push ( "workspaceSession.activeToolId must preserve active toolId ." ) ;
262273 }
263- if ( JSON . stringify ( activeEntry . session ?. payload ) !== JSON . stringify ( activePayload ) ) {
264- failures . push ( "Active session games entry must preserve payload." ) ;
274+ if ( JSON . stringify ( workspaceExportParsed . workspaceSession . activeSession ) !== JSON . stringify ( activePayload ) ) {
275+ failures . push ( "workspaceSession.activeSession must preserve active payload." ) ;
265276 }
266277 }
267278 }
0 commit comments