DAG Visualization Styling Reference
This document captures the exact styling specifications for the DAG (Directed Acyclic Graph) visualization, derived from the prototype HTML at docs/penpot-exports/html/audittrail-prototype.html. It serves as the single source of truth for any future DAG-related styling work.
Prototype Reference
The prototype renders the DAG using D3.js + dagre layout directly in SVG. The production implementation uses React Flow (@xyflow/react) + dagre (@dagrejs/dagre) with custom React node components. Both must produce visually identical output.
Edge Styling
Curve Type
| Property | Prototype (D3) | Production (React Flow) |
|---|---|---|
| Curve | Cubic bezier: M x1,y1 C x1,midY x2,midY x2,y2 | type: "default" (bezier) |
| Stroke width | 2 | strokeWidth: 2 |
| Stroke opacity | 0.4 (normal), 0.7 (highlighted), 0.05 (dimmed) | strokeOpacity: 0.4 / 0.7 / 0.05 |
| Stroke color | statusColors[targetNode.status] | EDGE_STATUS_COLOR[targetStatus] |
Important: The prototype uses cubic bezier curves, NOT stepped/angular paths. React Flow's type: "smoothstep" produces right-angle paths with rounded corners — this is visually wrong. Always use type: "default" which renders true bezier curves matching the prototype.
Arrow Markers
| Property | Prototype (D3) | Production (React Flow) |
|---|---|---|
| Shape | Open V-shape (unfilled, two lines) | MarkerType.Arrow (open V) |
| Size | 4px wide, 6px tall | width: 10, height: 10 |
| Stroke | Same color as edge, stroke-width: 1.5, stroke-opacity: 0.5 | strokeWidth: 1.5, color matches edge |
Important: Use MarkerType.Arrow (open/unfilled), NOT MarkerType.ArrowClosed (filled triangle). The prototype draws an open V-shape chevron, not a solid triangle.
Edge Color by Target Node Status
ok / success / complete -> #22c55e (green)
error -> #f87171 (red)
warning / running -> #eab308 (amber)
default -> #444444 (dark gray)
Highlighting Behavior
- Node click: Walk ancestors + descendants, set connected edges to
strokeOpacity: 0.7, non-connected to0.05 - Focus errors: Walk error node ancestors, set connected edges to
strokeOpacity: 0.8withstroke: #f87171, non-connected to0.03 - Reset: All edges return to
strokeOpacity: 0.4with original status color
Node Styling
Shared Structure (all 4 node types)
+---+--------------------------------------+
| | [BADGE] Node Name [*] | <- * = status dot
| A | 12ms $0.002 | <- A = left accent bar
| C | gpt-4o | <- model (LLM only)
+---+--------------------------------------+
| Property | Value |
|---|---|
| Width | 200px |
| Height | Auto (content-driven), dagre uses 72px estimate |
| Border radius | 8px |
| Background | #1a1a2e |
| Border | 2px solid <statusColor> at 0.8 opacity |
| Left border | Set to 0 width (accent bar covers it) |
| Left accent bar | 4px wide, full height, type color, border-radius: 8px 0 0 8px |
| Content padding | 10px 14px 10px 18px |
Node Border Opacity
The prototype sets stroke-opacity: 0.8 on node borders. Production must use rgba equivalents:
ok / success / complete -> rgba(34, 197, 94, 0.8)
error -> rgba(248, 113, 113, 0.8)
warning / running -> rgba(234, 179, 8, 0.8)
default -> rgba(99, 99, 102, 0.8)
When selected, the border switches to the type color at full opacity with a glow shadow.
Type Badge
| Property | Value |
|---|---|
| Padding | 1px 6px (production) / 2px 6px (prototype) |
| Border radius | 4px |
| Font size | 10px (production) / 9px (prototype) |
| Font weight | 500 (production) / 700 (prototype) |
| Text transform | uppercase |
| Font family | Inter, sans-serif |
| Background | Type color at 0.2 opacity |
| Text color | Type color |
Type Colors
LLM -> #60a5fa (blue)
Tool -> #c084fc (purple)
Agent -> #2dd4bf (teal)
Internal -> #636366 (gray)
Status Dot
| Property | Value |
|---|---|
| Size | 8px diameter (production) / 6px (prototype node), 8px (prototype detail) |
| Position | Right side of badge row, margin-left: auto |
| Shape | Circle (border-radius: 50%) |
| Color | Status color (same as border, full opacity) |
Node Name
| Property | Value |
|---|---|
| Font size | 13px |
| Font weight | 400 |
| Color | #fafafa |
| Overflow | text-overflow: ellipsis, white-space: nowrap |
| Max chars | Truncate at 22 chars in prototype |
Subtitle (Duration / Cost / Model)
| Property | Value |
|---|---|
| Font size | 11px (duration/cost), 10px (model) |
| Color | #a1a1a1 (duration/cost), #636366 (model) |
| Font family | Inter, sans-serif |
React Flow Handles (connection points)
| Property | Value |
|---|---|
| Size | 8px x 8px |
| Background | Type color |
| Border | 2px solid #1a1a2e (matches node background) |
| Position | Top (target), Bottom (source) |
Canvas
| Property | Value |
|---|---|
| Background color | #0a0a0a |
| Background pattern | Dots, gap: 20, size: 1, color: #222 |
| Min zoom | 0.1 (prototype: 0.2) |
| Max zoom | 2 (prototype: 3) |
| Color mode | dark |
| Fit view padding | 0.15 |
MiniMap
| Property | Value |
|---|---|
| Position | bottom-right |
| Background | #111 |
| Border | 1px solid #333 |
| Border radius | 8px |
| Mask color | rgba(0, 0, 0, 0.6) |
| Node colors | Same as type colors (LLM=#60a5fa, Tool=#c084fc, Agent=#2dd4bf, Internal=#636366) |
Tooltip (on hover)
Matches prototype .d3-tooltip class exactly.
| Property | Value |
|---|---|
| Background | #1c1c1e |
| Border | 1px solid #333333 |
| Border radius | 8px |
| Padding | 10px 14px |
| Font size | 12px |
| Text color | #fafafa |
| Max width | 280px (production) / 320px (prototype) |
| Box shadow | 0 8px 24px rgba(0,0,0,0.5) |
| Z-index | 9999 |
| Pointer events | none |
Tooltip Content
- Title:
font-weight: 600,font-size: 13px,margin-bottom: 6px - Row labels:
color: #a1a1a1 - Row values:
font-family: monospace,font-size: 11px,color: #a855f7(purple) - Type value color: Matches type color (LLM=#60a5fa, Tool=#c084fc, Agent=#2dd4bf, Internal=#636366)
- Status value color: Matches status color
Dagre Layout Configuration
| Property | Value |
|---|---|
| Direction | TB (top to bottom) |
| Node separation | 40px |
| Rank separation | 60px |
| Margin X | 20px (production) / 40px (prototype) |
| Margin Y | 20px (production) / 40px (prototype) |
| Node width estimate | 200px (matches node width) |
| Node height estimate | 72px |
Controls Panel
| Property | Value |
|---|---|
| Position | absolute top-3 right-3 z-10 |
| Container | rounded-lg border border-[#333] bg-[#171717]/90 p-1 backdrop-blur-sm |
| Buttons | DAG label, Fit View, Focus Errors, Collapse All |
| Error button | text-[#f87171] hover:text-[#f87171] hover:bg-[#f87171]/10 |
Bug History
Edge curves rendering as angular steps (fixed 2026-03-30)
Symptom: DAG edges appeared as right-angle stepped paths instead of smooth curves.
Root cause: React Flow edge type was set to "smoothstep" which renders orthogonal paths with rounded corners. The prototype uses cubic bezier curves (C command in SVG path data).
Fix: Changed type: "smoothstep" to type: "default" in apiEdgeToFlowEdge() in dag-viewer.tsx. React Flow's "default" edge type renders true bezier curves matching the prototype.
Edge arrows rendering as filled triangles (fixed 2026-03-30)
Symptom: Arrow markers at edge endpoints were solid filled triangles instead of open V-shapes.
Root cause: MarkerType.ArrowClosed renders a filled triangle. The prototype draws an open chevron using two lines.
Fix: Changed MarkerType.ArrowClosed to MarkerType.Arrow with strokeWidth: 1.5.
Node borders at full opacity (fixed 2026-03-30)
Symptom: Node borders appeared slightly heavier/brighter than prototype.
Root cause: Border used raw hex colors at full opacity. The prototype applies stroke-opacity: 0.8 to all node borders.
Fix: Added statusBorder lookup map with rgba values at 0.8 alpha to all 4 node components (llm-node, tool-node, agent-node, internal-node).
Files
| File | Purpose |
|---|---|
apps/web/components/dag/dag-viewer.tsx | Main DAG component, edge conversion, layout, highlighting |
apps/web/components/dag/nodes/llm-node.tsx | LLM node rendering |
apps/web/components/dag/nodes/tool-node.tsx | Tool node rendering |
apps/web/components/dag/nodes/agent-node.tsx | Agent node rendering |
apps/web/components/dag/nodes/internal-node.tsx | Internal node rendering |
apps/web/components/dag/dag-controls.tsx | DAG toolbar (Fit View, Focus Errors, etc.) |
apps/web/hooks/use-dag-data.ts | API hook + TypeScript interfaces for DAG data |
apps/api/src/audittrail/dag.py | Backend DAG builder (node/edge generation from spans) |
docs/penpot-exports/html/audittrail-prototype.html | Prototype source of truth (lines 2155-2268) |