# Field Capture

Construction field measurement PWA. Draw dimension lines on photos of sketches/blueprints, enter measurements, export to PDF/ZIP.

## Architecture

**Single-file SPA**: `field-capture.html` (~3262 lines) contains ALL HTML, CSS, and JS. No build step, no bundler, no external dependencies.

- `sw.js` — Service worker (offline caching, update toast)
- `manifest.json` — PWA manifest
- `icon-*.svg/.png` — App icons

## Tech Stack

| Layer | Technology |
|-------|-----------|
| Language | Vanilla JS (ES2020+), no frameworks |
| Module pattern | IIFE: `(function(){ "use strict"; ... })()` |
| Rendering | String-based HTML (innerHTML), Canvas 2D API |
| State | Plain `state` object → `localStorage` |
| UI state | Separate `ui` object (screen, modals, gestures, undo/redo) |
| CSS | Inline `<style>`, CSS custom properties, mobile-first |
| PDF/ZIP | Hand-built from scratch (no libraries) |
| Testing | Python + Selenium + Firefox (geckodriver) |
| Dev server | `python -m http.server 8000` |

## Coding Conventions

- **Indentation**: 2 spaces
- **Naming**: `camelCase` functions/variables, `PascalCase` classes (`PDFBuilder`), `UPPER_SNAKE` constants (`STORAGE_KEY`, `APP_VERSION`)
- **Strings**: Single quotes in JS, double quotes in HTML attributes
- **HTML generation**: String concatenation with `escapeHtml()` for user content
- **Event handling**: Delegated clicks via `data-action` attributes; `data-*` for params
- **Debug logging**: `console.log("[DBG] ...")`, captured to `window.__dbgLogs`
- **Coordinates**: Normalized 0-1 for sketch points, transformed to screen via view offset/scale

## Running

```
python -m http.server 8000
# Open http://localhost:8000/field-capture.html
```

## Testing

Tests are standalone Python scripts (no test framework — raw `assert` + `print`).

```bash
# Run a single test
python _test_snap.py

# Run all tests (Linux/macOS)
for f in _test_*.py; do python "$f"; done
```

**Test pattern**:
1. Launch Firefox at 480x900 (phone size) via geckodriver
2. Navigate to `http://localhost:8000/field-capture.html?debug`
3. Create a job, open a sketch, inject a synthetic photo via JS
4. Simulate drawing via dispatched `PointerEvent`s (pointerdown/move/up)
5. Assert state/UI outcomes

**Test hook**: When `?debug` is in the URL, the app exposes `window.__test` with internal state, UI, and functions for test automation.

**Test files**: `_test_line_modal.py`, `_test_snap.py`, `_test_undo.py`, `_test_copy_drag.py`, `_test_rect_copy.py`, `_test_tag_mode.py`, `_test_backnav.py`, `_test_zoom_pan.py`, `_test_pointer_draw.py`, `_test_toast.py`, and more.

## Key Features to Know

- **Geometric snapping**: Endpoint snap, perpendicular/near snap, start-point snap
- **Ortho mode**: Constrain lines to horizontal/vertical
- **Rectangle drawing**: Draw diagonal → creates 4 dimensioned sides
- **Copy-drag**: Long-press a line's tag, drag to duplicate
- **Endpoint editing**: Long-press near an endpoint to move it independently
- **Scale calibration**: Longest dimensioned line = scale reference; new lines auto-guess dimension
- **3-state tag overlay**: L-number tags, dimension values, or off
- **Undo/redo**: Unified snapshot history
- **Browser back-button**: SPA navigation via popstate

## Versioning

Keep `APP_VERSION` in `field-capture.html` and `SW_VERSION` in `sw.js` in sync. Bump `SW_VERSION` with a date and small sequential number (e.g. `2026-09-17-1`) — this triggers the PWA update toast. Use small increments so it stays readable over many changes.

## DO NOT

- Add npm packages or build tools — this is zero-dependency by design
- Use frameworks or libraries — vanilla JS only
- Split `field-capture.html` into multiple files — single-file architecture is intentional
- Use headless Firefox for tests — tests use visible Firefox at 480x900
- Modify `.aider.conf.yml` or `.env` — those are for Aider AI assistant config

## Domains

- All domains registered through Namecheap.
- Production URL: **app.kurtkloud.com/field-capture.html** (served via Start9 Pages nginx on StartOS box → Tailscale → IONOS VPS Caddy reverse proxy).
- **fcap.app** → reverse proxy with rewrite, **fcap.app stays in the URL bar**. Caddy proxies to the same backend and rewrites `/` → `/field-capture.html`. DNS A record @ → 198.71.48.192 (not yet set, user does it via Namecheap). All static resources (sw.js, manifest.json, icons) serve at their real paths.
- **fieldim.com**, **feeldim.win** — owned but not deployed.
