Skip to content

refactor: single-row user collection replaces the snapshot (U1) (@miodec) - #8523

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

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

Conversation

@Miodec

@Miodec Miodec commented Oct 26, 2026

Copy link
Copy Markdown
Member

Summary

U1 from docs/specs/solid-monkeytype/roadmap.md. A single-row user collection replaces the mutable snapshot and its reactive mirror.

 src/ts/
 ├── collections/
+│   └── user.ts          # local-only row keyed by uid; getUser(), named writes
 ├── states/
-│   ├── snapshot.ts      # reactive mirror (deleted)
+│   └── last-result.ts   # last saved result signal, moved out of the mirror
 └── db.ts                # getSnapshot = getUser (read-only shim until U4); result-save orchestration
auth sign-in → loadUserData()   fetch user, map, insert row (throws, no row on failure)
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)

On success, an optimistic action accepts the current row with its change re-applied, not the mutation's start-of-action copy. Otherwise a result save that lands while a name change is pending would get overwritten.

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__/collections/user.spec.ts (16 tests): every direct write, load success/failure, optimistic apply/rollback, a direct write during a pending action surviving, row without $synced/$origin
  • Full frontend suite: 85 files / 1638 tests pass; oxlint --type-aware --type-check clean
  • Browser (dev, signed in):
    • 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 in the row
    • favourite add (dev maxFavorites: 0) → server error shown, row rolled back
    • sign out → snapshot() undefined; sign back in → row reloaded

Merge Danger

Door: two-way

Blast Radius: account data

Every user-data writer and the sign-in load path changed. A missed field in the API→row mapping would show as a blank or stale value on account, profile, leaderboard or result screens. Readers go through the getSnapshot shim, so DeepReadonly turns any leftover mutation into 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

let dbSnapshot: Snapshot | undefined;
const firstDayOfTheWeek = getFirstDayOfTheWeek();
let testActivityByYear: Record<string, TestActivityCalendar> | undefined;
@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 2 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Related to Markdown files and documentation frontend User interface or web stuff waiting for review Pull requests that require a review before continuing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants