studiograph 1.2.0-beta.9 → 1.2.1

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 (226) hide show
  1. package/README.md +13 -18
  2. package/dist/agent/orchestrator.d.ts +56 -0
  3. package/dist/agent/orchestrator.js +167 -22
  4. package/dist/agent/orchestrator.js.map +1 -1
  5. package/dist/agent/prompts/system.md +51 -63
  6. package/dist/agent/skill-loader.js +8 -0
  7. package/dist/agent/skill-loader.js.map +1 -1
  8. package/dist/agent/skills/entity-schema.md +4 -19
  9. package/dist/agent/skills/gather-context.md +1 -1
  10. package/dist/agent/tools/graph-tools.d.ts +41 -1
  11. package/dist/agent/tools/graph-tools.js +133 -57
  12. package/dist/agent/tools/graph-tools.js.map +1 -1
  13. package/dist/agent/tools/ops-tools.js +82 -0
  14. package/dist/agent/tools/ops-tools.js.map +1 -1
  15. package/dist/agent/tools/permission-tools.d.ts +90 -0
  16. package/dist/agent/tools/permission-tools.js +207 -0
  17. package/dist/agent/tools/permission-tools.js.map +1 -0
  18. package/dist/agent/tools/sync-tools.js +2 -2
  19. package/dist/agent/tools/sync-tools.js.map +1 -1
  20. package/dist/auth/github.d.ts +1 -1
  21. package/dist/auth/github.js +21 -13
  22. package/dist/auth/github.js.map +1 -1
  23. package/dist/cli/commands/access.d.ts +11 -0
  24. package/dist/cli/commands/access.js +156 -0
  25. package/dist/cli/commands/access.js.map +1 -0
  26. package/dist/cli/commands/app.js +25 -2
  27. package/dist/cli/commands/app.js.map +1 -1
  28. package/dist/cli/commands/clear.d.ts +5 -0
  29. package/dist/cli/commands/clear.js +36 -0
  30. package/dist/cli/commands/clear.js.map +1 -0
  31. package/dist/cli/commands/clone.js +3 -3
  32. package/dist/cli/commands/clone.js.map +1 -1
  33. package/dist/cli/commands/collection.d.ts +10 -0
  34. package/dist/cli/commands/collection.js +187 -0
  35. package/dist/cli/commands/collection.js.map +1 -0
  36. package/dist/cli/commands/commit.js +2 -1
  37. package/dist/cli/commands/commit.js.map +1 -1
  38. package/dist/cli/commands/config.d.ts +100 -0
  39. package/dist/cli/commands/config.js +1 -1
  40. package/dist/cli/commands/config.js.map +1 -1
  41. package/dist/cli/commands/deploy.js +10 -1
  42. package/dist/cli/commands/deploy.js.map +1 -1
  43. package/dist/cli/commands/index.js +1 -1
  44. package/dist/cli/commands/index.js.map +1 -1
  45. package/dist/cli/commands/init.js +32 -40
  46. package/dist/cli/commands/init.js.map +1 -1
  47. package/dist/cli/commands/join.d.ts +0 -6
  48. package/dist/cli/commands/join.js +26 -111
  49. package/dist/cli/commands/join.js.map +1 -1
  50. package/dist/cli/commands/lint.js +1 -2
  51. package/dist/cli/commands/lint.js.map +1 -1
  52. package/dist/cli/commands/mcp.js +6 -5
  53. package/dist/cli/commands/mcp.js.map +1 -1
  54. package/dist/cli/commands/org.d.ts +10 -0
  55. package/dist/cli/commands/org.js +132 -0
  56. package/dist/cli/commands/org.js.map +1 -0
  57. package/dist/cli/commands/orphans.js +1 -2
  58. package/dist/cli/commands/orphans.js.map +1 -1
  59. package/dist/cli/commands/provision.js +3 -3
  60. package/dist/cli/commands/provision.js.map +1 -1
  61. package/dist/cli/commands/resolve.d.ts +8 -0
  62. package/dist/cli/commands/resolve.js +85 -0
  63. package/dist/cli/commands/resolve.js.map +1 -0
  64. package/dist/cli/commands/serve.js +55 -25
  65. package/dist/cli/commands/serve.js.map +1 -1
  66. package/dist/cli/commands/start.js +22 -205
  67. package/dist/cli/commands/start.js.map +1 -1
  68. package/dist/cli/commands/sync.js +53 -8
  69. package/dist/cli/commands/sync.js.map +1 -1
  70. package/dist/cli/commands/team.d.ts +12 -0
  71. package/dist/cli/commands/team.js +185 -0
  72. package/dist/cli/commands/team.js.map +1 -0
  73. package/dist/cli/index.js +41 -34
  74. package/dist/cli/index.js.map +1 -1
  75. package/dist/cli/scaffolding.d.ts +5 -3
  76. package/dist/cli/scaffolding.js +191 -97
  77. package/dist/cli/scaffolding.js.map +1 -1
  78. package/dist/cli/setup-wizard.js +20 -10
  79. package/dist/cli/setup-wizard.js.map +1 -1
  80. package/dist/cli/sync-review-interactive.js +1 -1
  81. package/dist/cli/sync-review-interactive.js.map +1 -1
  82. package/dist/core/graph.d.ts +5 -10
  83. package/dist/core/graph.js +84 -85
  84. package/dist/core/graph.js.map +1 -1
  85. package/dist/core/migration-runner.d.ts +42 -0
  86. package/dist/core/migration-runner.js +232 -0
  87. package/dist/core/migration-runner.js.map +1 -0
  88. package/dist/core/migration-types.d.ts +101 -0
  89. package/dist/core/migration-types.js +21 -0
  90. package/dist/core/migration-types.js.map +1 -0
  91. package/dist/core/migrations/20260219-formalize-memory-location.d.ts +2 -0
  92. package/dist/core/migrations/20260219-formalize-memory-location.js +35 -0
  93. package/dist/core/migrations/20260219-formalize-memory-location.js.map +1 -0
  94. package/dist/core/migrations/20260220-add-workspace-metadata.d.ts +12 -0
  95. package/dist/core/migrations/20260220-add-workspace-metadata.js +65 -0
  96. package/dist/core/migrations/20260220-add-workspace-metadata.js.map +1 -0
  97. package/dist/core/migrations/20260220-add-workspace-readme.d.ts +11 -0
  98. package/dist/core/migrations/20260220-add-workspace-readme.js +82 -0
  99. package/dist/core/migrations/20260220-add-workspace-readme.js.map +1 -0
  100. package/dist/core/migrations/20260220-migrate-yaml-to-json.d.ts +9 -0
  101. package/dist/core/migrations/20260220-migrate-yaml-to-json.js +64 -0
  102. package/dist/core/migrations/20260220-migrate-yaml-to-json.js.map +1 -0
  103. package/dist/core/migrations/index.d.ts +11 -0
  104. package/dist/core/migrations/index.js +23 -0
  105. package/dist/core/migrations/index.js.map +1 -0
  106. package/dist/core/types.d.ts +19 -34
  107. package/dist/core/types.js +3 -21
  108. package/dist/core/types.js.map +1 -1
  109. package/dist/core/user-config.d.ts +27 -0
  110. package/dist/core/user-config.js +51 -0
  111. package/dist/core/user-config.js.map +1 -1
  112. package/dist/core/validation.d.ts +978 -2182
  113. package/dist/core/validation.js +242 -397
  114. package/dist/core/validation.js.map +1 -1
  115. package/dist/core/workspace-manager.d.ts +32 -31
  116. package/dist/core/workspace-manager.js +171 -186
  117. package/dist/core/workspace-manager.js.map +1 -1
  118. package/dist/core/workspace.d.ts +0 -5
  119. package/dist/core/workspace.js +33 -101
  120. package/dist/core/workspace.js.map +1 -1
  121. package/dist/lib/lib/utils.d.ts +2 -0
  122. package/dist/lib/lib/utils.js +6 -0
  123. package/dist/lib/lib/utils.js.map +1 -0
  124. package/dist/mcp/tools.js +13 -13
  125. package/dist/mcp/tools.js.map +1 -1
  126. package/dist/server/chrome/chrome.css +562 -0
  127. package/dist/server/chrome/chrome.js +540 -0
  128. package/dist/server/index.js +163 -10
  129. package/dist/server/index.js.map +1 -1
  130. package/dist/server/plugin-loader.d.ts +7 -0
  131. package/dist/server/plugin-loader.js +9 -1
  132. package/dist/server/plugin-loader.js.map +1 -1
  133. package/dist/server/routes/chat.d.ts +3 -2
  134. package/dist/server/routes/chat.js +67 -16
  135. package/dist/server/routes/chat.js.map +1 -1
  136. package/dist/server/routes/git-api.d.ts +9 -0
  137. package/dist/server/routes/git-api.js +82 -0
  138. package/dist/server/routes/git-api.js.map +1 -0
  139. package/dist/server/routes/graph-api.d.ts +2 -2
  140. package/dist/server/routes/graph-api.js +267 -3
  141. package/dist/server/routes/graph-api.js.map +1 -1
  142. package/dist/server/routes/permissions-api.d.ts +9 -0
  143. package/dist/server/routes/permissions-api.js +176 -0
  144. package/dist/server/routes/permissions-api.js.map +1 -0
  145. package/dist/server/routes/workspace-api.d.ts +9 -0
  146. package/dist/server/routes/workspace-api.js +283 -0
  147. package/dist/server/routes/workspace-api.js.map +1 -0
  148. package/dist/services/assets/base.d.ts +2 -2
  149. package/dist/services/assets/base.js +4 -4
  150. package/dist/services/assets/base.js.map +1 -1
  151. package/dist/services/assets/index.d.ts +6 -6
  152. package/dist/services/assets/index.js +12 -12
  153. package/dist/services/assets/index.js.map +1 -1
  154. package/dist/services/git.d.ts +35 -0
  155. package/dist/services/git.js +93 -0
  156. package/dist/services/git.js.map +1 -1
  157. package/dist/services/github-provisioner.js +1 -2
  158. package/dist/services/github-provisioner.js.map +1 -1
  159. package/dist/services/github-service.d.ts +99 -0
  160. package/dist/services/github-service.js +310 -0
  161. package/dist/services/github-service.js.map +1 -0
  162. package/dist/services/markdown.js +4 -9
  163. package/dist/services/markdown.js.map +1 -1
  164. package/dist/services/memory-service.d.ts +4 -0
  165. package/dist/services/memory-service.js +13 -1
  166. package/dist/services/memory-service.js.map +1 -1
  167. package/dist/services/sync/commit.d.ts +2 -0
  168. package/dist/services/sync/commit.js +21 -6
  169. package/dist/services/sync/commit.js.map +1 -1
  170. package/dist/services/sync/graphrag-indexer.js +2 -2
  171. package/dist/services/sync/graphrag-indexer.js.map +1 -1
  172. package/dist/utils/git.d.ts +5 -0
  173. package/dist/utils/git.js +10 -0
  174. package/dist/utils/git.js.map +1 -1
  175. package/dist/utils/merge-resolver.d.ts +33 -8
  176. package/dist/utils/merge-resolver.js +157 -55
  177. package/dist/utils/merge-resolver.js.map +1 -1
  178. package/dist/utils/preflight.d.ts +1 -1
  179. package/dist/utils/preflight.js +1 -1
  180. package/dist/web/_app/env.js +1 -0
  181. package/dist/web/_app/immutable/assets/0.CDbX4Cwz.css +1 -0
  182. package/dist/web/_app/immutable/assets/2.DRHi7ABa.css +1 -0
  183. package/dist/web/_app/immutable/assets/3.BJy7pVXi.css +1 -0
  184. package/dist/web/_app/immutable/assets/4.Ad16uh9o.css +1 -0
  185. package/dist/web/_app/immutable/assets/5.BhKgiXd2.css +1 -0
  186. package/dist/web/_app/immutable/assets/6.CeHKR5ZY.css +1 -0
  187. package/dist/web/_app/immutable/assets/AppShell.CJtUfb0N.css +1 -0
  188. package/dist/web/_app/immutable/assets/ChatPanel.RFD5GGYI.css +1 -0
  189. package/dist/web/_app/immutable/assets/editor.CPAf2SRV.css +1 -0
  190. package/dist/web/_app/immutable/chunks/3_5VIr68.js +1 -0
  191. package/dist/web/_app/immutable/chunks/4QY4j-jX.js +1 -0
  192. package/dist/web/_app/immutable/chunks/BB_5th5W.js +3383 -0
  193. package/dist/web/_app/immutable/chunks/BButMJ6M.js +1 -0
  194. package/dist/web/_app/immutable/chunks/BiubvAUI.js +2 -0
  195. package/dist/web/_app/immutable/chunks/CAXuTUkp.js +1 -0
  196. package/dist/web/_app/immutable/chunks/CQENNNNl.js +6 -0
  197. package/dist/web/_app/immutable/chunks/CUzqHQY_.js +1 -0
  198. package/dist/web/_app/immutable/chunks/CV8ganSj.js +23 -0
  199. package/dist/web/_app/immutable/chunks/CYrVHOHA.js +1 -0
  200. package/dist/web/_app/immutable/chunks/CqkleIqs.js +1 -0
  201. package/dist/web/_app/immutable/chunks/DKyztuK9.js +1 -0
  202. package/dist/web/_app/immutable/chunks/DdNsM6oV.js +64 -0
  203. package/dist/web/_app/immutable/chunks/Dh_H7Owr.js +18 -0
  204. package/dist/web/_app/immutable/chunks/DnlgZ_Tk.js +5 -0
  205. package/dist/web/_app/immutable/chunks/DtVH--hH.js +6 -0
  206. package/dist/web/_app/immutable/chunks/F20aIBpJ.js +1 -0
  207. package/dist/web/_app/immutable/chunks/PPVm8Dsz.js +1 -0
  208. package/dist/web/_app/immutable/chunks/WSUKABI_.js +1 -0
  209. package/dist/web/_app/immutable/chunks/YFT1281h.js +2 -0
  210. package/dist/web/_app/immutable/chunks/aosHekRC.js +1 -0
  211. package/dist/web/_app/immutable/chunks/fwnBoL5x.js +1 -0
  212. package/dist/web/_app/immutable/chunks/hHxe9oXh.js +1 -0
  213. package/dist/web/_app/immutable/entry/app.BFVUP2fV.js +2 -0
  214. package/dist/web/_app/immutable/entry/start.BqsXHYP6.js +1 -0
  215. package/dist/web/_app/immutable/nodes/0.DQ5KdeNU.js +1 -0
  216. package/dist/web/_app/immutable/nodes/1.DwnzyOvm.js +1 -0
  217. package/dist/web/_app/immutable/nodes/2.FZxrWJeh.js +1 -0
  218. package/dist/web/_app/immutable/nodes/3.CQVWlbKr.js +1 -0
  219. package/dist/web/_app/immutable/nodes/4.CEh40Z_h.js +16 -0
  220. package/dist/web/_app/immutable/nodes/5.D_QsCLEf.js +4 -0
  221. package/dist/web/_app/immutable/nodes/6.CndEQ0o5.js +2 -0
  222. package/dist/web/_app/version.json +1 -0
  223. package/dist/web/index.html +43 -0
  224. package/package.json +21 -3
  225. package/dist/agent/skills/bundled/enrich-entities.md +0 -124
  226. package/dist/agent/skills/bundled/gather-context.md +0 -46
@@ -0,0 +1,540 @@
1
+ /**
2
+ * Studiograph Chrome — Shared App Header & Footer
3
+ *
4
+ * Usage: Add to any app editor HTML:
5
+ * <link rel="stylesheet" href="/api/chrome/chrome.css">
6
+ * <script src="/api/chrome/chrome.js"></script>
7
+ *
8
+ * Body data attributes:
9
+ * data-sg-repo — repo name
10
+ * data-sg-entity-id — entity ID
11
+ * data-sg-entity-type — entity type (deck, document, etc.)
12
+ * data-sg-view-url — URL for the View/Present button
13
+ * data-sg-view-label — label for the view button (default "View")
14
+ */
15
+ (function () {
16
+ 'use strict';
17
+
18
+ // ── Config ──────────────────────────────────────────────
19
+ var body = document.body;
20
+ var REPO = body.dataset.sgRepo || '';
21
+ var WORKSPACE = body.dataset.sgWorkspace || '';
22
+ var ENTITY_ID = body.dataset.sgEntityId || '';
23
+ var VIEW_URL = body.dataset.sgViewUrl || '';
24
+ var VIEW_LABEL = body.dataset.sgViewLabel || 'View';
25
+
26
+ if (!REPO && !WORKSPACE) return; // no repo or workspace = don't inject chrome
27
+
28
+ var workspaceMode = !REPO && !!WORKSPACE; // footer only, no header
29
+
30
+ if (!workspaceMode) body.classList.add('sg-chrome-active');
31
+
32
+ // ── Helpers ─────────────────────────────────────────────
33
+ function esc(s) {
34
+ if (!s) return '';
35
+ var d = document.createElement('div');
36
+ d.textContent = s;
37
+ return d.innerHTML;
38
+ }
39
+
40
+ function el(tag, cls, html) {
41
+ var e = document.createElement(tag);
42
+ if (cls) e.className = cls;
43
+ if (html) e.innerHTML = html;
44
+ return e;
45
+ }
46
+
47
+ function titleCase(str) {
48
+ return str.replace(/-/g, ' ').replace(/\b\w/g, function (c) { return c.toUpperCase(); });
49
+ }
50
+
51
+ /** Returns { repo, type, entity } parts for structured tooltip rendering */
52
+ function friendlyParts(repo, path, entityName) {
53
+ var cleaned = path.replace(/\/$/, '').replace(/\/main\.md$/, '').replace(/\.md$/, '');
54
+ var segments = cleaned.split('/').filter(Boolean);
55
+ var entity = entityName || titleCase(segments[segments.length - 1]);
56
+ var type = segments.length > 1 ? titleCase(segments[0]) : (entityName ? titleCase(segments[0]) : null);
57
+ return { repo: repo, type: type, entity: entity };
58
+ }
59
+
60
+ function renderPathHtml(parts) {
61
+ var sep = '<span class="sg-path-sep">/</span>';
62
+ var html = '<span class="sg-path-repo">' + esc(parts.repo) + '</span>';
63
+ if (parts.type) html += sep + '<span class="sg-path-type">' + esc(parts.type) + '</span>';
64
+ html += sep + '<span class="sg-path-entity">' + esc(parts.entity) + '</span>';
65
+ return html;
66
+ }
67
+
68
+ function statusClass(status) {
69
+ switch (status) {
70
+ case 'D': return 'sg-chrome-status-deleted';
71
+ case 'A': case '?': return 'sg-chrome-status-added';
72
+ default: return 'sg-chrome-status-modified';
73
+ }
74
+ }
75
+
76
+ // ── Theme ───────────────────────────────────────────────
77
+ function getTheme() {
78
+ return document.documentElement.dataset.theme || localStorage.getItem('theme') || 'dark';
79
+ }
80
+
81
+ function setTheme(theme) {
82
+ document.documentElement.dataset.theme = theme;
83
+ localStorage.setItem('theme', theme);
84
+ updateThemeIcon();
85
+ window.dispatchEvent(new CustomEvent('sg-theme-change', { detail: theme }));
86
+ }
87
+
88
+ function toggleTheme() {
89
+ setTheme(getTheme() === 'dark' ? 'light' : 'dark');
90
+ }
91
+
92
+ // ── Lucide SVG Icons ──────────────────────────────────
93
+ var SUN_ICON = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>';
94
+ var MOON_ICON = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>';
95
+ var ARROW_DOWN = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="m19 12-7 7-7-7"/></svg>';
96
+ var ARROW_UP = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5"/><path d="m5 12 7-7 7 7"/></svg>';
97
+
98
+ var BACK_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>';
99
+ var DOTS_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>';
100
+ var VIEW_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>';
101
+ var PRESENT_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="none"><polygon points="6 3 20 12 6 21 6 3"/></svg>';
102
+
103
+ var themeBtn;
104
+ function updateThemeIcon() {
105
+ if (themeBtn) themeBtn.innerHTML = getTheme() !== 'light' ? SUN_ICON : MOON_ICON;
106
+ }
107
+
108
+ // ── Build Header ────────────────────────────────────────
109
+ var header = el('header', 'sg-chrome-header');
110
+
111
+ // Left side: back + breadcrumb
112
+ var headerLeft = el('div', 'sg-chrome-header-left');
113
+ var backLink = el('a', 'sg-chrome-back', BACK_ICON);
114
+ backLink.href = '/';
115
+ backLink.title = 'Back to Studiograph';
116
+ backLink.addEventListener('click', function (e) {
117
+ if (window.history.length > 1) {
118
+ e.preventDefault();
119
+ window.history.back();
120
+ }
121
+ });
122
+ headerLeft.appendChild(backLink);
123
+
124
+ var breadcrumb = el('span', 'sg-chrome-breadcrumb',
125
+ esc(REPO) + '<span class="sg-chrome-sep">/</span>' + esc(ENTITY_ID));
126
+ headerLeft.appendChild(breadcrumb);
127
+ header.appendChild(headerLeft);
128
+
129
+ // Right side: save status, view, overflow
130
+ var headerRight = el('div', 'sg-chrome-header-right');
131
+
132
+ var saveStatusEl = el('span', 'sg-chrome-save-status');
133
+ saveStatusEl.id = 'sg-chrome-save-status';
134
+ headerRight.appendChild(saveStatusEl);
135
+
136
+ headerRight.appendChild(el('span', 'sg-chrome-divider'));
137
+
138
+ // View / Present button slot (always present for dynamic updates)
139
+ var viewBtnSlot = el('span', 'sg-chrome-view-slot');
140
+
141
+ // Static init for standalone mode (non-SPA)
142
+ if (VIEW_URL) {
143
+ var viewIcon = VIEW_LABEL === 'Present' ? PRESENT_ICON : VIEW_ICON;
144
+ var viewBtn = el('button', 'sg-chrome-btn sg-chrome-btn-accent',
145
+ viewIcon + '<span>' + esc(VIEW_LABEL) + '</span>');
146
+ viewBtn.title = VIEW_LABEL;
147
+ viewBtn.addEventListener('click', function () {
148
+ window.open(VIEW_URL, '_blank');
149
+ });
150
+ viewBtnSlot.appendChild(viewBtn);
151
+ }
152
+ headerRight.appendChild(viewBtnSlot);
153
+ headerRight.appendChild(el('span', 'sg-chrome-divider sg-chrome-view-divider'));
154
+
155
+ // Overflow menu
156
+ var overflowWrapper = el('div', 'sg-chrome-overflow-wrapper');
157
+ var overflowBtn = el('button', 'sg-chrome-overflow-btn', DOTS_ICON);
158
+ overflowBtn.title = 'More actions';
159
+ overflowWrapper.appendChild(overflowBtn);
160
+
161
+ var overflowMenu = el('div', 'sg-chrome-overflow-menu');
162
+ overflowWrapper.appendChild(overflowMenu);
163
+ headerRight.appendChild(overflowWrapper);
164
+
165
+ header.appendChild(headerRight);
166
+
167
+ // Overflow menu open/close
168
+ overflowBtn.addEventListener('click', function (e) {
169
+ e.stopPropagation();
170
+ overflowMenu.classList.toggle('open');
171
+ });
172
+ document.addEventListener('click', function () {
173
+ overflowMenu.classList.remove('open');
174
+ });
175
+ document.addEventListener('keydown', function (e) {
176
+ if (e.key === 'Escape') overflowMenu.classList.remove('open');
177
+ });
178
+
179
+ // ── Build Footer (matches StatusBar.svelte) ────────────
180
+ var footer = el('footer', 'sg-chrome-footer');
181
+
182
+ // Left: theme toggle + workspace stats
183
+ var footerLeft = el('div', 'sg-chrome-footer-left');
184
+
185
+ themeBtn = el('button', 'sg-chrome-theme-toggle');
186
+ themeBtn.title = 'Toggle theme';
187
+ themeBtn.addEventListener('click', toggleTheme);
188
+ footerLeft.appendChild(themeBtn);
189
+ updateThemeIcon();
190
+
191
+ var statsEl = el('span', 'sg-chrome-footer-stats');
192
+ footerLeft.appendChild(statsEl);
193
+ footer.appendChild(footerLeft);
194
+
195
+ // Center: Revert + Commit (absolute centered)
196
+ var footerCenter = el('div', 'sg-chrome-footer-center');
197
+
198
+ var revertBtn = el('button', 'sg-chrome-revert-btn', 'Revert');
199
+ revertBtn.title = 'Discard all unpublished changes';
200
+ revertBtn.hidden = true;
201
+
202
+ var commitAnchor = el('div', 'sg-chrome-tooltip-anchor');
203
+ var commitBtn = el('button', 'sg-chrome-commit-btn', '<span>Commit</span>');
204
+ commitBtn.title = 'Commit changes';
205
+ commitBtn.hidden = true;
206
+ var commitTooltip = el('div', 'sg-chrome-tooltip-popup');
207
+ commitAnchor.appendChild(commitBtn);
208
+ commitAnchor.appendChild(commitTooltip);
209
+
210
+ footerCenter.appendChild(revertBtn);
211
+ footerCenter.appendChild(commitAnchor);
212
+ footer.appendChild(footerCenter);
213
+
214
+ // Right: Pull + Push
215
+ var footerRight = el('div', 'sg-chrome-footer-right');
216
+
217
+ var pullBtn = el('button', 'sg-chrome-sync-btn',
218
+ ARROW_DOWN + '<span>Pull</span>');
219
+ pullBtn.title = 'Pull latest changes';
220
+
221
+ var pushAnchor = el('div', 'sg-chrome-tooltip-anchor');
222
+ var pushBtn = el('button', 'sg-chrome-sync-btn',
223
+ ARROW_UP + '<span>Push</span>');
224
+ pushBtn.title = 'Push changes to remote';
225
+ var pushTooltip = el('div', 'sg-chrome-tooltip-popup sg-chrome-tooltip-popup-right');
226
+ pushAnchor.appendChild(pushBtn);
227
+ pushAnchor.appendChild(pushTooltip);
228
+
229
+ footerRight.appendChild(pullBtn);
230
+ footerRight.appendChild(pushAnchor);
231
+ footer.appendChild(footerRight);
232
+
233
+ // ── Inject into DOM ─────────────────────────────────────
234
+ if (!workspaceMode) {
235
+ body.insertBefore(header, body.firstChild);
236
+ }
237
+ var footerMount = document.getElementById('sg-chrome-footer');
238
+ if (footerMount) {
239
+ footerMount.appendChild(footer);
240
+ } else if (workspaceMode) {
241
+ // SvelteKit SPA: mount point doesn't exist yet, wait for it
242
+ var observer = new MutationObserver(function (_mutations, obs) {
243
+ var el = document.getElementById('sg-chrome-footer');
244
+ if (el) {
245
+ el.appendChild(footer);
246
+ obs.disconnect();
247
+ }
248
+ });
249
+ observer.observe(body, { childList: true, subtree: true });
250
+ } else {
251
+ body.appendChild(footer);
252
+ }
253
+
254
+ // ── Git State ───────────────────────────────────────────
255
+ var gitState = {
256
+ repoStatuses: [], // [{repo, changes: [{path, status}]}]
257
+ totalChanges: 0,
258
+ ahead: 0,
259
+ behind: 0,
260
+ stats: { entities: 0, links: 0 }
261
+ };
262
+
263
+ function updateFooterUI() {
264
+ // ── Stats ──
265
+ if (gitState.stats.entities > 0) {
266
+ var e = gitState.stats.entities;
267
+ var l = gitState.stats.links;
268
+ statsEl.innerHTML =
269
+ '<span class="sg-chrome-stat">' + e.toLocaleString() + (e === 1 ? ' entity' : ' entities') + '</span>' +
270
+ '<span class="sg-chrome-stat-sep">|</span>' +
271
+ '<span class="sg-chrome-stat">' + l.toLocaleString() + (l === 1 ? ' link' : ' links') + '</span>';
272
+ }
273
+
274
+ // ── Commit / Revert ──
275
+ if (gitState.totalChanges > 0) {
276
+ revertBtn.hidden = false;
277
+ commitBtn.hidden = false;
278
+ commitBtn.innerHTML =
279
+ '<span>Commit</span><span class="sg-chrome-sync-count">' + gitState.totalChanges + '</span>';
280
+ // Tooltip: list changed files
281
+ var tooltipHtml = '';
282
+ gitState.repoStatuses.forEach(function (rs) {
283
+ rs.changes.forEach(function (change) {
284
+ var parts = friendlyParts(rs.repo, change.path, change.entityName);
285
+ tooltipHtml +=
286
+ '<div class="sg-chrome-tooltip-file">' +
287
+ '<span class="sg-chrome-tooltip-dot ' + statusClass(change.status) + '"></span>' +
288
+ renderPathHtml(parts) +
289
+ '</div>';
290
+ });
291
+ });
292
+ commitTooltip.innerHTML = tooltipHtml;
293
+ } else {
294
+ revertBtn.hidden = true;
295
+ commitBtn.hidden = true;
296
+ commitTooltip.innerHTML = '';
297
+ }
298
+
299
+ // ── Pull ──
300
+ if (gitState.behind > 0) {
301
+ pullBtn.hidden = false;
302
+ pullBtn.className = 'sg-chrome-sync-btn sg-chrome-sync-btn-accent';
303
+ pullBtn.innerHTML =
304
+ ARROW_DOWN + '<span>Pull</span><span class="sg-chrome-sync-count">' + gitState.behind + '</span>';
305
+ } else {
306
+ pullBtn.hidden = true;
307
+ }
308
+
309
+ // ── Push ──
310
+ if (gitState.ahead > 0) {
311
+ pushBtn.hidden = false;
312
+ pushBtn.className = 'sg-chrome-sync-btn sg-chrome-sync-btn-accent';
313
+ pushBtn.innerHTML =
314
+ ARROW_UP + '<span>Push</span><span class="sg-chrome-sync-count">' + gitState.ahead + '</span>';
315
+ pushTooltip.innerHTML =
316
+ '<div class="sg-chrome-tooltip-file">' +
317
+ gitState.ahead + ' commit' + (gitState.ahead === 1 ? '' : 's') + ' ahead of remote</div>';
318
+ } else {
319
+ pushBtn.hidden = true;
320
+ pushTooltip.innerHTML = '';
321
+ }
322
+ }
323
+
324
+ // ── Git Polling ─────────────────────────────────────────
325
+ function fetchGitStatus() {
326
+ return Promise.all([
327
+ fetch('/api/status').then(function (r) { return r.json(); }).catch(function () { return []; }),
328
+ fetch('/api/sync-status').then(function (r) { return r.json(); }).catch(function () { return { ahead: 0, behind: 0 }; })
329
+ ]).then(function (results) {
330
+ gitState.repoStatuses = results[0];
331
+ gitState.totalChanges = results[0].reduce(function (sum, r) { return sum + r.changes.length; }, 0);
332
+ gitState.ahead = results[1].ahead || 0;
333
+ gitState.behind = results[1].behind || 0;
334
+ updateFooterUI();
335
+ });
336
+ }
337
+
338
+ function fetchStats() {
339
+ fetch('/api/stats').then(function (r) { return r.json(); }).then(function (data) {
340
+ gitState.stats = data;
341
+ updateFooterUI();
342
+ }).catch(function () { /* silently fail */ });
343
+ }
344
+
345
+ // Initial fetch
346
+ fetchGitStatus();
347
+ fetchStats();
348
+
349
+ // Poll git status every 15s (stats less frequently — every 60s)
350
+ setInterval(fetchGitStatus, 15000);
351
+ setInterval(fetchStats, 60000);
352
+
353
+ // Sync with Svelte git store — refresh immediately when it updates
354
+ window.addEventListener('studiograph:git-refresh', function () {
355
+ fetchGitStatus();
356
+ fetchStats();
357
+ });
358
+
359
+ // ── Git Operations ──────────────────────────────────────
360
+ commitBtn.addEventListener('click', function () {
361
+ if (commitBtn.disabled) return;
362
+ // If a commit handler is registered, delegate to it
363
+ if (listeners['commit'] && listeners['commit'].length > 0) {
364
+ emitEvent('commit');
365
+ return;
366
+ }
367
+ commitBtn.disabled = true;
368
+ commitBtn.innerHTML = '<span>Committing\u2026</span>';
369
+ fetch('/api/commit', {
370
+ method: 'POST',
371
+ headers: { 'Content-Type': 'application/json' },
372
+ body: JSON.stringify({ message: 'Update from editor' })
373
+ })
374
+ .then(function (res) { return res.json(); })
375
+ .then(function () {
376
+ commitBtn.disabled = false;
377
+ fetchGitStatus();
378
+ fetchStats();
379
+ })
380
+ .catch(function () {
381
+ commitBtn.disabled = false;
382
+ alert('Commit failed: network error');
383
+ fetchGitStatus();
384
+ });
385
+ });
386
+
387
+ revertBtn.addEventListener('click', function () {
388
+ emitEvent('revert:open');
389
+ });
390
+
391
+ pushBtn.addEventListener('click', function () {
392
+ if (pushBtn.disabled) return;
393
+ pushBtn.disabled = true;
394
+ var prevHtml = pushBtn.innerHTML;
395
+ pushBtn.innerHTML = ARROW_UP + '<span>Pushing\u2026</span>';
396
+ fetch('/api/push', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' })
397
+ .then(function () { pushBtn.disabled = false; fetchGitStatus(); })
398
+ .catch(function () { pushBtn.disabled = false; fetchGitStatus(); });
399
+ });
400
+
401
+ pullBtn.addEventListener('click', function () {
402
+ if (pullBtn.disabled) return;
403
+ pullBtn.disabled = true;
404
+ pullBtn.innerHTML = ARROW_DOWN + '<span>Pulling\u2026</span>';
405
+ emitEvent('pull:start');
406
+ fetch('/api/pull', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' })
407
+ .then(function () {
408
+ pullBtn.disabled = false;
409
+ fetchGitStatus();
410
+ fetchStats();
411
+ emitEvent('pull');
412
+ })
413
+ .catch(function () { pullBtn.disabled = false; fetchGitStatus(); emitEvent('pull'); });
414
+ });
415
+
416
+ // ── Event System ────────────────────────────────────────
417
+ var listeners = {};
418
+
419
+ function emitEvent(name, data) {
420
+ (listeners[name] || []).forEach(function (fn) {
421
+ try { fn(data); } catch (e) { console.error('StudioChrome event error:', e); }
422
+ });
423
+ }
424
+
425
+ // ── Public API ──────────────────────────────────────────
426
+ window.StudioChrome = {
427
+ on: function (event, fn) {
428
+ if (!listeners[event]) listeners[event] = [];
429
+ listeners[event].push(fn);
430
+ },
431
+
432
+ off: function (event, fn) {
433
+ if (fn && listeners[event]) {
434
+ listeners[event] = listeners[event].filter(function (f) { return f !== fn; });
435
+ if (listeners[event].length === 0) delete listeners[event];
436
+ } else {
437
+ delete listeners[event];
438
+ }
439
+ },
440
+
441
+ /** Emit a mutation event (called by web UI after agent updates an entity) */
442
+ emitMutation: function (detail) {
443
+ emitEvent('mutation', detail);
444
+ },
445
+
446
+ /** Move footer into #sg-chrome-footer mount point (call after SPA navigation) */
447
+ mountFooter: function () {
448
+ var target = document.getElementById('sg-chrome-footer');
449
+ if (target && footer.parentNode !== target) {
450
+ target.appendChild(footer);
451
+ }
452
+ },
453
+
454
+ /**
455
+ * Set save status indicator.
456
+ * @param {string} text - Status text to display
457
+ * @param {string} [type] - 'saving'|'saved'|'error'|'' — controls styling
458
+ */
459
+ setSaveStatus: function (text, type) {
460
+ type = type || '';
461
+ saveStatusEl.textContent = text || '';
462
+ saveStatusEl.className = 'sg-chrome-save-status' + (type === 'error' ? ' sg-chrome-save-error' : '');
463
+ },
464
+
465
+ refreshStatus: function () {
466
+ fetchGitStatus();
467
+ },
468
+
469
+ refreshStats: function () {
470
+ fetchStats();
471
+ },
472
+
473
+ addMenuItem: function (opts) {
474
+ var btn = el('button', 'sg-chrome-menu-item' + (opts.danger ? ' sg-chrome-danger' : ''),
475
+ (opts.icon || '') + ' ' + esc(opts.label));
476
+ btn.addEventListener('click', function () {
477
+ overflowMenu.classList.remove('open');
478
+ if (opts.onClick) opts.onClick();
479
+ });
480
+ overflowMenu.appendChild(btn);
481
+ return btn;
482
+ },
483
+
484
+ addMenuSeparator: function () {
485
+ overflowMenu.appendChild(el('div', 'sg-chrome-menu-sep'));
486
+ },
487
+
488
+ /** Mount header into a target element (for SPA embedding) */
489
+ mountHeader: function (target) {
490
+ if (typeof target === 'string') target = document.getElementById(target);
491
+ if (target && header) {
492
+ // Clear overflow menu items from previous editor before mounting
493
+ overflowMenu.innerHTML = '';
494
+ target.appendChild(header);
495
+ }
496
+ },
497
+
498
+ /** Remove header from DOM (keeps element alive for re-mount) */
499
+ unmountHeader: function () {
500
+ if (header && header.parentNode) header.parentNode.removeChild(header);
501
+ },
502
+
503
+ /** Update breadcrumb text (for SPA embedding where repo/entity change) */
504
+ updateBreadcrumb: function (repo, entityId) {
505
+ breadcrumb.innerHTML = esc(repo) + '<span class="sg-chrome-sep">/</span>' + esc(entityId || '');
506
+ },
507
+
508
+ /** Set dynamic View/Present button (for SPA embedding) */
509
+ setViewAction: function (url, label) {
510
+ if (!url) return;
511
+ label = label || 'View';
512
+ var icon = label === 'Present' ? PRESENT_ICON : VIEW_ICON;
513
+ viewBtnSlot.innerHTML = '';
514
+ var btn = el('button', 'sg-chrome-btn sg-chrome-btn-accent',
515
+ icon + '<span>' + esc(label) + '</span>');
516
+ btn.title = label;
517
+ btn.addEventListener('click', function () {
518
+ window.open(url, '_blank');
519
+ });
520
+ viewBtnSlot.appendChild(btn);
521
+ var divider = header.querySelector('.sg-chrome-view-divider');
522
+ if (divider) divider.style.display = '';
523
+ },
524
+
525
+ /** Clear View/Present button */
526
+ clearViewAction: function () {
527
+ viewBtnSlot.innerHTML = '';
528
+ var divider = header.querySelector('.sg-chrome-view-divider');
529
+ if (divider) divider.style.display = 'none';
530
+ },
531
+
532
+ /** No-op stubs for backward compatibility with app editors */
533
+ setSaveHandlers: function () {},
534
+ clearSaveHandlers: function () {
535
+ saveStatusEl.textContent = '';
536
+ saveStatusEl.className = 'sg-chrome-save-status';
537
+ },
538
+ isDirty: function () { return false; },
539
+ };
540
+ })();