@@ -288,6 +288,23 @@ async function readWorkspaceSessionHydration(page) {
288288 } ) ;
289289}
290290
291+ function expectRuntimeBindingMetadata ( metadata , {
292+ bindingSource = "game.manifest.json" ,
293+ boundManifestPath = "/games/Asteroids/game.manifest.json" ,
294+ hasLiveRepoHandle = true ,
295+ sourceBindingState = "bound"
296+ } = { } ) {
297+ expect ( metadata ) . toMatchObject ( {
298+ bindingSource,
299+ boundManifestPath,
300+ hasLiveRepoHandle,
301+ sourceBindingState
302+ } ) ;
303+ expect ( metadata . handle ) . toBeUndefined ( ) ;
304+ expect ( metadata . repoHandle ) . toBeUndefined ( ) ;
305+ expect ( metadata . fileSystemHandle ) . toBeUndefined ( ) ;
306+ }
307+
291308async function dirtyPaletteToolState ( page , swatch ) {
292309 await page . evaluate ( ( nextSwatch ) => {
293310 const app = window . __workspaceManagerV2App ;
@@ -1491,14 +1508,21 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14911508 tiles . every ( ( tile ) => Array . from ( tile . querySelectorAll ( ".workspace-manager-v2__tool-tile-action" ) , ( action ) => action . textContent . trim ( ) ) . join ( "|" ) === "How To Use|Read Me" )
14921509 ) ) ) . toBe ( true ) ;
14931510 await selectMockRepo ( page ) ;
1494- expect ( await readWorkspaceSessionHydration ( page ) ) . toMatchObject ( {
1511+ const selectedRepoHydration = await readWorkspaceSessionHydration ( page ) ;
1512+ expect ( selectedRepoHydration ) . toMatchObject ( {
14951513 repoReference : {
14961514 displayName : "HTML-JavaScript-Gaming" ,
14971515 handleName : "HTML-JavaScript-Gaming" ,
14981516 kind : "file-system-directory-handle-reference"
14991517 } ,
15001518 toolKeys : [ ]
15011519 } ) ;
1520+ expectRuntimeBindingMetadata ( selectedRepoHydration . repoReference , {
1521+ bindingSource : "showDirectoryPicker" ,
1522+ boundManifestPath : "" ,
1523+ hasLiveRepoHandle : true ,
1524+ sourceBindingState : "repo-handle-acquired"
1525+ } ) ;
15021526 const compactCenterLayout = await page . evaluate ( ( ) => {
15031527 const getRect = ( selector ) => {
15041528 const element = document . querySelector ( selector ) ;
@@ -1542,6 +1566,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15421566 await expect ( page . locator ( "#workspaceContextOutput" ) ) . not . toHaveValue ( / " t o o l I d " / ) ;
15431567 await expect ( page . locator ( "#workspaceContextOutput" ) ) . not . toHaveValue ( / " w o r k s p a c e M a n i f e s t " / ) ;
15441568 await expect ( page . locator ( "#workspaceContextOutput" ) ) . not . toHaveValue ( / " w o r k s p a c e M e t a d a t a " / ) ;
1569+ await expect ( page . locator ( "#workspaceContextOutput" ) ) . not . toHaveValue ( / " h a s L i v e R e p o H a n d l e " / ) ;
1570+ await expect ( page . locator ( "#workspaceContextOutput" ) ) . not . toHaveValue ( / " s o u r c e B i n d i n g S t a t e " / ) ;
15451571 await expect ( page . locator ( "#workspaceContextOutput" ) ) . not . toHaveValue ( / s a m p l e s \/ / ) ;
15461572 await expect ( page . locator ( "#pickRepoBtn" ) ) . toBeDisabled ( ) ;
15471573 await expect ( page . locator ( "#activeGameSelect" ) ) . toBeDisabled ( ) ;
@@ -1550,6 +1576,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15501576 await expect ( page . locator ( "#cancelWorkspaceButton" ) ) . toBeEnabled ( ) ;
15511577 await expect ( page . locator ( "#activeGameContent button" ) ) . toHaveCount ( 0 ) ;
15521578 const selectedGameHydration = await readWorkspaceSessionHydration ( page ) ;
1579+ expectRuntimeBindingMetadata ( selectedGameHydration . repoReference ) ;
15531580 expect ( selectedGameHydration . toolKeys ) . toEqual ( [
15541581 "workspace.tools.asset-manager-v2" ,
15551582 "workspace.tools.palette-manager-v2" ,
@@ -1603,7 +1630,12 @@ test.describe("Workspace Manager V2 bootstrap", () => {
16031630 assetsPath : "games/Asteroids/assets" ,
16041631 repoReferenceKey : "workspace.repo.reference"
16051632 } ) ;
1633+ expectRuntimeBindingMetadata ( selectedGameHydration . workspaceByTool [ "asset-manager-v2" ] ) ;
1634+ expectRuntimeBindingMetadata ( selectedGameHydration . workspaceByTool [ "palette-manager-v2" ] ) ;
1635+ expectRuntimeBindingMetadata ( selectedGameHydration . workspaceByTool [ "preview-generator-v2" ] ) ;
1636+ expectRuntimeBindingMetadata ( selectedGameHydration . workspaceByTool [ "session-inspector-v2" ] ) ;
16061637 expect ( selectedGameHydration . toolSessions [ "asset-manager-v2" ] . state ) . toBeUndefined ( ) ;
1638+ expect ( JSON . stringify ( selectedGameHydration . toolSessions ) ) . not . toMatch ( / g e t D i r e c t o r y H a n d l e | c r e a t e W r i t a b l e | F i l e S y s t e m D i r e c t o r y H a n d l e / ) ;
16071639 expect ( Object . keys ( selectedGameHydration . dataByTool [ "asset-manager-v2" ] . assets ) ) . toHaveLength ( 14 ) ;
16081640 expect ( selectedGameHydration . toolSessions [ "templates-v2" ] ) . toBeUndefined ( ) ;
16091641 expect ( Object . values ( selectedGameHydration . dirtyByTool ) ) . toEqual ( [
@@ -1677,6 +1709,21 @@ test.describe("Workspace Manager V2 bootstrap", () => {
16771709 await expect ( page . locator ( "#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id='sessionStorage:workspace.tools.preview-generator-v2']" ) ) . toHaveCount ( 1 ) ;
16781710 await expect ( page . locator ( "#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id='sessionStorage:workspace.tools.session-inspector-v2']" ) ) . toHaveCount ( 1 ) ;
16791711 await expect ( page . locator ( "#sessionInspectorV2EntryList [data-session-inspector-v2-entry-id='sessionStorage:workspace.tools.templates-v2']" ) ) . toHaveCount ( 0 ) ;
1712+ await page . locator ( '[data-session-inspector-v2-entry-id="sessionStorage:workspace.tools.asset-manager-v2"]' ) . click ( ) ;
1713+ await expect ( page . locator ( "#sessionInspectorV2JsonOutput" ) ) . toContainText ( '"hasLiveRepoHandle": true' ) ;
1714+ await expect ( page . locator ( "#sessionInspectorV2JsonOutput" ) ) . toContainText ( '"sourceBindingState": "bound"' ) ;
1715+ await expect ( page . locator ( "#sessionInspectorV2JsonOutput" ) ) . toContainText ( '"boundManifestPath": "/games/Asteroids/game.manifest.json"' ) ;
1716+ await expect ( page . locator ( "#sessionInspectorV2JsonOutput" ) ) . toContainText ( '"bindingSource": "game.manifest.json"' ) ;
1717+ await expect ( page . locator ( "#sessionInspectorV2JsonOutput" ) ) . not . toContainText ( '"handle":' ) ;
1718+ await expect ( page . locator ( "#sessionInspectorV2JsonOutput" ) ) . not . toContainText ( '"repoHandle":' ) ;
1719+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / I N F O R u n t i m e b i n d i n g s t a t u s f o r s e s s i o n S t o r a g e : w o r k s p a c e \. t o o l s \. a s s e t - m a n a g e r - v 2 : h a s L i v e R e p o H a n d l e = t r u e ; s o u r c e B i n d i n g S t a t e = b o u n d ; b o u n d M a n i f e s t P a t h = \/ g a m e s \/ A s t e r o i d s \/ g a m e \. m a n i f e s t \. j s o n ; b i n d i n g S o u r c e = g a m e \. m a n i f e s t \. j s o n \. / ) ;
1720+ await page . locator ( '[data-session-inspector-v2-entry-id="sessionStorage:workspace.repo.reference"]' ) . click ( ) ;
1721+ await expect ( page . locator ( "#sessionInspectorV2JsonOutput" ) ) . toContainText ( '"hasLiveRepoHandle": true' ) ;
1722+ await expect ( page . locator ( "#sessionInspectorV2JsonOutput" ) ) . toContainText ( '"sourceBindingState": "bound"' ) ;
1723+ await expect ( page . locator ( "#sessionInspectorV2JsonOutput" ) ) . toContainText ( '"boundManifestPath": "/games/Asteroids/game.manifest.json"' ) ;
1724+ await expect ( page . locator ( "#sessionInspectorV2JsonOutput" ) ) . toContainText ( '"bindingSource": "game.manifest.json"' ) ;
1725+ await expect ( page . locator ( "#sessionInspectorV2JsonOutput" ) ) . not . toContainText ( '"handle":' ) ;
1726+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / I N F O R u n t i m e b i n d i n g s t a t u s f o r s e s s i o n S t o r a g e : w o r k s p a c e \. r e p o \. r e f e r e n c e : h a s L i v e R e p o H a n d l e = t r u e ; s o u r c e B i n d i n g S t a t e = b o u n d ; b o u n d M a n i f e s t P a t h = \/ g a m e s \/ A s t e r o i d s \/ g a m e \. m a n i f e s t \. j s o n ; b i n d i n g S o u r c e = g a m e \. m a n i f e s t \. j s o n \. / ) ;
16801727 await page . locator ( "#returnToWorkspaceButton" ) . click ( ) ;
16811728 await expect ( page ) . toHaveURL ( / w o r k s p a c e - m a n a g e r - v 2 \/ i n d e x \. h t m l \? h o s t C o n t e x t I d = w o r k s p a c e - m a n a g e r - v 2 - / ) ;
16821729 await expectWorkspaceReturnedFromTool ( page ) ;
@@ -1760,6 +1807,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17601807 expect ( storedContext . toolId ) . toBeUndefined ( ) ;
17611808 expect ( storedContext . activePalette ) . toBeUndefined ( ) ;
17621809 expect ( storedContext . workspaceManifest ) . toBeUndefined ( ) ;
1810+ expect ( storedContext . hasLiveRepoHandle ) . toBeUndefined ( ) ;
1811+ expect ( storedContext . sourceBindingState ) . toBeUndefined ( ) ;
17631812 expect ( storedContext . gameId ) . toBe ( "Asteroids" ) ;
17641813 expect ( storedContext . gameRoot ) . toBe ( "games/Asteroids/" ) ;
17651814 expect ( storedContext . assetsPath ) . toBe ( "games/Asteroids/assets" ) ;
@@ -2291,6 +2340,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
22912340 const restoredState = await page . evaluate ( ( ) => ( {
22922341 activeGameManifestPath : window . __workspaceManagerV2App . activeGame . manifestPath ,
22932342 hasRepoHandle : Boolean ( window . __workspaceManagerV2App . activeRepoHandle ) ,
2343+ repoReference : JSON . parse ( sessionStorage . getItem ( "workspace.repo.reference" ) ) ,
22942344 requiresRepoHandle : window . __workspaceManagerV2App . activeToolStateRequiresRepoHandle ,
22952345 writes : JSON . parse ( sessionStorage . getItem ( "workspace.repo.manifestWrites" ) || "[]" )
22962346 } ) ) ;
@@ -2300,6 +2350,13 @@ test.describe("Workspace Manager V2 bootstrap", () => {
23002350 writes : [ ]
23012351 } ) ;
23022352 expect ( restoredState . activeGameManifestPath ) . toBe ( `sessionStorage:${ hostContextId } ` ) ;
2353+ expectRuntimeBindingMetadata ( restoredState . repoReference , {
2354+ bindingSource : "sessionStorage restore" ,
2355+ boundManifestPath : `sessionStorage:${ hostContextId } ` ,
2356+ hasLiveRepoHandle : false ,
2357+ sourceBindingState : "missing-live-repo-handle"
2358+ } ) ;
2359+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( new RegExp ( `WARN Runtime handle lost: hasLiveRepoHandle=false; sourceBindingState=missing-live-repo-handle; boundManifestPath=sessionStorage:${ hostContextId } ; bindingSource=sessionStorage restore\\. Required action: Pick Repo Folder to rebind game\\.manifest\\.json before Save or tool launch\\.` ) ) ;
23032360
23042361 await page . evaluate ( ( ) => window . __workspaceManagerV2App . saveWorkspaceSession ( ) ) ;
23052362 await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( new RegExp ( `FAIL Save blocked: missing live repo folder handle for active toolState; active game source=sessionStorage:${ hostContextId } ; context\\.gameId=Asteroids; context\\.gameRoot=games/Asteroids/\\. Required action: Pick Repo Folder to rebind game\\.manifest\\.json before Save\\.` ) ) ;
@@ -2310,14 +2367,17 @@ test.describe("Workspace Manager V2 bootstrap", () => {
23102367 activeGameManifestKind : window . __workspaceManagerV2App . activeGame . manifestKind ,
23112368 activeGameManifestPath : window . __workspaceManagerV2App . activeGame . manifestPath ,
23122369 hasRepoHandle : Boolean ( window . __workspaceManagerV2App . activeRepoHandle ) ,
2370+ repoReference : JSON . parse ( sessionStorage . getItem ( "workspace.repo.reference" ) ) ,
23132371 requiresRepoHandle : window . __workspaceManagerV2App . activeToolStateRequiresRepoHandle
23142372 } ) ) ;
2315- expect ( reboundState ) . toEqual ( {
2373+ expect ( reboundState ) . toMatchObject ( {
23162374 activeGameManifestKind : "game-manifest" ,
23172375 activeGameManifestPath : "/games/Asteroids/game.manifest.json" ,
23182376 hasRepoHandle : true ,
23192377 requiresRepoHandle : false
23202378 } ) ;
2379+ expectRuntimeBindingMetadata ( reboundState . repoReference ) ;
2380+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K R u n t i m e h a n d l e r e b o u n d : h a s L i v e R e p o H a n d l e = t r u e ; s o u r c e B i n d i n g S t a t e = b o u n d ; b o u n d M a n i f e s t P a t h = \/ g a m e s \/ A s t e r o i d s \/ g a m e \. m a n i f e s t \. j s o n ; b i n d i n g S o u r c e = r e p o - f o l d e r - r e b i n d \. / ) ;
23212381
23222382 await page . locator ( "#saveWorkspaceButton" ) . click ( ) ;
23232383 await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K S a v e s o u r c e b i n d i n g : \/ g a m e s \/ A s t e r o i d s \/ g a m e \. m a n i f e s t \. j s o n \. / ) ;
@@ -2327,9 +2387,12 @@ test.describe("Workspace Manager V2 bootstrap", () => {
23272387 await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K S a v e d i r t y \/ c l e a n v a l i d a t i o n : 1 d i r t y t o o l S t a t e p a y l o a d p e r s i s t e d ; 1 t o o l S t a t e k e y m a r k e d c l e a n \. / ) ;
23282388 const saveState = await page . evaluate ( ( contextId ) => ( {
23292389 paletteSession : JSON . parse ( sessionStorage . getItem ( "workspace.tools.palette-manager-v2" ) ) ,
2390+ repoReference : JSON . parse ( sessionStorage . getItem ( "workspace.repo.reference" ) ) ,
23302391 savedContext : JSON . parse ( sessionStorage . getItem ( contextId ) ) ,
23312392 writes : JSON . parse ( sessionStorage . getItem ( "workspace.repo.manifestWrites" ) || "[]" )
23322393 } ) , hostContextId ) ;
2394+ expectRuntimeBindingMetadata ( saveState . repoReference ) ;
2395+ expectRuntimeBindingMetadata ( saveState . paletteSession . workspace ) ;
23332396 expect ( saveState . paletteSession . dirty ) . toEqual ( {
23342397 isDirty : false ,
23352398 reason : null ,
0 commit comments