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
package/README.md CHANGED
@@ -1,161 +1,310 @@
1
- <p align="center">
2
- <img src="banner.png" alt="GitMaps" width="100%" />
3
- </p>
1
+ # 🗺️ GitMaps — Spatial Code Explorer
4
2
 
5
- <p align="center">
6
- <img src="https://img.shields.io/badge/runtime-Bun-f472b6?style=flat-square" alt="Bun">
7
- <img src="https://img.shields.io/badge/framework-Melina-7c3aed?style=flat-square" alt="Melina">
8
- <img src="https://img.shields.io/badge/engine-GalaxyDraw-38bdf8?style=flat-square" alt="GalaxyDraw">
9
- <img src="https://img.shields.io/badge/license-ISC-4ade80?style=flat-square" alt="License">
10
- </p>
3
+ **Explore codebases on an infinite canvas instead of file trees.**
11
4
 
12
- # 🪐 GitMaps
5
+ [![Live Demo](https://img.shields.io/badge/demo-live-7c3aed.svg?style=flat-square)](https://gitmaps.xyz)
6
+ [![npm](https://img.shields.io/npm/v/gitmaps.svg?style=flat-square)](https://npmjs.com/package/gitmaps)
7
+ [![License](https://img.shields.io/badge/license-ISC-blue.svg?style=flat-square)](LICENSE)
13
8
 
14
- **Transcend the file tree.** GitMaps renders knowledge on an infinite canvas — with layers, time-travel, and a minimap to never lose context.
9
+ ---
10
+
11
+ ## 🎯 What is GitMaps?
12
+
13
+ GitMaps renders your entire repository on an infinite canvas with layers, git time-travel, and a minimap to never lose context.
14
+
15
+ **Traditional file trees are 1-dimensional. GitMaps gives you 5 dimensions:**
16
+
17
+ 1. **1D — Lines** — Individual lines of code
18
+ 2. **2D — Canvas** — Spatial arrangement of files
19
+ 3. **3D — Layers** — Focus areas (Auth, API, UI)
20
+ 4. **4D — Time** — Git history with persistent layout
21
+ 5. **5D — Connections** — Links between related code
22
+
23
+ ---
24
+
25
+ ## 🚀 Quick Start
26
+
27
+ ### Try Online (No Install)
28
+ Visit **[https://gitmaps.xyz](https://gitmaps.xyz)** and explore popular repos like React, Deno, or Svelte.
29
+
30
+ ### Run Locally
31
+ ```bash
32
+ git clone https://github.com/7flash/gitmaps.git
33
+ cd gitmaps
34
+ bun install
35
+ bun run dev
36
+ # Open http://localhost:3335
37
+ ```
15
38
 
16
- 🌐 **Try it now:** [gitmaps.xyz](https://gitmaps.xyz) — no install required
17
- 🔬 **Explore React:** [gitmaps.xyz/facebook/react](https://gitmaps.xyz/facebook/react)
39
+ ### Install Globally
40
+ ```bash
41
+ bun install -g gitmaps
42
+ gitmaps /path/to/your/repo
43
+ ```
18
44
 
19
45
  ---
20
46
 
21
47
  ## ✨ Features
22
48
 
23
- | Feature | Description |
24
- |---------|-------------|
25
- | 🖼️ **Infinite Canvas** | Pan, zoom, drag files anywhere. Your layout is saved per-commit. |
26
- | 📊 **Inline Diffs** | Green additions, red deletions right inside each card. Scrollbar markers show *where* changes are. |
27
- | **Commit Timeline** | `←` `→` arrow keys through history. Each commit shows exactly which files changed. |
28
- | 📌 **Persistent Layout** | Drag files where they belong in *your* mental model. Switch commits — arrangement stays. |
29
- | 🔍 **Command Palette** | `Ctrl+K` to fuzzy-search files with subsequence-weighted scoring and character-level match highlighting. |
30
- | 🌿 **Branch Comparison** | Side-by-side branch diff viewer with glassmorphism drawer, status badges, and diff cards rendered on canvas. |
31
- | 📦 **Multi-Repo Workspace** | Load 2-3 repos side-by-side. Auto-offset with zone labels. Sidebar tabs switch commit timelines. |
32
- | 👁️ **File Preview on Hover** | Glassmorphism tooltip at low zoom: language badge, path, first 12 lines of code. |
33
- | 🐙 **GitHub Import** | Paste a URL for instant clone, or search by username with live repo filtering. |
34
- | 🔗 **Connections** | `Alt+click` a line → pick target file → click target line. Visual bezier curves link related code. |
35
- | 📁 **Layers** | Group files into focused subsets. Each layer remembers its own viewport position. |
36
- | 🤖 **AI Chat** | Press `I` to open an AI sidebar that understands your current canvas context. |
37
- | ⌨️ **VIM-style Diff Nav** | `j`/`k` to jump between changes across files, `Shift+J`/`K` for file-level navigation. |
38
- | 🔎 **Cross-Card Search** | Press `/` to search across all visible file cards. Results highlighted in-place with match counts. |
39
- | ✏️ **Full Editor** | CodeMirror 6 with syntax highlighting, multi-tab, auto-save, git commit, and code minimap. |
40
- | ⇄ **Tab Diff** | Compare two open tabs side-by-side with LCS diff, change markers, synced scrolling. |
41
- | 🧭 **Symbol Outline** | Side panel showing all functions/classes/types in a file. Click to scroll. |
42
- | 🗺️ **Minimap** | Bird's-eye overview with click-to-navigate and viewport indicator. |
43
- | 📝 **PR Review** | Comment threads on any line, stored in localStorage. |
44
- | 📁 **Card Grouping** | Collapse entire directories into compact summary cards to reduce clutter. |
49
+ ### Core Experience
50
+ - 🗺️ **Infinite Canvas** — Pan, zoom, arrange files freely
51
+ - 📄 **File Cards** Interactive cards with code preview and inline diffs
52
+ - 🧭 **Minimap** — Bird's eye view with click-to-navigate
53
+ - 📚 **Layers** Organize files into focus layers
54
+
55
+ ### Git Integration
56
+ - **Commit Timeline** Browse history in sidebar
57
+ - 📊 **Inline Diffs** Green/red markers for changes
58
+ - 🔄 **Time Travel** Navigate commits while layout persists
59
+ - 🔀 **Branch Comparison** Compare branches side-by-side
60
+
61
+ ### Performance
62
+ - **WebGL Renderer** Pixi.js GPU acceleration for 1000+ cards at 60fps
63
+ - 🎯 **Viewport Culling** Only render visible cards
64
+ - 📦 **Progressive Loading** Load large repos in batches
65
+ - ⏱️ **30s Timeout** Large repo support (up from 5s)
66
+
67
+ ### Collaboration
68
+ - 👑 **Leader Mode** Full control when running locally
69
+ - 👁️ **Follower Mode** Read-only view on production
70
+ - 🔄 **Sync Controls** Push/pull canvas state to servers
71
+ - ⚡ **Auto-Sync** — Automatic position sync on changes
72
+
73
+ ### UX Features
74
+ - 📊 **Progress Bar** — Visual feedback during loading
75
+ - 🖼️ **Image/Video Rendering** — Media files display inline
76
+ - 📷 **Canvas Export** — Save layouts as PNG/JPEG/WebP
77
+ - 🎓 **Onboarding Tutorial** — 10-step interactive guide
78
+ - 🔗 **Shareable URLs** — `/owner/repo#commit` format
79
+ - 📑 **Multi-Repo Tabs** — Load multiple repos simultaneously
80
+ - ⌨️ **Keyboard Shortcuts** — Power user shortcuts
45
81
 
46
- ## 🚀 Quick Start
82
+ ---
83
+
84
+ ## 🎯 Use Cases
85
+
86
+ ### AI Code Review
87
+ AI agents generate thousands of lines across dozens of files. GitMaps renders every file simultaneously so you can see the full picture, spot patterns, and review changes spatially instead of one-file-at-a-time.
88
+
89
+ ### Architecture Exploration
90
+ Understand unfamiliar codebases faster by seeing how files relate spatially. Draw connections between related code across layers.
91
+
92
+ ### Onboarding
93
+ New team members explore the codebase spatially. Senior devs create canvas arrangements showing key files and their relationships.
94
+
95
+ ### Pair Programming
96
+ Driver runs locally (leader), navigator visits URL (follower). Both see same spatial arrangement.
97
+
98
+ ### Legacy Code Understanding
99
+ Visualize connections and dependencies in complex legacy systems. Layers help focus on specific concerns.
100
+
101
+ ---
102
+
103
+ ## 📊 Performance
104
+
105
+ | Repo Size | Renderer | Load Time | FPS |
106
+ |-----------|----------|-----------|-----|
107
+ | <100 files | DOM | <1s | 60 |
108
+ | 100-500 files | DOM | 1-3s | 60 |
109
+ | 500-1000 files | WebGL | 3-5s | 60 |
110
+ | 1000+ files | WebGL | 5-10s | 60 |
111
+ | 10000+ files | WebGL | 10-30s | 30-40 |
112
+
113
+ ### Tested Repositories
114
+ - ✅ [7flash/gitmaps](https://gitmaps.xyz/7flash/gitmaps) — 9 files, <1s
115
+ - ✅ [facebook/react](https://gitmaps.xyz/facebook/react) — 100 commits, 2.8s
116
+
117
+ ---
118
+
119
+ ## ⌨️ Keyboard Shortcuts
47
120
 
48
- ```sh
49
- # One-liner (requires Bun)
50
- npx gitmaps
51
- # http://localhost:3335
121
+ | Shortcut | Action |
122
+ |----------|--------|
123
+ | `Scroll` | Zoom in/out |
124
+ | `Space + Drag` | Pan canvas |
125
+ | `Click` | Select card |
126
+ | `Shift + Click` | Multi-select |
127
+ | `Drag Canvas` | Rect select |
128
+ | `Drag Card` | Move card |
129
+ | `Del` | Hide file |
130
+ | `H` | Arrange in row |
131
+ | `V` | Arrange in column |
132
+ | `G` | Arrange in grid |
133
+ | `W` | Fit to screen |
134
+ | `Ctrl + F` | Search across files |
135
+ | `Ctrl + O` | Find file |
136
+ | `Ctrl + G` | Toggle dependency graph |
137
+ | `Ctrl + +/-` | Text zoom |
138
+ | `Dbl-click` | Open editor |
139
+ | `Alt + Click` | Connect lines |
140
+ | `Arrow Keys` | Prev/next commit |
141
+ | `Ctrl + N` | New file |
142
+ | `Ctrl + S` | Save (in editor) |
143
+ | `?` | Show keyboard shortcuts |
144
+
145
+ ---
146
+
147
+ ## 🏗️ Architecture
148
+
149
+ ### Tech Stack
150
+ - **Runtime:** Bun / Node.js 18+
151
+ - **Framework:** Melina (full-stack TypeScript)
152
+ - **State:** XState for canvas state machine
153
+ - **Rendering:** DOM + Pixi.js (WebGL)
154
+ - **Editor:** CodeMirror 6
155
+ - **Database:** SQLite with Zod ORM
156
+
157
+ ### Project Structure
52
158
  ```
159
+ gitmaps/
160
+ ├── app/ # Main application
161
+ │ ├── api/ # API routes
162
+ │ ├── lib/ # Shared modules
163
+ │ ├── [owner]/[repo]/ # Dynamic routes
164
+ │ └── page.tsx # Main pages
165
+ ├── packages/
166
+ │ └── galaxydraw/ # Rendering engine
167
+ │ ├── src/
168
+ │ │ ├── core/ # Core engine
169
+ │ │ └── webgl-renderer.ts # WebGL renderer
170
+ │ └── demo/ # Performance benchmarks
171
+ └── docs/ # Documentation
172
+ ```
173
+
174
+ ---
53
175
 
54
- Or clone for development:
176
+ ## 🚀 Deployment
55
177
 
56
- ```sh
178
+ ### Production Server
179
+ ```bash
180
+ # Clone repo
57
181
  git clone https://github.com/7flash/gitmaps.git
58
- cd galaxy-canvas
182
+ cd gitmaps
183
+
184
+ # Install dependencies
59
185
  bun install
60
- bun run dev
61
- ```
62
186
 
63
- Open a repository by entering its path in the sidebar dropdown, or navigate directly:
187
+ # Build
188
+ bun run build
64
189
 
190
+ # Start server
191
+ bun run server.ts
192
+ # Or use bgrun for production
193
+ bgrun start
65
194
  ```
66
- http://localhost:3335/#/path/to/your/repo
195
+
196
+ ### Environment Variables
197
+ ```bash
198
+ # Server configuration
199
+ PORT=3335
200
+ NODE_ENV=production
201
+
202
+ # Database
203
+ DATABASE_PATH=./data/canvas_users.db
204
+
205
+ # GitHub OAuth (optional)
206
+ GITHUB_CLIENT_ID=your_client_id
207
+ GITHUB_CLIENT_SECRET=your_client_secret
67
208
  ```
68
209
 
69
- Or import from GitHub — click the GitHub icon, paste a repo URL, and it clones + opens instantly.
70
-
71
- ## 🖥️ Keyboard Shortcuts
72
-
73
- | Key | Action |
74
- |-----|--------|
75
- | `←` `→` | Previous / next commit |
76
- | `Ctrl+K` | Command palette — fuzzy file search |
77
- | `/` or `Ctrl+F` | Cross-card text search |
78
- | `j` / `k` | Next / previous diff hunk (VIM-style) |
79
- | `Shift+J` / `Shift+K` | Next / previous changed file |
80
- | `W` | Fit selected cards to screen |
81
- | `H` | Arrange selected in a row |
82
- | `V` | Arrange selected in a column |
83
- | `G` | Arrange selected in a grid |
84
- | `Ctrl+A` | Select all cards |
85
- | `Del` / `Backspace` | Hide selected files |
86
- | `Space+Drag` | Pan canvas |
87
- | `Scroll` | Zoom in/out |
88
- | `Ctrl+`/`Ctrl-` | Increase/decrease card font size |
89
- | `I` | Toggle AI chat sidebar |
90
- | `Alt+Click` | Start connection from clicked line |
91
- | `Esc` | Cancel / deselect all |
92
- | Double-click | Zoom to file |
210
+ ---
211
+
212
+ ## 🧪 Testing
213
+
214
+ ```bash
215
+ # Run tests
216
+ bun test
217
+
218
+ # Test specific module
219
+ bun test app/lib/
93
220
 
94
- ## 📦 Multi-Repo Workspace
221
+ # Performance benchmarks
222
+ open packages/galaxydraw/demo/webgl-demo.html
223
+ ```
95
224
 
96
- Load multiple repositories on the same canvas:
225
+ ---
97
226
 
98
- 1. Open any repo normally
99
- 2. Use the sidebar dropdown to load a second repo
100
- 3. Repos appear side-by-side with **zone labels** (color-coded floating badges)
101
- 4. **Sidebar tabs** switch commit timelines between repos
102
- 5. Each repo auto-offsets horizontally with an 800px gap
227
+ ## 📚 Documentation
103
228
 
104
- ## 🔗 Connections
229
+ - [Getting Started Guide](../GETTING-STARTED.md)
230
+ - [Deployment Checklist](../DEPLOYMENT-CHECKLIST.md)
231
+ - [Launch Kit](../LAUNCH-KIT.md)
232
+ - [Session Report](../SESSION-FINAL-REPORT.md)
105
233
 
106
- Draw visual links between related code across files:
234
+ ---
107
235
 
108
- 1. **Alt+click** a line number in any file card (source)
109
- 2. A **file picker** appears — search and select the target file
110
- 3. **Click a line** in the target file to complete the connection
236
+ ## 🤝 Contributing
111
237
 
112
- Connection markers appear as colored dots on the left side of each card. Click a marker to jump to the other end.
238
+ 1. Fork the repo
239
+ 2. Create a feature branch (`git checkout -b feat/amazing-feature`)
240
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
241
+ 4. Push to the branch (`git push origin feat/amazing-feature`)
242
+ 5. Open a Pull Request
113
243
 
114
- ## 📁 Layers
244
+ ### Development Setup
245
+ ```bash
246
+ git clone https://github.com/7flash/gitmaps.git
247
+ cd gitmaps
248
+ bun install
249
+ bun run dev
250
+ ```
115
251
 
116
- Layers let you isolate subsets of files for focused review:
252
+ ---
117
253
 
118
- - **Create**: Click `+ New Layer` in the bottom bar
119
- - **Add files**: Right-click a card → "Add to Layer"
120
- - **Switch**: Click any layer tab — canvas shows only that layer's files
121
- - **Default**: "All Files" layer shows everything
254
+ ## 📈 Roadmap
255
+
256
+ ### v1.0.0 (Current)
257
+ - Infinite canvas with pan/zoom
258
+ - ✅ Git integration with timeline
259
+ - ✅ Layers system
260
+ - ✅ WebGL rendering
261
+ - ✅ Leader/Follower mode
262
+ - ✅ Canvas export
263
+ - ✅ Onboarding tutorial
264
+
265
+ ### v1.1.0 (Planned)
266
+ - [ ] Real-time collaboration (WebSocket cursors)
267
+ - [ ] Canvas snapshots (save/restore layouts)
268
+ - [ ] Advanced search with filters
269
+ - [ ] Plugin system
270
+ - [ ] Custom themes
271
+
272
+ ### v2.0.0 (Future)
273
+ - [ ] AI-powered code analysis
274
+ - [ ] Automated architecture diagrams
275
+ - [ ] Integration with VS Code
276
+ - [ ] Team workspaces
277
+ - [ ] Comments and annotations
122
278
 
123
- Each layer remembers its own viewport position, so switching layers is instant context-switching.
279
+ ---
124
280
 
125
- ## 🎮 GalaxyDraw Engine
281
+ ## 🙏 Acknowledgments
126
282
 
127
- The canvas is powered by **GalaxyDraw**a zero-dependency infinite 2D canvas engine built for this project:
283
+ Inspired by **Ted Nelson's vision of intertwingularity** — the idea that everything is connected and hierarchical structures hide more relationships than they reveal.
128
284
 
129
- | Capability | Implementation |
130
- |-----------|----------------|
131
- | **Viewport culling** | Only creates DOM for visible cards. React repo (6833 files): 9 DOM nodes, 6824 deferred. ~35ms vs 14s. |
132
- | **Zoom LOD** | Below 25%, cards render as lightweight pills with vertical file names. Smooth fade transitions. |
133
- | **Throttled materialization** | Max 8 cards per frame with 150ms cooldown no frame drops. |
134
- | **Dual control modes** | Simple (drag=pan, scroll=zoom) or Advanced (space+drag=pan, rect select). |
135
- | **Touch support** | Single-finger pan + pinch-to-zoom on tablets. |
136
- | **Minimap** | Shows all files including deferred cards. Click to navigate. |
285
+ Built with:
286
+ - [Bun](https://bun.sh/) — Fast JavaScript runtime
287
+ - [Melina](https://github.com/7flash/melina) Full-stack framework
288
+ - [Pixi.js](https://pixijs.com/) WebGL rendering
289
+ - [CodeMirror](https://codemirror.net/)Code editor
290
+ - [XState](https://xstate.js.org/) State management
137
291
 
138
- GalaxyDraw is also used by [WARMAPS](https://warmaps.xyz) for its intelligence dashboard canvas.
292
+ ---
139
293
 
140
- ## 🔒 Production Security
294
+ ## 📄 License
141
295
 
142
- When deployed as a SaaS (`NODE_ENV=production`):
296
+ ISC © [7flash](https://github.com/7flash)
143
297
 
144
- - Path traversal protection — only `git-canvas/repos/` and `.data/uploads/` are accessible
145
- - Folder browser endpoint completely blocked
146
- - All 7 repo API routes validate paths via `validateRepoPath()`
298
+ ---
147
299
 
148
- ## ⚙️ Stack
300
+ ## 🔗 Links
149
301
 
150
- | Component | Technology |
151
- |-----------|------------|
152
- | Runtime | [Bun](https://bun.sh) |
153
- | Framework | [Melina](https://github.com/7flash/melina.js) v2.5 (file-based routing, SSR, hot reload) |
154
- | State | [XState](https://statemachine.xyz) v5 |
155
- | Database | [sqlite-zod-orm](https://github.com/7flash/measure-fn) (positions, connections, layers) |
156
- | Git | [simple-git](https://github.com/steveukx/git-js) |
157
- | Profiling | [measure-fn](https://github.com/7flash/measure-fn) |
302
+ - **Live Demo:** https://gitmaps.xyz
303
+ - **GitHub:** https://github.com/7flash/gitmaps
304
+ - **npm:** https://npmjs.com/package/gitmaps
305
+ - **jsx-ai:** https://npmjs.com/package/jsx-ai
306
+ - **Twitter:** https://twitter.com/7flash
158
307
 
159
- ## License
308
+ ---
160
309
 
161
- ISC
310
+ **Built with ❤️ on
@@ -0,0 +1 @@
1
+ export { default } from '../page.client';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Dynamic catch-all slug route — renders the same canvas page as root.
3
+ * URL: /foo, /owner/repo, /group/subgroup/repo, etc.
4
+ * The full slug is read client-side from window.location.pathname.
5
+ */
6
+ export { default } from '../page';
Binary file
@@ -0,0 +1,64 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import path from 'path';
3
+
4
+ const DB_PATH = path.join(process.cwd(), 'app', 'analytics.db');
5
+ const db = new Database(DB_PATH);
6
+
7
+ db.run(`CREATE TABLE IF NOT EXISTS hits (
8
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
9
+ path TEXT NOT NULL,
10
+ referrer TEXT,
11
+ ua TEXT,
12
+ ts INTEGER NOT NULL DEFAULT (unixepoch())
13
+ )`);
14
+
15
+ db.run(`CREATE INDEX IF NOT EXISTS idx_hits_ts ON hits(ts)`);
16
+ db.run(`CREATE INDEX IF NOT EXISTS idx_hits_path ON hits(path)`);
17
+
18
+ const insertStmt = db.prepare(`INSERT INTO hits (path, referrer, ua) VALUES (?, ?, ?)`);
19
+
20
+ /**
21
+ * POST /api/analytics — log a page hit
22
+ * Body: { path: string }
23
+ */
24
+ export async function POST(req: Request) {
25
+ try {
26
+ const { path: pagePath } = await req.json() as { path: string };
27
+ const referrer = req.headers.get('referer') || '';
28
+ const ua = req.headers.get('user-agent') || '';
29
+ insertStmt.run(pagePath || '/', referrer, ua.slice(0, 200));
30
+ return Response.json({ ok: true });
31
+ } catch {
32
+ return Response.json({ ok: false }, { status: 400 });
33
+ }
34
+ }
35
+
36
+ /**
37
+ * GET /api/analytics — return stats
38
+ * Query: ?hours=24 (default 24)
39
+ */
40
+ export function GET(req: Request) {
41
+ const url = new URL(req.url);
42
+ const hours = parseInt(url.searchParams.get('hours') || '24', 10);
43
+ const since = Math.floor(Date.now() / 1000) - hours * 3600;
44
+
45
+ const total = db.prepare(`SELECT COUNT(*) as count FROM hits WHERE ts > ?`).get(since) as any;
46
+ const byPath = db.prepare(`SELECT path, COUNT(*) as count FROM hits WHERE ts > ? GROUP BY path ORDER BY count DESC LIMIT 20`).all(since);
47
+ const byHour = db.prepare(`
48
+ SELECT strftime('%Y-%m-%d %H:00', ts, 'unixepoch') as hour, COUNT(*) as count
49
+ FROM hits WHERE ts > ? GROUP BY hour ORDER BY hour
50
+ `).all(since);
51
+ const byReferrer = db.prepare(`
52
+ SELECT referrer, COUNT(*) as count FROM hits
53
+ WHERE ts > ? AND referrer != ''
54
+ GROUP BY referrer ORDER BY count DESC LIMIT 10
55
+ `).all(since);
56
+
57
+ return Response.json({
58
+ total: (total as any).count,
59
+ hours,
60
+ byPath,
61
+ byHour,
62
+ byReferrer,
63
+ });
64
+ }
@@ -1,50 +1,112 @@
1
1
  /**
2
2
  * GET /api/auth/positions?repo=<url> — Load saved positions for a repo
3
- * POST /api/auth/positions — Save positions for a repo
4
- *
5
- * Enables shared repositories: each user has their own card layout
6
- * for the same cloned repository.
3
+ * POST /api/auth/positions — Save positions for a repo (Leader only)
4
+ *
5
+ * Leader/Follower enforcement:
6
+ * - Leaders (localhost/local network): Can read/write positions
7
+ * - Followers (remote/production): Read-only access
8
+ *
9
+ * This prevents unauthorized canvas modifications on production servers.
7
10
  */
8
- import { getSessionFromRequest, loadRepoPositions, saveRepoPositions } from '../../../lib/auth';
11
+ import {
12
+ getSessionFromRequest,
13
+ loadRepoPositions,
14
+ saveRepoPositions,
15
+ } from "../../../lib/auth";
16
+
17
+ /**
18
+ * Detect if request is from a leader (local) or follower (remote)
19
+ * Based on IP address - localhost and local network = leader
20
+ */
21
+ function isLeaderRequest(req: Request): boolean {
22
+ const forwarded = req.headers.get("x-forwarded-for");
23
+ const ip =
24
+ forwarded?.split(",")[0]?.trim() ||
25
+ req.headers.get("x-real-ip") ||
26
+ "unknown";
27
+
28
+ // Leader: localhost, local network IPs
29
+ const leaderPatterns = [
30
+ /^127\./, // 127.0.0.1
31
+ /^::1$/, // IPv6 localhost
32
+ /^192\.168\./, // Private network
33
+ /^10\./, // Private network
34
+ /^172\.(1[6-9]|2[0-9]|3[0-1])\./, // Private network
35
+ /^localhost$/i,
36
+ /^unknown$/i, // No IP header = likely local dev
37
+ ];
38
+
39
+ return leaderPatterns.some((pattern) => pattern.test(ip));
40
+ }
9
41
 
10
42
  export async function GET(req: Request) {
11
- const user = getSessionFromRequest(req);
12
- if (!user) {
13
- return Response.json({ authenticated: false });
14
- }
43
+ const url = new URL(req.url);
44
+ const repoUrl = url.searchParams.get("repo");
45
+ if (!repoUrl) {
46
+ return Response.json({ error: "repo param required" }, { status: 400 });
47
+ }
15
48
 
16
- const url = new URL(req.url);
17
- const repoUrl = url.searchParams.get('repo');
18
- if (!repoUrl) {
19
- return Response.json({ error: 'repo param required' }, { status: 400 });
20
- }
49
+ const user = getSessionFromRequest(req);
21
50
 
22
- const positionsJson = loadRepoPositions(user.id, repoUrl);
51
+ // Allow unauthenticated reads (for public repos)
52
+ if (!user) {
53
+ // Try to load from guest account (userId 0) or return empty
54
+ const positionsJson = loadRepoPositions(0, repoUrl);
23
55
  return Response.json({
24
- positions: positionsJson ? JSON.parse(positionsJson) : null,
25
- repoUrl,
56
+ positions: positionsJson ? JSON.parse(positionsJson) : null,
57
+ repoUrl,
58
+ authenticated: false,
26
59
  });
60
+ }
61
+
62
+ const positionsJson = loadRepoPositions(user.id, repoUrl);
63
+ return Response.json({
64
+ positions: positionsJson ? JSON.parse(positionsJson) : null,
65
+ repoUrl,
66
+ authenticated: true,
67
+ });
27
68
  }
28
69
 
29
70
  export async function POST(req: Request) {
30
- const user = getSessionFromRequest(req);
31
- if (!user) {
32
- return Response.json({ error: 'Not authenticated' }, { status: 401 });
33
- }
71
+ // Enforce leader-only writes
72
+ const isLeader = isLeaderRequest(req);
73
+ if (!isLeader) {
74
+ return Response.json(
75
+ {
76
+ error:
77
+ "Write access denied: Follower mode (read-only). Run GitMaps locally to edit canvas.",
78
+ code: "FOLLOWER_READ_ONLY",
79
+ },
80
+ { status: 403 },
81
+ );
82
+ }
83
+
84
+ const user = getSessionFromRequest(req);
34
85
 
35
- try {
36
- const body = await req.json() as {
37
- repoUrl: string;
38
- positions: Record<string, any>;
39
- };
86
+ // Allow local dev without auth (guest mode - use userId 0)
87
+ const userId = user?.id ?? 0;
40
88
 
41
- if (!body.repoUrl) {
42
- return Response.json({ error: 'repoUrl required' }, { status: 400 });
43
- }
89
+ try {
90
+ const body = (await req.json()) as {
91
+ repoUrl: string;
92
+ positions: Record<string, any>;
93
+ };
44
94
 
45
- saveRepoPositions(user.id, body.repoUrl, JSON.stringify(body.positions || {}));
46
- return Response.json({ ok: true });
47
- } catch (err: any) {
48
- return Response.json({ error: err.message }, { status: 400 });
95
+ if (!body.repoUrl) {
96
+ return Response.json({ error: "repoUrl required" }, { status: 400 });
49
97
  }
98
+
99
+ saveRepoPositions(
100
+ userId,
101
+ body.repoUrl,
102
+ JSON.stringify(body.positions || {}),
103
+ );
104
+ return Response.json({
105
+ ok: true,
106
+ mode: "leader",
107
+ syncedAt: new Date().toISOString(),
108
+ });
109
+ } catch (err: any) {
110
+ return Response.json({ error: err.message }, { status: 400 });
111
+ }
50
112
  }