-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path.cursorrules
More file actions
418 lines (345 loc) · 16.5 KB
/
Copy path.cursorrules
File metadata and controls
418 lines (345 loc) · 16.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
# Graphium: AI Assistant Context
**Project:** Graphium - Local-first D&D digital battlemap (Electron + React)
**Purpose:** This file provides context for AI coding assistants working on Graphium
## Quick Reference
**What is Graphium?** A dual-window Electron app for D&D Dungeon Masters. Projects a clean "World View" for players while the DM controls everything from an "Architect View."
**Tech Stack:**
- **Framework:** Electron 33 + React 18 + TypeScript 5
- **State:** Zustand (global store with subscriptions)
- **Canvas:** Konva/React-Konva (HTML5 Canvas wrapper)
- **Styling:** Tailwind CSS
- **Build:** Vite 6
- **Testing:** Vitest + React Testing Library
**Key Architecture:**
- Dual-window (Architect View + World View)
- IPC for state sync (Architect → World, one-way)
- Local-first (no cloud, `.graphium` ZIP files for campaigns)
- Privacy-focused error handling (PII sanitization)
## Project Structure
```
graphium/
├── electron/ # Main process (Node.js)
│ ├── main.ts # Window creation, IPC handlers, file I/O
│ └── preload.ts # Context bridge (secure IPC API)
├── src/ # Renderer process (React)
│ ├── components/ # React components
│ │ ├── Canvas/ # Konva canvas components
│ │ │ ├── CanvasManager.tsx # Main canvas (viewport, camera, tools)
│ │ │ ├── GridOverlay.tsx # Tactical grid (LINES/DOTS/HIDDEN)
│ │ │ ├── FogOfWarLayer.tsx # Fog of war overlay (World View only)
│ │ │ └── TokenErrorBoundary.tsx # Per-token error isolation
│ │ ├── HomeScreen/ # Landing page components
│ │ │ ├── BackgroundCanvas.tsx # Reusable canvas background
│ │ │ ├── PlaygroundToken.tsx # Interactive demo tokens (physics + trails)
│ │ │ ├── PlaygroundDrawings.tsx # Static tactical markers
│ │ │ └── VignetteOverlay.tsx # Edge fade effect
│ │ ├── AboutModal.tsx # App info modal (global)
│ │ ├── LogoIcon.tsx # Animated D20 dice logo
│ │ ├── HomeScreen.tsx # Landing page / splash screen
│ │ ├── ImageCropper.tsx # Token cropping UI
│ │ ├── Sidebar.tsx # Map/grid controls + token library
│ │ ├── SyncManager.tsx # IPC state sync manager
│ │ ├── PauseManager.tsx # Game pause state sync manager
│ │ ├── LoadingOverlay.tsx # World View pause screen
│ │ ├── Toast.tsx # Notification system
│ │ ├── PendingErrorsIndicator.tsx # Error reporting UI
│ │ └── PrivacyErrorBoundary.tsx # App-wide error boundary
│ ├── store/ # Zustand state management
│ │ └── gameStore.ts # Global game state (tokens, drawings, map, grid, toast)
│ ├── utils/ # Utility functions
│ │ ├── grid.ts # Smart grid snapping
│ │ ├── AssetProcessor.ts # Image optimization (WebP conversion)
│ │ ├── errorSanitizer.ts # PII removal from errors
│ │ └── globalErrorHandler.ts # Global error catching
│ └── App.tsx # Root component (Architect View)
└── docs/ # Documentation (organized by category)
├── index.md # Main documentation entry point
├── documentation-inventory.md # Complete documentation catalog
├── architecture/ # System design & performance docs (ALL CAPS)
├── components/ # Component implementation docs (lowercase-hyphen)
├── context/ # Domain knowledge & business rules (ALL CAPS)
├── features/ # Feature implementation docs (lowercase-hyphen)
├── guides/ # How-to guides & conventions (ALL CAPS)
└── planning/ # Project planning & migration docs (lowercase-hyphen)
```
## Documentation Structure
**Quick Start:** See `docs/index.md` for the main documentation entry point
**For AI Assistants:**
- This file (`.cursorrules`) - Quick reference and patterns
- `docs/documentation-inventory.md` - Complete documentation catalog
- `docs/architecture/ARCHITECTURE.md` - System architecture
- `docs/context/CONTEXT.md` - Domain knowledge
- `docs/guides/CONVENTIONS.md` - Code standards
**Documentation Organization:**
- **Architecture docs** (ALL CAPS): System design, decisions, IPC, performance
- **Feature docs** (lowercase-hyphen): Specific feature implementations
- **Component docs** (lowercase-hyphen): Component implementation details
- **Guides** (ALL CAPS): Conventions, tutorials, troubleshooting
- **Planning** (lowercase-hyphen): Migration plans, PR summaries
```
## Domain Glossary
**Critical Terms:**
- **Architect View**: The DM's control window with full controls, toolbar, sidebar
- **World View**: Clean player-facing window that displays only the canvas (no UI chrome)
- **Token**: Draggable image representing a character/creature on the battlemap (snaps to grid)
- **Drawing**: Freehand marker/eraser stroke (for fog of war, spell effects, etc.)
- **Map**: Background image for the battlemap (with calibration system)
- **Grid**: Tactical positioning overlay (LINES mode, DOTS mode, or HIDDEN)
- **Grid Snapping**: Automatic alignment of tokens to grid (smart: cell centers for 1x1, intersections for 2x2)
- **Fog of War**: Vision-based fog overlay (World View only) with three states: Unexplored (dark), Explored (dimmed), Current Vision (clear)
- **Daylight Mode**: Toggle to enable/disable fog of war (when enabled, fog is hidden)
- **Campaign File**: `.graphium` ZIP archive containing manifest.json + assets/ directory
- **Temp Assets**: Processed images stored in userData/temp_assets/ until campaign save
- **IPC (Inter-Process Communication)**: Electron's system for main ↔ renderer communication
- **Sync Manager**: Component that broadcasts Architect state changes to World View via IPC
- **Home Screen**: Landing page shown before entering editor (new campaigns, recent campaigns, branding)
- **About Modal**: Global information dialog accessible via `?` keyboard shortcut
- **Playground Tokens**: Demo tokens on landing page with physics, trails, and collision (landing page only)
- **Asset Processor**: Utility that resizes images and converts to WebP format
- **Custom Protocol**: `media://` URL scheme for secure local file access in renderer
- **Viewport**: Camera position and zoom level on the canvas
- **Calibration Mode**: Interactive tool to align map grid with in-game grid by drawing a reference square
- **Toast**: Temporary notification message (success/error/info)
- **Error Boundary**: React component that catches errors and shows fallback UI
- **PII Sanitization**: Removal of usernames, emails, IPs from error reports
- **Game Pause**: DM feature to freeze World View with loading overlay while preparing scenes
- **Loading Overlay**: Full-screen World View blocker shown when game is paused
- **Pause Manager**: System component that synchronizes pause state via IPC
**Grid Terms:**
- **Grid Size**: Pixel size of one grid cell (default: 50px = 5ft in D&D)
- **Grid Type**: Visual style - LINES (traditional), DOTS (minimal), HIDDEN (none)
- **Snap to Intersection**: Align token corner to grid line crossing (for even-sized tokens)
- **Snap to Cell Center**: Align token center to grid cell middle (for odd-sized tokens)
- **Viewport Culling**: Only render grid elements within visible camera bounds (performance)
**State Terms:**
- **GameState**: Zustand store containing all game data (tokens, drawings, map, grid settings, toast)
- **Store Subscription**: Zustand pattern for reacting to state changes (used by SyncManager)
- **Immutable Update**: Creating new object/array references when mutating state (required for Zustand)
## Core Patterns
### 1. State Management Pattern
**Zustand Store** (src/store/gameStore.ts):
```typescript
// ✅ CORRECT: Immutable updates
addToken: (token) => set((state) => ({
tokens: [...state.tokens, token] // New array
}))
// ❌ WRONG: Direct mutation (breaks reactivity)
addToken: (token) => set((state) => {
state.tokens.push(token) // DON'T DO THIS
return { tokens: state.tokens }
})
```
**Access Patterns:**
```typescript
// Component rendering (triggers re-render):
const tokens = useGameStore((state) => state.tokens)
// Event handlers (no re-render):
const handleClick = () => {
const { addToken } = useGameStore.getState()
addToken(newToken)
}
// Subscriptions (side effects):
useEffect(() => {
const unsub = useGameStore.subscribe((state) => {
window.ipcRenderer.send('SYNC_WORLD_STATE', state)
})
return unsub
}, [])
```
### 2. IPC Communication Pattern
**Architecture:** Architect (Producer) → Main Process (Relay) → World (Consumer)
**Sending from Renderer:**
```typescript
// One-way send (no response)
window.ipcRenderer.send('SYNC_WORLD_STATE', gameState)
// Request-response (async)
const result = await window.ipcRenderer.invoke('SAVE_CAMPAIGN', gameState)
```
**Receiving in Main Process:**
```typescript
// Handle send (no response)
ipcMain.on('SYNC_WORLD_STATE', (_event, state) => {
worldWindow?.webContents.send('SYNC_WORLD_STATE', state)
})
// Handle invoke (with response)
ipcMain.handle('SAVE_CAMPAIGN', async (_event, state) => {
// ... save logic
return { success: true, path: filePath }
})
```
### 3. Error Handling Pattern
**Three Layers:**
1. **PrivacyErrorBoundary** - Wraps entire app, catches React errors
2. **TokenErrorBoundary** - Per-token isolation (one bad token doesn't crash canvas)
3. **globalErrorHandler** - Catches window.onerror, unhandledrejection, main process errors
**PII Sanitization:**
```typescript
// Automatic sanitization before display/storage:
// "/Users/johnsmith/file.ts" → "/Users/<USER>/file.ts"
// "user@example.com" → "<EMAIL>"
// "Bearer sk-1234..." → "Bearer <TOKEN>"
```
**Error Persistence:**
- Errors stored in localStorage (`graphium_errors`)
- User can review and optionally report via email
- Consent-based reporting (privacy-first)
### 4. Asset Processing Pattern
**Flow:** Upload → Resize → WebP Conversion → Temp Storage → Campaign Save
```typescript
// 1. User uploads image
const file = e.target.files[0]
// 2. Process image (resize + WebP) - Returns cancellable handle
const handle = processImage(file, 'TOKEN')
const tempUrl = await handle.promise
// Returns: "file:///Users/.../temp_assets/1234567890-goblin.webp"
// 3. Use in game (temp storage)
addToken({ id: uuid(), x, y, src: tempUrl, scale: 1 })
// 4. Save campaign (copy to .graphium ZIP)
await window.ipcRenderer.invoke('SAVE_CAMPAIGN', gameState)
// Assets copied from temp_assets/ → campaign.graphium/assets/
```
### 5. Grid Snapping Pattern
**Smart Snapping** (based on token size):
```typescript
// Even-sized tokens (0x0, 2x2, 4x4) → Snap to intersections
const pos = snapToGrid(x, y, gridSize, 100, 100) // 2x2 token
// Returns corner aligned to grid intersection
// Odd-sized tokens (1x1, 3x3, 5x5) → Snap to cell centers
const pos = snapToGrid(x, y, gridSize, 50, 50) // 1x1 token
// Returns corner such that center aligns to cell center
// Legacy mode (no dimensions) → Simple rounding
const pos = snapToGrid(x, y, gridSize)
// Returns top-left corner snapped to nearest grid point
```
### 6. Viewport Pattern
**Clamping:** Prevents camera from going too far from content
```typescript
// Keep viewport within bounds of map/tokens
const clampedPosition = clampPosition(newPos, stageSize, contentBounds)
// Applied during:
// - Pan (drag canvas)
// - Zoom (mousewheel)
// - Auto-center on map upload
```
## Common Tasks
### Add a New IPC Channel
1. **Define in preload.ts** (expose to renderer):
```typescript
contextBridge.exposeInMainWorld('ipcRenderer', {
invoke: (...args) => ipcRenderer.invoke(...args),
// ... existing methods
})
```
2. **Implement handler in main.ts**:
```typescript
ipcMain.handle('MY_CHANNEL', async (_event, arg) => {
// ... logic
return result
})
```
3. **Call from renderer**:
```typescript
const result = await window.ipcRenderer.invoke('MY_CHANNEL', data)
```
### Add a New State Property
1. **Update GameState interface** (src/store/gameStore.ts):
```typescript
export interface GameState {
// ... existing properties
myNewProp: string
setMyNewProp: (value: string) => void
}
```
2. **Add initial value and action**:
```typescript
export const useGameStore = create<GameState>((set) => ({
// ... existing state
myNewProp: 'default',
setMyNewProp: (value) => set({ myNewProp: value })
}))
```
3. **Use in components**:
```typescript
const myNewProp = useGameStore((state) => state.myNewProp)
const { setMyNewProp } = useGameStore()
```
### Add a New Toast Notification
```typescript
const { showToast } = useGameStore()
// Error (red)
showToast('Failed to upload map', 'error')
// Success (green)
showToast('Campaign saved successfully', 'success')
// Info (blue)
showToast('World View opened', 'info')
```
### Add Error Handling to a Component
```typescript
// Wrap component with error boundary
import TokenErrorBoundary from './TokenErrorBoundary'
<TokenErrorBoundary tokenId={token.id}>
<MyComponent />
</TokenErrorBoundary>
```
### Add a New Tool to Canvas
1. **Add tool type** (src/App.tsx or types file):
```typescript
type Tool = 'select' | 'marker' | 'eraser' | 'myNewTool'
```
2. **Add toolbar button** (src/App.tsx):
```tsx
<button onClick={() => setTool('myNewTool')}>
My Tool
</button>
```
3. **Implement tool logic** (src/components/Canvas/CanvasManager.tsx):
```typescript
const handleMouseDown = (e) => {
if (tool === 'myNewTool') {
// ... tool logic
}
}
```
## Anti-Patterns
**❌ DON'T:**
- Mutate Zustand state directly (`state.tokens.push(...)`)
- Send state from World View back to Architect (one-way sync only)
- Use `file://` URLs directly in renderer (use `media://` protocol)
- Store sensitive data in error reports (always sanitize)
- Create commits without explicit user request
- Skip PII sanitization for any user-facing error messages
- Use `cat`, `grep`, `find` in bash when specialized tools exist (use Read, Grep, Glob instead)
- Block the main process with sync file operations (use async fs promises)
- Create overlapping error boundaries (one per isolation boundary)
- Toast for every minor event (reserve for user-actionable feedback)
**✅ DO:**
- Use Zustand actions for all state updates
- Keep World View as read-only consumer
- Convert `file://` to `media://` for renderer security
- Sanitize all errors before showing to user
- Ask before creating git commits
- Use async/await for all file I/O
- Keep IPC handlers lightweight (offload heavy work to utilities)
- Batch state updates when possible (better performance)
- Test error boundaries don't interfere with normal error recovery
- Provide clear, actionable toast messages
## New Features in NEXT Branch
**Major additions:**
- **Error Boundaries**: Privacy-focused error catching with PII sanitization
- **Toast Notifications**: Temporary success/error/info messages
- **Map Upload & Calibration**: Upload maps, interactive grid calibration
- **Grid Type Selector**: Switch between LINES, DOTS, HIDDEN modes
- **Smart Grid Snapping**: Even tokens → intersections, odd tokens → cell centers
- **Viewport Clamping**: Prevents camera from drifting too far from content
- **Batch Operations**: removeTokens(), removeDrawings() for efficient bulk deletes
- **Transform Operations**: updateTokenTransform(), updateDrawingTransform()
- **Fog of War**: Vision-based fog overlay with explored regions (works with or without map)
- **Daylight Mode Toggle**: Modern toggle switch component for enabling/disabling fog
- **Test Coverage**: Vitest setup with tests for key components/utilities
**Files to review for examples:**
- Error handling: `src/utils/errorSanitizer.ts`, `src/components/PrivacyErrorBoundary.tsx`
- Toast: `src/components/Toast.tsx`
- Grid: `src/utils/grid.ts`, `src/components/Canvas/GridOverlay.tsx`
- Map: `src/components/Sidebar.tsx` (upload/calibration UI)
- Fog of War: `src/components/Canvas/FogOfWarLayer.tsx`, `src/components/ToggleSwitch.tsx`
- Tests: `src/**/*.test.tsx`