Skip to content

Under the Hood

Doug Blank edited this page Oct 5, 2026 · 7 revisions

🌐 Also available in: Deutsch · Español · Français · 简体中文

Under the Hood

Implementation notes that don't fit the narrative arc of Architecture — details worth knowing if you're debugging something, auditing what the app stores where, or wondering why it behaves a certain way.

Browser storage: sessionStorage vs localStorage

Both are scoped to the browser profile + origin, not to whoever is logged in. If User A signs out and User B signs in on the same browser (same machine, same browser profile), User B's tab starts from whatever that origin's storage already held — unless the app explicitly clears or namespaces it. Gramps Connect splits its client-side storage across the two APIs along exactly that line:

sessionStorage (app/src/auth/auth.ts) holds the access token, refresh token, and username. logout() explicitly clears all three, and sessionStorage itself doesn't survive a closed tab — so a fresh login always starts clean, and nothing here leaks between users.

localStorage holds UI preferences, and — unlike the auth tokens — none of it is cleared on logout:

Key Scope File
gramps-connect_home_person per-tree (keyed by getTreeId()) app/src/store/homePersonPreference.ts
gramps-connect_column_widths global app/src/store/columnWidths.ts
gramps-connect_tree_manual_expand global app/src/store/treeExpandPreference.ts
gramps-connect_aside_widths global app/src/App.tsx
gramps-connect:map-viewport global app/src/components/visuals/MapCanvas.tsx
gramps-connect_browser_notifications_enabled global app/src/store/browserNotifications.ts

None of these are namespaced by user, and only home_person is namespaced by tree. Practical effect: on a shared machine, the next person to log in (even into a different tree, for the global keys) inherits the previous user's column widths, pane sizes, map viewport, and notification toggle — and, if it's the same tree, their home-person pick too. Nothing sensitive is exposed (no tree data or credentials live here, only display preferences), but it's a real behavior a user could notice and ask about.

If per-user isolation is ever wanted, the fix is the same pattern homePersonPreference.ts already uses for per-tree scoping — key the stored object by user id (or username) instead of, or in addition to, tree id.

Related/detail pane: instant repaint on revisit

The upper-right (and bottom "Reference detail") pane's per-record fetch (fetchObjectExtended, app/src/store/objectDetail.ts) is backed by a small in-memory, stale-while-revalidate cache, keyed by view + handle and capped at 50 entries (LRU-evicted). Navigating away from a record and back to it repaints the last-known detail immediately instead of showing a loading spinner — the underlying list views (DataTable) already did this via their persistent ViewStore/SQLite cache (see Architecture); this closes the same gap for the detail pane, which previously refetched from scratch on every single mount.

It's not a real cache in the "skip the fetch" sense: every mount still calls fetchObjectExtended exactly as before and overwrites the cache entry with the fresh result. Like the localStorage keys above, it's a module-level cache — not cleared on logout or tree switch.

The pane also refetches whenever live sync reports any tree change, not only a change to its own (view, handle). Live sync's normal path only bumps a ViewStore's revision for a change matching that store's own table/handle, which is precise for list views but not enough here: an edit made to some other record embedded in this one's resolved refs (a Note's text, an Event's date pulled in via extend=all/backlinks) wouldn't otherwise trigger a refetch, so it would stay stale until the pane was remounted. Refetching on every tree change trades a little extra polling traffic for correctness, rather than re-deriving which handles are embedded — that shape varies by object type (see Data Model and Editing).

Clone this wiki locally