Sync user preferences to cloud storage#163
Merged
Merged
Conversation
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>
|
Preview Deployment |
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
GET/PUT /api/auth/preferencesin the auth-api worker. Storage is opaque JSON in a newuser_preferencesD1 table; partial updates use a single atomicINSERT ... ON CONFLICT ... DO UPDATEso concurrent PUTs from two devices can't lose each other's writes.glidecomp:preferencesandglidecomp:themelocalStorage 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: trueflush onpagehide. Existing users' localStorage prefs upload once on first cloud-synced session.mapLocationis 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.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 testpasses (21 tests)cd web/frontend && bun run testpasses (23 tests)bun run typecheck:allpassesbun run buildsucceedscurl -b cookies.txt http://localhost:8788/api/auth/preferencesshows the change🤖 Generated with Claude Code