Files
projax/web/templates/graph.tmpl
mAi 3901a1888e 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
2026-05-15 19:06:57 +02:00

50 lines
2.0 KiB
Cheetah

{{define "content"}}
<h1>Graph <small class="muted">{{.Matched}} / {{.Total}} items</small></h1>
<section class="tagbar" id="graph-filterbar">
<form id="graph-filter" class="search"
hx-get="/graph"
hx-target="main"
hx-select="main"
hx-swap="outerHTML"
hx-trigger="change from:select, change from:input[type=checkbox], keyup changed delay:200ms from:input[name=q]"
hx-push-url="true">
<input type="search" name="q" value="{{.Filter.Q}}" placeholder="search…" autocomplete="off">
<label>tag&nbsp;
<select name="tag" multiple size="3">
{{$sel := .Filter.Tags}}
{{range .AllTags}}<option value="{{.}}" {{if contains $sel .}}selected{{end}}>{{.}}</option>{{end}}
</select>
</label>
<label>mgmt&nbsp;
<select name="mgmt" multiple size="4">
{{$selM := .Filter.Management}}
<option value="mai" {{if contains $selM "mai"}}selected{{end}}>mai</option>
<option value="self" {{if contains $selM "self"}}selected{{end}}>self</option>
<option value="external" {{if contains $selM "external"}}selected{{end}}>external</option>
<option value="unmanaged"{{if contains $selM "unmanaged"}}selected{{end}}>unmanaged</option>
</select>
</label>
<label class="checkbox">
<input type="checkbox" name="isolate" value="1" {{if .Isolate}}checked{{end}}>
isolate (hide non-matches)
</label>
{{if .Filter.Active}}<a class="clear" href="/graph">clear filters</a>{{end}}
<a class="download" href="/graph?download=svg">download SVG</a>
</form>
</section>
<section class="graph-canvas">
{{template "graph-svg" .P}}
</section>
<section class="graph-legend muted">
<span class="legend-key key-mai">mai</span>
<span class="legend-key key-self">self</span>
<span class="legend-key key-external">external</span>
<span class="legend-key key-mixed">mixed</span>
<span class="legend-key key-unmanaged">unmanaged</span>
· status opacity: active 1.0 · done 0.6 · archived 0.3
</section>
{{end}}