aws / aws/graph-explorer

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

Open
#1,852 0 comments 0 reactions 0 assignees View on GitHub
customization internal needs-triage reliability tech debt
Dominant language
TypeScript
Stars
481
Forks
108
Avg merge
6d 8h
Merged PRs (30d)
5

Description

## 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 primitives** — `persistedQuery` / `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 `` 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 mutation** — `useVertexStyling` / `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. **#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 #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

- Originated from #1820 (cross-tab clobber bug this model fixes more completely)
- Supersedes the approach in ADR `20260616-per-key-diff-merge-cross-tab-reconciliation`

> [!IMPORTANT]
> Internal only — this issue is maintained by the core team and is not accepting external contributions.

Contributor guide

Open the contributing guide

Research direction

Start with storageAtoms.ts and the existing Suspense path in AppStatusLoader, then trace useVertexStyling and useEdgeStyling plus the non-React entry points named in the issue. Reproduce the two-tab userStyling scenario and assess the proposed query-backed persistence without changing the IndexedDB shape. Done means a working PoC, confirmed clobber and freshness behavior, a replacement ADR, and a rollout recommendation.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
database, frontend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.