gitmaps 1.1.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 (121) hide show
  1. package/README.md +267 -118
  2. package/app/[...slug]/page.client.tsx +1 -0
  3. package/app/[...slug]/page.tsx +6 -0
  4. package/app/analytics.db +0 -0
  5. package/app/api/analytics/route.ts +64 -0
  6. package/app/api/auth/positions/route.ts +95 -33
  7. package/app/api/build-info/route.ts +19 -0
  8. package/app/api/chat/route.ts +13 -2
  9. package/app/api/og-image/route.ts +14 -0
  10. package/app/api/repo/file-content/route.ts +73 -20
  11. package/app/api/repo/load/route.test.ts +62 -0
  12. package/app/api/repo/load/route.ts +41 -1
  13. package/app/api/repo/pdf-thumb/route.ts +127 -0
  14. package/app/api/repo/resolve-slug/route.ts +51 -0
  15. package/app/api/repo/tree/route.ts +188 -104
  16. package/app/api/version/route.ts +26 -0
  17. package/app/globals.css +5706 -4938
  18. package/app/layout.tsx +1279 -490
  19. package/app/lib/auto-arrange.test.ts +158 -0
  20. package/app/lib/auto-arrange.ts +147 -0
  21. package/app/lib/canvas-export.ts +358 -358
  22. package/app/lib/canvas.ts +625 -564
  23. package/app/lib/cards.tsx +1361 -916
  24. package/app/lib/chat.tsx +65 -9
  25. package/app/lib/code-editor.ts +86 -2
  26. package/app/lib/context.test.ts +32 -0
  27. package/app/lib/context.ts +19 -3
  28. package/app/lib/cursor-sharing.ts +34 -0
  29. package/app/lib/events.tsx +71 -93
  30. package/app/lib/export-canvas.ts +287 -0
  31. package/app/lib/file-card-plugin.ts +148 -148
  32. package/app/lib/file-modal.tsx +49 -0
  33. package/app/lib/file-preview.ts +486 -427
  34. package/app/lib/github-import.test.ts +424 -0
  35. package/app/lib/initial-route-hydration.test.ts +283 -0
  36. package/app/lib/initial-route-hydration.ts +202 -0
  37. package/app/lib/landing-reset.test.ts +99 -0
  38. package/app/lib/landing-reset.ts +106 -0
  39. package/app/lib/landing-shell.test.ts +75 -0
  40. package/app/lib/large-repo-optimization.ts +37 -0
  41. package/app/lib/layout-snapshots.ts +320 -0
  42. package/app/lib/loading.test.ts +69 -0
  43. package/app/lib/loading.tsx +160 -45
  44. package/app/lib/mount-cleanup.test.ts +52 -0
  45. package/app/lib/mount-cleanup.ts +34 -0
  46. package/app/lib/mount-init.test.ts +123 -0
  47. package/app/lib/mount-init.ts +107 -0
  48. package/app/lib/mount-lifecycle.test.ts +39 -0
  49. package/app/lib/mount-lifecycle.ts +12 -0
  50. package/app/lib/mount-route-wiring.test.ts +87 -0
  51. package/app/lib/mount-route-wiring.ts +84 -0
  52. package/app/lib/multi-repo.ts +14 -0
  53. package/app/lib/onboarding-tutorial.ts +278 -0
  54. package/app/lib/positions.ts +190 -121
  55. package/app/lib/recent-commits.test.ts +869 -0
  56. package/app/lib/recent-commits.ts +227 -0
  57. package/app/lib/repo-handoff.test.ts +23 -0
  58. package/app/lib/repo-handoff.ts +16 -0
  59. package/app/lib/repo-progressive.ts +119 -0
  60. package/app/lib/repo-select.test.ts +61 -0
  61. package/app/lib/repo-select.ts +74 -0
  62. package/app/lib/repo.tsx +1383 -987
  63. package/app/lib/role.ts +228 -0
  64. package/app/lib/route-catchall.test.ts +27 -0
  65. package/app/lib/route-repo-entry.test.ts +95 -0
  66. package/app/lib/route-repo-entry.ts +36 -0
  67. package/app/lib/router-contract.test.ts +22 -0
  68. package/app/lib/router-contract.ts +19 -0
  69. package/app/lib/shared-layout.test.ts +86 -0
  70. package/app/lib/shared-layout.ts +82 -0
  71. package/app/lib/status-bar.test.ts +118 -0
  72. package/app/lib/status-bar.ts +365 -128
  73. package/app/lib/sync-controls.test.ts +43 -0
  74. package/app/lib/sync-controls.tsx +303 -0
  75. package/app/lib/test-dom.ts +145 -0
  76. package/app/lib/test-fixtures/router-contract/[...slug]/page.tsx +3 -0
  77. package/app/lib/test-fixtures/router-contract/api/health/route.ts +3 -0
  78. package/app/lib/test-fixtures/router-contract/api/version/route.ts +3 -0
  79. package/app/lib/test-fixtures/router-contract/galaxy-canvas/page.tsx +3 -0
  80. package/app/lib/test-fixtures/router-contract/page.tsx +3 -0
  81. package/app/lib/transclusion-smoke.test.ts +163 -0
  82. package/app/lib/tutorial.ts +301 -0
  83. package/app/lib/version.ts +93 -0
  84. package/app/lib/viewport-culling.ts +740 -735
  85. package/app/lib/virtual-files.ts +456 -0
  86. package/app/lib/webgl-text.ts +189 -0
  87. package/app/lib/{galaxydraw-bridge.ts → xydraw-bridge.ts} +485 -482
  88. package/app/lib/{galaxydraw.test.ts → xydraw.test.ts} +228 -229
  89. package/app/og-image.png +0 -0
  90. package/app/page.client.tsx +70 -269
  91. package/app/page.tsx +15 -16
  92. package/app/state/machine.js +13 -0
  93. package/package.json +16 -7
  94. package/server.ts +10 -0
  95. package/app/[owner]/[repo]/page.tsx +0 -6
  96. package/app/[slug]/page.tsx +0 -6
  97. package/packages/galaxydraw/README.md +0 -296
  98. package/packages/galaxydraw/banner.png +0 -0
  99. package/packages/galaxydraw/demo/build-static.ts +0 -100
  100. package/packages/galaxydraw/demo/client.ts +0 -154
  101. package/packages/galaxydraw/demo/dist/client.js +0 -8
  102. package/packages/galaxydraw/demo/index.html +0 -256
  103. package/packages/galaxydraw/demo/server.ts +0 -96
  104. package/packages/galaxydraw/dist/index.js +0 -984
  105. package/packages/galaxydraw/dist/index.js.map +0 -16
  106. package/packages/galaxydraw/node_modules/.bin/tsc.bunx +0 -0
  107. package/packages/galaxydraw/node_modules/.bin/tsc.exe +0 -0
  108. package/packages/galaxydraw/node_modules/.bin/tsserver.bunx +0 -0
  109. package/packages/galaxydraw/node_modules/.bin/tsserver.exe +0 -0
  110. package/packages/galaxydraw/package.json +0 -49
  111. package/packages/galaxydraw/perf.test.ts +0 -284
  112. package/packages/galaxydraw/src/core/cards.ts +0 -435
  113. package/packages/galaxydraw/src/core/engine.ts +0 -339
  114. package/packages/galaxydraw/src/core/events.ts +0 -81
  115. package/packages/galaxydraw/src/core/layout.ts +0 -136
  116. package/packages/galaxydraw/src/core/minimap.ts +0 -216
  117. package/packages/galaxydraw/src/core/state.ts +0 -177
  118. package/packages/galaxydraw/src/core/viewport.ts +0 -106
  119. package/packages/galaxydraw/src/galaxydraw.css +0 -166
  120. package/packages/galaxydraw/src/index.ts +0 -40
  121. 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
+ }