gitmaps 1.0.0 → 1.1.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 (145) hide show
  1. package/README.md +265 -122
  2. package/app/[...slug]/page.client.tsx +1 -0
  3. package/app/[...slug]/page.tsx +6 -0
  4. package/app/[owner]/[repo]/page.client.tsx +5 -0
  5. package/app/[slug]/page.client.tsx +5 -0
  6. package/app/analytics.db +0 -0
  7. package/app/api/analytics/route.ts +64 -0
  8. package/app/api/auth/positions/route.ts +95 -33
  9. package/app/api/build-info/route.ts +19 -0
  10. package/app/api/chat/route.ts +13 -2
  11. package/app/api/manifest.json/route.ts +20 -0
  12. package/app/api/og-image/route.ts +14 -0
  13. package/app/api/pwa-icon/route.ts +14 -0
  14. package/app/api/repo/clone-stream/route.ts +20 -12
  15. package/app/api/repo/file-content/route.ts +73 -20
  16. package/app/api/repo/imports/route.ts +21 -3
  17. package/app/api/repo/list/route.ts +30 -0
  18. package/app/api/repo/load/route.test.ts +62 -0
  19. package/app/api/repo/load/route.ts +41 -1
  20. package/app/api/repo/pdf-thumb/route.ts +127 -0
  21. package/app/api/repo/resolve-slug/route.ts +51 -0
  22. package/app/api/repo/tree/route.ts +188 -104
  23. package/app/api/repo/upload/route.ts +6 -9
  24. package/app/api/sw.js/route.ts +70 -0
  25. package/app/api/version/route.ts +26 -0
  26. package/app/galaxy-canvas/page.client.tsx +2 -0
  27. package/app/galaxy-canvas/page.tsx +5 -0
  28. package/app/globals.css +5844 -4694
  29. package/app/icon.png +0 -0
  30. package/app/layout.tsx +1284 -467
  31. package/app/lib/auto-arrange.test.ts +158 -0
  32. package/app/lib/auto-arrange.ts +147 -0
  33. package/app/lib/canvas-export.ts +358 -358
  34. package/app/lib/canvas-text.ts +4 -72
  35. package/app/lib/canvas.ts +625 -564
  36. package/app/lib/card-arrangement.ts +21 -7
  37. package/app/lib/card-context-menu.tsx +2 -2
  38. package/app/lib/card-groups.ts +9 -2
  39. package/app/lib/cards.tsx +1361 -914
  40. package/app/lib/chat.tsx +65 -9
  41. package/app/lib/code-editor.ts +86 -2
  42. package/app/lib/connections.tsx +34 -43
  43. package/app/lib/context.test.ts +32 -0
  44. package/app/lib/context.ts +19 -3
  45. package/app/lib/cursor-sharing.ts +34 -0
  46. package/app/lib/events.tsx +76 -73
  47. package/app/lib/export-canvas.ts +287 -0
  48. package/app/lib/file-card-plugin.ts +148 -134
  49. package/app/lib/file-modal.tsx +49 -0
  50. package/app/lib/file-preview.ts +486 -400
  51. package/app/lib/github-import.test.ts +424 -0
  52. package/app/lib/global-search.ts +48 -27
  53. package/app/lib/initial-route-hydration.test.ts +283 -0
  54. package/app/lib/initial-route-hydration.ts +202 -0
  55. package/app/lib/landing-reset.test.ts +99 -0
  56. package/app/lib/landing-reset.ts +106 -0
  57. package/app/lib/landing-shell.test.ts +75 -0
  58. package/app/lib/large-repo-optimization.ts +37 -0
  59. package/app/lib/layers.tsx +17 -18
  60. package/app/lib/layout-snapshots.ts +320 -0
  61. package/app/lib/loading.test.ts +69 -0
  62. package/app/lib/loading.tsx +160 -45
  63. package/app/lib/mount-cleanup.test.ts +52 -0
  64. package/app/lib/mount-cleanup.ts +34 -0
  65. package/app/lib/mount-init.test.ts +123 -0
  66. package/app/lib/mount-init.ts +107 -0
  67. package/app/lib/mount-lifecycle.test.ts +39 -0
  68. package/app/lib/mount-lifecycle.ts +12 -0
  69. package/app/lib/mount-route-wiring.test.ts +87 -0
  70. package/app/lib/mount-route-wiring.ts +84 -0
  71. package/app/lib/multi-repo.ts +14 -0
  72. package/app/lib/onboarding-tutorial.ts +278 -0
  73. package/app/lib/perf-overlay.ts +78 -0
  74. package/app/lib/positions.ts +191 -122
  75. package/app/lib/recent-commits.test.ts +869 -0
  76. package/app/lib/recent-commits.ts +227 -0
  77. package/app/lib/repo-handoff.test.ts +23 -0
  78. package/app/lib/repo-handoff.ts +16 -0
  79. package/app/lib/repo-progressive.ts +119 -0
  80. package/app/lib/repo-select.test.ts +61 -0
  81. package/app/lib/repo-select.ts +74 -0
  82. package/app/lib/repo.tsx +1383 -977
  83. package/app/lib/role.ts +228 -0
  84. package/app/lib/route-catchall.test.ts +27 -0
  85. package/app/lib/route-repo-entry.test.ts +95 -0
  86. package/app/lib/route-repo-entry.ts +36 -0
  87. package/app/lib/router-contract.test.ts +22 -0
  88. package/app/lib/router-contract.ts +19 -0
  89. package/app/lib/shared-layout.test.ts +86 -0
  90. package/app/lib/shared-layout.ts +82 -0
  91. package/app/lib/shortcuts-panel.ts +2 -0
  92. package/app/lib/status-bar.test.ts +118 -0
  93. package/app/lib/status-bar.ts +365 -128
  94. package/app/lib/sync-controls.test.ts +43 -0
  95. package/app/lib/sync-controls.tsx +303 -0
  96. package/app/lib/test-dom.ts +145 -0
  97. package/app/lib/test-fixtures/router-contract/[...slug]/page.tsx +3 -0
  98. package/app/lib/test-fixtures/router-contract/api/health/route.ts +3 -0
  99. package/app/lib/test-fixtures/router-contract/api/version/route.ts +3 -0
  100. package/app/lib/test-fixtures/router-contract/galaxy-canvas/page.tsx +3 -0
  101. package/app/lib/test-fixtures/router-contract/page.tsx +3 -0
  102. package/app/lib/transclusion-smoke.test.ts +163 -0
  103. package/app/lib/tutorial.ts +301 -0
  104. package/app/lib/version.ts +93 -0
  105. package/app/lib/viewport-culling.ts +740 -728
  106. package/app/lib/virtual-files.ts +456 -0
  107. package/app/lib/webgl-text.ts +189 -0
  108. package/app/lib/{galaxydraw-bridge.ts → xydraw-bridge.ts} +485 -477
  109. package/app/lib/{galaxydraw.test.ts → xydraw.test.ts} +228 -229
  110. package/app/og-image.png +0 -0
  111. package/app/page.client.tsx +70 -215
  112. package/app/page.tsx +27 -92
  113. package/app/state/machine.js +13 -0
  114. package/banner.png +0 -0
  115. package/package.json +17 -8
  116. package/server.ts +11 -1
  117. package/app/api/connections/route.ts +0 -72
  118. package/app/api/positions/route.ts +0 -80
  119. package/app/api/repo/browse/route.ts +0 -55
  120. package/app/lib/pr-review.ts +0 -374
  121. package/packages/galaxydraw/README.md +0 -296
  122. package/packages/galaxydraw/banner.png +0 -0
  123. package/packages/galaxydraw/demo/build-static.ts +0 -100
  124. package/packages/galaxydraw/demo/client.ts +0 -154
  125. package/packages/galaxydraw/demo/dist/client.js +0 -8
  126. package/packages/galaxydraw/demo/index.html +0 -256
  127. package/packages/galaxydraw/demo/server.ts +0 -96
  128. package/packages/galaxydraw/dist/index.js +0 -984
  129. package/packages/galaxydraw/dist/index.js.map +0 -16
  130. package/packages/galaxydraw/node_modules/.bin/tsc.bunx +0 -0
  131. package/packages/galaxydraw/node_modules/.bin/tsc.exe +0 -0
  132. package/packages/galaxydraw/node_modules/.bin/tsserver.bunx +0 -0
  133. package/packages/galaxydraw/node_modules/.bin/tsserver.exe +0 -0
  134. package/packages/galaxydraw/package.json +0 -49
  135. package/packages/galaxydraw/perf.test.ts +0 -284
  136. package/packages/galaxydraw/src/core/cards.ts +0 -435
  137. package/packages/galaxydraw/src/core/engine.ts +0 -339
  138. package/packages/galaxydraw/src/core/events.ts +0 -81
  139. package/packages/galaxydraw/src/core/layout.ts +0 -136
  140. package/packages/galaxydraw/src/core/minimap.ts +0 -216
  141. package/packages/galaxydraw/src/core/state.ts +0 -177
  142. package/packages/galaxydraw/src/core/viewport.ts +0 -106
  143. package/packages/galaxydraw/src/galaxydraw.css +0 -166
  144. package/packages/galaxydraw/src/index.ts +0 -40
  145. package/packages/galaxydraw/tsconfig.json +0 -30
@@ -1,16 +1,17 @@
1
1
  // @ts-nocheck
2
2
  /**
3
- * Positions — load/save card positions with dual storage:
4
- * - Server-side (SQLite via /api/auth/positions) when logged in
5
- * - localStorage fallback when not authenticated
3
+ * Positions — load/save card positions with role-based storage:
4
+ * - Leader (localhost): SQLite primary + localStorage backup
5
+ * - Follower (remote): Read-only from server
6
6
  *
7
- * Enables shared repositories: each user has their own card layout.
7
+ * Leader can push to remote servers for followers to view.
8
8
  */
9
- import { measure } from 'measure-fn';
10
- import type { CanvasContext } from './context';
11
- import { getUser } from './user';
9
+ import { measure } from "measure-fn";
10
+ import type { CanvasContext } from "./context";
11
+ import { getUser } from "./user";
12
+ import { isLeader, isFollower } from "./role";
12
13
 
13
- const STORAGE_PREFIX = 'gitcanvas:positions:';
14
+ const STORAGE_PREFIX = "gitcanvas:positions:";
14
15
 
15
16
  /** Debounce timer for batched saves */
16
17
  let _saveTimer: any = null;
@@ -18,159 +19,227 @@ const SAVE_DEBOUNCE_MS = 300;
18
19
 
