Skip to content

Spike: prove out TanStack Query as the persistence source-of-truth, using userStyling as the test case #1852

@kmcginnes

Description

@kmcginnes

Goal

The current persistence model (atomWithLocalForage) is load-once-into-memory + write-whole-value-back, with an optimistic in-memory cache and background flush. This design is the root cause of #1820 (cross-tab clobber) and a recurring source of issues. The committed ADR (per-key-diff-merge) patches the storage layer but, by its own scope boundary, leaves stale reads and live cross-tab freshness unsolved.

This spike validates an alternative: TanStack Query owns persistence as the async source of truth, accessed the way we'd access a remote server — query-backed reads with loading states, mutations with optimistic updates — mostly outside Jotai.

The question to resolve: is this approach sound and low-risk enough to adopt for all persisted collections, and is the blast radius actually small? (Initial investigation suggests reads funnel through ~5 selector files and writes through a handful of hooks, not the feared 32-sites-everywhere.)

Hard constraints:

  • The shape of data in localforage/IndexedDB must not change (zero migration; upgrading users barely notice — only new loading screens).
  • Lean on optimistic updates to keep the UI fast.
  • IndexedDB stays (per ADR indexeddb-not-localstorage-for-persistence) — localStorage is ruled out.

Expected Outcome

A working proof of concept migrating userStyling (the EXTREME-severity, highest-frequency collection in #1820) to the new model, plus a recommendation + go/no-go for the remaining collections.

The PoC must demonstrate:

  1. Foundation primitivespersistedQuery / persistedMutation factories wrapping localForage (getItem/setItem on the existing key, unchanged shape).
  2. Boot Suspense gate — replace the top-level await Promise.all([...]) in storageAtoms.ts for this key with ensureQueryData behind the existing <Suspense> in AppStatusLoader, so synchronous getQueryData reads remain available afterward (preserving the sync-read assumption non-React consumers depend on). This is the "new loading screen."
  3. Optimistic mutationuseVertexStyling / useEdgeStyling keep their current signatures; internally onMutate patches the cache, mutationFn re-reads fresh storage + applies the explicit delta + writes back, onError rolls back. Call sites untouched.
  4. Concurrent edits in multiple browser tabs silently clobber shared persisted collections (styling, schema, connections, sessions) #1820 fixed for this collection — the mutationFn read-modify-write against live IndexedDB eliminates sibling clobber; a BroadcastChannel → invalidateQueries listener fixes stale reads across tabs. Reproduce the two-tab scenario and confirm both type X and type Y survive.

Deliverables:

  • The userStyling PoC branch/PR.
  • A revised/replacement ADR superseding per-key-diff-merge (this model fixes clobber and stale reads and freshness).
  • A sliced rollout plan for the remaining collections (schema, configuration + active-configuration, sessions, userLayout, settings flags) with re-scoping notes for the existing Concurrent edits in multiple browser tabs silently clobber shared persisted collections (styling, schema, connections, sessions) #1820 sub-issues.
  • Findings on the known hard parts: sync-read preservation behind the boot gate; non-React mutators (schemaSyncQuery.ts, edgeConnectionsQuery.ts, nodeCountByNodeTypeQuery.ts, graphSession/storage.ts); selector-bridge correctness for mergedConfigurationSelector (joins config+schema+styling).

Why userStyling (not settings flags)

Settings flags are scalar booleans — they don't exhibit the read-modify-write clobber and wouldn't exercise optimistic delta-merge, cross-tab reconciliation, or the array-splice relocation. userStyling is the EXTREME-severity collection from #1820, the highest-frequency user mutation, and already carries the delta-based splice logic in useVertexStyling/useEdgeStyling — making it the true tracer bullet for the whole approach.

Non-goals

  • Migrating other collections (separate slices, gated on this spike's recommendation).
  • Changing the on-disk storage shape.
  • Rebuilding the Jotai derived-selector graph wholesale (a larger bet; revisit only if this spike recommends it).

Related Issues

Important

Internal only — this issue is maintained by the core team and is not accepting external contributions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    customizationCustomization options for rendering graph data in non-default waysinternalSignals that the team will work on this issue internally.needs-triageMaintainer needs to evaluatereliabilityIssues relating to improvements in reliabilitytech debtIssues, typically tasks, that are mainly about cleaning up code that is problematic in some way

    Type

    No fields configured for Spike.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions