Stage 1: project foundation (strict TS, lint boundaries B-1..B-7, directory structure, CI, boundary tests)

- dedicated git repo at /home/avi/Projects/Lumen (main)
- TypeScript strict (target ES2022, bundler, exactOptionalPropertyTypes, noUncheckedIndexedAccess)
- ESLint 9 + typescript-eslint strictTypeChecked + eslint-plugin-boundaries for B-1..B-7, no-restricted-globals/syntax for B-1/B-7
- Prettier 3.5
- Structure per IMPLEMENTATION-CONTRACT.md §4 (src/platform/idb|cache|sw, storage, data, sync/{transport,verifier}, domain/{emergency,schedule,map,festival,readiness,clock,favorites}, ui/{components,views,router,render}, app, emergency-baseline, assets, public, content, pipeline, tests, scripts)
- CI: .github/workflows/ci.yml (typecheck + lint + format + test)
- Boundary tests: tests/unit/boundaries.test.ts (4 tests) + scripts/check-boundaries.ts
- No feature code, no PWA/IDB/sync/mesh/accounts per contract Stage 1
This commit is contained in:
Lumen Stage1 2026-08-30 23:25:35 -05:00
commit c0bfd413ff
100 changed files with 9863 additions and 0 deletions

842
DISCOVERY.md Normal file
View file

@ -0,0 +1,842 @@
# Lumen — Discovery & Requirements Analysis
- **Phase:** Discovery & Requirements Analysis ONLY (no architecture lock-in, no implementation)
- **Date:** 2026-08-30
- **Status:** Complete — ready to inform a deliberate architecture phase
- **Author:** Lead architect / senior product engineer (automated discovery)
- **Scope guard:** No application code was written during this phase. No placeholder code exists. This document is the only artifact produced.
---
## 1. Project / Environment Findings
### 1.1 Project directory
`/home/avi/Projects/Lumen/` **exists but is effectively empty.** It was created on
Aug 30 20:11 and contains exactly one file:
| File | Content | Project-relevant? |
|------|---------|-------------------|
| `.directory` | KDE folder metadata (`[Desktop Entry] Icon=folder-yellow`) | No — desktop folder-color marker, not project work. Left untouched. |
Findings:
- **No source code**, no `package.json`, no lockfiles, no config files.
- **No README, design docs, or prior architecture.**
- **No tests, CI, or build tooling.**
- Nothing to preserve, migrate, or work around. This is a greenfield start.
### 1.2 Version control
- There is **no git repository inside** `/home/avi/Projects/Lumen/`.
- The directory sits inside a parent git repository rooted at `/home/avi`
(branch `master`, **zero commits**, no remotes). Any file added to Lumen is
technically visible to that parent repo, but there is no meaningful history.
- **Open question (VCS-1):** Should Lumen initialize its own dedicated git
repository? Recommended default: yes, at the start of the architecture phase.
### 1.3 Development environment
| Item | Value |
|------|-------|
| OS | Linux (development machine) |
| CPUs | 8 |
| RAM | 15 GiB |
| Disk | 1.8 TB, ~1.4 TB free (22% used) |
| Node.js | v22.23.2 |
| npm | 10.9.8 |
| pnpm | 11.24.0 |
| bun | 1.4.0 |
| git | 2.53.0 |
Any modern JS toolchain can run locally. No environment constraints detected.
### 1.4 Owner ecosystem context (non-binding)
Sibling projects in `/home/avi/Projects/` were lightly inspected for convention
signals only:
- `Folio` — Electron desktop app, TypeScript 5.5, esbuild.
- `Hammock` — Android/AOSP Kotlin project.
- `BookRead` — Python web app.
- Others: Nostr client, chess, misc.
**No existing web/PWA conventions exist** in the owner's workspace. TypeScript
appears to be the owner's preferred language, which may inform (but must not
predetermine) later technology evaluation.
---
## 2. Existing Files & Relevant Discoveries
There is no existing Lumen work product. The only discoveries that constrain the
project are external:
1. **Browser platform reality (as of Aug 2026)** — verified via research, detailed
in §12. The most consequential facts:
- iOS Safari enforces a **7-day inactivity eviction cap** on all script-writable
storage (localStorage, IndexedDB, Cache API, **service worker registrations**)
for sites used in Safari tabs. **Installed home-screen PWAs are exempt.**
- There is **no `beforeinstallprompt` on iOS** — installation is a manual,
multi-step "Add to Home Screen" flow that the product must teach.
- **No Background Sync / Periodic Background Sync on iOS Safari.** Sync only
happens while the app is open.
- Eviction, when it happens, is **all-or-nothing per origin** — IndexedDB,
Cache API, localStorage, and SW registrations are deleted together.
- Web Push on iOS requires iOS 16.4+, a home-screen-installed PWA, and is
region-dependent (EU behavior differs). Android Chrome push is full-featured.
- Safari 17+ storage quotas are generous on paper (~60% of disk per origin) but
are **best-effort**; `navigator.storage.persist()` is granted by heuristic
(home-screen install helps). Practical guidance from the field still recommends
keeping precached bundles lean (tens of MB, not hundreds).
2. **No existing festival data source exists.** The authoritative data pipeline
(who authors schedule/map/emergency content, in what format, hosted where)
must be designed from scratch or supplied by the festival organization.
3. **HTTPS is mandatory** for service workers, geolocation, and the Storage API.
Hosting must be HTTPS-capable (this does not predetermine a host).
---
## 3. Product Understanding
**Lumen** is a mobile-first **Progressive Web App** — explicitly *not* a native
app — serving as an offline-first festival companion for **~300–500 attendees**.
Core principle: **"Everything attendees need at the festival must work with zero
internet connectivity."** The festival environment is hostile to connectivity
(no cell/Wi-Fi, overloaded networks, airplane mode) and hostile to usability
(bright sun, darkness, noise, stress, low battery, movement).
The product is organized around four primary destinations, in priority order:
1. **EMERGENCY** — highest priority; must work with absolutely zero connectivity;
must remain reachable from everywhere in the app.
2. **SCHEDULE** — lineup, now/next, personal (local) favorites, filters, search.
3. **MAP** — festival-provided custom map with key locations; **not** online map
tiles; GPS never required.
4. **FESTIVAL** — general information (rules, FAQ, logistics, vendors, contacts).
Content should be treated as **versioned data** (a Festival Data Package), not
hardcoded logic. Connectivity is an **enhancement** (updates, announcements,
schedule changes) and must never be a prerequisite for critical functionality.
Future mesh networking is a **clean extension point only** — nothing mesh-related
is built or assumed in V1.
Success condition: an attendee who installs Lumen before arriving can use every
critical feature for the entire festival with their phone in airplane mode.
---
## 4. Explicit Requirements
IDs are traceable and will carry into the architecture phase.
### 4.1 Platform
| ID | Requirement |
|----|-------------|
| R-P1 | Lumen is a mobile-first PWA; primary targets are iOS Safari and Android Chrome. |
| R-P2 | No App Store / Play Store distribution; no native code in V1. |
| R-P3 | Served over HTTPS (required for SW, geolocation, storage APIs). |
| R-P4 | Desktop support only where it comes free; never a design target. |
### 4.2 Navigation & core UX
| ID | Requirement |
|----|-------------|
| R-N1 | Four primary destinations: EMERGENCY, SCHEDULE, MAP, FESTIVAL. |
| R-N2 | Navigation must be extremely simple and obvious. |
| R-N3 | Emergency must be easily accessible from every other part of the app. |
| R-N4 | UX optimized for one-handed use, gloves-free large touch targets, sunlight and darkness, stress, and low attention. |
### 4.3 Emergency
| ID | Requirement |
|----|-------------|
| R-E1 | Emergency baseline must be available with **zero** connectivity, always. |
| R-E2 | Static emergency data (procedures, contacts, locations, address/coordinates) bundled locally with the app/dataset. |
| R-E3 | Dynamic emergency data (e.g., updated contacts) may update when online but must never *depend* on a server. |
| R-E4 | Must support at minimum: 911 dialing, security contact, first aid & AED locations, emergency exits, muster points, procedures (weather/fire/lost person), festival address + GPS coordinates. |
| R-E5 | Emergency dialing (`tel:`) must work from installed standalone mode. |
### 4.4 Schedule
| ID | Requirement |
|----|-------------|
| R-S1 | Full schedule (artists, stages, events, workshops, times) available offline. |
| R-S2 | "Happening Now" and "Up Next" computed locally from local data. |
| R-S3 | Personal schedule (favorites) created and stored locally. |
| R-S4 | Filtering (stage, type) and search work offline. |
| R-S5 | Correct behavior analysis required for: device time, festival timezone, and incorrect device clocks. |
### 4.5 Map
| ID | Requirement |
|----|-------------|
| R-M1 | Festival map works offline; core map must NOT depend on online tiles. |
| R-M2 | Festival supplies custom map artwork/geometry. |
| R-M3 | Point-of-interest locations: stages, bathrooms, food, water, vendors, first aid, AEDs, security, entrances/exits, parking, camping, VIP, muster points, other infrastructure. |
| R-M4 | Browser Geolocation may be used as an enhancement; **never required** for critical functionality. |
| R-M5 | Map representation (SVG/vector vs raster vs canvas) to be evaluated, not yet chosen. |
### 4.6 Festival info
| ID | Requirement |
|----|-------------|
| R-F1 | General info (rules, FAQ, bring/don't-bring, parking, camping, transport, accessibility, vendors, merch, contacts, hours, venue) available offline. |
| R-F2 | Content is data-driven (part of the Festival Data Package). |
### 4.7 Offline-first architecture
| ID | Requirement |
|----|-------------|
| R-O1 | App shell loads with no network at all (after first successful install/load). |
| R-O2 | Service worker caches shell + assets; local storage holds all datasets. |
| R-O3 | Local data is the source of truth while offline. |
| R-O4 | App startup must never require network success. |
| R-O5 | Updates apply **atomically**; a failed/interrupted update preserves the last known-good dataset. |
| R-O6 | The app can self-assess dataset completeness/consistency → honest **OFFLINE READY** state. |
| R-O7 | Must survive: browser restart, phone restart, storage pressure; must degrade gracefully when storage was evicted (detect + re-bootstrap). |
| R-O8 | Respect browser storage limits: budget total footprint, monitor via `navigator.storage.estimate()`, request persistence. |
| R-O9 | Data versioning, integrity validation, and migration path for schema changes. |
### 4.8 Festival Data Package (concept)
| ID | Requirement |
|----|-------------|
| R-D1 | Festival content is versioned data, not hardcoded logic, wherever practical. |
| R-D2 | Package includes manifest + integrity/version metadata at minimum; exact structure TBD in architecture phase. |
| R-D3 | Define: authoritative source, publishing flow, delivery, validation, local storage, version tracking, atomic apply, failure handling, rollback. |
### 4.9 Future extensions
| ID | Requirement |
|----|-------------|
| R-X1 | Online extras (announcements, schedule changes, weather) may exist later; never gate V1 critical features. |
| R-X2 | Architecture must expose a clean transport-abstraction seam for future mesh without contaminating core logic. |
| R-X3 | No mesh technology is chosen, implemented, or library-dependended-upon in V1. |
### 4.10 Security & reliability
| ID | Requirement |
|----|-------------|
| R-G1 | Initial threat analysis performed (§18); realistic vs theoretical threats distinguished. |
| R-G2 | Festival data integrity protected (validation of updates before activation). |
| R-G3 | No unnecessary security complexity; no secrets in the client. |
| R-R1 | Behavior defined for every failure scenario in §19. |
---
## 5. Architectural Constraints
Non-negotiable principles (from the brief, adopted verbatim in intent):
1. Critical V1 functionality works without internet.
2. Emergency baseline always locally available.
3. Startup never requires network success.
4. UI never directly depends on a remote API for critical V1 features.
5. Festival content = versioned data, not hardcoded feature logic (where practical).
6. Local data is the offline source of truth.
7. Connectivity is an enhancement, not a prerequisite.
8. No partially-applied datasets.
9. Failed update ⇒ last known-good preserved.
10. App can determine whether it holds a complete, consistent offline dataset.
11. No mesh implementation in V1.
12. Mesh concerns never leak into feature/UI logic.
13. GPS never required for critical functionality.
14. No speculative complexity in V1.
15. Prefer the simplest architecture that satisfies the requirements.
**Constraints derived during discovery (to be ratified in architecture phase):**
- **C-16 (Install-first):** Because iOS storage eviction and push both hinge on
home-screen installation, *getting users installed before the festival* is a
load-bearing product requirement, not a nice-to-have.
- **C-17 (Bootstrap honesty):** True offline capability requires at least one
successful online bootstrap (app shell + dataset) before arrival. The
"pre-festival distribution" problem is therefore a real requirement area.
- **C-18 (Eviction survival):** The app must detect "my storage was wiped" on
launch and degrade to a clear recovery state rather than a broken blank app.
- **C-19 (Foreground-only background model):** Nothing may depend on background
execution, background sync, or push wake-ups for correctness on iOS.
- **C-20 (Single origin):** All app shell + data must live on one HTTPS origin
(storage and SW scope are per-origin; third-party hosting of assets has
partitioning/quota consequences).
---
## 6. Non-Requirements (V1)
Explicitly **out** of V1 scope:
| # | Non-requirement | Notes |
|---|-----------------|-------|
| NR-1 | Mesh networking (any form) | Seam only; no protocol, discovery, routing, store-and-forward, BLE/WebRTC mesh. |
| NR-2 | Native iOS/Android apps | PWA only. |
| NR-3 | App Store / Play Store presence | Not needed; avoid TWA/wrapper complexity too. |
| NR-4 | User accounts / authentication | No sign-in in V1; favorites are device-local. |
| NR-5 | Cross-device sync of favorites | Consequence of NR-4; revisit later. |
| NR-6 | Real-time collaboration / social features | None. |
| NR-7 | GPS-dependent features | Location is enhancement-only. |
| NR-8 | Online map tile services as core map | Core map is local/custom. |
| NR-9 | In-app payments / merch checkout | None. |
| NR-10 | User-generated content | None. |
| NR-11 | Multi-language i18n | Assumed English-only for V1 (A-09). |
| NR-12 | Background sync / periodic sync | Not reliably available cross-platform (C-19). |
| NR-13 | Push notifications as a critical channel | At best an enhancement; cannot be depended on (iOS install-only, region quirks). |
| NR-14 | Admin/CMS authoring UI | Authoring flow TBD; V1 may use a simple file-based pipeline (§15). |
| NR-15 | Analytics beyond minimal, privacy-safe diagnostics | Data minimization (§18). |
---
## 7. Assumptions
Every assumption below is **explicit and reversible**. None has been silently
converted into a requirement.
| ID | Question / Assumption | Impact | Recommended Default |
|----|-----------------------|--------|---------------------|
| A-01 | Festival is a single annual/one-off event (not multi-venue, multi-year platform). | Shapes data packaging & versioning scope. | Single-event V1; package keyed by festival edition so multi-event is a later, cheap extension. |
| A-02 | Attendee count 300–500; modest dataset size (schedule ≤ ~1k items; map assets ≤ ~50 MB total). | Storage budget, update strategy, performance targets. | Design for ≤ 50 MB total local footprint; verify with real content. |
| A-03 | One language (English). | All content schemas simpler. | English only; keep content strings in data (not code) so i18n is later possible. |
| A-04 | Festival can supply: schedule data, custom map artwork + POI coordinates, emergency info, general info — in some digital form. | The entire data pipeline depends on this. | Request structured inputs (spreadsheet/JSON + map file); define intake templates in architecture phase. |
| A-05 | Users will have internet access **before** the festival (at home/town) to install & bootstrap. | If false, need on-site distribution (gate kiosk, QR posters, local LAN server). | Assume pre-arrival bootstrap; plan on-site bootstrap as contingency (open question OQ-3). |
| A-06 | Attendees use reasonably modern phones: iOS 16.4+ / Android Chrome ~last 3 years. | Determines baseline APIs (SW, IDB, Intl, standalone). | Baseline: iOS 16.4+ Safari, Android Chrome 110+; graceful degradation below that to "basic static page still readable". |
| A-07 | No user accounts; device-local personalization only. | Eliminates auth, server-side state, sync conflicts. | Confirm; if accounts ever required, they'd be additive, not foundational. |
| A-08 | Emergency phone numbers/contacts are known and provided by organizers before build. | Emergency feature content. | Require a signed-off emergency content sheet as a project gate. |
| A-09 | No offline-update delivery via local LAN/mesh in V1; updates arrive only via internet when available. | Update architecture simplicity. | Internet-only updates in V1. |
| A-10 | Festival timezone is a single known IANA zone; schedule stored as absolute instants (UTC) + rendered in festival tz. | Now/Next correctness. | UTC storage + festival IANA zone rendering; device-tz toggle optional. |
| A-11 | The app is served from a single domain the organizers control. | SW scope, storage origin, update trust. | Single dedicated origin (e.g., `app.<festival>.tld`). |
| A-12 | V1 does not need push notifications to be "critical"; announcements are pull-based when app is opened online. | Avoids iOS push fragility as a dependency. | Push = optional enhancement post-V1. |
| A-13 | Low-end Android devices are in the audience; performance budget must include them. | Rendering choice for map, list virtualization, JS budget. | Budget: interactive in < 3s on a 2021 mid-range Android over local cache; map pan/zoom ≥ 30fps. |
| A-14 | "Call 911" is US-context (911). If festival is elsewhere, number differs. | Emergency correctness. | Make the primary emergency number **data-driven**, not hardcoded (911 default). |
| A-15 | Festival map is a fixed geographic area (no need for world-scale panning). | Map tech can be bounded/simple. | Bounded custom map; no slippy-world tiles. |
| A-16 | Organizers accept that dynamic emergency updates require the user to have opened the app online at least once to receive them. | Expectation-setting. | Document this limitation in ops runbook. |
| A-17 | Development is solo/small-team; ops simplicity matters (static hosting likely sufficient). | Backend complexity budget. | Prefer static-file distribution for V1 updates; dynamic backend only if a real need appears. |
---
## 8. Ambiguities
Items where the brief is genuinely underspecified. For the five most
consequential, the four-part analysis follows the table.
| ID | Ambiguity | Four-part analysis |
|----|-----------|--------------------|
| AMB-1 | **How do users get the app + dataset before/off-site?** (QR? link in email? gate Wi-Fi? pre-fest "install party"?) | **Unclear:** distribution channel & first-run experience. **Matters:** offline-first is impossible without a successful first online bootstrap (C-17); iOS makes install a manual multi-step flow. **Default:** URL/QR campaign before the festival + printed install instructions; app shows a clear "not yet offline ready" state until dataset downloaded. **If wrong:** large fraction of attendees arrive without data → product fails its core promise; mitigations (on-site LAN bootstrap) take real work. |
| AMB-2 | **Who authors/maintains festival data, and in what format?** | **Unclear:** authoritative source of schedule/map/emergency/info. **Matters:** determines the entire publishing pipeline and integrity model (§15). **Default:** organizers fill provided templates (CSV/JSON + map asset); Lumen build/publish tooling converts to signed Festival Data Package. **If wrong:** if organizers have an existing CMS/API, an adapter is needed; schema must stay import-friendly. |
| AMB-3 | **What does the festival map actually look like / what format will organizers provide?** (illustrated PNG? SVG? GIS data?) | **Unclear:** input artifact for the map subsystem. **Matters:** drives R-M5 evaluation (SVG vs raster vs canvas) and POI coordinate scheme. **Default:** illustrated raster background + separately authored POI coordinates in map-local coordinate space; SVG overlays if vector art is supplied. **If wrong:** if only GIS/venue data exists, a conversion step is needed; if map is enormous, tiling/splitting needed. |
| AMB-4 | **Are there any server-side capabilities at all?** (Is even static hosting available? Is there internet at the festival site for organizers?) | **Unclear:** whether updates/announcements are deliverable on-site. **Matters:** shapes whether "dynamic" features are realistic at all, and whether organizer comms need their own offline tooling. **Default:** static hosting on a CDN before + during event; assume organizer connectivity is as poor as attendees'. **If wrong:** if organizers have a site LAN, a local update mirror becomes a high-value later feature (and connects to the future transport seam). |
| AMB-5 | **When exactly is "OFFLINE READY" evaluated and shown, and what's the recovery UX when not ready?** | **Unclear:** indicator semantics, granularity (per-section vs global), and remediation UI. **Matters:** this is the app's honesty contract with users. **Default:** global indicator with per-section detail; re-bootstrap path when data missing; never claim ready unless manifest, all sections, assets, and integrity checks pass. **If wrong:** users trust a false "ready" and hit empty screens in emergencies — unacceptable. |
| AMB-6 | Festival duration / daily hours (affects schedule model, multi-day UX). | Default: support multi-day schedule; confirm dates. |
| AMB-7 | Whether schedule changes mid-festival are expected (and how urgent they are). | Default: yes, via dataset updates + optional announcements; urgency channel TBD. |
| AMB-8 | Branding/visual identity availability (icons, colors, splash). | Default: simple high-contrast house style until brand arrives. |
| AMB-9 | Whether the emergency number is 911 (US) or another jurisdiction. | Default: data-driven field; confirm jurisdiction. |
| AMB-10 | Accessibility commitments beyond legal baseline (WCAG AA?). | Default: WCAG 2.1 AA as target. |
| AMB-11 | Whether "lost person" includes a child-reunification workflow (active feature vs info page). | Default V1: informational procedure only; active reunification is a large feature and out of scope. |
| AMB-12 | Data retention/lifetime — does the app matter after the festival (memories/next-year teaser) or can it be ephemeral? | Default: keep working post-festival with the last dataset; cheap and harmless. |
---
## 9. Open Questions
Items that cannot be sensibly defaulted — they need stakeholder answers:
1. **OQ-1:** What is the festival's name, location, dates, daily hours, and IANA timezone?
2. **OQ-2:** What are the exact emergency contacts (security, first aid org, medical provider), muster points, and venue address/GPS? (Required before Emergency can be finalized.)
3. **OQ-3:** What distribution channels exist to reach attendees before arrival (email list, socials, ticketing partner)? Is on-site connectivity available for a bootstrap station?
4. **OQ-4:** Who owns content updates during the festival, and on what device? (Defines authoring/publishing UX requirements.)
5. **OQ-5:** Does the festival have an existing site/brand/domain to host under, and who controls DNS/hosting?
6. **OQ-6:** What map artifacts can organizers actually produce? (Artist brief? Existing venue plan?)
7. **OQ-7:** Are there legal/liability review requirements for emergency content (who approves the wording)?
8. **OQ-8:** Is there any budget for hosting/CDN or is fully static free-tier hosting required?
9. **OQ-9:** Expected minimum OS versions in the audience (ticket-purchase analytics may reveal)?
10. **OQ-10:** Should Lumen support post-festival feedback/contact channel, or is it read-only forever?
11. **OQ-11:** Will there be multiple stages with simultaneous programming? (Now/Next conflict UX.)
12. **OQ-12:** Are quiet hours / overnight periods relevant to the schedule model?
---
## 10. Major Architectural Decisions (to be made deliberately in the next phase)
Numbered for later reference; **none are decided here.**
| # | Decision | Why it matters | Candidates / considerations |
|---|----------|----------------|------------------------------|
| AD-1 | Frontend framework & rendering model | DX, bundle size, low-end device perf, ecosystem | No framework (vanilla TS + web components) vs Svelte/Solid (compile-away) vs React/Preact vs others. Must evaluate: JS budget, offline SW integration quality, team familiarity. |
| AD-2 | Service-worker strategy & tooling | The offline mechanism itself | Hand-written SW vs Workbox vs custom; precache vs runtime-cache split; SW update/activation UX (skipWaiting? user-prompted update?). |
| AD-3 | Local storage architecture | Data layer durability & query ergonomics | IndexedDB (raw vs wrapper like idb/Dexie) vs localStorage-only (too small) vs OPFS files vs SQLite-in-WASM (e.g., cr-sqlite/wa-sqlite) — evaluate iOS reliability carefully. |
| AD-4 | Festival Data Package schema & format | Interop, validation, size | JSON documents vs MessagePack vs SQLite file vs hybrid; one file vs section files; asset manifest design. |
| AD-5 | Integrity & authenticity model | Security (§18) | SHA-256 hashes in signed manifest only vs full package signature (e.g., Ed25519 public key baked into shell); key custody process. |
| AD-6 | Update apply/rollback mechanism | R-O5, R-O6 | Dual-slot (A/B dataset) with active pointer vs staged-write-then-commit vs version-keyed stores with GC of old versions; must survive mid-update kill. |
| AD-7 | Map representation | R-M5 | SVG (crisp, styleable, DOM-accessible, pan/zoom via transforms) vs raster tiles (pre-cached, memory-heavy when large) vs Canvas (perf, worse accessibility) vs layered hybrid; plus POI overlay model and map-local coordinate system. |
| AD-8 | Time handling strategy | R-S5 | UTC instants + festival IANA zone via Intl; optional server-time offset capture for skew detection; wrong-clock UX (warn vs auto-correct). |
| AD-9 | Hosting & distribution topology | R-P3, updates | Static CDN hosting (likely sufficient) vs tiny server; versioned immutable asset URLs; cache headers strategy. |
| AD-10 | Publish pipeline | R-D3 | Build-time generator (repo of JSON/CSV → signed package) vs lightweight admin tool vs CMS adapter; who presses "publish". |
| AD-11 | Transport abstraction seam shape | R-X2 | Interface boundaries so "InternetTransport" is the only V1 implementation and future local/mesh transports slot under a messaging/sync layer without touching UI. Must be minimal — risk of over-engineering. |
| AD-12 | Announcements design (even if V1-deferred) | Pull-based inbox model vs banner; storage of stale announcements; authenticity. |
| AD-13 | Observability without surveillance | R-G data minimization | No/low telemetry; on-device diagnostics screen; optional manual "share diagnostics" rather than automatic upload. |
| AD-14 | Repo/VCS & CI setup | VCS-1 | Own git repo; CI for build + Lighthouse/PWA checks + device-matrix tests. |
---
## 11. Technology Questions Requiring Evaluation
Each needs a small spike or documented evaluation in the next phase — not a
decision now.
1. **TQ-1 Framework perf on low-end Android:** measure schedule-list rendering and
map pan/zoom with candidate stacks on a throttled mid-range device.
2. **TQ-2 IndexedDB reliability on iOS WebKit:** transaction failure history,
large-write behavior near quota, behavior after forced kill; compare against
OPFS and localStorage+files hybrid.
3. **TQ-3 SQLite-WASM viability:** does it earn its complexity for querying
(search/filter) vs plain IDB indexes? iOS Safari WASM memory limits?
4. **TQ-4 Service worker update semantics on iOS:** activation timing,
`clients.claim` behavior, SW killed mid-update frequency; how to make "app
update during festival" safe.
5. **TQ-5 SVG pan/zoom approach:** pointer-event based transforms, pinch zoom
quality, accessibility of interactive SVG elements with VoiceOver/TalkBack.
6. **TQ-6 Asset compression:** WebP/AVIF support matrix for map art and icons;
real-world size of the festival map at legible quality.
7. **TQ-7 Persistent storage grant rates:** measure `navigator.storage.persist()`
outcomes on iOS (installed) and Android; define behavior when denied.
8. **TQ-8 Install-funnel tooling:** detection of standalone mode, iOS install
instruction UI patterns, measuring install conversion.
9. **TQ-9 Package signing practicality:** WebCrypto Ed25519 support matrix on
target browsers (verify availability on iOS 16.4 baseline) vs hash-only
manifest validation as fallback.
10. **TQ-10 Search implementation:** client-side fuzzy search over ≤ ~1k items —
naive filter vs lightweight index (e.g., precomputed trigrams); cost/benefit.
11. **TQ-11 Build tooling:** bundler choice (vite/esbuild/etc.) and PWA plugin
maturity vs hand-rolled SW; dev-server HTTPS fidelity to production.
12. **TQ-12 Geolocation behavior:** permission persistence in iOS standalone
mode, time-to-first-fix in crowds, battery impact; whether to surface it at
all in V1.
13. **TQ-13 Testing strategy:** real-device matrix (iOS 16.4, latest iOS, low-end
Android, latest Android), airplane-mode test scripts, storage-eviction
simulation techniques.
14. **TQ-14 Timezone correctness:** Intl behavior for the festival zone on
baseline devices; DST edge cases if relevant.
---
## 12. PWA / Browser Risks
Verified platform facts → risks → mitigations to carry into architecture.
| # | Risk | Evidence / Mechanism | Severity | Mitigation direction |
|---|------|----------------------|----------|----------------------|
| PW-1 | **7-day storage eviction (iOS)** for users who browse in a tab before the festival and don't install. | Safari ITP: 7-day cap on all script-writable storage incl. SW registration; installed home-screen apps exempt. | High | Install-first UX; re-cache shell on every launch; detect empty storage and re-bootstrap; never assume prior state. |
| PW-2 | **Manual-only install on iOS** (no `beforeinstallprompt`). | iOS requires Share → Add to Home Screen (4+ taps). | High | Custom install coach with screenshots; QR flow; pre-festival campaign; measure funnel. |
| PW-3 | **All-or-nothing eviction** can wipe dataset between install and festival (storage pressure). | Eviction deletes all origin data at once. | Medium | Keep footprint lean; `persist()` request; honest OFFLINE READY check on every launch; one-tap re-download. |
| PW-4 | **No background sync on iOS**: updates only happen while app is open. | Background Sync / Periodic Sync unsupported on iOS Safari. | Medium | Sync-on-open pattern; opportunistic updates when online; never schedule-dependent. |
| PW-5 | **Push is not a reliable channel** (iOS: installed-only, region quirks; both: permission opt-in). | Web Push iOS 16.4+ installed PWAs only; EU behavior differs. | Medium | Treat announcements as pull-based in V1 (NR-13). |
| PW-6 | **SW lifecycle fragility**: iOS kills SWs aggressively; mid-update kills possible. | Documented WebKit behavior; SWs not long-lived processes. | High | Keep SW logic small, idempotent, resumable; no in-SW long computations; atomic staged downloads (AD-6). |
| PW-7 | **Quota surprises**: best-effort storage; `persist()` not guaranteed. | Quotas are estimates; eviction under pressure is silent. | Medium | `storage.estimate()` monitoring, budget alarms, graceful degradation UI. |
| PW-8 | **Standalone-mode quirks on iOS**: no browser back/forward UI, external links can strand users, safe-area insets, keyboard viewport behavior. | Known standalone behavior. | Medium | In-app back affordances; intercept/label external links; `viewport-fit=cover` + safe-area CSS; test on device. |
| PW-9 | **HTTPS requirement** excludes casual local-HTTP testing/distribution; any on-site LAN bootstrap would need TLS or localhost tricks. | SW + Geolocation + Storage API require secure contexts. | Low–Med | Plan TLS for any distribution point; localhost-only for dev. |
| PW-10 | **First-load bootstrap race**: user goes offline before first full cache completes. | Network-dependent first run. | Medium | Download critical sections in priority order (Emergency → Schedule → Map → Info → assets); resumable; show progress and what's ready. |
| PW-11 | **Browser version skew**: older iOS/Android missing APIs. | Baseline assumption A-06. | Medium | Feature-detection layer; minimum viable static fallback page; state supported baseline clearly. |
| PW-12 | **App update during festival**: new app shell + old dataset (or vice versa) compatibility. | Independent update channels for shell vs data. | Medium | Manifest declares min/max compatible app/dataset versions; refuse to activate incompatible combos (R-O6). |
| PW-13 | **Storage partitioning / third-party assets**: cross-origin assets complicate caching & quota. | Chrome 115+/Safari partitioning. | Low | Single-origin everything (C-20). |
| PW-14 | **Private browsing**: ephemeral/restricted storage in private mode. | Safari private mode limits persistence. | Low | Detect where possible; advise normal mode for install. |
| PW-15 | **iOS EU regulatory variability**: PWA behavior differs in EU regions. | Documented iOS 17.4+ EU differences. | Low | Assume worst case (tab behavior); install-first mitigates. |
---
## 13. Offline Risks
| # | Risk | Notes | Mitigation direction |
|---|------|-------|----------------------|
| OF-1 | **Bootstrap dependency**: offline-first only works after one successful online session. | Fundamental; see C-17, AMB-1. | Pre-festival distribution; on-site contingency (OQ-3); printed core info as physical fallback (poster/handout) — organizers' call. |
| OF-2 | **Storage evicted between install and festival** (PW-3). | Highest-likelihood data-loss path. | Launch-time integrity check; guided re-bootstrap; emergency data additionally embedded in app shell where feasible (see OF-3). |
| OF-3 | **Emergency data loss on eviction** is unacceptable. | Static emergency baseline could be *embedded in the app bundle itself* (bundled JSON in the JS/HTML artifact) so it survives even total dataset loss. | Evaluate: ship immutable emergency baseline in shell + optional dynamic overlay from dataset. This is a strong candidate architectural decision. |
| OF-4 | **Partial download / interrupted update** leaves inconsistent state. | Mobile reality: screens off, radios flaky. | Section-level downloads with hashes; activate only complete validated sets; keep previous set until new set committed (AD-6). |
| OF-5 | **Corrupted local data** (bit rot unlikely; buggy writes more likely). | IDB transaction bugs. | Integrity check on launch (checksums per section); quarantine + re-download; never trust unchecked data for Emergency display. |
| OF-6 | **Quota exhaustion mid-download** (large map assets). | QuotaExceededError on write. | Pre-check `storage.estimate()`; size budgets in manifest; abort cleanly and keep last-good. |
| OF-7 | **Wrong device clock** breaks Now/Next and "is festival live". | Airplane-mode phones drift; users change clocks. | §15.7 analysis; server-offset capture when online; festival-timezone anchoring; explicit "your clock looks wrong" UX when skew detected. |
| OF-8 | **Stale dynamic data mistaken for fresh** (schedule changed, user never online). | Honesty problem. | Show dataset age/version everywhere relevant; "data as of …" labels; organizers announce changes redundantly (PA/boards). |
| OF-9 | **App-shell update breaks against stored data** (schema drift). | Mid-festival app updates. | Versioned schemas + migrations run at activation; dataset manifest declares schema version; rollback path (OF-10). |
| OF-10 | **Rollback mechanics** after a bad update is applied. | Rare but must be defined. | Retain N=1 previous dataset slot until new one proven; "restore previous" action; app can always fall back to embedded emergency baseline. |
| OF-11 | **Phone restart / browser restart** mid-session loses in-memory state. | Favorites unsaved, draft state. | Write-through persistence for any user mutation; no memory-only truth. |
| OF-12 | **Low-battery-induced cold starts** repeatedly re-warm the app. | Users force-quit to save battery. | Fast cold start budget (< ~2s to usable), minimal re-hydration cost. |
| OF-13 | **Users clearing "website data"** thinking it's harmless. | iOS Settings → Safari → clear. | Nothing to prevent; re-bootstrap must be painless; embedded emergency baseline (OF-3) limits blast radius. |
---
## 14. Emergency-System Risks
| # | Risk | Severity | Notes / Mitigation direction |
|---|------|----------|------------------------------|
| EM-1 | **Emergency content wrong or stale** (wrong security number, moved first-aid tent). | Critical | Content sign-off gate (A-08, OQ-2, OQ-7); version stamp visible on every emergency screen; dynamic overlay updates only additive/corrective; static baseline reviewed per edition. |
| EM-2 | **Emergency data missing after eviction**. | Critical | Embed static baseline in app shell (OF-3) so *some* emergency info exists even at zero storage. |
| EM-3 | **`tel:` link fails** (no SIM, airplane mode). | High | Note that emergency calls may still work without SIM (region-dependent); display numbers as copyable text too; never present dialing as the only path. |
| EM-4 | **GPS coordinates wrong** → rescuers sent to wrong location. | High | Coordinates come from validated dataset; display in multiple formats (decimal + what3words-style fallback TBD); human-readable address always primary. |
| EM-5 | **User panic UX**: fumbling, mis-taps, unreadable in sunlight. | High | Emergency screen design: giant targets, maximum contrast, zero clutter, works in dark and direct sun, reachable one-handed; test with real users. |
| EM-6 | **False sense of capability** ("offline ready" shown when it isn't). | High | OFFLINE READY semantics must be provable (R-O6); emergency has its own always-true baseline regardless (EM-2). |
| EM-7 | **Legal/liability of procedural content** (first-aid instructions, evacuation guidance). | Medium | Procedures written/approved with qualified parties; app positions itself as *information access*, not medical/emergency service; disclaimers reviewed (OQ-7). |
| EM-8 | **Emergency updates race**: contact changes mid-festival but user is offline. | Medium | Accept as residual risk (A-16); mitigate with physical redundancies (signage/PA) owned by organizers; dynamic overlay fetched aggressively whenever any connectivity appears. |
| EM-9 | **Localization of emergency comprehension** under stress. | Low–Med | Plain language, short imperatives, icons + text (English-only V1 per A-03; revisit if audience differs). |
| EM-10 | **Misuse/false-emergency**: app must not itself trigger emergency services accidentally. | Low | Explicit user action only for dialing; no auto-call features ever. |
---
## 15. Data Architecture Questions
### 15.1 Authoritative source & publishing
- Who authors each content class, and what tooling do they get? (OQ-4)
- Default direction: **content-as-data in a repo or simple store → build/publish
step produces a signed Festival Data Package → uploaded to static hosting →
app pulls by version**. No dynamic backend required for V1 (A-17).
- Must define publishing roles (who can publish emergency changes) and an
audit trail even in a minimal pipeline.
### 15.2 Package content (conceptual — structure TBD)
Candidate sections, each independently versioned & hashed:
- `manifest` (package id, edition, version, section inventory, hashes, sizes, min/max app compatibility, timestamp)
- `emergency` (static baseline candidates also embedded in shell — OF-3)
- `schedule` (stages, slots, artists, events, tags, change metadata)
- `map` (geometry/artwork reference, POI list with map-local coordinates, categories)
- `locations` (POI detail records; may merge with map — boundary TBD)
- `festival-info` (structured info pages/sections)
- `announcements` (V1: optional pull inbox; NR-13)
- `assets` (map images, artist images?, icons) — **asset weight budget needed** (A-02)
### 15.3 Delivery & validation
- Immutable, versioned URLs (content-addressed or `/v{n}/…`).
- Per-section hash verification; whole-package signature if WebCrypto Ed25519
baseline allows (TQ-9); else hash-chain in signed manifest.
- Size caps enforced client-side (DoS hygiene, §18).
### 15.4 Local storage model
- Likely split: **Cache Storage** for shell + immutable assets; **IndexedDB**
for structured datasets; possibly small **localStorage** for flags/preferences.
- Must decide: datasets as parsed objects vs raw blobs parsed on read (memory
vs speed trade-off).
- Must define store layout that supports **A/B slots** (AD-6).
### 15.5 Atomic apply & rollback
- Download → verify → write to staging slot → integrity check → flip active
pointer → GC old slot after N successful launches.
- Interruption at any step must leave previous active slot intact.
- Decision needed: is the "pointer flip" an IDB transaction or an SW-managed
cache rename? (Both have iOS failure modes to spike — TQ-2/TQ-4.)
### 15.6 Migrations & compatibility
- Dataset schema version + app compatibility range in manifest.
- App-shell updates may require data migration at activation; migrations must
be idempotent and reversible-where-possible (or gated behind backups).
### 15.7 Time model (R-S5 analysis)
- **Store** all event times as UTC instants; **render** in festival IANA
timezone via `Intl` (works offline; tz database ships with the OS/browser).
- **Device clock risk:** wrong clock ⇒ wrong Now/Next and wrong "festival live"
state. Strategy candidates (choose later):
1. Trust device clock (simplest; wrong clocks silently wrong).
2. Capture server-time offset whenever online; store offset + captured-at;
use offset when device skew exceeds threshold; show warning when stale.
3. Organizers broadcast clock corrections via announcements (social fix).
- Recommended direction: (2) with graceful degradation to (1), plus visible
dataset timestamp so users can reason about staleness.
- Edge cases: DST transitions near festival dates; multi-day events crossing
midnight; user traveling across zones before arrival.
### 15.8 Favorites / user state
- Separate local-only store (never part of festival package; never overwritten
by dataset updates; survives dataset rollback).
- Schema keyed by stable event IDs → **event IDs must be stable across dataset
versions** (requirement to push onto the data schema).
- On dataset update, handle: event moved (keep favorite, update time), event
cancelled (mark), event removed (orphan policy).
### 15.9 Size budgeting (open, needs real content)
- Working hypothesis: shell < 1 MB JS+CSS; schedule+info JSON < 2 MB; map
artwork dominates (target ≤ 30 MB across zoom levels); total < 50 MB (A-02).
- Must validate against actual map art early (it is the top size risk).
---
## 16. Synchronization Questions
| # | Question | Current thinking (not a decision) |
|---|----------|-----------------------------------|
| SY-1 | Is sync ever bidirectional in V1? | No. V1 is **pull-only** (device ← package). No user writes leave the device (NR-4/NR-5). This eliminates conflict resolution entirely. |
| SY-2 | When does the app check for updates? | On open, when connectivity detected; opportunistic; never blocking; never in background (C-19). |
| SY-3 | Update granularity? | Section-level diffs vs whole-package re-download; decide after size budgeting (§15.9). Whole-package may be simplest if < ~10 MB delta. |
| SY-4 | Announcements: pull inbox semantics? | Fetch list when online; store locally; expiry rules; dedupe by id; authenticity (§18). |
| SY-5 | What if two dataset versions arrive back-to-back? | Monotonic version acceptance; never downgrade from network (rollback is local-only action). |
| SY-6 | How is "freshness" communicated? | Dataset version + generated-at + fetched-at shown in status screen; staleness thresholds for warnings. |
| SY-7 | Should favorites ever sync cross-device later? | If ever: export/import (QR/text) before considering accounts; keep IDs stable to allow it. |
| SY-8 | Does anything need server-side state for updates? | Not for static package hosting. Dynamic backend only if live features materialize post-V1. |
---
## 17. Future Mesh Architectural Questions
**Scope reminder:** V1 implements nothing here. These questions shape the seam
(AD-11) and nothing else.
### 17.1 Browser feasibility analysis (honest assessment)
- **BLE:** Web Bluetooth exists on Android Chrome only (central role), **not on
iOS Safari**, and cannot do background advertising or device-to-device relay.
Not a viable mesh substrate in-browser for our primary targets.
- **Wi-Fi Direct / ad-hoc / mDNS:** not exposed to browsers. No LAN discovery
APIs in Safari/Chrome on mobile.
- **WebRTC data channels:** peer-to-peer capable in principle, but requires
signaling (normally internet or a shared rendezvous), NAT traversal help, and
user gestures; no background operation on iOS. A *local-network* WebRTC link
is conceivable but fragile and complex.
- **Conclusion:** a meaningful mesh capability will almost certainly require
**something outside the browser**: a native companion app, an OS-level local
network service, dedicated hardware relay, or organizer-run LAN
infrastructure. The browser app's job is to be **transport-agnostic**, not to
own the mesh.
### 17.2 Seam design questions (for later)
| # | Question |
|---|----------|
| ME-1 | Is the abstraction over **transports** (byte pipes) or over **sync/messaging semantics** (deliver named updates, reconcile state)? The latter is more useful and keeps mesh concerns out of features (principle 12). |
| ME-2 | Should the data layer model content as **immutable versioned documents + operations log** so any transport can carry them? (Aligns with Festival Data Package design — one design serving two futures.) |
| ME-3 | Where does transport selection/plumbing live? Proposal: a single `SyncChannel` interface injected into the data layer; V1 ships exactly one implementation (`HttpPullChannel`). UI only ever sees "data freshness", never transports. |
| ME-4 | Security boundary: every transport must deliver **signed/verified** payloads — validation stays in the data layer so an untrusted mesh cannot inject content (§18, SE-9). |
| ME-5 | What identity/trust would a future mesh need (device pairing? organizer-signed relay nodes?) — entirely deferred, but argues for keeping signing keys/scheme transport-independent now. |
| ME-6 | Cost control: the seam must cost ~near-zero in V1. If an abstraction demands speculative machinery, defer the abstraction itself until a concrete second transport exists (principle 14/15). |
**Recommendation for next phase:** keep the seam *conceptual and minimal* — a
clean module boundary and interface definition — rather than building framework
plumbing. Revisit only when a concrete transport requirement appears.
---
## 18. Security Questions & Initial Threat Analysis
### 18.1 Threat inventory (realistic vs theoretical)
| ID | Threat | Class | Analysis |
|----|--------|-------|----------|
| SE-1 | **Malicious/tampered festival data package** (MITM of update, compromised hosting) | Realistic | Primary supply-chain threat. Mitigation: HTTPS + content hashes in signed manifest (AD-5); refuse activation on verification failure. |
| SE-2 | **Compromised publish credentials** pushing bad data | Realistic | Custody: minimal publishers, signed packages, version monotonicity, fast manual rollback runbook. |
| SE-3 | **XSS/content injection via festival content** (info pages, announcements, artist bios) | Realistic | Render content as data/text; no raw HTML ingestion; if rich text needed, strict sanitizer allowlist. |
| SE-4 | **Malicious announcements** (fake evacuation order) | Realistic, high-impact | Announcements inherit package signing/authenticity; never render unsigned network content as emergency content. |
| SE-5 | **Replay of old (stale) signed package** | Realistic, low-impact | Monotonic version checks; never accept lower version from network. Stale-but-valid data is an honesty issue (OF-8) more than a security one. |
| SE-6 | **Local data tampering** (user or malware on shared device) | Theoretical-low | No high-value secrets stored; integrity checks catch accidental corruption; deliberate local tampering only harms that user. |
| SE-7 | **Location privacy leakage** | Low in V1 | Geolocation is optional, on-device only, never transmitted (no server writes in V1). Ensure no URL/query leakage of coords. |
| SE-8 | **User privacy / tracking** | Low by design | No accounts, no analytics by default (AD-13); if any diagnostics, on-device only, opt-in, no identifiers. |
| SE-9 | **Future mesh injection** (malicious peer content) | Future | Rule now: data layer validates signatures regardless of transport; no transport bypasses validation (ME-4). |
| SE-10 | **DoS via oversized update / asset bomb** | Realistic-medium | Manifest-declared sizes enforced pre-download; per-section caps; abort + keep last-good. |
| SE-11 | **Service worker hijack / scope confusion** | Low | Single origin (C-20); strict scope; subresource integrity for any external scripts (ideally zero external scripts). |
| SE-12 | **Secrets in client** | N/A by design | No API keys, no signing private keys, no credentials ship to the browser (R-G3). |
| SE-13 | **Emergency-number spoofing in dynamic overlay** | Realistic, high-impact | Dynamic emergency overlay must be package-signed like everything else; static baseline only replaceable via reviewed build. |
| SE-14 | **Dependency supply chain** (npm) | Standard | Lockfiles, minimal deps, review; standard hygiene — not a novel architecture concern. |
| SE-15 | **TLS misconfiguration / downgrade** | Standard | HSTS; HTTPS-only endpoints; no mixed content. |
### 18.2 Open security questions
- **SQ-1:** Ed25519 via WebCrypto on baseline devices? (TQ-9) If gaps exist,
fall back to SHA-256 hash manifest over TLS without weakening the model.
- **SQ-2:** Key custody: who holds the package signing key; how is it rotated;
what's the recovery if lost (requires app-shell update to ship new key)?
- **SQ-3:** Is there any scenario where the app accepts *unsigned* dynamic
content? Proposed answer: **never for emergency; never for executable
content; display-only, clearly labeled** for anything else — ideally none.
- **SQ-4:** Logging policy: keep on-device diagnostic log with PII scrubbed;
define what "scrubbed" means before any export feature exists.
- **SQ-5:** If an announcements inbox exists, retention/expiry and maximum
message size limits.
---
## 19. Reliability Questions — Failure-Scenario Matrix
Required behavior for each scenario (these become acceptance criteria):
| # | Scenario | Required Lumen behavior |
|---|----------|--------------------------|
| FA-1 | No internet / airplane mode | All critical features work from local data; connectivity status shown honestly; no error walls; no feature silently disabled without indication. |
| FA-2 | No Wi-Fi, no cellular | Identical to FA-1 (no distinction needed by the app beyond "offline"). |
| FA-3 | GPS unavailable | App fully functional; map usable without blue dot; no prompts blocking anything. |
| FA-4 | GPS inaccurate | If shown, location displayed with accuracy caveat; never used for safety-critical guidance ("go to X" is always user-read map + signage). |
| FA-5 | Browser restarted | Instant restore: shell from cache, data from IDB, favorites intact, OFFLINE READY re-verified in background without blocking first paint. |
| FA-6 | Phone restarted | Same as FA-5; cold-start budget applies (OF-12). |
| FA-7 | Browser storage unavailable (private mode/quota 0) | Detect; run degraded: render whatever ships in the app bundle (shell + embedded emergency baseline); explain limitation; advise normal mode/install. |
| FA-8 | Storage evicted | Detect empty/corrupt state at launch; show recovery screen: what's lost, one-tap re-download when online; embedded emergency baseline still available (OF-3); never blank-screen. |
| FA-9 | Corrupted local data | Integrity check fails → quarantine affected section → fall back to previous slot if present → else re-download → else embedded baseline + honest status. |
| FA-10 | Interrupted update | Staging slot discarded or resumed; active slot untouched; user never sees partial content; retry idempotently. |
| FA-11 | Failed update (hash mismatch / signature fail) | Reject; keep last-good; surface non-alarming status ("update unavailable — current data still works"); log diagnostics locally. |
| FA-12 | Invalid festival data published | Client-side validation rejects at activation; organizers need server-side validation + staging preview in the pipeline (publishing runbook). |
| FA-13 | Incorrect device time | Now/Next uses corrected offset when available; otherwise device time with visible dataset timestamp; warn on detected large skew (TQ-14). |
| FA-14 | Schedule changes | Dataset update flow; cancelled/moved handling (§15.8); announcements if enabled; organizers use PA/signage for urgency. |
| FA-15 | Emergency information changes | Dynamic overlay update when online; static baseline immutable per build; version stamp visible (EM-1). |
| FA-16 | App update during festival | Compatibility ranges in manifests (PW-12); staged SW activation; user-informed update prompt if mid-session; never break current session. |
| FA-17 | Low battery | Dark-mode default at night; no background timers; fast interactions; no unnecessary geolocation polling; respect `prefers-reduced-motion` and battery-driven perf degradation. |
| FA-18 | First launch, then immediate offline before bootstrap completes | Show exactly which sections are ready; Emergency baseline works regardless; guide user to reconnect for the rest (PW-10). |
---
## 20. UX Questions
### 20.1 Structural
- **Home:** four huge, obvious destinations (Emergency visually dominant). No
dashboard clutter, no onboarding maze.
- **Emergency accessibility from everywhere:** persistent affordance (e.g.,
always-visible emergency entry point in the nav/chrome) — decide pattern in
architecture/design phase; must not depend on which tab is active.
- **Navigation model:** bottom tab bar (thumb-reach) + explicit in-app back;
deep-links for emergency/muster info; standalone-mode safe (PW-8).
### 20.2 Environmental
- Sunlight: max contrast, large type, avoid fine gray detail.
- Night: true dark theme (OLED-friendly), red-shift option? (open UX question),
avoid flash-on-open.
- Rain/movement: oversized touch targets (≥ 48dp recommended), forgiving hit
areas, confirm destructive/important taps.
- Noise: don't rely on audio for anything critical.
### 20.3 Accessibility
- WCAG 2.1 AA target (AMB-10): screen-reader labels for all POIs and schedule
items; logical heading structure; focus management on tab switches;
`prefers-reduced-motion` honored; map needs a **non-visual or list-based
equivalent** (e.g., "nearest facilities list") — also serves GPS-free use.
- Interactive SVG accessibility if SVG route chosen (TQ-5).
### 20.4 State honesty
- **OFFLINE READY ✓** must be a provable composite state: shell cached +
dataset present + all sections validated + compatibility confirmed (+
optionally assets complete). Show degraded states distinctly:
`READY` / `PARTIAL (list what)` / `RECOVERY NEEDED` / `EMERGENCY ONLY`.
- Dataset age/version visible in one predictable place.
- No spinners pretending; no fake progress; loading states only where real.
### 20.5 Open UX questions
- UX-1: Does Emergency get a dedicated always-on button vs a tab (vs both)?
- UX-2: Install coach design & timing (first visit? pre-festival email asset?).
- UX-3: How much map interactivity in V1 (search POI, categories toggle,
"nearest" list) vs static labeled map?
- UX-4: Now/Next presentation when device clock untrusted.
- UX-5: Favorites conflict UX when favorited events overlap in time.
- UX-6: What does the app show **after** the festival ends (schedule in past
tense? memorial mode? next-edition placeholder?) — AMB-12.
- UX-7: Onboarding: zero-step ideal; is a 3-line "how to use offline" card
enough?
- UX-8: Theming: is brand available (AMB-8) or high-contrast utility style?
- UX-9: Haptics (`navigator.vibrate`) for emergency confirmations — Android
only; degrade silently.
---
## 21. Recommended Next Architectural Investigation
Ordered by risk-reduction value. Each is a **spike/investigation, not a
commitment**:
1. **SPIKE-1 — Storage & eviction test harness on real iOS hardware.**
Measure: IDB reliability near quota, `persist()` grant behavior, 7-day ITP
reproduction on tab vs installed PWA, all-or-nothing eviction recovery UX.
*(Retires PW-1/3/7, TQ-2/7 — highest-severity unknowns.)*
2. **SPIKE-2 — Atomic dataset update prototype.** Service worker + IDB A/B slot
with staged download, hash verify, pointer flip, kill-mid-update survival,
rollback. Include app-shell vs dataset compatibility gating. *(Retires AD-6,
OF-4/9/10, FA-10/11/16.)*
3. **SPIKE-3 — Map representation bake-off.** Realistic sample festival map in
SVG vs tiled raster vs canvas on a low-end Android + iPhone: pan/zoom fps,
memory, accessibility, authoring effort. *(Retires AD-7, AMB-3.)*
4. **SPIKE-4 — Festival Data Package schema draft v0** with sample content:
manifest, sections, hashes, stable event IDs, time model (UTC + festival tz),
favorites-overlay behavior. *(Retires most of §15.)*
5. **SPIKE-5 — Install & bootstrap funnel.** iOS install coach prototype,
standalone detection, first-run prioritized download order, OFFLINE READY
state machine draft. *(Retires PW-2/10, AMB-1/5.)*
6. **SPIKE-6 — Framework shortlist benchmark** (2–3 candidates) on throttled
mid-range Android: cold start, 1k-row schedule render, search latency, JS
size. *(Retires AD-1, TQ-1.)*
7. **SPIKE-7 — Time-handling validation.** Intl timezone behavior on baseline
devices, server-offset capture design, wrong-clock UX prototype. *(Retires
R-S5, FA-13.)*
8. **Content intake templates** for organizers (schedule CSV shape, POI sheet,
emergency content sheet, map art spec) — unblocks A-04, OQ-2/6 in parallel
with technical spikes.
Then, and only then: architecture decision records for AD-1…AD-14.
---
## 22. Preliminary V1 Boundaries
**Proposed V1 = "the airplane-mode promise":** an installed PWA that, after one
successful online bootstrap, provides Emergency, Schedule (with local favorites),
Map, and Festival Info entirely offline, with honest readiness states and safe,
atomic data updates when connectivity exists.
### In scope (V1)
- PWA: manifest, service worker, install coaching (esp. iOS), offline shell.
- Four destinations + persistent Emergency access.
- Festival Data Package: versioned, hashed, atomically applied, rollback-safe.
- Local storage: datasets + favorites + preferences + integrity/version metadata.
- OFFLINE READY state machine with honest degraded states.
- Static emergency baseline embedded in app shell + dynamic signed overlay.
- Schedule: browse, now/next (local), favorites, filters, search.
- Map: custom offline map + POIs; no GPS requirement; list-based facility
access for accessibility.
- Festival info pages (data-driven).
- Pull-based updates (app shell + data) over HTTPS from static hosting.
- Failure behaviors per §19 matrix.
- Baseline device support: iOS 16.4+ Safari (installed), Android Chrome ~110+.
### Out of scope (V1) — revisit only with evidence
- Mesh networking of any kind (seam definition only, per §17).
- Push notifications as a relied-upon channel.
- Accounts, cross-device sync, user-generated content.
- Live map/GPS features, geofencing, navigation.
- Payments, ticketing, merch checkout.
- Native wrappers (TWA/App Store), CMS with live backend.
- Multi-language, multi-edition/multi-festival tenancy (schema should not
block it — A-01 — but no feature work).
- Announcements: **design the schema seam, implement only if time permits**;
never gate V1 on it.
### V1 success criteria (draft)
1. Cold start → usable Emergency info in ≤ 3 seconds on a 2021 mid-range phone,
airplane mode, 20 launches in a row.
2. Full dataset (shell + all sections + map assets) verifiable OFFLINE READY on
iOS Safari installed PWA and Android Chrome after one bootstrap session.
3. Kill-switch test: kill app/SW/phone at every stage of an update → last-good
dataset always survives; no user-visible corruption.
4. Zero critical features reachable only through network calls.
5. Emergency baseline survives total storage wipe (embedded in shell).
---
## Appendix A — Self-Review Checklist
| Check | Result |
|-------|--------|
| No application implemented | ✅ Only this document exists |
| No placeholder application code | ✅ Verified: directory contents = `.directory` (pre-existing, untouched) + `DISCOVERY.md` |
| Existing project directory inspected | ✅ §1–§2 |
| No native-mobile assumptions leaked | ✅ PWA-only; native explicitly non-requirements (NR-2/3); mesh-in-native analyzed as future, not V1 |
| Browser treated as first-class constraint | ✅ §5 (C-16…C-20), §12, §13 |
| Offline operation treated as core requirement | ✅ §4.7, §13, §19, §22 |
| Emergency treated as core requirement | ✅ §4.3, §14, OF-3/EM-2 embedded-baseline concept |
| Mesh treated as future architecture only | ✅ §17, NR-1, R-X2/X3 |
| Unresolved requirements identified | ✅ §8 ambiguities, §9 open questions, §10 decisions |
| No final technology decisions made | ✅ §10/§11 are questions/candidates only |
## Appendix B — Platform Fact Sheet (verified Aug 2026, informs §12)
- iOS Safari ITP: 7-day cap on all script-writable storage incl. SW
registrations for non-installed sites; **installed home-screen apps exempt**.
- Safari 17+ quotas: ~60% of disk per origin (browser apps; home-screen web apps
included), 80% overall; best-effort; eviction is LRU under pressure and
**all-or-nothing per origin**.
- `navigator.storage.persist()`: WebKit grants by heuristic (home-screen app
helps); Chrome auto-grants installed PWAs.
- No `beforeinstallprompt` on iOS; manual Add-to-Home-Screen only.
- No Background Sync / Periodic Background Sync on iOS Safari.
- Web Push: iOS 16.4+, installed PWA only, region-dependent; Android Chrome
full support. Declarative Web Push in Safari 18.4+.
- iOS 26: home-screen-added sites default to opening as web apps.
- Web Bluetooth / Web NFC: unavailable on iOS Safari.
- Chrome: up to ~60% of disk per origin; storage partitioning since Chrome 115.
- All storage APIs + geolocation + SW require secure contexts (HTTPS).
*End of discovery document. Await instruction to proceed to architecture.*