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.
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 +947 -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 +84 -75
  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,84 @@
1
+ import type { CanvasContext } from './context';
2
+ import {
3
+ bootstrapInitialRouteUi,
4
+ handleInitialRouteError,
5
+ hideInitialRouteLanding,
6
+ hydrateInitialRouteRepo,
7
+ migrateLegacyHashRoute,
8
+ resolveInitialRepoPath,
9
+ showInitialRouteCloneStart,
10
+ } from './initial-route-hydration';
11
+ import { handlePopstateRepoEntry } from './route-repo-entry';
12
+
13
+ export async function wireMountRoutes(
14
+ ctx: CanvasContext,
15
+ options: {
16
+ isDisposed: () => boolean;
17
+ showLandingPlaceholder: () => void;
18
+ updateFavoriteStar: (path: string) => void;
19
+ applySharedLayout: () => Promise<void>;
20
+ hydrateRoutes?: typeof hydrateInitialRouteRepo;
21
+ resolveRepoPath?: typeof resolveInitialRepoPath;
22
+ handleRouteError?: typeof handleInitialRouteError;
23
+ bootstrapRepoUi?: typeof bootstrapInitialRouteUi;
24
+ bindPopstate?: typeof bindMountPopstate;
25
+ },
26
+ ) {
27
+ const hydrateRoutes = options.hydrateRoutes || hydrateInitialRouteRepo;
28
+ const resolveRepoPath = options.resolveRepoPath || resolveInitialRepoPath;
29
+ const handleRouteError = options.handleRouteError || handleInitialRouteError;
30
+ const bootstrapRepoUi = options.bootstrapRepoUi || bootstrapInitialRouteUi;
31
+ const bindPopstate = options.bindPopstate || bindMountPopstate;
32
+
33
+ await hydrateRoutes(ctx, {
34
+ disposed: options.isDisposed(),
35
+ showLandingPlaceholder: options.showLandingPlaceholder,
36
+ hideLanding: hideInitialRouteLanding,
37
+ migrateLegacyHashRoute,
38
+ resolveRepoPath: async (slug) => {
39
+ try {
40
+ return await resolveRepoPath(slug, {
41
+ onCloneStart: showInitialRouteCloneStart,
42
+ } as any);
43
+ } catch (err: any) {
44
+ return await handleRouteError(err);
45
+ }
46
+ },
47
+ bootstrapRepoUi: async (resolvedPath) => {
48
+ await bootstrapRepoUi(ctx, resolvedPath, {
49
+ disposed: options.isDisposed(),
50
+ applySharedLayout: options.applySharedLayout,
51
+ } as any);
52
+ },
53
+ updateFavoriteStar: options.updateFavoriteStar,
54
+ });
55
+
56
+ bindPopstate(ctx, {
57
+ isDisposed: options.isDisposed,
58
+ showLandingPlaceholder: options.showLandingPlaceholder,
59
+ updateFavoriteStar: options.updateFavoriteStar,
60
+ });
61
+ }
62
+
63
+ export function bindMountPopstate(
64
+ ctx: CanvasContext,
65
+ options: {
66
+ isDisposed: () => boolean;
67
+ showLandingPlaceholder: () => void;
68
+ updateFavoriteStar: (path: string) => void;
69
+ addListener?: (type: string, handler: () => void) => void;
70
+ },
71
+ ) {
72
+ const addListener = options.addListener || ((type: string, handler: () => void) => {
73
+ window.addEventListener(type, handler);
74
+ });
75
+
76
+ addListener('popstate', () => {
77
+ handlePopstateRepoEntry(ctx, {
78
+ disposed: options.isDisposed(),
79
+ currentRepoPath: ctx.snap().context.repoPath,
80
+ showLandingPlaceholder: options.showLandingPlaceholder,
81
+ updateFavoriteStar: options.updateFavoriteStar,
82
+ });
83
+ });
84
+ }
@@ -165,6 +165,20 @@ export function unloadRepo(ctx: CanvasContext, repoPath: string) {
165
165
  /**
166
166
  * Create repo zone tabs in the sidebar for switching between repos.
167
167
  */
168
+ export function clearMultiRepoWorkspace(ctx?: CanvasContext) {
169
+ for (const [, repo] of loadedRepos) {
170
+ if (repo.zoneLabel) repo.zoneLabel.remove();
171
+ }
172
+ loadedRepos.clear();
173
+ _activeRepoPath = null;
174
+
175
+ const container = document.getElementById('repoTabs');
176
+ if (container) {
177
+ container.innerHTML = '';
178
+ container.style.display = 'none';
179
+ }
180
+ }
181
+
168
182
  export function renderRepoTabs(ctx: CanvasContext) {
169
183
  const container = document.getElementById('repoTabs');
170
184
  if (!container) return;
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Interactive Onboarding Tutorial — Guide new users through GitMaps
3
+ *
4
+ * Features:
5
+ * - Step-by-step interactive tour
6
+ * - Highlights UI elements as it explains them
7
+ * - Keyboard shortcuts cheat sheet
8
+ * - Skip/resume anytime
9
+ * - Persists completion state
10
+ */
11
+
12
+ import type { CanvasContext } from './context';
13
+
14
+ export interface TutorialStep {
15
+ id: string;
16
+ title: string;
17
+ description: string;
18
+ highlightSelector: string;
19
+ position: 'top' | 'bottom' | 'left' | 'right';
20
+ action?: () => void;
21
+ }
22
+
23
+ const TUTORIAL_STEPS: TutorialStep[] = [
24
+ {
25
+ id: 'welcome',
26
+ title: 'Welcome to GitMaps! 🎉',
27
+ description: 'Explore codebases on an infinite canvas. Let\'s take a quick tour of the features.',
28
+ highlightSelector: '#app',
29
+ position: 'bottom',
30
+ },
31
+ {
32
+ id: 'repo-selector',
33
+ title: 'Repository Selector',
34
+ description: 'Select any loaded repository from the dropdown. Import new repos from GitHub with the button.',
35
+ highlightSelector: '#repoSelect',
36
+ position: 'bottom',
37
+ },
38
+ {
39
+ id: 'commit-timeline',
40
+ title: 'Commit Timeline',
41
+ description: 'Browse through commit history. Click any commit to see what changed. Use ← → arrow keys to navigate.',
42
+ highlightSelector: '#commitTimeline',
43
+ position: 'right',
44
+ },
45
+ {
46
+ id: 'canvas-area',
47
+ title: 'Infinite Canvas',
48
+ description: 'Your code lives here! Each file is a card. Pan with Space+Drag or middle-click. Scroll to zoom.',
49
+ highlightSelector: '#canvasViewport',
50
+ position: 'top',
51
+ },
52
+ {
53
+ id: 'file-cards',
54
+ title: 'File Cards',
55
+ description: 'Each card shows a file with code preview. Green/red markers show additions/deletions. Hover to see full preview.',
56
+ highlightSelector: '.file-card',
57
+ position: 'bottom',
58
+ },
59
+ {
60
+ id: 'minimap',
61
+ title: 'Minimap',
62
+ description: 'Never get lost! The minimap shows your entire canvas. Click to jump to any area.',
63
+ highlightSelector: '#minimap',
64
+ position: 'top',
65
+ },
66
+ {
67
+ id: 'arrange-toolbar',
68
+ title: 'Arrange Tools',
69
+ description: 'Organize cards with H (row), V (column), or G (grid). W fits all cards on screen.',
70
+ highlightSelector: '#arrangeToolbar',
71
+ position: 'bottom',
72
+ },
73
+ {
74
+ id: 'zoom-controls',
75
+ title: 'Zoom Controls',
76
+ description: 'Fine-tune zoom with the slider or +/- keys. Current zoom level shown in percentage.',
77
+ highlightSelector: '#zoomSlider',
78
+ position: 'top',
79
+ },
80
+ {
81
+ id: 'shortcuts',
82
+ title: 'Keyboard Shortcuts',
83
+ description: 'Press ? anytime to see all shortcuts. Power users love Ctrl+F (search), Ctrl+G (dependency graph), and Ctrl+O (find file).',
84
+ highlightSelector: '#hotkeyToggle',
85
+ position: 'bottom',
86
+ },
87
+ {
88
+ id: 'done',
89
+ title: 'You\'re Ready! 🚀',
90
+ description: 'Start exploring! Import a repo, arrange cards your way, and enjoy spatial code exploration.',
91
+ highlightSelector: '#app',
92
+ position: 'bottom',
93
+ action: () => {
94
+ localStorage.setItem('gitcanvas:onboardingComplete', 'true');
95
+ },
96
+ },
97
+ ];
98
+
99
+ let currentStep = 0;
100
+ let tutorialOverlay: HTMLElement | null = null;
101
+
102
+ /**
103
+ * Check if user has completed onboarding
104
+ */
105
+ export function hasCompletedOnboarding(): boolean {
106
+ return localStorage.getItem('gitcanvas:onboardingComplete') === 'true';
107
+ }
108
+
109
+ /**
110
+ * Reset onboarding progress
111
+ */
112
+ export function resetOnboarding(): void {
113
+ localStorage.removeItem('gitcanvas:onboardingComplete');
114
+ currentStep = 0;
115
+ }
116
+
117
+ /**
118
+ * Start the onboarding tutorial
119
+ */
120
+ export function startOnboarding(ctx: CanvasContext): void {
121
+ if (tutorialOverlay) return;
122
+
123
+ currentStep = 0;
124
+ showTutorialStep(ctx, currentStep);
125
+ }
126
+
127
+ /**
128
+ * Show a specific tutorial step
129
+ */
130
+ function showTutorialStep(ctx: CanvasContext, stepIndex: number): void {
131
+ if (stepIndex < 0 || stepIndex >= TUTORIAL_STEPS.length) {
132
+ hideTutorial();
133
+ return;
134
+ }
135
+
136
+ const step = TUTORIAL_STEPS[stepIndex];
137
+
138
+ // Create overlay if needed
139
+ if (!tutorialOverlay) {
140
+ tutorialOverlay = document.createElement('div');
141
+ tutorialOverlay.className = 'tutorial-overlay';
142
+ tutorialOverlay.innerHTML = `
143
+ <div class="tutorial-backdrop"></div>
144
+ <div class="tutorial-content">
145
+ <div class="tutorial-header">
146
+ <h3 class="tutorial-title"></h3>
147
+ <button class="tutorial-close" id="tutorialClose">×</button>
148
+ </div>
149
+ <div class="tutorial-description"></div>
150
+ <div class="tutorial-progress">
151
+ <span class="tutorial-step-count"></span>
152
+ </div>
153
+ <div class="tutorial-actions">
154
+ <button class="btn-ghost" id="tutorialSkip">Skip Tour</button>
155
+ <button class="btn-primary" id="tutorialNext">Next →</button>
156
+ </div>
157
+ </div>
158
+ `;
159
+
160
+ document.body.appendChild(tutorialOverlay);
161
+
162
+ // Add styles
163
+ const style = document.createElement('style');
164
+ style.textContent = `
165
+ .tutorial-overlay {
166
+ position: fixed;
167
+ inset: 0;
168
+ z-index: 99999;
169
+ display: flex;
170
+ align-items: center;
171
+ justify-content: center;
172
+ animation: fadeIn 0.2s ease;
173
+ }
174
+ .tutorial-backdrop {
175
+ position: absolute;
176
+ inset: 0;
177
+ background: rgba(10, 10, 15, 0.85);
178
+ backdrop-filter: blur(4px);
179
+ }
180
+ .tutorial-content {
181
+ position: relative;
182
+ background: var(--bg-secondary);
183
+ border: 1px solid var(--border-primary);
184
+ border-radius: 12px;
185
+ padding: 24px;
186
+ max-width: 450px;
187
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
188
+ animation: slideUp 0.3s ease;
189
+ }
190
+ .tutorial-header {
191
+ display: flex;
192
+ align-items: center;
193
+ justify-content: space-between;
194
+ margin-bottom: 12px;
195
+ }
196
+ .tutorial-title {
197
+ margin: 0;
198
+ font-size: 18px;
199
+ font-weight: 600;
200
+ background: linear-gradient(135deg, #a78bfa, #60a5fa);
201
+ -webkit-background-clip: text;
202
+ -webkit-text-fill-color: transparent;
203
+ }
204
+ .tutorial-close {
205
+ background: none;
206
+ border: none;
207
+ color: var(--text-muted);
208
+ font-size: 24px;
209
+ cursor: pointer;
210
+ padding: 4px 8px;
211
+ border-radius: 4px;
212
+ transition: all 0.2s;
213
+ }
214
+ .tutorial-close:hover {
215
+ background: var(--bg-tertiary);
216
+ color: var(--text-primary);
217
+ }
218
+ .tutorial-description {
219
+ color: var(--text-primary);
220
+ font-size: 14px;
221
+ line-height: 1.6;
222
+ margin-bottom: 20px;
223
+ }
224
+ .tutorial-progress {
225
+ margin-bottom: 20px;
226
+ }
227
+ .tutorial-step-count {
228
+ font-size: 12px;
229
+ color: var(--text-muted);
230
+ }
231
+ .tutorial-actions {
232
+ display: flex;
233
+ gap: 8px;
234
+ justify-content: space-between;
235
+ }
236
+ .tutorial-actions button {
237
+ padding: 10px 20px;
238
+ border-radius: 8px;
239
+ border: none;
240
+ font-size: 13px;
241
+ font-weight: 600;
242
+ cursor: pointer;
243
+ transition: all 0.2s;
244
+ }
245
+ .btn-primary {
246
+ background: linear-gradient(135deg, #7c3aed, #3b82f6);
247
+ color: white;
248
+ }
249
+ .btn-primary:hover {
250
+ transform: translateY(-1px);
251
+ box-shadow: 0 4px 12px rgba(124, 58, 237, 0.4);
252
+ }
253
+ @keyframes fadeIn {
254
+ from { opacity: 0; }
255
+ to { opacity: 1; }
256
+ }
257
+ @keyframes slideUp {
258
+ from { transform: translateY(20px); opacity: 0; }
259
+ to { transform: translateY(0); opacity: 1; }
260
+ }
261
+ `;
262
+ document.head.appendChild(style);
263
+
264
+ // Wire up buttons
265
+ tutorialOverlay.querySelector('#tutorialClose')?.addEventListener('click', hideTutorial);
266
+ tutorialOverlay.querySelector('#tutorialSkip')?.addEventListener('click', () => {
267
+ hideTutorial();
268
+ localStorage.setItem('gitcanvas:onboardingComplete', 'true');
269
+ });
270
+ tutorialOverlay.querySelector('#tutorialNext')?.addEventListener('click', () => {
271
+ const step = TUTORIAL_STEPS[currentStep];
272
+ if (step.action) step.action();
273
+ currentStep++;
274
+ showTutorialStep(ctx, currentStep);
275
+ });
276
+
277
+ // Keyboard navigation
278
+ document.addEventListener('keydown', ha