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
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Tests for auto-arrange layout algorithms.
3
+ * Run with: bun test app/lib/auto-arrange.test.ts
4
+ */
5
+ import { describe, expect, test } from 'bun:test';
6
+ import { arrangeByDirectory } from './auto-arrange';
7
+
8
+ // ─── Helpers ────────────────────────────────────────────
9
+
10
+ function makeFiles(paths: string[]) {
11
+ return paths.map(path => ({ path, content: '' }));
12
+ }
13
+
14
+ // ─── arrangeByDirectory ─────────────────────────────────
15
+
16
+ describe('arrangeByDirectory', () => {
17
+ test('returns position for every file', () => {
18
+ const files = makeFiles(['src/a.ts', 'src/b.ts', 'lib/c.ts']);
19
+ const positions = arrangeByDirectory(files);
20
+ expect(positions.size).toBe(3);
21
+ expect(positions.has('src/a.ts')).toBe(true);
22
+ expect(positions.has('src/b.ts')).toBe(true);
23
+ expect(positions.has('lib/c.ts')).toBe(true);
24
+ });
25
+
26
+ test('groups files from same directory together', () => {
27
+ const files = makeFiles(['src/a.ts', 'src/b.ts', 'src/c.ts', 'lib/x.ts']);
28
+ const positions = arrangeByDirectory(files);
29
+
30
+ const srcPositions = ['src/a.ts', 'src/b.ts', 'src/c.ts'].map(p => positions.get(p)!);
31
+ const libPos = positions.get('lib/x.ts')!;
32
+
33
+ // src files should be close together (within one block)
34
+ const srcXs = srcPositions.map(p => p.x);
35
+ const srcYs = srcPositions.map(p => p.y);
36
+ const srcSpanX = Math.max(...srcXs) - Math.min(...srcXs);
37
+ const srcSpanY = Math.max(...srcYs) - Math.min(...srcYs);
38
+
39
+ // Span should be small — within one block (3 files, likely 2 cols)
40
+ expect(srcSpanX).toBeLessThan(2000);
41
+ expect(srcSpanY).toBeLessThan(2000);
42
+
43
+ // lib file should NOT overlap with src block
44
+ const srcMinX = Math.min(...srcXs);
45
+ const srcMaxX = Math.max(...srcXs) + 580; // card width
46
+ const srcMinY = Math.min(...srcYs);
47
+ const srcMaxY = Math.max(...srcYs) + 700; // card height
48
+
49
+ // lib should be outside the src bounding box (separated by dirGap)
50
+ const isOutside = libPos.x >= srcMaxX || libPos.x + 580 <= srcMinX ||
51
+ libPos.y >= srcMaxY || libPos.y + 700 <= srcMinY;
52
+ expect(isOutside).toBe(true);
53
+ });
54
+
55
+ test('handles single file', () => {
56
+ const files = makeFiles(['README.md']);
57
+ const positions = arrangeByDirectory(files);
58
+ expect(positions.size).toBe(1);
59
+ const pos = positions.get('README.md')!;
60
+ expect(pos.x).toBeGreaterThanOrEqual(0);
61
+ expect(pos.y).toBeGreaterThanOrEqual(0);
62
+ });
63
+
64
+ test('handles root-level files (no directory)', () => {
65
+ const files = makeFiles(['README.md', 'package.json', '.gitignore']);
66
+ const positions = arrangeByDirectory(files);
67
+ // All should be in the "." directory group
68
+ expect(positions.size).toBe(3);
69
+ });
70
+
71
+ test('no two files share the same position', () => {
72
+ const paths = [];
73
+ for (let i = 0; i < 50; i++) {
74
+ const dir = `dir${i % 5}`;
75
+ paths.push(`${dir}/file${i}.ts`);
76
+ }
77
+ const files = makeFiles(paths);
78
+ const positions = arrangeByDirectory(files);
79
+
80
+ const posStrings = new Set<string>();
81
+ for (const [, pos] of positions) {
82
+ const key = `${pos.x},${pos.y}`;
83
+ expect(posStrings.has(key)).toBe(false);
84
+ posStrings.add(key);
85
+ }
86
+ });
87
+
88
+ test('respects custom origin', () => {
89
+ const files = makeFiles(['a.ts']);
90
+ const positions = arrangeByDirectory(files, { originX: 500, originY: 300 });
91
+ const pos = positions.get('a.ts')!;
92
+ expect(pos.x).toBe(500);
93
+ expect(pos.y).toBe(300);
94
+ });
95
+
96
+ test('respects custom card dimensions', () => {
97
+ const files = makeFiles(['src/a.ts', 'src/b.ts']);
98
+ const positions = arrangeByDirectory(files, { cardWidth: 200, fileGap: 10 });
99
+ const posA = positions.get('src/a.ts')!;
100
+ const posB = positions.get('src/b.ts')!;
101
+
102
+ // With 2 files in 1 dir, they should be in a row
103
+ // Gap between them should be based on card width + gap
104
+ const dx = Math.abs(posB.x - posA.x);
105
+ expect(dx).toBe(210); // 200 + 10
106
+ });
107
+
108
+ test('larger directories get more columns', () => {
109
+ // 9 files should get ceil(sqrt(9)) = 3 columns
110
+ const files = makeFiles(Array.from({ length: 9 }, (_, i) => `src/f${i}.ts`));
111
+ const positions = arrangeByDirectory(files);
112
+
113
+ const xs = new Set<number>();
114
+ for (const [, pos] of positions) {
115
+ xs.add(pos.x);
116
+ }
117
+ expect(xs.size).toBe(3); // 3 unique x positions = 3 columns
118
+ });
119
+
120
+ test('handles deeply nested paths', () => {
121
+ const files = makeFiles([
122
+ 'src/components/ui/Button.tsx',
123
+ 'src/components/ui/Modal.tsx',
124
+ 'src/lib/utils/helpers.ts',
125
+ ]);
126
+ const positions = arrangeByDirectory(files);
127
+ expect(positions.size).toBe(3);
128
+
129
+ // Button and Modal should be grouped (same dir: src/components/ui)
130
+ const btnPos = positions.get('src/components/ui/Button.tsx')!;
131
+ const modalPos = positions.get('src/components/ui/Modal.tsx')!;
132
+ // They share a directory so should be adjacent
133
+ const distance = Math.hypot(modalPos.x - btnPos.x, modalPos.y - btnPos.y);
134
+ expect(distance).toBeLessThan(1500);
135
+ });
136
+
137
+ test('empty input returns empty map', () => {
138
+ const positions = arrangeByDirectory([]);
139
+ expect(positions.size).toBe(0);
140
+ });
141
+
142
+ test('directories sorted largest-first', () => {
143
+ // 5 files in "big/", 1 file in "small/"
144
+ const files = makeFiles([
145
+ 'big/a.ts', 'big/b.ts', 'big/c.ts', 'big/d.ts', 'big/e.ts',
146
+ 'small/x.ts',
147
+ ]);
148
+ const positions = arrangeByDirectory(files);
149
+
150
+ // "big" directory should come first (top-left area)
151
+ const bigMinX = Math.min(...['big/a.ts', 'big/b.ts', 'big/c.ts', 'big/d.ts', 'big/e.ts']
152
+ .map(p => positions.get(p)!.x));
153
+ const smallX = positions.get('small/x.ts')!.x;
154
+
155
+ // big should start at origin, small should be offset
156
+ expect(bigMinX).toBeLessThanOrEqual(smallX);
157
+ });
158
+ });
@@ -0,0 +1,147 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Auto-arrange algorithms for initial canvas layout.
4
+ *
5
+ * arrangeByDirectory: Groups files by their parent directory,
6
+ * lays out each directory as a tight grid block, then positions
7
+ * directory blocks in a treemap-like grid layout.
8
+ *
9
+ * This creates a spatially meaningful layout where files from
10
+ * the same directory are clustered together.
11
+ */
12
+ import { measure } from 'measure-fn';
13
+ import type { CanvasContext } from './context';
14
+ import { savePosition, flushPositions } from './positions';
15
+ import { updateMinimap } from './canvas';
16
+
17
+ interface FileEntry {
18
+ path: string;
19
+ [key: string]: any;
20
+ }
21
+
22
+ interface DirBlock {
23
+ dir: string;
24
+ files: FileEntry[];
25
+ // Computed layout
26
+ cols: number;
27
+ rows: number;
28
+ blockW: number;
29
+ blockH: number;
30
+ }
31
+
32
+ /**
33
+ * Arrange files grouped by directory.
34
+ * Each directory gets a tight grid block, and blocks are arranged
35
+ * in a larger treemap-like layout sorted by size (largest first).
36
+ */
37
+ export function arrangeByDirectory(
38
+ files: FileEntry[],
39
+ opts: {
40
+ cardWidth?: number;
41
+ cardHeight?: number;
42
+ fileGap?: number;
43
+ dirGap?: number;
44
+ originX?: number;
45
+ originY?: number;
46
+ } = {},
47
+ ): Map<string, { x: number; y: number }> {
48
+ const positions = new Map<string, { x: number; y: number }>();
49
+ const {
50
+ cardWidth = 580,
51
+ cardHeight = 700,
52
+ fileGap = 20,
53
+ dirGap = 80,
54
+ originX = 50,
55
+ originY = 50,
56
+ } = opts;
57
+
58
+ // Group files by parent directory
59
+ const dirMap = new Map<string, FileEntry[]>();
60
+ for (const f of files) {
61
+ const dir = f.path.includes('/') ? f.path.substring(0, f.path.lastIndexOf('/')) : '.';
62
+ if (!dirMap.has(dir)) dirMap.set(dir, []);
63
+ dirMap.get(dir)!.push(f);
64
+ }
65
+
66
+ // Sort directories: largest first, then alphabetical
67
+ const dirs = Array.from(dirMap.entries())
68
+ .sort((a, b) => b[1].length - a[1].length || a[0].localeCompare(b[0]));
69
+
70
+ // Compute each directory block dimensions
71
+ const blocks: DirBlock[] = dirs.map(([dir, dirFiles]) => {
72
+ const count = dirFiles.length;
73
+ const cols = Math.max(1, Math.min(count, Math.ceil(Math.sqrt(count))));
74
+ const rows = Math.ceil(count / cols);
75
+ return {
76
+ dir,
77
+ files: dirFiles,
78
+ cols,
79
+ rows,
80
+ blockW: cols * (cardWidth + fileGap) - fileGap,
81
+ blockH: rows * (cardHeight + fileGap) - fileGap,
82
+ };
83
+ });
84
+
85
+ // Lay out blocks in a wrapping row layout
86
+ // Target total width ~= sqrt(total area) for a square-ish result
87
+ const totalArea = blocks.reduce((sum, b) => sum + (b.blockW + dirGap) * (b.blockH + dirGap), 0);
88
+ const targetRowWidth = Math.max(3000, Math.sqrt(totalArea) * 1.2);
89
+
90
+ let curX = originX;
91
+ let curY = originY;
92
+ let rowMaxH = 0;
93
+
94
+ for (const block of blocks) {
95
+ // Wrap to next row if this block would exceed target width
96
+ if (curX > originX && curX + block.blockW > targetRowWidth) {
97
+ curX = originX;
98
+ curY += rowMaxH + dirGap;
99
+ rowMaxH = 0;
100
+ }
101
+
102
+ // Place files within this block
103
+ block.files.forEach((f, i) => {
104
+ const col = i % block.cols;
105
+ const row = Math.floor(i / block.cols);
106
+ const x = curX + col * (cardWidth + fileGap);
107
+ const y = curY + row * (cardHeight + fileGap);
108
+ positions.set(f.path, { x, y });
109
+ });
110
+
111
+ curX += block.blockW + dirGap;
112
+ rowMaxH = Math.max(rowMaxH, block.blockH);
113
+ }
114
+
115
+ return positions;
116
+ }
117
+
118
+ /**
119
+ * Apply auto-arrange to a CanvasContext — rearranges ALL files
120
+ * by directory grouping. Only sets positions for files that
121
+ * don't already have saved positions.
122
+ */
123
+ export function autoArrangeIfNew(
124
+ ctx: CanvasContext,
125
+ files: FileEntry[],
126
+ forceAll = false,
127
+ ): Map<string, { x: number; y: number }> {
128
+ return measure('arrange:byDirectory', () => {
129
+ const positions = arrangeByDirectory(files);
130
+ const commitHash = 'allfiles';
131
+
132
+ for (const [path, pos] of positions) {
133
+ const posKey = `${commitHash}:${path}`;
134
+ // Only override if no existing position (or forced)
135
+ if (forceAll || !ctx.positions.has(posKey)) {
136
+ savePosition(ctx, commitHash, path, pos.x, pos.y);
137
+ }
138
+ }
139
+
140
+ if (forceAll) {
141
+ flushPositions(ctx);
142
+ updateMinimap(ctx);
143
+ }
144
+
145
+ return positions;
146
+ });
147
+ }