# ADR-013: Each PWA Is a Single Self-Contained HTML File — No Framework, No Build Pipeline

## Status

Accepted, 2026-06-25.

## Status History

```yaml
status_history:
  - date: 2026-06-25
    status: Proposed
    changed_by: hkl
    reason: Formalising foundational tech stack decision that shapes every PWA
    changed_via: adr-kit (360lm)
  - date: 2026-06-25
    status: Accepted
    changed_by: hkl
    reason: All 20 PWAs follow this pattern; no framework has been introduced
    changed_via: adr-kit (360lm)
```

## Context

360lm is developed by a non-technical owner (hkl) working with Claude Code as the primary development tool. There is no dedicated frontend engineering team. A framework-based approach (React, Vue, Angular) would require: a Node.js build pipeline, package management, component architecture decisions, bundler configuration, and TypeScript — all of which add complexity that is hard to reason about without a frontend background. The primary constraint is that the developer must be able to read, understand, and direct changes to the entire PWA in a single file during a conversation with Claude.

## Decision

Every 360lm PWA is a single `index.html` file containing inline CSS, inline JavaScript, and all HTML. No frontend framework (React, Vue, Svelte, Angular). No build pipeline (no webpack, vite, esbuild). No TypeScript. External dependencies (SortableJS, Leaflet, XLSX.js) are self-hosted static files loaded via `<script src="">` tags — never CDN links in production (SW cannot cache CDN resources reliably; see ADR-005).

**Decision Maker:** hkl

## Alternatives Considered

- **React + Vite.** Rejected: requires build pipeline; component model adds indirection that makes Claude-assisted development harder to follow; `npm run build` step breaks the direct edit-reload workflow.
- **Vue single-file components.** Rejected: same build pipeline problem; `.vue` files add a compilation step; not readable as plain HTML in a browser.
- **Web Components (vanilla).** Rejected: Shadow DOM adds debugging complexity; custom element lifecycle (connectedCallback etc.) is non-obvious; no clear benefit over plain functions for this use case.
- **Alpine.js or HTMX (lightweight frameworks).** Rejected: Alpine adds a declarative reactive layer that obscures control flow; HTMX requires server-side HTML rendering which PostgREST does not provide. Both introduce a new mental model without enough benefit. // ponytail: upgrade trigger=PWA file exceeds 5000 lines and modularity becomes a maintenance burden

## Consequences

**Positive:**
- Entire PWA is readable in one file — Claude can see all context in one read.
- No build step: edit `index.html` → reload browser → done.
- Service worker caches a single file — no asset manifest complexity.
- Zero npm dependencies at the PWA level — no `package.json`, no `node_modules`.
- Easy to deploy: rsync a single file.

**Negative / Trade-offs:**
- Large files (tour-pg `index.html` is 3000+ lines) — harder to navigate manually.
- No component reuse across PWAs — patterns must be manually replicated or put in `shared/*.js`.
- No tree-shaking, no minification — files are larger than a bundled app.
- Inline JS/CSS cannot be linted by standard tooling without extra configuration.

**Risks and mitigations:**
- File grows too large to reason about: mitigated by extracting reusable logic to `shared/*.js` when a pattern appears in 3+ PWAs. Current ceiling: 5000 lines before mandatory extraction.
- External library loaded from CDN breaks offline: mitigated by self-hosting all dependencies under the PWA directory and caching via SW (see ADR-005).

## Related Decisions

- ADR-005 (SW CACHE_VER) — single file makes SW caching straightforward.
- ADR-014 (PostgREST API) — no server-side rendering needed; API returns JSON consumed by inline JS.

## References

- `memory/project_arch.md` — PWA list, all are single index.html
- `pwa_dev_style.md` — PWA development style guide
