@ibgib/space-gib 0.0.34 → 0.0.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/client/bootstrap.mjs +1 -1
  2. package/dist/client/{chunk-25PXC4HP.mjs → chunk-5DTAUY3E.mjs} +1586 -464
  3. package/dist/client/chunk-J7X3XTAI.mjs +53 -0
  4. package/dist/client/css/activity-bar.css +21 -3
  5. package/dist/client/css/layout.css +126 -5
  6. package/dist/client/css/tutorial.css +327 -0
  7. package/dist/client/index.html +6 -0
  8. package/dist/client/index.mjs +22 -45
  9. package/dist/client/privacy.html +22 -22
  10. package/dist/client/script.mjs +1 -1
  11. package/dist/client/style.css +1 -0
  12. package/dist/server/server.mjs +3 -3
  13. package/package.json +6 -6
  14. package/src/client/AUTO-GENERATED-version.mts +1 -1
  15. package/src/client/cosmos/seed-cosmos-workspace.mts +20 -6
  16. package/src/client/css/activity-bar.css +21 -3
  17. package/src/client/css/layout.css +126 -5
  18. package/src/client/css/tutorial.css +327 -0
  19. package/src/client/dev-tools/vcs-workspace.mts +4 -4
  20. package/src/client/index.html +6 -0
  21. package/src/client/privacy.html +22 -22
  22. package/src/client/style.css +1 -0
  23. package/src/client/ui/component/changes/changes.css +413 -0
  24. package/src/client/ui/component/changes/changes.html +37 -0
  25. package/src/client/ui/component/changes/changes.mts +766 -0
  26. package/src/client/ui/component/changes/index.mts +9 -0
  27. package/src/client/ui/component/chat-view/chat-view.html +6 -58
  28. package/src/client/ui/component/chat-view/chat-view.mts +13 -26
  29. package/src/client/ui/component/clone/clone.mts +67 -39
  30. package/src/client/ui/component/code-editor/code-editor.css +91 -9
  31. package/src/client/ui/component/code-editor/code-editor.html +58 -45
  32. package/src/client/ui/component/code-editor/code-editor.mts +265 -49
  33. package/src/client/ui/component/cosmos-navigator/cosmos-navigator.css +1 -1
  34. package/src/client/ui/component/cosmos-navigator/cosmos-navigator.html +5 -10
  35. package/src/client/ui/component/cosmos-navigator/cosmos-navigator.mts +328 -64
  36. package/src/client/ui/component/file-explorer/file-explorer.css +475 -11
  37. package/src/client/ui/component/file-explorer/file-explorer.html +45 -65
  38. package/src/client/ui/component/file-explorer/file-explorer.mts +1164 -98
  39. package/src/client/ui/component/nested-chat/chat/chat.mts +5 -90
  40. package/src/client/ui/component/nested-chat/nested-chat.mts +10 -8
  41. package/src/client/ui/component/timeline/timeline.css +16 -1
  42. package/src/client/ui/component/timeline/timeline.mts +37 -93
  43. package/src/client/ui/component/tutorial/index.mts +9 -0
  44. package/src/client/ui/component/tutorial/scripts/pitch-tour-script.mts +265 -0
  45. package/src/client/ui/component/tutorial/tutorial-controller.mts +622 -0
  46. package/src/client/ui/component/tutorial/tutorial-types.mts +45 -0
  47. package/src/client/ui/component/tutorial/tutorial.css +260 -0
  48. package/src/client/ui/router/space-gib-router-helpers.mts +33 -233
  49. package/src/client/ui/router/space-gib-router-helpers.respec.mts +298 -122
  50. package/src/client/ui/router/space-gib-router-service.mts +28 -151
  51. package/src/client/ui/router/space-gib-router-types.mts +32 -76
  52. package/src/client/ui/shell/cosmic-big-bang.mts +12 -3
  53. package/src/client/ui/shell/space-gib-shell-service.mts +715 -143
  54. package/dist/client/chunk-RXWLL2AI.mjs +0 -30
@@ -31,6 +31,10 @@ import {
31
31
  registerFileExplorerComponent, FILE_EXPLORER_COMPONENT_NAME,
32
32
  FileExplorerComponentInstance,
33
33
  } from "../component/file-explorer/index.mjs";
34
+ import {
35
+ registerChangesComponent, CHANGES_COMPONENT_NAME,
36
+ ChangesComponentInstance,
37
+ } from "../component/changes/index.mjs";
34
38
  import {
35
39
  registerTimelineComponent, TIMELINE_COMPONENT_NAME,
36
40
  TimelineComponentInstance, TimelineItem,
@@ -49,6 +53,10 @@ import {
49
53
  import {
50
54
  registerPitchComponent, PITCH_COMPONENT_NAME, PitchComponentInstance,
51
55
  } from "../component/pitch/index.mjs";
56
+ import {
57
+ getTutorialController,
58
+ PITCH_TOUR_SCRIPT,
59
+ } from "../component/tutorial/index.mjs";
52
60
  import {
53
61
  registerCodeEditorComponent, CODE_EDITOR_COMPONENT_NAME,
54
62
  CodeEditorComponentInstance,
@@ -69,20 +77,137 @@ import {
69
77
  getSpaceGibRouterSvc,
70
78
  SpaceGibRouteInfo,
71
79
  SpaceGibTenant,
80
+ GalaxyRouteInfo,
72
81
  RawAddrRouteInfo,
73
82
  RawTjpRouteInfo,
74
83
  } from "../router/index.mjs";
84
+ import { GalaxyPolicy } from "@ibgib/core-gib/dist/cosmology/galaxy/galaxy-types.mjs";
75
85
  import { getGlobalMetaspace_waitIfNeeded } from "@ibgib/web-gib/dist/helpers.mjs";
76
- import { prewarmPlaygroundMetaspace } from "../../cosmos/seed-cosmos-workspace.mjs";
86
+ import { getPlaygroundMetaspaceAndSpace, prewarmPlaygroundMetaspace } from "../../cosmos/seed-cosmos-workspace.mjs";
87
+ import {
88
+ getBranchAndRootItemInfosAndIbGibsAndSpaceFromGalaxyIbGib,
89
+ getFileItemFromPath,
90
+ getFileContent,
91
+ } from "@ibgib/core-gib/dist/cosmology/branching/item/item-helpers.mjs";
92
+ import { GalaxyIbGib_V1 } from "@ibgib/core-gib/dist/cosmology/galaxy/galaxy-types.mjs";
93
+ import { B2tFSGalaxyIbGib_V1 } from "@ibgib/core-gib/dist/cosmology/branching/repo/b2tfs-repo-types.mjs";
94
+ import { getIbGibFromSpace } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
95
+ import {
96
+ executeVcsBranch,
97
+ executeVcsCheckout,
98
+ } from "@ibgib/core-gib/dist/cosmology/branching/commands/index.mjs";
99
+ import { validateBranchName } from "@ibgib/core-gib/dist/cosmology/branching/branch/branch-helpers.mjs";
100
+
101
+ export interface PanelLoadContext {
102
+ route: GalaxyRouteInfo;
103
+ cosmosName: string;
104
+ galaxyName: string;
105
+ discriminator?: string;
106
+ policy: GalaxyPolicy;
107
+ cosmosInfo?: CosmosInfo;
108
+ shell: SpaceGibShellService;
109
+ }
110
+
111
+ export type PanelName = 'LeftPanel' | 'CenterPanel' | 'RightPanel';
112
+
113
+ export type PanelLoaderFn = (route: GalaxyRouteInfo, context: PanelLoadContext) => Promise<void>;
77
114
 
115
+ export type PanelLoaderMap = {
116
+ [K in PanelName as `loadRouteFor${K}`]: PanelLoaderFn;
117
+ };
78
118
 
79
119
  export class SpaceGibShellService {
80
120
  private lc: string = `[SpaceGibShellService]`;
81
121
 
122
+ /**
123
+ * Default convention panel loaders for Branching Galaxies (Class I Substrate / Code).
124
+ */
125
+ protected branchingPanelLoaders: PanelLoaderMap = {
126
+ loadRouteForLeftPanel: async (route, context) => {
127
+ this.expandActivityBar();
128
+ this.expandLeftPanel();
129
+ this.setActiveActivityBarButton('space', route.galaxy);
130
+ this.showLeftPanelView('code', route.galaxy);
131
+ if (this.fileExplorerInstance) {
132
+ this.fileExplorerInstance.setGalaxyAndBranch(route.galaxy, route.branch || 'main');
133
+ if (route.filePath) {
134
+ this.fileExplorerInstance.selectFile(route.filePath);
135
+ }
136
+ }
137
+ if (this.changesInstance) {
138
+ this.changesInstance.setGalaxyAndBranch(route.galaxy, route.branch || 'main', { preserveChanges: true });
139
+ }
140
+ if (this.codeEditorInstance) {
141
+ this.codeEditorInstance.setGalaxyAndBranch(route.galaxy, route.branch || 'main');
142
+ }
143
+ if (this.timelineInstance) {
144
+ this.timelineInstance.setBranch(route.branch || 'main');
145
+ }
146
+ },
147
+ loadRouteForCenterPanel: async (route, context) => {
148
+ this.switchToReposMode({ skipNavigate: true });
149
+ await this.showCode(route.filePath, route.branch, route.galaxy);
150
+ },
151
+ loadRouteForRightPanel: async (route, context) => {
152
+ if (route.query?.inspect) {
153
+ this.expandRightPanel();
154
+ }
155
+ },
156
+ };
157
+
158
+ /**
159
+ * Default convention panel loaders for Linear Galaxies (Class IV Discourse / Notes).
160
+ */
161
+ protected linearPanelLoaders: PanelLoaderMap = {
162
+ loadRouteForLeftPanel: async (route, context) => {
163
+ this.expandActivityBar();
164
+ this.expandLeftPanel();
165
+ this.setActiveActivityBarButton('space', route.galaxy);
166
+ this.showLeftPanelView('text', route.galaxy);
167
+ },
168
+ loadRouteForCenterPanel: async (route, context) => {
169
+ this.switchToNotesMode(route.topicTjp || route.galaxy, { skipNavigate: true, galaxyId: route.galaxy });
170
+ await this.showNotes(route.topicTjp || route.galaxy, {
171
+ trail: route.trail,
172
+ trailMode: route.trailMode,
173
+ query: route.query,
174
+ });
175
+ },
176
+ loadRouteForRightPanel: async (route, context) => {
177
+ if (route.query?.inspect) {
178
+ this.expandRightPanel();
179
+ }
180
+ },
181
+ };
182
+
183
+ /**
184
+ * Registry of convention panel loaders keyed by topology or discriminator.
185
+ */
186
+ protected panelLoaderRegistry: Record<string, PanelLoaderMap> = {
187
+ branching: this.branchingPanelLoaders,
188
+ linear: this.linearPanelLoaders,
189
+ };
190
+
191
+ public registerPanelLoaders(key: string, loaders: PanelLoaderMap): void {
192
+ this.panelLoaderRegistry[key] = loaders;
193
+ }
194
+
195
+ public async loadRouteForPanels(route: GalaxyRouteInfo, context: PanelLoadContext): Promise<void> {
196
+ const loaders =
197
+ (context.discriminator ? this.panelLoaderRegistry[context.discriminator] : undefined) ||
198
+ this.panelLoaderRegistry[context.policy.topology] ||
199
+ this.branchingPanelLoaders;
200
+
201
+ await loaders.loadRouteForLeftPanel(route, context);
202
+ await loaders.loadRouteForCenterPanel(route, context);
203
+ await loaders.loadRouteForRightPanel(route, context);
204
+ }
205
+
82
206
  public initialized: Promise<void>;
83
207
  private isApplyingRoute: boolean = false;
84
208
  private cosmosNavigatorInstance?: CosmosNavigatorComponentInstance;
85
209
  private fileExplorerInstance?: FileExplorerComponentInstance;
210
+ private changesInstance?: ChangesComponentInstance;
86
211
  private timelineInstance?: TimelineComponentInstance;
87
212
  private timelineItemDetailsInstance?: TimelineItemDetailsComponentInstance;
88
213
  private chatViewInstance?: ChatViewComponentInstance;
@@ -92,6 +217,13 @@ export class SpaceGibShellService {
92
217
  private notesExplorerInstance?: NotesExplorerComponentInstance;
93
218
  private notesInspectorInstance?: NotesInspectorComponentInstance;
94
219
 
220
+ public getCosmosNavigator(): CosmosNavigatorComponentInstance | undefined {
221
+ return this.cosmosNavigatorInstance;
222
+ }
223
+ public getFileExplorer(): FileExplorerComponentInstance | undefined {
224
+ return this.fileExplorerInstance;
225
+ }
226
+
95
227
  // Panel States
96
228
  private leftPanelState: PanelState = 'collapsed';
97
229
  private rightPanelState: PanelState = 'collapsed';
@@ -100,6 +232,8 @@ export class SpaceGibShellService {
100
232
  private isResizingLeft = false;
101
233
  private isResizingRight = false;
102
234
  private isResizingFooter = false;
235
+ private isResizingExplorerChanges = false;
236
+ private isResizingChangesTimeline = false;
103
237
 
104
238
  // Playground Banner State
105
239
  private hasBigBangExploded = false;
@@ -114,6 +248,7 @@ export class SpaceGibShellService {
114
248
  await this.registerComponents();
115
249
  await this.initCosmosNavigator();
116
250
  await this.initFileExplorer();
251
+ await this.initChanges();
117
252
  await this.initTimeline();
118
253
  await this.initChatView();
119
254
  await this.initCodeEditor();
@@ -175,6 +310,44 @@ export class SpaceGibShellService {
175
310
  footerResizer.addEventListener('mousedown', () => this.isResizingFooter = true);
176
311
  }
177
312
 
313
+ // Accordion Horizontal Resizers (Left Panel)
314
+ const resizerExplorerChanges = document.getElementById('resizer-explorer-changes');
315
+ if (resizerExplorerChanges) {
316
+ resizerExplorerChanges.addEventListener('mousedown', (e) => {
317
+ e.preventDefault();
318
+ this.isResizingExplorerChanges = true;
319
+ resizerExplorerChanges.classList.add('active');
320
+ });
321
+ resizerExplorerChanges.addEventListener('dblclick', () => {
322
+ const explorerEl = document.getElementById('code-file-explorer-container');
323
+ if (explorerEl) {
324
+ explorerEl.style.flex = '';
325
+ explorerEl.style.height = '';
326
+ }
327
+ });
328
+ }
329
+
330
+ const resizerChangesTimeline = document.getElementById('resizer-changes-timeline');
331
+ if (resizerChangesTimeline) {
332
+ resizerChangesTimeline.addEventListener('mousedown', (e) => {
333
+ e.preventDefault();
334
+ this.isResizingChangesTimeline = true;
335
+ resizerChangesTimeline.classList.add('active');
336
+ });
337
+ resizerChangesTimeline.addEventListener('dblclick', () => {
338
+ const changesEl = document.getElementById('code-changes-container');
339
+ const timelineEl = document.getElementById('code-timeline-container');
340
+ if (changesEl) {
341
+ changesEl.style.flex = '';
342
+ changesEl.style.height = '';
343
+ }
344
+ if (timelineEl) {
345
+ timelineEl.style.flex = '';
346
+ timelineEl.style.height = '';
347
+ }
348
+ });
349
+ }
350
+
178
351
  // Window mouse events for dragging
179
352
  window.addEventListener('mousemove', (e) => this.handleMouseMove(e));
180
353
  window.addEventListener('mouseup', () => this.handleMouseUp());
@@ -437,7 +610,7 @@ export class SpaceGibShellService {
437
610
  }
438
611
 
439
612
  private handleMouseMove(e: MouseEvent) {
440
- if (!this.isResizingLeft && !this.isResizingRight && !this.isResizingFooter) return;
613
+ if (!this.isResizingLeft && !this.isResizingRight && !this.isResizingFooter && !this.isResizingExplorerChanges && !this.isResizingChangesTimeline) return;
441
614
 
442
615
  // Prevent text selection while dragging
443
616
  e.preventDefault();
@@ -459,12 +632,40 @@ export class SpaceGibShellService {
459
632
  let newHeight = Math.max(24, Math.min(window.innerHeight - e.clientY, 400));
460
633
  document.documentElement.style.setProperty('--footer-panel-height', `${newHeight}px`);
461
634
  }
635
+
636
+ if (this.isResizingExplorerChanges) {
637
+ const explorerEl = document.getElementById('code-file-explorer-container');
638
+ if (explorerEl) {
639
+ const rect = explorerEl.getBoundingClientRect();
640
+ const parentHeight = explorerEl.parentElement?.clientHeight || 600;
641
+ const maxExplorerHeight = parentHeight - 75;
642
+ const newHeight = Math.max(76, Math.min(e.clientY - rect.top, maxExplorerHeight));
643
+ explorerEl.style.flex = 'none';
644
+ explorerEl.style.height = `${newHeight}px`;
645
+ }
646
+ }
647
+
648
+ if (this.isResizingChangesTimeline) {
649
+ const changesEl = document.getElementById('code-changes-container');
650
+ if (changesEl) {
651
+ const rect = changesEl.getBoundingClientRect();
652
+ const parentHeight = changesEl.parentElement?.clientHeight || 600;
653
+ const maxChangesHeight = parentHeight - (changesEl.offsetTop || 0) - 35;
654
+ const newHeight = Math.max(35, Math.min(e.clientY - rect.top, maxChangesHeight));
655
+ changesEl.style.flex = 'none';
656
+ changesEl.style.height = `${newHeight}px`;
657
+ }
658
+ }
462
659
  }
463
660
 
464
661
  private handleMouseUp() {
465
662
  this.isResizingLeft = false;
466
663
  this.isResizingRight = false;
467
664
  this.isResizingFooter = false;
665
+ this.isResizingExplorerChanges = false;
666
+ this.isResizingChangesTimeline = false;
667
+ document.getElementById('resizer-explorer-changes')?.classList.remove('active');
668
+ document.getElementById('resizer-changes-timeline')?.classList.remove('active');
468
669
  }
469
670
 
470
671
  private updateCssVariables() {
@@ -479,6 +680,7 @@ export class SpaceGibShellService {
479
680
  await registerAdminReviewComponent();
480
681
  await registerCosmosNavigatorComponent();
481
682
  await registerFileExplorerComponent();
683
+ await registerChangesComponent();
482
684
  await registerTimelineComponent();
483
685
  await registerTimelineItemDetailsComponent();
484
686
  await registerChatViewComponent();
@@ -560,6 +762,27 @@ export class SpaceGibShellService {
560
762
  }
561
763
  }
562
764
 
765
+ public show404(message: string): void {
766
+ const centerPanel = document.getElementById(ID_CENTER_PANEL_CONTENT);
767
+ if (centerPanel) {
768
+ centerPanel.innerHTML = `
769
+ <div class="cosmos-not-found-view" style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #94a3b8; text-align: center; padding: 2rem; font-family: sans-serif;">
770
+ <div style="font-size: 3.5rem; margin-bottom: 1rem;">🌌 404</div>
771
+ <h2 style="font-size: 1.5rem; color: #fff; margin-bottom: 0.5rem; font-weight: 600;">Cosmos Not Found</h2>
772
+ <p style="margin-bottom: 1.5rem; max-width: 420px; font-size: 0.95rem; line-height: 1.5;">${message}</p>
773
+ <button id="btn-404-return" type="button" style="padding: 10px 22px; border-radius: 8px; background: rgba(124, 92, 252, 0.2); border: 1px solid rgba(124, 92, 252, 0.4); color: #c4b5fd; cursor: pointer; font-weight: 500; font-size: 0.95rem; transition: background 0.2s;">
774
+ Return to Polycosmos
775
+ </button>
776
+ </div>
777
+ `;
778
+ const returnBtn = document.getElementById('btn-404-return');
779
+ returnBtn?.addEventListener('click', () => {
780
+ getSpaceGibRouterSvc().navigate({ kind: 'polycosmos' });
781
+ });
782
+ }
783
+ this.collapsePanels();
784
+ }
785
+
563
786
  private async initFileExplorer(): Promise<void> {
564
787
  const lc = `${this.lc}[${this.initFileExplorer.name}]`;
565
788
  try {
@@ -594,49 +817,91 @@ export class SpaceGibShellService {
594
817
 
595
818
  private bindFileExplorerEvents(): void {
596
819
  window.addEventListener('space-gib-file-selected', (e: any) => {
597
- const { branch, path, filename } = e.detail || {};
598
- console.log(`${this.lc} File selected: ${path || filename} on worktree branch ${branch}`);
599
- const fullFile = path || filename || 'src/index.ts';
820
+ const { branch, path, filename, galaxy } = e.detail || {};
821
+ console.log(`${this.lc} File selected: ${path || filename} on worktree branch ${branch} in galaxy ${galaxy}`);
822
+ const fullFile = path || filename;
823
+ if (!fullFile) {
824
+ return;
825
+ }
600
826
  const branchName = branch || 'main';
601
- const tipAddr = '7f8a1b2';
827
+ const galaxyName = galaxy || 'code-1';
828
+ const tipAddr = 'tip';
829
+
602
830
  this.chatViewInstance?.setTargetFile(fullFile, `${branchName} @ ${tipAddr}`);
603
831
  this.nestedChatInstance?.activateIbGib({ addr: `file ${fullFile}^${tipAddr}` });
604
832
 
605
- let mockContent: string | undefined = undefined;
606
- if (fullFile === 'src/helper.ts') {
607
- mockContent = `/**\n * @module my-app/helper\n * Utility functions for space-gib in-memory VCS.\n */\n\nexport function add(a: number, b: number): number {\n return a + b;\n}\n\nexport function multiply(a: number, b: number): number {\n return a * b;\n}\n`;
608
- } else if (fullFile === 'README.md') {
609
- mockContent = `# my-app\n\nDecentralized version-controlled application running on **space-gib**.\n\n- Zero SaaS lock-in\n- Inverted soft rel8ns for living companion discussions\n- Merkle DAG branch worktrees\n`;
610
- } else if (fullFile === 'tsconfig.json') {
611
- mockContent = `{\n "compilerOptions": {\n "target": "ES2022",\n "module": "NodeNext",\n "strict": true,\n "esModuleInterop": true\n }\n}\n`;
612
- }
833
+ this.showCode(fullFile, branchName, galaxyName);
613
834
 
614
- this.showCode(fullFile, branchName).then(() => {
615
- this.codeEditorInstance?.loadFile({
616
- filepath: fullFile,
835
+ const activeCosmos = this.cosmosNavigatorInstance?.getActiveCosmos();
836
+ if (activeCosmos) {
837
+ const tenant = this.getActiveTenant();
838
+ getSpaceGibRouterSvc().navigate({
839
+ kind: 'galaxy',
840
+ cosmos: activeCosmos.id || activeCosmos.name,
841
+ galaxy: galaxyName,
842
+ topology: 'branching',
843
+ discriminator: 'code',
617
844
  branch: branchName,
618
- tipAddr,
619
- content: mockContent,
845
+ filePath: fullFile,
846
+ ...(tenant ? { tenant } : {}),
620
847
  });
621
- });
848
+ }
849
+ });
622
850
 
623
- // Reveal discussions in right panel
624
- this.expandRightPanel();
851
+ window.addEventListener('space-gib-branch-switched', async (e: any) => {
852
+ const { galaxy, branch } = e.detail || {};
853
+ const targetGalaxy = galaxy || this.fileExplorerInstance?.getGalaxyName() || 'code-1';
854
+ const targetBranch = branch || 'main';
855
+
856
+ console.log(`${this.lc} Branch switched: [${targetGalaxy}] -> [${targetBranch}]`);
857
+
858
+ // 1. Update File Explorer, Changes, Timeline, Code Editor
859
+ this.fileExplorerInstance?.setGalaxyAndBranch(targetGalaxy, targetBranch);
860
+ this.changesInstance?.setGalaxyAndBranch(targetGalaxy, targetBranch, { preserveChanges: true });
861
+ this.codeEditorInstance?.setGalaxyAndBranch(targetGalaxy, targetBranch);
862
+ this.timelineInstance?.setBranch(targetBranch);
625
863
 
864
+ // 2. Refresh active file under new branch context
865
+ const activePath = this.fileExplorerInstance?.getSelectedFilePath() || this.codeEditorInstance?.getFilepath();
866
+ if (activePath) {
867
+ await this.showCode(activePath, targetBranch, targetGalaxy);
868
+ } else {
869
+ await this.showCode(undefined, targetBranch, targetGalaxy);
870
+ }
871
+
872
+ // 3. Update router navigation
626
873
  const activeCosmos = this.cosmosNavigatorInstance?.getActiveCosmos();
627
874
  if (activeCosmos) {
628
- const tenant = this.getActiveTenant();
875
+ const tenant = this.getActiveTenant(activeCosmos);
629
876
  getSpaceGibRouterSvc().navigate({
630
- kind: 'code-galaxy',
877
+ kind: 'galaxy',
631
878
  cosmos: activeCosmos.id || activeCosmos.name,
632
- galaxyId: 'code-1',
633
- branch: branchName,
634
- filePath: fullFile,
879
+ galaxy: targetGalaxy,
880
+ topology: 'branching',
881
+ discriminator: 'code',
882
+ branch: targetBranch,
883
+ ...(activePath ? { filePath: activePath } : {}),
635
884
  ...(tenant ? { tenant } : {}),
636
885
  });
637
886
  }
638
887
  });
639
888
 
889
+ window.addEventListener('space-gib-view-mode-changed', (e: any) => {
890
+ const { mode } = e.detail || {};
891
+ if (mode) {
892
+ this.changesInstance?.setViewMode(mode);
893
+ }
894
+ });
895
+
896
+ window.addEventListener('space-gib-accordion-toggled', () => {
897
+ const explorerEl = document.getElementById('code-file-explorer-container');
898
+ const changesEl = document.getElementById('code-changes-container');
899
+ const timelineEl = document.getElementById('code-timeline-container');
900
+ if (explorerEl) { explorerEl.style.flex = ''; explorerEl.style.height = ''; }
901
+ if (changesEl) { changesEl.style.flex = ''; changesEl.style.height = ''; }
902
+ if (timelineEl) { timelineEl.style.flex = ''; timelineEl.style.height = ''; }
903
+ });
904
+
640
905
  window.addEventListener('space-gib-new-file-requested', (e: any) => {
641
906
  const { branch } = e.detail || {};
642
907
  const filename = prompt(`Create new file on branch [${branch || 'main'}]:`, 'untitled.ts');
@@ -654,6 +919,117 @@ export class SpaceGibShellService {
654
919
  });
655
920
  }
656
921
 
922
+ private async initChanges(): Promise<void> {
923
+ const lc = `${this.lc}[${this.initChanges.name}]`;
924
+ try {
925
+ const container = document.getElementById('code-changes-container');
926
+ if (!container) {
927
+ console.warn(`${lc} code-changes-container element not found.`);
928
+ return;
929
+ }
930
+
931
+ const componentSvc = await getComponentSvc();
932
+ this.changesInstance = await componentSvc.getComponentInstance({
933
+ path: CHANGES_COMPONENT_NAME,
934
+ ibGibAddr: ROOT_ADDR,
935
+ useRegExpPrefilter: true,
936
+ }) as ChangesComponentInstance;
937
+
938
+ if (!this.changesInstance) {
939
+ throw new Error(`Failed to instantiate ${CHANGES_COMPONENT_NAME}`);
940
+ }
941
+
942
+ await componentSvc.inject({
943
+ parentEl: container,
944
+ componentToInject: this.changesInstance,
945
+ });
946
+
947
+ if (this.fileExplorerInstance) {
948
+ this.changesInstance.setViewMode(this.fileExplorerInstance.getViewMode());
949
+ }
950
+
951
+ this.bindChangesEvents();
952
+
953
+ } catch (error) {
954
+ console.error(`${lc} ${extractErrorMsg(error)}`);
955
+ }
956
+ }
957
+
958
+ private bindChangesEvents(): void {
959
+ window.addEventListener('space-gib-commit-created', (e: any) => {
960
+ const { branch, message, tipAddr, shortHash } = e.detail || {};
961
+ console.log(`${this.lc} Commit created: [${shortHash}] "${message}" on branch [${branch}]`);
962
+
963
+ // 1. Clear status badges in file explorer for this branch
964
+ this.fileExplorerInstance?.clearAllStatusBadges(branch);
965
+ this.fileExplorerInstance?.updateDirtyBadges();
966
+
967
+ // 2. Add snapshot to timeline
968
+ this.timelineInstance?.addSnapshot({
969
+ hash: tipAddr,
970
+ shortHash: shortHash || (tipAddr ? tipAddr.slice(-8) : 'unknown'),
971
+ message: message || 'Update',
972
+ author: 'You',
973
+ timestamp: 'Just now',
974
+ branch: branch || 'main',
975
+ isTip: true,
976
+ });
977
+
978
+ // 3. Update breadcrumb tip and mark committed in code editor
979
+ if (this.codeEditorInstance) {
980
+ if (shortHash) {
981
+ this.codeEditorInstance.setTipAddr(shortHash);
982
+ }
983
+ const activePath = this.codeEditorInstance.getFilepath();
984
+ if (activePath) {
985
+ this.codeEditorInstance.markAsCommitted(shortHash || tipAddr);
986
+ }
987
+ }
988
+ });
989
+
990
+ window.addEventListener('space-gib-file-created', (e: any) => {
991
+ const { path, content, galaxy, branch } = e.detail || {};
992
+ if (galaxy || branch) {
993
+ const g = galaxy || this.changesInstance?.getGalaxyName() || 'code-1';
994
+ const b = branch || this.changesInstance?.getBranchName() || 'main';
995
+ this.changesInstance?.setGalaxyAndBranch(g, b, { preserveChanges: true });
996
+ this.codeEditorInstance?.setGalaxyAndBranch(g, b);
997
+ }
998
+ if (path) {
999
+ this.changesInstance?.recordChange({
1000
+ path,
1001
+ status: 'untracked',
1002
+ content: content ?? '',
1003
+ });
1004
+ this.fileExplorerInstance?.updateDirtyBadges();
1005
+ }
1006
+ });
1007
+
1008
+ window.addEventListener('space-gib-change-discarded', (e: any) => {
1009
+ const { path, originalContent, branch, galaxy } = e.detail || {};
1010
+ if (path) {
1011
+ const b = branch || this.fileExplorerInstance?.getBranchName() || 'main';
1012
+ const g = galaxy || this.fileExplorerInstance?.getGalaxyName() || 'code-1';
1013
+ if (this.fileExplorerInstance?.isUntracked(path, b, g)) {
1014
+ this.fileExplorerInstance.removeFile(path, b, g);
1015
+ if (this.codeEditorInstance && this.codeEditorInstance.getFilepath() === path) {
1016
+ this.codeEditorInstance.clearFile({ repo: g, branch: b });
1017
+ }
1018
+ } else {
1019
+ this.fileExplorerInstance?.setFileStatus(path, undefined, b, g);
1020
+ if (this.codeEditorInstance && this.codeEditorInstance.getFilepath() === path) {
1021
+ if (originalContent !== undefined) {
1022
+ this.codeEditorInstance.setContent(originalContent);
1023
+ } else {
1024
+ this.showCode(path, b, g);
1025
+ }
1026
+ }
1027
+ }
1028
+ this.fileExplorerInstance?.updateDirtyBadges();
1029
+ }
1030
+ });
1031
+ }
1032
+
657
1033
  private async initTimeline(): Promise<void> {
658
1034
  const lc = `${this.lc}[${this.initTimeline.name}]`;
659
1035
  try {
@@ -781,7 +1157,7 @@ export class SpaceGibShellService {
781
1157
  }
782
1158
  }
783
1159
 
784
- public async showCode(filePath?: string, branch?: string): Promise<void> {
1160
+ public async showCode(filePath?: string, branch?: string, galaxy?: string): Promise<void> {
785
1161
  const lc = `${this.lc}[${this.showCode.name}]`;
786
1162
  try {
787
1163
  const centerPanel = document.getElementById(ID_CENTER_PANEL_CONTENT);
@@ -790,6 +1166,10 @@ export class SpaceGibShellService {
790
1166
  return;
791
1167
  }
792
1168
 
1169
+ const currentRoute = getSpaceGibRouterSvc().getCurrentRoute();
1170
+ const targetGalaxy = galaxy || (currentRoute && 'galaxy' in currentRoute ? (currentRoute as any).galaxy : undefined) || this.fileExplorerInstance?.getGalaxyName() || this.changesInstance?.getGalaxyName() || 'code-1';
1171
+ const targetBranch = branch || (currentRoute && 'branch' in currentRoute ? (currentRoute as any).branch : undefined) || this.fileExplorerInstance?.getBranchName() || this.changesInstance?.getBranchName() || 'main';
1172
+
793
1173
  if (!this.codeEditorInstance) {
794
1174
  const componentSvc = await getComponentSvc();
795
1175
  this.codeEditorInstance = await componentSvc.getComponentInstance({
@@ -806,25 +1186,81 @@ export class SpaceGibShellService {
806
1186
  componentToInject: this.codeEditorInstance,
807
1187
  });
808
1188
 
1189
+ this.codeEditorInstance.setGalaxyAndBranch(targetGalaxy, targetBranch);
1190
+
809
1191
  if (filePath) {
810
- let content: string | undefined = undefined;
811
- if (filePath === 'src/helper.ts') {
812
- content = `/**\n * @module my-app/helper\n * Utility functions for space-gib in-memory VCS.\n */\n\nexport function add(a: number, b: number): number {\n return a + b;\n}\n\nexport function multiply(a: number, b: number): number {\n return a * b;\n}\n`;
813
- } else if (filePath === 'README.md') {
814
- content = `# my-app\n\nDecentralized version-controlled application running on **space-gib**.\n\n- Zero SaaS lock-in\n- Inverted soft rel8ns for living companion discussions\n- Merkle DAG branch worktrees\n`;
815
- } else if (filePath === 'tsconfig.json') {
816
- content = `{\n "compilerOptions": {\n "target": "ES2022",\n "module": "NodeNext",\n "strict": true,\n "esModuleInterop": true\n }\n}\n`;
1192
+ let casContent: string | undefined = undefined;
1193
+ let tipAddr: string = 'tip';
1194
+
1195
+ try {
1196
+ const { metaspace, space } = await getPlaygroundMetaspaceAndSpace();
1197
+ const router = getSpaceGibRouterSvc();
1198
+ let galaxyIbGib: B2tFSGalaxyIbGib_V1 | undefined;
1199
+ for (const manifest of router.cosmosManifestCache?.values() || []) {
1200
+ const entry = manifest.data?.galaxies?.[targetGalaxy];
1201
+ if (entry?.galaxyAddr) {
1202
+ galaxyIbGib = await getIbGibFromSpace<B2tFSGalaxyIbGib_V1>({ addr: entry.galaxyAddr, space });
1203
+ if (galaxyIbGib) break;
1204
+ }
1205
+ }
1206
+ if (galaxyIbGib) {
1207
+ const branchAndRoot = await getBranchAndRootItemInfosAndIbGibsAndSpaceFromGalaxyIbGib({
1208
+ galaxyIbGib,
1209
+ branchName: targetBranch,
1210
+ metaspace,
1211
+ });
1212
+ tipAddr = branchAndRoot.branchIbGib.gib || 'tip';
1213
+ const rootItem = branchAndRoot.rootItemIbGib_latest || branchAndRoot.rootItemIbGib;
1214
+ const fileItem = await getFileItemFromPath({
1215
+ branchSpace: branchAndRoot.branchSpace,
1216
+ rootItem,
1217
+ filePath,
1218
+ });
1219
+ if (fileItem) {
1220
+ casContent = await getFileContent({ branchSpace: branchAndRoot.branchSpace, fileItem });
1221
+ }
1222
+ }
1223
+ } catch (readErr) {
1224
+ console.warn(`${lc} Error reading file [${filePath}] from branch space: ${extractErrorMsg(readErr)}`);
817
1225
  }
818
1226
 
1227
+ const workingChange = this.changesInstance?.getChange(filePath, targetBranch);
1228
+ const isUntrackedInExplorer = this.fileExplorerInstance?.isUntracked(filePath, targetBranch, targetGalaxy);
1229
+ const fileExistsInCas = casContent !== undefined;
1230
+ const hasWorkingTreeChange = workingChange !== undefined;
1231
+
1232
+ // If file doesn't exist in CAS, and is neither in working tree nor marked untracked in explorer, clear editor
1233
+ if (!fileExistsInCas && !hasWorkingTreeChange && !isUntrackedInExplorer) {
1234
+ this.codeEditorInstance.clearFile({
1235
+ repo: targetGalaxy,
1236
+ branch: targetBranch,
1237
+ });
1238
+ this.fileExplorerInstance?.selectFile('');
1239
+ return;
1240
+ }
1241
+
1242
+ const isUntracked = !fileExistsInCas;
1243
+ const content = workingChange?.content ?? casContent ?? '';
1244
+
819
1245
  this.codeEditorInstance.loadFile({
820
1246
  filepath: filePath,
821
- branch: branch || 'main',
822
- tipAddr: '7f8a1b2',
1247
+ repo: targetGalaxy,
1248
+ branch: targetBranch,
1249
+ tipAddr,
823
1250
  content,
1251
+ originalContent: casContent,
1252
+ isUntracked,
1253
+ });
1254
+ } else {
1255
+ this.codeEditorInstance.clearFile({
1256
+ repo: targetGalaxy,
1257
+ branch: targetBranch,
824
1258
  });
825
1259
  }
826
1260
  }
827
1261
 
1262
+ this.changesInstance?.setGalaxyAndBranch(targetGalaxy, targetBranch, { preserveChanges: true });
1263
+
828
1264
  const codeChatContainer = document.getElementById('code-chat-view-container');
829
1265
  const notesInspectorContainer = document.getElementById('notes-inspector-container');
830
1266
  if (notesInspectorContainer) { notesInspectorContainer.style.display = 'none'; }
@@ -1004,8 +1440,25 @@ export class SpaceGibShellService {
1004
1440
 
1005
1441
  private bindCodeEditorEvents(): void {
1006
1442
  window.addEventListener('space-gib-code-changed', (e: any) => {
1007
- const { filepath, lineCount } = e.detail || {};
1008
- console.log(`${this.lc} Code changed in ${filepath} (${lineCount} lines)`);
1443
+ const { filepath, content, lineCount, isReverted, isUntracked: isUntrackedProp, branch, repo } = e.detail || {};
1444
+ const targetBranch = branch || this.changesInstance?.getBranchName() || 'main';
1445
+ const targetGalaxy = repo || this.changesInstance?.getGalaxyName() || 'code-1';
1446
+ console.log(`${this.lc} Code changed in ${filepath} on branch [${targetBranch}] (${lineCount} lines, isReverted=${isReverted})`);
1447
+ if (filepath) {
1448
+ if (isReverted) {
1449
+ this.changesInstance?.removeChange(filepath, targetBranch);
1450
+ this.fileExplorerInstance?.setFileStatus(filepath, undefined, targetBranch, targetGalaxy);
1451
+ } else {
1452
+ const isUntracked = Boolean(isUntrackedProp || this.changesInstance?.isUntracked(filepath, targetBranch) || this.fileExplorerInstance?.isUntracked(filepath, targetBranch, targetGalaxy));
1453
+ const status = isUntracked ? 'untracked' : 'modified';
1454
+ this.changesInstance?.recordChange({
1455
+ path: filepath,
1456
+ status,
1457
+ content: content ?? '',
1458
+ }, targetBranch);
1459
+ this.fileExplorerInstance?.setFileStatus(filepath, status, targetBranch, targetGalaxy);
1460
+ }
1461
+ }
1009
1462
  });
1010
1463
 
1011
1464
  window.addEventListener('space-gib-code-copied', (e: any) => {
@@ -1055,38 +1508,30 @@ export class SpaceGibShellService {
1055
1508
  }
1056
1509
 
1057
1510
  public async showPitch(): Promise<void> {
1058
- const lc = `${this.lc}[${this.showPitch.name}]`;
1059
- try {
1060
- const centerPanel = document.getElementById(ID_CENTER_PANEL_CONTENT);
1061
- if (!centerPanel) {
1062
- console.warn(`${lc} ${ID_CENTER_PANEL_CONTENT} element not found.`);
1063
- return;
1064
- }
1065
-
1066
- if (!this.pitchInstance) {
1067
- const componentSvc = await getComponentSvc();
1068
- this.pitchInstance = await componentSvc.getComponentInstance({
1069
- path: PITCH_COMPONENT_NAME,
1070
- ibGibAddr: ROOT_ADDR,
1071
- useRegExpPrefilter: true,
1072
- }) as PitchComponentInstance;
1073
- }
1511
+ await this.startPitchTour();
1512
+ }
1074
1513
 
1075
- if (this.pitchInstance) {
1076
- const componentSvc = await getComponentSvc();
1077
- await componentSvc.inject({
1078
- parentEl: centerPanel,
1079
- componentToInject: this.pitchInstance,
1080
- });
1514
+ public async startPitchTour(): Promise<void> {
1515
+ const lc = `${this.lc}[${this.startPitchTour.name}]`;
1516
+ console.log(`${lc} startPitchTour triggered.`);
1517
+ try {
1518
+ const tutorial = getTutorialController();
1519
+ console.log(`${lc} tutorialController isActive = ${tutorial.isActive}`);
1520
+ if (tutorial.isActive) {
1521
+ await tutorial.next();
1522
+ } else {
1523
+ await tutorial.start(PITCH_TOUR_SCRIPT, this);
1081
1524
  }
1082
-
1083
- this.collapsePanels();
1084
1525
  } catch (error) {
1085
1526
  console.error(`${lc} ${extractErrorMsg(error)}`);
1086
1527
  }
1087
1528
  }
1088
1529
 
1089
1530
  public closePitch(): void {
1531
+ const tutorial = getTutorialController();
1532
+ if (tutorial.isActive) {
1533
+ tutorial.stop();
1534
+ }
1090
1535
  this.showCosmos();
1091
1536
  }
1092
1537
 
@@ -1165,8 +1610,7 @@ export class SpaceGibShellService {
1165
1610
  if (activitySpaces) {
1166
1611
  let matched = false;
1167
1612
  activitySpaces.querySelectorAll('.activity-bar-btn').forEach(b => {
1168
- const isMatch = b.getAttribute('data-space-id') === spaceId ||
1169
- b.getAttribute('data-space-type') === spaceId;
1613
+ const isMatch = b.getAttribute('data-space-id') === spaceId;
1170
1614
  b.classList.toggle('active', isMatch);
1171
1615
  if (isMatch) matched = true;
1172
1616
  });
@@ -1269,10 +1713,33 @@ export class SpaceGibShellService {
1269
1713
  }
1270
1714
  if (space.type === 'code') {
1271
1715
  this.showLeftPanelView('code', space.name);
1272
- this.switchToReposMode();
1716
+ this.switchToReposMode({ skipNavigate: true, galaxyId: space.name });
1717
+ if (!this.isApplyingRoute && cosmos?.name) {
1718
+ const tenant = this.getActiveTenant(cosmos.name);
1719
+ getSpaceGibRouterSvc().navigate({
1720
+ kind: 'galaxy',
1721
+ ...(tenant ? { tenant } : {}),
1722
+ cosmos: cosmos.name,
1723
+ galaxy: space.name,
1724
+ topology: 'branching',
1725
+ discriminator: 'code',
1726
+ branch: 'main',
1727
+ });
1728
+ }
1273
1729
  } else if (space.type === 'text') {
1274
1730
  this.showLeftPanelView('text', space.name);
1275
- this.switchToNotesMode(space.name);
1731
+ this.switchToNotesMode(space.name, { skipNavigate: true, galaxyId: space.name });
1732
+ if (!this.isApplyingRoute && cosmos?.name) {
1733
+ const tenant = this.getActiveTenant(cosmos.name);
1734
+ getSpaceGibRouterSvc().navigate({
1735
+ kind: 'galaxy',
1736
+ ...(tenant ? { tenant } : {}),
1737
+ cosmos: cosmos.name,
1738
+ galaxy: space.name,
1739
+ topology: 'linear',
1740
+ discriminator: 'notes',
1741
+ });
1742
+ }
1276
1743
  }
1277
1744
  }
1278
1745
  });
@@ -1304,6 +1771,7 @@ export class SpaceGibShellService {
1304
1771
  const btnHeaderClone = document.getElementById('btn-header-clone');
1305
1772
 
1306
1773
  btnHeaderPitch?.addEventListener('click', () => {
1774
+ console.log(`[SpaceGibShellService] #btn-header-pitch clicked! Triggering showPitch()...`);
1307
1775
  this.showPitch();
1308
1776
  });
1309
1777
 
@@ -1315,12 +1783,100 @@ export class SpaceGibShellService {
1315
1783
  this.openCloneModal(e.detail);
1316
1784
  });
1317
1785
 
1318
- window.addEventListener('space-gib-branch-requested', (e: any) => {
1786
+ window.addEventListener('space-gib-branch-requested', async (e: any) => {
1319
1787
  const { galaxyName, sourceBranch, newBranchName, scope, itemPath } = e.detail || {};
1320
- const scopeDesc = scope === 'item' ? `item [${itemPath}]` : `worktree [${sourceBranch}]`;
1321
- console.log(`${this.lc} [Phase 2 Branch Stub] Created branch: [${newBranchName}] diverging from ${scopeDesc} in ${galaxyName || 'Code Galaxy'}`);
1322
- this.showLeftPanelView('code', 'Code Space');
1323
- this.fileExplorerInstance?.setBranch(newBranchName);
1788
+ const cleanGalaxy = galaxyName || 'code-1';
1789
+ const cleanSourceBranch = sourceBranch || 'main';
1790
+ const cleanNewBranch = newBranchName?.trim();
1791
+ if (!cleanNewBranch) return;
1792
+
1793
+ const validation = validateBranchName(cleanNewBranch);
1794
+ if (!validation.isValid) {
1795
+ alert(`Invalid branch name: ${validation.error}`);
1796
+ return;
1797
+ }
1798
+
1799
+ const lc = `${this.lc}[space-gib-branch-requested]`;
1800
+ try {
1801
+ const { metaspace, space } = await getPlaygroundMetaspaceAndSpace();
1802
+ const router = getSpaceGibRouterSvc();
1803
+
1804
+ // 1. Resolve GalaxyIbGib
1805
+ let galaxyIbGib: B2tFSGalaxyIbGib_V1 | undefined;
1806
+ for (const manifest of router.cosmosManifestCache?.values() || []) {
1807
+ const entry = manifest.data?.galaxies?.[cleanGalaxy];
1808
+ if (entry?.galaxyAddr) {
1809
+ galaxyIbGib = await getIbGibFromSpace<B2tFSGalaxyIbGib_V1>({ addr: entry.galaxyAddr, space });
1810
+ if (galaxyIbGib) break;
1811
+ }
1812
+ }
1813
+
1814
+ if (!galaxyIbGib) {
1815
+ throw new Error(`Galaxy [${cleanGalaxy}] not found in cosmos manifest.`);
1816
+ }
1817
+
1818
+ // 2. Resolve source branch space
1819
+ const branchAndRoot = await getBranchAndRootItemInfosAndIbGibsAndSpaceFromGalaxyIbGib({
1820
+ galaxyIbGib,
1821
+ branchName: cleanSourceBranch,
1822
+ metaspace,
1823
+ });
1824
+
1825
+ const activeBranchSpace = branchAndRoot.branchSpace;
1826
+
1827
+ // 3. Execute isomorphic VCS branch creation
1828
+ const branchRes = await executeVcsBranch({
1829
+ metaspace,
1830
+ localSpace: space,
1831
+ sourceBranchSpace: activeBranchSpace,
1832
+ galaxyIbGib,
1833
+ branchName: cleanNewBranch,
1834
+ sourceBranchName: cleanSourceBranch,
1835
+ itemPath: scope === 'item' ? itemPath : undefined,
1836
+ });
1837
+
1838
+ const finalGalaxy = branchRes.updatedGalaxy || galaxyIbGib;
1839
+ const finalGalaxyAddr = getIbGibAddr({ ibGib: finalGalaxy });
1840
+
1841
+ // 4. Update router cosmos manifest cache with updated galaxy address
1842
+ for (const manifest of router.cosmosManifestCache?.values() || []) {
1843
+ if (manifest.data?.galaxies?.[cleanGalaxy]) {
1844
+ manifest.data.galaxies[cleanGalaxy].galaxyAddr = finalGalaxyAddr;
1845
+ break;
1846
+ }
1847
+ }
1848
+
1849
+ // 5. Broadcast new galaxy IbGib to metaspace subscribers
1850
+ await (metaspace as any).registerNewIbGib?.({ ibGib: finalGalaxy, space });
1851
+
1852
+ // 6. Update Changes, File Explorer & Code Editor
1853
+ this.changesInstance?.setGalaxyAndBranch(cleanGalaxy, cleanNewBranch);
1854
+ this.fileExplorerInstance?.setGalaxyAndBranch(cleanGalaxy, cleanNewBranch);
1855
+ this.codeEditorInstance?.setGalaxyAndBranch(cleanGalaxy, cleanNewBranch);
1856
+ this.timelineInstance?.setBranch(cleanNewBranch);
1857
+
1858
+ // 8. Navigate router to new branch
1859
+ const activeCosmos = this.cosmosNavigatorInstance?.getActiveCosmos();
1860
+ const activePath = this.codeEditorInstance?.getFilepath();
1861
+ if (activeCosmos) {
1862
+ const tenant = this.getActiveTenant(activeCosmos);
1863
+ router.navigate({
1864
+ kind: 'galaxy',
1865
+ cosmos: activeCosmos.id || activeCosmos.name,
1866
+ galaxy: cleanGalaxy,
1867
+ topology: 'branching',
1868
+ discriminator: 'code',
1869
+ branch: cleanNewBranch,
1870
+ ...(activePath ? { filePath: activePath } : {}),
1871
+ ...(tenant ? { tenant } : {}),
1872
+ });
1873
+ }
1874
+
1875
+ console.log(`${lc} Successfully diverged to new branch [${cleanNewBranch}] from ${scope === 'item' ? `item [${itemPath}]` : `worktree [${cleanSourceBranch}]`} in galaxy [${cleanGalaxy}]`);
1876
+ } catch (err) {
1877
+ console.error(`${lc} Failed to branch: ${extractErrorMsg(err)}`);
1878
+ alert(`Failed to create branch: ${extractErrorMsg(err)}`);
1879
+ }
1324
1880
  });
1325
1881
 
1326
1882
  btnFooterAgent?.addEventListener('click', () => {
@@ -1413,9 +1969,9 @@ export class SpaceGibShellService {
1413
1969
 
1414
1970
  btnSideNewCosmos?.addEventListener('click', () => {
1415
1971
  this.switchToSpacesMode();
1416
- this.cosmosNavigatorInstance?.triggerBigBangSequence(() => {
1972
+ this.cosmosNavigatorInstance?.triggerBigBangSequence(async () => {
1417
1973
  const nextIdx = (this.cosmosNavigatorInstance?.getCosmoses().length || 0) + 1;
1418
- const newCosmos = this.cosmosNavigatorInstance?.createCosmos(`Cosmos ${nextIdx}`);
1974
+ const newCosmos = await this.cosmosNavigatorInstance?.createCosmos(`cosmos-${nextIdx}`);
1419
1975
  if (newCosmos) {
1420
1976
  this.cosmosNavigatorInstance?.selectCosmos(newCosmos.id);
1421
1977
  }
@@ -1461,13 +2017,14 @@ export class SpaceGibShellService {
1461
2017
  spacesList.querySelectorAll('.cosmos-space-item').forEach(el => {
1462
2018
  el.addEventListener('click', () => {
1463
2019
  const type = el.getAttribute('data-space-type');
2020
+ const spaceId = el.getAttribute('data-space-id') || '';
1464
2021
  const name = el.querySelector('.space-item-name')?.textContent || '';
1465
2022
  if (type === 'code') {
1466
2023
  this.showLeftPanelView('code', name);
1467
- this.switchToReposMode();
2024
+ this.switchToReposMode({ galaxyId: spaceId });
1468
2025
  } else {
1469
2026
  this.showLeftPanelView('text', name);
1470
- this.switchToNotesMode(name);
2027
+ this.switchToNotesMode(name, { galaxyId: spaceId });
1471
2028
  }
1472
2029
  });
1473
2030
  });
@@ -1492,10 +2049,10 @@ export class SpaceGibShellService {
1492
2049
 
1493
2050
  if (type === 'code') {
1494
2051
  this.showLeftPanelView('code', title);
1495
- this.switchToReposMode();
2052
+ this.switchToReposMode({ galaxyId: spaceId });
1496
2053
  } else {
1497
2054
  this.showLeftPanelView('text', title);
1498
- this.switchToNotesMode(title);
2055
+ this.switchToNotesMode(title, { galaxyId: spaceId });
1499
2056
  }
1500
2057
  });
1501
2058
  });
@@ -1603,12 +2160,12 @@ export class SpaceGibShellService {
1603
2160
  });
1604
2161
 
1605
2162
  const btnNew = menu.querySelector('#btn-header-menu-new-cosmos');
1606
- btnNew?.addEventListener('click', () => {
2163
+ btnNew?.addEventListener('click', async () => {
1607
2164
  menu.classList.add('hidden');
1608
2165
  this.switchToSpacesMode();
1609
2166
  this.cosmosNavigatorInstance?.triggerBigBangExplosion();
1610
2167
  const nextIdx = cosmoses.length + 1;
1611
- const newC = this.cosmosNavigatorInstance?.createCosmos(`Cosmos ${nextIdx}`);
2168
+ const newC = await this.cosmosNavigatorInstance?.createCosmos(`cosmos-${nextIdx}`);
1612
2169
  if (newC) {
1613
2170
  setTimeout(() => {
1614
2171
  this.cosmosNavigatorInstance?.selectCosmos(newC.id);
@@ -1623,17 +2180,25 @@ export class SpaceGibShellService {
1623
2180
  const viewCode = document.getElementById('left-panel-code-view');
1624
2181
  const viewText = document.getElementById('left-panel-text-view');
1625
2182
 
1626
- if (viewPoly) { viewPoly.style.display = viewName === 'polycosmos' ? 'block' : 'none'; }
1627
- if (viewCosmos) { viewCosmos.style.display = viewName === 'cosmos' ? 'block' : 'none'; }
2183
+ if (viewPoly) {
2184
+ viewPoly.style.display = viewName === 'polycosmos' ? 'flex' : 'none';
2185
+ viewPoly.style.flexDirection = 'column';
2186
+ }
2187
+ if (viewCosmos) {
2188
+ viewCosmos.style.display = viewName === 'cosmos' ? 'flex' : 'none';
2189
+ viewCosmos.style.flexDirection = 'column';
2190
+ }
1628
2191
  if (viewCode) {
1629
- viewCode.style.display = viewName === 'code' ? 'block' : 'none';
2192
+ viewCode.style.display = viewName === 'code' ? 'flex' : 'none';
2193
+ viewCode.style.flexDirection = 'column';
1630
2194
  if (title) {
1631
2195
  const titleEl = document.getElementById('left-code-space-title');
1632
2196
  if (titleEl) { titleEl.textContent = title; }
1633
2197
  }
1634
2198
  }
1635
2199
  if (viewText) {
1636
- viewText.style.display = viewName === 'text' ? 'block' : 'none';
2200
+ viewText.style.display = viewName === 'text' ? 'flex' : 'none';
2201
+ viewText.style.flexDirection = 'column';
1637
2202
  if (viewName === 'text') {
1638
2203
  this.initNotesExplorer(title);
1639
2204
  }
@@ -1668,7 +2233,7 @@ export class SpaceGibShellService {
1668
2233
  this.setMode('spaces');
1669
2234
  }
1670
2235
 
1671
- private switchToReposMode(opts?: { skipNavigate?: boolean }): void {
2236
+ private switchToReposMode(opts?: { skipNavigate?: boolean; galaxyId?: string }): void {
1672
2237
  this.setMode('repos');
1673
2238
  this.expandRightPanel();
1674
2239
  if (!opts?.skipNavigate && !this.isApplyingRoute) {
@@ -1676,9 +2241,11 @@ export class SpaceGibShellService {
1676
2241
  if (activeCosmos) {
1677
2242
  const tenant = this.getActiveTenant(activeCosmos);
1678
2243
  getSpaceGibRouterSvc().navigate({
1679
- kind: 'code-galaxy',
2244
+ kind: 'galaxy',
1680
2245
  cosmos: activeCosmos.id || activeCosmos.name,
1681
- galaxyId: 'code-1',
2246
+ galaxy: opts?.galaxyId || 'code-1',
2247
+ topology: 'branching',
2248
+ discriminator: 'code',
1682
2249
  branch: 'main',
1683
2250
  ...(tenant ? { tenant } : {}),
1684
2251
  });
@@ -1694,10 +2261,12 @@ export class SpaceGibShellService {
1694
2261
  if (activeCosmos) {
1695
2262
  const tenant = this.getActiveTenant(activeCosmos);
1696
2263
  getSpaceGibRouterSvc().navigate({
1697
- kind: 'notes-galaxy',
2264
+ kind: 'galaxy',
1698
2265
  cosmos: activeCosmos.id || activeCosmos.name,
1699
- galaxyId: opts?.galaxyId || 'notes-1',
1700
- ...(title ? { topicTjp: title } : {}),
2266
+ galaxy: opts?.galaxyId || 'notes-1',
2267
+ topology: 'linear',
2268
+ discriminator: 'notes',
2269
+ ...(title ? { topicTjp: title, trail: [title], trailMode: 'tjp' } : {}),
1701
2270
  ...(tenant ? { tenant } : {}),
1702
2271
  });
1703
2272
  }
@@ -1745,12 +2314,17 @@ export class SpaceGibShellService {
1745
2314
  try {
1746
2315
  if (route.kind !== 'polycosmos') {
1747
2316
  this.hasBigBangExploded = true;
2317
+ if (this.cosmosNavigatorInstance) {
2318
+ this.cosmosNavigatorInstance.hasBigBangExploded = true;
2319
+ }
1748
2320
  this.revealChrome();
1749
2321
  }
1750
2322
 
1751
2323
  // If the route omitted a tenant specifier but the user is authenticated (or in playground),
1752
2324
  // canonicalize the URL in the address bar so copying and sharing is always fully qualified.
1753
- if (route.kind !== 'admin-review' && !route.tenant) {
2325
+ // Exception: For bare landing ('polycosmos'), do not attach tenant or redirect to /@playground/polycosmos
2326
+ // so the URL remains bare (e.g. https://local.ibgib.space) to keep full focus on Big Bang.
2327
+ if (route.kind !== 'admin-review' && route.kind !== 'polycosmos' && !route.tenant) {
1754
2328
  const cosmosName = 'cosmos' in route ? route.cosmos : undefined;
1755
2329
  const activeTenant = this.getActiveTenant(cosmosName);
1756
2330
  if (activeTenant) {
@@ -1767,7 +2341,11 @@ export class SpaceGibShellService {
1767
2341
  }
1768
2342
  case 'cosmos': {
1769
2343
  if (this.cosmosNavigatorInstance) {
1770
- const cosmos = await this.cosmosNavigatorInstance.ensureCosmos(route.cosmos);
2344
+ const cosmos = await this.cosmosNavigatorInstance.findCosmos(route.cosmos);
2345
+ if (!cosmos) {
2346
+ this.show404(`Cosmos "${route.cosmos}" not found.`);
2347
+ return;
2348
+ }
1771
2349
  this.cosmosNavigatorInstance.selectCosmos(cosmos.id);
1772
2350
  this.updateActiveCosmosDisplay(cosmos);
1773
2351
  }
@@ -1776,60 +2354,46 @@ export class SpaceGibShellService {
1776
2354
  await this.showCosmos();
1777
2355
  break;
1778
2356
  }
1779
- case 'code-galaxy': {
2357
+ case 'galaxy': {
2358
+ let cosmosInfo: CosmosInfo | undefined;
1780
2359
  if (this.cosmosNavigatorInstance) {
1781
- const cosmos = await this.cosmosNavigatorInstance.ensureCosmos(route.cosmos);
1782
- if (!cosmos.spaces.some(s => s.type === 'code' || s.id === route.galaxyId)) {
1783
- cosmos.spaces.push({
1784
- id: route.galaxyId || 'code-1',
1785
- type: 'code',
1786
- name: 'Code Space 1',
1787
- shortLabel: 'Code 1',
1788
- icon: '</>',
1789
- });
2360
+ cosmosInfo = await this.cosmosNavigatorInstance.findCosmos(route.cosmos);
2361
+ if (!cosmosInfo) {
2362
+ this.show404(`Cosmos "${route.cosmos}" not found.`);
2363
+ return;
1790
2364
  }
1791
- this.cosmosNavigatorInstance.selectCosmos(cosmos.id);
1792
- this.updateActiveCosmosDisplay(cosmos, 'code');
1793
- }
1794
- this.expandActivityBar();
1795
- this.expandLeftPanel();
1796
- this.setActiveActivityBarButton('space', 'code');
1797
- this.showLeftPanelView('code', route.galaxyId);
1798
- this.switchToReposMode({ skipNavigate: true });
1799
- if (route.branch && this.fileExplorerInstance) {
1800
- this.fileExplorerInstance.setBranch(route.branch);
1801
- }
1802
- if (route.filePath && this.fileExplorerInstance) {
1803
- this.fileExplorerInstance.selectFile(route.filePath);
1804
- }
1805
- await this.showCode(route.filePath, route.branch);
1806
- break;
1807
- }
1808
- case 'notes-galaxy': {
1809
- if (this.cosmosNavigatorInstance) {
1810
- const cosmos = await this.cosmosNavigatorInstance.ensureCosmos(route.cosmos);
1811
- if (!cosmos.spaces.some(s => s.type === 'text' || s.id === route.galaxyId)) {
1812
- cosmos.spaces.push({
1813
- id: route.galaxyId || 'notes-1',
1814
- type: 'text',
1815
- name: 'Text Space 1',
1816
- shortLabel: 'Text 1',
1817
- icon: '📝',
2365
+ const spaceType = route.topology === 'branching' ? 'code' : 'text';
2366
+ if (!cosmosInfo.spaces.some(s => s.id === route.galaxy)) {
2367
+ cosmosInfo.spaces.push({
2368
+ id: route.galaxy,
2369
+ type: spaceType,
2370
+ name: route.galaxy,
2371
+ shortLabel: route.galaxy,
2372
+ icon: route.topology === 'branching' ? '</>' : '📝',
1818
2373
  });
1819
2374
  }
1820
- this.cosmosNavigatorInstance.selectCosmos(cosmos.id);
1821
- this.updateActiveCosmosDisplay(cosmos, 'text');
2375
+ this.cosmosNavigatorInstance.selectCosmos(cosmosInfo.id);
2376
+ this.updateActiveCosmosDisplay(cosmosInfo, route.galaxy || spaceType);
1822
2377
  }
1823
- this.expandActivityBar();
1824
- this.expandLeftPanel();
1825
- this.setActiveActivityBarButton('space', 'text');
1826
- this.showLeftPanelView('text', route.galaxyId);
1827
- this.switchToNotesMode(route.topicTjp || route.galaxyId, { skipNavigate: true, galaxyId: route.galaxyId });
1828
- await this.showNotes(route.topicTjp || route.galaxyId, {
1829
- trail: route.trail,
1830
- trailMode: route.trailMode,
1831
- query: route.query,
1832
- });
2378
+
2379
+ const policy: GalaxyPolicy = {
2380
+ topology: route.topology,
2381
+ role: route.topology === 'branching' ? 'intrinsic' : 'extrinsic',
2382
+ storageMode: 'text',
2383
+ defaultBranch: 'main',
2384
+ };
2385
+
2386
+ const context: PanelLoadContext = {
2387
+ route,
2388
+ cosmosName: route.cosmos,
2389
+ galaxyName: route.galaxy,
2390
+ discriminator: route.discriminator,
2391
+ policy,
2392
+ cosmosInfo,
2393
+ shell: this,
2394
+ };
2395
+
2396
+ await this.loadRouteForPanels(route, context);
1833
2397
  break;
1834
2398
  }
1835
2399
  case 'raw-addr': {
@@ -1858,7 +2422,11 @@ export class SpaceGibShellService {
1858
2422
  try {
1859
2423
  console.log(`${lc} Resolving raw snapshot route: cosmos=${route.cosmos}, spaceId=${route.spaceId}, addr=${route.ibGibAddr}`);
1860
2424
  if (this.cosmosNavigatorInstance) {
1861
- const cosmos = await this.cosmosNavigatorInstance.ensureCosmos(route.cosmos);
2425
+ const cosmos = await this.cosmosNavigatorInstance.findCosmos(route.cosmos);
2426
+ if (!cosmos) {
2427
+ this.show404(`Cosmos "${route.cosmos}" not found.`);
2428
+ return;
2429
+ }
1862
2430
  this.cosmosNavigatorInstance.selectCosmos(cosmos.id);
1863
2431
  this.updateActiveCosmosDisplay(cosmos);
1864
2432
  }
@@ -1899,7 +2467,11 @@ export class SpaceGibShellService {
1899
2467
  try {
1900
2468
  console.log(`${lc} Resolving living tip route: cosmos=${route.cosmos}, spaceId=${route.spaceId}, tjp=${route.tjpAddr}`);
1901
2469
  if (this.cosmosNavigatorInstance) {
1902
- const cosmos = await this.cosmosNavigatorInstance.ensureCosmos(route.cosmos);
2470
+ const cosmos = await this.cosmosNavigatorInstance.findCosmos(route.cosmos);
2471
+ if (!cosmos) {
2472
+ this.show404(`Cosmos "${route.cosmos}" not found.`);
2473
+ return;
2474
+ }
1903
2475
  this.cosmosNavigatorInstance.selectCosmos(cosmos.id);
1904
2476
  this.updateActiveCosmosDisplay(cosmos);
1905
2477
  }