@@ -429,6 +429,123 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => {
429429 }
430430} ) ;
431431
432+ test ( "Idea Board gates Create Project to Ready ideas and locks converted projects" , async ( { page } ) => {
433+ const server = await startRepoServer ( {
434+ gameJourneyCompletionMetricsLegacyDbPath : null ,
435+ gameJourneyCompletionMetricsPostgresClient : createGameJourneyCompletionMetricsPostgresClientStub ( ) ,
436+ } ) ;
437+ const previousApiUrl = process . env . GAMEFOUNDRY_API_URL ;
438+ const previousSiteUrl = process . env . GAMEFOUNDRY_SITE_URL ;
439+ const previousSupabaseEnv = {
440+ GAMEFOUNDRY_DATABASE_URL : process . env . GAMEFOUNDRY_DATABASE_URL ,
441+ GAMEFOUNDRY_SUPABASE_ANON_KEY : process . env . GAMEFOUNDRY_SUPABASE_ANON_KEY ,
442+ GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY : process . env . GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY ,
443+ GAMEFOUNDRY_SUPABASE_URL : process . env . GAMEFOUNDRY_SUPABASE_URL ,
444+ } ;
445+ process . env . GAMEFOUNDRY_API_URL = `${ server . baseUrl } /api` ;
446+ process . env . GAMEFOUNDRY_SITE_URL = server . baseUrl ;
447+ process . env . GAMEFOUNDRY_DATABASE_URL = "postgres://idea-board:test@127.0.0.1:5432/idea_board" ;
448+ process . env . GAMEFOUNDRY_SUPABASE_ANON_KEY = "idea-board-anon-key" ;
449+ process . env . GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY = "idea-board-service-role-key" ;
450+ process . env . GAMEFOUNDRY_SUPABASE_URL = `${ server . baseUrl } /fake-supabase` ;
451+ const createGameRequests = [ ] ;
452+ const failedRequests = [ ] ;
453+ const pageErrors = [ ] ;
454+ const consoleErrors = [ ] ;
455+
456+ page . on ( "request" , ( request ) => {
457+ const requestUrl = request . url ( ) ;
458+ if ( requestUrl . includes ( "/api/toolbox/game-hub/repositories/" ) && requestUrl . includes ( "/methods/createGame" ) ) {
459+ createGameRequests . push ( request . postDataJSON ( ) ) ;
460+ }
461+ } ) ;
462+ page . on ( "response" , ( response ) => {
463+ if ( response . status ( ) >= 400 ) failedRequests . push ( `${ response . status ( ) } ${ response . url ( ) } ` ) ;
464+ } ) ;
465+ page . on ( "pageerror" , ( error ) => {
466+ const text = error . stack || error . message ;
467+ if ( ! isBrowserExtensionNoise ( text ) ) pageErrors . push ( error . message ) ;
468+ } ) ;
469+ page . on ( "console" , ( message ) => {
470+ if ( message . type ( ) === "error" && ! isBrowserExtensionNoise ( message . text ( ) ) ) consoleErrors . push ( message . text ( ) ) ;
471+ } ) ;
472+
473+ try {
474+ await page . route ( "**/api/platform-settings/banner" , async ( route ) => {
475+ await route . fulfill ( {
476+ contentType : "application/json" ,
477+ body : JSON . stringify ( {
478+ data : { banner : { active : false , message : "" , tone : "info" } } ,
479+ ok : true ,
480+ } ) ,
481+ } ) ;
482+ } ) ;
483+ await page . route ( "**/api/toolbox/registry/snapshot" , async ( route ) => {
484+ await route . fulfill ( {
485+ contentType : "application/json" ,
486+ body : JSON . stringify ( {
487+ data : {
488+ activeTools : [ ] ,
489+ readinessByStatus : { } ,
490+ tools : [ ] ,
491+ toolboxContract : { } ,
492+ } ,
493+ ok : true ,
494+ } ) ,
495+ } ) ;
496+ } ) ;
497+ await page . request . post ( `${ server . baseUrl } /api/session/user` , {
498+ data : { userKey : MOCK_DB_KEYS . users . user1 } ,
499+ } ) ;
500+
501+ await page . goto ( `${ server . baseUrl } /toolbox/idea-board/index.html` , { waitUntil : "networkidle" } ) ;
502+ await expect ( page . locator ( "[data-idea-board-idea-row='top-thoughts'] [data-idea-board-idea-action='create-project']" ) ) . toHaveCount ( 0 ) ;
503+ await expect ( page . locator ( "[data-idea-board-idea-row='sky-orchard'] [data-idea-board-idea-action='create-project']" ) ) . toHaveCount ( 0 ) ;
504+ await expect ( page . locator ( "[data-idea-board-idea-row='clockwork-courier'] [data-idea-board-idea-action='create-project']" ) ) . toHaveCount ( 0 ) ;
505+
506+ await page . locator ( "[data-idea-board-add-idea]" ) . click ( ) ;
507+ await page . locator ( "[data-idea-board-idea-input]" ) . fill ( "Validation Reef" ) ;
508+ await page . locator ( "[data-idea-board-pitch-input]" ) . fill ( "Verify project gating and read-only conversion." ) ;
509+ await page . locator ( "[data-idea-board-idea-status-input]" ) . selectOption ( "Refining" ) ;
510+ await page . locator ( "[data-idea-board-idea-action='save']" ) . click ( ) ;
511+ await expect ( page . locator ( "[data-idea-board-idea-row='validation-reef'] td" ) . nth ( 1 ) ) . toHaveText ( "Refining" ) ;
512+ await expect ( page . locator ( "[data-idea-board-idea-row='validation-reef'] [data-idea-board-idea-action='create-project']" ) ) . toHaveCount ( 0 ) ;
513+ expect ( createGameRequests ) . toEqual ( [ ] ) ;
514+
515+ await page . locator ( "[data-idea-board-idea-cell='validation-reef']" ) . click ( ) ;
516+ await page . locator ( "[data-idea-board-add-note='validation-reef']" ) . click ( ) ;
517+ await page . locator ( "[data-idea-board-note-input]" ) . fill ( "This note should become read-only project context." ) ;
518+ await page . locator ( "[data-idea-board-note-action='save']" ) . click ( ) ;
519+ await expect ( page . locator ( "[data-idea-board-notes-count='validation-reef']" ) ) . toHaveText ( "1 Note" ) ;
520+
521+ await page . locator ( "[data-idea-board-idea-row='validation-reef'] [data-idea-board-idea-action='edit']" ) . click ( ) ;
522+ await page . locator ( "[data-idea-board-idea-status-input]" ) . selectOption ( "Ready" ) ;
523+ await page . locator ( "[data-idea-board-idea-action='save']" ) . click ( ) ;
524+ await expect ( page . locator ( "[data-idea-board-idea-row='validation-reef'] td" ) . nth ( 1 ) ) . toHaveText ( "Ready" ) ;
525+ await expect ( page . locator ( "[data-idea-board-idea-row='validation-reef'] [data-idea-board-idea-action]" ) ) . toHaveText ( [ "Edit" , "Create Project" , "Delete" ] ) ;
526+
527+ await page . locator ( "[data-idea-board-idea-row='validation-reef'] [data-idea-board-idea-action='create-project']" ) . click ( ) ;
528+ await expect ( page . locator ( "[data-idea-board-idea-row='validation-reef'] td" ) . nth ( 1 ) ) . toHaveText ( "Project" ) ;
529+ await expect ( page . locator ( "[data-idea-board-idea-row='validation-reef'] [data-idea-board-idea-action]" ) ) . toHaveText ( [ "Open in Game Hub" , "Archive" ] ) ;
530+ await expect ( page . locator ( "[data-idea-board-idea-row='validation-reef'] [data-idea-board-idea-action='edit']" ) ) . toHaveCount ( 0 ) ;
531+ await expect ( page . locator ( "[data-idea-board-idea-row='validation-reef'] [data-idea-board-idea-action='delete']" ) ) . toHaveCount ( 0 ) ;
532+ await expect ( page . locator ( "[data-idea-board-add-note='validation-reef']" ) ) . toHaveCount ( 0 ) ;
533+ await expect ( page . locator ( "[data-idea-board-notes-table='validation-reef'] [data-idea-board-note-action]" ) ) . toHaveCount ( 0 ) ;
534+ await expect ( page . locator ( "[data-idea-board-note-input-row]" ) ) . toHaveCount ( 0 ) ;
535+ expect ( createGameRequests ) . toHaveLength ( 1 ) ;
536+ expect ( Object . keys ( createGameRequests [ 0 ] . args [ 0 ] ) . sort ( ) ) . toEqual ( [ "name" , "purpose" , "sourceIdea" , "status" ] ) ;
537+
538+ expect ( failedRequests ) . toEqual ( [ ] ) ;
539+ expect ( pageErrors ) . toEqual ( [ ] ) ;
540+ expect ( consoleErrors ) . toEqual ( [ ] ) ;
541+ } finally {
542+ restoreEnvValue ( "GAMEFOUNDRY_API_URL" , previousApiUrl ) ;
543+ restoreEnvValue ( "GAMEFOUNDRY_SITE_URL" , previousSiteUrl ) ;
544+ Object . entries ( previousSupabaseEnv ) . forEach ( ( [ key , value ] ) => restoreEnvValue ( key , value ) ) ;
545+ await server . close ( ) ;
546+ }
547+ } ) ;
548+
432549test ( "Idea Board guest Create Project redirects to sign in without creating a project" , async ( { page } ) => {
433550 const server = await startRepoServer ( ) ;
434551 const previousApiUrl = process . env . GAMEFOUNDRY_API_URL ;
0 commit comments