@ibgib/space-gib 0.0.34 → 0.0.35

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 (45) hide show
  1. package/dist/client/bootstrap.mjs +1 -1
  2. package/dist/client/{chunk-25PXC4HP.mjs → chunk-FIAGRVBY.mjs} +1487 -468
  3. package/dist/client/chunk-XLQ3UCQY.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/index.html +6 -0
  7. package/dist/client/index.mjs +22 -45
  8. package/dist/client/privacy.html +22 -22
  9. package/dist/client/script.mjs +1 -1
  10. package/dist/server/server.mjs +3 -3
  11. package/package.json +6 -6
  12. package/src/client/AUTO-GENERATED-version.mts +1 -1
  13. package/src/client/cosmos/seed-cosmos-workspace.mts +20 -6
  14. package/src/client/css/activity-bar.css +21 -3
  15. package/src/client/css/layout.css +126 -5
  16. package/src/client/dev-tools/vcs-workspace.mts +4 -4
  17. package/src/client/index.html +6 -0
  18. package/src/client/privacy.html +22 -22
  19. package/src/client/ui/component/changes/changes.css +413 -0
  20. package/src/client/ui/component/changes/changes.html +37 -0
  21. package/src/client/ui/component/changes/changes.mts +766 -0
  22. package/src/client/ui/component/changes/index.mts +9 -0
  23. package/src/client/ui/component/chat-view/chat-view.html +6 -58
  24. package/src/client/ui/component/chat-view/chat-view.mts +13 -26
  25. package/src/client/ui/component/clone/clone.mts +67 -39
  26. package/src/client/ui/component/code-editor/code-editor.css +91 -9
  27. package/src/client/ui/component/code-editor/code-editor.html +58 -45
  28. package/src/client/ui/component/code-editor/code-editor.mts +265 -49
  29. package/src/client/ui/component/cosmos-navigator/cosmos-navigator.css +1 -1
  30. package/src/client/ui/component/cosmos-navigator/cosmos-navigator.html +5 -10
  31. package/src/client/ui/component/cosmos-navigator/cosmos-navigator.mts +297 -64
  32. package/src/client/ui/component/file-explorer/file-explorer.css +475 -11
  33. package/src/client/ui/component/file-explorer/file-explorer.html +45 -65
  34. package/src/client/ui/component/file-explorer/file-explorer.mts +1164 -98
  35. package/src/client/ui/component/nested-chat/chat/chat.mts +5 -90
  36. package/src/client/ui/component/nested-chat/nested-chat.mts +10 -8
  37. package/src/client/ui/component/timeline/timeline.css +16 -1
  38. package/src/client/ui/component/timeline/timeline.mts +37 -93
  39. package/src/client/ui/router/space-gib-router-helpers.mts +33 -233
  40. package/src/client/ui/router/space-gib-router-helpers.respec.mts +298 -122
  41. package/src/client/ui/router/space-gib-router-service.mts +28 -151
  42. package/src/client/ui/router/space-gib-router-types.mts +32 -76
  43. package/src/client/ui/shell/cosmic-big-bang.mts +12 -3
  44. package/src/client/ui/shell/space-gib-shell-service.mts +687 -119
  45. 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,
@@ -69,20 +73,137 @@ import {
69
73
  getSpaceGibRouterSvc,
70
74
  SpaceGibRouteInfo,
71
75
  SpaceGibTenant,
76
+ GalaxyRouteInfo,
72
77
  RawAddrRouteInfo,
73
78
  RawTjpRouteInfo,
74
79
  } from "../router/index.mjs";
80
+ import { GalaxyPolicy } from "@ibgib/core-gib/dist/cosmology/galaxy/galaxy-types.mjs";
75
81
  import { getGlobalMetaspace_waitIfNeeded } from "@ibgib/web-gib/dist/helpers.mjs";
76
- import { prewarmPlaygroundMetaspace } from "../../cosmos/seed-cosmos-workspace.mjs";
82
+ import { getPlaygroundMetaspaceAndSpace, prewarmPlaygroundMetaspace } from "../../cosmos/seed-cosmos-workspace.mjs";
83
+ import {
84
+ getBranchAndRootItemInfosAndIbGibsAndSpaceFromGalaxyIbGib,
85
+ getFileItemFromPath,
86
+ getFileContent,
87
+ } from "@ibgib/core-gib/dist/cosmology/branching/item/item-helpers.mjs";
88
+ import { GalaxyIbGib_V1 } from "@ibgib/core-gib/dist/cosmology/galaxy/galaxy-types.mjs";
89
+ import { B2tFSGalaxyIbGib_V1 } from "@ibgib/core-gib/dist/cosmology/branching/repo/b2tfs-repo-types.mjs";
90
+ import { getIbGibFromSpace } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
91
+ import {
92
+ executeVcsBranch,
93
+ executeVcsCheckout,
94
+ } from "@ibgib/core-gib/dist/cosmology/branching/commands/index.mjs";
95
+ import { validateBranchName } from "@ibgib/core-gib/dist/cosmology/branching/branch/branch-helpers.mjs";
96
+
97
+ export interface PanelLoadContext {
98
+ route: GalaxyRouteInfo;
99
+ cosmosName: string;
100
+ galaxyName: string;
101
+ discriminator?: string;
102
+ policy: GalaxyPolicy;
103
+ cosmosInfo?: CosmosInfo;
104
+ shell: SpaceGibShellService;
105
+ }
106
+
107
+ export type PanelName = 'LeftPanel' | 'CenterPanel' | 'RightPanel';
108
+
109
+ export type PanelLoaderFn = (route: GalaxyRouteInfo, context: PanelLoadContext) => Promise<void>;
77
110
 
111
+ export type PanelLoaderMap = {
112
+ [K in PanelName as `loadRouteFor${K}`]: PanelLoaderFn;
113
+ };
78
114
 
79
115
  export class SpaceGibShellService {
80
116
  private lc: string = `[SpaceGibShellService]`;
81
117
 
118
+ /**
119
+ * Default convention panel loaders for Branching Galaxies (Class I Substrate / Code).
120
+ */
121
+ protected branchingPanelLoaders: PanelLoaderMap = {
122
+ loadRouteForLeftPanel: async (route, context) => {
123
+ this.expandActivityBar();
124
+ this.expandLeftPanel();
125
+ this.setActiveActivityBarButton('space', route.galaxy);
126
+ this.showLeftPanelView('code', route.galaxy);
127
+ if (this.fileExplorerInstance) {
128
+ this.fileExplorerInstance.setGalaxyAndBranch(route.galaxy, route.branch || 'main');
129
+ if (route.filePath) {
130
+ this.fileExplorerInstance.selectFile(route.filePath);
131
+ }
132
+ }
133
+ if (this.changesInstance) {
134
+ this.changesInstance.setGalaxyAndBranch(route.galaxy, route.branch || 'main', { preserveChanges: true });
135
+ }
136
+ if (this.codeEditorInstance) {
137
+ this.codeEditorInstance.setGalaxyAndBranch(route.galaxy, route.branch || 'main');
138
+ }
139
+ if (this.timelineInstance) {
140
+ this.timelineInstance.setBranch(route.branch || 'main');
141
+ }
142
+ },
143
+ loadRouteForCenterPanel: async (route, context) => {
144
+ this.switchToReposMode({ skipNavigate: true });
145
+ await this.showCode(route.filePath, route.branch, route.galaxy);
146
+ },
147
+ loadRouteForRightPanel: async (route, context) => {
148
+ if (route.query?.inspect) {
149
+ this.expandRightPanel();
150
+ }
151
+ },
152
+ };
153
+
154
+ /**
155
+ * Default convention panel loaders for Linear Galaxies (Class IV Discourse / Notes).
156
+ */
157
+ protected linearPanelLoaders: PanelLoaderMap = {
158
+ loadRouteForLeftPanel: async (route, context) => {
159
+ this.expandActivityBar();
160
+ this.expandLeftPanel();
161
+ this.setActiveActivityBarButton('space', route.galaxy);
162
+ this.showLeftPanelView('text', route.galaxy);
163
+ },
164
+ loadRouteForCenterPanel: async (route, context) => {
165
+ this.switchToNotesMode(route.topicTjp || route.galaxy, { skipNavigate: true, galaxyId: route.galaxy });
166
+ await this.showNotes(route.topicTjp || route.galaxy, {
167
+ trail: route.trail,
168
+ trailMode: route.trailMode,
169
+ query: route.query,
170
+ });
171
+ },
172
+ loadRouteForRightPanel: async (route, context) => {
173
+ if (route.query?.inspect) {
174
+ this.expandRightPanel();
175
+ }
176
+ },
177
+ };
178
+
179
+ /**
180
+ * Registry of convention panel loaders keyed by topology or discriminator.
181
+ */
182
+ protected panelLoaderRegistry: Record<string, PanelLoaderMap> = {
183
+ branching: this.branchingPanelLoaders,
184
+ linear: this.linearPanelLoaders,
185
+ };
186
+
187
+ public registerPanelLoaders(key: string, loaders: PanelLoaderMap): void {
188
+ this.panelLoaderRegistry[key] = loaders;
189
+ }
190
+
191
+ public async loadRouteForPanels(route: GalaxyRouteInfo, context: PanelLoadContext): Promise<void> {
192
+ const loaders =
193
+ (context.discriminator ? this.panelLoaderRegistry[context.discriminator] : undefined) ||
194
+ this.panelLoaderRegistry[context.policy.topology] ||
195
+ this.branchingPanelLoaders;
196
+
197
+ await loaders.loadRouteForLeftPanel(route, context);
198
+ await loaders.loadRouteForCenterPanel(route, context);
199
+ await loaders.loadRouteForRightPanel(route, context);
200
+ }
201
+
82
202
  public initialized: Promise<void>;
83
203
  private isApplyingRoute: boolean = false;
84
204
  private cosmosNavigatorInstance?: CosmosNavigatorComponentInstance;
85
205
  private fileExplorerInstance?: FileExplorerComponentInstance;
206
+ private changesInstance?: ChangesComponentInstance;
86
207
  private timelineInstance?: TimelineComponentInstance;
87
208
  private timelineItemDetailsInstance?: TimelineItemDetailsComponentInstance;
88
209
  private chatViewInstance?: ChatViewComponentInstance;
@@ -100,6 +221,8 @@ export class SpaceGibShellService {
100
221
  private isResizingLeft = false;
101
222
  private isResizingRight = false;
102
223
  private isResizingFooter = false;
224
+ private isResizingExplorerChanges = false;
225
+ private isResizingChangesTimeline = false;
103
226
 
104
227
  // Playground Banner State
105
228
  private hasBigBangExploded = false;
@@ -114,6 +237,7 @@ export class SpaceGibShellService {
114
237
  await this.registerComponents();
115
238
  await this.initCosmosNavigator();
116
239
  await this.initFileExplorer();
240
+ await this.initChanges();
117
241
  await this.initTimeline();
118
242
  await this.initChatView();
119
243
  await this.initCodeEditor();
@@ -175,6 +299,44 @@ export class SpaceGibShellService {
175
299
  footerResizer.addEventListener('mousedown', () => this.isResizingFooter = true);
176
300
  }
177
301
 
302
+ // Accordion Horizontal Resizers (Left Panel)
303
+ const resizerExplorerChanges = document.getElementById('resizer-explorer-changes');
304
+ if (resizerExplorerChanges) {
305
+ resizerExplorerChanges.addEventListener('mousedown', (e) => {
306
+ e.preventDefault();
307
+ this.isResizingExplorerChanges = true;
308
+ resizerExplorerChanges.classList.add('active');
309
+ });
310
+ resizerExplorerChanges.addEventListener('dblclick', () => {
311
+ const explorerEl = document.getElementById('code-file-explorer-container');
312
+ if (explorerEl) {
313
+ explorerEl.style.flex = '';
314
+ explorerEl.style.height = '';
315
+ }
316
+ });
317
+ }
318
+
319
+ const resizerChangesTimeline = document.getElementById('resizer-changes-timeline');
320
+ if (resizerChangesTimeline) {
321
+ resizerChangesTimeline.addEventListener('mousedown', (e) => {
322
+ e.preventDefault();
323
+ this.isResizingChangesTimeline = true;
324
+ resizerChangesTimeline.classList.add('active');
325
+ });
326
+ resizerChangesTimeline.addEventListener('dblclick', () => {
327
+ const changesEl = document.getElementById('code-changes-container');
328
+ const timelineEl = document.getElementById('code-timeline-container');
329
+ if (changesEl) {
330
+ changesEl.style.flex = '';
331
+ changesEl.style.height = '';
332
+ }
333
+ if (timelineEl) {
334
+ timelineEl.style.flex = '';
335
+ timelineEl.style.height = '';
336
+ }
337
+ });
338
+ }
339
+
178
340
  // Window mouse events for dragging
179
341
  window.addEventListener('mousemove', (e) => this.handleMouseMove(e));
180
342
  window.addEventListener('mouseup', () => this.handleMouseUp());
@@ -437,7 +599,7 @@ export class SpaceGibShellService {
437
599
  }
438
600
 
439
601
  private handleMouseMove(e: MouseEvent) {
440
- if (!this.isResizingLeft && !this.isResizingRight && !this.isResizingFooter) return;
602
+ if (!this.isResizingLeft && !this.isResizingRight && !this.isResizingFooter && !this.isResizingExplorerChanges && !this.isResizingChangesTimeline) return;
441
603
 
442
604
  // Prevent text selection while dragging
443
605
  e.preventDefault();
@@ -459,12 +621,40 @@ export class SpaceGibShellService {
459
621
  let newHeight = Math.max(24, Math.min(window.innerHeight - e.clientY, 400));
460
622
  document.documentElement.style.setProperty('--footer-panel-height', `${newHeight}px`);
461
623
  }
624
+
625
+ if (this.isResizingExplorerChanges) {
626
+ const explorerEl = document.getElementById('code-file-explorer-container');
627
+ if (explorerEl) {
628
+ const rect = explorerEl.getBoundingClientRect();
629
+ const parentHeight = explorerEl.parentElement?.clientHeight || 600;
630
+ const maxExplorerHeight = parentHeight - 75;
631
+ const newHeight = Math.max(76, Math.min(e.clientY - rect.top, maxExplorerHeight));
632
+ explorerEl.style.flex = 'none';
633
+ explorerEl.style.height = `${newHeight}px`;
634
+ }
635
+ }
636
+
637
+ if (this.isResizingChangesTimeline) {
638
+ const changesEl = document.getElementById('code-changes-container');
639
+ if (changesEl) {
640
+ const rect = changesEl.getBoundingClientRect();
641
+ const parentHeight = changesEl.parentElement?.clientHeight || 600;
642
+ const maxChangesHeight = parentHeight - (changesEl.offsetTop || 0) - 35;
643
+ const newHeight = Math.max(35, Math.min(e.clientY - rect.top, maxChangesHeight));
644
+ changesEl.style.flex = 'none';
645
+ changesEl.style.height = `${newHeight}px`;
646
+ }
647
+ }
462
648
  }
463
649
 
464
650
  private handleMouseUp() {
465
651
  this.isResizingLeft = false;
466
652
  this.isResizingRight = false;
467
653
  this.isResizingFooter = false;
654
+ this.isResizingExplorerChanges = false;
655
+ this.isResizingChangesTimeline = false;
656
+ document.getElementById('resizer-explorer-changes')?.classList.remove('active');
657
+ document.getElementById('resizer-changes-timeline')?.classList.remove('active');
468
658
  }
469
659
 
470
660
  private updateCssVariables() {
@@ -479,6 +669,7 @@ export class SpaceGibShellService {
479
669
  await registerAdminReviewComponent();
480
670
  await registerCosmosNavigatorComponent();
481
671
  await registerFileExplorerComponent();
672
+ await registerChangesComponent();
482
673
  await registerTimelineComponent();
483
674
  await registerTimelineItemDetailsComponent();
484
675
  await registerChatViewComponent();
@@ -560,6 +751,27 @@ export class SpaceGibShellService {
560
751
  }
561
752
  }
562
753
 
754
+ public show404(message: string): void {
755
+ const centerPanel = document.getElementById(ID_CENTER_PANEL_CONTENT);
756
+ if (centerPanel) {
757
+ centerPanel.innerHTML = `
758
+ <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;">
759
+ <div style="font-size: 3.5rem; margin-bottom: 1rem;">🌌 404</div>
760
+ <h2 style="font-size: 1.5rem; color: #fff; margin-bottom: 0.5rem; font-weight: 600;">Cosmos Not Found</h2>
761
+ <p style="margin-bottom: 1.5rem; max-width: 420px; font-size: 0.95rem; line-height: 1.5;">${message}</p>
762
+ <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;">
763
+ Return to Polycosmos
764
+ </button>
765
+ </div>
766
+ `;
767
+ const returnBtn = document.getElementById('btn-404-return');
768
+ returnBtn?.addEventListener('click', () => {
769
+ getSpaceGibRouterSvc().navigate({ kind: 'polycosmos' });
770
+ });
771
+ }
772
+ this.collapsePanels();
773
+ }
774
+
563
775
  private async initFileExplorer(): Promise<void> {
564
776
  const lc = `${this.lc}[${this.initFileExplorer.name}]`;
565
777
  try {
@@ -594,49 +806,91 @@ export class SpaceGibShellService {
594
806
 
595
807
  private bindFileExplorerEvents(): void {
596
808
  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';
809
+ const { branch, path, filename, galaxy } = e.detail || {};
810
+ console.log(`${this.lc} File selected: ${path || filename} on worktree branch ${branch} in galaxy ${galaxy}`);
811
+ const fullFile = path || filename;
812
+ if (!fullFile) {
813
+ return;
814
+ }
600
815
  const branchName = branch || 'main';
601
- const tipAddr = '7f8a1b2';
816
+ const galaxyName = galaxy || 'code-1';
817
+ const tipAddr = 'tip';
818
+
602
819
  this.chatViewInstance?.setTargetFile(fullFile, `${branchName} @ ${tipAddr}`);
603
820
  this.nestedChatInstance?.activateIbGib({ addr: `file ${fullFile}^${tipAddr}` });
604
821
 
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
- }
822
+ this.showCode(fullFile, branchName, galaxyName);
613
823
 
614
- this.showCode(fullFile, branchName).then(() => {
615
- this.codeEditorInstance?.loadFile({
616
- filepath: fullFile,
824
+ const activeCosmos = this.cosmosNavigatorInstance?.getActiveCosmos();
825
+ if (activeCosmos) {
826
+ const tenant = this.getActiveTenant();
827
+ getSpaceGibRouterSvc().navigate({
828
+ kind: 'galaxy',
829
+ cosmos: activeCosmos.id || activeCosmos.name,
830
+ galaxy: galaxyName,
831
+ topology: 'branching',
832
+ discriminator: 'code',
617
833
  branch: branchName,
618
- tipAddr,
619
- content: mockContent,
834
+ filePath: fullFile,
835
+ ...(tenant ? { tenant } : {}),
620
836
  });
621
- });
837
+ }
838
+ });
622
839
 
623
- // Reveal discussions in right panel
624
- this.expandRightPanel();
840
+ window.addEventListener('space-gib-branch-switched', async (e: any) => {
841
+ const { galaxy, branch } = e.detail || {};
842
+ const targetGalaxy = galaxy || this.fileExplorerInstance?.getGalaxyName() || 'code-1';
843
+ const targetBranch = branch || 'main';
844
+
845
+ console.log(`${this.lc} Branch switched: [${targetGalaxy}] -> [${targetBranch}]`);
846
+
847
+ // 1. Update File Explorer, Changes, Timeline, Code Editor
848
+ this.fileExplorerInstance?.setGalaxyAndBranch(targetGalaxy, targetBranch);
849
+ this.changesInstance?.setGalaxyAndBranch(targetGalaxy, targetBranch, { preserveChanges: true });
850
+ this.codeEditorInstance?.setGalaxyAndBranch(targetGalaxy, targetBranch);
851
+ this.timelineInstance?.setBranch(targetBranch);
852
+
853
+ // 2. Refresh active file under new branch context
854
+ const activePath = this.fileExplorerInstance?.getSelectedFilePath() || this.codeEditorInstance?.getFilepath();
855
+ if (activePath) {
856
+ await this.showCode(activePath, targetBranch, targetGalaxy);
857
+ } else {
858
+ await this.showCode(undefined, targetBranch, targetGalaxy);
859
+ }
625
860
 
861
+ // 3. Update router navigation
626
862
  const activeCosmos = this.cosmosNavigatorInstance?.getActiveCosmos();
627
863
  if (activeCosmos) {
628
- const tenant = this.getActiveTenant();
864
+ const tenant = this.getActiveTenant(activeCosmos);
629
865
  getSpaceGibRouterSvc().navigate({
630
- kind: 'code-galaxy',
866
+ kind: 'galaxy',
631
867
  cosmos: activeCosmos.id || activeCosmos.name,
632
- galaxyId: 'code-1',
633
- branch: branchName,
634
- filePath: fullFile,
868
+ galaxy: targetGalaxy,
869
+ topology: 'branching',
870
+ discriminator: 'code',
871
+ branch: targetBranch,
872
+ ...(activePath ? { filePath: activePath } : {}),
635
873
  ...(tenant ? { tenant } : {}),
636
874
  });
637
875
  }
638
876
  });
639
877
 
878
+ window.addEventListener('space-gib-view-mode-changed', (e: any) => {
879
+ const { mode } = e.detail || {};
880
+ if (mode) {
881
+ this.changesInstance?.setViewMode(mode);
882
+ }
883
+ });
884
+
885
+ window.addEventListener('space-gib-accordion-toggled', () => {
886
+ const explorerEl = document.getElementById('code-file-explorer-container');
887
+ const changesEl = document.getElementById('code-changes-container');
888
+ const timelineEl = document.getElementById('code-timeline-container');
889
+ if (explorerEl) { explorerEl.style.flex = ''; explorerEl.style.height = ''; }
890
+ if (changesEl) { changesEl.style.flex = ''; changesEl.style.height = ''; }
891
+ if (timelineEl) { timelineEl.style.flex = ''; timelineEl.style.height = ''; }
892
+ });
893
+
640
894
  window.addEventListener('space-gib-new-file-requested', (e: any) => {
641
895
  const { branch } = e.detail || {};
642
896
  const filename = prompt(`Create new file on branch [${branch || 'main'}]:`, 'untitled.ts');
@@ -654,6 +908,117 @@ export class SpaceGibShellService {
654
908
  });
655
909
  }
656
910
 
911
+ private async initChanges(): Promise<void> {
912
+ const lc = `${this.lc}[${this.initChanges.name}]`;
913
+ try {
914
+ const container = document.getElementById('code-changes-container');
915
+ if (!container) {
916
+ console.warn(`${lc} code-changes-container element not found.`);
917
+ return;
918
+ }
919
+
920
+ const componentSvc = await getComponentSvc();
921
+ this.changesInstance = await componentSvc.getComponentInstance({
922
+ path: CHANGES_COMPONENT_NAME,
923
+ ibGibAddr: ROOT_ADDR,
924
+ useRegExpPrefilter: true,
925
+ }) as ChangesComponentInstance;
926
+
927
+ if (!this.changesInstance) {
928
+ throw new Error(`Failed to instantiate ${CHANGES_COMPONENT_NAME}`);
929
+ }
930
+
931
+ await componentSvc.inject({
932
+ parentEl: container,
933
+ componentToInject: this.changesInstance,
934
+ });
935
+
936
+ if (this.fileExplorerInstance) {
937
+ this.changesInstance.setViewMode(this.fileExplorerInstance.getViewMode());
938
+ }
939
+
940
+ this.bindChangesEvents();
941
+
942
+ } catch (error) {
943
+ console.error(`${lc} ${extractErrorMsg(error)}`);
944
+ }
945
+ }
946
+
947
+ private bindChangesEvents(): void {
948
+ window.addEventListener('space-gib-commit-created', (e: any) => {
949
+ const { branch, message, tipAddr, shortHash } = e.detail || {};
950
+ console.log(`${this.lc} Commit created: [${shortHash}] "${message}" on branch [${branch}]`);
951
+
952
+ // 1. Clear status badges in file explorer for this branch
953
+ this.fileExplorerInstance?.clearAllStatusBadges(branch);
954
+ this.fileExplorerInstance?.updateDirtyBadges();
955
+
956
+ // 2. Add snapshot to timeline
957
+ this.timelineInstance?.addSnapshot({
958
+ hash: tipAddr,
959
+ shortHash: shortHash || (tipAddr ? tipAddr.slice(-8) : 'unknown'),
960
+ message: message || 'Update',
961
+ author: 'You',
962
+ timestamp: 'Just now',
963
+ branch: branch || 'main',
964
+ isTip: true,
965
+ });
966
+
967
+ // 3. Update breadcrumb tip and mark committed in code editor
968
+ if (this.codeEditorInstance) {
969
+ if (shortHash) {
970
+ this.codeEditorInstance.setTipAddr(shortHash);
971
+ }
972
+ const activePath = this.codeEditorInstance.getFilepath();
973
+ if (activePath) {
974
+ this.codeEditorInstance.markAsCommitted(shortHash || tipAddr);
975
+ }
976
+ }
977
+ });
978
+
979
+ window.addEventListener('space-gib-file-created', (e: any) => {
980
+ const { path, content, galaxy, branch } = e.detail || {};
981
+ if (galaxy || branch) {
982
+ const g = galaxy || this.changesInstance?.getGalaxyName() || 'code-1';
983
+ const b = branch || this.changesInstance?.getBranchName() || 'main';
984
+ this.changesInstance?.setGalaxyAndBranch(g, b, { preserveChanges: true });
985
+ this.codeEditorInstance?.setGalaxyAndBranch(g, b);
986
+ }
987
+ if (path) {
988
+ this.changesInstance?.recordChange({
989
+ path,
990
+ status: 'untracked',
991
+ content: content ?? '',
992
+ });
993
+ this.fileExplorerInstance?.updateDirtyBadges();
994
+ }
995
+ });
996
+
997
+ window.addEventListener('space-gib-change-discarded', (e: any) => {
998
+ const { path, originalContent, branch, galaxy } = e.detail || {};
999
+ if (path) {
1000
+ const b = branch || this.fileExplorerInstance?.getBranchName() || 'main';
1001
+ const g = galaxy || this.fileExplorerInstance?.getGalaxyName() || 'code-1';
1002
+ if (this.fileExplorerInstance?.isUntracked(path, b, g)) {
1003
+ this.fileExplorerInstance.removeFile(path, b, g);
1004
+ if (this.codeEditorInstance && this.codeEditorInstance.getFilepath() === path) {
1005
+ this.codeEditorInstance.clearFile({ repo: g, branch: b });
1006
+ }
1007
+ } else {
1008
+ this.fileExplorerInstance?.setFileStatus(path, undefined, b, g);
1009
+ if (this.codeEditorInstance && this.codeEditorInstance.getFilepath() === path) {
1010
+ if (originalContent !== undefined) {
1011
+ this.codeEditorInstance.setContent(originalContent);
1012
+ } else {
1013
+ this.showCode(path, b, g);
1014
+ }
1015
+ }
1016
+ }
1017
+ this.fileExplorerInstance?.updateDirtyBadges();
1018
+ }
1019
+ });
1020
+ }
1021
+
657
1022
  private async initTimeline(): Promise<void> {
658
1023
  const lc = `${this.lc}[${this.initTimeline.name}]`;
659
1024
  try {
@@ -781,7 +1146,7 @@ export class SpaceGibShellService {
781
1146
  }
782
1147
  }
783
1148
 
784
- public async showCode(filePath?: string, branch?: string): Promise<void> {
1149
+ public async showCode(filePath?: string, branch?: string, galaxy?: string): Promise<void> {
785
1150
  const lc = `${this.lc}[${this.showCode.name}]`;
786
1151
  try {
787
1152
  const centerPanel = document.getElementById(ID_CENTER_PANEL_CONTENT);
@@ -790,6 +1155,10 @@ export class SpaceGibShellService {
790
1155
  return;
791
1156
  }
792
1157
 
1158
+ const currentRoute = getSpaceGibRouterSvc().getCurrentRoute();
1159
+ const targetGalaxy = galaxy || (currentRoute && 'galaxy' in currentRoute ? (currentRoute as any).galaxy : undefined) || this.fileExplorerInstance?.getGalaxyName() || this.changesInstance?.getGalaxyName() || 'code-1';
1160
+ const targetBranch = branch || (currentRoute && 'branch' in currentRoute ? (currentRoute as any).branch : undefined) || this.fileExplorerInstance?.getBranchName() || this.changesInstance?.getBranchName() || 'main';
1161
+
793
1162
  if (!this.codeEditorInstance) {
794
1163
  const componentSvc = await getComponentSvc();
795
1164
  this.codeEditorInstance = await componentSvc.getComponentInstance({
@@ -806,25 +1175,81 @@ export class SpaceGibShellService {
806
1175
  componentToInject: this.codeEditorInstance,
807
1176
  });
808
1177
 
1178
+ this.codeEditorInstance.setGalaxyAndBranch(targetGalaxy, targetBranch);
1179
+
809
1180
  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`;
1181
+ let casContent: string | undefined = undefined;
1182
+ let tipAddr: string = 'tip';
1183
+
1184
+ try {
1185
+ const { metaspace, space } = await getPlaygroundMetaspaceAndSpace();
1186
+ const router = getSpaceGibRouterSvc();
1187
+ let galaxyIbGib: B2tFSGalaxyIbGib_V1 | undefined;
1188
+ for (const manifest of router.cosmosManifestCache?.values() || []) {
1189
+ const entry = manifest.data?.galaxies?.[targetGalaxy];
1190
+ if (entry?.galaxyAddr) {
1191
+ galaxyIbGib = await getIbGibFromSpace<B2tFSGalaxyIbGib_V1>({ addr: entry.galaxyAddr, space });
1192
+ if (galaxyIbGib) break;
1193
+ }
1194
+ }
1195
+ if (galaxyIbGib) {
1196
+ const branchAndRoot = await getBranchAndRootItemInfosAndIbGibsAndSpaceFromGalaxyIbGib({
1197
+ galaxyIbGib,
1198
+ branchName: targetBranch,
1199
+ metaspace,
1200
+ });
1201
+ tipAddr = branchAndRoot.branchIbGib.gib || 'tip';
1202
+ const rootItem = branchAndRoot.rootItemIbGib_latest || branchAndRoot.rootItemIbGib;
1203
+ const fileItem = await getFileItemFromPath({
1204
+ branchSpace: branchAndRoot.branchSpace,
1205
+ rootItem,
1206
+ filePath,
1207
+ });
1208
+ if (fileItem) {
1209
+ casContent = await getFileContent({ branchSpace: branchAndRoot.branchSpace, fileItem });
1210
+ }
1211
+ }
1212
+ } catch (readErr) {
1213
+ console.warn(`${lc} Error reading file [${filePath}] from branch space: ${extractErrorMsg(readErr)}`);
817
1214
  }
818
1215
 
1216
+ const workingChange = this.changesInstance?.getChange(filePath, targetBranch);
1217
+ const isUntrackedInExplorer = this.fileExplorerInstance?.isUntracked(filePath, targetBranch, targetGalaxy);
1218
+ const fileExistsInCas = casContent !== undefined;
1219
+ const hasWorkingTreeChange = workingChange !== undefined;
1220
+
1221
+ // If file doesn't exist in CAS, and is neither in working tree nor marked untracked in explorer, clear editor
1222
+ if (!fileExistsInCas && !hasWorkingTreeChange && !isUntrackedInExplorer) {
1223
+ this.codeEditorInstance.clearFile({
1224
+ repo: targetGalaxy,
1225
+ branch: targetBranch,
1226
+ });
1227
+ this.fileExplorerInstance?.selectFile('');
1228
+ return;
1229
+ }
1230
+
1231
+ const isUntracked = !fileExistsInCas;
1232
+ const content = workingChange?.content ?? casContent ?? '';
1233
+
819
1234
  this.codeEditorInstance.loadFile({
820
1235
  filepath: filePath,
821
- branch: branch || 'main',
822
- tipAddr: '7f8a1b2',
1236
+ repo: targetGalaxy,
1237
+ branch: targetBranch,
1238
+ tipAddr,
823
1239
  content,
1240
+ originalContent: casContent,
1241
+ isUntracked,
1242
+ });
1243
+ } else {
1244
+ this.codeEditorInstance.clearFile({
1245
+ repo: targetGalaxy,
1246
+ branch: targetBranch,
824
1247
  });
825
1248
  }
826
1249
  }
827
1250
 
1251
+ this.changesInstance?.setGalaxyAndBranch(targetGalaxy, targetBranch, { preserveChanges: true });
1252
+
828
1253
  const codeChatContainer = document.getElementById('code-chat-view-container');
829
1254
  const notesInspectorContainer = document.getElementById('notes-inspector-container');
830
1255
  if (notesInspectorContainer) { notesInspectorContainer.style.display = 'none'; }
@@ -1004,8 +1429,25 @@ export class SpaceGibShellService {
1004
1429
 
1005
1430
  private bindCodeEditorEvents(): void {
1006
1431
  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)`);
1432
+ const { filepath, content, lineCount, isReverted, isUntracked: isUntrackedProp, branch, repo } = e.detail || {};
1433
+ const targetBranch = branch || this.changesInstance?.getBranchName() || 'main';
1434
+ const targetGalaxy = repo || this.changesInstance?.getGalaxyName() || 'code-1';
1435
+ console.log(`${this.lc} Code changed in ${filepath} on branch [${targetBranch}] (${lineCount} lines, isReverted=${isReverted})`);
1436
+ if (filepath) {
1437
+ if (isReverted) {
1438
+ this.changesInstance?.removeChange(filepath, targetBranch);
1439
+ this.fileExplorerInstance?.setFileStatus(filepath, undefined, targetBranch, targetGalaxy);
1440
+ } else {
1441
+ const isUntracked = Boolean(isUntrackedProp || this.changesInstance?.isUntracked(filepath, targetBranch) || this.fileExplorerInstance?.isUntracked(filepath, targetBranch, targetGalaxy));
1442
+ const status = isUntracked ? 'untracked' : 'modified';
1443
+ this.changesInstance?.recordChange({
1444
+ path: filepath,
1445
+ status,
1446
+ content: content ?? '',
1447
+ }, targetBranch);
1448
+ this.fileExplorerInstance?.setFileStatus(filepath, status, targetBranch, targetGalaxy);
1449
+ }
1450
+ }
1009
1451
  });
1010
1452
 
1011
1453
  window.addEventListener('space-gib-code-copied', (e: any) => {
@@ -1165,8 +1607,7 @@ export class SpaceGibShellService {
1165
1607
  if (activitySpaces) {
1166
1608
  let matched = false;
1167
1609
  activitySpaces.querySelectorAll('.activity-bar-btn').forEach(b => {
1168
- const isMatch = b.getAttribute('data-space-id') === spaceId ||
1169
- b.getAttribute('data-space-type') === spaceId;
1610
+ const isMatch = b.getAttribute('data-space-id') === spaceId;
1170
1611
  b.classList.toggle('active', isMatch);
1171
1612
  if (isMatch) matched = true;
1172
1613
  });
@@ -1269,10 +1710,33 @@ export class SpaceGibShellService {
1269
1710
  }
1270
1711
  if (space.type === 'code') {
1271
1712
  this.showLeftPanelView('code', space.name);
1272
- this.switchToReposMode();
1713
+ this.switchToReposMode({ skipNavigate: true, galaxyId: space.name });
1714
+ if (!this.isApplyingRoute && cosmos?.name) {
1715
+ const tenant = this.getActiveTenant(cosmos.name);
1716
+ getSpaceGibRouterSvc().navigate({
1717
+ kind: 'galaxy',
1718
+ ...(tenant ? { tenant } : {}),
1719
+ cosmos: cosmos.name,
1720
+ galaxy: space.name,
1721
+ topology: 'branching',
1722
+ discriminator: 'code',
1723
+ branch: 'main',
1724
+ });
1725
+ }
1273
1726
  } else if (space.type === 'text') {
1274
1727
  this.showLeftPanelView('text', space.name);
1275
- this.switchToNotesMode(space.name);
1728
+ this.switchToNotesMode(space.name, { skipNavigate: true, galaxyId: space.name });
1729
+ if (!this.isApplyingRoute && cosmos?.name) {
1730
+ const tenant = this.getActiveTenant(cosmos.name);
1731
+ getSpaceGibRouterSvc().navigate({
1732
+ kind: 'galaxy',
1733
+ ...(tenant ? { tenant } : {}),
1734
+ cosmos: cosmos.name,
1735
+ galaxy: space.name,
1736
+ topology: 'linear',
1737
+ discriminator: 'notes',
1738
+ });
1739
+ }
1276
1740
  }
1277
1741
  }
1278
1742
  });
@@ -1315,12 +1779,100 @@ export class SpaceGibShellService {
1315
1779
  this.openCloneModal(e.detail);
1316
1780
  });
1317
1781
 
1318
- window.addEventListener('space-gib-branch-requested', (e: any) => {
1782
+ window.addEventListener('space-gib-branch-requested', async (e: any) => {
1319
1783
  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);
1784
+ const cleanGalaxy = galaxyName || 'code-1';
1785
+ const cleanSourceBranch = sourceBranch || 'main';
1786
+ const cleanNewBranch = newBranchName?.trim();
1787
+ if (!cleanNewBranch) return;
1788
+
1789
+ const validation = validateBranchName(cleanNewBranch);
1790
+ if (!validation.isValid) {
1791
+ alert(`Invalid branch name: ${validation.error}`);
1792
+ return;
1793
+ }
1794
+
1795
+ const lc = `${this.lc}[space-gib-branch-requested]`;
1796
+ try {
1797
+ const { metaspace, space } = await getPlaygroundMetaspaceAndSpace();
1798
+ const router = getSpaceGibRouterSvc();
1799
+
1800
+ // 1. Resolve GalaxyIbGib
1801
+ let galaxyIbGib: B2tFSGalaxyIbGib_V1 | undefined;
1802
+ for (const manifest of router.cosmosManifestCache?.values() || []) {
1803
+ const entry = manifest.data?.galaxies?.[cleanGalaxy];
1804
+ if (entry?.galaxyAddr) {
1805
+ galaxyIbGib = await getIbGibFromSpace<B2tFSGalaxyIbGib_V1>({ addr: entry.galaxyAddr, space });
1806
+ if (galaxyIbGib) break;
1807
+ }
1808
+ }
1809
+
1810
+ if (!galaxyIbGib) {
1811
+ throw new Error(`Galaxy [${cleanGalaxy}] not found in cosmos manifest.`);
1812
+ }
1813
+
1814
+ // 2. Resolve source branch space
1815
+ const branchAndRoot = await getBranchAndRootItemInfosAndIbGibsAndSpaceFromGalaxyIbGib({
1816
+ galaxyIbGib,
1817
+ branchName: cleanSourceBranch,
1818
+ metaspace,
1819
+ });
1820
+
1821
+ const activeBranchSpace = branchAndRoot.branchSpace;
1822
+
1823
+ // 3. Execute isomorphic VCS branch creation
1824
+ const branchRes = await executeVcsBranch({
1825
+ metaspace,
1826
+ localSpace: space,
1827
+ sourceBranchSpace: activeBranchSpace,
1828
+ galaxyIbGib,
1829
+ branchName: cleanNewBranch,
1830
+ sourceBranchName: cleanSourceBranch,
1831
+ itemPath: scope === 'item' ? itemPath : undefined,
1832
+ });
1833
+
1834
+ const finalGalaxy = branchRes.updatedGalaxy || galaxyIbGib;
1835
+ const finalGalaxyAddr = getIbGibAddr({ ibGib: finalGalaxy });
1836
+
1837
+ // 4. Update router cosmos manifest cache with updated galaxy address
1838
+ for (const manifest of router.cosmosManifestCache?.values() || []) {
1839
+ if (manifest.data?.galaxies?.[cleanGalaxy]) {
1840
+ manifest.data.galaxies[cleanGalaxy].galaxyAddr = finalGalaxyAddr;
1841
+ break;
1842
+ }
1843
+ }
1844
+
1845
+ // 5. Broadcast new galaxy IbGib to metaspace subscribers
1846
+ await (metaspace as any).registerNewIbGib?.({ ibGib: finalGalaxy, space });
1847
+
1848
+ // 6. Update Changes, File Explorer & Code Editor
1849
+ this.changesInstance?.setGalaxyAndBranch(cleanGalaxy, cleanNewBranch);
1850
+ this.fileExplorerInstance?.setGalaxyAndBranch(cleanGalaxy, cleanNewBranch);
1851
+ this.codeEditorInstance?.setGalaxyAndBranch(cleanGalaxy, cleanNewBranch);
1852
+ this.timelineInstance?.setBranch(cleanNewBranch);
1853
+
1854
+ // 8. Navigate router to new branch
1855
+ const activeCosmos = this.cosmosNavigatorInstance?.getActiveCosmos();
1856
+ const activePath = this.codeEditorInstance?.getFilepath();
1857
+ if (activeCosmos) {
1858
+ const tenant = this.getActiveTenant(activeCosmos);
1859
+ router.navigate({
1860
+ kind: 'galaxy',
1861
+ cosmos: activeCosmos.id || activeCosmos.name,
1862
+ galaxy: cleanGalaxy,
1863
+ topology: 'branching',
1864
+ discriminator: 'code',
1865
+ branch: cleanNewBranch,
1866
+ ...(activePath ? { filePath: activePath } : {}),
1867
+ ...(tenant ? { tenant } : {}),
1868
+ });
1869
+ }
1870
+
1871
+ console.log(`${lc} Successfully diverged to new branch [${cleanNewBranch}] from ${scope === 'item' ? `item [${itemPath}]` : `worktree [${cleanSourceBranch}]`} in galaxy [${cleanGalaxy}]`);
1872
+ } catch (err) {
1873
+ console.error(`${lc} Failed to branch: ${extractErrorMsg(err)}`);
1874
+ alert(`Failed to create branch: ${extractErrorMsg(err)}`);
1875
+ }
1324
1876
  });
1325
1877
 
1326
1878
  btnFooterAgent?.addEventListener('click', () => {
@@ -1413,9 +1965,9 @@ export class SpaceGibShellService {
1413
1965
 
1414
1966
  btnSideNewCosmos?.addEventListener('click', () => {
1415
1967
  this.switchToSpacesMode();
1416
- this.cosmosNavigatorInstance?.triggerBigBangSequence(() => {
1968
+ this.cosmosNavigatorInstance?.triggerBigBangSequence(async () => {
1417
1969
  const nextIdx = (this.cosmosNavigatorInstance?.getCosmoses().length || 0) + 1;
1418
- const newCosmos = this.cosmosNavigatorInstance?.createCosmos(`Cosmos ${nextIdx}`);
1970
+ const newCosmos = await this.cosmosNavigatorInstance?.createCosmos(`cosmos-${nextIdx}`);
1419
1971
  if (newCosmos) {
1420
1972
  this.cosmosNavigatorInstance?.selectCosmos(newCosmos.id);
1421
1973
  }
@@ -1461,13 +2013,14 @@ export class SpaceGibShellService {
1461
2013
  spacesList.querySelectorAll('.cosmos-space-item').forEach(el => {
1462
2014
  el.addEventListener('click', () => {
1463
2015
  const type = el.getAttribute('data-space-type');
2016
+ const spaceId = el.getAttribute('data-space-id') || '';
1464
2017
  const name = el.querySelector('.space-item-name')?.textContent || '';
1465
2018
  if (type === 'code') {
1466
2019
  this.showLeftPanelView('code', name);
1467
- this.switchToReposMode();
2020
+ this.switchToReposMode({ galaxyId: spaceId });
1468
2021
  } else {
1469
2022
  this.showLeftPanelView('text', name);
1470
- this.switchToNotesMode(name);
2023
+ this.switchToNotesMode(name, { galaxyId: spaceId });
1471
2024
  }
1472
2025
  });
1473
2026
  });
@@ -1492,10 +2045,10 @@ export class SpaceGibShellService {
1492
2045
 
1493
2046
  if (type === 'code') {
1494
2047
  this.showLeftPanelView('code', title);
1495
- this.switchToReposMode();
2048
+ this.switchToReposMode({ galaxyId: spaceId });
1496
2049
  } else {
1497
2050
  this.showLeftPanelView('text', title);
1498
- this.switchToNotesMode(title);
2051
+ this.switchToNotesMode(title, { galaxyId: spaceId });
1499
2052
  }
1500
2053
  });
1501
2054
  });
@@ -1603,12 +2156,12 @@ export class SpaceGibShellService {
1603
2156
  });
1604
2157
 
1605
2158
  const btnNew = menu.querySelector('#btn-header-menu-new-cosmos');
1606
- btnNew?.addEventListener('click', () => {
2159
+ btnNew?.addEventListener('click', async () => {
1607
2160
  menu.classList.add('hidden');
1608
2161
  this.switchToSpacesMode();
1609
2162
  this.cosmosNavigatorInstance?.triggerBigBangExplosion();
1610
2163
  const nextIdx = cosmoses.length + 1;
1611
- const newC = this.cosmosNavigatorInstance?.createCosmos(`Cosmos ${nextIdx}`);
2164
+ const newC = await this.cosmosNavigatorInstance?.createCosmos(`cosmos-${nextIdx}`);
1612
2165
  if (newC) {
1613
2166
  setTimeout(() => {
1614
2167
  this.cosmosNavigatorInstance?.selectCosmos(newC.id);
@@ -1623,17 +2176,25 @@ export class SpaceGibShellService {
1623
2176
  const viewCode = document.getElementById('left-panel-code-view');
1624
2177
  const viewText = document.getElementById('left-panel-text-view');
1625
2178
 
1626
- if (viewPoly) { viewPoly.style.display = viewName === 'polycosmos' ? 'block' : 'none'; }
1627
- if (viewCosmos) { viewCosmos.style.display = viewName === 'cosmos' ? 'block' : 'none'; }
2179
+ if (viewPoly) {
2180
+ viewPoly.style.display = viewName === 'polycosmos' ? 'flex' : 'none';
2181
+ viewPoly.style.flexDirection = 'column';
2182
+ }
2183
+ if (viewCosmos) {
2184
+ viewCosmos.style.display = viewName === 'cosmos' ? 'flex' : 'none';
2185
+ viewCosmos.style.flexDirection = 'column';
2186
+ }
1628
2187
  if (viewCode) {
1629
- viewCode.style.display = viewName === 'code' ? 'block' : 'none';
2188
+ viewCode.style.display = viewName === 'code' ? 'flex' : 'none';
2189
+ viewCode.style.flexDirection = 'column';
1630
2190
  if (title) {
1631
2191
  const titleEl = document.getElementById('left-code-space-title');
1632
2192
  if (titleEl) { titleEl.textContent = title; }
1633
2193
  }
1634
2194
  }
1635
2195
  if (viewText) {
1636
- viewText.style.display = viewName === 'text' ? 'block' : 'none';
2196
+ viewText.style.display = viewName === 'text' ? 'flex' : 'none';
2197
+ viewText.style.flexDirection = 'column';
1637
2198
  if (viewName === 'text') {
1638
2199
  this.initNotesExplorer(title);
1639
2200
  }
@@ -1668,7 +2229,7 @@ export class SpaceGibShellService {
1668
2229
  this.setMode('spaces');
1669
2230
  }
1670
2231
 
1671
- private switchToReposMode(opts?: { skipNavigate?: boolean }): void {
2232
+ private switchToReposMode(opts?: { skipNavigate?: boolean; galaxyId?: string }): void {
1672
2233
  this.setMode('repos');
1673
2234
  this.expandRightPanel();
1674
2235
  if (!opts?.skipNavigate && !this.isApplyingRoute) {
@@ -1676,9 +2237,11 @@ export class SpaceGibShellService {
1676
2237
  if (activeCosmos) {
1677
2238
  const tenant = this.getActiveTenant(activeCosmos);
1678
2239
  getSpaceGibRouterSvc().navigate({
1679
- kind: 'code-galaxy',
2240
+ kind: 'galaxy',
1680
2241
  cosmos: activeCosmos.id || activeCosmos.name,
1681
- galaxyId: 'code-1',
2242
+ galaxy: opts?.galaxyId || 'code-1',
2243
+ topology: 'branching',
2244
+ discriminator: 'code',
1682
2245
  branch: 'main',
1683
2246
  ...(tenant ? { tenant } : {}),
1684
2247
  });
@@ -1694,10 +2257,12 @@ export class SpaceGibShellService {
1694
2257
  if (activeCosmos) {
1695
2258
  const tenant = this.getActiveTenant(activeCosmos);
1696
2259
  getSpaceGibRouterSvc().navigate({
1697
- kind: 'notes-galaxy',
2260
+ kind: 'galaxy',
1698
2261
  cosmos: activeCosmos.id || activeCosmos.name,
1699
- galaxyId: opts?.galaxyId || 'notes-1',
1700
- ...(title ? { topicTjp: title } : {}),
2262
+ galaxy: opts?.galaxyId || 'notes-1',
2263
+ topology: 'linear',
2264
+ discriminator: 'notes',
2265
+ ...(title ? { topicTjp: title, trail: [title], trailMode: 'tjp' } : {}),
1701
2266
  ...(tenant ? { tenant } : {}),
1702
2267
  });
1703
2268
  }
@@ -1745,12 +2310,17 @@ export class SpaceGibShellService {
1745
2310
  try {
1746
2311
  if (route.kind !== 'polycosmos') {
1747
2312
  this.hasBigBangExploded = true;
2313
+ if (this.cosmosNavigatorInstance) {
2314
+ this.cosmosNavigatorInstance.hasBigBangExploded = true;
2315
+ }
1748
2316
  this.revealChrome();
1749
2317
  }
1750
2318
 
1751
2319
  // If the route omitted a tenant specifier but the user is authenticated (or in playground),
1752
2320
  // canonicalize the URL in the address bar so copying and sharing is always fully qualified.
1753
- if (route.kind !== 'admin-review' && !route.tenant) {
2321
+ // Exception: For bare landing ('polycosmos'), do not attach tenant or redirect to /@playground/polycosmos
2322
+ // so the URL remains bare (e.g. https://local.ibgib.space) to keep full focus on Big Bang.
2323
+ if (route.kind !== 'admin-review' && route.kind !== 'polycosmos' && !route.tenant) {
1754
2324
  const cosmosName = 'cosmos' in route ? route.cosmos : undefined;
1755
2325
  const activeTenant = this.getActiveTenant(cosmosName);
1756
2326
  if (activeTenant) {
@@ -1767,7 +2337,11 @@ export class SpaceGibShellService {
1767
2337
  }
1768
2338
  case 'cosmos': {
1769
2339
  if (this.cosmosNavigatorInstance) {
1770
- const cosmos = await this.cosmosNavigatorInstance.ensureCosmos(route.cosmos);
2340
+ const cosmos = await this.cosmosNavigatorInstance.findCosmos(route.cosmos);
2341
+ if (!cosmos) {
2342
+ this.show404(`Cosmos "${route.cosmos}" not found.`);
2343
+ return;
2344
+ }
1771
2345
  this.cosmosNavigatorInstance.selectCosmos(cosmos.id);
1772
2346
  this.updateActiveCosmosDisplay(cosmos);
1773
2347
  }
@@ -1776,60 +2350,46 @@ export class SpaceGibShellService {
1776
2350
  await this.showCosmos();
1777
2351
  break;
1778
2352
  }
1779
- case 'code-galaxy': {
2353
+ case 'galaxy': {
2354
+ let cosmosInfo: CosmosInfo | undefined;
1780
2355
  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
- });
2356
+ cosmosInfo = await this.cosmosNavigatorInstance.findCosmos(route.cosmos);
2357
+ if (!cosmosInfo) {
2358
+ this.show404(`Cosmos "${route.cosmos}" not found.`);
2359
+ return;
1790
2360
  }
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: '📝',
2361
+ const spaceType = route.topology === 'branching' ? 'code' : 'text';
2362
+ if (!cosmosInfo.spaces.some(s => s.id === route.galaxy)) {
2363
+ cosmosInfo.spaces.push({
2364
+ id: route.galaxy,
2365
+ type: spaceType,
2366
+ name: route.galaxy,
2367
+ shortLabel: route.galaxy,
2368
+ icon: route.topology === 'branching' ? '</>' : '📝',
1818
2369
  });
1819
2370
  }
1820
- this.cosmosNavigatorInstance.selectCosmos(cosmos.id);
1821
- this.updateActiveCosmosDisplay(cosmos, 'text');
2371
+ this.cosmosNavigatorInstance.selectCosmos(cosmosInfo.id);
2372
+ this.updateActiveCosmosDisplay(cosmosInfo, route.galaxy || spaceType);
1822
2373
  }
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
- });
2374
+
2375
+ const policy: GalaxyPolicy = {
2376
+ topology: route.topology,
2377
+ role: route.topology === 'branching' ? 'intrinsic' : 'extrinsic',
2378
+ storageMode: 'text',
2379
+ defaultBranch: 'main',
2380
+ };
2381
+
2382
+ const context: PanelLoadContext = {
2383
+ route,
2384
+ cosmosName: route.cosmos,
2385
+ galaxyName: route.galaxy,
2386
+ discriminator: route.discriminator,
2387
+ policy,
2388
+ cosmosInfo,
2389
+ shell: this,
2390
+ };
2391
+
2392
+ await this.loadRouteForPanels(route, context);
1833
2393
  break;
1834
2394
  }
1835
2395
  case 'raw-addr': {
@@ -1858,7 +2418,11 @@ export class SpaceGibShellService {
1858
2418
  try {
1859
2419
  console.log(`${lc} Resolving raw snapshot route: cosmos=${route.cosmos}, spaceId=${route.spaceId}, addr=${route.ibGibAddr}`);
1860
2420
  if (this.cosmosNavigatorInstance) {
1861
- const cosmos = await this.cosmosNavigatorInstance.ensureCosmos(route.cosmos);
2421
+ const cosmos = await this.cosmosNavigatorInstance.findCosmos(route.cosmos);
2422
+ if (!cosmos) {
2423
+ this.show404(`Cosmos "${route.cosmos}" not found.`);
2424
+ return;
2425
+ }
1862
2426
  this.cosmosNavigatorInstance.selectCosmos(cosmos.id);
1863
2427
  this.updateActiveCosmosDisplay(cosmos);
1864
2428
  }
@@ -1899,7 +2463,11 @@ export class SpaceGibShellService {
1899
2463
  try {
1900
2464
  console.log(`${lc} Resolving living tip route: cosmos=${route.cosmos}, spaceId=${route.spaceId}, tjp=${route.tjpAddr}`);
1901
2465
  if (this.cosmosNavigatorInstance) {
1902
- const cosmos = await this.cosmosNavigatorInstance.ensureCosmos(route.cosmos);
2466
+ const cosmos = await this.cosmosNavigatorInstance.findCosmos(route.cosmos);
2467
+ if (!cosmos) {
2468
+ this.show404(`Cosmos "${route.cosmos}" not found.`);
2469
+ return;
2470
+ }
1903
2471
  this.cosmosNavigatorInstance.selectCosmos(cosmos.id);
1904
2472
  this.updateActiveCosmosDisplay(cosmos);
1905
2473
  }