Epoch is a React timeline editor. Users create tracks (rows) and events (time-bound rectangles). Events can be dragged, resized, and moved between tracks.
- React 19 + TypeScript
- Vite (dev server, build)
- Tailwind CSS (utility classes, no CSS files)
- Lucide React (icons)
- No state management library (useState + useRef)
- LocalStorage for persistence
App.tsx # Main component, all state, all handlers
types.ts # TypeScript interfaces (ITrack, IEvent, IDragState, etc.)
constants.ts # Default data, zoom levels, color palette
components/
AppHeader.tsx # Top bar with zoom controls
TrackSidebar.tsx # Left sidebar, track list, drag-to-reorder
TimelineCanvas.tsx # Main canvas, renders events, handles interactions
Modal.tsx # Base modal wrapper
TrackModal.tsx # Edit/create track
EventModal.tsx # Edit/create event
DataModal.tsx # Import/export JSON, timeline bounds settings
Button.tsx # Reusable button component
color/ColorPicker.tsx # Color selection grid
utils/
dateUtils.ts # Date parsing, formatting, arithmetic (parseDate, formatDate, addDays, getDaysDiff, etc.)
eventLanes.ts # Calculate lane assignments for overlapping events
interface ITrack {
id: string;
title: string;
color: string;
order: number; // Controls vertical position
}
interface IEvent {
id: string;
trackId: string;
title: string;
description?: string;
startDate: string; // "YYYY-MM-DD"
endDate: string; // "YYYY-MM-DD"
color?: string;
}ALL state lives in App.tsx:
data: { tracks: ITrack[], events: IEvent[] }— main datazoomIndex— current zoom level indexdragState— event drag/resize stateisPanning— canvas pan stateviewBounds— min/max year for timeline rangeeditingTrack,editingEvent— modal editing targetsactiveModal— which modal is open (enum ModalType)
Events are positioned via pixel calculation:
left = getDaysDiff(timelineRange.min, eventStart) * pixelsPerDay;
width = durationDays * pixelsPerDay;handleDragStart()— captures initial state- Global
mousemove/mouseuplisteners update position - On mouseup, calculate delta days and update event dates
utils/eventLanes.ts assigns lane indices to overlapping events. Track height expands based on max lane count.
ZOOM_LEVELS array in constants.ts. Values are pixels-per-day. Zoom preserves center point using targetZoomCenterRef.
Data saved to localStorage keys: chrono_data, chrono_bounds
- Update
IEventintypes.ts - Update
EventModal.tsxform - Handle in
handleSaveEvent()inApp.tsx
- Update
ITrackintypes.ts - Update
TrackModal.tsxform - Handle in
handleSaveTrack()inApp.tsx
Edit ZOOM_LEVELS or DEFAULT_ZOOM_INDEX in constants.ts
Edit COLOR_PALETTE in constants.ts
- Add enum value to
ModalTypeintypes.ts - Create modal component extending
Modal.tsxpattern - Add state and handlers in
App.tsx - Add conditional render in
App.tsxreturn
pnpm install # Install deps
pnpm dev # Dev server (localhost:5173)
pnpm build # Production build to dist/
pnpm preview # Preview production build- Functional components only
- Tailwind for all styling (no CSS files)
- Date strings always "YYYY-MM-DD" format
- IDs generated via
crypto.randomUUID() - No external state management
- Components receive data + callbacks as props
- All business logic in App.tsx handlers