From 7834461eb9643194ace76f011a2416b959834848 Mon Sep 17 00:00:00 2001 From: Andraxion Date: Fri, 24 Jul 2026 22:54:19 -0400 Subject: [PATCH] Polish sparse graph navigation --- docs/CONTRACT.md | 11 ++-- docs/MCP_CONTRACT.md | 15 ++--- docs/NEW_PROJECT_QUICKSTART.md | 5 +- pyproject.toml | 2 +- src/docforge/visualization.py | 112 ++++++++++++++++++++++++++++++--- tests/test_mcp_server.py | 2 +- tests/test_visualization.py | 27 +++++++- uv.lock | 2 +- 8 files changed, 148 insertions(+), 28 deletions(-) diff --git a/docs/CONTRACT.md b/docs/CONTRACT.md index 90185a2..a10de25 100644 --- a/docs/CONTRACT.md +++ b/docs/CONTRACT.md @@ -23,7 +23,7 @@ approved contract and measured cross-project evidence; it is not an unimplemente - Result envelope: `schemas/result.schema.json`, version 1. - Changeset schema: `schemas/changeset.schema.json`, version 1. - Index schema: version 1, disposable and reproducible. -- Core, CLI, and MCP server: version 0.8.1. +- Core, CLI, and MCP server: version 0.8.2. Schema files describe the generic interchange contract. Runtime validation remains responsible for path confinement, source hashing, relationship resolution, dependency cycles, project limits, stale @@ -105,11 +105,14 @@ overview, bounded search, exact node content, bounded incoming-and-outgoing neig lease heartbeat. The heartbeat changes no project or index state. There is no write endpoint, arbitrary query endpoint, static filesystem handler, external asset, or project-selection control. -The `graph-browser@5` template provides mouse-wheel zoom centered on the pointer, left-button drag +The `graph-browser@6` template provides mouse-wheel zoom centered on the pointer, left-button drag pan, explicit zoom-in and zoom-out buttons, a reset-view button, and a live zoom percentage. A four-pixel drag threshold defers pointer capture and preserves node activation for ordinary clicks. -Loading another root node resets the viewport so the new neighborhood begins centered and fully -framed. Empty-canvas guidance is hidden whenever a neighborhood is rendered. +Loading another root node fits the viewport to the returned neighborhood, including a useful +minimum scale for a single-node result. The current root begins selected, and activating another +graph node moves the visible selection ring to it. Space centers the viewport on the selected node +without changing zoom. Reset restores the fitted neighborhood view. Empty-canvas guidance is hidden +whenever a neighborhood is rendered. Activating a graph node opens a modal inspector containing that node's complete validated metadata and content. Inspection does not replace the current neighborhood or reset the viewport. The modal diff --git a/docs/MCP_CONTRACT.md b/docs/MCP_CONTRACT.md index 12053d5..a01318b 100644 --- a/docs/MCP_CONTRACT.md +++ b/docs/MCP_CONTRACT.md @@ -61,7 +61,7 @@ only through the explicit local CLI integration command. ## Visualization boundary -`docforge_visualize` starts the fixed built-in `graph-browser@5` template against the currently +`docforge_visualize` starts the fixed built-in `graph-browser@6` template against the currently validated derived index. It may focus one stable node, run one bounded lexical query, or open the project overview. The tool returns a loopback URL and exact snapshot identity. @@ -70,12 +70,13 @@ expression. Its HTTP surface is token-bound, read-only, same-origin, and limited search/filter, node-neighborhood JSON, and a read-only browser-lease heartbeat. The browser exposes an exact validated index snapshot. It rejects index replacement or alteration and requires another MCP invocation to refresh. -Viewport interaction is entirely client-side: wheel zoom, left-button drag pan, explicit zoom -buttons, and reset never request or mutate project data. Graph-node activation fetches exact node -data from the same bounded read endpoint and opens a client-side modal inspector. Replacing the -current root requires the modal's explicit Explore neighborhood action. The open browser renews a -bounded lease in a detached local worker, so standard-input transaction completion does not close -the listener. The worker tracks the longer-lived MCP client host and closes when that owner exits. +Viewport interaction is entirely client-side: fitted neighborhood framing, wheel zoom, left-button +drag pan, explicit zoom buttons, reset, and Space-to-center selection never request or mutate +project data. Graph-node activation visibly selects the node, fetches exact node data from the same +bounded read endpoint, and opens a client-side modal inspector. Replacing the current root requires +the modal's explicit Explore neighborhood action. The open browser renews a bounded lease in a +detached local worker, so standard-input transaction completion does not close the listener. The +worker tracks the longer-lived MCP client host and closes when that owner exits. Explicit service shutdown closes its tracked worker, and abandoned pages expire. ## Excluded tools diff --git a/docs/NEW_PROJECT_QUICKSTART.md b/docs/NEW_PROJECT_QUICKSTART.md index 1fd9dd4..492e026 100644 --- a/docs/NEW_PROJECT_QUICKSTART.md +++ b/docs/NEW_PROJECT_QUICKSTART.md @@ -16,10 +16,11 @@ The DocForge repository contains the complete generic CLI and stdio MCP server. - Project identity, validation, exact-node retrieval, lexical search, and filtering. - Backlinks, dependency traversal, impact traversal, and bounded context profiles. -- `docforge_visualize`, which starts the token-protected, loopback-only `graph-browser@5` viewer. +- `docforge_visualize`, which starts the token-protected, loopback-only `graph-browser@6` viewer. The viewer supports search, family filtering, exact-node inspection, bounded neighborhoods, modal inspection without losing the current neighborhood, explicit neighborhood exploration, - mouse-wheel zoom, left-button drag panning, zoom controls, and viewport reset. + fitted single- and multi-node framing, visible node selection, Space-to-center, mouse-wheel zoom, + left-button drag panning, zoom controls, and viewport reset. - Isolated documentation changesets, proposal validation, diffs, and escaped HTML previews when a proposal writer and render view are configured. diff --git a/pyproject.toml b/pyproject.toml index 4339b8d..50c3633 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "docforge" -version = "0.8.1" +version = "0.8.2" description = "Project-scoped documentation indexing and context service" readme = "README.md" requires-python = ">=3.12" diff --git a/src/docforge/visualization.py b/src/docforge/visualization.py index 8034700..8af4bb0 100644 --- a/src/docforge/visualization.py +++ b/src/docforge/visualization.py @@ -23,7 +23,7 @@ from typing import cast from .errors import DocForgeError from .index import APPLICATION_ID, INDEX_SCHEMA_VERSION, ProjectIndex, re_tokenize -VISUALIZATION_TEMPLATE = "graph-browser@5" +VISUALIZATION_TEMPLATE = "graph-browser@6" DEFAULT_EDGE_LIMIT = 100 MAX_EDGE_LIMIT = 400 DEFAULT_INITIAL_GRACE_SECONDS = 120.0 @@ -1009,9 +1009,22 @@ _GRAPH_BROWSER_HTML = r""" .edge.boundary-edge { stroke: #52718b; stroke-dasharray: 5 4; } .edge-label { fill: #8198ae; font-size: 9px; pointer-events: none; } .node { cursor: pointer; } - .node circle { stroke-width: 1.8; transition: stroke-width .15s, filter .15s; } - .node.root circle { stroke-width: 3; filter: drop-shadow(0 0 8px rgba(81, 215, 255, .24)); } - .node:hover circle { stroke: #fff; stroke-width: 3; } + .node:focus { outline: none; } + .node > circle:not(.selection-ring) { + stroke-width: 1.8; transition: stroke-width .15s, filter .15s; + } + .node.root > circle:not(.selection-ring) { + stroke-width: 3; filter: drop-shadow(0 0 8px rgba(81, 215, 255, .24)); + } + .node:hover > circle:not(.selection-ring) { stroke: #fff; stroke-width: 3; } + .node .selection-ring { + fill: none; stroke: #ffd166; stroke-width: 0; opacity: 0; + pointer-events: none; vector-effect: non-scaling-stroke; + transition: opacity .15s, stroke-width .15s; + } + .node.selected .selection-ring, .node:focus-visible .selection-ring { + stroke-width: 2.5; opacity: 1; filter: drop-shadow(0 0 5px rgba(255, 209, 102, .7)); + } .node text { fill: var(--text); font-size: 10px; pointer-events: none; } .node .family { fill: var(--muted); font-size: 8px; } .empty { @@ -1142,7 +1155,7 @@ _GRAPH_BROWSER_HTML = r""" The project-bound listener is unavailable. Invoke docforge_visualize again.
- Click node to inspect · mouse wheel to zoom · left-drag to pan + Click node to inspect · Space centers selection · mouse wheel zooms · left-drag pans