Skip to content

Persist Graph View layout algorithm - #2145

Open
mjuarros wants to merge 9 commits into
aws:mainfrom
mjuarros:save-layout-in-graph-session
Open

mjuarros wants to merge 9 commits into
aws:mainfrom
mjuarros:save-layout-in-graph-session

Conversation

@mjuarros

@mjuarros mjuarros commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Loading a graph from a previous Session or from a saved graph file always came back on the default Layout, so users had to pick their Layout again every time.

User-facing: The Graph View remembers the chosen Layout per browser tab. Restoring the previous Session brings back the Layout it was last shown in. Save Graph records the Layout in the file, and Load Graph applies it. A Session or file saved by an older version keeps the current Layout.

flowchart LR
    Picker[Layout picker] --> GVL["Graph View Layout<br/>(per tab)"]
    Picker -->|graph on screen is the active Session| Session[(Session)]
    GVL --> Save[Save Graph] --> File[Exported Graph File]
    Session -->|Restore, after entities load| GVL
    File -->|Load Graph, after entities load| GVL
Loading

How to read:

  1. core/graphLayout.ts: storedLayoutNameSchema for the per-tab Graph View Layout (missing or unknown falls back to F_COSE), and savedGraphLayoutNameSchema for a Session or file (missing or unknown parses as no Layout, so the current one stays; unknown also warns).
  2. core/StateProvider/graphSession/graphViewLayoutAlgorithm.ts: the writable atom over the Graph View Layout that replaces the in-memory global. Choosing a Layout while the graph on screen is the active Session also updates that Session. It leaves a Session waiting to be restored alone.
  3. core/StateProvider/graphSession/{storage,useUpdateGraphSession,useRestoreGraphSession}.ts: the Session's optional layout, written on update and applied on restore once the entities load.
  4. modules/GraphViewer/{exportedGraph,ExportGraphButton,ImportGraphButton}.ts(x): the optional layout field in the Exported Graph File, written on save and applied on load once the entities load. createExportedGraph now takes an options object.
  5. docs/features/graph-view.md and GLOSSARY.md: updated Layout docs, and a new Exported Graph File term.

Evidence

Before: A restored Session or loaded graph file always showed F_COSE, and a reload reset the Layout.

Session not saved

sessionNoSaveGraphLayout

Export not saved

ExportNoSaveGraph

After:

Session saved

SessionLayoutSave

Export saved

ExportedLayoutSave

Verified live: against a TinkerPop Gremlin Server 3.7.3 container.

  • Chrome: every flow. That covers choosing a Layout and checking it in sessionStorage and IndexedDB, restoring a Session, changing the Layout before and after restoring, saving a file, and loading files and Sessions with a Layout, without one, and with an unknown one. It also covers switching connections without touching the other connection's saved Session.
  • Firefox and Safari: loading a file applies its Layout.

Merge Danger

Door: two-way

Both fields are additive and optional, and the graph file's Format Generation stays at 1. Older builds ignore the new layout key, and this build reads older Sessions and files without one.

Impact: low

Related

Check List

  • I confirm that my contribution is made under the terms of the Apache 2.0 license.
  • I have verified pnpm checks passes with no errors.
  • I have verified pnpm test passes with no failures.
  • I have covered new added functionality with unit tests if necessary.
  • I have updated documentation if necessary.

@mjuarros
mjuarros marked this pull request as ready for review September 8, 2026 16:22
@mjuarros
mjuarros force-pushed the save-layout-in-graph-session branch from 65d4478 to 95811d0 Compare September 17, 2026 19:04

@kmcginnes kmcginnes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks Mario. This needs a rebase and a narrower scope before a detailed review.

Scope: keep this PR to the layout algorithm

I want this PR to cover only #890: save the chosen layout algorithm in the graph file and the previous session, and restore it on load. Please move the exact node positions, viewport pan/zoom, and the restoration transaction (the second commit, the arrangement ADR, and the GLOSSARY.md entry) out of this PR.

Node positions changing between sessions is fine for now. Storing them is a separate and much bigger task, and it should get its own issue and design discussion before any code.

Connector changes: split these out too

Two changes in connector/ aren't about the layout:

  • fetchEntityDetails.ts builds endpoint-only vertices from restored edges. That changes the "not found" counts for every query language.
  • mapSparqlValueToScalar.ts now returns invalid dates as strings, and an existing test was flipped to match.

I'm not convinced either is the right change, but they're worth discussing. Please open them separately. I expect the endpoint-vertex change is really a prerequisite of the arrangement work, so it can go with that discussion.

Rebase notes

Main has moved quite a bit. In particular:

  • #2144 needs its own rebase first (see my comment there); this PR builds on its core/graphLayout.ts.
  • #2245 removed Iterator helpers (.values().map(...), .toArray()) and vitest globals. Use plain loops or Array.from(...), and explicit vitest imports.
  • Connection types such as ConfigurationId and ConnectionWithId now come from @/connections.
  • CONTEXT.md is now GLOSSARY.md.
  • #1894 added per-tab View Layouts (createSessionScopedAtom), which affects where the layout choice lives (next section).

Where the layout choice lives

