Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ vendor/
frontend/node_modules/
frontend/dist/

# Tests
frontend/test-results/
frontend/playwright-report/
test-results/

# Embedded frontend build output — regenerated by `make build` and by the
# Docker build. Keeping the dir (.gitkeep) so //go:embed has a target.
internal/router/static/*
Expand All @@ -33,3 +38,41 @@ Thumbs.db

# Data
/data/

# ── GSD baseline (auto-generated) ──
.gsd
.gsd-worktrees/
.gsd-backups/
.gsd-id
.mcp.json
.bg-shell/
nul
nul.*
con
con.*
prn
prn.*
aux
aux.*
com[1-9]
com[1-9].*
lpt[1-9]
lpt[1-9].*
*.swo
*~
*.code-workspace
.env.*
!.env.example
node_modules/
.next/
dist/
build/
__pycache__/
*.pyc
.venv/
venv/
target/
*.log
coverage/
.cache/
tmp/
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ ENV PATH="/usr/local/go/bin:${PATH}"

# Build frontend
WORKDIR /tmp/frontend
# The symlink frontend/public/icons -> ../../public/icons resolves from
# /tmp/frontend to /tmp/public/icons. Copy the source icon files there
# so the symlink is valid during and after the build.
COPY public/icons/ /tmp/public/icons/
COPY frontend/package.json frontend/package-lock.json* ./
RUN npm ci --production=false
COPY frontend/ ./
Expand Down
120 changes: 114 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,123 @@ Six installation options are available (see [INSTALL.md](INSTALL.md) for details
5. **Docker Compose**
6. **Manual installation**

## Documentation
## Progressive Web App (PWA)

- [Installation Guide](INSTALL.md) -- all deployment options
- [User Guide](USER-GUIDE.md) -- how to use the app day-to-day
- [API Documentation](API.md) -- REST API reference for integrations
BabyTracker runs as a Progressive Web App so you can install it on a tablet, phone, or desktop and use it without an active internet connection. The app caches all static assets and API responses, queues writes while offline, and replays them automatically when the network returns.

## Screenshots
### Installing the app

| Platform | How to install |
|---|---|
| **Android (Chrome)** | Tap the install banner that appears after first visit, or use the browser menu **⋮ → Install app**. |
| **iOS (Safari)** | Open the app in Safari, tap the **Share** button, then scroll and tap **Add to Home Screen**. |
| **Desktop (Chrome / Edge)** | Click the install icon in the address bar, or use the browser menu **⋮ → Install BabyTracker**. |

Once installed the app launches in its own window (standalone mode) with no browser chrome, and continues working when the Pi is offline.

### Offline behavior

| Feature | What happens offline |
|---|---|
| **Pages & assets** | Served from the service worker's precache — the app loads instantly. |
| **Reading data** | The last cached API responses are shown; a thin offline banner appears at the top of the page when connectivity is lost. |
| **Writing data** | Feedings, sleep logs, and every other tracked entry are queued in IndexedDB. A small indicator in the header shows the queue depth. On reconnection the queue replays automatically, and failed writes surface an error badge. |
| **Gallery / photos** | Photo metadata is cached, but actual photo images require the network (gallery endpoints are `NetworkOnly` in the service worker). |

### Caching strategy

| Scope | Strategy | Details |
|---|---|---|
| Static assets (JS, CSS, images) | Precached at install / update | All Vite-built files are precached via Workbox. |
| API responses (JSON) | NetworkFirst, 5 s fallback to cache | Cached for 24 h, up to 50 entries (R009). |
| Gallery & photos | NetworkOnly | Always fetched live from the server. |

### Development

PWA assets (manifest, service worker, icons) are generated at build time by [`vite-plugin-pwa`](https://github.com/vite-pwa/vite-plugin). Run `npm run build` in the `frontend/` directory and the output includes everything needed: `sw.js`, `manifest.webmanifest`, icon images, and the `registerSW.js` registration script.

## End-to-End Testing (Playwright)

BabyTracker ships a Playwright test suite covering PWA installability, offline behaviour, IndexedDB persistence, and write-queue replay. The tests are located in `frontend/e2e/`.

### Quick start

Install Playwright browsers once, then run the full suite against the Vite production build:

```bash
cd frontend

# 1. Install Playwright system dependencies (one-time)
npx playwright install --with-deps chromium firefox webkit

# 2. Run all tests (builds + previews the app automatically)
npx playwright test
```

The `playwright.config.ts` `webServer` hook builds the frontend (`npm run build`) and serves it via `vite preview` on port 5173 before running each test run. You do not need a running backend — the tests intercept API calls and use demo mode.

### What the tests cover

| File | Scope |
|---|---|
| `smoke.spec.ts` | PWA manifest validation, icon availability, service worker serving, iOS meta tags, Workbox runtime cache config, IndexedDB cache module, install prompt component |
| `offline-ui.spec.ts` | Offline banner visibility, reconnecting flash state, banner dismissal, gallery offline state, write-queue indicator presence |
| `write-queue.spec.ts` | IndexedDB write persistence (correct order), replay on reconnect, failed write error marking with HTTP status + message |

### Architecture note: mocking offline

The tests use `context.setOffline(true)` (Playwright's browser-engine-level network simulation) rather than `page.evaluate()` to mock `navigator.onLine`, because `navigator.onLine` is read-only in Chromium and cannot be overridden via JavaScript evaluation. `context.setOffline()` simulates disconnection at the network stack level, which is the most reliable approach for PWA testing.

### Running a single test file or project

```bash
# One test file only
npx playwright test e2e/offline-ui.spec.ts

# One browser engine (useful for debugging engine-specific bugs)
npx playwright test --project chromium
npx playwright test --project firefox
npx playwright test --project webkit

# Only the write-queue tests
npx playwright test e2e/write-queue.spec.ts
```

### CI configuration

In CI (`CI` environment variable set) the tests run with:
- **One retry** on failure (transient flakiness is retried once)
- **Traces** captured on first retry only
- **Forbid-only** enabled (failing tests block the pipeline)

To emulate CI locally:

```bash
CI=1 npx playwright test --reporter=html
```

This generates `frontend/playwright-report/index.html` with a full visual report.

### Debugging failures

When a test fails, a screenshot and Playwright trace are saved under `frontend/test-results/`. To open the trace inspector:

```bash
npx playwright show-trace frontend/test-results/<path-to-trace.zip>
```

To replay a failing test in an interactive Chromium browser (keeps the page open after the test):

```bash
npx playwright test --debug e2e/offline-ui.spec.ts
```

### Adding new tests

Screenshots coming soon.
- Use the `context.setOffline(true/false)` pattern for offline simulation (see existing tests for reference).
- Enable demo mode by routing `/api/config` to return `{ "demo_mode": true }` before navigation (see `enableDemoMode()` helper).
- For IndexedDB tests, use `page.evaluate()` to interact with `keyval-store` directly, as the `idb-keyval` library uses that DB name.
- Write tests for both the component level (banner renders) and the source level (configuration constants in `.js`/`.jsx` files) where applicable.

## Tech Stack

Expand Down
4 changes: 4 additions & 0 deletions deploy/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Stage 1: Build frontend
FROM node:22-alpine AS frontend-builder
WORKDIR /app/frontend
# Resolve the frontend/public/icons symlink: it points to ../../public/icons
# which resolves to /app/public/icons from WORKDIR. Copy the source icon
# files there so the symlink is valid during and after the build.
COPY public/icons/ /app/public/icons/
COPY frontend/package.json frontend/package-lock.json* ./
RUN npm ci
COPY frontend/ ./
Expand Down
Loading