Public: anyone can read this; the crew can change it.

Offline-first feed: a Mac with no wifi still shows the last feed and keeps your tells #371

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A Mac with no network now still shows the last home feed, with a note saying how old it is. Dismisses, mutes, opens and tells queue and send when the network returns. The 238 tests across the feed, studio, live, board, map, big and web suites pass, including the new `test/offline.test.ts` (added to the `check` in `forge.json`). I didn't get a clean `bun x tsc --noEmit` run: here it fails with `Can…

shipped production is serving it deploy 97ab7b8

opened this

A Mac with no network now still shows the last home feed, with a note saying how old it is. Dismisses, mutes, opens and tells queue and send when the network returns. The 238 tests across the feed, studio, live, board, map, big and web suites pass, including the new test/offline.test.ts (added to the check in forge.json). I didn't get a clean bun x tsc --noEmit run: here it fails with Cannot find type definition file for 'bun'. I didn't run the full forge.json check, only the suites named above.

What changed

  • Service worker (src/web/sw.ts, served at /sw.js): / is network-first, and the last good copy is served when the network fails. The stylesheet, /assets/* and icons are cached.
  • Age note: a note above the feed says "Offline: this feed is from 12 min ago". The page reloads itself when online fires.
  • Sign-out clears the cache and the outbox.
  • CSP gains worker-src 'self'.
  • Outbox (src/web/outbox.ts): acts are kept in localStorage (200 at most), each with an Idempotency-Key made when it is queued. They are flushed on page open, on online and after each add. It does not use Background Sync.
  • Queue behaviour: entries leave the queue on any 2xx or a final 4xx. A network error, 5xx, 408, 429 or 401 keeps them in order. The page says when acts are waiting.
  • Idempotent replay: signed-in browser sessions now honour Idempotency-Key on /api/v1/ mutations, scoped to the person. A replay returns the first answer. This is a small change in src/http/server.ts that applies to every /api/v1/ mutation made with a session, not only the feed.
  • Tells: new POST /api/v1/feed/tells {app, text} is the JSON tell the outbox sends. Card forms still work without JavaScript.
  • /events resume: the live client reopens /events?since=<last id> when the browser gives up on the stream, and on online. It then refreshes every region once.
  • Docs: docs/FEED.md has an Offline section.
  • Tests cover ordered replay across a reload, key reuse after a lost answer, 5xx versus 4xx handling, concurrent flushes, a tell or interaction sent twice by key landing once, the validation and same-origin checks on tells, and the worker's validity and headers.
  • Existing tests: I kept the literal new EventSource('/events') that six other suites assert on.

Left out

  • Steering a run offline. It is a plain form post and needs the network.
  • Updating the cached / from live swaps. It stays the page as last loaded.
  • Swipes, because there is no swipe UI yet.
  • Push and anything native.
  • I could not exercise the worker in a real browser here. Its source is tested only for validity.

To try it Open / signed in, then go offline in dev tools and reload. Dismiss a card or send a tell while offline, then go back online and watch the queue drain.

🤖 Generated with Claude Code

Closes #363

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs