# GPX Visualizer — Project Notes for Claude ## Repository layout ``` gpx-vis/ ├── gpx-vis-backend/ Node.js/Express REST API (default port 3000) ├── gpx-vis-frontend/ Vanilla HTML/CSS/JS SPA (served by any web server) └── sample/ Standalone drag-and-drop GPX viewer (no backend) ``` ## Backend (`gpx-vis-backend/`) **Stack:** Express 5, Sequelize ORM, SQLite (default) or MySQL/MariaDB, bcryptjs, JWT, multer, xml2js **Entry point:** `index.js` **Config:** Copy `config.example.js` → `config.js` **Routes:** - `src/routes/auth.js` — login, register, `/api/auth/me` - `src/routes/directories.js` — folder CRUD - `src/routes/tracks.js` — track upload, list, update, delete, share - `src/routes/stats.js` — per-user and per-directory stats - `src/routes/share.js` — public share link access - `src/routes/admin.js` — user management (admin only) **Models:** `User`, `Directory` (tree via parentId), `Track`, `TrackPoint`, `ShareLink` **Key behaviours:** - First registered user becomes admin + active; subsequent users need admin activation - GPX track points are stored in the DB (`TrackPoint` table), not as files - GPX compression: skip a point if distance < 2 m OR (time < 30 s AND bearing change < 30°) - Track date = first GPX timestamp if present, else upload date - Share links use 10-char consonant-vowel alternating codes (e.g. `bodaveximu`) **Tests:** `npm test` — mocha, uses in-memory SQLite via `test/setup.js` (overrides `Module._load` for config at module level) ## Frontend (`gpx-vis-frontend/`) **Stack:** Vanilla JS, Leaflet.js (CDN), no build step **Config:** Copy `config.example.js` → `config.js` **JS modules (loaded in order):** - `js/api.js` — all `fetch` wrappers (`API.*`) - `js/auth.js` — login/register forms, JWT storage - `js/map.js` — Leaflet map, track layers, hover marker, URL hash state (`#map=lat,lng,zoom&tracks=id1,id2&open=id`) - `js/elevation.js` — canvas altitude-profile chart, chart↔map hover cross-linking - `js/browser.js` — file/folder tree browser, drag-and-drop, track actions - `js/stats.js` — stats tab rendering - `js/app.js` — global utilities, admin panel, share-page init, main entry point **`browser.js` design:** - Tree view with expand/collapse triangles; expanded state survives reloads - Clicking a folder selects it (upload context) and loads all its tracks onto the map, then zooms to fit - Tracks are draggable onto folders; drop asks for confirmation before moving - `selectedDirId` is the current upload context (new folder / file upload go here) - `dirContents` cache: key `'root'` or numeric dir id → `{dirs, tracks}` - `dirMeta` cache: dir id → `{id, name, parentId}` (used for breadcrumb path) **`elevation.js` design:** - `Elevation.setTrack(pts)` — accepts flat `[{lat,lon,ele,time,dist}]` array from `MapView.getTrackPoints()` - `Elevation.onMapHover(point)` / `onMapLeave()` — called by `map.js` when hovering the current track - `Elevation.formatTooltip(point)` — shared HTML formatter used by the Leaflet map tooltip - Chart hover calls `MapView.showHoverMarker()` / `hideHoverMarker()` to move the map dot - Both modules reference each other at call time (not at definition time) — load order doesn't matter **`map.js` hover design:** - `flattenPoints(trackData)` converts segments `[[lat,lon,ele,time],…]` → `[{lat,lon,ele,time,dist}]` with cumulative haversine distance - On every `mousemove`, iterates all loaded track points to find the nearest within `HOVER_TOLERANCE_PX = 20` px - If nearest point belongs to `currentTrackId`, calls `Elevation.onMapHover()`; otherwise calls `Elevation.onMapLeave()` - Click on the map pins/unpins the hover tooltip (sticky mode) ## Deployment — when to restart the backend **Backend restart required** (changes to Node.js source, config, or dependencies): - Any edit under `gpx-vis-backend/src/` - Changes to `gpx-vis-backend/index.js` or `config.js` - Running `npm install` (new/updated packages) - After `git pull` when any backend file changed **No restart needed** (frontend-only changes): - Any edit under `gpx-vis-frontend/` (HTML, CSS, JS, config.js) - Changes to `CLAUDE.md`, `README.md`, or other docs After `git pull`, check `git diff HEAD~1 --name-only` — if all changed files are under `gpx-vis-frontend/` or are docs, skip the restart. ## Development notes - Do not mock the database in tests — `test/setup.js` wires real in-memory SQLite - All track geometry is stored as `TrackPoint` rows; no file storage after upload - The `Module._load` override in `test/setup.js` is required because `config.js` is loaded at module level in several files - `API.getTracks('')` returns root-level tracks (directoryId = null) - Track point array format from `/api/tracks/:id/points`: `segments: [[[lat, lon, elevation, time], …], …]`