Skip to content

refactor: user store replaces the snapshot (U1, U4) (@miodec) - #8523

Open
Miodec wants to merge 24 commits into
masterfrom
u1-user-collection
Open

Miodec wants to merge 24 commits into
masterfrom
u1-user-collection

Conversation

@Miodec

@Miodec Miodec commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

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 the DB.getSnapshot() reads. The "snapshot" naming is gone.

 src/ts/
 ├── collections/
+│   └── results.ts               # + NormalizedLocalResult (was SnapshotResult)
 ├── constants/
-│   ├── default-snapshot.ts      # Snapshot, SnapshotResult
+│   ├── user-data.ts             # UserData, getDefaultUserData
 ├── states/
-│   ├── snapshot.ts              # reactive mirror (deleted)
+│   ├── user.ts                  # the store: getUser(), named writes
+│   └── last-result.ts           # last saved result signal, moved out of the mirror
 ├── utils/
-│   └── snapshot-init-error.ts
+│   └── user-data-load-error.ts  # UserDataLoadError
 └── db.ts                        # getSnapshot/setSnapshot/initSnapshot deleted
auth sign-in → loadUserData()   fetch user, map, store it (throws UserDataLoadError, no user on failure)
             → userLoaded event (was snapshotUpdated)
auth sign-out → clearUser()
reads        → getUser()        reactive in Solid, plain read elsewhere; DeepReadonly
direct       → recordResult, addXp, setStreak, addBadge, setInboxUnreadSize,
               updateLbMemory, resetPersonalBests, setDiscordLink
optimistic   → updateName, unlinkDiscord, setStreakHourOffset, rateQuote,
               setQuoteFavorite, updateProfile   (apply now, roll back on failure)
console      → window.user (was window.snapshot)

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):

  • Resetting PBs updates the PBs shown straight away. The reactive copy used to keep the old ones.
  • User changes show at once and roll back if the request fails. They used to show only after the request.
  • The account page's activity calendar updates after a test without a reload.

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 details
  • Full frontend suite after merging master: 105 files / 1771 tests pass; oxlint --type-aware --type-check clean
  • Browser (dev, signed in, collection version before the store swap):
    • finished test → typing stats +1, activity padded to today, account calendar shows "1 test on Monday 26 Oct 2026"
    • edit profile → bio updates on the page, then restored
    • quote rating → stored on the user
    • favourite add (dev maxFavorites: 0) → server error shown, change rolled back
    • sign out → user undefined; sign back in → user reloaded

Merge 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() returns DeepReadonly, so any mutation left outside the named writes is a type error.

🤖 Generated with Claude Code

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>
Copilot AI balanced review requested due to automatic review settings October 26, 2026 11:56
@monkeytypegeorge monkeytypegeorge added frontend User interface or web stuff docs Related to Markdown files and documentation labels Oct 26, 2026
@github-actions github-actions Bot added the waiting for review Pull requests that require a review before continuing label Oct 26, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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.ts single-row collection with reactive mirror, direct writes, and reusable optimistic-action helper.
  • db.ts slimmed to a getSnapshot = getUser shim; 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

Comment thread frontend/src/ts/db.ts Resolved
@Miodec Miodec changed the title refactor: single-row user collection replaces the snapshot (U1) refactor: single-row user collection replaces the snapshot (U1) (@miodec) Oct 26, 2026
Miodec and others added 6 commits October 27, 2026 22:32
@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>
@monkeytypegeorge monkeytypegeorge added the api A change to the Application Programming Interface (API) label Nov 9, 2026
Miodec and others added 3 commits November 9, 2026 16:05
…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
@Miodec Miodec changed the title refactor: single-row user collection replaces the snapshot (U1) (@miodec) refactor: user store replaces the snapshot (U1, U4) (@miodec) Nov 9, 2026
Miodec and others added 3 commits November 9, 2026 17:11
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ions/results (@Miodec)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…timistic actions as plain functions (@Miodec)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Miodec added a commit that referenced this pull request Nov 9, 2026
Miodec and others added 6 commits November 9, 2026 17:35
# 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>
Miodec and others added 3 commits November 9, 2026 17:56
…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>
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

Continuous integration check(s) failed. Please review the failing check's logs and make the necessary changes.

@github-actions github-actions Bot added waiting for update Pull requests or issues that require changes/comments before continuing and removed waiting for review Pull requests that require a review before continuing labels Nov 9, 2026
@github-actions github-actions Bot removed the waiting for update Pull requests or issues that require changes/comments before continuing label Nov 9, 2026
# 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}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is the cast needed?

@github-actions github-actions Bot added the waiting for update Pull requests or issues that require changes/comments before continuing label Nov 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api A change to the Application Programming Interface (API) docs Related to Markdown files and documentation frontend User interface or web stuff waiting for update Pull requests or issues that require changes/comments before continuing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants