@@ -13,7 +13,10 @@ let repoDirHandle = null;
1313let stopRequested = false ;
1414let repoDisplayName = "" ;
1515let isGenerating = false ;
16- let workspaceLaunchReady = false ;
16+ let workspacePreviewAssetFolder = "" ;
17+ let workspacePreviewFileValid = false ;
18+ let workspacePreviewGameId = "" ;
19+ let workspacePreviewTargetPath = "" ;
1720const ui = new PreviewGeneratorV2Ui ( ) ;
1821const logger = new PreviewGeneratorV2Logger ( {
1922 statusEl : ui . statusLog . getStatusElement ( ) ,
@@ -99,39 +102,62 @@ function workspaceAssetFolder(manifest) {
99102 return assetsPath . slice ( gameRoot . length + 1 ) ;
100103}
101104
102- function workspaceImageAssetFolder ( manifest ) {
105+ function workspacePreviewTarget ( manifest ) {
103106 const gameRoot = normalizeWorkspacePath ( manifest . gameRoot ) ;
104107 const assetFolder = workspaceAssetFolder ( manifest ) ;
105108 if ( ! assetFolder ) {
106- return "" ;
109+ return { ok : false , message : "assetsPath must be inside gameRoot." } ;
107110 }
108111 const imageAsset = Object . values ( manifest . tools ?. [ "asset-manager-v2" ] ?. assets || { } )
109- . find ( ( asset ) => asset ?. type === "image" && normalizeWorkspacePath ( asset . path ) ) ;
112+ . find ( ( asset ) => asset ?. type === "image" && asset ?. role === "bezel" && normalizeWorkspacePath ( asset . path ) ) ;
113+ if ( ! imageAsset ) {
114+ return { ok : false , message : "manifest must include a bezel image asset path under assetsPath." } ;
115+ }
110116 const imagePath = normalizeWorkspacePath ( imageAsset ?. path ) ;
111117 const imagePathFromGameRoot = imagePath . startsWith ( `${ gameRoot } /` )
112118 ? imagePath . slice ( gameRoot . length + 1 )
113119 : imagePath ;
114120 if ( ! imagePathFromGameRoot . startsWith ( `${ assetFolder } /` ) ) {
115- return "" ;
121+ return { ok : false , message : ` ${ imagePath || "(empty)" } must resolve under ${ assetFolder } .` } ;
116122 }
117- const imageFolder = imagePathFromGameRoot . split ( "/" ) . slice ( 0 , - 1 ) . join ( "/" ) ;
118- return imageFolder || "" ;
123+ const previewAssetFolder = imagePathFromGameRoot . split ( "/" ) . slice ( 0 , - 1 ) . join ( "/" ) ;
124+ if ( ! previewAssetFolder ) {
125+ return { ok : false , message : `${ imagePath } does not include an asset folder.` } ;
126+ }
127+ return {
128+ ok : true ,
129+ previewAssetFolder,
130+ previewTargetPath : `${ gameRoot } /${ imagePathFromGameRoot } `
131+ } ;
119132}
120133
121- async function readWorkspacePreviewSvg ( manifest , assetFolder ) {
122- const gameRoot = normalizeWorkspacePath ( manifest . gameRoot ) ;
123- if ( ! gameRoot || ! assetFolder ) {
124- return { ok : false , message : "Workspace Manager V2 manifest preview path is incomplete." } ;
134+ async function validateWorkspacePreviewTarget ( previewTargetPath ) {
135+ if ( ! previewTargetPath ) {
136+ return { ok : false , message : "Workspace Manager V2 manifest preview target path is empty." } ;
125137 }
126- const previewPath = `/${ gameRoot } /${ assetFolder } /preview.svg` ;
127138 try {
128- const response = await fetch ( previewPath , { cache : "no-store" } ) ;
139+ const response = await fetch ( `/ ${ previewTargetPath } ` , { cache : "no-store" } ) ;
129140 if ( ! response . ok ) {
130- return { ok : false , missing : true , previewPath } ;
141+ return { ok : false , message : `${ previewTargetPath } returned ${ response . status } .` } ;
142+ }
143+ const contentType = String ( response . headers . get ( "content-type" ) || "" ) ;
144+ const hasImageExtension = / \. ( p n g | j p e ? g | g i f | w e b p | s v g ) $ / i. test ( previewTargetPath ) ;
145+ if ( contentType && ! contentType . startsWith ( "image/" ) && ! hasImageExtension ) {
146+ return { ok : false , message : `${ previewTargetPath } is not an image response.` } ;
131147 }
132- return { ok : true , previewPath , svgContent : await response . text ( ) } ;
148+ return { ok : true } ;
133149 } catch ( error ) {
134- return { ok : false , message : `Unable to read ${ previewPath } : ${ error . message } ` } ;
150+ return { ok : false , message : `Unable to read ${ previewTargetPath } : ${ error . message } ` } ;
151+ }
152+ }
153+
154+ async function validateRepoRootHandle ( handle ) {
155+ try {
156+ await PreviewGeneratorV2RepoAccess . getDirectoryHandle ( handle , "games" ) ;
157+ await PreviewGeneratorV2RepoAccess . getDirectoryHandle ( handle , "tools" ) ;
158+ return { ok : true } ;
159+ } catch ( error ) {
160+ return { ok : false , message : `Selected folder is not the repo root: ${ error . message } ` } ;
135161 }
136162}
137163
@@ -173,6 +199,10 @@ function updateWriteFolderSampleLabel() {
173199 }
174200}
175201
202+ function updatePreviewTargetLabel ( ) {
203+ ui . outputSummary . setPreviewTarget ( workspacePreviewTargetPath || "not available yet" ) ;
204+ }
205+
176206async function updateWriteFolderActualLabelFromInput ( ) {
177207 const lines = parseInputList ( ui . pathsOrIds . getValue ( ) ) ;
178208 if ( ! lines . length ) {
@@ -195,6 +225,7 @@ async function updateWriteFolderActualLabelFromInput() {
195225
196226async function updatePathPreviewLabels ( ) {
197227 updateWriteFolderSampleLabel ( ) ;
228+ updatePreviewTargetLabel ( ) ;
198229 await updateWriteFolderActualLabelFromInput ( ) ;
199230}
200231
@@ -552,13 +583,24 @@ function printSummary(results) {
552583}
553584
554585class PreviewGeneratorV2App {
586+ hasValidWorkspacePreviewTarget ( ) {
587+ if ( ! isWorkspaceManagerLaunch ( ) ) {
588+ return true ;
589+ }
590+ return workspacePreviewFileValid
591+ && ui . getSelectedTargetType ( ) === "games"
592+ && getAssetFolderRelativePath ( ) === workspacePreviewAssetFolder
593+ && parseInputList ( ui . pathsOrIds . getValue ( ) ) . includes ( workspacePreviewGameId ) ;
594+ }
595+
555596 hasRequiredGenerateFields ( ) {
556- return ( Boolean ( repoDirHandle ) || workspaceLaunchReady )
597+ return Boolean ( repoDirHandle )
557598 && parseInputList ( ui . pathsOrIds . getValue ( ) ) . length > 0
558599 && ui . targetSource . hasBaseUrl ( )
559600 && ui . assetFolder . hasValue ( )
560601 && ui . targetSource . hasSelection ( )
561- && ui . captureMode . hasSelection ( ) ;
602+ && ui . captureMode . hasSelection ( )
603+ && this . hasValidWorkspacePreviewTarget ( ) ;
562604 }
563605
564606 syncGeneratePreviewButton ( ) {
@@ -567,9 +609,18 @@ class PreviewGeneratorV2App {
567609
568610 async handlePickRepo ( ) {
569611 try {
570- repoDirHandle = await window . showDirectoryPicker ( { mode : "readwrite" } ) ;
571- workspaceLaunchReady = false ;
572- repoDisplayName = PreviewGeneratorV2RepoAccess . getRepoDestinationDisplayName ( repoDirHandle ) ;
612+ const selectedRepoHandle = await window . showDirectoryPicker ( { mode : "readwrite" } ) ;
613+ const repoValidation = await validateRepoRootHandle ( selectedRepoHandle ) ;
614+ if ( ! repoValidation . ok ) {
615+ repoDirHandle = null ;
616+ repoDisplayName = "" ;
617+ ui . setRepoDestinationDisplayName ( "not selected" ) ;
618+ this . syncGeneratePreviewButton ( ) ;
619+ logger . log ( `FAIL ${ repoValidation . message } ` ) ;
620+ return ;
621+ }
622+ repoDirHandle = selectedRepoHandle ;
623+ repoDisplayName = PreviewGeneratorV2RepoAccess . getRepoDestinationDisplayName ( selectedRepoHandle ) ;
573624
574625 ui . setRepoDestinationDisplayName ( repoDisplayName ) ;
575626 logger . clearStatus ( ) ;
@@ -586,12 +637,12 @@ class PreviewGeneratorV2App {
586637 async handleExecute ( ) {
587638 if ( ! this . hasRequiredGenerateFields ( ) ) {
588639 this . syncGeneratePreviewButton ( ) ;
589- logger . log ( "Provide repo destination, base URL, asset folder, and at least one path or ID before generating." ) ;
640+ logger . log ( "Provide repo destination, base URL, asset folder, preview target, and at least one path or ID before generating." ) ;
590641 return ;
591642 }
592643
593644 if ( ! repoDirHandle ) {
594- logger . log ( "Workspace launch context is hydrated; Pick Repo Folder is required before writing preview output." ) ;
645+ logger . log ( "Pick the actual repo root folder before writing preview output." ) ;
595646 return ;
596647 }
597648
@@ -678,41 +729,46 @@ class PreviewGeneratorV2App {
678729 }
679730 logger . log ( "Workspace launch context hydration started." ) ;
680731 if ( ! contextResult . ok ) {
681- workspaceLaunchReady = false ;
732+ workspacePreviewFileValid = false ;
682733 logger . log ( `FAIL Workspace launch context hydration: ${ contextResult . message } ` ) ;
683734 this . syncGeneratePreviewButton ( ) ;
684735 return ;
685736 }
686737
687738 const manifest = contextResult . manifest ;
688- const assetFolder = workspaceImageAssetFolder ( manifest ) ;
689- if ( ! assetFolder ) {
690- workspaceLaunchReady = false ;
691- logger . log ( " FAIL Workspace launch context hydration: manifest must include an image asset path under assetsPath." ) ;
739+ const previewTarget = workspacePreviewTarget ( manifest ) ;
740+ if ( ! previewTarget . ok ) {
741+ workspacePreviewFileValid = false ;
742+ logger . log ( ` FAIL Workspace launch context hydration: ${ previewTarget . message } ` ) ;
692743 this . syncGeneratePreviewButton ( ) ;
693744 return ;
694745 }
695746
696- repoDisplayName = `${ manifest . gameId } workspace (${ normalizeWorkspacePath ( manifest . gameRoot ) } )` ;
697- ui . setRepoDestinationDisplayName ( repoDisplayName ) ;
747+ repoDisplayName = "" ;
748+ repoDirHandle = null ;
749+ workspacePreviewAssetFolder = previewTarget . previewAssetFolder ;
750+ workspacePreviewGameId = manifest . gameId ;
751+ workspacePreviewTargetPath = previewTarget . previewTargetPath ;
752+ ui . setRepoDestinationDisplayName ( "not selected" ) ;
753+ ui . repoDestination . setWorkspaceContextLabel ( `${ manifest . gameId } workspace (${ normalizeWorkspacePath ( manifest . gameRoot ) } )` ) ;
698754 ui . targetSource . setSelectedTargetType ( "games" ) ;
699- ui . assetFolder . setValue ( assetFolder ) ;
755+ ui . assetFolder . setValue ( workspacePreviewAssetFolder ) ;
700756 ui . pathsOrIds . setValue ( manifest . gameId ) ;
701757 await updatePathPreviewLabels ( ) ;
702- workspaceLaunchReady = true ;
703758 logger . log ( `OK Workspace launch context hydrated for ${ manifest . gameId } .` ) ;
704- logger . log ( ` Repo selected: ${ repoDisplayName } ` ) ;
759+ logger . log ( " Repo selected: not selected; pick the actual repo root folder." ) ;
705760 logger . log ( "Target source: games" ) ;
706761 logger . log ( `Asset folder: ${ getAssetFolderDisplayPath ( ) } ` ) ;
762+ logger . log ( `Preview target: ${ workspacePreviewTargetPath } ` ) ;
707763
708- const previewResult = await readWorkspacePreviewSvg ( manifest , assetFolder ) ;
764+ const previewResult = await validateWorkspacePreviewTarget ( workspacePreviewTargetPath ) ;
709765 if ( previewResult . ok ) {
710- ui . setLastGeneratedImage ( previewResult . svgContent , `${ manifest . gameId } preview.svg` ) ;
711- logger . log ( `OK Loaded existing preview image from ${ previewResult . previewPath } .` ) ;
712- } else if ( previewResult . missing ) {
713- logger . log ( `SKIP No existing preview image at ${ previewResult . previewPath } .` ) ;
766+ workspacePreviewFileValid = true ;
767+ ui . setPreviewTargetImage ( workspacePreviewTargetPath ) ;
768+ logger . log ( `OK Workspace preview target is valid at ${ workspacePreviewTargetPath } .` ) ;
714769 } else {
715- logger . log ( `WARN ${ previewResult . message } ` ) ;
770+ workspacePreviewFileValid = false ;
771+ logger . log ( `FAIL Workspace preview target validation: ${ previewResult . message } ` ) ;
716772 }
717773 this . syncGeneratePreviewButton ( ) ;
718774 }
0 commit comments