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,477 +1,485 @@
1
- // @ts-nocheck
2
- /**
3
- * GalaxyDraw Bridge — Adapter between GitMaps and the galaxydraw engine.
4
- *
5
- * Wires galaxydraw's CanvasState + CardManager into the existing
6
- * server-rendered DOM and XState persistence layer.
7
- *
8
- * Architecture:
9
- * - CanvasState manages zoom/pan/transform (replaces manual math)
10
- * - CardManager creates/defers cards via FileCardPlugin + DiffCardPlugin
11
- * - XState actor remains source-of-truth for persistence
12
- * - Server-rendered DOM (#canvasViewport, #canvasContent) stays intact
13
- */
14
-
15
- import { CanvasState } from '../../packages/galaxydraw/src/core/state';
16
- import { CardManager } from '../../packages/galaxydraw/src/core/cards';
17
- import { EventBus } from '../../packages/galaxydraw/src/core/events';
18
- import { createFileCardPlugin, createDiffCardPlugin } from './file-card-plugin';
19
- import type { CanvasContext } from './context';
20
-
21
- /**
22
- * Shared galaxydraw state instance.
23
- * Replaces manual `ctx.canvas.style.transform = ...` calls.
24
- */
25
- let _gdState: CanvasState | null = null;
26
- let _cardManager: CardManager | null = null;
27
- let _eventBus: EventBus | null = null;
28
-
29
- /**
30
- * Initialize the galaxydraw state engine and bind to existing DOM.
31
- * Call this after ctx.canvas and ctx.canvasViewport are set.
32
- */
33
- export function initGalaxyDrawState(ctx: CanvasContext): CanvasState {
34
- _gdState = new CanvasState();
35
-
36
- if (ctx.canvasViewport && ctx.canvas) {
37
- _gdState.bind(ctx.canvasViewport, ctx.canvas);
38
- }
39
-
40
- // Sync initial state from XState
41
- const state = ctx.snap().context;
42
- if (state.zoom) _gdState.zoom = state.zoom;
43
- if (state.offsetX) _gdState.offsetX = state.offsetX;
44
- if (state.offsetY) _gdState.offsetY = state.offsetY;
45
- _gdState.applyTransform();
46
-
47
- return _gdState;
48
- }
49
-
50
- /**
51
- * Get the shared CanvasState instance.
52
- */
53
- export function getGalaxyDrawState(): CanvasState | null {
54
- return _gdState;
55
- }
56
-
57
- /**
58
- * Zoom toward a screen point using galaxydraw's engine,
59
- * then sync the computed state back to XState for persistence.
60
- *
61
- * @returns The new zoom/offset values (for callers that need them)
62
- */
63
- export function zoomTowardScreen(
64
- ctx: CanvasContext,
65
- screenX: number,
66
- screenY: number,
67
- factor: number,
68
- ): { zoom: number; offsetX: number; offsetY: number } {
69
- const gd = _gdState;
70
-
71
- if (gd) {
72
- // Delegate to galaxydraw engine
73
- gd.zoomToward(screenX, screenY, factor);
74
- // Sync back to XState for persistence
75
- ctx.actor.send({ type: 'SET_ZOOM', zoom: gd.zoom });
76
- ctx.actor.send({ type: 'SET_OFFSET', x: gd.offsetX, y: gd.offsetY });
77
- return { zoom: gd.zoom, offsetX: gd.offsetX, offsetY: gd.offsetY };
78
- }
79
-
80
- // Fallback: manual math (pre-bridge init)
81
- const state = ctx.snap().context;
82
- const rect = ctx.canvasViewport?.getBoundingClientRect();
83
- const mouseX = screenX - (rect?.left ?? 0);
84
- const mouseY = screenY - (rect?.top ?? 0);
85
- const newZoom = Math.min(3, Math.max(0.1, state.zoom * factor));
86
- const scale = newZoom / state.zoom;
87
- const newOffsetX = mouseX - (mouseX - state.offsetX) * scale;
88
- const newOffsetY = mouseY - (mouseY - state.offsetY) * scale;
89
- ctx.actor.send({ type: 'SET_ZOOM', zoom: newZoom });
90
- ctx.actor.send({ type: 'SET_OFFSET', x: newOffsetX, y: newOffsetY });
91
- return { zoom: newZoom, offsetX: newOffsetX, offsetY: newOffsetY };
92
- }
93
-
94
- /**
95
- * Pan by pixel delta via galaxydraw's engine.
96
- * Syncs back to XState for persistence.
97
- */
98
- export function panByDelta(
99
- ctx: CanvasContext,
100
- dx: number,
101
- dy: number,
102
- ): void {
103
- const gd = _gdState;
104
-
105
- if (gd) {
106
- gd.pan(dx, dy);
107
- ctx.actor.send({ type: 'SET_OFFSET', x: gd.offsetX, y: gd.offsetY });
108
- return;
109
- }
110
-
111
- // Fallback
112
- const state = ctx.snap().context;
113
- ctx.actor.send({ type: 'SET_OFFSET', x: state.offsetX + dx, y: state.offsetY + dy });
114
- }
115
-
116
- /**
117
- * Convert screen coordinates to world coordinates.
118
- * Delegates to CanvasState.screenToWorld() when available.
119
- */
120
- export function screenToWorld(
121
- ctx: CanvasContext,
122
- screenX: number,
123
- screenY: number,
124
- ): { x: number; y: number } {
125
- const gd = _gdState;
126
-
127
- if (gd) {
128
- return gd.screenToWorld(screenX, screenY);
129
- }
130
-
131
- // Fallback
132
- const state = ctx.snap().context;
133
- const rect = ctx.canvasViewport?.getBoundingClientRect();
134
- return {
135
- x: (screenX - (rect?.left ?? 0) - state.offsetX) / state.zoom,
136
- y: (screenY - (rect?.top ?? 0) - state.offsetY) / state.zoom,
137
- };
138
- }
139
-
140
- /**
141
- * Center the viewport on a world coordinate.
142
- * Delegates to CanvasState.panTo() when available.
143
- */
144
- export function panToWorld(
145
- ctx: CanvasContext,
146
- worldX: number,
147
- worldY: number,
148
- ): void {
149
- const gd = _gdState;
150
-
151
- if (gd) {
152
- gd.panTo(worldX, worldY);
153
- ctx.actor.send({ type: 'SET_OFFSET', x: gd.offsetX, y: gd.offsetY });
154
- return;
155
- }
156
-
157
- // Fallback
158
- const state = ctx.snap().context;
159
- const vp = ctx.canvasViewport;
160
- if (vp) {
161
- const vpW = vp.clientWidth;
162
- const vpH = vp.clientHeight;
163
- const newOffsetX = vpW / 2 - worldX * state.zoom;
164
- const newOffsetY = vpH / 2 - worldY * state.zoom;
165
- ctx.actor.send({ type: 'SET_OFFSET', x: newOffsetX, y: newOffsetY });
166
- }
167
- }
168
-
169
- // ─── Card Manager ───────────────────────────────────────
170
-
171
- /**
172
- * Initialize the CardManager with file card plugins.
173
- * Call after initGalaxyDrawState() when ctx.canvas is available.
174
- *
175
- * The CardManager handles:
176
- * - Card creation via plugins (FileCardPlugin, DiffCardPlugin)
177
- * - Drag, resize, z-order management
178
- * - Selection (single, multi)
179
- * - Deferred rendering (virtualization)
180
- */
181
- import { scheduleRenderConnections } from './connections';
182
-
183
- export function initCardManager(ctx: CanvasContext): CardManager | null {
184
- if (!_gdState || !ctx.canvas) {
185
- console.warn('[galaxydraw-bridge] Cannot init CardManager: state or canvas not ready');
186
- return null;
187
- }
188
-
189
- _eventBus = new EventBus();
190
- _cardManager = new CardManager(_gdState, _eventBus, ctx.canvas, {
191
- defaultWidth: 580,
192
- defaultHeight: 700,
193
- minWidth: 280,
194
- minHeight: 200,
195
- gridSize: 0,
196
- cornerSize: 40,
197
- });
198
-
199
- // Register plugins
200
- _cardManager.registerPlugin(createFileCardPlugin());
201
- _cardManager.registerPlugin(createDiffCardPlugin());
202
-
203
- // Sync card events back to XState for persistence
204
- _eventBus.on('card:move', (ev) => {
205
- const { id, x, y } = ev;
206
- ctx.actor.send({ type: 'SAVE_POSITION', path: id, x, y });
207
- scheduleRenderConnections(ctx);
208
- });
209
-
210
- _eventBus.on('card:resize', (ev) => {
211
- const { id, width, height } = ev;
212
- ctx.actor.send({ type: 'RESIZE_CARD', path: id, width, height });
213
- scheduleRenderConnections(ctx);
214
- });
215
-
216
- console.log('[galaxydraw-bridge] CardManager initialized with file + diff plugins');
217
- return _cardManager;
218
- }
219
-
220
- /**
221
- * Get the shared CardManager instance.
222
- */
223
- export function getCardManager(): CardManager | null {
224
- return _cardManager;
225
- }
226
-
227
- /**
228
- * Get the shared EventBus instance.
229
- */
230
- export function getEventBus(): EventBus | null {
231
- return _eventBus;
232
- }
233
-
234
- // ─── Card Creation via CardManager ──────────────────────
235
-
236
- import { FILE_CARD_TYPE, DIFF_CARD_TYPE } from './file-card-plugin';
237
- import { getActiveLayer } from './layers';
238
- import { updateHiddenUI } from './hidden-files';
239
- import type { ViewportRect } from '../../packages/galaxydraw/src/core/state';
240
-
241
- /**
242
- * Render all files on canvas using CardManager instead of direct DOM.
243
- *
244
- * This replaces the viewport culling logic in renderAllFilesOnCanvas():
245
- * - Cards in/near viewport CardManager.create() (immediate DOM)
246
- * - Cards outside viewport → CardManager.defer() (lazy materialization)
247
- * - On scroll/zoom materializeViewport() creates deferred cards
248
- *
249
- * Benefits over the legacy approach:
250
- * - Drag/resize/z-order handled uniformly by CardManager
251
- * - EventBus emits card:create/card:move/card:resize for persistence
252
- * - Cleaner separation between rendering and interaction
253
- */
254
- export function renderAllFilesViaCardManager(ctx: CanvasContext, files: any[]) {
255
- if (!_cardManager || !_gdState) {
256
- // Fallback to legacy if CardManager not initialized
257
- console.warn('[galaxydraw-bridge] CardManager not ready, falling back to legacy render');
258
- return false; // Signal caller to use legacy path
259
- }
260
-
261
- _cardManager.clear();
262
-
263
- // Also clear existing DOM cards, pills, and deferred state
264
- // Without this, layer switching leaves orphaned elements
265
- ctx.fileCards.forEach(card => card.remove());
266
- ctx.fileCards.clear();
267
- ctx.deferredCards.clear();
268
- ctx.canvas?.querySelectorAll('.dir-label').forEach(el => el.remove());
269
- ctx.canvas?.querySelectorAll('.file-pill').forEach(el => el.remove());
270
- // Clear pill tracking Map
271
- const { clearAllPills } = require('./viewport-culling');
272
- clearAllPills(ctx);
273
- if (ctx.svgOverlay) ctx.svgOverlay.innerHTML = '';
274
-
275
- const visibleFiles = files.filter(f => !ctx.hiddenFiles.has(f.path));
276
- updateHiddenUI(ctx);
277
-
278
- // Build changed file data map
279
- const changedFileDataMap = new Map<string, any>();
280
- if (ctx.commitFilesData) {
281
- ctx.commitFilesData.forEach(f => changedFileDataMap.set(f.path, f));
282
- }
283
-
284
- let layerFiles = visibleFiles;
285
- const activeLayer = getActiveLayer();
286
- if (activeLayer) {
287
- layerFiles = visibleFiles.filter(f => !!activeLayer.files[f.path]);
288
- } else {
289
- // Default layer: exclude files that have been moved to other layers
290
- const { isFileMovedFromDefault } = require('./layers');
291
- layerFiles = visibleFiles.filter(f => !isFileMovedFromDefault(f.path));
292
- }
293
-
294
- // Grid layout: square-ish
295
- const count = layerFiles.length;
296
- const cols = Math.max(1, Math.ceil(Math.sqrt(count)));
297
- const defaultCardWidth = 580;
298
- const defaultCardHeight = 700;
299
- const gap = 20;
300
- const cellW = defaultCardWidth + gap;
301
- const cellH = defaultCardHeight + gap;
302
-
303
- // Viewport rect for initial visibility check
304
- const MARGIN = 800;
305
- const state = _gdState.snapshot();
306
- const vpEl = ctx.canvasViewport;
307
- const vpW = vpEl?.clientWidth || window.innerWidth;
308
- const vpH = vpEl?.clientHeight || window.innerHeight;
309
- const zoom = state.zoom || 1;
310
- const offsetX = state.offsetX || 0;
311
- const offsetY = state.offsetY || 0;
312
- const worldLeft = (-offsetX - MARGIN) / zoom;
313
- const worldTop = (-offsetY - MARGIN) / zoom;
314
- const worldRight = (vpW - offsetX + MARGIN) / zoom;
315
- const worldBottom = (vpH - offsetY + MARGIN) / zoom;
316
-
317
- let createdCount = 0;
318
- let deferredCount = 0;
319
-
320
- // Cache XState state once outside the loop avoids N snapshots for N files
321
- const cachedCardSizes = ctx.snap().context.cardSizes || {};
322
-
323
- layerFiles.forEach((f, index) => {
324
- const posKey = `allfiles:${f.path}`;
325
- let x: number, y: number;
326
-
327
- if (ctx.positions.has(posKey)) {
328
- const pos = ctx.positions.get(posKey);
329
- x = pos.x; y = pos.y;
330
- } else {
331
- const col = index % cols;
332
- const row = Math.floor(index / cols);
333
- x = 50 + col * cellW;
334
- y = 50 + row * cellH;
335
- }
336
-
337
- // Get saved size (from cached snapshot — no per-file ctx.snap() call)
338
- let size = cachedCardSizes[f.path];
339
- if (!size && ctx.positions.has(posKey)) {
340
- const pos = ctx.positions.get(posKey);
341
- if (pos.width) size = { width: pos.width, height: pos.height };
342
- }
343
-
344
- // Merge diff/layer data
345
- let fileWithDiff = { ...f };
346
- if (activeLayer && activeLayer.files[fileWithDiff.path]) {
347
- fileWithDiff.layerSections = activeLayer.files[fileWithDiff.path].sections;
348
- }
349
-
350
- const isChanged = ctx.changedFilePaths.has(f.path);
351
- if (isChanged && changedFileDataMap.has(fileWithDiff.path)) {
352
- const diffData = changedFileDataMap.get(fileWithDiff.path);
353
- if (diffData.content) {
354
- fileWithDiff.content = diffData.content;
355
- fileWithDiff.lines = diffData.content.split('\n').length;
356
- }
357
- fileWithDiff.status = diffData.status;
358
- fileWithDiff.hunks = diffData.hunks;
359
-
360
- if (diffData.hunks?.length > 0) {
361
- const addedLines = new Set<number>();
362
- const deletedBeforeLine = new Map<number, string[]>();
363
- for (const hunk of diffData.hunks) {
364
- let newLine = hunk.newStart;
365
- let pendingDeleted: string[] = [];
366
- for (const l of hunk.lines) {
367
- if (l.type === 'add') {
368
- addedLines.add(newLine);
369
- if (pendingDeleted.length > 0) {
370
- const existing = deletedBeforeLine.get(newLine) || [];
371
- deletedBeforeLine.set(newLine, existing.concat(pendingDeleted));
372
- pendingDeleted = [];
373
- }
374
- newLine++;
375
- } else if (l.type === 'del') {
376
- pendingDeleted.push(l.content);
377
- } else {
378
- if (pendingDeleted.length > 0) {
379
- const existing = deletedBeforeLine.get(newLine) || [];
380
- deletedBeforeLine.set(newLine, existing.concat(pendingDeleted));
381
- pendingDeleted = [];
382
- }
383
- newLine++;
384
- }
385
- }
386
- if (pendingDeleted.length > 0) {
387
- const existing = deletedBeforeLine.get(newLine) || [];
388
- deletedBeforeLine.set(newLine, existing.concat(pendingDeleted));
389
- }
390
- }
391
- fileWithDiff.addedLines = addedLines;
392
- fileWithDiff.deletedBeforeLine = deletedBeforeLine;
393
- }
394
- }
395
-
396
- const cardData = {
397
- id: f.path,
398
- x, y,
399
- width: size?.width || defaultCardWidth,
400
- height: size?.height || defaultCardHeight,
401
- meta: { file: fileWithDiff, ctx, savedSize: size },
402
- };
403
-
404
- // Check if in viewport
405
- const inViewport =
406
- x + (size?.width || defaultCardWidth) > worldLeft &&
407
- x < worldRight &&
408
- y + (size?.height || defaultCardHeight) > worldTop &&
409
- y < worldBottom;
410
-
411
- if (inViewport) {
412
- const card = _cardManager!.create(FILE_CARD_TYPE, cardData);
413
- if (card) {
414
- // Sync to ctx.fileCards so minimap, fitAll, etc. can find it
415
- ctx.fileCards.set(f.path, card);
416
- // Apply change markers for diff highlighting
417
- if (isChanged) {
418
- card.classList.add('file-card--changed');
419
- card.dataset.changed = 'true';
420
- }
421
- }
422
- createdCount++;
423
- } else {
424
- _cardManager!.defer(FILE_CARD_TYPE, cardData);
425
- // Also store in ctx.deferredCards so minimap, fitAll, etc. can see ALL files
426
- ctx.deferredCards.set(f.path, {
427
- file: fileWithDiff, x, y,
428
- size: { width: cardData.width, height: cardData.height },
429
- isChanged,
430
- });
431
- deferredCount++;
432
- }
433
- });
434
-
435
- console.log(`[gd-bridge] ${createdCount} created, ${deferredCount} deferred (${layerFiles.length} total)`);
436
- return true; // Signal: we handled it
437
- }
438
-
439
- /**
440
- * Materialize deferred cards that are now in the viewport.
441
- * Call this on zoom/pan changes.
442
- */
443
- export function materializeViewport(ctx: CanvasContext): number {
444
- if (!_cardManager || !_gdState) return 0;
445
-
446
- const MARGIN = 800;
447
- const state = _gdState.snapshot();
448
- const vpEl = ctx.canvasViewport;
449
- const vpW = vpEl?.clientWidth || window.innerWidth;
450
- const vpH = vpEl?.clientHeight || window.innerHeight;
451
- const zoom = state.zoom || 1;
452
- const offsetX = state.offsetX || 0;
453
- const offsetY = state.offsetY || 0;
454
-
455
- const rect: ViewportRect = {
456
- left: (-offsetX - MARGIN) / zoom,
457
- top: (-offsetY - MARGIN) / zoom,
458
- right: (vpW - offsetX + MARGIN) / zoom,
459
- bottom: (vpH - offsetY + MARGIN) / zoom,
460
- };
461
-
462
- const count = _cardManager.materializeInRect(rect);
463
-
464
- // Sync newly materialized cards to ctx.fileCards for minimap/fitAll
465
- // AND remove from ctx.deferredCards so viewport-culling doesn't re-create them
466
- if (count > 0) {
467
- for (const [id, card] of _cardManager.cards) {
468
- if (!ctx.fileCards.has(id)) {
469
- ctx.fileCards.set(id, card);
470
- }
471
- // Remove from deferredCards to prevent duplicate creation by viewport-culling
472
- ctx.deferredCards.delete(id);
473
- }
474
- }
475
-
476
- return count;
477
- }
1
+ // @ts-nocheck
2
+ /**
3
+ * XyDraw Bridge — Adapter between GitMaps and the xydraw engine.
4
+ *
5
+ * Wires xydraw's CanvasState + CardManager into the existing
6
+ * server-rendered DOM and XState persistence layer.
7
+ *
8
+ * Architecture:
9
+ * - CanvasState manages zoom/pan/transform (replaces manual math)
10
+ * - CardManager creates/defers cards via FileCardPlugin + DiffCardPlugin
11
+ * - XState actor remains source-of-truth for persistence
12
+ * - Server-rendered DOM (#canvasViewport, #canvasContent) stays intact
13
+ */
14
+
15
+ import { CanvasState } from '../../packages/galaxydraw/src/core/state';
16
+ import { CardManager } from '../../packages/galaxydraw/src/core/cards';
17
+ import { EventBus } from '../../packages/galaxydraw/src/core/events';
18
+ import { createFileCardPlugin, createDiffCardPlugin } from './file-card-plugin';
19
+ import type { CanvasContext } from './context';
20
+
21
+ /**
22
+ * Shared xydraw state instance.
23
+ * Replaces manual `ctx.canvas.style.transform = ...` calls.
24
+ */
25
+ let _gdState: CanvasState | null = null;
26
+ let _cardManager: CardManager | null = null;
27
+ let _eventBus: EventBus | null = null;
28
+
29
+ /**
30
+ * Initialize the xydraw state engine and bind to existing DOM.
31
+ * Call this after ctx.canvas and ctx.canvasViewport are set.
32
+ */
33
+ export function initXyDrawState(ctx: CanvasContext): CanvasState {
34
+ _gdState = new CanvasState();
35
+
36
+ if (ctx.canvasViewport && ctx.canvas) {
37
+ _gdState.bind(ctx.canvasViewport, ctx.canvas);
38
+ }
39
+
40
+ // Sync initial state from XState
41
+ const state = ctx.snap().context;
42
+ if (state.zoom) _gdState.zoom = state.zoom;
43
+ if (state.offsetX) _gdState.offsetX = state.offsetX;
44
+ if (state.offsetY) _gdState.offsetY = state.offsetY;
45
+ _gdState.applyTransform();
46
+
47
+ return _gdState;
48
+ }
49
+
50
+ /**
51
+ * Get the shared CanvasState instance.
52
+ */
53
+ export function getXyDrawState(): CanvasState | null {
54
+ return _gdState;
55
+ }
56
+
57
+ export const initGalaxyDrawState = initXyDrawState;
58
+ export const getGalaxyDrawState = getXyDrawState;
59
+
60
+ /**
61
+ * Zoom toward a screen point using xydraw's engine,
62
+ * then sync the computed state back to XState for persistence.
63
+ *
64
+ * @returns The new zoom/offset values (for callers that need them)
65
+ */
66
+ export function zoomTowardScreen(
67
+ ctx: CanvasContext,
68
+ screenX: number,
69
+ screenY: number,
70
+ factor: number,
71
+ ): { zoom: number; offsetX: number; offsetY: number } {
72
+ const gd = _gdState;
73
+
74
+ if (gd) {
75
+ // Delegate to xydraw engine
76
+ gd.zoomToward(screenX, screenY, factor);
77
+ // Sync back to XState for persistence
78
+ ctx.actor.send({ type: 'SET_ZOOM', zoom: gd.zoom });
79
+ ctx.actor.send({ type: 'SET_OFFSET', x: gd.offsetX, y: gd.offsetY });
80
+ return { zoom: gd.zoom, offsetX: gd.offsetX, offsetY: gd.offsetY };
81
+ }
82
+
83
+ // Fallback: manual math (pre-bridge init)
84
+ const state = ctx.snap().context;
85
+ const rect = ctx.canvasViewport?.getBoundingClientRect();
86
+ const mouseX = screenX - (rect?.left ?? 0);
87
+ const mouseY = screenY - (rect?.top ?? 0);
88
+ const newZoom = Math.min(3, Math.max(0.1, state.zoom * factor));
89
+ const scale = newZoom / state.zoom;
90
+ const newOffsetX = mouseX - (mouseX - state.offsetX) * scale;
91
+ const newOffsetY = mouseY - (mouseY - state.offsetY) * scale;
92
+ ctx.actor.send({ type: 'SET_ZOOM', zoom: newZoom });
93
+ ctx.actor.send({ type: 'SET_OFFSET', x: newOffsetX, y: newOffsetY });
94
+ return { zoom: newZoom, offsetX: newOffsetX, offsetY: newOffsetY };
95
+ }
96
+
97
+ /**
98
+ * Pan by pixel delta via xydraw's engine.
99
+ * Syncs back to XState for persistence.
100
+ */
101
+ export function panByDelta(
102
+ ctx: CanvasContext,
103
+ dx: number,
104
+ dy: number,
105
+ ): void {
106
+ const gd = _gdState;
107
+
108
+ if (gd) {
109
+ gd.pan(dx, dy);
110
+ ctx.actor.send({ type: 'SET_OFFSET', x: gd.offsetX, y: gd.offsetY });
111
+ return;
112
+ }
113
+
114
+ // Fallback
115
+ const state = ctx.snap().context;
116
+ ctx.actor.send({ type: 'SET_OFFSET', x: state.offsetX + dx, y: state.offsetY + dy });
117
+ }
118
+
119
+ /**
120
+ * Convert screen coordinates to world coordinates.
121
+ * Delegates to CanvasState.screenToWorld() when available.
122
+ */
123
+ export function screenToWorld(
124
+ ctx: CanvasContext,
125
+ screenX: number,
126
+ screenY: number,
127
+ ): { x: number; y: number } {
128
+ const gd = _gdState;
129
+
130
+ if (gd) {
131
+ return gd.screenToWorld(screenX, screenY);
132
+ }
133
+
134
+ // Fallback
135
+ const state = ctx.snap().context;
136
+ const rect = ctx.canvasViewport?.getBoundingClientRect();
137
+ return {
138
+ x: (screenX - (rect?.left ?? 0) - state.offsetX) / state.zoom,
139
+ y: (screenY - (rect?.top ?? 0) - state.offsetY) / state.zoom,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Center the viewport on a world coordinate.
145
+ * Delegates to CanvasState.panTo() when available.
146
+ */
147
+ export function panToWorld(
148
+ ctx: CanvasContext,
149
+ worldX: number,
150
+ worldY: number,
151
+ ): void {
152
+ const gd = _gdState;
153
+
154
+ if (gd) {
155
+ gd.panTo(worldX, worldY);
156
+ ctx.actor.send({ type: 'SET_OFFSET', x: gd.offsetX, y: gd.offsetY });
157
+ return;
158
+ }
159
+
160
+ // Fallback
161
+ const state = ctx.snap().context;
162
+ const vp = ctx.canvasViewport;
163
+ if (vp) {
164
+ const vpW = vp.clientWidth;
165
+ const vpH = vp.clientHeight;
166
+ const newOffsetX = vpW / 2 - worldX * state.zoom;
167
+ const newOffsetY = vpH / 2 - worldY * state.zoom;
168
+ ctx.actor.send({ type: 'SET_OFFSET', x: newOffsetX, y: newOffsetY });
169
+ }
170
+ }
171
+
172
+ // ─── Card Manager ───────────────────────────────────────
173
+
174
+ /**
175
+ * Initialize the CardManager with file card plugins.
176
+ * Call after initXyDrawState() when ctx.canvas is available.
177
+ *
178
+ * The CardManager handles:
179
+ * - Card creation via plugins (FileCardPlugin, DiffCardPlugin)
180
+ * - Drag, resize, z-order management
181
+ * - Selection (single, multi)
182
+ * - Deferred rendering (virtualization)
183
+ */
184
+ import { scheduleRenderConnections } from './connections';
185
+
186
+ export function initCardManager(ctx: CanvasContext): CardManager | null {
187
+ if (!_gdState || !ctx.canvas) {
188
+ console.warn('[xydraw-bridge] Cannot init CardManager: state or canvas not ready');
189
+ return null;
190
+ }
191
+
192
+ _eventBus = new EventBus();
193
+ _cardManager = new CardManager(_gdState, _eventBus, ctx.canvas, {
194
+ defaultWidth: 580,
195
+ defaultHeight: 700,
196
+ minWidth: 280,
197
+ minHeight: 200,
198
+ gridSize: 0,
199
+ cornerSize: 40,
200
+ });
201
+
202
+ // Register plugins
203
+ _cardManager.registerPlugin(createFileCardPlugin());
204
+ _cardManager.registerPlugin(createDiffCardPlugin());
205
+
206
+ // Sync card events back to XState for persistence
207
+ _eventBus.on('card:move', (ev) => {
208
+ const { id, x, y } = ev;
209
+ ctx.actor.send({ type: 'SAVE_POSITION', path: id, x, y });
210
+ scheduleRenderConnections(ctx);
211
+ });
212
+
213
+ _eventBus.on('card:resize', (ev) => {
214
+ const { id, width, height } = ev;
215
+ ctx.actor.send({ type: 'RESIZE_CARD', path: id, width, height });
216
+ scheduleRenderConnections(ctx);
217
+ });
218
+
219
+ console.log('[xydraw-bridge] CardManager initialized with file + diff plugins');
220
+ return _cardManager;
221
+ }
222
+
223
+ /**
224
+ * Get the shared CardManager instance.
225
+ */
226
+ export function getCardManager(): CardManager | null {
227
+ return _cardManager;
228
+ }
229
+
230
+ /**
231
+ * Get the shared EventBus instance.
232
+ */
233
+ export function getEventBus(): EventBus | null {
234
+ return _eventBus;
235
+ }
236
+
237
+ // ─── Card Creation via CardManager ──────────────────────
238
+
239
+ import { FILE_CARD_TYPE, DIFF_CARD_TYPE } from './file-card-plugin';
240
+ import { getActiveLayer } from './layers';
241
+ import { updateHiddenUI } from './hidden-files';
242
+ import type { ViewportRect } from '../../packages/xydraw/src/core/state';
243
+
244
+ /**
245
+ * Render all files on canvas using CardManager instead of direct DOM.
246
+ *
247
+ * This replaces the viewport culling logic in renderAllFilesOnCanvas():
248
+ * - Cards in/near viewport → CardManager.create() (immediate DOM)
249
+ * - Cards outside viewport → CardManager.defer() (lazy materialization)
250
+ * - On scroll/zoom materializeViewport() creates deferred cards
251
+ *
252
+ * Benefits over the legacy approach:
253
+ * - Drag/resize/z-order handled uniformly by CardManager
254
+ * - EventBus emits card:create/card:move/card:resize for persistence
255
+ * - Cleaner separation between rendering and interaction
256
+ */
257
+ export function renderAllFilesViaCardManager(ctx: CanvasContext, files: any[]) {
258
+ if (!_cardManager || !_gdState) {
259
+ // Fallback to legacy if CardManager not initialized
260
+ console.warn('[xydraw-bridge] CardManager not ready, falling back to legacy render');
261
+ return false; // Signal caller to use legacy path
262
+ }
263
+
264
+ _cardManager.clear();
265
+
266
+ // Also clear existing DOM cards, pills, and deferred state
267
+ // Without this, layer switching leaves orphaned elements
268
+ ctx.fileCards.forEach(card => card.remove());
269
+ ctx.fileCards.clear();
270
+ ctx.deferredCards.clear();
271
+ ctx.canvas?.querySelectorAll('.dir-label').forEach(el => el.remove());
272
+ ctx.canvas?.querySelectorAll('.file-pill').forEach(el => el.remove());
273
+ // Clear pill tracking Map
274
+ const { clearAllPills } = require('./viewport-culling');
275
+ clearAllPills(ctx);
276
+ if (ctx.svgOverlay) ctx.svgOverlay.innerHTML = '';
277
+
278
+ const { resetCardGroups, restoreCollapsedDirs } = require('./card-groups');
279
+ resetCardGroups();
280
+
281
+ const visibleFiles = files.filter(f => !ctx.hiddenFiles.has(f.path));
282
+ updateHiddenUI(ctx);
283
+
284
+ // Build changed file data map
285
+ const changedFileDataMap = new Map<string, any>();
286
+ if (ctx.commitFilesData) {
287
+ ctx.commitFilesData.forEach(f => changedFileDataMap.set(f.path, f));
288
+ }
289
+
290
+ let layerFiles = visibleFiles;
291
+ const activeLayer = getActiveLayer();
292
+ if (activeLayer) {
293
+ layerFiles = visibleFiles.filter(f => !!activeLayer.files[f.path]);
294
+ } else {
295
+ // Default layer: exclude files that have been moved to other layers
296
+ const { isFileMovedFromDefault } = require('./layers');
297
+ layerFiles = visibleFiles.filter(f => !isFileMovedFromDefault(f.path));
298
+ }
299
+
300
+ // Grid layout: square-ish
301
+ const count = layerFiles.length;
302
+ const cols = Math.max(1, Math.ceil(Math.sqrt(count)));
303
+ const defaultCardWidth = 580;
304
+ const defaultCardHeight = 700;
305
+ const gap = 20;
306
+ const cellW = defaultCardWidth + gap;
307
+ const cellH = defaultCardHeight + gap;
308
+
309
+ // Viewport rect for initial visibility check
310
+ const MARGIN = 800;
311
+ const state = _gdState.snapshot();
312
+ const vpEl = ctx.canvasViewport;
313
+ const vpW = vpEl?.clientWidth || window.innerWidth;
314
+ const vpH = vpEl?.clientHeight || window.innerHeight;
315
+ const zoom = state.zoom || 1;
316
+ const offsetX = state.offsetX || 0;
317
+ const offsetY = state.offsetY || 0;
318
+ const worldLeft = (-offsetX - MARGIN) / zoom;
319
+ const worldTop = (-offsetY - MARGIN) / zoom;
320
+ const worldRight = (vpW - offsetX + MARGIN) / zoom;
321
+ const worldBottom = (vpH - offsetY + MARGIN) / zoom;
322
+
323
+ let createdCount = 0;
324
+ let deferredCount = 0;
325
+
326
+ // Cache XState state once outside the loop — avoids N snapshots for N files
327
+ const cachedCardSizes = ctx.snap().context.cardSizes || {};
328
+
329
+ layerFiles.forEach((f, index) => {
330
+ const posKey = `allfiles:${f.path}`;
331
+ let x: number, y: number;
332
+
333
+ if (ctx.positions.has(posKey)) {
334
+ const pos = ctx.positions.get(posKey);
335
+ x = pos.x; y = pos.y;
336
+ } else {
337
+ const col = index % cols;
338
+ const row = Math.floor(index / cols);
339
+ x = 50 + col * cellW;
340
+ y = 50 + row * cellH;
341
+ }
342
+
343
+ // Get saved size (from cached snapshot — no per-file ctx.snap() call)
344
+ let size = cachedCardSizes[f.path];
345
+ if (!size && ctx.positions.has(posKey)) {
346
+ const pos = ctx.positions.get(posKey);
347
+ if (pos.width) size = { width: pos.width, height: pos.height };
348
+ }
349
+
350
+ // Merge diff/layer data
351
+ let fileWithDiff = { ...f };
352
+ if (activeLayer && activeLayer.files[fileWithDiff.path]) {
353
+ fileWithDiff.layerSections = activeLayer.files[fileWithDiff.path].sections;
354
+ }
355
+
356
+ const isChanged = ctx.changedFilePaths.has(f.path);
357
+ if (isChanged && changedFileDataMap.has(fileWithDiff.path)) {
358
+ const diffData = changedFileDataMap.get(fileWithDiff.path);
359
+ if (diffData.content) {
360
+ fileWithDiff.content = diffData.content;
361
+ fileWithDiff.lines = diffData.content.split('\n').length;
362
+ }
363
+ fileWithDiff.status = diffData.status;
364
+ fileWithDiff.hunks = diffData.hunks;
365
+
366
+ if (diffData.hunks?.length > 0) {
367
+ const addedLines = new Set<number>();
368
+ const deletedBeforeLine = new Map<number, string[]>();
369
+ for (const hunk of diffData.hunks) {
370
+ let newLine = hunk.newStart;
371
+ let pendingDeleted: string[] = [];
372
+ for (const l of hunk.lines) {
373
+ if (l.type === 'add') {
374
+ addedLines.add(newLine);
375
+ if (pendingDeleted.length > 0) {
376
+ const existing = deletedBeforeLine.get(newLine) || [];
377
+ deletedBeforeLine.set(newLine, existing.concat(pendingDeleted));
378
+ pendingDeleted = [];
379
+ }
380
+ newLine++;
381
+ } else if (l.type === 'del') {
382
+ pendingDeleted.push(l.content);
383
+ } else {
384
+ if (pendingDeleted.length > 0) {
385
+ const existing = deletedBeforeLine.get(newLine) || [];
386
+ deletedBeforeLine.set(newLine, existing.concat(pendingDeleted));
387
+ pendingDeleted = [];
388
+ }
389
+ newLine++;
390
+ }
391
+ }
392
+ if (pendingDeleted.length > 0) {
393
+ const existing = deletedBeforeLine.get(newLine) || [];
394
+ deletedBeforeLine.set(newLine, existing.concat(pendingDeleted));
395
+ }
396
+ }
397
+ fileWithDiff.addedLines = addedLines;
398
+ fileWithDiff.deletedBeforeLine = deletedBeforeLine;
399
+ }
400
+ }
401
+
402
+ const cardData = {
403
+ id: f.path,
404
+ x, y,
405
+ width: size?.width || defaultCardWidth,
406
+ height: size?.height || defaultCardHeight,
407
+ meta: { file: fileWithDiff, ctx, savedSize: size },
408
+ };
409
+
410
+ // Check if in viewport
411
+ const inViewport =
412
+ x + (size?.width || defaultCardWidth) > worldLeft &&
413
+ x < worldRight &&
414
+ y + (size?.height || defaultCardHeight) > worldTop &&
415
+ y < worldBottom;
416
+
417
+ if (inViewport) {
418
+ const card = _cardManager!.create(FILE_CARD_TYPE, cardData);
419
+ if (card) {
420
+ // Sync to ctx.fileCards so minimap, fitAll, etc. can find it
421
+ ctx.fileCards.set(f.path, card);
422
+ // Apply change markers for diff highlighting
423
+ if (isChanged) {
424
+ card.classList.add('file-card--changed');
425
+ card.dataset.changed = 'true';
426
+ }
427
+ }
428
+ createdCount++;
429
+ } else {
430
+ _cardManager!.defer(FILE_CARD_TYPE, cardData);
431
+ // Also store in ctx.deferredCards so minimap, fitAll, etc. can see ALL files
432
+ ctx.deferredCards.set(f.path, {
433
+ file: fileWithDiff, x, y,
434
+ size: { width: cardData.width, height: cardData.height },
435
+ isChanged,
436
+ });
437
+ deferredCount++;
438
+ }
439
+ });
440
+
441
+ restoreCollapsedDirs(ctx);
442
+
443
+ console.log(`[gd-bridge] ${createdCount} created, ${deferredCount} deferred (${layerFiles.length} total)`);
444
+ return true; // Signal: we handled it
445
+ }
446
+
447
+ /**
448
+ * Materialize deferred cards that are now in the viewport.
449
+ * Call this on zoom/pan changes.
450
+ */
451
+ export function materializeViewport(ctx: CanvasContext): number {
452
+ if (!_cardManager || !_gdState) return 0;
453
+
454
+ const MARGIN = 800;
455
+ const state = _gdState.snapshot();
456
+ const vpEl = ctx.canvasViewport;
457
+ const vpW = vpEl?.clientWidth || window.innerWidth;
458
+ const vpH = vpEl?.clientHeight || window.innerHeight;
459
+ const zoom = state.zoom || 1;
460
+ const offsetX = state.offsetX || 0;
461
+ const offsetY = state.offsetY || 0;
462
+
463
+ const rect: ViewportRect = {
464
+ left: (-offsetX - MARGIN) / zoom,
465
+ top: (-offsetY - MARGIN) / zoom,
466
+ right: (vpW - offsetX + MARGIN) / zoom,
467
+ bottom: (vpH - offsetY + MARGIN) / zoom,
468
+ };
469
+
470
+ const count = _cardManager.materializeInRect(rect);
471
+
472
+ // Sync newly materialized cards to ctx.fileCards for minimap/fitAll
473
+ // AND remove from ctx.deferredCards so viewport-culling doesn't re-create them
474
+ if (count > 0) {
475
+ for (const [id, card] of _cardManager.cards) {
476
+ if (!ctx.fileCards.has(id)) {
477
+ ctx.fileCards.set(id, card);
478
+ }
479
+ // Remove from deferredCards to prevent duplicate creation by viewport-culling
480
+ ctx.deferredCards.delete(id);
481
+ }
482
+ }
483
+
484
+ return count;
485
+ }