Skip to content

Sync user preferences to cloud storage#163

Merged
pokle merged 4 commits into
masterfrom
cloud-sync-preferences
May 11, 2026
Merged

Sync user preferences to cloud storage#163
pokle merged 4 commits into
masterfrom
cloud-sync-preferences

Conversation

@pokle
Copy link
Copy Markdown
Owner

@pokle pokle commented May 11, 2026

Summary

  • Adds GET/PUT /api/auth/preferences in the auth-api worker. Storage is opaque JSON in a new user_preferences D1 table; partial updates use a single atomic INSERT ... ON CONFLICT ... DO UPDATE so concurrent PUTs from two devices can't lose each other's writes.
  • Frontend sync layer backs the existing glidecomp:preferences and glidecomp:theme localStorage keys with the cloud API. localStorage stays the synchronous read cache (no startup flicker, works offline, anonymous users unchanged). When signed in: hydrate on startup, 2s debounced PUTs on mutation, keepalive: true flush on pagehide. Existing users' localStorage prefs upload once on first cloud-synced session.
  • mapLocation is allowlisted as device-local: stripped on upload, preserved when cloud overwrites local during hydrate. Different devices have different screens, and continuous pan events would generate noisy writes.
  • Bootstraps vitest harnesses for both auth-api (no tests previously) and the frontend (no tests previously). 44 tests total (21 backend + 23 frontend) covering auth gating, validation, size cap, partial updates, CASCADE on user delete, concurrent PUTs, the hydrate matrix, debounce coalescing, retry/4xx/keepalive, pagehide flush, and the local-only mapLocation behaviour.

No audit log: per CLAUDE.md, audit is scoped to mutations that affect competition scores. User preferences don't.

Test plan

  • cd web/workers/auth-api && bun run test passes (21 tests)
  • cd web/frontend && bun run test passes (23 tests)
  • bun run typecheck:all passes
  • bun run build succeeds
  • Manual: sign in via dev-login, change units in the analysis page, wait 2s, curl -b cookies.txt http://localhost:8788/api/auth/preferences shows the change
  • Manual: clear localStorage, reload — values hydrate from cloud, theme re-applies
  • Manual: open same account in a private window — settings match
  • Manual: pan the map repeatedly — no PUTs generated (verify in network tab)
  • Manual: sign out — sync stops; further changes are localStorage-only

🤖 Generated with Claude Code

pokle and others added 4 commits May 11, 2026 07:47
Adds GET/PUT /api/auth/preferences in the auth-api worker for syncing
per-user app preferences and custom theme across devices. Storage is
opaque JSON in a new D1 table; partial updates are atomic via a single
ON CONFLICT statement so concurrent PUTs from two devices can't lose
each other's writes.

Bootstraps a vitest+miniflare test harness for auth-api (none existed)
and covers the route with 21 cases including auth gating, validation,
size cap, partial updates, CASCADE on user delete, and a concurrent-PUT
test pinning the atomicity guarantee.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a frontend sync layer that backs the existing localStorage-based
preferences and theme with the cloud API. localStorage remains the
synchronous read cache so startup has no flicker and anonymous users
keep working as before. When signed in, the layer hydrates from cloud
on import, debounces mutations (2s) into PUTs, and flushes any pending
writes on pagehide via keepalive fetch. Existing users' localStorage
prefs are uploaded once on first cloud-synced session.

mapLocation is allowlisted as device-local: stripped on upload and
preserved when cloud values overwrite localStorage during hydrate.
Different devices have different screens; viewport sync would also
generate noisy writes from continuous pan events.

Bootstraps a vitest+jsdom harness for the frontend (none existed) with
a localStorage polyfill that bypasses Node 22+'s broken built-in web
storage shim. Covers the sync layer with 23 cases across hydrate
quadrants, debounce coalescing, retry/4xx/keepalive behaviour, and
the pagehide flush path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The root tsconfig includes web/workers/**/* and was excluding test
directories per-worker (competition-api/test, mcp-api/test). auth-api/test
was missing from that list — created in the prior commit — so the root
typecheck reached into env.d.ts, transitively typechecked src/auth.ts
under stricter root types, and surfaced both a pre-existing Kysely type
clash and cloudflare:test module-resolution errors that don't apply to
the auth-api package's own typecheck.

Matches the established pattern for the other workers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a `storage` event listener that fires when another tab on the same
origin writes to localStorage. When the prefs key changes, refresh the
config cache and dispatch the existing `glidecomp:preferences-changed`
event so reactive UI updates. When the theme key changes, re-apply the
theme via the same path autoApply uses — including the BASECOAT_LIGHT
fallback when the other tab cleared the theme (resetTheme).

The source tab never sees its own writes (per spec), and the storage
handler never triggers a cloud PUT — the writing tab already scheduled
one. Cross-device sync still requires a page reload; this only covers
multi-tab on a single device, which is free.

The module-level singleton now skips listeners when MODE === "test" so
it doesn't shadow test-instantiated copies. Test instances explicitly
construct `new PreferencesSync()` (quiet=false) to exercise the
listener paths. Six new test cases cover prefs propagation, theme
propagation, theme-cleared fallback, key filtering, sessionStorage
filtering, and the no-cloud-PUT invariant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

Preview Deployment
https://42e90e8d.glidecomp.pages.dev
Commit: 1f6b08e

@pokle pokle merged commit 3689fac into master May 11, 2026
7 checks passed
@pokle pokle deleted the cloud-sync-preferences branch May 11, 2026 10:02
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