@ibgib/space-gib 0.0.33 → 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 (118) hide show
  1. package/dist/client/bootstrap.mjs +24 -51
  2. package/dist/client/chunk-FIAGRVBY.mjs +10933 -0
  3. package/dist/client/chunk-XLQ3UCQY.mjs +53 -0
  4. package/dist/client/css/activity-bar.css +435 -0
  5. package/dist/client/css/base.css +167 -0
  6. package/dist/client/css/components.css +218 -0
  7. package/dist/client/css/cosmic.css +557 -0
  8. package/dist/client/css/dev-tools.css +163 -0
  9. package/dist/client/css/landing.css +184 -0
  10. package/dist/client/css/layout.css +1017 -0
  11. package/dist/client/css/onboarding.css +181 -0
  12. package/dist/client/css/repos.css +740 -0
  13. package/dist/client/css/variables.css +64 -0
  14. package/dist/client/index.html +208 -31
  15. package/dist/client/index.mjs +22 -45
  16. package/dist/client/privacy.html +22 -22
  17. package/dist/client/script.mjs +1 -1
  18. package/dist/client/style.css +11 -1056
  19. package/dist/respec-gib.node.mjs +5 -0
  20. package/dist/server/server.mjs +6880 -3110
  21. package/package.json +6 -6
  22. package/src/client/AUTO-GENERATED-version.mts +1 -1
  23. package/src/client/cosmos/seed-cosmos-workspace.mts +83 -0
  24. package/src/client/css/activity-bar.css +435 -0
  25. package/src/client/css/base.css +167 -0
  26. package/src/client/css/components.css +218 -0
  27. package/src/client/css/cosmic.css +557 -0
  28. package/src/client/css/dev-tools.css +163 -0
  29. package/src/client/css/landing.css +184 -0
  30. package/src/client/css/layout.css +1017 -0
  31. package/src/client/css/onboarding.css +181 -0
  32. package/src/client/css/repos.css +740 -0
  33. package/src/client/css/variables.css +64 -0
  34. package/src/client/dev-tools/vcs-workspace.mts +7 -7
  35. package/src/client/index.html +208 -31
  36. package/src/client/privacy.html +22 -22
  37. package/src/client/style.css +11 -1056
  38. package/src/client/ui/component/changes/changes.css +413 -0
  39. package/src/client/ui/component/changes/changes.html +37 -0
  40. package/src/client/ui/component/changes/changes.mts +766 -0
  41. package/src/client/ui/component/changes/index.mts +9 -0
  42. package/src/client/ui/component/chat-view/IMPLEMENTATION.md +48 -0
  43. package/src/client/ui/component/chat-view/chat-view.css +381 -0
  44. package/src/client/ui/component/chat-view/chat-view.html +52 -0
  45. package/src/client/ui/component/chat-view/chat-view.mts +264 -0
  46. package/src/client/ui/component/chat-view/index.mts +9 -0
  47. package/src/client/ui/component/clone/IMPLEMENTATION.md +68 -0
  48. package/src/client/ui/component/clone/clone-constants.mts +16 -0
  49. package/src/client/ui/component/clone/clone-types.mts +60 -0
  50. package/src/client/ui/component/clone/clone.css +383 -0
  51. package/src/client/ui/component/clone/clone.html +89 -0
  52. package/src/client/ui/component/clone/clone.mts +648 -0
  53. package/src/client/ui/component/clone/index.mts +11 -0
  54. package/src/client/ui/component/code-editor/IMPLEMENTATION.md +89 -0
  55. package/src/client/ui/component/code-editor/code-editor.css +357 -0
  56. package/src/client/ui/component/code-editor/code-editor.html +67 -0
  57. package/src/client/ui/component/code-editor/code-editor.mts +643 -0
  58. package/src/client/ui/component/code-editor/index.mts +9 -0
  59. package/src/client/ui/component/cosmos-navigator/IMPLEMENTATION.md +31 -0
  60. package/src/client/ui/component/cosmos-navigator/cosmos-navigator.css +864 -0
  61. package/src/client/ui/component/cosmos-navigator/cosmos-navigator.html +92 -0
  62. package/src/client/ui/component/cosmos-navigator/cosmos-navigator.mts +1317 -0
  63. package/src/client/ui/component/cosmos-navigator/index.mts +18 -0
  64. package/src/client/ui/component/file-explorer/IMPLEMENTATION.md +42 -0
  65. package/src/client/ui/component/file-explorer/file-explorer.css +720 -0
  66. package/src/client/ui/component/file-explorer/file-explorer.html +55 -0
  67. package/src/client/ui/component/file-explorer/file-explorer.mts +1412 -0
  68. package/src/client/ui/component/file-explorer/index.mts +9 -0
  69. package/src/client/ui/component/nested-chat/IMPLEMENTATION.md +18 -0
  70. package/src/client/ui/component/nested-chat/chat/IMPLEMENTATION.md +14 -0
  71. package/src/client/ui/component/nested-chat/chat/chat-constants.mts +14 -0
  72. package/src/client/ui/component/nested-chat/chat/chat-helpers.mts +66 -0
  73. package/src/client/ui/component/nested-chat/chat/chat.css +654 -0
  74. package/src/client/ui/component/nested-chat/chat/chat.html +90 -0
  75. package/src/client/ui/component/nested-chat/chat/chat.mts +561 -0
  76. package/src/client/ui/component/nested-chat/chat/index.mts +7 -0
  77. package/src/client/ui/component/nested-chat/index.mts +14 -0
  78. package/src/client/ui/component/nested-chat/nested-chat-constants.mts +12 -0
  79. package/src/client/ui/component/nested-chat/nested-chat-types.mts +53 -0
  80. package/src/client/ui/component/nested-chat/nested-chat.css +142 -0
  81. package/src/client/ui/component/nested-chat/nested-chat.html +11 -0
  82. package/src/client/ui/component/nested-chat/nested-chat.mts +395 -0
  83. package/src/client/ui/component/notes-explorer/index.mts +9 -0
  84. package/src/client/ui/component/notes-explorer/notes-explorer.css +313 -0
  85. package/src/client/ui/component/notes-explorer/notes-explorer.html +136 -0
  86. package/src/client/ui/component/notes-explorer/notes-explorer.mts +240 -0
  87. package/src/client/ui/component/notes-inspector/index.mts +9 -0
  88. package/src/client/ui/component/notes-inspector/notes-inspector.css +244 -0
  89. package/src/client/ui/component/notes-inspector/notes-inspector.html +97 -0
  90. package/src/client/ui/component/notes-inspector/notes-inspector.mts +152 -0
  91. package/src/client/ui/component/pitch/IMPLEMENTATION.md +48 -0
  92. package/src/client/ui/component/pitch/index.mts +9 -0
  93. package/src/client/ui/component/pitch/pitch.css +295 -0
  94. package/src/client/ui/component/pitch/pitch.html +97 -0
  95. package/src/client/ui/component/pitch/pitch.mts +135 -0
  96. package/src/client/ui/component/timeline/IMPLEMENTATION.md +33 -0
  97. package/src/client/ui/component/timeline/index.mts +9 -0
  98. package/src/client/ui/component/timeline/timeline.css +279 -0
  99. package/src/client/ui/component/timeline/timeline.html +23 -0
  100. package/src/client/ui/component/timeline/timeline.mts +261 -0
  101. package/src/client/ui/component/timeline-item-details/IMPLEMENTATION.md +28 -0
  102. package/src/client/ui/component/timeline-item-details/index.mts +9 -0
  103. package/src/client/ui/component/timeline-item-details/timeline-item-details.css +286 -0
  104. package/src/client/ui/component/timeline-item-details/timeline-item-details.html +64 -0
  105. package/src/client/ui/component/timeline-item-details/timeline-item-details.mts +194 -0
  106. package/src/client/ui/router/index.mts +3 -0
  107. package/src/client/ui/router/space-gib-router-helpers.mts +72 -0
  108. package/src/client/ui/router/space-gib-router-helpers.respec.mts +529 -0
  109. package/src/client/ui/router/space-gib-router-service.mts +63 -0
  110. package/src/client/ui/router/space-gib-router-types.mts +49 -0
  111. package/src/client/ui/shell/cosmic-big-bang.mts +853 -0
  112. package/src/client/ui/shell/space-gib-shell-constants.mts +7 -0
  113. package/src/client/ui/shell/space-gib-shell-service.mts +2297 -32
  114. package/src/server/server.mts +20 -1
  115. package/tsconfig.test.json +5 -1
  116. package/dist/client/chunk-CRJV762I.mjs +0 -3102
  117. package/dist/client/chunk-LXRCF5OS.mjs +0 -25
  118. package/dist/client/chunk-UBDQUZZV.mjs +0 -1
@@ -1,32 +1,217 @@
1
1
  import { extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
2
+ import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
2
3
  import { ROOT_ADDR } from "@ibgib/ts-gib/dist/V1/constants.mjs";
3
4
  import { getComponentSvc } from "@ibgib/web-gib/dist/ui/component/ibgib-component-service.mjs";
4
- import { IbGibDynamicComponentMeta } from "@ibgib/web-gib/dist/ui/component/component-types.mjs";
5
-
6
- import { APP_CONFIG } from "../../constants.mjs";
7
- import { getIbGibGlobalThis_SpaceGib } from "../../helpers.web.mjs";
8
- import {
9
- ID_APP_ROOT, ID_HEADER_PANEL, ID_LEFT_PANEL, ID_CENTER_PANEL,
10
- ID_RIGHT_PANEL, ID_FOOTER_PANEL, ID_LEFT_RESIZER, ID_RIGHT_RESIZER,
11
- ID_FOOTER_RESIZER, ID_BTN_LEFT_PANEL_TOGGLE, ID_BTN_RIGHT_PANEL_TOGGLE,
12
- ID_BTN_CREATE_KEYSTONE, ID_CENTER_PANEL_CONTENT
13
- } from "./space-gib-shell-constants.mjs";
14
5
  import {
15
6
  EVENT_IBGIB_SHELL_READY, EVENT_IBGIB_UI_BUSY, EVENT_IBGIB_UI_MESSAGE,
16
- UI_ACTION_SHOW_IDENTITY_MANAGER, UI_ACTION_SHOW_KEYSTONE_CREATOR,
17
- UI_ACTION_SHOW_ADD_DEVICE, UI_ACTION_SHOW_PRIVACY_POLICY
7
+ EVENT_IBGIB_IDENTITY_CHANGED, UI_ACTION_SHOW_IDENTITY_MANAGER,
8
+ UI_ACTION_SHOW_KEYSTONE_CREATOR, UI_ACTION_SHOW_ADD_DEVICE,
9
+ UI_ACTION_SHOW_PRIVACY_POLICY
18
10
  } from "@ibgib/web-gib/dist/ui/ui-constants.mjs";
19
11
  import { showFullscreenDialog, FullscreenDialogController } from "@ibgib/web-gib/dist/ui/ui-helpers.mjs";
20
- import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
21
12
  import { getKeystoneSyncService } from "@ibgib/web-gib/dist/identity/keystone-sync-service.mjs";
22
- import { PanelState } from "./space-gib-shell-types.mjs";
23
13
  import { registerStandardIdentityComponents } from "@ibgib/web-gib/dist/ui/component/identity/index.mjs";
24
14
  import { registerPrivacyPolicyComponent } from "@ibgib/web-gib/dist/ui/component/privacy-policy/index.mjs";
15
+ import { registerAdminReviewComponent } from "@ibgib/web-gib/dist/ui/component/admin/admin-review/index.mjs";
16
+
17
+ import { APP_CONFIG } from "../../constants.mjs";
18
+ import { getIbGibGlobalThis_SpaceGib } from "../../helpers.web.mjs";
19
+ import {
20
+ ID_HEADER_PANEL, ID_LEFT_PANEL, ID_CENTER_PANEL, ID_RIGHT_PANEL,
21
+ ID_FOOTER_PANEL, ID_LEFT_RESIZER, ID_RIGHT_RESIZER, ID_FOOTER_RESIZER,
22
+ ID_BTN_LEFT_PANEL_TOGGLE, ID_BTN_RIGHT_PANEL_TOGGLE, ID_BTN_CREATE_KEYSTONE,
23
+ ID_CENTER_PANEL_CONTENT,
24
+ } from "./space-gib-shell-constants.mjs";
25
+ import { PanelState } from "./space-gib-shell-types.mjs";
26
+ import {
27
+ registerCosmosNavigatorComponent, COSMOS_NAVIGATOR_COMPONENT_NAME,
28
+ CosmosNavigatorComponentInstance, CosmosInfo,
29
+ } from "../component/cosmos-navigator/index.mjs";
30
+ import {
31
+ registerFileExplorerComponent, FILE_EXPLORER_COMPONENT_NAME,
32
+ FileExplorerComponentInstance,
33
+ } from "../component/file-explorer/index.mjs";
34
+ import {
35
+ registerChangesComponent, CHANGES_COMPONENT_NAME,
36
+ ChangesComponentInstance,
37
+ } from "../component/changes/index.mjs";
38
+ import {
39
+ registerTimelineComponent, TIMELINE_COMPONENT_NAME,
40
+ TimelineComponentInstance, TimelineItem,
41
+ } from "../component/timeline/index.mjs";
42
+ import {
43
+ registerTimelineItemDetailsComponent, TIMELINE_ITEM_DETAILS_COMPONENT_NAME,
44
+ TimelineItemDetailsComponentInstance,
45
+ } from "../component/timeline-item-details/index.mjs";
46
+ import {
47
+ registerChatViewComponent, ChatViewComponentInstance,
48
+ } from "../component/chat-view/index.mjs";
49
+ import {
50
+ registerNestedChatComponent, NESTED_CHAT_COMPONENT_NAME,
51
+ NestedChatComponentInstance,
52
+ } from "../component/nested-chat/index.mjs";
53
+ import {
54
+ registerPitchComponent, PITCH_COMPONENT_NAME, PitchComponentInstance,
55
+ } from "../component/pitch/index.mjs";
56
+ import {
57
+ registerCodeEditorComponent, CODE_EDITOR_COMPONENT_NAME,
58
+ CodeEditorComponentInstance,
59
+ } from "../component/code-editor/index.mjs";
60
+ import {
61
+ registerCloneComponent, CLONE_COMPONENT_NAME, CloneComponentInstance,
62
+ CloneModalOptions, EVENT_SPACE_GIB_CLONE_CLOSED,
63
+ } from "../component/clone/index.mjs";
64
+ import {
65
+ registerNotesExplorerComponent, NOTES_EXPLORER_COMPONENT_NAME,
66
+ NotesExplorerComponentInstance,
67
+ } from "../component/notes-explorer/index.mjs";
68
+ import {
69
+ registerNotesInspectorComponent, NOTES_INSPECTOR_COMPONENT_NAME,
70
+ NotesInspectorComponentInstance,
71
+ } from "../component/notes-inspector/index.mjs";
72
+ import {
73
+ getSpaceGibRouterSvc,
74
+ SpaceGibRouteInfo,
75
+ SpaceGibTenant,
76
+ GalaxyRouteInfo,
77
+ RawAddrRouteInfo,
78
+ RawTjpRouteInfo,
79
+ } from "../router/index.mjs";
80
+ import { GalaxyPolicy } from "@ibgib/core-gib/dist/cosmology/galaxy/galaxy-types.mjs";
81
+ import { getGlobalMetaspace_waitIfNeeded } from "@ibgib/web-gib/dist/helpers.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>;
110
+
111
+ export type PanelLoaderMap = {
112
+ [K in PanelName as `loadRouteFor${K}`]: PanelLoaderFn;
113
+ };
25
114
 
26
115
  export class SpaceGibShellService {
27
116
  private lc: string = `[SpaceGibShellService]`;
28
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
+
29
202
  public initialized: Promise<void>;
203
+ private isApplyingRoute: boolean = false;
204
+ private cosmosNavigatorInstance?: CosmosNavigatorComponentInstance;
205
+ private fileExplorerInstance?: FileExplorerComponentInstance;
206
+ private changesInstance?: ChangesComponentInstance;
207
+ private timelineInstance?: TimelineComponentInstance;
208
+ private timelineItemDetailsInstance?: TimelineItemDetailsComponentInstance;
209
+ private chatViewInstance?: ChatViewComponentInstance;
210
+ private nestedChatInstance?: NestedChatComponentInstance;
211
+ private pitchInstance?: PitchComponentInstance;
212
+ private codeEditorInstance?: CodeEditorComponentInstance;
213
+ private notesExplorerInstance?: NotesExplorerComponentInstance;
214
+ private notesInspectorInstance?: NotesInspectorComponentInstance;
30
215
 
31
216
  // Panel States
32
217
  private leftPanelState: PanelState = 'collapsed';
@@ -36,6 +221,11 @@ export class SpaceGibShellService {
36
221
  private isResizingLeft = false;
37
222
  private isResizingRight = false;
38
223
  private isResizingFooter = false;
224
+ private isResizingExplorerChanges = false;
225
+ private isResizingChangesTimeline = false;
226
+
227
+ // Playground Banner State
228
+ private hasBigBangExploded = false;
39
229
 
40
230
  constructor() {
41
231
  this.initialized = this.initialize();
@@ -45,6 +235,13 @@ export class SpaceGibShellService {
45
235
  this.initElements();
46
236
  this.initEventHandlers();
47
237
  await this.registerComponents();
238
+ await this.initCosmosNavigator();
239
+ await this.initFileExplorer();
240
+ await this.initChanges();
241
+ await this.initTimeline();
242
+ await this.initChatView();
243
+ await this.initCodeEditor();
244
+ await this.initPitch();
48
245
  }
49
246
 
50
247
  private initElements(): void {
@@ -60,6 +257,14 @@ export class SpaceGibShellService {
60
257
  private initEventHandlers(): void {
61
258
  const lc = `${this.lc}[${this.initEventHandlers.name}]`;
62
259
  try {
260
+ // Playground Mode Banner actions
261
+ const btnBannerCreate = document.getElementById('btn-banner-create-identity');
262
+ if (btnBannerCreate) {
263
+ btnBannerCreate.addEventListener('click', () => {
264
+ this.showKeystoneCreator();
265
+ });
266
+ }
267
+
63
268
  // Left Panel Toggle
64
269
  const btnLeft = document.getElementById(ID_BTN_LEFT_PANEL_TOGGLE);
65
270
  if (btnLeft) {
@@ -94,6 +299,44 @@ export class SpaceGibShellService {
94
299
  footerResizer.addEventListener('mousedown', () => this.isResizingFooter = true);
95
300
  }
96
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
+
97
340
  // Window mouse events for dragging
98
341
  window.addEventListener('mousemove', (e) => this.handleMouseMove(e));
99
342
  window.addEventListener('mouseup', () => this.handleMouseUp());
@@ -123,6 +366,8 @@ export class SpaceGibShellService {
123
366
  this.showAddDevice();
124
367
  } else if (action === UI_ACTION_SHOW_PRIVACY_POLICY) {
125
368
  this.showPrivacyPolicy();
369
+ } else if (action === 'show-admin-review' || action === 'admin-review') {
370
+ this.showAdminReview();
126
371
  }
127
372
  });
128
373
 
@@ -162,11 +407,47 @@ export class SpaceGibShellService {
162
407
  }
163
408
  });
164
409
 
410
+ // Mode Switcher (Spaces vs Repositories)
411
+ this.initModeSwitcher();
412
+
165
413
  } catch (error) {
166
414
  console.error(`${lc} ${extractErrorMsg(error)}`);
167
415
  }
168
416
  }
169
417
 
418
+ private currentMode: 'spaces' | 'repos' | 'notes' = 'spaces';
419
+
420
+ private setMode(mode: 'spaces' | 'repos' | 'notes', notesTitle?: string): void {
421
+ this.currentMode = mode;
422
+ if (mode === 'repos') {
423
+ this.showCode();
424
+ } else if (mode === 'notes') {
425
+ this.showNotes(notesTitle);
426
+ } else {
427
+ this.showCosmos();
428
+ }
429
+ }
430
+
431
+ private initModeSwitcher(): void {
432
+ const lc = `${this.lc}[${this.initModeSwitcher.name}]`;
433
+ try {
434
+ // URL param mode check (spaces by default, in-memory)
435
+ let initialMode: 'spaces' | 'repos' = 'spaces';
436
+ try {
437
+ const urlParams = new URLSearchParams(window.location.search);
438
+ const urlMode = urlParams.get('mode');
439
+ if (urlMode === 'repos') {
440
+ initialMode = 'repos';
441
+ }
442
+ } catch {
443
+ // ignore
444
+ }
445
+ this.setMode(initialMode);
446
+ } catch (error) {
447
+ console.error(`${lc} ${extractErrorMsg(error)} (E: c8e054e0639221c7b673949d9b2a2d26)`);
448
+ }
449
+ }
450
+
170
451
  public async showIdentityManager() {
171
452
  const lc = `${this.lc}[${this.showIdentityManager.name}]`;
172
453
  try {
@@ -262,6 +543,37 @@ export class SpaceGibShellService {
262
543
  }
263
544
  }
264
545
 
546
+ public async showAdminReview(tjpGib?: string) {
547
+ const lc = `${this.lc}[${this.showAdminReview.name}]`;
548
+ try {
549
+ const componentSvc = await getComponentSvc();
550
+
551
+ // create the component
552
+ const adminReviewComponent = await componentSvc.getComponentInstance({
553
+ path: 'ibgib-admin-review',
554
+ ibGibAddr: ROOT_ADDR, // Virtual address for initial render
555
+ useRegExpPrefilter: true,
556
+ });
557
+ if (!adminReviewComponent) { throw new Error(`(UNEXPECTED) adminReviewComponent falsy? (E: 8392a019bb2819827364810293847199)`); }
558
+
559
+ if (tjpGib && typeof (adminReviewComponent as any).setTjpGib === 'function') {
560
+ (adminReviewComponent as any).setTjpGib(tjpGib);
561
+ }
562
+
563
+ // get a ref to the parent container
564
+ const centerPanel = document.getElementById(ID_CENTER_PANEL_CONTENT);
565
+ if (!centerPanel) { throw new Error(`(UNEXPECTED) centerPanel falsy? (E: 9392a019bb2819827364810293847199)`); }
566
+
567
+ // inject via componentSvc
568
+ await componentSvc.inject({
569
+ parentEl: centerPanel,
570
+ componentToInject: adminReviewComponent
571
+ });
572
+ } catch (error) {
573
+ console.error(`${lc} ${extractErrorMsg(error)}`);
574
+ }
575
+ }
576
+
265
577
  private toggleLeftPanel() {
266
578
  this.leftPanelState = this.leftPanelState === 'expanded' ? 'collapsed' : 'expanded';
267
579
  const panel = document.getElementById(ID_LEFT_PANEL);
@@ -287,7 +599,7 @@ export class SpaceGibShellService {
287
599
  }
288
600
 
289
601
  private handleMouseMove(e: MouseEvent) {
290
- if (!this.isResizingLeft && !this.isResizingRight && !this.isResizingFooter) return;
602
+ if (!this.isResizingLeft && !this.isResizingRight && !this.isResizingFooter && !this.isResizingExplorerChanges && !this.isResizingChangesTimeline) return;
291
603
 
292
604
  // Prevent text selection while dragging
293
605
  e.preventDefault();
@@ -309,12 +621,40 @@ export class SpaceGibShellService {
309
621
  let newHeight = Math.max(24, Math.min(window.innerHeight - e.clientY, 400));
310
622
  document.documentElement.style.setProperty('--footer-panel-height', `${newHeight}px`);
311
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
+ }
312
648
  }
313
649
 
314
650
  private handleMouseUp() {
315
651
  this.isResizingLeft = false;
316
652
  this.isResizingRight = false;
317
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');
318
658
  }
319
659
 
320
660
  private updateCssVariables() {
@@ -326,40 +666,1965 @@ export class SpaceGibShellService {
326
666
  try {
327
667
  await registerStandardIdentityComponents();
328
668
  await registerPrivacyPolicyComponent();
669
+ await registerAdminReviewComponent();
670
+ await registerCosmosNavigatorComponent();
671
+ await registerFileExplorerComponent();
672
+ await registerChangesComponent();
673
+ await registerTimelineComponent();
674
+ await registerTimelineItemDetailsComponent();
675
+ await registerChatViewComponent();
676
+ await registerNestedChatComponent();
677
+ await registerPitchComponent();
678
+ await registerCodeEditorComponent();
679
+ await registerCloneComponent();
680
+ await registerNotesExplorerComponent();
681
+ await registerNotesInspectorComponent();
329
682
  } catch (error) {
330
683
  console.error(`${lc} ${extractErrorMsg(error)}`);
331
684
  }
332
685
  }
333
686
 
334
- public async onEngineReady(): Promise<void> {
335
- const lc = `${this.lc}[${this.onEngineReady.name}]`;
687
+ private async initCosmosNavigator(): Promise<void> {
688
+ const lc = `${this.lc}[${this.initCosmosNavigator.name}]`;
336
689
  try {
337
- await this.initialized;
338
- console.log(`${lc} SpaceGib Shell Service is ready.`);
690
+ const componentSvc = await getComponentSvc();
691
+ this.cosmosNavigatorInstance = await componentSvc.getComponentInstance({
692
+ path: COSMOS_NAVIGATOR_COMPONENT_NAME,
693
+ ibGibAddr: ROOT_ADDR,
694
+ useRegExpPrefilter: true,
695
+ }) as CosmosNavigatorComponentInstance;
339
696
 
340
- // Ensure the UI removes the loading text
341
- const statusSection = document.getElementById('status-section');
342
- if (statusSection) {
343
- statusSection.style.display = 'none';
697
+ if (!this.cosmosNavigatorInstance) {
698
+ throw new Error(`Failed to instantiate ${COSMOS_NAVIGATOR_COMPONENT_NAME}`);
699
+ }
700
+
701
+ this.bindCosmosNavigatorEvents();
702
+ this.bindCosmosChromeEvents();
703
+
704
+ // Initial state sync
705
+ const activeCosmos = this.cosmosNavigatorInstance.getActiveCosmos();
706
+ if (activeCosmos) {
707
+ this.updateActiveCosmosDisplay(activeCosmos);
708
+ }
709
+ const allCosmoses = this.cosmosNavigatorInstance.getCosmoses();
710
+ if (allCosmoses.length > 0) {
711
+ this.revealChrome();
712
+ }
713
+ this.updatePolycosmosList(allCosmoses, activeCosmos?.id || null);
714
+
715
+ // Inject directly into center panel on startup
716
+ await this.showCosmos();
717
+
718
+ } catch (error) {
719
+ console.error(`${lc} ${extractErrorMsg(error)}`);
720
+ }
721
+ }
722
+
723
+ public async showCosmos(): Promise<void> {
724
+ const lc = `${this.lc}[${this.showCosmos.name}]`;
725
+ try {
726
+ const centerPanel = document.getElementById(ID_CENTER_PANEL_CONTENT);
727
+ if (!centerPanel) {
728
+ console.warn(`${lc} ${ID_CENTER_PANEL_CONTENT} element not found.`);
729
+ return;
730
+ }
731
+
732
+ if (!this.cosmosNavigatorInstance) {
733
+ const componentSvc = await getComponentSvc();
734
+ this.cosmosNavigatorInstance = await componentSvc.getComponentInstance({
735
+ path: COSMOS_NAVIGATOR_COMPONENT_NAME,
736
+ ibGibAddr: ROOT_ADDR,
737
+ useRegExpPrefilter: true,
738
+ }) as CosmosNavigatorComponentInstance;
739
+ }
740
+
741
+ if (this.cosmosNavigatorInstance) {
742
+ const componentSvc = await getComponentSvc();
743
+ await componentSvc.inject({
744
+ parentEl: centerPanel,
745
+ componentToInject: this.cosmosNavigatorInstance,
746
+ });
747
+ this.cosmosNavigatorInstance.ensureStars();
748
+ }
749
+ } catch (error) {
750
+ console.error(`${lc} ${extractErrorMsg(error)}`);
751
+ }
752
+ }
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
+
775
+ private async initFileExplorer(): Promise<void> {
776
+ const lc = `${this.lc}[${this.initFileExplorer.name}]`;
777
+ try {
778
+ const container = document.getElementById('code-file-explorer-container');
779
+ if (!container) {
780
+ console.warn(`${lc} code-file-explorer-container element not found.`);
781
+ return;
344
782
  }
345
783
 
346
- // Mount identity-header component in the header bar
347
784
  const componentSvc = await getComponentSvc();
348
- const identityHeader = await componentSvc.getComponentInstance({
349
- path: 'ibgib-identity-header',
785
+ this.fileExplorerInstance = await componentSvc.getComponentInstance({
786
+ path: FILE_EXPLORER_COMPONENT_NAME,
350
787
  ibGibAddr: ROOT_ADDR,
351
788
  useRegExpPrefilter: true,
789
+ }) as FileExplorerComponentInstance;
790
+
791
+ if (!this.fileExplorerInstance) {
792
+ throw new Error(`Failed to instantiate ${FILE_EXPLORER_COMPONENT_NAME}`);
793
+ }
794
+
795
+ await componentSvc.inject({
796
+ parentEl: container,
797
+ componentToInject: this.fileExplorerInstance,
352
798
  });
353
799
 
354
- const headerContainer = document.getElementById('identity-header-container');
355
- if (headerContainer && identityHeader) {
356
- headerContainer.innerHTML = '';
357
- headerContainer.appendChild(identityHeader as any);
800
+ this.bindFileExplorerEvents();
801
+
802
+ } catch (error) {
803
+ console.error(`${lc} ${extractErrorMsg(error)}`);
804
+ }
805
+ }
806
+
807
+ private bindFileExplorerEvents(): void {
808
+ window.addEventListener('space-gib-file-selected', (e: any) => {
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;
358
814
  }
815
+ const branchName = branch || 'main';
816
+ const galaxyName = galaxy || 'code-1';
817
+ const tipAddr = 'tip';
359
818
 
360
- window.dispatchEvent(new CustomEvent(EVENT_IBGIB_SHELL_READY, { detail: { timestamp: Date.now() } }));
819
+ this.chatViewInstance?.setTargetFile(fullFile, `${branchName} @ ${tipAddr}`);
820
+ this.nestedChatInstance?.activateIbGib({ addr: `file ${fullFile}^${tipAddr}` });
821
+
822
+ this.showCode(fullFile, branchName, galaxyName);
823
+
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',
833
+ branch: branchName,
834
+ filePath: fullFile,
835
+ ...(tenant ? { tenant } : {}),
836
+ });
837
+ }
838
+ });
839
+
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
+ }
860
+
861
+ // 3. Update router navigation
862
+ const activeCosmos = this.cosmosNavigatorInstance?.getActiveCosmos();
863
+ if (activeCosmos) {
864
+ const tenant = this.getActiveTenant(activeCosmos);
865
+ getSpaceGibRouterSvc().navigate({
866
+ kind: 'galaxy',
867
+ cosmos: activeCosmos.id || activeCosmos.name,
868
+ galaxy: targetGalaxy,
869
+ topology: 'branching',
870
+ discriminator: 'code',
871
+ branch: targetBranch,
872
+ ...(activePath ? { filePath: activePath } : {}),
873
+ ...(tenant ? { tenant } : {}),
874
+ });
875
+ }
876
+ });
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
+
894
+ window.addEventListener('space-gib-new-file-requested', (e: any) => {
895
+ const { branch } = e.detail || {};
896
+ const filename = prompt(`Create new file on branch [${branch || 'main'}]:`, 'untitled.ts');
897
+ if (filename) {
898
+ console.log(`${this.lc} New file requested: ${filename} on branch ${branch}`);
899
+ }
900
+ });
901
+
902
+ window.addEventListener('space-gib-new-folder-requested', (e: any) => {
903
+ const { branch } = e.detail || {};
904
+ const foldername = prompt(`Create new folder on branch [${branch || 'main'}]:`, 'components');
905
+ if (foldername) {
906
+ console.log(`${this.lc} New folder requested: ${foldername} on branch ${branch}`);
907
+ }
908
+ });
909
+ }
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
+
1022
+ private async initTimeline(): Promise<void> {
1023
+ const lc = `${this.lc}[${this.initTimeline.name}]`;
1024
+ try {
1025
+ const container = document.getElementById('code-timeline-container');
1026
+ if (!container) {
1027
+ console.warn(`${lc} code-timeline-container element not found.`);
1028
+ return;
1029
+ }
1030
+
1031
+ const componentSvc = await getComponentSvc();
1032
+ this.timelineInstance = await componentSvc.getComponentInstance({
1033
+ path: TIMELINE_COMPONENT_NAME,
1034
+ ibGibAddr: ROOT_ADDR,
1035
+ useRegExpPrefilter: true,
1036
+ }) as TimelineComponentInstance;
1037
+
1038
+ if (!this.timelineInstance) {
1039
+ throw new Error(`Failed to instantiate ${TIMELINE_COMPONENT_NAME}`);
1040
+ }
1041
+
1042
+ await componentSvc.inject({
1043
+ parentEl: container,
1044
+ componentToInject: this.timelineInstance,
1045
+ });
1046
+
1047
+ this.bindTimelineEvents();
1048
+
1049
+ } catch (error) {
1050
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1051
+ }
1052
+ }
1053
+
1054
+ private bindTimelineEvents(): void {
1055
+ window.addEventListener('space-gib-timeline-item-selected', (e: any) => {
1056
+ const item: TimelineItem = e.detail?.item;
1057
+ if (item) {
1058
+ console.log(`${this.lc} Timeline snapshot selected: ${item.shortHash} - ${item.message}`);
1059
+ this.showTimelineItemDetails(item);
1060
+ }
1061
+ });
1062
+
1063
+ window.addEventListener('space-gib-timeline-details-closed', () => {
1064
+ console.log(`${this.lc} Timeline details closed. Returning to previous view.`);
1065
+ const currentRoute = getSpaceGibRouterSvc().getCurrentRoute();
1066
+ if (currentRoute && (currentRoute.kind === 'raw-addr' || currentRoute.kind === 'raw-tjp')) {
1067
+ const tenant = this.getActiveTenant() || currentRoute.tenant;
1068
+ getSpaceGibRouterSvc().navigate({
1069
+ kind: 'cosmos',
1070
+ cosmos: currentRoute.cosmos,
1071
+ ...(tenant ? { tenant } : {}),
1072
+ });
1073
+ } else {
1074
+ this.showCode();
1075
+ }
1076
+ });
1077
+ }
1078
+
1079
+ private async initChatView(): Promise<void> {
1080
+ const lc = `${this.lc}[${this.initChatView.name}]`;
1081
+ try {
1082
+ const container = document.getElementById('code-chat-view-container');
1083
+ if (!container) {
1084
+ console.warn(`${lc} code-chat-view-container element not found.`);
1085
+ return;
1086
+ }
1087
+
1088
+ const componentSvc = await getComponentSvc();
1089
+ this.nestedChatInstance = await componentSvc.getComponentInstance({
1090
+ path: NESTED_CHAT_COMPONENT_NAME,
1091
+ ibGibAddr: ROOT_ADDR,
1092
+ useRegExpPrefilter: true,
1093
+ }) as NestedChatComponentInstance;
1094
+
1095
+ if (!this.nestedChatInstance) {
1096
+ throw new Error(`Failed to instantiate ${NESTED_CHAT_COMPONENT_NAME}`);
1097
+ }
1098
+
1099
+ await componentSvc.inject({
1100
+ parentEl: container,
1101
+ componentToInject: this.nestedChatInstance,
1102
+ });
1103
+
1104
+ this.bindChatViewEvents();
1105
+
1106
+ } catch (error) {
1107
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1108
+ }
1109
+ }
1110
+
1111
+ private bindChatViewEvents(): void {
1112
+ window.addEventListener('space-gib-chat-close-requested', () => {
1113
+ this.toggleRightPanel();
1114
+ });
1115
+
1116
+ window.addEventListener('space-gib-chat-closed', () => {
1117
+ if (this.rightPanelState === 'expanded') {
1118
+ this.toggleRightPanel();
1119
+ }
1120
+ });
1121
+
1122
+ window.addEventListener('space-gib-chat-message-sent', (e: any) => {
1123
+ const { message, targetFile, contextAddr } = e.detail || {};
1124
+ console.log(`${this.lc} Chat message sent on context ${contextAddr || targetFile}:`, message);
1125
+ });
1126
+ }
1127
+
1128
+ private async initCodeEditor(): Promise<void> {
1129
+ const lc = `${this.lc}[${this.initCodeEditor.name}]`;
1130
+ try {
1131
+ const componentSvc = await getComponentSvc();
1132
+ this.codeEditorInstance = await componentSvc.getComponentInstance({
1133
+ path: CODE_EDITOR_COMPONENT_NAME,
1134
+ ibGibAddr: ROOT_ADDR,
1135
+ useRegExpPrefilter: true,
1136
+ }) as CodeEditorComponentInstance;
1137
+
1138
+ if (!this.codeEditorInstance) {
1139
+ throw new Error(`Failed to instantiate ${CODE_EDITOR_COMPONENT_NAME}`);
1140
+ }
1141
+
1142
+ this.bindCodeEditorEvents();
1143
+
1144
+ } catch (error) {
1145
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1146
+ }
1147
+ }
1148
+
1149
+ public async showCode(filePath?: string, branch?: string, galaxy?: string): Promise<void> {
1150
+ const lc = `${this.lc}[${this.showCode.name}]`;
1151
+ try {
1152
+ const centerPanel = document.getElementById(ID_CENTER_PANEL_CONTENT);
1153
+ if (!centerPanel) {
1154
+ console.warn(`${lc} ${ID_CENTER_PANEL_CONTENT} element not found.`);
1155
+ return;
1156
+ }
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
+
1162
+ if (!this.codeEditorInstance) {
1163
+ const componentSvc = await getComponentSvc();
1164
+ this.codeEditorInstance = await componentSvc.getComponentInstance({
1165
+ path: CODE_EDITOR_COMPONENT_NAME,
1166
+ ibGibAddr: ROOT_ADDR,
1167
+ useRegExpPrefilter: true,
1168
+ }) as CodeEditorComponentInstance;
1169
+ }
1170
+
1171
+ if (this.codeEditorInstance) {
1172
+ const componentSvc = await getComponentSvc();
1173
+ await componentSvc.inject({
1174
+ parentEl: centerPanel,
1175
+ componentToInject: this.codeEditorInstance,
1176
+ });
1177
+
1178
+ this.codeEditorInstance.setGalaxyAndBranch(targetGalaxy, targetBranch);
1179
+
1180
+ if (filePath) {
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)}`);
1214
+ }
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
+
1234
+ this.codeEditorInstance.loadFile({
1235
+ filepath: filePath,
1236
+ repo: targetGalaxy,
1237
+ branch: targetBranch,
1238
+ tipAddr,
1239
+ content,
1240
+ originalContent: casContent,
1241
+ isUntracked,
1242
+ });
1243
+ } else {
1244
+ this.codeEditorInstance.clearFile({
1245
+ repo: targetGalaxy,
1246
+ branch: targetBranch,
1247
+ });
1248
+ }
1249
+ }
1250
+
1251
+ this.changesInstance?.setGalaxyAndBranch(targetGalaxy, targetBranch, { preserveChanges: true });
1252
+
1253
+ const codeChatContainer = document.getElementById('code-chat-view-container');
1254
+ const notesInspectorContainer = document.getElementById('notes-inspector-container');
1255
+ if (notesInspectorContainer) { notesInspectorContainer.style.display = 'none'; }
1256
+ if (codeChatContainer) {
1257
+ codeChatContainer.style.display = 'block';
1258
+ if (this.nestedChatInstance && this.nestedChatInstance.parentElement !== codeChatContainer) {
1259
+ codeChatContainer.appendChild(this.nestedChatInstance as any);
1260
+ }
1261
+ }
1262
+
1263
+ this.expandRightPanel();
1264
+ } catch (error) {
1265
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1266
+ }
1267
+ }
1268
+
1269
+ public async showNotes(
1270
+ title?: string,
1271
+ opts?: {
1272
+ trail?: string[];
1273
+ trailMode?: 'tjp' | 'addr';
1274
+ query?: Record<string, string | undefined>;
1275
+ }
1276
+ ): Promise<void> {
1277
+ const lc = `${this.lc}[${this.showNotes.name}]`;
1278
+ try {
1279
+ const centerPanel = document.getElementById(ID_CENTER_PANEL_CONTENT);
1280
+ if (!centerPanel) {
1281
+ console.warn(`${lc} ${ID_CENTER_PANEL_CONTENT} element not found.`);
1282
+ return;
1283
+ }
1284
+
1285
+ // In Notes mode, Center Panel is the primary nested chat topic stream!
1286
+ if (!this.nestedChatInstance) {
1287
+ const componentSvc = await getComponentSvc();
1288
+ this.nestedChatInstance = await componentSvc.getComponentInstance({
1289
+ path: NESTED_CHAT_COMPONENT_NAME,
1290
+ ibGibAddr: ROOT_ADDR,
1291
+ useRegExpPrefilter: true,
1292
+ }) as NestedChatComponentInstance;
1293
+ }
1294
+
1295
+ if (this.nestedChatInstance) {
1296
+ const componentSvc = await getComponentSvc();
1297
+ await componentSvc.inject({
1298
+ parentEl: centerPanel,
1299
+ componentToInject: this.nestedChatInstance,
1300
+ });
1301
+
1302
+ if (opts?.trail && opts.trail.length > 0) {
1303
+ for (const step of opts.trail) {
1304
+ const stepAddr = opts.trailMode === 'addr' ? step : `topic ${step}^gib_notes`;
1305
+ await this.nestedChatInstance.activateIbGib({ addr: stepAddr });
1306
+ }
1307
+ } else {
1308
+ await this.nestedChatInstance.activateIbGib({ addr: `topic ${title || 'Notes Space 1'}^gib_notes` });
1309
+ }
1310
+ }
1311
+
1312
+ // In Notes mode, Right Panel is the contextual inspector!
1313
+ await this.initNotesInspector();
1314
+ const codeChatContainer = document.getElementById('code-chat-view-container');
1315
+ const notesInspectorContainer = document.getElementById('notes-inspector-container');
1316
+ if (codeChatContainer) { codeChatContainer.style.display = 'none'; }
1317
+ if (notesInspectorContainer) { notesInspectorContainer.style.display = 'block'; }
1318
+
1319
+ if (opts?.query?.inspect && this.notesInspectorInstance) {
1320
+ const inspectVal = opts.query.inspect;
1321
+ if (inspectVal.includes('#L')) {
1322
+ const [file, lineStr] = inspectVal.split('#L');
1323
+ this.notesInspectorInstance.inspectFileAnchor({
1324
+ file,
1325
+ line: Number(lineStr) || 1,
1326
+ snippet: `// Anchor in ${file} at line ${lineStr}`,
1327
+ });
1328
+ } else {
1329
+ this.notesInspectorInstance.inspectNode({ addr: inspectVal });
1330
+ }
1331
+ }
1332
+
1333
+ this.expandRightPanel();
1334
+ } catch (error) {
1335
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1336
+ }
1337
+ }
1338
+
1339
+ private async initNotesExplorer(title?: string): Promise<void> {
1340
+ const lc = `${this.lc}[${this.initNotesExplorer.name}]`;
1341
+ try {
1342
+ const container = document.getElementById('notes-explorer-container');
1343
+ if (!container) {
1344
+ console.warn(`${lc} notes-explorer-container element not found.`);
1345
+ return;
1346
+ }
1347
+
1348
+ if (!this.notesExplorerInstance) {
1349
+ const componentSvc = await getComponentSvc();
1350
+ this.notesExplorerInstance = await componentSvc.getComponentInstance({
1351
+ path: NOTES_EXPLORER_COMPONENT_NAME,
1352
+ ibGibAddr: ROOT_ADDR,
1353
+ useRegExpPrefilter: true,
1354
+ }) as NotesExplorerComponentInstance;
1355
+
1356
+ if (this.notesExplorerInstance) {
1357
+ await componentSvc.inject({
1358
+ parentEl: container,
1359
+ componentToInject: this.notesExplorerInstance,
1360
+ });
1361
+ }
1362
+ }
1363
+
1364
+ if (title && this.notesExplorerInstance) {
1365
+ this.notesExplorerInstance.setTitle(title);
1366
+ }
1367
+ } catch (error) {
1368
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1369
+ }
1370
+ }
1371
+
1372
+ private async initNotesInspector(): Promise<void> {
1373
+ const lc = `${this.lc}[${this.initNotesInspector.name}]`;
1374
+ try {
1375
+ const container = document.getElementById('notes-inspector-container');
1376
+ if (!container) { return; }
1377
+
1378
+ if (!this.notesInspectorInstance) {
1379
+ const componentSvc = await getComponentSvc();
1380
+ this.notesInspectorInstance = await componentSvc.getComponentInstance({
1381
+ path: NOTES_INSPECTOR_COMPONENT_NAME,
1382
+ ibGibAddr: ROOT_ADDR,
1383
+ useRegExpPrefilter: true,
1384
+ }) as NotesInspectorComponentInstance;
1385
+
1386
+ if (this.notesInspectorInstance) {
1387
+ await componentSvc.inject({
1388
+ parentEl: container,
1389
+ componentToInject: this.notesInspectorInstance,
1390
+ });
1391
+ }
1392
+ }
1393
+ } catch (error) {
1394
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1395
+ }
1396
+ }
1397
+
1398
+ public async showTimelineItemDetails(item: TimelineItem): Promise<void> {
1399
+ const lc = `${this.lc}[${this.showTimelineItemDetails.name}]`;
1400
+ try {
1401
+ const centerPanel = document.getElementById(ID_CENTER_PANEL_CONTENT);
1402
+ if (!centerPanel) {
1403
+ console.warn(`${lc} ${ID_CENTER_PANEL_CONTENT} element not found.`);
1404
+ return;
1405
+ }
1406
+
1407
+ if (!this.timelineItemDetailsInstance) {
1408
+ const componentSvc = await getComponentSvc();
1409
+ this.timelineItemDetailsInstance = await componentSvc.getComponentInstance({
1410
+ path: TIMELINE_ITEM_DETAILS_COMPONENT_NAME,
1411
+ ibGibAddr: ROOT_ADDR,
1412
+ useRegExpPrefilter: true,
1413
+ }) as TimelineItemDetailsComponentInstance;
1414
+ }
1415
+
1416
+ if (this.timelineItemDetailsInstance) {
1417
+ const componentSvc = await getComponentSvc();
1418
+ await componentSvc.inject({
1419
+ parentEl: centerPanel,
1420
+ componentToInject: this.timelineItemDetailsInstance,
1421
+ });
1422
+
1423
+ this.timelineItemDetailsInstance.setTimelineItem(item);
1424
+ }
1425
+ } catch (error) {
1426
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1427
+ }
1428
+ }
1429
+
1430
+ private bindCodeEditorEvents(): void {
1431
+ window.addEventListener('space-gib-code-changed', (e: any) => {
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
+ }
1451
+ });
1452
+
1453
+ window.addEventListener('space-gib-code-copied', (e: any) => {
1454
+ const { filepath } = e.detail || {};
1455
+ console.log(`${this.lc} Code copied from ${filepath}`);
1456
+ });
1457
+ }
1458
+
1459
+ private async initPitch(): Promise<void> {
1460
+ const lc = `${this.lc}[${this.initPitch.name}]`;
1461
+ try {
1462
+ const componentSvc = await getComponentSvc();
1463
+ this.pitchInstance = await componentSvc.getComponentInstance({
1464
+ path: PITCH_COMPONENT_NAME,
1465
+ ibGibAddr: ROOT_ADDR,
1466
+ useRegExpPrefilter: true,
1467
+ }) as PitchComponentInstance;
1468
+
1469
+ if (!this.pitchInstance) {
1470
+ throw new Error(`Failed to instantiate ${PITCH_COMPONENT_NAME}`);
1471
+ }
1472
+
1473
+ this.bindPitchEvents();
1474
+
1475
+ } catch (error) {
1476
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1477
+ }
1478
+ }
1479
+
1480
+ private bindPitchEvents(): void {
1481
+ window.addEventListener('space-gib-pitch-closed', () => {
1482
+ this.closePitch();
1483
+ });
1484
+ }
1485
+
1486
+ public collapsePanels(): void {
1487
+ this.leftPanelState = 'collapsed';
1488
+ const leftPanel = document.getElementById(ID_LEFT_PANEL);
1489
+ if (leftPanel) {
1490
+ leftPanel.classList.add('collapsed');
1491
+ }
1492
+ this.rightPanelState = 'collapsed';
1493
+ const rightPanel = document.getElementById(ID_RIGHT_PANEL);
1494
+ if (rightPanel) {
1495
+ rightPanel.classList.add('collapsed');
1496
+ }
1497
+ }
1498
+
1499
+ public async showPitch(): Promise<void> {
1500
+ const lc = `${this.lc}[${this.showPitch.name}]`;
1501
+ try {
1502
+ const centerPanel = document.getElementById(ID_CENTER_PANEL_CONTENT);
1503
+ if (!centerPanel) {
1504
+ console.warn(`${lc} ${ID_CENTER_PANEL_CONTENT} element not found.`);
1505
+ return;
1506
+ }
1507
+
1508
+ if (!this.pitchInstance) {
1509
+ const componentSvc = await getComponentSvc();
1510
+ this.pitchInstance = await componentSvc.getComponentInstance({
1511
+ path: PITCH_COMPONENT_NAME,
1512
+ ibGibAddr: ROOT_ADDR,
1513
+ useRegExpPrefilter: true,
1514
+ }) as PitchComponentInstance;
1515
+ }
1516
+
1517
+ if (this.pitchInstance) {
1518
+ const componentSvc = await getComponentSvc();
1519
+ await componentSvc.inject({
1520
+ parentEl: centerPanel,
1521
+ componentToInject: this.pitchInstance,
1522
+ });
1523
+ }
1524
+
1525
+ this.collapsePanels();
1526
+ } catch (error) {
1527
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1528
+ }
1529
+ }
1530
+
1531
+ public closePitch(): void {
1532
+ this.showCosmos();
1533
+ }
1534
+
1535
+ public async openCloneModal(options?: CloneModalOptions): Promise<void> {
1536
+ const lc = `${this.lc}[${this.openCloneModal.name}]`;
1537
+ try {
1538
+ const componentSvc = await getComponentSvc();
1539
+ const cloneInstance = await componentSvc.getComponentInstance({
1540
+ path: CLONE_COMPONENT_NAME,
1541
+ ibGibAddr: ROOT_ADDR,
1542
+ useRegExpPrefilter: true,
1543
+ }) as CloneComponentInstance;
1544
+
1545
+ if (!cloneInstance) {
1546
+ throw new Error(`Failed to instantiate ${CLONE_COMPONENT_NAME}`);
1547
+ }
1548
+
1549
+ const activeCosmos = this.cosmosNavigatorInstance?.getActiveCosmos();
1550
+ const effectiveOpts: CloneModalOptions = {
1551
+ cosmosId: activeCosmos?.id || 'cosmos-1',
1552
+ cosmosName: activeCosmos?.name || 'Cosmos 1',
1553
+ ...options,
1554
+ };
1555
+ cloneInstance.setOptions(effectiveOpts);
1556
+
1557
+ document.body.appendChild(cloneInstance);
1558
+
1559
+ cloneInstance.addEventListener(EVENT_SPACE_GIB_CLONE_CLOSED, () => {
1560
+ cloneInstance.remove();
1561
+ });
1562
+ } catch (error) {
1563
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1564
+ }
1565
+ }
1566
+
1567
+ public revealChrome(): void {
1568
+ const header = document.getElementById(ID_HEADER_PANEL);
1569
+ const footer = document.getElementById(ID_FOOTER_PANEL);
1570
+ const footerResizer = document.getElementById(ID_FOOTER_RESIZER);
1571
+
1572
+ header?.classList.remove('pre-bang-hidden');
1573
+ header?.classList.add('chrome-fade-in');
1574
+
1575
+ footer?.classList.remove('pre-bang-hidden');
1576
+ footer?.classList.add('chrome-fade-in');
1577
+
1578
+ footerResizer?.classList.remove('pre-bang-hidden');
1579
+ footerResizer?.classList.add('chrome-fade-in');
1580
+
1581
+ setTimeout(() => {
1582
+ header?.classList.remove('chrome-fade-in');
1583
+ header?.classList.add('chrome-revealed');
1584
+ footer?.classList.remove('chrome-fade-in');
1585
+ footer?.classList.add('chrome-revealed');
1586
+ footerResizer?.classList.remove('chrome-fade-in');
1587
+ footerResizer?.classList.add('chrome-revealed');
1588
+ }, 2500);
1589
+ }
1590
+
1591
+ public setActiveActivityBarButton(type: 'polycosmos' | 'cosmos' | 'space', spaceId?: string): void {
1592
+ const btnPoly = document.getElementById('activity-btn-polycosmos');
1593
+ const btnCosmos = document.getElementById('activity-btn-cosmos');
1594
+ const activitySpaces = document.getElementById('activity-spaces-container');
1595
+
1596
+ if (type === 'polycosmos') {
1597
+ btnPoly?.classList.add('active');
1598
+ btnCosmos?.classList.remove('active');
1599
+ activitySpaces?.querySelectorAll('.activity-bar-btn').forEach(b => b.classList.remove('active'));
1600
+ } else if (type === 'cosmos') {
1601
+ btnCosmos?.classList.add('active');
1602
+ btnPoly?.classList.remove('active');
1603
+ activitySpaces?.querySelectorAll('.activity-bar-btn').forEach(b => b.classList.remove('active'));
1604
+ } else if (type === 'space') {
1605
+ btnPoly?.classList.remove('active');
1606
+ btnCosmos?.classList.remove('active');
1607
+ if (activitySpaces) {
1608
+ let matched = false;
1609
+ activitySpaces.querySelectorAll('.activity-bar-btn').forEach(b => {
1610
+ const isMatch = b.getAttribute('data-space-id') === spaceId;
1611
+ b.classList.toggle('active', isMatch);
1612
+ if (isMatch) matched = true;
1613
+ });
1614
+ if (!matched && (spaceId === 'code' || spaceId === 'text')) {
1615
+ const firstOfType = activitySpaces.querySelector(`.activity-bar-btn[data-space-type="${spaceId}"]`);
1616
+ firstOfType?.classList.add('active');
1617
+ }
1618
+ }
1619
+ }
1620
+ }
1621
+
1622
+ private bindCosmosNavigatorEvents(): void {
1623
+ window.addEventListener('space-gib-big-bang-exploded', () => {
1624
+ this.hasBigBangExploded = true;
1625
+ this.revealChrome();
1626
+ this.updatePlaygroundBannerVisibility();
1627
+ });
1628
+
1629
+ window.addEventListener('space-gib-identity-docked', () => {
1630
+ const identityContainer = document.getElementById('identity-header-container');
1631
+ if (identityContainer) {
1632
+ if (!this.hasActiveIdentity()) {
1633
+ identityContainer.classList.add('identity-active-highlight');
1634
+ }
1635
+ const btn = identityContainer.querySelector('button') || identityContainer;
1636
+ (btn as HTMLElement).focus?.();
1637
+ }
1638
+ });
1639
+
1640
+ window.addEventListener(EVENT_IBGIB_IDENTITY_CHANGED, () => {
1641
+ const identityContainer = document.getElementById('identity-header-container');
1642
+ if (this.hasActiveIdentity()) {
1643
+ identityContainer?.classList.remove('identity-active-highlight');
1644
+ }
1645
+ this.updatePlaygroundBannerVisibility();
1646
+ });
1647
+
1648
+ window.addEventListener('space-gib-agent-docked', () => {
1649
+ const btnAgent = document.getElementById('btn-footer-agent');
1650
+ if (btnAgent) {
1651
+ btnAgent.classList.add('is-docked');
1652
+ btnAgent.classList.add('agent-btn-docked-pulse');
1653
+ setTimeout(() => {
1654
+ btnAgent.classList.remove('agent-btn-docked-pulse');
1655
+ }, 1800);
1656
+ }
1657
+ });
1658
+
1659
+ window.addEventListener('space-gib-pitch-docked', () => {
1660
+ const btnPitch = document.getElementById('btn-header-pitch');
1661
+ if (btnPitch) {
1662
+ btnPitch.classList.add('is-docked');
1663
+ btnPitch.classList.add('pitch-btn-active-pulse');
1664
+ setTimeout(() => {
1665
+ btnPitch.classList.remove('pitch-btn-active-pulse');
1666
+ }, 2000);
1667
+ }
1668
+ });
1669
+
1670
+ window.addEventListener('space-gib-pitch-requested', () => {
1671
+ this.showPitch();
1672
+ });
1673
+
1674
+ window.addEventListener('space-gib-cosmos-selected', (e: any) => {
1675
+ const cosmos: CosmosInfo = e.detail?.cosmos;
1676
+ if (cosmos) {
1677
+ this.updateActiveCosmosDisplay(cosmos);
1678
+ this.showLeftPanelView('cosmos');
1679
+ if (!this.isApplyingRoute) {
1680
+ const tenant = this.getActiveTenant() || (cosmos.isTemporary ? { type: 'username', siteUsername: 'playground' } : undefined);
1681
+ getSpaceGibRouterSvc().navigate({
1682
+ kind: 'cosmos',
1683
+ cosmos: cosmos.name || cosmos.id,
1684
+ ...(tenant ? { tenant } : {}),
1685
+ });
1686
+ }
1687
+ }
1688
+ });
1689
+
1690
+ window.addEventListener('space-gib-activity-bar-expand-requested', () => {
1691
+ this.expandActivityBar();
1692
+ });
1693
+
1694
+ window.addEventListener('space-gib-space-added', (e: any) => {
1695
+ const { space, cosmos } = e.detail;
1696
+ if (cosmos) {
1697
+ this.updateActiveCosmosDisplay(cosmos, space?.id || space?.type);
1698
+ }
1699
+ this.expandActivityBar();
1700
+ this.expandLeftPanel();
1701
+
1702
+ if (space) {
1703
+ this.setActiveActivityBarButton('space', space.id);
1704
+ const dockedBtn = document.querySelector(`.activity-space-btn[data-space-id="${space.id}"]`);
1705
+ if (dockedBtn) {
1706
+ dockedBtn.classList.add('space-btn-docked-pulse');
1707
+ setTimeout(() => {
1708
+ dockedBtn.classList.remove('space-btn-docked-pulse');
1709
+ }, 1800);
1710
+ }
1711
+ if (space.type === 'code') {
1712
+ this.showLeftPanelView('code', space.name);
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
+ }
1726
+ } else if (space.type === 'text') {
1727
+ this.showLeftPanelView('text', 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
+ }
1740
+ }
1741
+ }
1742
+ });
1743
+
1744
+ window.addEventListener('space-gib-polycosmos-activated', (e: any) => {
1745
+ const { cosmoses, activeCosmosId } = e.detail;
1746
+ this.updatePolycosmosDisplay(cosmoses || [], activeCosmosId);
1747
+ this.showLeftPanelView('polycosmos');
1748
+ if (!this.isApplyingRoute) {
1749
+ const tenant = this.getActiveTenant();
1750
+ getSpaceGibRouterSvc().navigate({
1751
+ kind: 'polycosmos',
1752
+ ...(tenant ? { tenant } : {}),
1753
+ });
1754
+ }
1755
+ });
1756
+ }
1757
+
1758
+ private bindCosmosChromeEvents(): void {
1759
+ const btnHeaderPolycosmos = document.getElementById('btn-header-polycosmos');
1760
+ const btnHeaderCosmosSelect = document.getElementById('btn-header-cosmos-select');
1761
+ const headerCosmosMenu = document.getElementById('header-cosmos-menu');
1762
+ const activityBtnPolycosmos = document.getElementById('activity-btn-polycosmos');
1763
+ const activityBtnCosmos = document.getElementById('activity-btn-cosmos');
1764
+ const btnSideNewCosmos = document.getElementById('btn-side-new-cosmos');
1765
+ const brandGalaxy = document.getElementById('header-brand-galaxy');
1766
+ const btnFooterAgent = document.getElementById('btn-footer-agent');
1767
+ const btnHeaderPitch = document.getElementById('btn-header-pitch');
1768
+ const btnHeaderClone = document.getElementById('btn-header-clone');
1769
+
1770
+ btnHeaderPitch?.addEventListener('click', () => {
1771
+ this.showPitch();
1772
+ });
1773
+
1774
+ btnHeaderClone?.addEventListener('click', () => {
1775
+ this.openCloneModal();
1776
+ });
1777
+
1778
+ window.addEventListener('space-gib-clone-requested', (e: any) => {
1779
+ this.openCloneModal(e.detail);
1780
+ });
1781
+
1782
+ window.addEventListener('space-gib-branch-requested', async (e: any) => {
1783
+ const { galaxyName, sourceBranch, newBranchName, scope, itemPath } = e.detail || {};
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
+ }
1876
+ });
1877
+
1878
+ btnFooterAgent?.addEventListener('click', () => {
1879
+ alert('🤖 ibgib Agent: "Hello! I am your AI assistant across superspaces. How can I help you today?"');
1880
+ });
1881
+
1882
+ const goToPolycosmos = () => {
1883
+ this.switchToSpacesMode();
1884
+ this.cosmosNavigatorInstance?.setZoomState('polycosmos');
1885
+ this.setActiveActivityBarButton('polycosmos');
1886
+ this.showLeftPanelView('polycosmos');
1887
+ const allCosmoses = this.cosmosNavigatorInstance?.getCosmoses() || [];
1888
+ const activeCosmos = this.cosmosNavigatorInstance?.getActiveCosmos();
1889
+ this.updatePolycosmosDisplay(allCosmoses, activeCosmos?.id || null);
1890
+
1891
+ const tenant = this.getActiveTenant();
1892
+ getSpaceGibRouterSvc().navigate({
1893
+ kind: 'polycosmos',
1894
+ ...(tenant ? { tenant } : {}),
1895
+ });
1896
+ };
1897
+
1898
+ const goToCosmos = () => {
1899
+ this.switchToSpacesMode();
1900
+ this.cosmosNavigatorInstance?.setZoomState('cosmos');
1901
+ this.setActiveActivityBarButton('cosmos');
1902
+ this.showLeftPanelView('cosmos');
1903
+ const activeCosmos = this.cosmosNavigatorInstance?.getActiveCosmos();
1904
+ if (activeCosmos) {
1905
+ this.updateActiveCosmosDisplay(activeCosmos);
1906
+ const tenant = this.getActiveTenant() || (activeCosmos.isTemporary ? { type: 'username', siteUsername: 'playground' } : undefined);
1907
+ getSpaceGibRouterSvc().navigate({
1908
+ kind: 'cosmos',
1909
+ cosmos: activeCosmos.name || activeCosmos.id,
1910
+ ...(tenant ? { tenant } : {}),
1911
+ });
1912
+ }
1913
+ };
1914
+
1915
+ window.addEventListener('space-gib-new-note-requested', async (e: any) => {
1916
+ const { title } = e.detail || {};
1917
+ await this.showNotes();
1918
+ this.nestedChatInstance?.enterNewTopicMode(title);
1919
+ });
1920
+
1921
+ window.addEventListener('space-gib-note-selected', (e: any) => {
1922
+ const { file, line } = e.detail || {};
1923
+ if (file) {
1924
+ this.notesInspectorInstance?.inspectFileAnchor({ file, line });
1925
+ }
1926
+ });
1927
+
1928
+ window.addEventListener('space-gib-jump-to-code-requested', (e: any) => {
1929
+ const { file, line } = e.detail || {};
1930
+ this.showLeftPanelView('code', 'Code Galaxy 1');
1931
+ this.showCode(file);
1932
+ this.setActiveActivityBarButton('space', 'space-code-1');
1933
+ });
1934
+
1935
+ window.addEventListener('space-gib-inspector-close-requested', () => {
1936
+ this.toggleRightPanel();
1937
+ });
1938
+
1939
+ btnHeaderPolycosmos?.addEventListener('click', () => goToPolycosmos());
1940
+ activityBtnPolycosmos?.addEventListener('click', () => goToPolycosmos());
1941
+ brandGalaxy?.addEventListener('click', (e) => {
1942
+ e.preventDefault();
1943
+ goToPolycosmos();
1944
+ });
1945
+ const brandTitle = document.querySelector('.brand-title');
1946
+ brandTitle?.addEventListener('click', (e) => {
1947
+ e.preventDefault();
1948
+ goToPolycosmos();
1949
+ });
1950
+
1951
+ activityBtnCosmos?.addEventListener('click', () => goToCosmos());
1952
+
1953
+ btnHeaderCosmosSelect?.addEventListener('click', (e) => {
1954
+ e.stopPropagation();
1955
+ if (headerCosmosMenu) {
1956
+ headerCosmosMenu.classList.toggle('hidden');
1957
+ }
1958
+ });
1959
+
1960
+ document.addEventListener('click', (e) => {
1961
+ if (headerCosmosMenu && !headerCosmosMenu.contains(e.target as Node)) {
1962
+ headerCosmosMenu.classList.add('hidden');
1963
+ }
1964
+ });
1965
+
1966
+ btnSideNewCosmos?.addEventListener('click', () => {
1967
+ this.switchToSpacesMode();
1968
+ this.cosmosNavigatorInstance?.triggerBigBangSequence(async () => {
1969
+ const nextIdx = (this.cosmosNavigatorInstance?.getCosmoses().length || 0) + 1;
1970
+ const newCosmos = await this.cosmosNavigatorInstance?.createCosmos(`cosmos-${nextIdx}`);
1971
+ if (newCosmos) {
1972
+ this.cosmosNavigatorInstance?.selectCosmos(newCosmos.id);
1973
+ }
1974
+ });
1975
+ });
1976
+
1977
+ const btnSideAddSpace = document.getElementById('btn-side-add-space');
1978
+ btnSideAddSpace?.addEventListener('click', () => {
1979
+ this.cosmosNavigatorInstance?.handleSpaceClick('code');
1980
+ });
1981
+ }
1982
+
1983
+ private updateActiveCosmosDisplay(cosmos: CosmosInfo, activeSpaceIdOrType?: string): void {
1984
+ const headerName = document.getElementById('header-active-cosmos-name');
1985
+ if (headerName) { headerName.textContent = cosmos.name; }
1986
+
1987
+ const headerBadge = document.getElementById('header-temp-badge');
1988
+ if (headerBadge) {
1989
+ headerBadge.style.display = cosmos.isTemporary ? 'inline-flex' : 'none';
1990
+ }
1991
+
1992
+ const projectName = document.getElementById('cosmos-project-name');
1993
+ if (projectName) { projectName.textContent = cosmos.name; }
1994
+
1995
+ const spacesCount = document.getElementById('cosmos-spaces-count');
1996
+ if (spacesCount) { spacesCount.textContent = String(cosmos.spaces.length); }
1997
+
1998
+ const spacesList = document.getElementById('cosmos-spaces-list');
1999
+ if (spacesList) {
2000
+ if (cosmos.spaces.length === 0) {
2001
+ spacesList.innerHTML = `<p class="empty-list-note">No spaces added yet.<br>Click Code or Text in Cosmos to attach a space.</p>`;
2002
+ } else {
2003
+ spacesList.innerHTML = cosmos.spaces.map(s => `
2004
+ <div class="cosmos-space-item" data-space-id="${s.id}" data-space-type="${s.type}">
2005
+ <span class="space-item-icon">${s.type === 'code' ? '&lt;/&gt;' : (s.icon || '📦')}</span>
2006
+ <div class="space-item-info">
2007
+ <span class="space-item-name">${s.name}</span>
2008
+ <span class="space-item-sub">${s.type === 'code' ? 'Code Repository' : 'Metatext Discussion'}</span>
2009
+ </div>
2010
+ </div>
2011
+ `).join('');
2012
+
2013
+ spacesList.querySelectorAll('.cosmos-space-item').forEach(el => {
2014
+ el.addEventListener('click', () => {
2015
+ const type = el.getAttribute('data-space-type');
2016
+ const spaceId = el.getAttribute('data-space-id') || '';
2017
+ const name = el.querySelector('.space-item-name')?.textContent || '';
2018
+ if (type === 'code') {
2019
+ this.showLeftPanelView('code', name);
2020
+ this.switchToReposMode({ galaxyId: spaceId });
2021
+ } else {
2022
+ this.showLeftPanelView('text', name);
2023
+ this.switchToNotesMode(name, { galaxyId: spaceId });
2024
+ }
2025
+ });
2026
+ });
2027
+ }
2028
+ }
2029
+
2030
+ const activitySpaces = document.getElementById('activity-spaces-container');
2031
+ if (activitySpaces) {
2032
+ activitySpaces.innerHTML = cosmos.spaces.map(s => `
2033
+ <button class="activity-bar-btn activity-space-btn" type="button" title="${s.name}" data-space-id="${s.id}" data-space-type="${s.type}">
2034
+ <span class="activity-icon">${s.type === 'code' ? '&lt;/&gt;' : (s.icon || '📦')}</span>
2035
+ <span class="activity-label">${s.shortLabel}</span>
2036
+ </button>
2037
+ `).join('');
2038
+
2039
+ activitySpaces.querySelectorAll('.activity-space-btn').forEach(btn => {
2040
+ btn.addEventListener('click', () => {
2041
+ const type = btn.getAttribute('data-space-type');
2042
+ const title = btn.getAttribute('title') || '';
2043
+ const spaceId = btn.getAttribute('data-space-id') || '';
2044
+ this.setActiveActivityBarButton('space', spaceId);
2045
+
2046
+ if (type === 'code') {
2047
+ this.showLeftPanelView('code', title);
2048
+ this.switchToReposMode({ galaxyId: spaceId });
2049
+ } else {
2050
+ this.showLeftPanelView('text', title);
2051
+ this.switchToNotesMode(title, { galaxyId: spaceId });
2052
+ }
2053
+ });
2054
+ });
2055
+ }
2056
+
2057
+ if (activeSpaceIdOrType) {
2058
+ this.setActiveActivityBarButton('space', activeSpaceIdOrType);
2059
+ } else {
2060
+ this.setActiveActivityBarButton('cosmos');
2061
+ }
2062
+
2063
+ if (cosmos.spaces.length > 0) {
2064
+ this.expandActivityBar();
2065
+ }
2066
+
2067
+ const allCosmoses = this.cosmosNavigatorInstance?.getCosmoses() || [];
2068
+ this.updateHeaderCosmosMenu(allCosmoses, cosmos.id);
2069
+ this.updatePlaygroundBannerVisibility();
2070
+ }
2071
+
2072
+ public updatePlaygroundBannerVisibility(): void {
2073
+ const banner = document.getElementById('playground-banner');
2074
+ if (!banner) { return; }
2075
+
2076
+ if (this.hasActiveIdentity()) {
2077
+ banner.classList.add('hidden');
2078
+ return;
2079
+ }
2080
+
2081
+ // On bare landing before the big bang explosion, do not show the banner
2082
+ const currentRoute = getSpaceGibRouterSvc().getCurrentRoute();
2083
+ const isBareLanding = !currentRoute || currentRoute.kind === 'polycosmos';
2084
+ if (isBareLanding && !this.hasBigBangExploded) {
2085
+ banner.classList.add('hidden');
2086
+ return;
2087
+ }
2088
+
2089
+ // In playground mode (no authenticated identity), the banner is ALWAYS visible
2090
+ banner.classList.remove('hidden');
2091
+ }
2092
+
2093
+ private updatePolycosmosDisplay(cosmoses: CosmosInfo[], activeId: string | null): void {
2094
+ const polyCount = document.getElementById('polycosmos-total-count');
2095
+ if (polyCount) { polyCount.textContent = String(cosmoses.length); }
2096
+
2097
+ this.updatePolycosmosList(cosmoses, activeId);
2098
+ this.setActiveActivityBarButton('polycosmos');
2099
+ }
2100
+
2101
+ private updatePolycosmosList(cosmoses: CosmosInfo[], activeId: string | null): void {
2102
+ const listEl = document.getElementById('polycosmos-projects-list');
2103
+ if (!listEl) { return; }
2104
+
2105
+ if (cosmoses.length === 0) {
2106
+ listEl.innerHTML = `<p class="empty-list-note">No cosmos projects yet.<br>Click "Initiate Big Bang" in the center to create your first cosmos!</p>`;
2107
+ return;
2108
+ }
2109
+
2110
+ listEl.innerHTML = cosmoses.map(c => `
2111
+ <div class="cosmos-space-item ${c.id === activeId ? 'active' : ''}" data-cosmos-id="${c.id}">
2112
+ <span class="space-item-icon">🌌</span>
2113
+ <div class="space-item-info">
2114
+ <span class="space-item-name">${c.name}</span>
2115
+ <span class="space-item-sub">${c.spaces.length} space${c.spaces.length === 1 ? '' : 's'}</span>
2116
+ </div>
2117
+ </div>
2118
+ `).join('');
2119
+
2120
+ listEl.querySelectorAll('.cosmos-space-item').forEach(el => {
2121
+ el.addEventListener('click', () => {
2122
+ const id = el.getAttribute('data-cosmos-id');
2123
+ if (id) {
2124
+ this.cosmosNavigatorInstance?.selectCosmos(id);
2125
+ }
2126
+ });
2127
+ });
2128
+ }
2129
+
2130
+ private updateHeaderCosmosMenu(cosmoses: CosmosInfo[], activeId: string | null): void {
2131
+ const menu = document.getElementById('header-cosmos-menu');
2132
+ if (!menu) { return; }
2133
+
2134
+ menu.innerHTML = `
2135
+ ${cosmoses.map(c => `
2136
+ <button type="button" class="header-menu-item ${c.id === activeId ? 'active' : ''}" data-cosmos-id="${c.id}">
2137
+ <span>🌌 ${c.name}</span>
2138
+ <span class="orbit-cosmos-badge" style="font-size:0.7rem; padding:1px 6px;">${c.spaces.length}</span>
2139
+ </button>
2140
+ `).join('')}
2141
+ <div class="header-menu-divider"></div>
2142
+ <button type="button" class="header-menu-item" id="btn-header-menu-new-cosmos">
2143
+ <span>+ New Cosmos</span>
2144
+ </button>
2145
+ `;
2146
+
2147
+ menu.querySelectorAll('.header-menu-item[data-cosmos-id]').forEach(btn => {
2148
+ btn.addEventListener('click', () => {
2149
+ const id = btn.getAttribute('data-cosmos-id');
2150
+ if (id) {
2151
+ menu.classList.add('hidden');
2152
+ this.switchToSpacesMode();
2153
+ this.cosmosNavigatorInstance?.selectCosmos(id);
2154
+ }
2155
+ });
2156
+ });
2157
+
2158
+ const btnNew = menu.querySelector('#btn-header-menu-new-cosmos');
2159
+ btnNew?.addEventListener('click', async () => {
2160
+ menu.classList.add('hidden');
2161
+ this.switchToSpacesMode();
2162
+ this.cosmosNavigatorInstance?.triggerBigBangExplosion();
2163
+ const nextIdx = cosmoses.length + 1;
2164
+ const newC = await this.cosmosNavigatorInstance?.createCosmos(`cosmos-${nextIdx}`);
2165
+ if (newC) {
2166
+ setTimeout(() => {
2167
+ this.cosmosNavigatorInstance?.selectCosmos(newC.id);
2168
+ }, 300);
2169
+ }
2170
+ });
2171
+ }
2172
+
2173
+ private showLeftPanelView(viewName: 'polycosmos' | 'cosmos' | 'code' | 'text', title?: string): void {
2174
+ const viewPoly = document.getElementById('left-panel-polycosmos-view');
2175
+ const viewCosmos = document.getElementById('left-panel-cosmos-view');
2176
+ const viewCode = document.getElementById('left-panel-code-view');
2177
+ const viewText = document.getElementById('left-panel-text-view');
2178
+
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
+ }
2187
+ if (viewCode) {
2188
+ viewCode.style.display = viewName === 'code' ? 'flex' : 'none';
2189
+ viewCode.style.flexDirection = 'column';
2190
+ if (title) {
2191
+ const titleEl = document.getElementById('left-code-space-title');
2192
+ if (titleEl) { titleEl.textContent = title; }
2193
+ }
2194
+ }
2195
+ if (viewText) {
2196
+ viewText.style.display = viewName === 'text' ? 'flex' : 'none';
2197
+ viewText.style.flexDirection = 'column';
2198
+ if (viewName === 'text') {
2199
+ this.initNotesExplorer(title);
2200
+ }
2201
+ }
2202
+ }
2203
+
2204
+ private expandActivityBar(): void {
2205
+ const bar = document.getElementById('activity-bar');
2206
+ if (bar) {
2207
+ bar.classList.remove('collapsed');
2208
+ bar.classList.add('expanded');
2209
+ }
2210
+ }
2211
+
2212
+ private expandLeftPanel(): void {
2213
+ const panel = document.getElementById(ID_LEFT_PANEL);
2214
+ if (panel) {
2215
+ panel.classList.remove('collapsed');
2216
+ this.leftPanelState = 'expanded';
2217
+ }
2218
+ }
2219
+
2220
+ public expandRightPanel(): void {
2221
+ const panel = document.getElementById(ID_RIGHT_PANEL);
2222
+ if (panel) {
2223
+ panel.classList.remove('collapsed');
2224
+ this.rightPanelState = 'expanded';
2225
+ }
2226
+ }
2227
+
2228
+ private switchToSpacesMode(): void {
2229
+ this.setMode('spaces');
2230
+ }
2231
+
2232
+ private switchToReposMode(opts?: { skipNavigate?: boolean; galaxyId?: string }): void {
2233
+ this.setMode('repos');
2234
+ this.expandRightPanel();
2235
+ if (!opts?.skipNavigate && !this.isApplyingRoute) {
2236
+ const activeCosmos = this.cosmosNavigatorInstance?.getActiveCosmos();
2237
+ if (activeCosmos) {
2238
+ const tenant = this.getActiveTenant(activeCosmos);
2239
+ getSpaceGibRouterSvc().navigate({
2240
+ kind: 'galaxy',
2241
+ cosmos: activeCosmos.id || activeCosmos.name,
2242
+ galaxy: opts?.galaxyId || 'code-1',
2243
+ topology: 'branching',
2244
+ discriminator: 'code',
2245
+ branch: 'main',
2246
+ ...(tenant ? { tenant } : {}),
2247
+ });
2248
+ }
2249
+ }
2250
+ }
2251
+
2252
+ private switchToNotesMode(title?: string, opts?: { skipNavigate?: boolean; galaxyId?: string }): void {
2253
+ this.setMode('notes', title);
2254
+ this.expandRightPanel();
2255
+ if (!opts?.skipNavigate && !this.isApplyingRoute) {
2256
+ const activeCosmos = this.cosmosNavigatorInstance?.getActiveCosmos();
2257
+ if (activeCosmos) {
2258
+ const tenant = this.getActiveTenant(activeCosmos);
2259
+ getSpaceGibRouterSvc().navigate({
2260
+ kind: 'galaxy',
2261
+ cosmos: activeCosmos.id || activeCosmos.name,
2262
+ galaxy: opts?.galaxyId || 'notes-1',
2263
+ topology: 'linear',
2264
+ discriminator: 'notes',
2265
+ ...(title ? { topicTjp: title, trail: [title], trailMode: 'tjp' } : {}),
2266
+ ...(tenant ? { tenant } : {}),
2267
+ });
2268
+ }
2269
+ }
2270
+ }
2271
+
2272
+ public getActiveTenant(cosmos?: CosmosInfo | string): SpaceGibTenant | undefined {
2273
+ const domainIdentity = (globalThis as any).ibgib?.identity?.domainIdentity;
2274
+ if (domainIdentity) {
2275
+ const siteUsername = domainIdentity.data?.siteUsername || domainIdentity.data?.name || domainIdentity.data?.username;
2276
+ if (siteUsername) {
2277
+ return { type: 'username', siteUsername };
2278
+ }
2279
+ const tjpAddr = domainIdentity.data?.tjp?.tjpAddr || domainIdentity.data?.tjpGib;
2280
+ if (tjpAddr) {
2281
+ return { type: 'keystone', keystoneTjp: tjpAddr };
2282
+ }
2283
+ }
2284
+ // Without an active authenticated identity, all workspaces and routes are playground-scoped
2285
+ return { type: 'username', siteUsername: 'playground' };
2286
+ }
2287
+
2288
+ private async initRouterIntegration(): Promise<void> {
2289
+ const lc = `${this.lc}[${this.initRouterIntegration.name}]`;
2290
+ try {
2291
+ const router = getSpaceGibRouterSvc();
2292
+
2293
+ // 1. Subscribe to route changes (popstate, direct navigation)
2294
+ router.subscribe(async (route) => {
2295
+ await this.handleRouteActivation(route);
2296
+ });
2297
+
2298
+ // 2. Load current URL from address bar
2299
+ const initialRoute = await router.loadCurrentURL();
2300
+ console.log(`${lc} Initial route activated:`, initialRoute);
2301
+ } catch (err) {
2302
+ console.error(`${lc} Failed to initialize router integration: ${extractErrorMsg(err)}`);
2303
+ }
2304
+ }
2305
+
2306
+ public async handleRouteActivation(route: SpaceGibRouteInfo): Promise<void> {
2307
+ const lc = `${this.lc}[${this.handleRouteActivation.name}]`;
2308
+ if (this.isApplyingRoute) { return; }
2309
+ this.isApplyingRoute = true;
2310
+ try {
2311
+ if (route.kind !== 'polycosmos') {
2312
+ this.hasBigBangExploded = true;
2313
+ if (this.cosmosNavigatorInstance) {
2314
+ this.cosmosNavigatorInstance.hasBigBangExploded = true;
2315
+ }
2316
+ this.revealChrome();
2317
+ }
2318
+
2319
+ // If the route omitted a tenant specifier but the user is authenticated (or in playground),
2320
+ // canonicalize the URL in the address bar so copying and sharing is always fully qualified.
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) {
2324
+ const cosmosName = 'cosmos' in route ? route.cosmos : undefined;
2325
+ const activeTenant = this.getActiveTenant(cosmosName);
2326
+ if (activeTenant) {
2327
+ route.tenant = activeTenant;
2328
+ getSpaceGibRouterSvc().navigate(route, { replace: true, silent: true });
2329
+ }
2330
+ }
2331
+
2332
+ switch (route.kind) {
2333
+ case 'polycosmos': {
2334
+ await this.showCosmos();
2335
+ this.cosmosNavigatorInstance?.setZoomState('polycosmos');
2336
+ break;
2337
+ }
2338
+ case 'cosmos': {
2339
+ if (this.cosmosNavigatorInstance) {
2340
+ const cosmos = await this.cosmosNavigatorInstance.findCosmos(route.cosmos);
2341
+ if (!cosmos) {
2342
+ this.show404(`Cosmos "${route.cosmos}" not found.`);
2343
+ return;
2344
+ }
2345
+ this.cosmosNavigatorInstance.selectCosmos(cosmos.id);
2346
+ this.updateActiveCosmosDisplay(cosmos);
2347
+ }
2348
+ this.showLeftPanelView('cosmos');
2349
+ this.expandLeftPanel();
2350
+ await this.showCosmos();
2351
+ break;
2352
+ }
2353
+ case 'galaxy': {
2354
+ let cosmosInfo: CosmosInfo | undefined;
2355
+ if (this.cosmosNavigatorInstance) {
2356
+ cosmosInfo = await this.cosmosNavigatorInstance.findCosmos(route.cosmos);
2357
+ if (!cosmosInfo) {
2358
+ this.show404(`Cosmos "${route.cosmos}" not found.`);
2359
+ return;
2360
+ }
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' ? '</>' : '📝',
2369
+ });
2370
+ }
2371
+ this.cosmosNavigatorInstance.selectCosmos(cosmosInfo.id);
2372
+ this.updateActiveCosmosDisplay(cosmosInfo, route.galaxy || spaceType);
2373
+ }
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);
2393
+ break;
2394
+ }
2395
+ case 'raw-addr': {
2396
+ await this.handleRawAddrSnapshotRoute(route);
2397
+ break;
2398
+ }
2399
+ case 'raw-tjp': {
2400
+ await this.handleRawTjpRoute(route);
2401
+ break;
2402
+ }
2403
+ case 'admin-review': {
2404
+ await this.showAdminReview(route.tjpGib);
2405
+ break;
2406
+ }
2407
+ }
2408
+ } catch (err) {
2409
+ console.error(`${lc} Failed to apply route: ${extractErrorMsg(err)}`);
2410
+ } finally {
2411
+ this.isApplyingRoute = false;
2412
+ this.updatePlaygroundBannerVisibility();
2413
+ }
2414
+ }
2415
+
2416
+ private async handleRawAddrSnapshotRoute(route: RawAddrRouteInfo): Promise<void> {
2417
+ const lc = `${this.lc}[${this.handleRawAddrSnapshotRoute.name}]`;
2418
+ try {
2419
+ console.log(`${lc} Resolving raw snapshot route: cosmos=${route.cosmos}, spaceId=${route.spaceId}, addr=${route.ibGibAddr}`);
2420
+ if (this.cosmosNavigatorInstance) {
2421
+ const cosmos = await this.cosmosNavigatorInstance.findCosmos(route.cosmos);
2422
+ if (!cosmos) {
2423
+ this.show404(`Cosmos "${route.cosmos}" not found.`);
2424
+ return;
2425
+ }
2426
+ this.cosmosNavigatorInstance.selectCosmos(cosmos.id);
2427
+ this.updateActiveCosmosDisplay(cosmos);
2428
+ }
2429
+
2430
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
2431
+ let space = (metaspace as any).localUserSpaces?.find((s: any) => s.data?.uuid === route.spaceId || s.data?.name === route.spaceId);
2432
+ if (!space) {
2433
+ space = (metaspace as any).spaces?.find((s: any) => s.data?.uuid === route.spaceId || s.data?.name === route.spaceId);
2434
+ }
2435
+
2436
+ let targetIbGib: any;
2437
+ if (space) {
2438
+ const getRes = await space.get({ addr: route.ibGibAddr });
2439
+ targetIbGib = getRes?.ibGibs?.[0];
2440
+ }
2441
+
2442
+ if (!targetIbGib) {
2443
+ try {
2444
+ const domainAddr = route.tenant?.type === 'username' ? route.tenant.siteUsername : route.cosmos;
2445
+ const res = await fetch(`/api/ibgib/${encodeURIComponent(domainAddr)}/${encodeURIComponent(route.ibGibAddr)}`);
2446
+ if (res.ok) {
2447
+ const payload = await res.json();
2448
+ targetIbGib = payload?.ibGib;
2449
+ }
2450
+ } catch (netErr) {
2451
+ console.warn(`${lc} Server fetch for raw addr failed: ${extractErrorMsg(netErr)}`);
2452
+ }
2453
+ }
2454
+
2455
+ await this.showRawSnapshot(route.ibGibAddr, targetIbGib);
2456
+ } catch (err) {
2457
+ console.error(`${lc} ${extractErrorMsg(err)}`);
2458
+ }
2459
+ }
2460
+
2461
+ private async handleRawTjpRoute(route: RawTjpRouteInfo): Promise<void> {
2462
+ const lc = `${this.lc}[${this.handleRawTjpRoute.name}]`;
2463
+ try {
2464
+ console.log(`${lc} Resolving living tip route: cosmos=${route.cosmos}, spaceId=${route.spaceId}, tjp=${route.tjpAddr}`);
2465
+ if (this.cosmosNavigatorInstance) {
2466
+ const cosmos = await this.cosmosNavigatorInstance.findCosmos(route.cosmos);
2467
+ if (!cosmos) {
2468
+ this.show404(`Cosmos "${route.cosmos}" not found.`);
2469
+ return;
2470
+ }
2471
+ this.cosmosNavigatorInstance.selectCosmos(cosmos.id);
2472
+ this.updateActiveCosmosDisplay(cosmos);
2473
+ }
2474
+
2475
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
2476
+ let space = (metaspace as any).localUserSpaces?.find((s: any) => s.data?.uuid === route.spaceId || s.data?.name === route.spaceId);
2477
+ if (!space) {
2478
+ space = (metaspace as any).spaces?.find((s: any) => s.data?.uuid === route.spaceId || s.data?.name === route.spaceId);
2479
+ }
2480
+
2481
+ let tipAddr = route.tjpAddr;
2482
+ let targetIbGib: any;
2483
+ if (space && typeof (space as any).getLatestIbGib === 'function') {
2484
+ const latest = await (space as any).getLatestIbGib({ tjpAddr: route.tjpAddr });
2485
+ if (latest) {
2486
+ targetIbGib = latest;
2487
+ tipAddr = (latest as any).ibGibAddr || getIbGibAddr({ ibGib: latest }) || tipAddr;
2488
+ }
2489
+ }
2490
+
2491
+ if (!targetIbGib) {
2492
+ try {
2493
+ const domainAddr = route.tenant?.type === 'username' ? route.tenant.siteUsername : route.cosmos;
2494
+ const res = await fetch(`/api/ibgib/${encodeURIComponent(domainAddr)}/${encodeURIComponent(route.tjpAddr)}`);
2495
+ if (res.ok) {
2496
+ const payload = await res.json();
2497
+ targetIbGib = payload?.ibGib;
2498
+ if (targetIbGib) {
2499
+ tipAddr = (targetIbGib as any).ibGibAddr || getIbGibAddr({ ibGib: targetIbGib }) || tipAddr;
2500
+ }
2501
+ }
2502
+ } catch (netErr) {
2503
+ console.warn(`${lc} Server fetch for raw tjp failed: ${extractErrorMsg(netErr)}`);
2504
+ }
2505
+ }
2506
+
2507
+ await this.showRawSnapshot(tipAddr, targetIbGib, { isLivingTip: true, tjpAddr: route.tjpAddr });
2508
+ } catch (err) {
2509
+ console.error(`${lc} ${extractErrorMsg(err)}`);
2510
+ }
2511
+ }
2512
+
2513
+ public async showRawSnapshot(
2514
+ addr: string,
2515
+ ibGib?: any,
2516
+ opts?: { isLivingTip?: boolean; tjpAddr?: string }
2517
+ ): Promise<void> {
2518
+ const lc = `${this.lc}[${this.showRawSnapshot.name}]`;
2519
+ try {
2520
+ const centerPanel = document.getElementById(ID_CENTER_PANEL_CONTENT);
2521
+ if (!centerPanel) { return; }
2522
+
2523
+ if (!this.timelineItemDetailsInstance) {
2524
+ const componentSvc = await getComponentSvc();
2525
+ this.timelineItemDetailsInstance = await componentSvc.getComponentInstance({
2526
+ path: TIMELINE_ITEM_DETAILS_COMPONENT_NAME,
2527
+ ibGibAddr: ROOT_ADDR,
2528
+ useRegExpPrefilter: true,
2529
+ }) as TimelineItemDetailsComponentInstance;
2530
+ }
2531
+
2532
+ if (this.timelineItemDetailsInstance) {
2533
+ const componentSvc = await getComponentSvc();
2534
+ await componentSvc.inject({
2535
+ parentEl: centerPanel,
2536
+ componentToInject: this.timelineItemDetailsInstance,
2537
+ });
2538
+
2539
+ const shortHash = addr.slice(0, 10);
2540
+ const isLiving = opts?.isLivingTip;
2541
+ const message = isLiving
2542
+ ? `🌱 Living Tip: ${addr.split('^')[0] || addr}`
2543
+ : `📌 Snapshot (Pinned): ${addr.split('^')[0] || addr}`;
2544
+ const timestamp = isLiving ? 'Latest tip' : 'Pinned point-in-time';
2545
+ const branch = isLiving ? 'living-tip' : 'snapshot';
2546
+
2547
+ let rawData: string;
2548
+ if (ibGib?.data) {
2549
+ if (typeof ibGib.data.text === 'string') {
2550
+ rawData = ibGib.data.text;
2551
+ } else if (typeof ibGib.data.content === 'string') {
2552
+ rawData = ibGib.data.content;
2553
+ } else {
2554
+ rawData = JSON.stringify(ibGib.data, null, 2);
2555
+ }
2556
+ } else if (ibGib) {
2557
+ rawData = JSON.stringify(ibGib, null, 2);
2558
+ } else {
2559
+ rawData = `Address: ${addr}\n(Content not loaded)`;
2560
+ }
2561
+
2562
+ this.timelineItemDetailsInstance.setTimelineItem({
2563
+ hash: addr,
2564
+ shortHash: shortHash,
2565
+ message,
2566
+ author: ibGib?.rel8ns?.ancestor?.[0] || (isLiving ? 'ibgib living tip' : 'ibgib snapshot'),
2567
+ timestamp,
2568
+ branch,
2569
+ diffPreview: rawData,
2570
+ changedFiles: [
2571
+ { path: addr, status: 'A', additions: 1, deletions: 0 }
2572
+ ]
2573
+ });
2574
+ }
2575
+ } catch (error) {
2576
+ console.error(`${lc} ${extractErrorMsg(error)}`);
2577
+ }
2578
+ }
2579
+
2580
+ public hasActiveIdentity(): boolean {
2581
+ return Boolean((globalThis as any).ibgib?.identity?.domainIdentity);
2582
+ }
2583
+
2584
+ public async onEngineReady(): Promise<void> {
2585
+ const lc = `${this.lc}[${this.onEngineReady.name}]`;
2586
+ try {
2587
+ await this.initialized;
2588
+ console.log(`${lc} SpaceGib Shell Service is ready.`);
2589
+
2590
+ // Ensure the UI removes the loading text
2591
+ const statusSection = document.getElementById('status-section');
2592
+ if (statusSection) {
2593
+ statusSection.style.display = 'none';
2594
+ }
2595
+
2596
+ // Mount identity-header component in the header bar
2597
+ const componentSvc = await getComponentSvc();
2598
+ const identityHeader = await componentSvc.getComponentInstance({
2599
+ path: 'ibgib-identity-header',
2600
+ ibGibAddr: ROOT_ADDR,
2601
+ useRegExpPrefilter: true,
2602
+ });
2603
+
2604
+ const headerContainer = document.getElementById('identity-header-container');
2605
+ if (headerContainer && identityHeader) {
2606
+ headerContainer.innerHTML = '';
2607
+ headerContainer.appendChild(identityHeader as any);
2608
+ if (this.hasActiveIdentity()) {
2609
+ headerContainer.classList.remove('identity-active-highlight');
2610
+ }
2611
+ }
2612
+
2613
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_SHELL_READY, { detail: { timestamp: Date.now() } }));
2614
+
2615
+ // Connect HTML5 History API router and activate current URL
2616
+ await this.initRouterIntegration();
2617
+
2618
+ // Update playground mode banner visibility
2619
+ this.updatePlaygroundBannerVisibility();
2620
+
2621
+ // Pre-warm in-memory temporary metaspace in background for snappy tutorial/playground mode
2622
+ prewarmPlaygroundMetaspace().catch(err => {
2623
+ console.warn(`${lc} Background prewarm of temporary metaspace failed: ${extractErrorMsg(err)}`);
2624
+ });
2625
+
2626
+ // Trigger 1 (App Boot): On onEngineReady, if active identity exists, check for updates.
361
2627
 
362
- // Trigger 1 (App Boot): On onEngineReady, if active identity exists, check for updates.
363
2628
  const domainIdentity = (globalThis as any).ibgib?.identity?.domainIdentity;
364
2629
  if (domainIdentity) {
365
2630
  const activePrimaryAddr = getIbGibAddr({ ibGib: domainIdentity });