@ibgib/space-gib 0.0.34 β†’ 0.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/client/bootstrap.mjs +1 -1
  2. package/dist/client/{chunk-25PXC4HP.mjs β†’ chunk-FIAGRVBY.mjs} +1487 -468
  3. package/dist/client/chunk-XLQ3UCQY.mjs +53 -0
  4. package/dist/client/css/activity-bar.css +21 -3
  5. package/dist/client/css/layout.css +126 -5
  6. package/dist/client/index.html +6 -0
  7. package/dist/client/index.mjs +22 -45
  8. package/dist/client/privacy.html +22 -22
  9. package/dist/client/script.mjs +1 -1
  10. package/dist/server/server.mjs +3 -3
  11. package/package.json +6 -6
  12. package/src/client/AUTO-GENERATED-version.mts +1 -1
  13. package/src/client/cosmos/seed-cosmos-workspace.mts +20 -6
  14. package/src/client/css/activity-bar.css +21 -3
  15. package/src/client/css/layout.css +126 -5
  16. package/src/client/dev-tools/vcs-workspace.mts +4 -4
  17. package/src/client/index.html +6 -0
  18. package/src/client/privacy.html +22 -22
  19. package/src/client/ui/component/changes/changes.css +413 -0
  20. package/src/client/ui/component/changes/changes.html +37 -0
  21. package/src/client/ui/component/changes/changes.mts +766 -0
  22. package/src/client/ui/component/changes/index.mts +9 -0
  23. package/src/client/ui/component/chat-view/chat-view.html +6 -58
  24. package/src/client/ui/component/chat-view/chat-view.mts +13 -26
  25. package/src/client/ui/component/clone/clone.mts +67 -39
  26. package/src/client/ui/component/code-editor/code-editor.css +91 -9
  27. package/src/client/ui/component/code-editor/code-editor.html +58 -45
  28. package/src/client/ui/component/code-editor/code-editor.mts +265 -49
  29. package/src/client/ui/component/cosmos-navigator/cosmos-navigator.css +1 -1
  30. package/src/client/ui/component/cosmos-navigator/cosmos-navigator.html +5 -10
  31. package/src/client/ui/component/cosmos-navigator/cosmos-navigator.mts +297 -64
  32. package/src/client/ui/component/file-explorer/file-explorer.css +475 -11
  33. package/src/client/ui/component/file-explorer/file-explorer.html +45 -65
  34. package/src/client/ui/component/file-explorer/file-explorer.mts +1164 -98
  35. package/src/client/ui/component/nested-chat/chat/chat.mts +5 -90
  36. package/src/client/ui/component/nested-chat/nested-chat.mts +10 -8
  37. package/src/client/ui/component/timeline/timeline.css +16 -1
  38. package/src/client/ui/component/timeline/timeline.mts +37 -93
  39. package/src/client/ui/router/space-gib-router-helpers.mts +33 -233
  40. package/src/client/ui/router/space-gib-router-helpers.respec.mts +298 -122
  41. package/src/client/ui/router/space-gib-router-service.mts +28 -151
  42. package/src/client/ui/router/space-gib-router-types.mts +32 -76
  43. package/src/client/ui/shell/cosmic-big-bang.mts +12 -3
  44. package/src/client/ui/shell/space-gib-shell-service.mts +687 -119
  45. package/dist/client/chunk-RXWLL2AI.mjs +0 -30
@@ -0,0 +1,9 @@
1
+ import { getComponentSvc } from "@ibgib/web-gib/dist/ui/component/ibgib-component-service.mjs";
2
+ import { ChangesComponentMeta, CHANGES_COMPONENT_NAME } from "./changes.mjs";
3
+
4
+ export * from "./changes.mjs";
5
+
6
+ export async function registerChangesComponent(): Promise<void> {
7
+ const componentSvc = await getComponentSvc();
8
+ componentSvc.registerComponentMeta(new ChangesComponentMeta());
9
+ }
@@ -5,7 +5,7 @@
5
5
  <span class="chat-header-icon">πŸ’¬</span>
6
6
  <div class="chat-title-group">
7
7
  <h3 class="chat-title">Discussions</h3>
