feat(phase 3f graph): visual /graph view, server-rendered SVG, layered DAG

- internal/graph package: pure-Go layered top-down DAG layout
  - LayerByLongestPath (multi-parent sits at max(parent-layer)+1)
  - OrderInLayer (slug-sort, deterministic)
  - Compute returns positions + edges + canvas size
  - cycle-safe (depth-cap)
- web/graph.go handler: filter chips reused from tree_filter
  - dim mode default (opacity 0.15 on non-matches)
  - ?isolate=1 hides non-matches + prunes orphaned edges
  - ?download=svg serves raw SVG attachment
- graph_svg.tmpl renders inline SVG: border colour by management
  (mai blue / self green / external orange / mixed dashed purple),
  opacity by status, tag pills, ×N multi-parent badge, click-navigate
- nav adds "graph" link; design.md §"Graph view" documents the surface
- 4 integration tests cover render, dim, isolate, SVG download
- 6 layout unit tests cover layering, ordering, cycle-guard
This commit is contained in:
mAi
2026-05-15 19:06:57 +02:00
parent b10ecf1e85
commit 3901a1888e
10 changed files with 951 additions and 0 deletions

View File

@@ -217,3 +217,21 @@ table.bulk .chip-add input { padding: 1px 4px; font-size: 0.85em; width: 7em; }
background: var(--bg-alt); color: var(--muted); border: 1px solid var(--border);
}
.dashboard .issue-row .upd { font-size: 0.8em; }
/* --- /graph --- */
.graph-canvas { overflow: auto; border: 1px solid var(--border); margin-top: 12px; background: #fafafa; }
.graph-svg { display: block; }
#graph-filterbar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
#graph-filterbar input[type=search] { width: 22em; }
#graph-filterbar select[multiple] { min-width: 9em; }
#graph-filterbar .download { color: var(--accent); margin-left: auto; }
.graph-legend { margin: 8px 0; font-size: 0.85em; }
.graph-legend .legend-key {
display: inline-block; padding: 2px 8px; border-radius: 3px;
border: 2px solid; margin-right: 4px; font-family: ui-monospace, monospace; font-size: 0.85em;
}
.graph-legend .key-mai { border-color: #2563eb; color: #2563eb; }
.graph-legend .key-self { border-color: #15803d; color: #15803d; }
.graph-legend .key-external { border-color: #ea580c; color: #ea580c; }
.graph-legend .key-mixed { border-color: #7c3aed; color: #7c3aed; border-style: dashed; }
.graph-legend .key-unmanaged { border-color: #9ca3af; color: #9ca3af; }