# Rationale: Staff attendance locations admin page

## Context

> ⚠️ Premise note: Copies into other schools are unlinked and later matched by name. That is simple to build and easy to get wrong if two schools already used the same name for different gates. A link table would be safer and is a bigger decision than this page. We still design the unlinked copy because you asked for a branch picker on create and edit, and spec 0001 has no cross school id.

Staff check in needs named places with a center and a radius. Spec 0001 already put `staff_attendance_locations` in each school database, with no `school_id` column and no hard delete in the model. This slice is the first admin write path on that table. The app is PHP pages in a WebView, styled with `zapapp.css`. There is no npm app and no map library in the tree yet.

Forces: the person drawing the fence is often on a phone at the gate; they need to see other places so two gates do not sit on top of each other; several schools in one organisation share the same physical site; used places must keep a `location_id` because day rows point at it; radius 20 to 2000 cannot be a MySQL CHECK on this host; Nominatim and OpenStreetMap tiles need no paid key, which matches a portal that must not grow new secrets.

If we do not decide this, `/develop` will invent the map stack, the delete rule, and how branch copies work, and those guesses will fight spec 0001.

Compliance: school coordinates are a real place, not a staff face. Access stays on admin, director, and backend (plus staff sessions whose status is admin). This is not a finished NDPR review.

## Options considered

### Option 1: App list plus Leaflet map on the 0001 table, optional unlinked branch copies

PHP pages in `app/admin`, engine and AJAX in `admin/includes`, Leaflet 1.9 from a CDN, OSM tiles, Nominatim through PHP. Delete removes unused rows and deactivates used ones. Extra schools get a name matched copy.

**Pros**:
- Fits the existing app bootstrap, CSRF, and zap-app Pattern C.
- No API key and no npm.
- Honours spec 0001 ids for history.

**Cons**:
- CDN and OSM need network.
- Unlinked copies can overwrite a same named gate in another school.
- Name uniqueness in PHP can race.

### Option 2: Paid map SDK (Google Maps or Mapbox) in the browser

A keyed JS SDK with Places search instead of Nominatim.

**Pros**:
- Stronger search and usually faster tiles.
- One vendor for map and geocoding.

**Cons**:
- New secret and a billing account, which AGENTS.md tells us not to add.
- WebView and key restriction work is extra.
- Search would still need a proxy so the key does not sit in page JS.

### Option 3: Numeric latitude and longitude fields, no map

A form with name, two numbers, and radius.

**Pros**:
- Fewest moving parts. No CDN, no OSM policy, no Nominatim cap.

**Cons**:
- Admins cannot see the fence or overlap on a phone at the gate, which is the job of this slice.

### Option 4: Linked multi school location row in the owner database

One canonical place, many school databases point at it.

**Pros**:
- Edit once, every branch stays in sync.
- No name match collisions.

**Cons**:
- Spec 0001 forbids `school_id` on the school tables and keeps tenant data in the school database. A cross school link belongs in a new spec, not a silent extra table on this page.

## Rationale

Option 1 matches the stack and the 0001 table. The real job is drawing a fence you can see, so Option 3 fails the product. Option 2 is a better map with a cost and a secret this repo is trying not to grow. Option 4 is the right sync model if branch copies become painful; it is a new decision, not this page.

Leaflet plus OSM plus Nominatim is the boring path: no key, works in a WebView over HTTPS, and search is a helper rather than the only way to set a point. Search runs on submit, not while typing, so we do not use Nominatim as autocomplete (their usage policy discourages that, and 20 lookups per 10 minutes would burn fast). Click, drag, and my location still work when Nominatim is down. A PHP proxy is required so we send a real User-Agent and so the browser is not the OSM client. Tiles use `tile.openstreetmap.org` with no subdomain `{s}`. Extra school writes restore the session database in `finally`. On edit, extra schools match the old name so a rename updates the same gate instead of inserting a second row.

Delete is a hybrid because 0001 already said used places must not vanish (days keep `location_id`) while you still wanted a Delete action. Unused rows can go. Used rows turn off. Unique names include inactive rows so a deactivated gate is the one you turn back on, not a second row with the same name.

Branch copies stay unlinked because 0001 has no cross school key. Create and edit both offer the picker; other schools start unchecked on edit so a save cannot surprise another campus. Current school always writes. Failures on extra schools are reported and kept, because there is no distributed transaction across school databases.

Pagination is skipped on purpose. A school has a handful of gates. The expert default to paginate every list would add chrome for three cards. The cap is 50 so this cannot become an unbounded dump.

Role check includes staff sessions with `$info['status']` equal to `admin` because that is how school admins sign in today. Principal and academic_admin are out unless that status is admin, matching the scope line rather than the wider Manage staff nav.
