gitmaps 1.1.0 → 1.1.2
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.
- package/README.md +267 -118
- package/app/[...slug]/page.client.tsx +1 -0
- package/app/[...slug]/page.tsx +6 -0
- package/app/analytics.db +0 -0
- package/app/api/analytics/route.ts +64 -0
- package/app/api/auth/positions/route.ts +95 -33
- package/app/api/build-info/route.ts +19 -0
- package/app/api/chat/route.ts +13 -2
- package/app/api/og-image/route.ts +14 -0
- package/app/api/repo/file-content/route.ts +73 -20
- package/app/api/repo/load/route.test.ts +62 -0
- package/app/api/repo/load/route.ts +41 -1
- package/app/api/repo/pdf-thumb/route.ts +127 -0
- package/app/api/repo/resolve-slug/route.ts +51 -0
- package/app/api/repo/tree/route.ts +188 -104
- package/app/api/version/route.ts +26 -0
- package/app/globals.css +5706 -4938
- package/app/layout.tsx +1279 -490
- package/app/lib/auto-arrange.test.ts +158 -0
- package/app/lib/auto-arrange.ts +147 -0
- package/app/lib/canvas-export.ts +358 -358
- package/app/lib/canvas.ts +625 -564
- package/app/lib/cards.tsx +1361 -916
- package/app/lib/chat.tsx +65 -9
- package/app/lib/code-editor.ts +86 -2
- package/app/lib/context.test.ts +32 -0
- package/app/lib/context.ts +19 -3
- package/app/lib/cursor-sharing.ts +34 -0
- package/app/lib/events.tsx +71 -93
- package/app/lib/export-canvas.ts +287 -0
- package/app/lib/file-card-plugin.ts +148 -148
- package/app/lib/file-modal.tsx +49 -0
- package/app/lib/file-preview.ts +486 -427
- package/app/lib/github-import.test.ts +424 -0
- package/app/lib/initial-route-hydration.test.ts +283 -0
- package/app/lib/initial-route-hydration.ts +202 -0
- package/app/lib/landing-reset.test.ts +99 -0
- package/app/lib/landing-reset.ts +106 -0
- package/app/lib/landing-shell.test.ts +75 -0
- package/app/lib/large-repo-optimization.ts +37 -0
- package/app/lib/layout-snapshots.ts +320 -0
- package/app/lib/loading.test.ts +69 -0
- package/app/lib/loading.tsx +160 -45
- package/app/lib/mount-cleanup.test.ts +52 -0
- package/app/lib/mount-cleanup.ts +34 -0
- package/app/lib/mount-init.test.ts +123 -0
- package/app/lib/mount-init.ts +107 -0
- package/app/lib/mount-lifecycle.test.ts +39 -0
- package/app/lib/mount-lifecycle.ts +12 -0
- package/app/lib/mount-route-wiring.test.ts +87 -0
- package/app/lib/mount-route-wiring.ts +84 -0
- package/app/lib/multi-repo.ts +14 -0
- package/app/lib/onboarding-tutorial.ts +278 -0
- package/app/lib/positions.ts +190 -121
- package/app/lib/recent-commits.test.ts +947 -0
- package/app/lib/recent-commits.ts +227 -0
- package/app/lib/repo-handoff.test.ts +23 -0
- package/app/lib/repo-handoff.ts +16 -0
- package/app/lib/repo-progressive.ts +119 -0
- package/app/lib/repo-select.test.ts +61 -0
- package/app/lib/repo-select.ts +74 -0
- package/app/lib/repo.tsx +1383 -987
- package/app/lib/role.ts +228 -0
- package/app/lib/route-catchall.test.ts +27 -0
- package/app/lib/route-repo-entry.test.ts +95 -0
- package/app/lib/route-repo-entry.ts +36 -0
- package/app/lib/router-contract.test.ts +22 -0
- package/app/lib/router-contract.ts +19 -0
- package/app/lib/shared-layout.test.ts +86 -0
- package/app/lib/shared-layout.ts +82 -0
- package/app/lib/status-bar.test.ts +118 -0
- package/app/lib/status-bar.ts +365 -128
- package/app/lib/sync-controls.test.ts +43 -0
- package/app/lib/sync-controls.tsx +303 -0
- package/app/lib/test-dom.ts +145 -0
- package/app/lib/test-fixtures/router-contract/[...slug]/page.tsx +3 -0
- package/app/lib/test-fixtures/router-contract/api/health/route.ts +3 -0
- package/app/lib/test-fixtures/router-contract/api/version/route.ts +3 -0
- package/app/lib/test-fixtures/router-contract/galaxy-canvas/page.tsx +3 -0
- package/app/lib/test-fixtures/router-contract/page.tsx +3 -0
- package/app/lib/transclusion-smoke.test.ts +163 -0
- package/app/lib/tutorial.ts +301 -0
- package/app/lib/version.ts +93 -0
- package/app/lib/viewport-culling.ts +740 -735
- package/app/lib/virtual-files.ts +456 -0
- package/app/lib/webgl-text.ts +189 -0
- package/app/lib/{galaxydraw-bridge.ts → xydraw-bridge.ts} +485 -482
- package/app/lib/{galaxydraw.test.ts → xydraw.test.ts} +228 -229
- package/app/og-image.png +0 -0
- package/app/page.client.tsx +70 -269
- package/app/page.tsx +15 -16
- package/app/state/machine.js +13 -0
- package/package.json +84 -75
- package/server.ts +10 -0
- package/app/[owner]/[repo]/page.tsx +0 -6
- package/app/[slug]/page.tsx +0 -6
- package/packages/galaxydraw/README.md +0 -296
- package/packages/galaxydraw/banner.png +0 -0
- package/packages/galaxydraw/demo/build-static.ts +0 -100
- package/packages/galaxydraw/demo/client.ts +0 -154
- package/packages/galaxydraw/demo/dist/client.js +0 -8
- package/packages/galaxydraw/demo/index.html +0 -256
- package/packages/galaxydraw/demo/server.ts +0 -96
- package/packages/galaxydraw/dist/index.js +0 -984
- package/packages/galaxydraw/dist/index.js.map +0 -16
- package/packages/galaxydraw/node_modules/.bin/tsc.bunx +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsc.exe +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsserver.exe +0 -0
- package/packages/galaxydraw/package.json +0 -49
- package/packages/galaxydraw/perf.test.ts +0 -284
- package/packages/galaxydraw/src/core/cards.ts +0 -435
- package/packages/galaxydraw/src/core/engine.ts +0 -339
- package/packages/galaxydraw/src/core/events.ts +0 -81
- package/packages/galaxydraw/src/core/layout.ts +0 -136
- package/packages/galaxydraw/src/core/minimap.ts +0 -216
- package/packages/galaxydraw/src/core/state.ts +0 -177
- package/packages/galaxydraw/src/core/viewport.ts +0 -106
- package/packages/galaxydraw/src/galaxydraw.css +0 -166
- package/packages/galaxydraw/src/index.ts +0 -40
- 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
|
+
}
|