PR 2 · Use asseyri.net for production, admin, review and data; provider-neutral e-mail
Commit e2f991c3c569
Gate report
# Phase 0 gate report — Foundations
**Date:** 2026-09-30 · **Branch:** `phase-0/foundations` · **Status:** code complete, waiting for accounts + your devices
## Summary (read this first)
- **Built:** the whole Phase 0 codebase — monorepo, invite-only database with RLS, admin "Invite tester" page and Edge
Function, data Worker with session check, PWA shell (install guide, onboarding, e-mail-code sign-in, offline app shell,
"What works on this phone"), design tokens in 3 themes with Storybook, Python pipeline with a one-atoll pack job and a
job runner, Oracle Cloud Shell bootstrap, CI and 16 operation workflows ("buttons"), all documents, and a **Test lab**
in the app with one page per spike.
- **Verified here:** 85 TypeScript tests, 10 Python tests, lint, types, builds, Storybook build, licence check
(commercial build contains no non-commercial source), bundle budget, actionlint on every workflow, and Playwright on
Chromium **and** WebKit at phone and tablet sizes (38 passed, 2 skipped by design).
- **Owner answers applied (2026-09-30):** name **NavMV** (`navmv`), repository **navmv/navmv** (private), test phones
**iPhone on iOS 26** and **Samsung Galaxy S24**, Oracle region **US West (Phoenix)**, owner operates from a PC
(ADR-009). FAD spreadsheets received and pre-checked privately (`private/fads/`, not in the repository).
- **Not live yet:** nothing is deployed until the Supabase/Cloudflare secrets are in GitHub (`docs/HANDOVER.md`).
- **Your next step:** secrets via `scripts/set-secrets.ps1`, public Supabase values in chat, then `docs/HUMAN_TODO.md`
Steps 3–7 — each step ends with a button in `docs/OPS.md`.
## Decisions
| # | Question | Answer |
|---|---|---|
| H-01 | Working name | ✅ NavMV |
| H-02 | Repository | ✅ navmv/navmv (private) |
| H-03 | FAD lists | ✅ received — 36 of 124 rows flagged for your review (private report) |
| H-04 | Devices | ✅ iPhone (iOS 26), Samsung Galaxy S24 — tablet: to confirm |
| H-05 | Oracle | ✅ Always Free, US West (Phoenix) |
| H-19 | Own domain (on Cloudflare) | ⏳ use now or at the upgrade — see chat |
## Preview and review site
- Preview link: **none yet** — needs Cloudflare (HUMAN_TODO Step 4). The `Review site` workflow will then post
`https://pr-N.navmv-review.pages.dev` with the app preview, Storybook, test report, Lighthouse and screenshots.
- Screenshots (day/dark/night × EN/DV × phone/tablet on Chromium and WebKit) are generated by Playwright; checked locally.
One finding fixed from them: English fallback text in the Dhivehi (RTL) layout had punctuation at the wrong end →
`unicode-bidi: plaintext` on text blocks.
## Test results (local run, 2026-09-30)
| Check | Result |
|---|---|
| Unit — shared (licence, manifest, MFB decoder, sun times) | 15/15 ✅ |
| Unit — UI tokens, contrast (every text pair × 3 themes), Night has no blue/white, theme resolution | 52/52 ✅ |
| Unit — data Worker (JWT/JWKS, roles, Range, CORS, quota, no-store, traversal) | 11/11 ✅ |
| Contract Python ↔ TypeScript (MFB golden bytes, manifest fixture) | ✅ both sides |
| Pipeline (pytest + ruff) | 10/10 ✅, ruff clean |
| Lint (ESLint 10, react-hooks 7) / types (all packages + service worker) | ✅ / ✅ |
| Builds: PWA, admin, Storybook, review site | ✅ |
| Licence check — commercial build | ✅ no personal_only source; negative control: personal build contains EOX host |
| Bundle budget | shell 183 KB gz ✅ (≤ 220) · projected Map route 457 KB gz ❌ vs 350 → Phase 1 task |
| Playwright Chromium (Pixel 7, Galaxy Tab S4) | 20/20 ✅ incl. "opens offline after first visit" |
| Playwright WebKit (iPhone 14, iPad gen 7) | 18 ✅, 2 skipped (service-worker offline test not reliable in WebKit off-Apple; covered on devices) |
| actionlint (17 workflows) | ✅ no findings |
| Database RLS tests (pgTAP, 18 assertions) | ⏳ written; runs in CI (needs Docker, not on this PC) |
| Edge Function `deno check`, shellcheck, gitleaks | ⏳ run in CI |
Bugs found and fixed during the phase: duplicate `manifest.webmanifest` in the precache stopped the service worker from
registering (caught by the offline E2E test); Night accent with black text is only 4.1:1 (caught by the contrast test —
Night accent is now marker-only, documented in `docs/DESIGN.md`); `current_role` clashed with a reserved SQL word.
## Spikes (each has a test page in the app and a checklist in `docs/spikes/`)
| Spike | State | Provisional decision |
|---|---|---|
| S1 Map performance | ⏳ device run | MapLibre GL JS; pass = pan ≥ 45 FPS iPhone/tablet, ≥ 30 Android |
| S2 OPFS + PMTiles 128 MB download/resume/render | ⏳ device run | Worker + sync access handles, createWritable fallback, state per chunk |
| S3 Storage persistence | ⏳ 7-day observation | Install-first on iOS; persist() before big downloads |
| S4 60-min tracking | ⏳ device run | Foreground + Wake Lock + gap segments |
| S5 Alarm audio | ⏳ device run | Looping audio + audioSession + flash + vibration + pre-arm checklist |
| S6 Bundle decode | ⏳ device run · sizes measured: 3-day **2.26 MB**, 10-day **7.54 MB** gz ✅ | MFB1 as specified |
| S7 Compass | ⏳ device run | GPS course > 1 kn, compass below |
| S8 Heatmap | ⏳ device run | Raster per step for display, H3 values for explanations |
| S9 Sign-in in installed iOS app | ⏳ needs Supabase + devices | E-mail code, localStorage session (IndexedDB adapter if iOS drops it) |
| S10 Thaana | ⏳ device run (UI) · map labels → Phase 1 | HTML carries Dhivehi; Latin map labels until glyph PBFs pass |
| S11 Web Push | ⏳ needs VAPID keys; delivery test in Phase 1 | VAPID via Edge Function + in-app inbox |
| S12 Worker auth | ✅ logic (11 tests) · ⏳ CPU time after deploy | JWKS + role check, 401 → refresh → retry |
| S13 Online imagery | ⏳ needs keys | EOX online (personal), own S2 offline, Esri HD |
| S14 Oracle bootstrap | ⏳ first real run | Server + lean fallback |
## Acceptance criteria (brief §10 Phase 0)
| Criterion | State |
|---|---|
| Every spike has a documented decision | 🟡 provisional decisions documented; final ones need your device results |
| CI green incl. commercial-profile build | 🟡 all checks pass locally; CI runs once pushed |
| Previews and review sites open on the tablet | ⏳ needs Cloudflare |
| Invite myself and sign in with a code in the installed app on all three devices | ⏳ needs Supabase + devices (flow tested with the mock backend) |
| Worker refuses requests without a valid session | ✅ unit-tested · ⏳ live check (S12 page) |
| Storybook shows tokens in 3 themes and 2 directions | ✅ `Foundations/Tokens → Matrix` |
| Server can be rebuilt from the bootstrap | ⏳ first real run (S14) |
| Every operation in `docs/OPS.md` is a button | ✅ 17 workflows + Cloud Shell one-liners |
| `HUMAN_TODO.md` complete | ✅ |
## Known issues
1. **Map-route JavaScript budget** will be exceeded (457 vs 350 KB gz) once the map is on the home route — Phase 1
plan: lazy supabase-js, `zod/mini`, trim shell dependencies.
2. Supabase Storage objects are not in backups yet (none exist before Phase 1).
3. Sentry and Cloudflare Web Analytics are not wired (need accounts/DSNs); UptimeRobot uses the Worker's `/health`
endpoints; Healthchecks pings are built into every job.
4. Thaana **map** labels not solved (UI text is fine) — Phase 1 with the chart style.
5. The app icon is a placeholder until the name and logo are chosen.
6. The Kaafu bounding box comes from the brief's example; Phase 1 derives atoll boxes from reef data.
## Free-tier usage
Nothing deployed yet → 0 everywhere. Expected GitHub Actions use: ~25 min per push to a *ready* PR (CI incl. E2E +
review site), ~10 min per draft push, ~3 min per staging deploy — keep PRs in draft while iterating.
## Non-commercial compliance checklist
No revenue ✅ · invite-only, not promoted, `noindex` header + meta, robots.txt allows crawling ✅ · personal-only sources
tagged and compiled out of commercial builds ✅ · EOX online only, offline only with written OK (draft e-mail ready) ✅ ·
WDPA never in packs/exports (not used yet) ✅ · GFW token server-side only (not used yet) ✅ · MapTiler logo / Open-Meteo
attribution: layers not live yet · FAD data: none yet ✅ · commercial-profile build passes ✅.
## What happens after you approve
Merge the PR → `Deploy staging` runs → then Phase 1 (Offline MVP + tester toolkit) starts only after your go-ahead.