# 6. A vanilla TypeScript PWA with a size budget and lazy chunks Date: 2026-10-19 (design system: 2026-10-19/20) ## Status Accepted ## Context The phone app opens from an icon, offline, one-handed. It also needs heavy pieces (an Excalidraw editor, Markdown rendering, Mermaid) that must not slow that first screen. The project's goal is to stay tiny: no UI framework, icon library or web font. ## Decision - Vanilla TypeScript built by Vite into the Python package (`scrap/static`). React exists only inside the lazily loaded Excalidraw chunk. - The app shell has a gzipped JavaScript **budget** (16.5 kB, raised from the original 15 kB), enforced by `npm run size` in CI. The service worker precaches the shell only; the lazy chunks it also keeps for offline use are those in `OFFLINE` in `web/vite.config.ts` (the composer, editor, page editor, Markdown renderer, sharing, search filters and recent searches), while Excalidraw (~405 kB gzipped), its fonts and Mermaid load only when used. - The server gzips scripts, styles, pages and JSON, so the gzipped sizes are what crosses the wire. - One design system, all tokens in `tokens.css`: paper and ink with a single amber accent, designed dark mode, controls under the thumb, every gesture also a button, icons always with words. Contrast is held to WCAG AA twice: a unit test over the tokens and an end-to-end sweep of real computed colours in both themes. - A CSP (`font-src 'self' data:; connect-src 'self'`) keeps Excalidraw from reaching its CDN fallback. - Pages open in a new tab as real links; swipes (row actions, paging) start away from the screen edges, which belong to the phone's Back gesture. ## Consequences - New features usually mean a new lazy chunk, not growth of the shell. - Accessibility regressions fail CI rather than review. - No framework means some hand-written view plumbing; the trade was accepted for size and readability.