8
- <span class="chat-target-badge" id="chat-target-file">src/index.ts</span>
8
+ <span class="chat-target-badge" id="chat-target-file">No file selected</span>
9
9
  </div>
10
10
  </div>
11
11
  <div class="chat-header-actions">
@@ -17,69 +17,17 @@
17
17
  <div class="chat-sub-bar">
18
18
  <div class="chat-context-item">
19
19
  <span class="context-icon">🌿</span>
20
- <span class="context-text" id="chat-branch-tip">main @ 7f8a1b2</span>
20
+ <span class="context-text" id="chat-branch-tip"></span>
21
21
  </div>
22
22
  <div class="chat-filter-item">
23
- <span class="filter-text">2 Notes</span>
23
+ <span class="filter-text" id="chat-notes-count">0 Notes</span>
24
24
  </div>
25
25
  </div>
26
26
 
27
27
  <!-- Messages Stream -->
28
28
  <div class="chat-messages" id="chat-messages">
29
- <!-- Message 1: Architectural Note -->
30
- <div class="chat-message in-band-note">
31
- <div class="msg-header">
32
- <div class="msg-author-group">
33
- <span class="msg-avatar">πŸ‘©β€πŸ’»</span>
34
- <span class="msg-author">Alice</span>
35
- <span class="msg-badge">Keystone</span>
36
- </div>
37
- <span class="msg-time">2m ago</span>
38
- </div>
39
- <div class="msg-anchor">
40
- <span class="anchor-icon">πŸ”—</span>
41
- <span class="anchor-label">src/index.ts#L1-18</span>
42
- <span class="anchor-tag">Inverted Rel8n</span>
43
- </div>
44
- <div class="msg-body">
45
- <p class="msg-lead"><strong>Architectural Rationale</strong></p>
46
- <p>This entrypoint initializes the virtual repository workspace within the browser’s local IndexedDB metaspace.
47
- </p>
48
- <div class="msg-callout">
49
- <span class="callout-emoji">πŸ’‘</span>
50
- <div class="callout-text">
51
- <strong>Why Living Comments?</strong> Unlike centralized SaaS review comments, this discussion node is an
52
- in-band Merkle ibgib traveling directly with the branch DAG.
53
- </div>
54
- </div>
55
- </div>
56
- <div class="msg-footer">
57
- <button class="msg-action-btn" type="button">Reply</button>
58
- <button class="msg-action-btn" type="button">πŸ‘ 1</button>
59
- </div>
60
- </div>
61
-
62
- <!-- Message 2: Code Review Discussion -->
63
- <div class="chat-message peer-comment">
64
- <div class="msg-header">
65
- <div class="msg-author-group">
66
- <span class="msg-avatar">πŸ‘¨β€πŸš€</span>
67
- <span class="msg-author">Bob</span>
68
- <span class="msg-badge">Peer</span>
69
- </div>
70
- <span class="msg-time">1m ago</span>
71
- </div>
72
- <div class="msg-anchor">
73
- <span class="anchor-icon">πŸ”—</span>
74
- <span class="anchor-label">src/index.ts#L36-42</span>
75
- </div>
76
- <div class="msg-body">
77
- <p>Looks super clean! Should we add an async try/catch boundary around <code>boot()</code> as well?</p>
78
- </div>
79
- <div class="msg-footer">
80
- <button class="msg-action-btn" type="button">Reply</button>
81
- <button class="msg-action-btn" type="button">πŸ‘</button>
82
- </div>
29
+ <div class="empty-chat-msg" style="padding: 24px 16px; font-size: 12px; color: #8a99a8; text-align: center; font-style: italic;">
30
+ No discussions yet for this context.
83
31
  </div>
84
32
  </div>
85
33
 
@@ -91,7 +39,7 @@
91
39
  <div class="compose-tools-left">
92
40
  <button id="btn-chat-pin" class="compose-tool-btn active" type="button" title="Pin to current line selection">
93
41
  <span class="tool-icon">πŸ”—</span>
94
- <span class="tool-label">src/index.ts</span>
42
+ <span class="tool-label" id="chat-pin-label">No target</span>
95
43
  </button>
96
44
  </div>
97
45
  <button id="btn-chat-send" class="compose-send-btn" type="button">
@@ -85,8 +85,8 @@ export class ChatViewComponentInstance
85
85
 
86
86
  protected lc: string = `[${ChatViewComponentInstance.name}]`;
87
87
 
88
- private targetFile: string = 'src/index.ts';
89
- private branchTip: string = 'main @ 7f8a1b2';
88
+ private targetFile: string = '';
89
+ private branchTip: string = '';
90
90
 
91
91
  get el(): ChatViewElements {
92
92
  if (!this.elements) {
@@ -95,29 +95,7 @@ export class ChatViewComponentInstance
95
95
  return this.elements;
96
96
  }
97
97
 
98
- private messages: ChatMessageItem[] = [
99
- {
100
- id: 'msg-1',
101
- author: 'Alice',
102
- avatar: 'πŸ‘©β€πŸ’»',
103
- role: 'Keystone',
104
- timeAgo: '2m ago',
105
- anchor: 'src/index.ts#L1-18',
106
- text: 'Architectural Rationale: This entrypoint initializes the virtual repository workspace within the browser’s local IndexedDB metaspace.',
107
- callout: 'Why Living Comments? Unlike centralized SaaS review comments, this discussion node is an in-band Merkle ibgib traveling directly with the branch DAG.',
108
- isInBandNote: true,
109
- },
110
- {
111
- id: 'msg-2',
112
- author: 'Bob',
113
- avatar: 'πŸ‘¨β€πŸš€',
114
- role: 'Peer',
115
- timeAgo: '1m ago',
116
- anchor: 'src/index.ts#L36-42',
117
- text: 'Looks super clean! Should we add an async try/catch boundary around boot() as well?',
118
- isInBandNote: false,
119
- }
120
- ];
98
+ private messages: ChatMessageItem[] = [];
121
99
 
122
100
  override async created(): Promise<void> {
123
101
  const lc = `${this.lc}[${this.created.name}]`;
@@ -165,7 +143,7 @@ export class ChatViewComponentInstance
165
143
 
166
144
  this.el.btnPinEl?.addEventListener('click', () => {
167
145
  const currentPin = this.el?.btnPinEl?.querySelector('.tool-label')?.textContent || '';
168
- const newPin = prompt('Pin comment to code reference (e.g. src/index.ts#L10-25):', currentPin);
146
+ const newPin = prompt('Pin comment to code reference (e.g. file.ts#L10-25):', currentPin);
169
147
  if (newPin && this.el?.btnPinEl) {
170
148
  const label = this.el.btnPinEl.querySelector('.tool-label');
171
149
  if (label) { label.textContent = newPin; }
@@ -216,6 +194,15 @@ export class ChatViewComponentInstance
216
194
  private renderMessages(): void {
217
195
  if (!this.el?.messagesContainerEl) return;
218
196
 
197
+ if (this.messages.length === 0) {
198
+ this.el.messagesContainerEl.innerHTML = `
199
+ <div class="empty-chat-msg" style="padding: 24px 16px; font-size: 12px; color: #8a99a8; text-align: center; font-style: italic;">
200
+ No discussions yet for this context.
201
+ </div>
202
+ `;
203
+ return;
204
+ }
205
+
219
206
  this.el.messagesContainerEl.innerHTML = this.messages.map(msg => `
220
207
  <div class="chat-message ${msg.isInBandNote ? 'in-band-note' : 'peer-comment'}">
221
208
  <div class="msg-header">
@@ -6,6 +6,7 @@ import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
6
6
  import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
7
7
  import { extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
8
8
  import { promptForText } from "@ibgib/web-gib/dist/helpers.web.mjs";
9
+ import { validateBranchName } from "@ibgib/core-gib/dist/cosmology/branching/branch/branch-helpers.mjs";
9
10
  import {
10
11
  IbGibDynamicComponentMetaBase, IbGibDynamicComponentInstanceBase,
11
12
  } from "@ibgib/web-gib/dist/ui/component/ibgib-dynamic-component-bases.mjs";
@@ -417,28 +418,41 @@ export class CloneComponentInstance
417
418
  const btnBranchItem = this.el.singleItemContainerEl.querySelector('#btn-clone-modal-branch-item');
418
419
  btnBranchItem?.addEventListener('click', async () => {
419
420
  const filename = item.path.split('/').pop() || item.path;
420
- const defaultBranch = `feature/branch-${filename.replace(/[^a-zA-Z0-9]/g, '-')}`;
421
- const newBranchName = await promptForText({
422
- title: `Create Branch from Item`,
423
- msg: `Enter branch name isolating and diverging from item [${item.path}]:`,
424
- defaultValue: defaultBranch,
425
- cancelable: true,
426
- });
427
- if (newBranchName) {
428
- this.showStatus(`Branch created: ${newBranchName}`);
429
- window.dispatchEvent(new CustomEvent('space-gib-branch-requested', {
430
- detail: {
431
- galaxyName: item.galaxyName || 'Code Galaxy',
432
- sourceBranch: item.branchName || 'main',
433
- itemPath: item.path,
434
- isDir: item.isDir,
435
- newBranchName,
436
- scope: 'item',
437
- },
438
- bubbles: true,
439
- composed: true,
440
- }));
421
+ let candidateName = `feature-branch-${filename.replace(/[^a-zA-Z0-9_.-]/g, '-')}`;
422
+ let promptMsg = `Enter branch name isolating and diverging from item [${item.path}]:`;
423
+ let cleanBranchName = '';
424
+
425
+ while (true) {
426
+ const newBranchName = await promptForText({
427
+ title: `Create Branch from Item`,
428
+ msg: promptMsg,
429
+ defaultValue: candidateName,
430
+ cancelable: true,
431
+ });
432
+ if (!newBranchName || !newBranchName.trim()) return;
433
+ cleanBranchName = newBranchName.trim();
434
+ const validation = validateBranchName(cleanBranchName);
435
+ if (!validation.isValid) {
436
+ candidateName = cleanBranchName;
437
+ promptMsg = `Invalid branch name: ${validation.error}\n\nPlease enter a valid branch name:`;
438
+ continue;
439
+ }
440
+ break;
441
441
  }
442
+
443
+ this.showStatus(`Branch created: ${cleanBranchName}`);
444
+ window.dispatchEvent(new CustomEvent('space-gib-branch-requested', {
445
+ detail: {
446
+ galaxyName: item.galaxyName || 'Code Galaxy',
447
+ sourceBranch: item.branchName || 'main',
448
+ itemPath: item.path,
449
+ isDir: item.isDir,
450
+ newBranchName: cleanBranchName,
451
+ scope: 'item',
452
+ },
453
+ bubbles: true,
454
+ composed: true,
455
+ }));
442
456
  });
443
457
  }
444
458
 
@@ -484,25 +498,39 @@ export class CloneComponentInstance
484
498
  e.stopPropagation();
485
499
  const branchName = btn.getAttribute('data-branch') || 'main';
486
500
  const galaxyName = btn.getAttribute('data-galaxy') || 'Code Galaxy';
487
- const newBranchName = await promptForText({
488
- title: 'Create Branch from Branch',
489
- msg: `Enter branch name diverging from [${branchName}]:`,
490
- defaultValue: `feature/branch-from-${branchName}`,
491
- cancelable: true,
492
- });
493
- if (newBranchName) {
494
- this.showStatus(`Branch created: ${newBranchName}`);
495
- window.dispatchEvent(new CustomEvent('space-gib-branch-requested', {
496
- detail: {
497
- galaxyName,
498
- sourceBranch: branchName,
499
- newBranchName,
500
- scope: 'branch',
501
- },
502
- bubbles: true,
503
- composed: true,
504
- }));
501
+ let candidateName = `feature-branch-from-${branchName}`;
502
+ let promptMsg = `Enter branch name diverging from [${branchName}]:`;
503
+ let cleanBranchName = '';
504
+
505
+ while (true) {
506
+ const newBranchName = await promptForText({
507
+ title: 'Create Branch from Branch',
508
+ msg: promptMsg,
509
+ defaultValue: candidateName,
510
+ cancelable: true,
511
+ });
512
+ if (!newBranchName || !newBranchName.trim()) return;
513
+ cleanBranchName = newBranchName.trim();
514
+ const validation = validateBranchName(cleanBranchName);
515
+ if (!validation.isValid) {
516
+ candidateName = cleanBranchName;
517
+ promptMsg = `Invalid branch name: ${validation.error}\n\nPlease enter a valid branch name:`;
518
+ continue;
519
+ }
520
+ break;
505
521
  }
522
+
523
+ this.showStatus(`Branch created: ${cleanBranchName}`);
524
+ window.dispatchEvent(new CustomEvent('space-gib-branch-requested', {
525
+ detail: {
526
+ galaxyName,
527
+ sourceBranch: branchName,
528
+ newBranchName: cleanBranchName,
529
+ scope: 'branch',
530
+ },
531
+ bubbles: true,
532
+ composed: true,
533
+ }));
506
534
  });
507
535
  });
508
536
  }
@@ -18,6 +18,65 @@
18
18
  height: 100%;
19
19
  min-height: 0;
20
20
  overflow: hidden;
21
+ position: relative;
22
+ box-sizing: border-box;
23
+ border: 2px solid var(--branch-accent-color, rgba(255, 255, 255, 0.08));
24
+ box-shadow: inset 0 0 16px var(--branch-accent-translucent, rgba(0, 0, 0, 0.2));
25
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
26
+ }
27
+
28
+ /* ── Empty Placeholder View ── */
29
+ .editor-empty-placeholder {
30
+ display: flex;
31
+ flex-direction: column;
32
+ align-items: center;
33
+ justify-content: center;
34
+ width: 100%;
35
+ height: 100%;
36
+ color: var(--clr-text-muted, #7e8c9f);
37
+ text-align: center;
38
+ padding: 2.5rem;
39
+ box-sizing: border-box;
40
+ }
41
+
42
+ .editor-empty-placeholder .empty-icon {
43
+ font-size: 3.5rem;
44
+ margin-bottom: 1.25rem;
45
+ opacity: 0.4;
46
+ filter: drop-shadow(0 0 12px rgba(124, 92, 252, 0.2));
47
+ }
48
+
49
+ .editor-empty-placeholder .empty-title {
50
+ font-size: 1.35rem;
51
+ font-weight: 600;
52
+ color: var(--clr-text-primary, #f0f4fc);
53
+ margin-bottom: 0.6rem;
54
+ letter-spacing: -0.01em;
55
+ }
56
+
57
+ .editor-empty-placeholder .empty-desc {
58
+ font-size: 0.95rem;
59
+ max-width: 420px;
60
+ line-height: 1.6;
61
+ color: var(--clr-text-muted, #8b949e);
62
+ }
63
+
64
+ .editor-empty-placeholder .highlight {
65
+ color: var(--clr-accent, #78f87e);
66
+ font-weight: 600;
67
+ padding: 0.15rem 0.4rem;
68
+ background: rgba(120, 248, 126, 0.12);
69
+ border: 1px solid rgba(120, 248, 126, 0.25);
70
+ border-radius: 4px;
71
+ }
72
+
73
+ .editor-main-container {
74
+ display: flex;
75
+ flex-direction: column;
76
+ width: 100%;
77
+ height: 100%;
78
+ min-height: 0;
79
+ overflow: hidden;
21
80
  }
22
81
 
23
82
  /* ── Integrated Header (File Breadcrumb + Virtual Tip + Actions) ── */
@@ -74,38 +133,40 @@
74
133
  display: inline-flex;
75
134
  align-items: center;
76
135
  gap: 0.35rem;
77
- background: rgba(124, 92, 252, 0.12);
78
- border: 1px solid rgba(124, 92, 252, 0.35);
136
+ background: var(--branch-accent-translucent, rgba(124, 92, 252, 0.12));
137
+ border: 1px solid var(--branch-accent-color, rgba(124, 92, 252, 0.35));
79
138
  border-radius: var(--radius-sm, 6px);
80
139
  padding: 0.2rem 0.55rem;
81
140
  font-size: 0.775rem;
82
141
  font-family: var(--font-mono, monospace);
83
- color: #c4b5fd;
142
+ color: var(--branch-accent-color, #c4b5fd);
84
143
  user-select: none;
85
144
  transition: all 150ms ease;
86
145
  }
87
146
 
88
147
  .tip-virtual-breadcrumb:hover {
89
- background: rgba(124, 92, 252, 0.2);
90
- border-color: rgba(124, 92, 252, 0.6);
91
- box-shadow: 0 0 10px rgba(124, 92, 252, 0.25);
148
+ background: var(--branch-accent-translucent, rgba(124, 92, 252, 0.2));
149
+ border-color: var(--branch-accent-color, rgba(124, 92, 252, 0.6));
150
+ box-shadow: 0 0 10px var(--branch-accent-translucent, rgba(124, 92, 252, 0.25));
92
151
  }
93
152
 
94
153
  .tip-sep {
95
- color: rgba(196, 181, 253, 0.5);
154
+ color: var(--branch-accent-color, rgba(196, 181, 253, 0.5));
155
+ opacity: 0.7;
96
156
  }
97
157
 
98
158
  .tip-icon {
99
159
  font-size: 0.8rem;
160
+ color: var(--branch-accent-color, #a78bfa);
100
161
  }
101
162
 
102
163
  .tip-branch {
103
- color: #ddd6fe;
164
+ color: #ffffff;
104
165
  font-weight: 600;
105
166
  }
106
167
 
107
168
  .tip-hash {
108
- color: #a78bfa;
169
+ color: var(--branch-accent-color, #a78bfa);
109
170
  font-weight: 700;
110
171
  letter-spacing: 0.02em;
111
172
  }
@@ -181,6 +242,27 @@
181
242
  border-color: rgba(120, 248, 126, 0.7);
182
243
  }
183
244
 
245
+ .mode-toggle-btn.saving {
246
+ border-color: rgba(120, 248, 126, 0.6);
247
+ color: #78f87e;
248
+ background: rgba(120, 248, 126, 0.12);
249
+ box-shadow: 0 0 8px rgba(120, 248, 126, 0.2);
250
+ }
251
+
252
+ .mode-toggle-btn .save-spinner {
253
+ width: 10px;
254
+ height: 10px;
255
+ border: 1.5px solid rgba(120, 248, 126, 0.3);
256
+ border-top-color: var(--clr-accent, #78f87e);
257
+ border-radius: 50%;
258
+ animation: save-spin 0.6s linear infinite;
259
+ flex-shrink: 0;
260
+ }
261
+
262
+ @keyframes save-spin {
263
+ to { transform: rotate(360deg); }
264
+ }
265
+
184
266
  .action-btn {
185
267
  display: inline-flex;
186
268
  align-items: center;
@@ -1,54 +1,67 @@
1
1
  <div class="editor-root" id="editor-root">
2
- <!-- Combined File View Header & Virtual Breadcrumb -->
3
- <header class="editor-header" id="editor-header">
4
- <div class="header-left">
5
- <div class="file-breadcrumb">
6
- <span class="crumb-icon">πŸ“„</span>
7
- <span class="crumb-repo" id="crumb-repo">my-app</span>
8
- <span class="crumb-sep">/</span>
9
- <span class="crumb-path" id="crumb-path">src/index.ts</span>
10
- </div>
11
-
12
- <!-- Secondary Virtual Breadcrumb: Active Branch & Tip Merkle Hash -->
13
- <div class="tip-virtual-breadcrumb" id="crumb-tip-container" title="Active Branch Tip Merkle Address">
14
- <span class="tip-sep">@</span>
15
- <span class="tip-icon">🌿</span>
16
- <span class="tip-branch" id="crumb-branch">main</span>
17
- <span class="tip-hash" id="crumb-tip">7f8a1b2</span>
18
- </div>
2
+ <!-- Empty placeholder when no file is loaded -->
3
+ <div class="editor-empty-placeholder" id="editor-empty-placeholder" style="display: none;">
4
+ <div class="empty-icon">πŸ“„</div>
5
+ <div class="empty-title">No file open</div>
6
+ <div class="empty-desc">
7
+ Select a file from the explorer on the left, or click <span class="highlight">πŸ“„+</span> to create a file.
19
8
  </div>
9
+ </div>
20
10
 
21
- <div class="header-right">
22
- <div class="editor-stats">
23
- <span class="stat-badge" id="stat-lang">TypeScript</span>
24
- <span class="stat-sep">Β·</span>
25
- <span class="stat-badge" id="stat-lines">18 lines</span>
26
- <span class="stat-sep">Β·</span>
27
- <span class="stat-badge" id="stat-size">520 B</span>
11
+ <!-- Main editor container -->
12
+ <div class="editor-main-container" id="editor-main-container">
13
+ <!-- Combined File View Header & Virtual Breadcrumb -->
14
+ <header class="editor-header" id="editor-header">
15
+ <div class="header-left">
16
+ <div class="file-breadcrumb">
17
+ <span class="crumb-icon">πŸ“„</span>
18
+ <span class="crumb-repo" id="crumb-repo"></span>
19
+ <span class="crumb-sep">/</span>
20
+ <span class="crumb-path" id="crumb-path"></span>
21
+ </div>
22
+
23
+ <!-- Secondary Virtual Breadcrumb: Active Branch & Tip Merkle Hash -->
24
+ <div class="tip-virtual-breadcrumb" id="crumb-tip-container" title="Active Branch Tip Merkle Address">
25
+ <span class="tip-sep">@</span>
26
+ <span class="tip-icon">🌿</span>
27
+ <span class="tip-branch" id="crumb-branch">main</span>
28
+ <span class="tip-hash" id="crumb-tip"></span>
29
+ </div>
28
30
  </div>
29
31
 
30
- <div class="header-actions">
31
- <!-- Read-Only / Editable Mode Toggle -->
32
- <button id="btn-toggle-readonly" class="mode-toggle-btn" type="button" title="Toggle Read-Only / Editable Mode">
33
- <span class="mode-icon" id="mode-icon">πŸ”’</span>
34
- <span class="mode-text" id="mode-text">Read-Only</span>
35
- </button>
32
+ <div class="header-right">
33
+ <div class="editor-stats">
34
+ <span class="stat-badge" id="stat-lang"></span>
35
+ <span class="stat-sep">Β·</span>
36
+ <span class="stat-badge" id="stat-lines">0 lines</span>
37
+ <span class="stat-sep">Β·</span>
38
+ <span class="stat-badge" id="stat-size">0 B</span>
39
+ </div>
40
+
41
+ <div class="header-actions">
42
+ <!-- Read-Only / Editable Mode Toggle & Save Status -->
43
+ <button id="btn-toggle-readonly" class="mode-toggle-btn" type="button" title="Toggle Read-Only / Editable Mode">
44
+ <span class="save-spinner" id="btn-save-spinner" style="display: none;"></span>
45
+ <span class="mode-icon" id="mode-icon">πŸ”’</span>
46
+ <span class="mode-text" id="mode-text">Read-Only</span>
47
+ </button>
36
48
 
37
- <!-- Copy Code Action -->
38
- <button id="btn-copy-code" class="action-btn" type="button" title="Copy code to clipboard">
39
- <span class="btn-icon">πŸ“‹</span>
40
- </button>
49
+ <!-- Copy Code Action -->
50
+ <button id="btn-copy-code" class="action-btn" type="button" title="Copy code to clipboard">
51
+ <span class="btn-icon">πŸ“‹</span>
52
+ </button>
53
+ </div>
41
54
  </div>
42
- </div>
43
- </header>
55
+ </header>
44
56
 
45
- <!-- Single Editor Body (Gutter + Code Textarea) -->
46
- <main class="editor-body" id="editor-body">
47
- <div class="editor-gutter" id="editor-gutter" aria-hidden="true">
48
- <span class="line-num">1</span>
49
- </div>
50
- <div class="editor-content-wrapper">
51
- <textarea id="editor-textarea" class="editor-textarea" spellcheck="false" autocomplete="off" autocapitalize="off" wrap="off" placeholder="Write code here..."></textarea>
52
- </div>
53
- </main>
57
+ <!-- Single Editor Body (Gutter + Code Textarea) -->
58
+ <main class="editor-body" id="editor-body">
59
+ <div class="editor-gutter" id="editor-gutter" aria-hidden="true">
60
+ <span class="line-num">1</span>
61
+ </div>
62
+ <div class="editor-content-wrapper">
63
+ <textarea id="editor-textarea" class="editor-textarea" spellcheck="false" autocomplete="off" autocapitalize="off" wrap="off" placeholder="Write code here..."></textarea>
64
+ </div>
65
+ </main>
66
+ </div>
54
67
  </div>