|
| 1 | +# AGENT.md |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Epoch is a React timeline editor. Users create tracks (rows) and events (time-bound rectangles). Events can be dragged, resized, and moved between tracks. |
| 6 | + |
| 7 | +## Tech Stack |
| 8 | + |
| 9 | +- React 19 + TypeScript |
| 10 | +- Vite (dev server, build) |
| 11 | +- Tailwind CSS (utility classes, no CSS files) |
| 12 | +- Lucide React (icons) |
| 13 | +- No state management library (useState + useRef) |
| 14 | +- LocalStorage for persistence |
| 15 | + |
| 16 | +## File Structure |
| 17 | + |
| 18 | +``` |
| 19 | +App.tsx # Main component, all state, all handlers |
| 20 | +types.ts # TypeScript interfaces (ITrack, IEvent, IDragState, etc.) |
| 21 | +constants.ts # Default data, zoom levels, color palette |
| 22 | +components/ |
| 23 | + AppHeader.tsx # Top bar with zoom controls |
| 24 | + TrackSidebar.tsx # Left sidebar, track list, drag-to-reorder |
| 25 | + TimelineCanvas.tsx # Main canvas, renders events, handles interactions |
| 26 | + Modal.tsx # Base modal wrapper |
| 27 | + TrackModal.tsx # Edit/create track |
| 28 | + EventModal.tsx # Edit/create event |
| 29 | + DataModal.tsx # Import/export JSON, timeline bounds settings |
| 30 | + Button.tsx # Reusable button component |
| 31 | + color/ColorPicker.tsx # Color selection grid |
| 32 | +utils/ |
| 33 | + dateUtils.ts # Date parsing, formatting, arithmetic (parseDate, formatDate, addDays, getDaysDiff, etc.) |
| 34 | + eventLanes.ts # Calculate lane assignments for overlapping events |
| 35 | +``` |
| 36 | + |
| 37 | +## Data Model |
| 38 | + |
| 39 | +```typescript |
| 40 | +interface ITrack { |
| 41 | + id: string; |
| 42 | + title: string; |
| 43 | + color: string; |
| 44 | + order: number; // Controls vertical position |
| 45 | +} |
| 46 | + |
| 47 | +interface IEvent { |
| 48 | + id: string; |
| 49 | + trackId: string; |
| 50 | + title: string; |
| 51 | + description?: string; |
| 52 | + startDate: string; // "YYYY-MM-DD" |
| 53 | + endDate: string; // "YYYY-MM-DD" |
| 54 | + color?: string; |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +## State Location |
| 59 | + |
| 60 | +ALL state lives in `App.tsx`: |
| 61 | + |
| 62 | +- `data: { tracks: ITrack[], events: IEvent[] }` — main data |
| 63 | +- `zoomIndex` — current zoom level index |
| 64 | +- `dragState` — event drag/resize state |
| 65 | +- `isPanning` — canvas pan state |
| 66 | +- `viewBounds` — min/max year for timeline range |
| 67 | +- `editingTrack`, `editingEvent` — modal editing targets |
| 68 | +- `activeModal` — which modal is open (enum ModalType) |
| 69 | + |
| 70 | +## Key Patterns |
| 71 | + |
| 72 | +### Event Positioning |
| 73 | + |
| 74 | +Events are positioned via pixel calculation: |
| 75 | + |
| 76 | +```typescript |
| 77 | +left = getDaysDiff(timelineRange.min, eventStart) * pixelsPerDay; |
| 78 | +width = durationDays * pixelsPerDay; |
| 79 | +``` |
| 80 | + |
| 81 | +### Drag/Resize |
| 82 | + |
| 83 | +- `handleDragStart()` — captures initial state |
| 84 | +- Global `mousemove`/`mouseup` listeners update position |
| 85 | +- On mouseup, calculate delta days and update event dates |
| 86 | + |
| 87 | +### Lane Stacking |
| 88 | + |
| 89 | +`utils/eventLanes.ts` assigns lane indices to overlapping events. Track height expands based on max lane count. |
| 90 | + |
| 91 | +### Zoom |
| 92 | + |
| 93 | +`ZOOM_LEVELS` array in constants.ts. Values are pixels-per-day. Zoom preserves center point using `targetZoomCenterRef`. |
| 94 | + |
| 95 | +### Persistence |
| 96 | + |
| 97 | +Data saved to `localStorage` keys: `chrono_data`, `chrono_bounds` |
| 98 | + |
| 99 | +## Common Tasks |
| 100 | + |
| 101 | +### Add new event field |
| 102 | + |
| 103 | +1. Update `IEvent` in `types.ts` |
| 104 | +2. Update `EventModal.tsx` form |
| 105 | +3. Handle in `handleSaveEvent()` in `App.tsx` |
| 106 | + |
| 107 | +### Add new track field |
| 108 | + |
| 109 | +1. Update `ITrack` in `types.ts` |
| 110 | +2. Update `TrackModal.tsx` form |
| 111 | +3. Handle in `handleSaveTrack()` in `App.tsx` |
| 112 | + |
| 113 | +### Modify zoom behavior |
| 114 | + |
| 115 | +Edit `ZOOM_LEVELS` or `DEFAULT_ZOOM_INDEX` in `constants.ts` |
| 116 | + |
| 117 | +### Change default colors |
| 118 | + |
| 119 | +Edit `COLOR_PALETTE` in `constants.ts` |
| 120 | + |
| 121 | +### Add new modal |
| 122 | + |
| 123 | +1. Add enum value to `ModalType` in `types.ts` |
| 124 | +2. Create modal component extending `Modal.tsx` pattern |
| 125 | +3. Add state and handlers in `App.tsx` |
| 126 | +4. Add conditional render in `App.tsx` return |
| 127 | + |
| 128 | +## Commands |
| 129 | + |
| 130 | +```bash |
| 131 | +pnpm install # Install deps |
| 132 | +pnpm dev # Dev server (localhost:5173) |
| 133 | +pnpm build # Production build to dist/ |
| 134 | +pnpm preview # Preview production build |
| 135 | +``` |
| 136 | + |
| 137 | +## Conventions |
| 138 | + |
| 139 | +- Functional components only |
| 140 | +- Tailwind for all styling (no CSS files) |
| 141 | +- Date strings always "YYYY-MM-DD" format |
| 142 | +- IDs generated via `crypto.randomUUID()` |
| 143 | +- No external state management |
| 144 | +- Components receive data + callbacks as props |
| 145 | +- All business logic in App.tsx handlers |
0 commit comments