Add docker build

This commit is contained in:
Andras Schmelczer 2026-02-01 13:10:34 +00:00
parent c84af213e2
commit 627ba5496a
4 changed files with 184 additions and 0 deletions

13
.dockerignore Normal file
View file

@ -0,0 +1,13 @@
data/
data_sources/
.venv
**/node_modules
**/dist
server-rs/target
.git
.task
.claude
__pycache__
*.parquet
analyses/
*.log

49
.github/workflows/docker.yml vendored Normal file
View file

@ -0,0 +1,49 @@
name: Docker
on:
push:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest
type=sha,prefix=sha-,format=short
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

View file

@ -117,6 +117,103 @@ React 18 + TypeScript. deck.gl `H3HexagonLayer` over MapLibre GL. TailwindCSS. N
- Properties pane uses feature names from API response (human-readable), not hardcoded field names
- Proxy: dev server on :3030 proxies `/api` to :8001; also handles VS Code `/proxy/PORT` patterns
## Frontend Design Guide (STRICT — must be followed for all UI changes)
The frontend uses Tailwind's `darkMode: 'class'` strategy. The `dark` class is toggled on `<html>`. Every visible element must have both light and dark styles. **Never add a light-only color class without its `dark:` counterpart.** Run `task build:frontend` after any UI change to verify.
### Theme System
- **State**: `App.tsx` owns a `theme` state (`'light' | 'dark' | 'system'`), persisted in `localStorage` under the key `theme`, default `'system'`.
- **Effective theme**: When `'system'`, resolved via `window.matchMedia('(prefers-color-scheme: dark)')`. A `change` listener re-renders on OS preference flip.
- **Toggle cycle**: light → dark → system → light. Three-way, not binary.
- **Flash prevention**: `index.html` contains an inline `<script>` that applies the `dark` class before first paint. If the localStorage/matchMedia logic in that script changes, update it to match `App.tsx`.
- **Prop plumbing**: `effectiveTheme` (`'light' | 'dark'`) is passed as a prop to `<Map>` and `<HomePage>`. Components that need the resolved theme must receive it as a prop — do not read localStorage or matchMedia inside child components.
### Color Token Reference
Every UI element must use the correct token from this table. Do not invent new pairings.
| Role | Light class | Dark class | Hex (dark) |
|------|------------|------------|------------|
| **Page / pane background** | `bg-warm-50` or `bg-white` | `dark:bg-warm-900` | #1c1917 |
| **Card / elevated surface** | `bg-white` | `dark:bg-warm-800` | #292524 |
| **Inset / recessed surface** | `bg-warm-100` or `bg-warm-50` | `dark:bg-warm-800` | #292524 |
| **Input / select background** | `bg-white` | `dark:bg-warm-800` or `dark:bg-warm-900` | |
| **Primary border** | `border-warm-200` | `dark:border-warm-700` | #44403c |
| **Subtle border (dividers)** | `border-warm-100` | `dark:border-warm-800` | #292524 |
| **Primary text (headings)** | `text-navy-950` or implicit dark | `dark:text-warm-100` | #f5f5f4 |
| **Body text** | `text-warm-700` | `dark:text-warm-300` | #d6d3d1 |
| **Secondary text (labels, hints)** | `text-warm-500` or `text-warm-600` | `dark:text-warm-400` | #a8a29e |
| **Disabled / placeholder text** | `text-warm-400` / `placeholder-warm-400` | `dark:text-warm-500` / `dark:placeholder-warm-500` | #78716c |
| **Accent text (links, actions)** | `text-teal-600` | `dark:text-teal-400` | #1de4c3 |
| **Accent hover text** | `hover:text-teal-800` | `dark:hover:text-teal-300` | #51f7d9 |
| **Accent background (highlights)** | `bg-teal-50` | `dark:bg-teal-900/30` | |
| **Active ring / focus ring** | `ring-teal-400` | same — works in both | |
| **Price / key metric text** | `text-teal-700` | `dark:text-teal-400` | |
| **Remove / close button** | `text-warm-400 hover:text-warm-700` | `dark:hover:text-warm-300` | |
| **Checkbox accent** | `accent-teal-600` | same — works in both | |
| **Header (unchanged both modes)** | `bg-navy-900 text-white` | same | |
### Mapping Rules for Specific Contexts
**Sidebars (Filters, POIPane, PropertiesPane, right-pane tabs):**
- Container: `bg-white dark:bg-warm-900`
- Inner cards / dropdown menus: `bg-white dark:bg-warm-800`
- Borders: `border-warm-200 dark:border-warm-700`
- Tab text (active): add `dark:text-warm-100`
- Tab text (inactive): `text-warm-600 dark:text-warm-400`
**Map overlays (PostcodeSearch, MapLegend, POI popup, loading indicator):**
- Background: `bg-white dark:bg-warm-800`
- Text: `dark:text-warm-200`
- Semi-transparent variants: use `/90` opacity suffix (e.g. `dark:bg-warm-800/90`)
- Deck.gl tooltip (inline styles, not Tailwind): use `#292524` bg / `#e7e5e4` text / `rgba(0,0,0,0.5)` shadow in dark.
- Deck.gl postcode labels (RGB arrays): `[220,220,220,220]` text / `[30,30,30,200]` outline in dark; inverse in light.
**Map basemaps:**
- Light: `https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json`
- Dark: `https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json`
- `handleMapLoad` must only apply label/water tweaks in light mode. Dark Matter has good defaults.
**HomePage (landing page):**
- Page bg: `bg-warm-50 dark:bg-warm-900`
- Cards: `bg-white dark:bg-warm-800` with `border-warm-200 dark:border-warm-700`
- Backdrop-blur panels: use `/60` or `/40` opacity on both `bg-warm-50` and `dark:bg-warm-900`
- HexCanvas: reads `isDark` ref; uses dimmer fill (`#058172`) and stroke (`#0a665b`) at 60% opacity multiplier.
- All headings: `dark:text-warm-100`. All body: `dark:text-warm-300` or `dark:text-warm-400`.
**DataSourcesPage:**
- Same card pattern as above. Footer is already dark (`bg-navy-900`) — no changes needed.
- License badges: `bg-warm-100 dark:bg-warm-700 text-warm-600 dark:text-warm-300`
- Links: `text-teal-600 dark:text-teal-400`
**DataSources floating button (on map):**
- `bg-white/90 dark:bg-warm-800/90` with `text-teal-600 dark:text-teal-400`
### Rules for New Components
1. **Every `bg-white` needs `dark:bg-warm-800` or `dark:bg-warm-900`.** Pane-level = warm-900, card-level = warm-800.
2. **Every `border-warm-200` needs `dark:border-warm-700`.**
3. **Every `text-warm-*` needs a `dark:text-warm-*` counterpart.** Follow the token table — don't guess.
4. **Every `text-teal-600` needs `dark:text-teal-400`.** Every `hover:text-teal-800` needs `dark:hover:text-teal-300`.
5. **Every `bg-teal-50` needs `dark:bg-teal-900/30`.**
6. **Every `hover:bg-warm-50` needs `dark:hover:bg-warm-700` or `dark:hover:bg-warm-800`.**
7. **Inputs and selects**: always add `dark:bg-warm-800 dark:text-warm-200 dark:border-warm-700`. Placeholders get `dark:placeholder-warm-500`.
8. **Checkboxes**: always include `accent-teal-600 rounded`.
9. **Do not use Tailwind `dark:` classes inside deck.gl layers or canvas code.** Use the `theme` prop / ref and conditional JS values.
10. **Do not add `transition-*` classes for theme switching.** The global CSS rule in `index.css` handles transitions for `background-color`, `border-color`, and `color` on all standard HTML elements. Adding per-element transition classes will conflict.
11. **Never hardcode hex colors in JSX `style=` props for themed elements** (except deck.gl tooltip and canvas, which can't use Tailwind). Use the Tailwind classes from the token table instead.
12. **The header (`bg-navy-900`) is identical in both themes.** Do not add dark variants to it.
### Verification Checklist (for any UI PR)
- [ ] `task build:frontend` passes with no errors
- [ ] Every new `bg-*`, `text-*`, `border-*` class has a `dark:` counterpart (search your diff)
- [ ] Toggle through all three modes (light → dark → system) with no flash
- [ ] Map basemap switches when theme changes
- [ ] Sidebars, dropdowns, and popups are readable in both modes
- [ ] HomePage and DataSourcesPage adapt correctly
## Key Implementation Details
- **Spatial sort**: Rows sorted by 0.01° grid cell at load time for cache-friendly sequential access

25
Dockerfile Normal file
View file

@ -0,0 +1,25 @@
# Stage 1: Build frontend
FROM node:20-slim AS frontend
WORKDIR /app/frontend
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci
COPY frontend/ ./
RUN npm run build
# Stage 2: Build Rust server
FROM rust:1.83-bookworm AS server
WORKDIR /app
COPY server-rs/ server-rs/
WORKDIR /app/server-rs
RUN cargo build --release
# Stage 3: Runtime
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=server /app/server-rs/target/release/property-map-server ./
COPY --from=frontend /app/frontend/dist ./dist/
EXPOSE 8001
ENTRYPOINT ["./property-map-server"]
CMD ["--data", "/data/wide.parquet", "--pois", "/data/filtered_uk_pois.parquet"]