GLOSSARY.md and the per-tab storage ADR say the chosen Layout belongs in the view's View Layout. Right now liveGraphLayoutAtom is an in-memory global and the Session owns the value. I'd do this instead:

  • Add layoutAlgorithm to the Graph View Layout codec, the same way #2144 does for the Schema View.
  • Back graphViewLayoutAlgorithmAtom with it.
  • Treat the Session and the graph file as holding a snapshot of the choice, and write that snapshot back to the View Layout on restore.

This also fixes a bug in the current version: the live atom is never reset, so after switching connections the next session update writes connection A's layout into connection B's Session.

Validation

  • Graph file import uses z.enum(layoutNames), so a file with a layout this release doesn't know, e.g. one exported from a newer release, fails to import entirely. Fall back to the default instead, the same way stored sessions do, and use one shared schema for both.
  • That makes resolveGraphSessionLayout redundant. Both of its call sites (ImportGraphButton.tsx and useRestoreGraphSession.ts) receive values that are already validated, so its fallback never runs. Remove it and let the zod schema do the work.
  • transformGraphSessions<ConnectionId>: the generic only ever has one type argument (ConfigurationId), so drop it.

For the arrangement follow-up, not this PR

So these don't get lost when that work picks back up:

  • The arrangement schema is defined twice, in storage.ts and in exportedGraph.ts.
  • mergeGraphArrangements makes two extra passes. A Map already keeps insertion order, so Array.from(byId.values()) is enough.
  • Previous-session restore and graph import repeat the same start, fetch, check, populate, commit, and clear sequence. That should be one shared flow.
  • useInitCytoscape tracks the connection with connectionIdRef, layoutTargetRef, dragTargetRef, and a connectionId prop on Graph. GraphViewer already remounts the canvas for each connection with key={config?.id}, so those guards protect against a case that can't happen. Either drop them or drop the key. Also use lodash's DebouncedFunc instead of ReturnType<typeof debounce>.
  • The "is the session empty? then RESET, otherwise set" check appears three times. It could live in activeGraphSessionAtom's setter.

@mjuarros
mjuarros force-pushed the save-layout-in-graph-session branch 2 times, most recently from 9d0fa89 to f2db768 Compare October 6, 2026 20:06
@mjuarros mjuarros changed the title Persist Graph View layout and exact arrangements Persist Graph View layout algorithm Oct 6, 2026
@mjuarros

mjuarros commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks Kris. Rebased onto current main and narrowed to the layout algorithm only. Point by point:

Scope — layout only. Exact node positions, viewport pan/zoom, the restoration transaction, the arrangement ADR, and its glossary entry are out of this PR. They now live in #2374 for the design discussion you asked for. Node positions changing between sessions is the accepted behavior here for now.

Connector changes — split out. Both are gone from this PR. fetchEntityDetails.ts (endpoint-only vertices from restored edges) moved to #2374, since it's a prerequisite of the arrangement work as you suspected. The mapSparqlValueToScalar.ts invalid-date change is noted in #2374 as a separate, unrelated item to evaluate on its own; main's existing test is untouched here.

Where the layout choice lives. Done as suggested: layoutAlgorithm is now part of the Graph View Layout codec (mirroring #2144's Schema View), graphViewLayoutAlgorithmAtom is backed by it, and the Session and graph file hold a snapshot written back to the View Layout on restore. The in-memory global is gone, so the connection-switch leak you flagged is fixed — there's a pinning test for it.

Validation. Import no longer uses z.enum(layoutNames); the graph file and stored sessions now share one layout-name schema that falls back to the default for a missing or unknown value, so a file from a newer release still imports. resolveGraphSessionLayout and the transformGraphSessions generic aren't present on current main, so there was nothing to remove — the shared schema does the work directly.

Rebase notes. All applied: built on the merged #2144 core/graphLayout.ts; no Iterator helpers and explicit vitest imports (#2245); connection types from @/connections; GLOSSARY.md, not CONTEXT.md; and the per-tab View Layout from #1894 is where the choice now lives.

The arrangement follow-up refactors you parked (duplicate schema, mergeGraphArrangements passes, the shared restore flow, the useInitCytoscape guards vs the remount key, DebouncedFunc, the triple empty-session check) are all captured in #2374 so they're not lost. Ready for another look.

@mjuarros
mjuarros force-pushed the save-layout-in-graph-session branch 2 times, most recently from 0111522 to 780c3bc Compare October 7, 2026 01:07
mjuarros and others added 9 commits October 21, 2026 12:32
…iles

Save the chosen Graph View layout algorithm so it is restored when a
graph is loaded from the previous session or an exported graph file.

The layout choice lives in the Graph View Layout codec (mirroring the
Schema View) and is backed by a View Layout atom rather than an
in-memory global, so it persists per browser tab across reloads. The
per-tab View Layout is the single source of truth: session restore
brings back the entities only and never changes the layout, while
importing a graph file applies that file's layout since a file is a
self-contained artifact. One shared schema validates the layout name on
both the file and the stored session, falling back to the default for a
missing (quiet) or unknown (warn) value instead of failing the load.

Storage keys and persisted field names are unchanged; legacy session and
graph data without a layout remain usable.
@kmcginnes
kmcginnes force-pushed the save-layout-in-graph-session branch from 780c3bc to 8f08974 Compare October 22, 2026 15:59

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Save layout choice to saved graph file & previous session

2 participants