Skip to content

Conversation

@nvandessel
Copy link
Owner

Summary

  • Adds floop graph --format html that generates a self-contained HTML file with an interactive force-directed graph visualization, then opens it in the default browser
  • Nodes are colored by behavior kind (Catppuccin Mocha palette), sized by PageRank + degree, with hover tooltips and click detail panels
  • Uses force-graph v1.51.1 (MIT, ~177KB) embedded via //go:embed — works offline, no server needed
  • Also updates MCP floop_graph handler to support html format

New CLI flags

floop graph --format html              # Generate HTML, open in browser
floop graph --format html -o graph.html  # Save to specific path
floop graph --format html --no-open    # Generate without opening browser

New files

  • internal/visualization/assets/force-graph.min.js — Embedded JS library
  • internal/visualization/templates/graph.html.tmpl — HTML template with CSS + visualization JS
  • internal/visualization/embed.go//go:embed directives
  • internal/visualization/browser.go — Cross-platform OpenBrowser() (xdg-open/open/cmd)

Modified files

  • internal/visualization/dot.goFormatHTML, EnrichmentData, RenderEnrichedJSON(), RenderHTML(), collectEdges()
  • cmd/floop/cmd_graph.go — HTML format case, --output/-o, --no-open flags, PageRank computation
  • internal/mcp/handlers.go — HTML case in handleFloopGraph
  • internal/mcp/schema.go — Updated format description

Test plan

  • go test ./internal/visualization/... — 12 tests pass (enriched JSON, HTML rendering, browser smoke, edge collection)
  • go test ./... — All 24 packages pass
  • go build ./cmd/floop — Binary compiles with embedded assets
  • floop graph --format html --no-open -o /tmp/test.html — 205KB self-contained HTML generated
  • floop graph --format dot and --format json — Existing formats unchanged
  • Manual: Open generated HTML in browser, verify force-directed layout, hover tooltips, click detail panel, zoom/pan

🤖 Generated with Claude Code

@greptile-apps
Copy link

greptile-apps bot commented Feb 10, 2026

Greptile Overview

Greptile Summary

This PR adds an offline, self-contained interactive HTML graph renderer (force-graph embedded via go:embed) and wires --format html into the floop graph CLI and the MCP floop_graph tool.

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

  • This PR is close to merge-ready but has a few correctness/integration issues affecting HTML opening and MCP output lifecycle.
  • Core rendering approach is straightforward and covered by tests, but the CLI constructs an invalid/non-portable file:// URL and the MCP handler writes temp files without cleanup while the schema docs don’t describe the html output shape, which can break clients or cause resource leaks.
  • cmd/floop/cmd_graph.go, internal/mcp/handlers.go, internal/mcp/schema.go

Important Files Changed

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
Loading

Copy link

@greptile-apps greptile-apps bot left a 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

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link

greptile-apps bot commented Feb 10, 2026

Additional Comments (1)

internal/mcp/schema.go
Schema mismatch for html

FloopGraphOutput.Graph is documented as "DOT string or JSON object", but the handler returns a temp file path string for format: "html". This makes the tool output misleading/incorrect for clients that rely on the schema description; update the schema docstring (and/or type shape) to include the html case (e.g., "DOT string, JSON object, or HTML file path").

@nvandessel nvandessel marked this pull request as draft February 10, 2026 16:30
nvandessel and others added 5 commits February 10, 2026 17:57
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>
@nvandessel nvandessel force-pushed the feat/graph-html-visualization branch from f6abdcc to 8bcd413 Compare February 11, 2026 02:28
@nvandessel
Copy link
Owner Author

@greptile review

@greptile-apps
Copy link

greptile-apps bot commented Feb 11, 2026

Greptile Overview

Greptile Summary

Adds a new HTML output path for the behavior graph: floop graph --format html now renders a self-contained, offline-capable force-directed visualization (force-graph embedded via go:embed) and optionally opens it in the default browser.

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 html format through the MCP floop_graph tool.

Confidence Score: 3/5

  • This PR is close to mergeable, but the HTML renderer currently introduces a concrete stored-XSS/script-injection issue.
  • Core functionality (rendering + embedding assets + CLI/MCP wiring) is straightforward and covered by tests, but RenderHTML injects graph JSON into a <script> tag via template.JS, allowing </script> in stored behavior text to break out and execute attacker-controlled JS when the HTML is opened.
  • internal/visualization/dot.go and internal/visualization/templates/graph.html.tmpl

Important Files Changed

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
Loading

Copy link

@greptile-apps greptile-apps bot left a 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

Edit Code Review Agent Settings | Greptile

Comment on lines 287 to 293
// Execute template
var buf bytes.Buffer
data := htmlTemplateData{
ForceGraphJS: template.JS(jsBytes),
GraphJSON: template.JS(graphJSON),
}
if err := tmpl.Execute(&buf, data); err != nil {
Copy link

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>
@nvandessel nvandessel force-pushed the feat/graph-html-visualization branch from 697d4d1 to 03f1ca6 Compare February 11, 2026 07:19
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