feat: frontend pages + PWA + Docker + docs (v0.1.0 push) - #2
Merged
Conversation
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
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 onmain(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/vehiclespicker/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.tswith app-shell precache, network-first/api/*, andsync-queuemessage handler that flushes IndexedDB on focuswindow.focusE2E (Task 25):
Docker + release (Tasks 26, 27, 29):
Dockerfileon node:22-alpine (~150–200 MB target), runs asnode,/healthzprobecompose.example.ymlfork-friendly drop-in.github/workflows/build.ymlmulti-arch (linux/amd64 + linux/arm64) GHCR release onmainpush +v*.*.*tags@sveltejs/adapter-auto→@sveltejs/adapter-nodeDocs (Tasks 30, 31, 32):
docs/shortcuts.md+shortcuts/recipes (URL-deep-link + direct-POST voice flows)docs/uat.mdv0.1.0 manual checklistREADME.mdpolish: quickstart, env-var table, doc links, screenshots placeholderNotable decisions
eslint.config.js) was extended withglobals.browserfor client/svelte code andglobals.serviceworkerfor the SW — first project use ofwindow/navigator/self.eslint-disable-next-line svelte/no-navigation-without-resolvecomments were added forgoto('/')and<a href="/">calls — SvelteKit 2.57 typed-routes rejects literal route IDs even for/. Worth revisiting in a follow-up that adoptsresolve().fxStalenot cleared on the manual-FX/identity branches, andmanualFxRatenot cleared when the auto chain came back online.Test plan
npm run lintcleannpm run check0 errors (6 baselinestate_referenced_locallywarnings on the form)npm run buildsucceeds with adapter-node, emitsbuild/index.jsnpm test— 60/60 unit + integration passingnpm run test:e2e— 2/2 Playwright specs passing locally on mobile-safariAfter merge: tag
v0.1.0(Task 34), then deploy to homelab (Tasks 35–36).