Skip to content

Add Architecture Decision Records (ADRs) #109

@jasoneplumb

Description

@jasoneplumb

Parent Epic

#104

Problem

docs/architecture.md explains how the system works but not why specific approaches were chosen over alternatives. Architecture Decision Records (ADRs) document trade-offs explicitly — this is the core signal of "progressive systems design" to technical reviewers.

Current State

Architecture is documented as implementation descriptions. Design rationale is scattered in code comments or not recorded at all.

Desired State

Create docs/adr/ directory with 5 ADRs covering the project's key design decisions:

ADR-001: Single Mutable State vs. Event Bus / Redux

  • Context: App needs shared state across 10+ modules (GPS, recording, controls, geocoding)
  • Decision: Single AppState object threaded by reference; no event bus, no Redux
  • Alternatives rejected: Redux (too much boilerplate at this scale), Event emitter (indirection without type safety), React Context (would require React)
  • Consequences: Simple, zero-dependency state management; TypeScript strict mode prevents footguns; scales to current complexity but would need rethinking at ~50 modules

ADR-002: Refcount-Based GPS Polling vs. Pub/Sub

  • Context: Two features (locate + recording) independently need GPS polling; must not interfere
  • Decision: Integer refcount (updateCallback) — increment to start, decrement to stop, timer runs when > 0
  • Alternatives rejected: Pub/sub (overkill for 2 consumers), Separate timers (wasteful, race conditions), RxJS observables (heavy dependency)
  • Consequences: Zero-allocation, zero-dependency coordination; trivially correct for N consumers

ADR-003: offsetHeight vs. dvh Units for iOS Safari Viewport

  • Context: CSS vh units and window.innerHeight disagree on older iOS Safari — the info-panel bottom sheet bled ~80px into view
  • Decision: Use el.offsetHeight (actual rendered height) for all snap-point calculations
  • Alternatives rejected: dvh units (not supported on older iOS), -webkit-fill-available (inconsistent), JavaScript viewport polyfill (adds complexity)
  • Consequences: Works across all iOS versions; matches the pattern already used by geocode-bar

ADR-004: Local-Only Data Architecture

  • Context: GPS trail data is sensitive (reveals home, work, daily patterns); app needs to handle it responsibly
  • Decision: All data stays in the browser (localStorage, Cache API, IndexedDB). No server-side storage, no accounts, no analytics.
  • Alternatives rejected: Cloud sync (requires accounts, server infra, privacy policy complexity), Anonymous telemetry (still collects data), Hybrid (complexity without clear user benefit at this scale)
  • Consequences: Zero backend infrastructure; users own their data completely; no sync across devices (acceptable trade-off)

ADR-005: Service Worker + Cache API for Offline Support

  • Context: Outdoor mapping app must work where connectivity is unreliable
  • Decision: Two-tier offline strategy — passive Workbox caching (tiles viewed) + proactive region pre-download (Cache API)
  • Alternatives rejected: IndexedDB tile storage (complex, no native URL matching), App shell only (tiles are the main content), Native app wrapper (loses PWA benefits)
  • Consequences: Works in all modern browsers; Safari ~50MB quota is the main constraint; pre-download fills gaps that passive caching misses

ADR Template Format

# ADR-NNN: Title

## Status
Accepted | Superseded | Deprecated

## Context
What is the issue we're facing? What forces are at play?

## Decision
What did we decide to do?

## Alternatives Considered
What other options were evaluated and why were they rejected?

## Consequences
What are the results of the decision? Both positive and negative.

Suggested Prompt

Create Architecture Decision Records for webmap.dev in docs/adr/:

1. Create docs/adr/README.md with a brief explanation of what ADRs are 
   and an index linking to each ADR.

2. Create these 5 ADRs using the template format (Status, Context, 
   Decision, Alternatives Considered, Consequences):

   - ADR-001-single-mutable-state.md — Why AppState is a single mutable 
     object instead of Redux/event bus. Read src/types.ts and src/main.ts 
     for context on how state flows through modules.

   - ADR-002-refcount-gps-polling.md — Why GPS polling uses integer 
     refcount instead of pub/sub. Read src/timer.ts and the 
     activatePolling/deactivatePolling pattern in src/main.ts.

   - ADR-003-offsetheight-ios-safari.md — Why snap-point math uses 
     offsetHeight instead of dvh units. Read the fullHeightPx() function 
     in src/bottom-sheet.ts and the iOS Safari vh bug it fixes.

   - ADR-004-local-only-data.md — Why all data stays in the browser with 
     no server-side storage. Consider the privacy implications of GPS 
     trail data.

   - ADR-005-offline-tile-strategy.md — Why offline uses two tiers 
     (Workbox passive + Cache API pre-download). Read 
     src/offline-download.ts and vite.config.ts workbox config.

3. Read the actual source files referenced above to ground each ADR in 
   real implementation details, not generic descriptions.

Commit all files and push.

Acceptance Criteria

  • docs/adr/ directory exists with README.md index
  • 5 ADRs created with consistent template format
  • Each ADR references actual source files and patterns
  • Alternatives section names specific rejected approaches with reasons
  • Consequences section includes both positive and negative outcomes

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important — fix this cycledocumentationImprovements or additions to documentationenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions