Repository navigation
Conversation
Adds collections/user.ts: a local-only TanStack DB collection holding the signed-in user, read through getUser() (module-level reactive mirror) and written through named functions only: direct writes for what the server already did, optimistic actions for user changes. All writers move over; the states/snapshot mirror and DB.setSnapshot/initSnapshot are deleted. DB.getSnapshot stays as a read-only shim over getUser until U4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
It is a large two-way-door refactor of all user-data writers plus a subtle optimistic-action concurrency model, and it introduces a stale cross-user testActivityByYear cache that needs human verification.
1 open issue
What changed in this PR
Implements roadmap step U1: replaces the mutable Snapshot object and its reactive Solid mirror with a single-row, local-only TanStack DB collection keyed by uid (collections/user.ts). Reads go through one reactive getUser(); DB.getSnapshot becomes a read-only shim (= getUser, returning DeepReadonly<Snapshot>). Writers are split into named direct writes (xp, streak, typing stats, PBs/PB reset, lb memory, badges, inbox count, Discord link) and optimistic actions (name, Discord unlink, streak offset, quote rating, favourite quote, profile) that apply immediately and roll back on failure. The auth flow now calls loadUserData()/clearUser(), and the last-result signal moves to states/last-result.ts.
Changes:
- New
collections/user.tssingle-row collection with reactive mirror, direct writes, and reusable optimistic-action helper. db.tsslimmed to agetSnapshot = getUsershim; snapshot init/mutation code and the reactive mirror (states/snapshot.ts) deleted; all writers/readers migrated.- Spec/roadmap updated (U1 marked done, intended behaviour deviations documented) and new
user.spec.ts(16 tests) added.
| File | Description |
|---|---|
| frontend/src/ts/collections/user.ts | New single-row collection: load/clear, direct writes, optimistic actions, PB/test-activity helpers. |
| frontend/src/ts/db.ts | Removes snapshot state/mutators; getSnapshot shim; rebuilds calendar from plain data; module-level per-year cache. |
| frontend/src/ts/states/snapshot.ts | Deleted reactive mirror + mini-snapshot. |
| frontend/src/ts/states/last-result.ts | New home for the last-result signal. |
| frontend/src/ts/constants/default-snapshot.ts | testActivity is now plain TestActivity; drops calendar classes/isMiniSnapshot. |
| frontend/src/ts/utils/types.ts | Adds DeepReadonly<T>. |
| frontend/src/ts/auth.tsx | Uses loadUserData()/clearUser() instead of initSnapshot/setSnapshot. |
| frontend/src/ts/controllers/quotes-controller.ts | Delegates favourite toggling to setQuoteFavorite. |
| frontend/src/ts/components/.../*.tsx | Many components/modals switch reads to getUser() and writes to the new named actions. |
| frontend/src/ts/collections/{results,inbox,connections}.ts | Import moved/renamed user helpers. |
| frontend/__tests__/collections/user.spec.ts | New coverage for load, direct writes, optimistic apply/rollback. |
| frontend/__tests__/__harness__/app-shell.tsx | Uses userTesting.setUser instead of setSnapshot. |
| docs/specs/solid-monkeytype/{spec,roadmap}.md | Documents U1 as done and the intended behaviour deviations. |
Review effort: Balanced
@tanstack/db 0.15 holds direct writes back while an optimistic action is pending, so the user collection's actions dropped writes like addXp made meanwhile. Its optimistic actions are now direct writes: apply, request, reapply with the server's values, or roll back the changed fields that weren't changed again since. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NZNRfGDYsGYMUTp2zYUTrv
# Conflicts: # frontend/src/ts/components/layout/header/Nav.tsx # frontend/src/ts/components/layout/overlays/Banners.tsx
# Conflicts: # docs/specs/solid-monkeytype/spec.md
The collection had no sync source and needed a mirror store anyway; one Solid store in states/user.ts now holds the user. Same API. Spec Q30. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Snapshot → UserData, SnapshotResult → UserResult, getDefaultSnapshot → getDefaultUserData (constants/user-data.ts) - SnapshotInitError → UserDataLoadError (utils/user-data-load-error.ts) - auth event snapshotUpdated → userLoaded - delete the DB.getSnapshot() shim; readers import getUser() - console global window.snapshot → window.user Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Store (@Miodec) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # docs/AGENT_BROWSER.md # docs/specs/solid-monkeytype/roadmap.md # frontend/src/ts/components/pages/account/Table.tsx # frontend/src/ts/controllers/quotes-controller.ts
# Conflicts: # frontend/__tests__/controllers/preset-controller.spec.ts # frontend/src/ts/collections/connections.ts # frontend/src/ts/collections/inbox.ts # frontend/src/ts/components/layout/header/AccountMenu.tsx # frontend/src/ts/components/layout/header/Nav.tsx # frontend/src/ts/components/layout/overlays/Banners.tsx # frontend/src/ts/components/modals/EditProfileModal.tsx # frontend/src/ts/components/modals/PbTablesModal.tsx # frontend/src/ts/components/modals/QuoteRateModal.tsx # frontend/src/ts/components/modals/QuoteSearchModal.tsx # frontend/src/ts/components/modals/StreakHourOffsetModal.tsx # frontend/src/ts/components/modals/account-settings/ReauthConfirmModals.tsx # frontend/src/ts/components/modals/account-settings/UnlinkDiscordModal.tsx # frontend/src/ts/components/modals/account-settings/UpdateNameModal.tsx # frontend/src/ts/components/pages/account-settings/AccountTab.tsx # frontend/src/ts/components/pages/account/MyProfile.tsx # frontend/src/ts/components/pages/connections/FriendsList.tsx # frontend/src/ts/components/pages/leaderboard/LeaderboardPage.tsx # frontend/src/ts/components/pages/profile/ActivityCalendar.tsx # frontend/src/ts/components/pages/test/modes-notice/PbNotice.tsx # frontend/src/ts/components/pages/test/result/ResultSecondaryStats.tsx # frontend/src/ts/components/pages/test/result/TestResult.tsx # frontend/src/ts/controllers/preset-controller.ts # frontend/src/ts/controllers/quotes-controller.ts # frontend/src/ts/db.ts # frontend/src/ts/dev/auth.ts # frontend/src/ts/elements/result-batches.ts # frontend/src/ts/index.tsx # frontend/src/ts/queries/profile.ts # frontend/src/ts/router/routes.ts # frontend/src/ts/states/leaderboard-selection.ts # frontend/src/ts/states/snapshot.ts # frontend/src/ts/test/test-logic.ts # frontend/src/ts/url-actions/actions/discord-oauth.ts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ollections/results, activity calendar loader → ActivityCalendar (@Miodec) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Continuous integration check(s) failed. Please review the failing check's logs and make the necessary changes. |
# Conflicts: # docs/specs/solid-monkeytype/roadmap.md # frontend/src/ts/components/layout/header/AccountMenu.tsx
| {/* UserProfile only reads the profile */} | ||
| {(p) => ( | ||
| <UserProfile | ||
| profile={p() as unknown as UserProfileType} |

Summary
U1 + U4 from
docs/specs/solid-monkeytype/roadmap.md. One Solid store holds the signed-in user and replaces the mutable snapshot, its reactive mirror and theDB.getSnapshot()reads. The "snapshot" naming is gone.An optimistic action applies its change, sends the request, then applies the change again with the server's values. If the request fails, it rolls back only the fields it changed that nothing has written since. So a result save that lands while a name change is pending keeps its update, whether the name change succeeds or fails.
Why a store, not a collection (spec Q30): the collection had no sync source and still needed a mirror for synchronous reads.
Intended deviations (added to the spec):
Evidence
__tests__/states/user.spec.ts(19 tests): load success/failure; every direct write; optimistic apply/rollback; a direct write made while an action is pending survives both success and failure; a newer action's change survives an older one failing; profile keeps the server's sanitized detailsoxlint --type-aware --type-checkcleanmaxFavorites: 0) → server error shown, change rolled backMerge Danger
Door: two-way
Blast Radius: account data
Every user-data reader and writer changed, and so did the sign-in load path. A missed field in the API→user mapping would show as a blank or stale value on account, profile, leaderboard or result screens.
getUser()returnsDeepReadonly, so any mutation left outside the named writes is a type error.🤖 Generated with Claude Code