Skip to content

feat: frontend pages + PWA + Docker + docs (v0.1.0 push) - #2

Merged
varunpan merged 15 commits into
mainfrom
feat/frontend-pwa-deploy
May 8, 2026
Merged

feat: frontend pages + PWA + Docker + docs (v0.1.0 push)#2
varunpan merged 15 commits into
mainfrom
feat/frontend-pwa-deploy

Conversation

@varunpan

@varunpan varunpan commented May 8, 2026

Copy link
Copy Markdown
Owner

Summary

Completes v0.1.0 implementation Tasks 19–32 from docs/superpowers/plans/2026-05-07-quicklogger-implementation.md. Backend, CI, and GitHub repo were already in place on main (PR #1); this PR adds everything else needed to ship the PWA.

What's in here

Frontend pages (Tasks 19–22):

  • / main fuel-entry form (mockup B): vehicle pill, odometer/date row, volume + gal/L toggle, cost + currency, fill-to-full / missed-fillup toggles, expandable extras, live MPG + USD preview, offline-queue fallback on submit failure
  • /vehicles picker
  • /history (queued submissions + last LubeLogger record)
  • /settings (default unit + currency)

PWA (Tasks 23–24):

  • static/manifest.webmanifest + placeholder icons (SVG-as-PNG, replace before stable)
  • src/service-worker.ts with app-shell precache, network-first /api/*, and sync-queue message handler that flushes IndexedDB on focus
  • Layout registers SW + triggers sync on window.focus

E2E (Task 25):

  • Playwright happy-path + URL-prefill specs, mobile-Safari profile, route-mocked LubeLogger/FX

Docker + release (Tasks 26, 27, 29):

  • Multi-stage Dockerfile on node:22-alpine (~150–200 MB target), runs as node, /healthz probe
  • compose.example.yml fork-friendly drop-in
  • .github/workflows/build.yml multi-arch (linux/amd64 + linux/arm64) GHCR release on main push + v*.*.* tags
  • Switched @sveltejs/adapter-auto@sveltejs/adapter-node

Docs (Tasks 30, 31, 32):

  • docs/shortcuts.md + shortcuts/ recipes (URL-deep-link + direct-POST voice flows)
  • docs/uat.md v0.1.0 manual checklist
  • README.md polish: quickstart, env-var table, doc links, screenshots placeholder

Notable decisions

  • ESLint config (eslint.config.js) was extended with globals.browser for client/svelte code and globals.serviceworker for the SW — first project use of window/navigator/self.
  • A handful of eslint-disable-next-line svelte/no-navigation-without-resolve comments were added for goto('/') and <a href="/"> calls — SvelteKit 2.57 typed-routes rejects literal route IDs even for /. Worth revisiting in a follow-up that adopts resolve().
  • Two FX-state corner cases were caught and fixed during code review: fxStale not cleared on the manual-FX/identity branches, and manualFxRate not cleared when the auto chain came back online.

Test plan

  • npm run lint clean
  • npm run check 0 errors (6 baseline state_referenced_locally warnings on the form)
  • npm run build succeeds with adapter-node, emits build/index.js
  • npm test — 60/60 unit + integration passing
  • npm run test:e2e — 2/2 Playwright specs passing locally on mobile-safari
  • CI green (waiting on this PR's run)
  • Manual UAT on iPhone (Task 36, post-merge)

After merge: tag v0.1.0 (Task 34), then deploy to homelab (Tasks 35–36).

varunpan added 15 commits May 8, 2026 00:55
Mockup B layout: vehicle pill, odometer/date row, volume + unit
toggle, cost + currency dropdown, fill-to-full / missed-fillup
toggles, expandable extras (notes), live preview line, submit
button. URL query params honored for Shortcuts deep-link path.
On submit failure (non-4xx), payload enqueues to IndexedDB.
Architecture doc Frontend-pages section populated.
Two corner cases the initial form missed:
- fxStale was not cleared when the FX chain became unreachable or
  when the user reverted to USD, so a stale-rate banner could
  outlive the rate it described.
- manualFxRate was not cleared when the auto chain came back
  online, so previewUsd kept using the user's manual override
  even though the live rate was now available.
Lists vehicles from /api/vehicles, tap to set lastVehicleId in prefs
and return to /. Empty-state message when LubeLogger has none.
Pending IDB queue surfaced in amber, with status + attempts +
lastError. Last LubeLogger record shown raw (good enough for v0.1.0,
prettify later).
Writes through to localStorage on each change. Note clarifies that
server-side conversion is the source of truth, prefs are UI-only.
Standalone display, portrait, dark theme matching the app shell.
Icons are SVG-as-PNG placeholders — see TODO in Task 23 to replace
with proper PNGs before tagging.
App-shell precache, network-first /api/* with 504 fallback, sync-queue
message handler that flushes IndexedDB on focus. Layout registers SW
and triggers sync on window.focus. Architecture doc populated.

ESLint config updated with browser globals for client code/svelte
files and serviceworker globals for the SW (first project use of
window/navigator/self in source).
Playwright tests stub /api/* via route() so they don't need a real
LubeLogger. Mobile Safari profile (iPhone 14).
Switched to @sveltejs/adapter-node. Image runs as 'node' user with
/data owned by node for FX cache writes. HEALTHCHECK probes /healthz.
deployment doc Image-build section populated.
compose.example.yml documents every env var, sane defaults, and a
clean reverse-proxy pattern. Self-hosting doc populated.
Buildx + QEMU produce linux/amd64 + linux/arm64 manifest. Tags via
docker/metadata-action: latest on main, semver from git tags, sha
shorts always. GHA cache for layer reuse. Release-workflow doc
populated.
Two patterns: URL deep-link prefill (simpler) and direct-POST voice
flow (no UI). Recipes are step-by-step build instructions for the
iOS Shortcuts app since .shortcut binaries must be authored on iOS.
iCloud share-link table is populated after first publish.
Covers cert, PWA install, happy path, unit conversion, offline queue,
both Shortcut paths, FX outage path, settings persistence.
Quickstart covers fork-friendly path. Env vars listed with required
column + defaults. Linked subdocs cover deeper material. Screenshots
placeholder directory created (real PNGs added after first deploy).
Tests run on the mobile-safari profile (iPhone 14 / WebKit) defined
in playwright.config.ts, so the CI step needs the webkit binary.
@varunpan
varunpan merged commit 5d5d445 into main May 8, 2026
1 check passed
@varunpan
varunpan deleted the feat/frontend-pwa-deploy branch May 8, 2026 11:26
varunpan added a commit that referenced this pull request Jun 11, 2026
The per-provider guard was 'typeof rate !== "number"', but typeof NaN is
'number', so a provider returning NaN (or 0, or a negative) passed, got
cached, and yielded cost * NaN / 0 / negative. Tightened all three branches
to Number.isFinite(rate) && rate > 0, so a bad rate is a provider failure
and the chain falls through.

Also removed the withTimeout() wrapper: it built an AbortController whose
signal was never wired to anything, so it was a no-op around the frankfurter
fetch. The real timeout is AbortSignal.timeout(TIMEOUT_MS), already on every
provider fetch; the frankfurter call now uses it directly.

Refs review #2, #18.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant