# Map — Rules (per FVW v8 §11.9)

**Module ID:** `oscar.map`
**Version:** 0.2.0

## 1. Capability Declaration

- **Mode:** Sovereign SPA Mode (per DO-001)
- **Runtime renderer:** Leaflet. The prior v0.1.0 declaration of "SVG-only, no Leaflet dependency" is explicitly superseded. The runtime has used Leaflet since the original v1 implementation; v0.2.0 codifies that as the authoritative runtime renderer. If a future revision migrates away from Leaflet, this rules.md and codex.md §C1 must be amended with a new fragment; runtime code may not change silently.

## 2. Do-not-proceed rule

Stop on App-VP test failure.

## 3. Contract primacy

Contract = recipe.md + codex.md. codex.md §C1 is authoritative for renderer choice; recipe.md §A is authoritative for product description; conflicts resolved in favour of codex.md §C1 for technical / architectural claims and recipe.md §A for behavioural / product claims.

## 4. Anti-drift enforcement

1. Recipe → Source
2. Visual Diff
3. Behavioural (C6)
4. Contract (constitutional_fragments)
5. Coverage matrix — re-run after every doctrinal amendment; drift = zero is the bar for declaring alignment

## 5. Plan-as-law

See plan.md. The plan order in v0.2.0 is binding: doctrine surfaces must be implemented in the order listed in plan.md. Reordering requires an explicit amendment fragment.

## 6. Fallback behaviour

- No project → save disabled (v0.1.0, retained)
- No layers matching filter → map shows empty base tiles + filter-state badge; layer list shows empty-state copy; no error
- Vertex drag released outside map → coordinate clamped to nearest in-bounds tile; no error
- Stale selection (layer deleted) → selection cleared silently; side panel closes

## 7. User-simulator requirement

C6 user simulations must be runnable. v0.2.0 expands the C6 list to 20 simulations covering selection, filters, exports, vertex edit, and documents view. All 20 must pass against production main before v0.2.0 is declared SHIPPED.

## 8. Re-anchor cadence

At every phase boundary. For v0.2.0, the phase boundaries are the implementation steps in plan.md — between each step, re-anchor by re-running C6 user simulations + coverage matrix.

## 9. Coverage matrix

See coverage-matrix.md. Coverage matrix in v0.2.0 enumerates every fragment, every rule, and every file expected for the implementation. A row is satisfied when the source file exists, the implementation matches the rule, and the fragment is bound.

## 10. Versioning rules (v0.2.0)

A layer's `version` field is incremented on each significant edit. Significant edits are:

- Vertex move (drag committed)
- Layer delete
- Layer rename
- Layer kind change
- Linked-tree change (link / unlink / re-link to a different tree)

Non-significant edits do not bump version:

- Note text edit
- Photo attach / detach
- Photo reorder
- Selection (no edit)
- Filter (no edit)
- Map pan / zoom (no edit)

Each version bump writes a `LayerVersion` record to the `layer_versions` IDB store with:

- `layerId` — the layer that was edited
- `version` — the new version number (after the bump)
- `timestamp` — ISO8601 UTC
- `operation` — one of `'vertex-move' | 'delete' | 'rename' | 'kind-change' | 'linked-tree-change'`
- `actor` — `'operator'` in v0.2.0 (future cloud-sync will replace with profile id)

## 11. Document-snapshot rule (v0.2.0)

Every export writes a `Document` record to the `documents` IDB store. The record MUST include the layer version of every layer included in the export, captured at the moment of export. This is the audit trail: given a Document, one can recover exactly what state was represented.

`Document.layerIds: string[]` — the layer IDs included
`Document.layerVersions: Record<layerId, versionNumber>` — version of each layer at export time

Documents are snapshots. Layers are the source of truth. A Document's `layerVersions` map is read-only and must not be updated when the underlying layers are edited — that is what makes the Document a snapshot of the past, not a live view.

## 12. Annotation rule (v0.2.0)

Annotations on Documents are stored separately in the `annotations` IDB store. v0.2.0 defines the schema and the write path. v0.2.0 does NOT define:

- An auto-interpretation pass (operator must interpret manually)
- An "apply annotation to layer" mutation
- A "dismiss annotation" auto-flow

Operator can write an annotation (status: 'open'), update its status to 'applied' or 'dismissed' manually, and read annotations back. No code in v0.2.0 reads annotation text and mutates a layer based on it. The schema exists so a future doctrine pass can implement auto-interpretation without breaking the storage contract.

## 13. Cloud-sync stub rule (v0.2.0)

The forward-compat sync fields defined in codex.md §C10 exist on every `MapLayer` and `Document` record. v0.2.0 MUST NOT:

- Read the `syncState` field for any behaviour
- Write the `syncState` field from anywhere
- Make sync state visible in any UI surface
- Block any operation on `syncState !== 'synced'`

The fields exist purely so a future cloud-sync doctrine pass can populate them and read from them without a schema migration. Their presence in v0.2.0 is a no-op.

## 14. Two-way selection rule (v0.2.0)

Selection is a single shared piece of state (`selectedLayerId`). Map and layer list both subscribe to it. Either surface may set or clear the selection. Both surfaces MUST reflect the current selection at all times. There is no separate "map-selected" and "list-selected" — they are the same selection.

## 15. Empty-filter rule (v0.2.0)

A filter with no active values is treated as "no filter applied", which means "show all". Specifically:

- `filterKind` empty Set → all kinds visible
- `filterDate === null` → all dates visible
- `filterName === ''` → all names visible

A user can clear filters by deselecting all chips / clearing the name search box. There is also a "Clear all filters" affordance on the filter chip row.

---

## END OF RULES.MD (v0.2.0)