19
20
  // ─── Get the localStorage key for the current repo ───────
20
21
  function getStorageKey(ctx: CanvasContext): string | null {
21
- const repoPath = ctx.snap?.()?.context?.repoPath;
22
- if (!repoPath) return null;
23
- return `${STORAGE_PREFIX}${repoPath}`;
22
+ const repoPath = ctx.snap?.()?.context?.repoPath;
23
+ if (!repoPath) return null;
24
+ return `${STORAGE_PREFIX}${repoPath}`;
24
25
  }
25
26
 
26
27
  function getRepoPath(ctx: CanvasContext): string | null {
27
- return ctx.snap?.()?.context?.repoPath || null;
28
+ return ctx.snap?.()?.context?.repoPath || null;
28
29
  }
29
30
 
30
31
  // ─── Load all saved positions ────────────────────────────
31
32
  export async function loadSavedPositions(ctx: CanvasContext) {
32
- return measure('positions:load', async () => {
33
+ return measure("positions:load", async () => {
34
+ try {
35
+ const repoPath = getRepoPath(ctx);
36
+ if (!repoPath) return;
37
+
38
+ // Clear old positions first to prevent stale data
39
+ ctx.positions = new Map();
40
+
41
+ if (isLeader()) {
42
+ // Leader: Try SQLite first, then localStorage backup
33
43
  try {
34
- const repoPath = getRepoPath(ctx);
35
- if (!repoPath) return;
36
-
37
- // Clear old positions first to prevent stale data from
38
- // a previously loaded repo bleeding into the new one
39
- ctx.positions = new Map();
40
-
41
- // Try server-side first (if logged in)
42
- const user = getUser();
43
- if (user) {
44
- try {
45
- const res = await fetch(`/api/auth/positions?repo=${encodeURIComponent(repoPath)}`);
46
- const data = await res.json();
47
- if (data.positions) {
48
- ctx.positions = new Map(Object.entries(data.positions));
49
- // Migrate legacy expanded state from separate localStorage key
50
- _migrateLegacyExpanded(ctx, repoPath);
51
- return;
52
- }
53
- } catch { /* fall through to localStorage */ }
44
+ const res = await fetch(
45
+ `/api/auth/positions?repo=${encodeURIComponent(repoPath)}`,
46
+ );
47
+ if (res.ok) {
48
+ const data = await res.json();
49
+ if (data.positions) {
50
+ ctx.positions = new Map(Object.entries(data.positions));
51
+ _migrateLegacyExpanded(ctx, repoPath);
52
+ return;
54
53
  }
54
+ }
55
+ } catch {
56
+ /* fall through to localStorage */
57
+ }
55
58
 
56
- // Fallback: localStorage
57
- const key = getStorageKey(ctx);
58
- if (!key) return;
59
- const raw = localStorage.getItem(key);
60
- if (raw) {
61
- const data = JSON.parse(raw);
62
- ctx.positions = new Map(Object.entries(data));
59
+ // Fallback: localStorage
60
+ const key = getStorageKey(ctx);
61
+ if (key) {
62
+ const raw = localStorage.getItem(key);
63
+ if (raw) {
64
+ const data = JSON.parse(raw);
65
+ ctx.positions = new Map(Object.entries(data));
66
+ }
67
+ }
68
+ _migrateLegacyExpanded(ctx, repoPath);
69
+ } else {
70
+ // Follower: Read-only from server
71
+ try {
72
+ const res = await fetch(
73
+ `/api/auth/positions?repo=${encodeURIComponent(repoPath)}&readonly=true`,
74
+ );
75
+ if (res.ok) {
76
+ const data = await res.json();
77
+ if (data.positions) {
78
+ ctx.positions = new Map(Object.entries(data.positions));
63
79
  }
64
-
65
- // Migrate legacy expanded state from separate localStorage key
66
- _migrateLegacyExpanded(ctx, repoPath);
80
+ }
67
81
  } catch (e) {
68
- measure('positions:loadError', () => e);
82
+ console.warn(
83
+ "[positions] Failed to load from server (follower mode)",
84
+ e,
85
+ );
69
86
  }
70
- });
87
+ }
88
+ } catch (e) {
89
+ measure("positions:loadError", () => e);
90
+ }
91
+ });
71
92
  }
72
93
 
73
94
  // ─── Persist all positions (debounced) ───────────────────
74
- function flushPositions(ctx: CanvasContext) {
75
- const repoPath = getRepoPath(ctx);
76
- if (!repoPath) return;
95
+ export function flushPositions(ctx: CanvasContext) {
96
+ const repoPath = getRepoPath(ctx);
97
+ if (!repoPath) return;
77
98
 
78
- const obj: Record<string, any> = {};
79
- for (const [k, v] of ctx.positions) {
80
- obj[k] = v;
81
- }
99
+ const obj: Record<string, any> = {};
100
+ for (const [k, v] of ctx.positions) {
101
+ obj[k] = v;
102
+ }
82
103
 
83
- // Always save to localStorage (instant)
104
+ if (isLeader()) {
105
+ // Leader: Save to localStorage + SQLite
84
106
  try {
85
- const key = getStorageKey(ctx);
86
- if (key) localStorage.setItem(key, JSON.stringify(obj));
87
- } catch { }
88
-
89
- // Also sync to server if logged in (async, fire-and-forget)
90
- const user = getUser();
91
- if (user) {
92
- fetch('/api/auth/positions', {
93
- method: 'POST',
94
- headers: { 'Content-Type': 'application/json' },
95
- body: JSON.stringify({ repoUrl: repoPath, positions: obj }),
96
- }).catch(() => { /* silent — localStorage is the safety net */ });
107
+ const key = getStorageKey(ctx);
108
+ if (key) localStorage.setItem(key, JSON.stringify(obj));
109
+ } catch {}
110
+
111
+ // Sync to local server (async, fire-and-forget)
112
+ fetch("/api/auth/positions", {
113
+ method: "POST",
114
+ headers: { "Content-Type": "application/json" },
115
+ body: JSON.stringify({ repoUrl: repoPath, positions: obj }),
116
+ }).catch(() => {
117
+ /* silent localStorage is the safety net */
118
+ });
119
+
120
+ // Auto-sync to remote server if enabled
121
+ try {
122
+ const { isAutoSyncEnabled, pushToServer } = require("./sync-controls");
123
+ if (isAutoSyncEnabled()) {
124
+ pushToServer(repoPath, obj).catch(() => {
125
+ /* silent */
126
+ });
127
+ }
128
+ } catch {
129
+ /* sync-controls not loaded or auto-sync disabled */
97
130
  }
131
+ }
132
+ // Follower: No saves allowed (read-only)
98
133
  }
99
134
 
100
135
  // ─── Save a single card position (debounced) ─────────────
101
- export async function savePosition(ctx: CanvasContext, commitHash: string, filePath: string, x?: number, y?: number, width?: number, height?: number) {
102
- return measure('positions:save', async () => {
103
- try {
104
- const posKey = `${commitHash}:${filePath}`;
105
- const existing = ctx.positions.get(posKey) || {};
106
- const newPos = {
107
- x: x !== undefined ? x : existing.x,
108
- y: y !== undefined ? y : existing.y,
109
- width: width !== undefined ? width : existing.width,
110
- height: height !== undefined ? height : existing.height,
111
- expanded: existing.expanded, // preserve expanded flag
112
- };
113
- ctx.positions.set(posKey, newPos);
114
-
115
- // Debounced persist — avoid hammering storage on every drag frame
116
- if (_saveTimer) clearTimeout(_saveTimer);
117
- _saveTimer = setTimeout(() => flushPositions(ctx), SAVE_DEBOUNCE_MS);
118
- } catch (e) {
119
- measure('positions:saveError', () => e);
120
- }
121
- });
136
+ export async function savePosition(
137
+ ctx: CanvasContext,
138
+ commitHash: string,
139
+ filePath: string,
140
+ x?: number,
141
+ y?: number,
142
+ width?: number,
143
+ height?: number,
144
+ immediate = false,
145
+ ) {
146
+ // Follower: No saves allowed
147
+ if (isFollower()) {
148
+ console.warn(
149
+ "[positions] Cannot save positions in follower mode (read-only)",
150
+ );
151
+ return;
152
+ }
153
+
154
+ return measure("positions:save", async () => {
155
+ try {
156
+ const posKey = `${commitHash}:${filePath}`;
157
+ const existing = ctx.positions.get(posKey) || {};
158
+ const newPos = {
159
+ x: x !== undefined ? x : existing.x,
160
+ y: y !== undefined ? y : existing.y,
161
+ width: width !== undefined ? width : existing.width,
162
+ height: height !== undefined ? height : existing.height,
163
+ expanded: existing.expanded,
164
+ };
165
+ ctx.positions.set(posKey, newPos);
166
+
167
+ // Debounced persist (or immediate if requested)
168
+ if (_saveTimer) clearTimeout(_saveTimer);
169
+ if (immediate) {
170
+ flushPositions(ctx);
171
+ } else {
172
+ _saveTimer = setTimeout(() => flushPositions(ctx), SAVE_DEBOUNCE_MS);
173
+ }
174
+ } catch (e) {
175
+ measure("positions:saveError", () => e);
176
+ }
177
+ });
178
+ }
179
+
180
+ // ─── Force immediate save (no debounce) ──────────────────
181
+ export function flushPositionsImmediate(ctx: CanvasContext) {
182
+ if (_saveTimer) clearTimeout(_saveTimer);
183
+ flushPositions(ctx);
122
184
  }
123
185
 
124
186
  // ─── Position key helper ─────────────────────────────────
125
187
  export function getPositionKey(filePath: string, commitHash: string): string {
126
- return `${commitHash}:${filePath}`;
188
+ return `${commitHash}:${filePath}`;
127
189
  }
128
190
 
129
191
  // ─── Expanded state (unified with positions) ─────────────
130
192
 
131
193
  /** Check if a file path is saved as expanded in positions */
132
- export function isPathExpandedInPositions(ctx: CanvasContext, filePath: string): boolean {
133
- // Check allfiles key (primary)
134
- const key = `allfiles:${filePath}`;
135
- const pos = ctx.positions.get(key);
136
- return !!(pos && pos.expanded);
194
+ export function isPathExpandedInPositions(
195
+ ctx: CanvasContext,
196
+ filePath: string,
197
+ ): boolean {
198
+ // Check allfiles key (primary)
199
+ const key = `allfiles:${filePath}`;
200
+ const pos = ctx.positions.get(key);
201
+ return !!(pos && pos.expanded);
137
202
  }
138
203
 
139
204
  /** Mark a file path as expanded or collapsed in positions */
140
- export function setPathExpandedInPositions(ctx: CanvasContext, filePath: string, expanded: boolean) {
141
- const key = `allfiles:${filePath}`;
142
- const existing = ctx.positions.get(key) || {};
143
- ctx.positions.set(key, { ...existing, expanded });
144
- // Trigger debounced persist
145
- if (_saveTimer) clearTimeout(_saveTimer);
146
- _saveTimer = setTimeout(() => flushPositions(ctx), SAVE_DEBOUNCE_MS);
205
+ export function setPathExpandedInPositions(
206
+ ctx: CanvasContext,
207
+ filePath: string,
208
+ expanded: boolean,
209
+ ) {
210
+ const key = `allfiles:${filePath}`;
211
+ const existing = ctx.positions.get(key) || {};
212
+ ctx.positions.set(key, { ...existing, expanded });
213
+ // Trigger debounced persist
214
+ if (_saveTimer) clearTimeout(_saveTimer);
215
+ _saveTimer = setTimeout(() => flushPositions(ctx), SAVE_DEBOUNCE_MS);
147
216
  }
148
217
 
149
218
  /** Migrate legacy expanded state from separate localStorage key into positions */
150
219
  function _migrateLegacyExpanded(ctx: CanvasContext, repoPath: string) {
151
- const legacyKey = `gitcanvas:expanded:${repoPath}`;
152
- try {
153
- const raw = localStorage.getItem(legacyKey);
154
- if (!raw) return;
155
- const paths: string[] = JSON.parse(raw);
156
- if (!Array.isArray(paths) || paths.length === 0) return;
157
-
158
- let migrated = 0;
159
- for (const filePath of paths) {
160
- const posKey = `allfiles:${filePath}`;
161
- const existing = ctx.positions.get(posKey) || {};
162
- if (!existing.expanded) {
163
- ctx.positions.set(posKey, { ...existing, expanded: true });
164
- migrated++;
165
- }
166
- }
220
+ const legacyKey = `gitcanvas:expanded:${repoPath}`;
221
+ try {
222
+ const raw = localStorage.getItem(legacyKey);
223
+ if (!raw) return;
224
+ const paths: string[] = JSON.parse(raw);
225
+ if (!Array.isArray(paths) || paths.length === 0) return;
226
+
227
+ let migrated = 0;
228
+ for (const filePath of paths) {
229
+ const posKey = `allfiles:${filePath}`;
230
+ const existing = ctx.positions.get(posKey) || {};
231
+ if (!existing.expanded) {
232
+ ctx.positions.set(posKey, { ...existing, expanded: true });
233
+ migrated++;
234
+ }
235
+ }
167
236
 
168
- if (migrated > 0) {
169
- // Persist immediately to save the migration
170
- flushPositions(ctx);
171
- }
237
+ if (migrated > 0) {
238
+ // Persist immediately to save the migration
239
+ flushPositions(ctx);
240
+ }
172
241
 
173
- // Remove the legacy key
174
- localStorage.removeItem(legacyKey);
175
- } catch { }
242
+ // Remove the legacy key
243
+ localStorage.removeItem(legacyKey);
244
+ } catch {}
176
245
  }