@@ -1207,13 +1207,14 @@ class WorkspaceV2SessionProducer {
12071207 if ( ! parsed . ok || ! this . isValidSessionPayload ( parsed . value ) ) {
12081208 return null ;
12091209 }
1210+ const payloadValidation = this . validateWorkspaceToolSessionPayload ( parsed . value , "activeSession" ) ;
1211+ if ( ! payloadValidation . ok ) {
1212+ return null ;
1213+ }
12101214 return parsed . value ;
12111215 }
12121216
12131217 resolveActiveSessionPayloadForWorkspaceManifest ( ) {
1214- if ( this . isValidSessionPayload ( this . currentSessionPayload ) ) {
1215- return this . currentSessionPayload ;
1216- }
12171218 return this . readActiveSessionPayloadForLibraryActions ( ) ;
12181219 }
12191220
@@ -1226,7 +1227,7 @@ class WorkspaceV2SessionProducer {
12261227 if ( ! recentEntry ) {
12271228 return null ;
12281229 }
1229- return this . resolveSessionPayloadFromContextId ( sessionId . trim ( ) , recentEntry . payload ) ;
1230+ return this . resolveSessionPayloadFromContextId ( sessionId . trim ( ) ) ;
12301231 }
12311232
12321233 readSessionPayloadForLibraryWrite ( sessionId ) {
@@ -1241,6 +1242,10 @@ class WorkspaceV2SessionProducer {
12411242 if ( ! parsed . ok || ! this . isValidSessionPayload ( parsed . value ) ) {
12421243 return null ;
12431244 }
1245+ const payloadValidation = this . validateWorkspaceToolSessionPayload ( parsed . value , `sessionStorage.${ sessionId . trim ( ) } ` ) ;
1246+ if ( ! payloadValidation . ok ) {
1247+ return null ;
1248+ }
12441249 return parsed . value ;
12451250 }
12461251
@@ -1249,9 +1254,6 @@ class WorkspaceV2SessionProducer {
12491254 if ( this . isValidSessionPayload ( activePayload ) ) {
12501255 return activePayload ;
12511256 }
1252- if ( this . isValidSessionPayload ( this . currentSessionPayload ) ) {
1253- return this . currentSessionPayload ;
1254- }
12551257 return null ;
12561258 }
12571259
@@ -1450,27 +1452,39 @@ class WorkspaceV2SessionProducer {
14501452 if ( ! this . isValidSessionPayload ( sessionPayload ) ) {
14511453 return { ok : false , message : "Session activation failed: payload is invalid." } ;
14521454 }
1453- const versionedPayload = this . withSessionVersion ( sessionPayload ) ;
1454- const sizeValidation = this . validateSessionPayloadSize ( versionedPayload ) ;
1455+ const payloadValidation = this . validateWorkspaceToolSessionPayload ( sessionPayload , "sessionActivation" ) ;
1456+ if ( ! payloadValidation . ok ) {
1457+ return { ok : false , message : payloadValidation . message } ;
1458+ }
1459+ const sizeValidation = this . validateSessionPayloadSize ( sessionPayload ) ;
14551460 if ( ! sizeValidation . ok ) {
14561461 return { ok : false , message : sizeValidation . message } ;
14571462 }
14581463 sessionStorage . setItem ( hostContextId . trim ( ) , sizeValidation . metrics . serializedPayload ) ;
14591464 this . currentHostContextId = hostContextId . trim ( ) ;
1460- this . setCurrentSessionPayload ( versionedPayload , sourceLabel ) ;
1461- return { ok : true , message : "" , payload : versionedPayload } ;
1465+ this . setCurrentSessionPayload ( sessionPayload , sourceLabel ) ;
1466+ return { ok : true , message : "" , payload : sessionPayload } ;
14621467 }
14631468
14641469 applySessionPayload ( sessionPayload , sourceLabel ) {
14651470 if ( ! this . isValidSessionPayload ( sessionPayload ) ) {
14661471 this . statusNode . textContent = "Session payload is invalid. Expected a JSON object payload." ;
14671472 return false ;
14681473 }
1474+ const payloadValidation = this . validateWorkspaceToolSessionPayload ( sessionPayload , "sessionPayload" ) ;
1475+ if ( ! payloadValidation . ok ) {
1476+ this . statusNode . textContent = payloadValidation . message ;
1477+ return false ;
1478+ }
14691479 const toolId = this . selectedToolId ( ) ;
14701480 if ( ! toolId ) {
14711481 this . statusNode . textContent = "Select a V2 tool before applying session payload." ;
14721482 return false ;
14731483 }
1484+ if ( sessionPayload . toolId . trim ( ) !== toolId ) {
1485+ this . statusNode . textContent = `Session payload toolId '${ sessionPayload . toolId . trim ( ) } ' does not match selected tool '${ toolId } '.` ;
1486+ return false ;
1487+ }
14741488 if ( this . hasWorkspaceActivePalette ( ) && this . isPaletteManagerToolId ( toolId ) ) {
14751489 this . statusNode . textContent = this . singleActivePaletteBlockedMessage ( ) ;
14761490 return false ;
@@ -1549,6 +1563,11 @@ class WorkspaceV2SessionProducer {
15491563 this . statusNode . textContent = `Session library entry '${ sessionName } ' is invalid.` ;
15501564 return null ;
15511565 }
1566+ const payloadValidation = this . validateWorkspaceToolSessionPayload ( parsed [ sessionName ] , `tools.workspace-v2.savedSessions.${ sessionName } ` ) ;
1567+ if ( ! payloadValidation . ok ) {
1568+ this . statusNode . textContent = `Session library entry '${ sessionName } ' is invalid: ${ payloadValidation . message } ` ;
1569+ return null ;
1570+ }
15521571 }
15531572 return parsed ;
15541573 } catch ( error ) {
@@ -1741,6 +1760,9 @@ class WorkspaceV2SessionProducer {
17411760 if ( typeof entry . tool !== "string" || ! entry . tool . trim ( ) ) return false ;
17421761 if ( typeof entry . timestamp !== "string" || ! entry . timestamp . trim ( ) ) return false ;
17431762 if ( ! this . isValidSessionPayload ( entry . payload ) ) return false ;
1763+ const payloadValidation = this . validateWorkspaceToolSessionPayload ( entry . payload , `history.${ entry . hostContextId . trim ( ) } .payload` ) ;
1764+ if ( ! payloadValidation . ok ) return false ;
1765+ if ( entry . payload . toolId . trim ( ) !== entry . tool . trim ( ) ) return false ;
17441766 return true ;
17451767 }
17461768
@@ -1771,6 +1793,7 @@ class WorkspaceV2SessionProducer {
17711793 } ) ;
17721794 if ( invalidCount > 0 ) {
17731795 console . warn ( `[WorkspaceV2SessionHistory] Ignored ${ invalidCount } invalid history entr${ invalidCount === 1 ? "y" : "ies" } .` ) ;
1796+ this . statusNode . textContent = `Session history contains ${ invalidCount } invalid entr${ invalidCount === 1 ? "y" : "ies" } . Remove invalid entries before loading sessions.` ;
17741797 }
17751798 return validEntries ;
17761799 }
@@ -2015,19 +2038,20 @@ class WorkspaceV2SessionProducer {
20152038 ) ;
20162039 }
20172040
2018- resolveSessionPayloadFromContextId ( contextId , fallbackPayload ) {
2041+ resolveSessionPayloadFromContextId ( contextId ) {
20192042 if ( typeof contextId === "string" && contextId . trim ( ) ) {
20202043 const raw = sessionStorage . getItem ( contextId . trim ( ) ) ;
20212044 if ( typeof raw === "string" ) {
20222045 const parsed = this . safeParseJson ( raw ) ;
20232046 if ( parsed . ok && this . isValidSessionPayload ( parsed . value ) ) {
2047+ const payloadValidation = this . validateWorkspaceToolSessionPayload ( parsed . value , `sessionStorage.${ contextId . trim ( ) } ` ) ;
2048+ if ( ! payloadValidation . ok ) {
2049+ return null ;
2050+ }
20242051 return parsed . value ;
20252052 }
20262053 }
20272054 }
2028- if ( this . isValidSessionPayload ( fallbackPayload ) ) {
2029- return fallbackPayload ;
2030- }
20312055 return null ;
20322056 }
20332057
@@ -2040,7 +2064,7 @@ class WorkspaceV2SessionProducer {
20402064 if ( ! this . isValidSessionHistoryEntry ( entry ) ) {
20412065 return ;
20422066 }
2043- const resolvedPayload = this . resolveSessionPayloadFromContextId ( entry . hostContextId , entry . payload ) ;
2067+ const resolvedPayload = this . resolveSessionPayloadFromContextId ( entry . hostContextId ) ;
20442068 if ( ! this . isValidSessionPayload ( resolvedPayload ) ) {
20452069 return ;
20462070 }
@@ -2678,6 +2702,11 @@ class WorkspaceV2SessionProducer {
26782702 this . setMergedSessionStatus ( "Enter a merged session ID before using in Diff/Merge." ) ;
26792703 return ;
26802704 }
2705+ const payloadValidation = this . validateWorkspaceToolSessionPayload ( this . lastMergedSessionResult . payload , "mergedSessionResult" ) ;
2706+ if ( ! payloadValidation . ok ) {
2707+ this . setMergedSessionStatus ( payloadValidation . message ) ;
2708+ return ;
2709+ }
26812710 const serialized = JSON . stringify ( this . lastMergedSessionResult . payload ) ;
26822711 sessionStorage . setItem ( mergedSessionId , serialized ) ;
26832712 this . addRecentSessionEntry ( mergedSessionId , this . lastMergedSessionResult . toolId , this . lastMergedSessionResult . payload ) ;
@@ -3218,58 +3247,31 @@ class WorkspaceV2SessionProducer {
32183247 if ( ! paletteJson || typeof paletteJson !== "object" || Array . isArray ( paletteJson ) ) {
32193248 return { ok : false , message : "Fixture is invalid. paletteJson must be an object for palette-manager-v2." , value : null } ;
32203249 }
3221- if ( Object . prototype . hasOwnProperty . call ( paletteJson , "colors" ) && ! Array . isArray ( paletteJson . swatches ) ) {
3222- if ( ! Array . isArray ( paletteJson . colors ) ) {
3223- return { ok : false , message : "Fixture is invalid. paletteJson.colors must be an array when swatches is missing." , value : null } ;
3224- }
3225- const convertedSwatches = [ ] ;
3226- for ( let index = 0 ; index < paletteJson . colors . length ; index += 1 ) {
3227- const colorEntry = paletteJson . colors [ index ] ;
3228- if ( typeof colorEntry === "string" ) {
3229- if ( ! / ^ # ( [ A - F a - f 0 - 9 ] { 6 } | [ A - F a - f 0 - 9 ] { 8 } ) $ / . test ( colorEntry ) ) {
3230- return { ok : false , message : `Fixture is invalid. paletteJson.colors[${ index } ] must be #RRGGBB or #RRGGBBAA.` , value : null } ;
3231- }
3232- convertedSwatches . push ( {
3233- symbol : String . fromCharCode ( 65 + ( index % 26 ) ) ,
3234- hex : colorEntry ,
3235- name : `Color ${ index + 1 } `
3236- } ) ;
3237- continue ;
3238- }
3239- if ( ! colorEntry || typeof colorEntry !== "object" || Array . isArray ( colorEntry ) ) {
3240- return { ok : false , message : `Fixture is invalid. paletteJson.colors[${ index } ] must be a string or object.` , value : null } ;
3241- }
3242- if ( typeof colorEntry . hex !== "string" || ! / ^ # ( [ A - F a - f 0 - 9 ] { 6 } | [ A - F a - f 0 - 9 ] { 8 } ) $ / . test ( colorEntry . hex ) ) {
3243- return { ok : false , message : `Fixture is invalid. paletteJson.colors[${ index } ].hex must be #RRGGBB or #RRGGBBAA.` , value : null } ;
3244- }
3245- const symbol = typeof colorEntry . symbol === "string" && colorEntry . symbol . length === 1
3246- ? colorEntry . symbol
3247- : String . fromCharCode ( 65 + ( index % 26 ) ) ;
3248- const name = typeof colorEntry . name === "string" && colorEntry . name . trim ( )
3249- ? colorEntry . name
3250- : `Color ${ index + 1 } ` ;
3251- convertedSwatches . push ( {
3252- symbol,
3253- hex : colorEntry . hex ,
3254- name
3255- } ) ;
3256- }
3257- const normalizedPalette = { ...paletteJson } ;
3258- delete normalizedPalette . colors ;
3259- normalizedPalette . swatches = convertedSwatches ;
3260- return { ok : true , message : "" , value : normalizedPalette } ;
3250+ if ( Object . prototype . hasOwnProperty . call ( paletteJson , "colors" ) ) {
3251+ return { ok : false , message : "Fixture is invalid. paletteJson.colors is not supported; use paletteJson.swatches." , value : null } ;
32613252 }
32623253 if ( ! Array . isArray ( paletteJson . swatches ) ) {
32633254 return { ok : false , message : "Fixture is invalid. paletteJson.swatches must be an array for palette-manager-v2." , value : null } ;
32643255 }
3256+ const swatchValidation = this . validatePaletteSwatchesForWorkspaceExport ( paletteJson . swatches , "fixture.sessionContext.paletteJson.swatches" ) ;
3257+ if ( ! swatchValidation . ok ) {
3258+ return { ok : false , message : swatchValidation . message , value : null } ;
3259+ }
32653260 return { ok : true , message : "" , value : paletteJson } ;
32663261 }
32673262
32683263 normalizeFixtureSessionContext ( toolId , sessionContext ) {
32693264 if ( ! this . isValidSessionPayload ( sessionContext ) ) {
32703265 return { ok : false , message : "Fixture is invalid. Missing sessionContext object." , value : null } ;
32713266 }
3272- const normalizedSession = this . withSessionVersion ( this . cloneSessionValue ( sessionContext ) ) ;
3267+ const normalizedSession = this . cloneSessionValue ( sessionContext ) ;
3268+ const payloadValidation = this . validateWorkspaceToolSessionPayload ( normalizedSession , "fixture.sessionContext" ) ;
3269+ if ( ! payloadValidation . ok ) {
3270+ return { ok : false , message : payloadValidation . message , value : null } ;
3271+ }
3272+ if ( typeof normalizedSession . toolId !== "string" || normalizedSession . toolId . trim ( ) !== toolId ) {
3273+ return { ok : false , message : `Fixture is invalid. sessionContext.toolId must be '${ toolId } '.` , value : null } ;
3274+ }
32733275 if ( toolId === "palette-manager-v2" ) {
32743276 if ( Object . prototype . hasOwnProperty . call ( normalizedSession , "payloadJson" ) ) {
32753277 return { ok : false , message : "Fixture is invalid. payloadJson is not supported for palette-manager-v2." , value : null } ;
@@ -3510,7 +3512,18 @@ class WorkspaceV2SessionProducer {
35103512 if ( typeof sessionPayload . toolId !== "string" || ! sessionPayload . toolId . trim ( ) ) {
35113513 return { ok : false , message : `${ sessionPath } .toolId is required.` } ;
35123514 }
3513- if ( sessionPayload . toolId . trim ( ) === "palette-manager-v2" ) {
3515+ const toolId = sessionPayload . toolId . trim ( ) ;
3516+ const allowedToolIds = new Set ( [
3517+ "asset-browser-v2" ,
3518+ "palette-manager-v2" ,
3519+ "svg-asset-studio-v2" ,
3520+ "tilemap-studio-v2" ,
3521+ "vector-map-editor-v2"
3522+ ] ) ;
3523+ if ( ! allowedToolIds . has ( toolId ) ) {
3524+ return { ok : false , message : `${ sessionPath } .toolId '${ toolId } ' is not supported.` } ;
3525+ }
3526+ if ( toolId === "palette-manager-v2" ) {
35143527 if ( Object . prototype . hasOwnProperty . call ( sessionPayload , "payloadJson" ) ) {
35153528 return { ok : false , message : `${ sessionPath } .payloadJson is not supported for palette-manager-v2. Use paletteJson.` } ;
35163529 }
@@ -3530,6 +3543,16 @@ class WorkspaceV2SessionProducer {
35303543 if ( Object . prototype . hasOwnProperty . call ( sessionPayload . paletteJson , "colors" ) ) {
35313544 return { ok : false , message : `${ sessionPath } .paletteJson.colors is not supported. Use paletteJson.swatches.` } ;
35323545 }
3546+ return { ok : true , message : "" } ;
3547+ }
3548+ if ( ! Object . prototype . hasOwnProperty . call ( sessionPayload , "payloadJson" ) ) {
3549+ return { ok : false , message : `${ sessionPath } .payloadJson is required for ${ toolId } .` } ;
3550+ }
3551+ if ( ! sessionPayload . payloadJson || typeof sessionPayload . payloadJson !== "object" || Array . isArray ( sessionPayload . payloadJson ) ) {
3552+ return { ok : false , message : `${ sessionPath } .payloadJson must be an object for ${ toolId } .` } ;
3553+ }
3554+ if ( Object . prototype . hasOwnProperty . call ( sessionPayload , "paletteJson" ) ) {
3555+ return { ok : false , message : `${ sessionPath } .paletteJson is not supported for ${ toolId } .` } ;
35333556 }
35343557 return { ok : true , message : "" } ;
35353558 }
@@ -3706,16 +3729,15 @@ class WorkspaceV2SessionProducer {
37063729 this . setImportExportStatus ( `Import error: ${ validation . message } ` ) ;
37073730 return ;
37083731 }
3709- this . workspaceImportedToolEntries = { } ;
3732+ const nextWorkspaceImportedToolEntries = { } ;
37103733 const importedToolIds = Object . keys ( parsed . tools ) . sort ( ( left , right ) => left . localeCompare ( right ) ) ;
37113734 importedToolIds . forEach ( ( toolId ) => {
37123735 if ( toolId === "palette-browser" || toolId === "workspace-v2" ) {
37133736 return ;
37143737 }
3715- this . workspaceImportedToolEntries [ toolId ] = this . cloneSessionValue ( parsed . tools [ toolId ] ) ;
3738+ nextWorkspaceImportedToolEntries [ toolId ] = this . cloneSessionValue ( parsed . tools [ toolId ] ) ;
37163739 } ) ;
37173740 const workspaceV2Tool = parsed . tools [ "workspace-v2" ] ;
3718- this . workspaceManifestGame = this . cloneSessionValue ( workspaceV2Tool . game ) ;
37193741 const activeHostContextId = workspaceV2Tool . activeHostContextId . trim ( ) ;
37203742 const activeToolId = workspaceV2Tool . activeToolId . trim ( ) ;
37213743 const activePayload = workspaceV2Tool . activeSession ;
@@ -3724,6 +3746,8 @@ class WorkspaceV2SessionProducer {
37243746 this . setImportExportStatus ( `Import error: ${ activation . message } ` ) ;
37253747 return ;
37263748 }
3749+ this . workspaceManifestGame = this . cloneSessionValue ( workspaceV2Tool . game ) ;
3750+ this . workspaceImportedToolEntries = nextWorkspaceImportedToolEntries ;
37273751 localStorage . setItem ( this . libraryStorageKey , JSON . stringify ( workspaceV2Tool . savedSessions ) ) ;
37283752 this . updateWorkspaceActivePaletteFromManifest ( parsed ) ;
37293753 this . workspaceJsonNode . value = JSON . stringify ( parsed , null , 2 ) ;
0 commit comments