-
Notifications
You must be signed in to change notification settings - Fork 0
feat: interactive force-directed HTML graph visualization #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Greptile OverviewGreptile SummaryThis PR adds an offline, self-contained interactive HTML graph renderer (force-graph embedded via The visualization is generated from the existing behavior graph JSON, optionally enriched with PageRank scores for node sizing, rendered through an embedded HTML template + embedded JS asset, then either printed (dot/json) or written to disk (html) and optionally opened in a browser. Confidence Score: 3/5
|
| Filename | Overview |
|---|---|
| cmd/floop/cmd_graph.go | Adds html graph output with optional write-to-file and browser open; current file URL construction uses "file://"+path which is invalid on Windows. |
| internal/mcp/handlers.go | Adds html format support to floop_graph by rendering HTML to a temp file; leaves temp files behind (no cleanup) and returns file path in Graph. |
| internal/mcp/schema.go | Updates format description to include html, but output schema still describes Graph as only DOT string or JSON object (html returns a file path string). |
| internal/visualization/assets/LICENSE-force-graph | Adds vendored force-graph license text for embedded JS asset. |
| internal/visualization/assets/force-graph.min.js | Adds embedded vendored force-graph minified JS library used for offline HTML rendering. |
| internal/visualization/browser.go | Adds cross-platform OpenBrowser helper using xdg-open/open/cmd start; correctness depends on callers passing a valid URL. |
| internal/visualization/browser_test.go | Adds compile/supported-platform smoke tests for OpenBrowser (does not actually open a browser). |
| internal/visualization/dot.go | Adds HTML rendering pipeline (RenderEnrichedJSON, RenderHTML) and shared collectEdges; enrichment logic relies on RenderJSON node slice type. |
| internal/visualization/dot_test.go | Adds tests for enriched JSON, HTML rendering, and edge collection; includes checks that may be brittle (size-based HTML assertion). |
| internal/visualization/embed.go | Embeds visualization assets and templates via //go:embed for offline HTML generation. |
| internal/visualization/templates/graph.html.tmpl | Adds self-contained HTML template with force-graph visualization, tooltip, and detail panel; uses innerHTML only with escaped content. |
Sequence Diagram
sequenceDiagram
participant User
participant CLI as floop CLI
participant GS as GraphStore
participant Rank as ranking.ComputePageRank
participant Viz as visualization.RenderHTML
participant Assets as go:embed assets/templates
participant FS as OS filesystem
participant Browser as OpenBrowser
participant MCP as MCP Server
alt CLI: floop graph --format html
User->>CLI: run graph command
CLI->>GS: openStoreForGraph(root)
CLI->>Rank: ComputePageRank(ctx, GS)
CLI->>Viz: RenderHTML(ctx, GS, enrichment)
Viz->>GS: QueryNodes / GetEdges
Viz->>Assets: ReadFile(force-graph.min.js, graph.html.tmpl)
Viz-->>CLI: htmlBytes
CLI->>FS: WriteFile(outPath, htmlBytes)
opt --no-open not set
CLI->>Browser: OpenBrowser(file://...)
end
else MCP: floop_graph format=html
User->>MCP: CallTool floop_graph
MCP->>Viz: RenderHTML(ctx, store, enrichment)
Viz->>Assets: ReadFile(force-graph.min.js, graph.html.tmpl)
Viz-->>MCP: htmlBytes
MCP->>FS: CreateTemp + Write(htmlBytes)
MCP-->>User: Graph=tempFilePath
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
11 files reviewed, 3 comments
Additional Comments (1)
|
Add RenderHTML() and RenderEnrichedJSON() to the visualization package, producing a self-contained HTML file with an embedded force-directed graph. Uses force-graph v1.51.1 (MIT) via go:embed for offline-capable, zero-server visualization. Nodes are colored by behavior kind (Catppuccin Mocha palette), sized by PageRank + degree, with hover tooltips and click detail panels. Includes OpenBrowser() cross-platform utility, embedded assets/templates, and comprehensive tests for enriched JSON, HTML rendering, and edge collection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add --format html, --output/-o, and --no-open flags to floop graph command. Computes PageRank for node sizing, generates self-contained HTML, writes to file, and opens in default browser. MCP floop_graph handler also supports html format using cached PageRank scores. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Much stronger charge repulsion (-1000 - 20*N) to spread nodes apart - Very weak link force (0.02) so edges don't pull everything into a blob - Smaller node radii (sqrt(val)*2.5) to reduce overlap - Labels only render at very high zoom (>8x), keeping overview clean - Brighter edge color and wider strokes for visibility - Zoom-to-fit capped at 1x to ensure overview perspective - Multiple zoom-to-fit calls as simulation settles Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Detail panel now shows canonical behavior content when clicking a node - Edge colors match their kind (blue=requires, red=overrides/conflicts, green=learned-from, purple=merged-into, gray=similar-to) - Added merged-into edge type to legend and styling - Increased link strength (0.02→0.08) so connected nodes cluster better - Added test for canonical content in enriched JSON - Fix click event: debounce background click to prevent panel dismissal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Return HTML content directly as string instead of writing to temp file in MCP handler, eliminating orphaned temp file accumulation - Populate EdgeCount in MCP HTML output (was defaulting to 0) - Pass file path directly to OpenBrowser instead of constructing fragile file:// URL that breaks on paths with spaces - Export CollectEdges for reuse by MCP handler Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
f6abdcc to
8bcd413
Compare
|
@greptile review |
Greptile OverviewGreptile SummaryAdds a new HTML output path for the behavior graph: The visualization pipeline builds enriched node/edge JSON (including optional PageRank scores and canonical content for a detail panel), injects it into an embedded HTML template, and exposes the same Confidence Score: 3/5
|
| Filename | Overview |
|---|---|
| cmd/floop/cmd_graph.go | Adds html graph output path/flags, renders embedded HTML via visualization.RenderHTML, and optionally opens browser. |
| internal/mcp/handlers.go | Extends floop_graph tool to support html format and returns rendered HTML plus node/edge counts. |
| internal/mcp/schema.go | Updates schema description to include html format for floop_graph. |
| internal/visualization/assets/LICENSE-force-graph | Adds embedded license file for force-graph dependency. |
| internal/visualization/assets/force-graph.min.js | Adds embedded force-graph minified JS asset used by HTML renderer. |
| internal/visualization/browser.go | Adds cross-platform OpenBrowser helper using xdg-open/open/cmd start; prior review noted file:// URL correctness concerns. |
| internal/visualization/browser_test.go | Adds compilation/supported-platform smoke tests for OpenBrowser. |
| internal/visualization/dot.go | Adds HTML rendering pipeline (enriched JSON + template + embedded JS) and CollectEdges; uses template.JS for GraphJSON which enables script-breakout XSS. |
| internal/visualization/dot_test.go | Adds tests for enriched JSON, HTML rendering, and CollectEdges; does not cover script-breakout escaping edge cases. |
| internal/visualization/embed.go | Adds go:embed directives for assets and templates. |
| internal/visualization/templates/graph.html.tmpl | Adds self-contained HTML template with force-graph visualization and detail panel; consumes raw injected JS/JSON from Go templates. |
Sequence Diagram
sequenceDiagram
participant User
participant CLI as floop CLI
participant Store as GraphStore
participant Rank as ranking.ComputePageRank
participant Viz as visualization.RenderHTML
participant Embed as embed.FS assets/templates
participant Browser as visualization.OpenBrowser
User->>CLI: floop graph --format html [-o path] [--no-open]
CLI->>Store: openStoreForGraph(root)
CLI->>Rank: ComputePageRank(ctx, Store, config)
Rank-->>CLI: map[behaviorID]score
CLI->>Viz: RenderHTML(ctx, Store, enrichment)
Viz->>Store: QueryNodes(kind=behavior)
Viz->>Store: GetEdges(outbound, all)
Viz->>Embed: ReadFile(assets/force-graph.min.js)
Viz->>Embed: ReadFile(templates/graph.html.tmpl)
Viz-->>CLI: HTML bytes (self-contained)
CLI->>CLI: WriteFile(outPath, htmlBytes)
alt no-open = false
CLI->>Browser: OpenBrowser(outPath)
Browser-->>CLI: cmd.Start()
end
Note over Viz,Embed: Template injects ForceGraphJS + GraphJSON into <script> tags
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
11 files reviewed, 1 comment
internal/visualization/dot.go
Outdated
| // Execute template | ||
| var buf bytes.Buffer | ||
| data := htmlTemplateData{ | ||
| ForceGraphJS: template.JS(jsBytes), | ||
| GraphJSON: template.JS(graphJSON), | ||
| } | ||
| if err := tmpl.Execute(&buf, data); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script-breakout XSS risk
RenderHTML injects graphJSON into a <script> block via template.JS(graphJSON). Since graphJSON includes stored behavior strings (e.g., canonical from the graph store), a value containing </script> will terminate the script tag and allow arbitrary HTML/JS injection when the generated HTML is opened. This is a real stored-XSS vector for anyone viewing the graph. Consider embedding JSON as escaped text (e.g., <script type="application/json"> + json.HTMLEscape) and then JSON.parse(...), or otherwise ensure </script> can’t appear in the injected script context.
- Eliminate template.JS usage that triggered gosec G203 XSS warnings:
- ForceGraph library: load via <script src="data:..."> base64 URI
instead of inline injection (template.URL, trusted embedded asset)
- Graph JSON: HTML-escape via json.HTMLEscape before injection
(template.HTML, neutralizes </script> breakout from user content)
- Remove function nil-check that triggered staticcheck SA4031
- Update tests to match new embedding strategy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
697d4d1 to
03f1ca6
Compare
Summary
floop graph --format htmlthat generates a self-contained HTML file with an interactive force-directed graph visualization, then opens it in the default browser//go:embed— works offline, no server neededfloop_graphhandler to supporthtmlformatNew CLI flags
New files
internal/visualization/assets/force-graph.min.js— Embedded JS libraryinternal/visualization/templates/graph.html.tmpl— HTML template with CSS + visualization JSinternal/visualization/embed.go—//go:embeddirectivesinternal/visualization/browser.go— Cross-platformOpenBrowser()(xdg-open/open/cmd)Modified files
internal/visualization/dot.go—FormatHTML,EnrichmentData,RenderEnrichedJSON(),RenderHTML(),collectEdges()cmd/floop/cmd_graph.go— HTML format case,--output/-o,--no-openflags, PageRank computationinternal/mcp/handlers.go— HTML case inhandleFloopGraphinternal/mcp/schema.go— Updated format descriptionTest plan
go test ./internal/visualization/...— 12 tests pass (enriched JSON, HTML rendering, browser smoke, edge collection)go test ./...— All 24 packages passgo build ./cmd/floop— Binary compiles with embedded assetsfloop graph --format html --no-open -o /tmp/test.html— 205KB self-contained HTML generatedfloop graph --format dotand--format json— Existing formats unchanged🤖 Generated with Claude Code