|
| 1 | +# Copilot Instructions for nina-maps |
| 2 | + |
| 3 | +## Architecture Overview |
| 4 | + |
| 5 | +This is a React map editor for geospatial data using MapLibre GL. The architecture follows a configuration-driven approach where map definitions are JSON files validated against Zod schemas. |
| 6 | + |
| 7 | +**Key data flow:** JSON config → Zod validation → Zustand store → transformation libs → MapLibre/DeckGL layers |
| 8 | + |
| 9 | +## Schemas (src/schemas/) |
| 10 | + |
| 11 | +All types are defined as Zod schemas and exported from `src/schemas/index.ts` via `src/types.ts`. When modifying types: |
| 12 | + |
| 13 | +1. Edit the Zod schema in the appropriate file under `src/schemas/` |
| 14 | +2. Run `pnpm generate-schema` to regenerate `schemas/map-config.schema.json` |
| 15 | +3. Types are auto-inferred - never manually duplicate type definitions |
| 16 | + |
| 17 | +Schema organization: |
| 18 | +- `legend/` - Raster (linear, interval, image) and vector (fill, line, circle) legends |
| 19 | +- `source/` - Layer sources: pmtiles, titiler, raster, parquet, wms, wmts |
| 20 | +- `layer/` - Layer, Folder, Tree structures |
| 21 | +- `map.ts` - MapConfig, MapSettings, ViewState |
| 22 | + |
| 23 | +## State Management (src/hooks/app.ts) |
| 24 | + |
| 25 | +Global state uses Zustand with immer middleware. The store (`useAppStore`) holds the entire MapConfig and provides actions for mutations. |
| 26 | + |
| 27 | +**Pattern:** Use reselect selectors (`createAppSelector`) for derived data. Export custom hooks like `useLayer(id)`, `useLayers()`, `useMaplibreMapConf()`. |
| 28 | + |
| 29 | +## Layer Transformation (src/libs/) |
| 30 | + |
| 31 | +- `toMaplibre.ts` - Converts layer configs to MapLibre source/layer specs. Contains `buildPMTilesLayer()`, `buildRasterLayer()`, etc. |
| 32 | +- `toDeckGL.ts` - Converts parquet layers to DeckGL layers with GeoArrow support |
| 33 | + |
| 34 | +When adding a new layer type: add schema in `src/schemas/source/`, export from `src/schemas/source/index.ts`, add builder function in `toMaplibre.ts`. |
| 35 | + |
| 36 | +## Routing (src/routes/) |
| 37 | + |
| 38 | +Uses TanStack Router with file-based routing: |
| 39 | +- `$mapId.tsx` - Dynamic map routes |
| 40 | +- `editor/` - Editor-specific routes with `_layout` for shared layout |
| 41 | +- `_view/` folders contain read-only views, `edit/` contains editable forms |
| 42 | + |
| 43 | +Routes use `createFileRoute()` and can define loaders with TanStack Query via `queryOptions()`. |
| 44 | + |
| 45 | +## Components |
| 46 | + |
| 47 | +- `src/components/layer-fields/` - Form fields for each source type (PMTilesFields, TitilerFields, etc.) |
| 48 | +- `src/components/form-items/` - Reusable form primitives with TanStack Form |
| 49 | +- `MaplibreMap.tsx` - Main map component, integrates Popup for feature info |
| 50 | + |
| 51 | +## Commands |
| 52 | + |
| 53 | +```bash |
| 54 | +pnpm dev # Development server (proxies /titiler to localhost:8989) |
| 55 | +pnpm build # Production build with TypeScript check |
| 56 | +pnpm lint # Biome check |
| 57 | +pnpm lint:fix # Biome auto-fix |
| 58 | +pnpm generate-schema # Regenerate JSON schema from Zod definitions |
| 59 | +``` |
| 60 | + |
| 61 | +## Code Style |
| 62 | + |
| 63 | +- Use `@/` path alias for imports from `src/` (configured in tsconfig.app.json and vite.config.ts) |
| 64 | +- Biome for formatting (2-space indent, 120 line width) - run `pnpm lint:fix` |
| 65 | +- JSDoc comments for exported functions, especially in libs/ |
| 66 | + |
| 67 | +## Adding New Source Types |
| 68 | + |
| 69 | +1. Create schema file: `src/schemas/source/newtype.ts` |
| 70 | +2. Export from `src/schemas/source/index.ts` and add to `LayerConfigSchema` union |
| 71 | +3. Add form fields: `src/components/layer-fields/NewTypeFields.tsx` |
| 72 | +4. Add transformation: `src/libs/toMaplibre.ts` (or `toDeckGL.ts` for DeckGL layers) |
| 73 | +5. Run `pnpm generate-schema` |
0 commit comments