@@ -101,6 +101,23 @@ const GENERATED_ULID_SEQUENCE = Object.freeze({
101101const RECOMMENDED_TARGET_LINKED_RECORD_TYPE = "recommended-target" ;
102102const RECOMMENDED_TARGET_NOTE_KEY = GAME_JOURNEY_KEYS . notes . designPass ;
103103const SOURCE_IDEA_LINKED_RECORD_TYPE = "source-idea-note" ;
104+ const JOURNEY_BOOTSTRAP_LINKED_RECORD_TYPE = "journey-bootstrap-bucket" ;
105+
106+ export const GAME_JOURNEY_BOOTSTRAP_BUCKETS = Object . freeze ( [
107+ "Idea" ,
108+ "Design" ,
109+ "Graphics" ,
110+ "Audio" ,
111+ "Objects" ,
112+ "Worlds" ,
113+ "Interface" ,
114+ "Controls" ,
115+ "Rules" ,
116+ "Progression" ,
117+ "Play Test" ,
118+ "Publish" ,
119+ "Share" ,
120+ ] ) ;
104121
105122export const GAME_JOURNEY_STATUSES = [
106123 {
@@ -708,6 +725,106 @@ export function createGameJourneyMockRepository(options = {}) {
708725 return idea ? `Source Idea: ${ idea } ` : "Source Idea" ;
709726 }
710727
728+ function noteTypeKeyForBootstrapBucket ( bucketName ) {
729+ const slug = slugSegment ( bucketName , "task" ) ;
730+ const matchingType = tables . game_journey_note_types . find ( ( type ) => type . typeSlug === slug ) ;
731+ return matchingType ?. key || GAME_JOURNEY_KEYS . noteTypes . task ;
732+ }
733+
734+ function ensureJourneyBootstrapBuckets ( activeGame ) {
735+ if ( ! activeGame ) {
736+ return {
737+ buckets : [ ] ,
738+ createdItems : 0 ,
739+ createdNotes : 0 ,
740+ } ;
741+ }
742+
743+ const ownerKey = safeCurrentUserKey ( ) ;
744+ const timestampValue = new Date ( ) . toISOString ( ) ;
745+ let createdNotes = 0 ;
746+ let createdItems = 0 ;
747+ const bucketSummaries = [ ] ;
748+ GAME_JOURNEY_BOOTSTRAP_BUCKETS . forEach ( ( bucketName , index ) => {
749+ const bucketOrder = index + 1 ;
750+ const bucketSlug = slugSegment ( bucketName , "bucket" ) ;
751+ const noteSlug = `journey-bucket-${ slugSegment ( activeGame . id || activeGame . key ) } -${ String ( bucketOrder ) . padStart ( 2 , "0" ) } -${ bucketSlug } ` ;
752+ let note = tables . game_journey_notes . find (
753+ ( candidate ) => candidate . gameKey === activeGame . key && candidate . slug === noteSlug ,
754+ ) ;
755+
756+ if ( ! note ) {
757+ note = {
758+ key : makeUlid ( nextNoteNumber ) ,
759+ slug : noteSlug ,
760+ gameKey : activeGame . key ,
761+ ownerKey,
762+ name : bucketName ,
763+ typeKey : noteTypeKeyForBootstrapBucket ( bucketName ) ,
764+ bucketOrder,
765+ createdAt : timestampValue ,
766+ updatedAt : timestampValue ,
767+ createdBy : ownerKey ,
768+ updatedBy : ownerKey ,
769+ } ;
770+ nextNoteNumber += 1 ;
771+ tables . game_journey_notes . push ( note ) ;
772+ createdNotes += 1 ;
773+ }
774+
775+ const linkedRecordId = `${ slugSegment ( activeGame . id || activeGame . key ) } :${ String ( bucketOrder ) . padStart ( 2 , "0" ) } :${ bucketSlug } ` ;
776+ let item = getItemsForNote ( note . key ) . find (
777+ ( candidate ) =>
778+ candidate . linkedRecordType === JOURNEY_BOOTSTRAP_LINKED_RECORD_TYPE &&
779+ candidate . linkedRecordId === linkedRecordId ,
780+ ) ;
781+
782+ if ( ! item ) {
783+ item = {
784+ key : makeUlid ( nextItemNumber ) ,
785+ gameKey : activeGame . key ,
786+ noteKey : note . key ,
787+ status : "not-started" ,
788+ title : `${ bucketName } progress placeholder` ,
789+ userDetails : "" ,
790+ createdBy : ownerKey ,
791+ updatedBy : ownerKey ,
792+ templateKey : "" ,
793+ linkedRecordType : JOURNEY_BOOTSTRAP_LINKED_RECORD_TYPE ,
794+ linkedRecordId,
795+ indent : 0 ,
796+ order : 1 ,
797+ createdAt : timestampValue ,
798+ updatedAt : timestampValue ,
799+ } ;
800+ nextItemNumber += 1 ;
801+ tables . game_journey_items . push ( item ) ;
802+ createdItems += 1 ;
803+ }
804+
805+ bucketSummaries . push ( {
806+ bucketName,
807+ itemKey : item . key ,
808+ noteKey : note . key ,
809+ order : bucketOrder ,
810+ } ) ;
811+ } ) ;
812+
813+ if ( createdNotes || createdItems ) {
814+ const firstBucket = bucketSummaries [ 0 ] ;
815+ selectedNoteKey = firstBucket ?. noteKey || selectedNoteKey ;
816+ selectedItemKey = firstBucket ?. itemKey || selectedItemKey ;
817+ addActivity ( activeGame . key , firstBucket ?. noteKey || "" , `Created ${ GAME_JOURNEY_BOOTSTRAP_BUCKETS . length } Game Journey starter buckets.` , ownerKey ) ;
818+ persistTables ( ) ;
819+ }
820+
821+ return {
822+ buckets : bucketSummaries ,
823+ createdItems,
824+ createdNotes,
825+ } ;
826+ }
827+
711828 function ensureSourceIdeaJourneyItems ( activeGame ) {
712829 const sourceIdea = activeGame ?. sourceIdea && typeof activeGame . sourceIdea === "object"
713830 ? activeGame . sourceIdea
@@ -1039,7 +1156,11 @@ export function createGameJourneyMockRepository(options = {}) {
10391156 . filter ( currentUserCanSeeNote )
10401157 . filter ( ( note ) => noteMatchesFilter ( note , filterId ) )
10411158 . map ( ( note ) => hydrateNote ( note , filterId ) )
1042- . sort ( ( left , right ) => right . updatedAt . localeCompare ( left . updatedAt ) ) ;
1159+ . sort ( ( left , right ) => {
1160+ const leftOrder = Number . isFinite ( Number ( left . bucketOrder ) ) ? Number ( left . bucketOrder ) : Number . POSITIVE_INFINITY ;
1161+ const rightOrder = Number . isFinite ( Number ( right . bucketOrder ) ) ? Number ( right . bucketOrder ) : Number . POSITIVE_INFINITY ;
1162+ return leftOrder - rightOrder || right . updatedAt . localeCompare ( left . updatedAt ) ;
1163+ } ) ;
10431164 }
10441165
10451166 function addNote ( { name, typeKey } = { } ) {
@@ -1500,11 +1621,29 @@ export function createGameJourneyMockRepository(options = {}) {
15001621 gameId === GAME_JOURNEY_KEYS . game ? GAME_JOURNEY_ROUTE_GAME_ALIAS : gameId ;
15011622 const openedGame = gameWorkspaceRepository . openGame ( workspaceGameId ) ;
15021623 if ( openedGame ) {
1503- ensureSourceIdeaJourneyItems ( getActiveGame ( ) ) ;
1624+ bootstrapGameJourneyForGame ( getActiveGame ( ) ) ;
15041625 }
15051626 return openedGame ;
15061627 }
15071628
1629+ function bootstrapGameJourneyForGame ( game = getActiveGame ( ) ) {
1630+ const activeGame = game ?. key ? game : game ? { ...game , key : journeyGameKey ( game ) } : getActiveGame ( ) ;
1631+ if ( ! activeGame ) {
1632+ return {
1633+ buckets : [ ] ,
1634+ createdItems : 0 ,
1635+ createdNotes : 0 ,
1636+ sourceIdeaItems : [ ] ,
1637+ } ;
1638+ }
1639+ const bucketResult = ensureJourneyBootstrapBuckets ( activeGame ) ;
1640+ const sourceIdeaItems = ensureSourceIdeaJourneyItems ( activeGame ) ;
1641+ return {
1642+ ...bucketResult ,
1643+ sourceIdeaItems,
1644+ } ;
1645+ }
1646+
15081647 return {
15091648 getTables : async ( ) => clone ( {
15101649 game_journey_completion_metrics : await completionMetricsStore . listMetrics ( ) ,
@@ -1519,6 +1658,7 @@ export function createGameJourneyMockRepository(options = {}) {
15191658 getSystemUser : ( ) => getMockDbSystemUser ( ) ,
15201659 getActiveGame,
15211660 openGame,
1661+ bootstrapGameJourneyForGame,
15221662 clearActiveGame : ( ) => gameWorkspaceRepository . clearTestData ( ) ,
15231663 listNoteTypes : ( ) => clone ( tables . game_journey_note_types ) ,
15241664 addNoteType,
0 commit comments