Impl agent memory graph second-brain UI - #294
Merged
Merged
Conversation
Searchable Source/Target relationship pickers (SearchableSelect) plus an animated force-directed "second brain" graph view for the agent memory dialog, replacing the top-down tree layout.
10 tasks: searchable Source/Target pickers, pure-TS layout helpers, a requestAnimationFrame force simulation hosted inside the existing Vue Flow context, circle-rim edge routing, hover-dim, cluster legend, a connections panel, and doc updates. Includes a position-preservation fix (capture live positions before every graph reload) needed because Vue Flow's node reconciliation overwrites position on every prop change.
Replace the native Source/Target <select> elements in the "New relationship" form with the SearchableSelect combobox already used for node operations, so large entity lists are searchable.
Degree count, radius-by-degree, stable blue-family cluster color by entity type, and deterministic cluster-anchored seed positions. Not wired up yet.
Renderless AgentMemoryGraphForceSim runs a link-spring + repulsion + centering + cluster-gravity + collision simulation via requestAnimationFrame, ticking node.position through Vue Flow's own updateNode (skips nodes currently being dragged). FlowPane hosts it and forwards reheat()/snapshotPositions()/focusNode(id); dragging a node reheats the sim so neighbors resettle. Not wired to the graph data yet.
Nodes become colored circles (radius by connection count, color by entity-type cluster) with captions below, driven by the force simulation instead of the old top-down tree layout. Node positions are captured before every reload so drag/settle state survives graph refreshes. Edges route circle-rim to circle-rim with a fainter, blue-tinted stroke. Hovering a node dims unrelated nodes and edges; a cluster legend lists entity-type colors.
Selecting a node now shows its outgoing and incoming relationships in the detail panel, each clickable to select+center that node. "Tidy layout" and the label-visibility toggle no longer remount Vue Flow (flowLayoutEpoch removed) — tidy now just reheats the force simulation so the existing arrangement resettles instead of jumping back to a fresh seed layout.
Root cause of the reported clutter: relationship labels have no inter-label collision avoidance, so with several edges near each other their curvature-offset label positions can coincidentally land almost exactly on top of one another (verified: two labels measured ~1.5px apart in a 4-node repro) — confirmed independent of overall node spacing (widening the force-sim distances made it worse, not better, so that was reverted). Extending the existing hover-dim mechanism to also key off the selected node means clicking a node now fades everything not directly connected to it, which resolves the practical case (the one the report screenshotted) and is also the requested "fade out others on click" behavior.
…chain highlight Per follow-up feedback on the second-brain view: - Hover is now fully inert (removed the hover tooltip and hover-driven dim/select machinery); clicking a node already opens the full detail panel, so hover added a second, redundant interaction mode. - Selecting a node now animates its directly-connected edges with a flowing stroke-dashoffset highlight in the primary accent color, instead of a static highlight — relationship labels are hidden by default and only rendered for that active chain, which also removes the labels that were competing for the same screen space. - Circles are smaller (radius 11-22px, was 16-34px) and edge curvature is much flatter (0.02-0.15, was 0.12-0.4) so lines read as straight rather than bowed/spiral, and increased simulation damping (0.7, was 0.82) so nodes settle without oscillating.
Per follow-up feedback: - Replace the looping "marching ants" chain highlight with a one-shot SVG line-draw animation: selecting a node makes its edges' purple overlay grow from the selected node outward (reversed for edges where the selection is the target), and drain back the same way on deselect. Root-caused an apparent "never stops" artifact during investigation to Playwright's getComputedStyle polling racing with Vue's render flush — verified via direct internal-state tracing that the actual animation is a clean one-shot 0->1 over ~450ms with no restart loop. - Default (non-active) edges are now solid, thin, and subtle instead of dashed blue, matching the requested network-hairline look; removed vue-flow's `animated: true` flag, which was injecting its own default dashed marching-ants CSS that fought this styling.
…ox center Root cause (found via precise DOM measurement, not guessing): Vue Flow's computedPosition is the node box's top-left corner. The old node template stacked the circle and its caption in a flex column, so whenever a caption was wider than the circle (frequently, since captions can be up to 110px and circles are only 22-44px), the node's own box grew wider/taller than the circle to fit it — meaning computedPosition-as-circle-center was off by up to half the caption's width, consistently reproducing the reported "lines floating, disconnected from the circles" look. Verified this was not a timing/staleness issue: the gap persisted unchanged after 20 full seconds of guaranteed quiescence, and did not correlate with node position freshness, but disappeared entirely (down to floating-point noise) once the node's own box was fixed to exactly the circle's diameter. Fix: flowNodes now sets each node's width/height to its circle's diameter, so Vue Flow's box exactly matches the circle; the caption is now an absolutely-positioned overlay below the box instead of a flex sibling that grows it. Edges compute the circle's true center as computedPosition + radius on both axes. Also removed a leftover `width: auto !important` CSS override (needed for the old content-sized layout) that would otherwise have collapsed the node's new explicit width. Also, per follow-up feedback: - Selecting a node now toggles: clicking the same node again deselects it and plays the drain animation in reverse. - Relationship labels only fade in once the fill animation reaches the target node, instead of appearing immediately on selection.
- Compact (labelsHidden) mode now also shrinks the circles themselves (0.55x scale, floor 4px), not just hiding captions, so pins read as small markers instead of full-size circles with nothing to fill the extra space. flowNodes now depends on labelsHidden, so toggleLabels() captures live positions first (same fix as loadGraph()) to avoid snapping nodes back to a stale layout. - Hovering a node in compact mode shows a name/type/attributes popover, since there's no caption to identify a plain dot by; hover stays fully inert in normal mode, unchanged. - Clicking an edge that's part of the selected node's active chain now deselects (playing the drain animation in reverse), mirroring clicking the selected node again — wired via a new edge-click event forwarded through AgentMemoryGraphFlowPane.
…er captions Root cause of "deselect just instantly drops instead of animating reversed": confirmed via mount/unmount instrumentation that Vue Flow recreates the edge component on every selection change (its slot-based custom-edge render path has early `return null` branches). The previous fix attempted to persist reveal progress in a Map declared at the top of <script setup> — but <script setup> content runs inside setup(), so that Map was itself being recreated fresh on every remount, exactly like the ref it was meant to protect. It's now declared in a genuine module-scope sibling <script> block, so a deselect correctly finds start=1 and animates down to 0 instead of finding a fresh always-zero start with nothing to animate. Also: - Dimmed nodes/edges fade much further (0.25->0.1, 0.15->0.06) for a starker selected-vs-rest contrast. - Node captions are bigger and bolder (10px medium -> 12px semibold) with a text-shadow for contrast against the dotted background, and the active-chain edge label similarly bumped, so the graph reads without zooming in.
… selection - Deselecting a node now keeps everyone else faded for the same REVEAL_DURATION_MS the drain animation takes (via a new dimmingNodeId that lags behind selectedNodeId only on deselect), so other entities' color restores once the animation actually finishes instead of snapping back instantly underneath it. active/growFromEnd still track the real selection immediately so the animation itself isn't delayed. REVEAL_DURATION_MS is now exported from AgentMemoryGraphEdge.vue as the single source of truth for both the animation and this delay. - Removed the top-right entity-type color legend chip entirely. - Default fit-view padding on open tightened (0.18 -> 0.08) so the graph reads bigger by default while everything still fits. - Selecting a node (canvas click or a Connections-panel row) now also fits+centers the camera on that node plus its direct neighbors (focusOnNode generalized to focusOnNodes(ids)), instead of leaving the camera wherever it was.
… to canvas, isolate hovered relation Selecting a memory-graph node now widens its incident links' rest length in the force simulation so crowded hub neighbors spread apart instead of relying on a cosmetic label overlay. Node captions flip above the circle when the active relationship label would otherwise land below it. All nodes are clamped to a 10% safe area inset from the canvas edges so the wider spacing can't push them under the toolbar buttons. Hovering one active edge now hides sibling active edges/labels so overlapping relationship labels near a hub can be read one at a time.
…de labels Hovering an edge only sets hoveredEdgeId when that edge actually belongs to the current selection's chain, so hovering an unrelated background edge (or hovering anything with nothing selected) no longer hides the selected hub's labels. Hover also now hides only the relationship-label chip, not the underlying hairline or purple reveal line, which stay visible.
Captions are absolutely positioned outside their node's own box (above/below the circle) so they can reach into the space where connecting edges and labels sit, and Vue Flow's node layer paints above the edges' SVG layer. With no pointer-events-none, a caption sitting on top of a nearby edge silently ate the hover, so the edge's mouseenter (and the hide-siblings behavior) never fired — reproduced by sampling real points along each edge's curve and checking elementFromPoint, which showed the caption intercepting the hit right at the neighbor end of several edges.
Hovering a node's circle now shows the name/type/attributes popover regardless of compact mode, giving a quick attribute peek without opening the edit panel. Suppressed while any node is selected so it doesn't compete with the selected chain's own highlighting, relation labels, and hover-isolate behavior; the popover is also cleared immediately when a selection starts, so one already open from the node just clicked can't linger.
…g leaf captions Node hover now only shows the attributes popover in two cases: any node in compact (pin) mode, since captions are hidden there entirely, or a selected hub's leaf (neighbor) node in normal mode. Hovering the hub itself, an unrelated node, or anything with no selection stays inert. Hovering a leaf also hides the other leaves' name captions (not the hub's own), mirroring the edge hover-isolate treatment of relationship labels, so a crowded hub's names don't compete with the one being inspected. Root-caused a bug in this pass: selectedNeighborIds intentionally includes the hub itself (for the existing dim/fade behavior), so reusing it directly for the leaf check made hovering the hub incorrectly trigger the popover and hid the hub's own caption. Added selectedLeafIds (hub excluded) for this feature instead.
…tions Hovering a selected hub's leaf now reuses the same hoveredEdgeId mechanism as hovering that leaf's edge directly (via the new edgeIdBetween lookup), so it hides sibling relationship-label chips while leaving every node's own name caption alone. The previous pass hid the other leaves' captions instead, which wasn't the intended target — the crowding this addresses is in the relation labels, not the node names.
Vue Flow's own DOM order paints .vue-flow__nodes (node captions included) above .vue-flow__edge-labels, and neither layer sets a z-index — so a dimmed background node's caption that happened to land on top of an active relationship label would bleed through it instead of sitting behind it (e.g. selecting Ada, whose "going_to" label landed on Volvo's caption). Both layers are pointer-events:none, so raising the labels layer's z-index above nodes is purely visual and doesn't affect clicking/dragging nodes underneath.
…show A selected hub's leaf only pops up its attribute popover on hover when it has 2+ attributes — with 0 or 1, the popover would show little beyond what the caption already says, so it's not worth it. The relationship-label isolation still applies regardless of attribute count, since that's an unrelated benefit. Compact mode is untouched: it always shows the popover for any node since that's the only way to identify a pin there at all.
mbakgun
enabled auto-merge (squash)
July 7, 2026 21:11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Searchable Source/Target relationship pickers (SearchableSelect) plus
an animated force-directed "second brain" graph view for the agent
memory dialog, replacing the top-down tree layout.