Skip to content

feat: reactivity graph panel - #18

Open
lxsmnsyc wants to merge 5 commits into
mainfrom
reactivity-graph
Open

feat: reactivity graph panel#18
lxsmnsyc wants to merge 5 commits into
mainfrom
reactivity-graph

Conversation

@lxsmnsyc

Copy link
Copy Markdown
Member

Adds a third panel to the dev toolbar that maps the running reactive graph, and a demo app that exercises every panel.

The panel

  • Signals, memos and effects are nodes. An edge points from a source to the computation that reads it.
  • Hovering a node shows its value, type, state, owner path and edge counts.
  • Selecting a node highlights everything upstream and downstream of it, dims the rest, and lists its sources and observers as buttons you can click to walk the graph.
  • Nodes pulse and count changes while the app updates.
  • The graph can be filtered by kind, searched by name, value or owner, paused, panned and zoomed.

It reads the graph through the development hooks in solid-js (DEV.hooks, getSources, getObservers, getChildren, getSignals), so it stays empty in a production build of the runtime, and it installs those hooks only while it is open. Nodes are discovered from hook registrations plus an owner-tree walk from every known root, so nodes created before the panel opened still appear.

Two details worth review:

  • The toolbar wraps the app, so excluding the toolbar's own owner would exclude the app with it. AppScope marks the scope that owns props.children as app code, and the nearest marker wins during the owner walk.
  • Each snapshot carries a fingerprint. An unchanged fingerprint is not written to state, which stops the panel's own renders from feeding it another update.

Internal flag bits from @solidjs/signals (REACTIVE_DIRTY, STATUS_PENDING, EFFECT_USER and friends) are copied into the registry, because the runtime does not export them. Every read is defensive.

The demo

examples/demo is an orders dashboard, run with pnpm demo. It is built with @solidjs/vite-plugin in start mode, so the plugin owns the entries and mounts the toolbar. Signals, memos and effects are named, an async memo calls a "use server" function for the pending state, and a button throws for the error panel.

The demo reports its own server function calls through the exported pushServerFunctionCall, because @solidjs/web 2.0.0-rc.0 does not ship observeServerFunctionCalls yet, which leaves the toolbar's auto-capture inert against that version.

Testing

  • 28 unit tests, including layering, cycle handling, value formatting and the registry's behaviour without a dev runtime.
  • 7 end to end tests, including a new one covering the hover card, selection roles, live values and kind filtering.
  • pnpm build, pnpm typecheck and pnpm format:check are clean.
  • The demo was driven by hand: it serves and hydrates with no console warnings, the graph shows 52 nodes and 38 edges, the server function shows as POST loadRate 200, and the thrown error resolves to a source-mapped frame.

🤖 Generated with Claude Code

lxsmnsyc and others added 2 commits September 11, 2026 21:06
The toolbar gains a third panel that maps the running reactive graph.

- Signals, memos and effects are nodes, and an edge points from a source to the computation that reads it.
- Hovering a node shows its value, type, state, owner path and edge counts.
- Selecting a node highlights everything upstream and downstream of it and lists its sources and observers.
- Nodes pulse and count changes while the app updates.
- The graph can be filtered by kind, searched by name, value or owner, paused, panned and zoomed.

The panel reads the graph through the development hooks in solid-js, so it
stays empty in a production build of the runtime. It only installs those hooks
while it is open. The toolbar marks its own scope so its signals never show up
in the graph, and marks the scope that owns the app so the wrapped app still
does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
examples/demo is an orders dashboard that exercises every toolbar panel.
Run it with `pnpm demo`.

- It is built with @solidjs/vite-plugin in start mode, so the plugin owns the entries and mounts the toolbar.
- Signals, memos and effects are named, so the reactivity graph reads as the app's own data flow.
- An async memo calls a "use server" function, which shows the pending state in the graph and a request and response in the server function panel.
- A button throws so the error panel has a source-mapped frame to show.

The demo reports its own server function calls through `pushServerFunctionCall`,
because @solidjs/web 2.0.0-rc.0 does not report them to the toolbar yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@socket-security

socket-security Bot commented Sep 11, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​solidjs/​vite-plugin@​3.0.0-next.359610010094100

View full report

The reactivity panel showed the selected node's value as pretty printed JSON,
which dropped everything JSON cannot hold. It now uses the same expandable tree
the server function panel uses for serialized values.

- The row primitives move from the seroval viewer to `src/ui/ValueTree.tsx`, so both panels share one look.
- `ValueInspector` walks live values. It handles arrays, maps, sets, typed arrays, errors, DOM nodes, functions and class instances, and marks values that point back up the branch as circular.
- Children are built when a row opens, so a deep object costs nothing until it is inspected.
- Getters are reported, never called, so inspecting a value cannot run app code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lxsmnsyc

Copy link
Copy Markdown
Member Author

Pushed 83162e6: the graph's value pane now uses the same expandable tree as the server function viewer, instead of pretty printed JSON.

  • The row primitives (TreeLeaf, TreeBranch, TreeKey, ValueToken) move out of SerovalViewer.tsx into src/ui/ValueTree.tsx, with their CSS, so both panels share one look. SerovalValue.tsx is gone and the seroval viewer imports the shared pieces.
  • ValueInspector walks live values rather than serialized ones, which is what the graph holds. It covers arrays, maps, sets, typed arrays, errors, DOM nodes, functions and class instances, and marks anything that points back up its own branch as circular.
  • Getters are reported, never called, so opening a row cannot run app code or throw.
  • Rows build their children on open, so a deep object costs nothing until it is inspected.

Covered by 12 new unit tests on the entry helpers and an end to end assertion that expands a node's value and reads its keys.

@lxsmnsyc

Copy link
Copy Markdown
Member Author
Screenshot 2026-09-11 at 9 33 07 PM

lxsmnsyc and others added 2 commits September 12, 2026 01:29
An effect with no sources has no incoming edges, so the layout put it in the
first column together with the signals. That column should be about where data
comes from, and such an effect reads nothing.

Effects with no sources now get a column of their own before the signals, and
every other node shifts one column to the right.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The layout was rebuilt from scratch on every snapshot, so one new node could
reorder a column and shift every other column, moving nodes out from under the
pointer.

- Each layout is seeded with the one it replaces. Nodes keep the slot they had, and the crossing sweeps can no longer reorder them.
- New nodes are inserted where the sweeps put them, so they still land near what they read.
- Columns hang from the top instead of being centred, so a column that grows no longer moves the others.

Adding two rows to the demo now moves 13 of 72 surviving nodes, each by one or
two slots because a new node landed above them, and none of them change order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant