25 KiB
Rack Presentation Improvements Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Make the generated rack elevation interactive (inline SVG, clickable boxes, hover tooltips) with status-encoded borders and a legend, and theme the mermaid power/network graphs (colour-by-kind + clickable nodes).
Architecture: Pure rendering upgrade in scripts/gen_rack.py. New module-level helpers (_host_url, _status_stroke, _stroke_attrs, _placement, _tooltip); render_svg wraps each device in <a>+<title>, encodes status via border, adds a legend, both-gutter U-numbers, and column frames; render_page inlines the SVG (via md_in_html) with a download link; render_power/render_network add per-node style + click. Each task regenerates the artifacts so the drift guard stays green.
Tech Stack: Python 3 (stdlib + PyYAML only), pytest, MkDocs Material (md_in_html, mermaid), Forgejo Actions CI.
Spec: notes/dev/specs/2026-06-24-rack-presentation-design.md.
Global Constraints
- Scripts use stdlib + PyYAML only; deterministic and offline.
re/yamlalready imported — no new imports. - Links/clicks use root-relative final URLs
/hardware/<host>/(mkdocs does not rewrite raw hrefs inside inline SVG or mermaid;use_directory_urlsdefaults true; site at domain root). - Status border mapping (verbatim): in-use
#333333/1.5/solid; staging#333333/1.5/dash4 2; broken#e15759/3/solid; spare#bbbbbb/1.5/solid; donated#bbbbbb/1.5/solid; other →#333333/1.5/solid. Fill stays the kind colour. - The inline elevation block is
<div class="rack-elevation">+ the SVG markup (no blank lines inside) +</div>; nomarkdownattribute (raw passthrough). The standalonerack01-elevation.svgis still generated, plus a[Download SVG](rack01-elevation.svg)link. - Tooltip text:
"<host> · <kind> · <status> · cluster: <cluster|—> · <placement>",_esc-applied; placement = U-range /0U <face>/<shelf>/<face>/slot <slot>. - Reuse
item()/_write_item()/shelf()test helpers; do not redefine them. - Each task ends with regenerated
docs/infrastructure/racks/rack01.{md,svg},make testgreen,mkdocs build --strictpassing, andmake docs-checkexit 0. - No edits to
mkdocs.yml,Makefile,.forgejo/workflows/docs.yml, orscripts/overview_config.yml.
Task 1: Helpers + interactive elevation (inline, links, tooltips, status borders)
Files:
- Modify:
scripts/gen_rack.py(helpers;render_svg<a>/<title>/status borders +style=on<svg>;render_pageinline + download link) - Modify:
tests/test_gen_rack.py(append tests) - Regenerate:
docs/infrastructure/racks/rack01.md,rack01-elevation.svg
Interfaces:
-
Produces (module-level, used by Task 2):
_host_url(host) -> str,_status_stroke(status) -> tuple[str, float, str],_stroke_attrs(status) -> str,_placement(fm) -> str,_tooltip(fm) -> str. -
Step 1: Append failing tests to
tests/test_gen_rack.py
def test_svg_boxes_link_to_host_pages():
items = [item(hostname="srv04", rack_u=5, u_height=1, rack_face="front")]
svg = gen_rack.render_svg("rack01", items)
assert '<a href="/hardware/srv04/">' in svg
assert "<title>" in svg
def test_svg_status_border_styles():
staging = gen_rack.render_svg("rack01", [
item(hostname="a", rack_u=1, u_height=1, rack_face="front",
status="staging")])
broken = gen_rack.render_svg("rack01", [
item(hostname="b", rack_u=1, u_height=1, rack_face="front",
status="broken")])
assert 'stroke-dasharray="4 2"' in staging
assert 'stroke="#e15759"' in broken and 'stroke-width="3"' in broken
def test_svg_tooltip_has_cluster_and_placement():
items = [item(hostname="srv01", rack_u=1, u_height=1, rack_face="front",
status="staging", cluster="tappaas")]
svg = gen_rack.render_svg("rack01", items)
assert "cluster: tappaas" in svg
assert "U1" in svg
def test_svg_has_responsive_style():
svg = gen_rack.render_svg("rack01", [])
assert "max-width:100%" in svg
def test_render_page_inlines_svg_with_download_link():
items = [item(hostname="srv04", rack_u=5, u_height=1, rack_face="front")]
page = gen_rack.render_page("rack01", items)
assert '<div class="rack-elevation">' in page
assert "<svg" in page
assert "[Download SVG](rack01-elevation.svg)" in page
assert "![Rack rack01 elevation]" not in page
- Step 2: Run to verify failure
Run: pytest tests/test_gen_rack.py -q
Expected: FAIL — the new assertions fail (<a href / <title> / max-width / inline <div> not present yet).
- Step 3: Add the helper functions after
_escinscripts/gen_rack.py
Insert immediately after the _esc function (before _sorted_items):
STATUS_STROKE: dict[str, tuple[str, float, str]] = {
"in-use": ("#333333", 1.5, ""),
"staging": ("#333333", 1.5, "4 2"),
"broken": ("#e15759", 3, ""),
"spare": ("#bbbbbb", 1.5, ""),
"donated": ("#bbbbbb", 1.5, ""),
}
DEFAULT_STATUS_STROKE: tuple[str, float, str] = ("#333333", 1.5, "")
def _status_stroke(status: object) -> tuple[str, float, str]:
return STATUS_STROKE.get(status, DEFAULT_STATUS_STROKE)
def _stroke_attrs(status: object) -> str:
stroke, sw, dash = _status_stroke(status)
dash_attr = f' stroke-dasharray="{dash}"' if dash else ""
return f'stroke="{stroke}" stroke-width="{sw}"{dash_attr}'
def _host_url(host: object) -> str:
return f"/hardware/{host}/"
def _placement(fm: dict) -> str:
if "mounted_on" in fm:
return (
f"{fm.get('mounted_on', '?')}/{fm.get('shelf_face', '')}/"
f"slot {fm.get('shelf_slot', '')}"
)
face = fm.get("rack_face")
if face in ZERO_U_FACES:
return f"0U {face}"
u = fm.get("rack_u")
h = fm.get("u_height")
if isinstance(u, int) and isinstance(h, int):
return f"U{u}" if h == 1 else f"U{u}–U{u + h - 1}"
return "?"
def _tooltip(fm: dict) -> str:
host = fm.get("hostname", "?")
return _esc(
f"{host} · {fm.get('kind', '')} · {fm.get('status', '')} · "
f"cluster: {fm.get('cluster', '—')} · {_placement(fm)}"
)
- Step 4: Add the responsive
styleto the<svg>tag inrender_svg
Replace:
p.append(
f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" '
f'height="{height}" viewBox="0 0 {width} {height}" '
f'font-family="sans-serif" font-size="11">'
)
with:
p.append(
f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" '
f'height="{height}" viewBox="0 0 {width} {height}" '
f'style="max-width:100%;height:auto" '
f'font-family="sans-serif" font-size="11">'
)
- Step 5: Wrap
draw_devicein a link + title and use the status border
Replace the whole draw_device function with:
def draw_device(fm: dict, col_x: int) -> None:
u = fm["rack_u"]
h = fm["u_height"]
y = u_y(u)
box_h = h * U_H
color = KIND_COLORS.get(fm.get("kind", ""), DEFAULT_COLOR)
name = fm.get("hostname", "?")
urange = f"U{u}" if h == 1 else f"U{u}–U{u + h - 1}"
p.append(f'<a href="{_host_url(name)}">')
p.append(f"<title>{_tooltip(fm)}</title>")
p.append(
f'<rect x="{col_x + 1}" y="{y + 1}" width="{COL_W - 2}" '
f'height="{box_h - 2}" rx="3" fill="{color}" '
f"{_stroke_attrs(fm.get('status'))}/>"
)
p.append(
f'<text x="{col_x + COL_W // 2}" y="{y + box_h // 2 + 4}" '
f'text-anchor="middle" fill="#ffffff">'
f"{_esc(name)} ({urange})</text>"
)
p.append("</a>")
- Step 6: Wrap
draw_railin a link + title and use the status border
Replace the whole draw_rail function with:
def draw_rail(fm: dict, x: int) -> None:
color = KIND_COLORS.get(fm.get("kind", ""), DEFAULT_COLOR)
name = fm.get("hostname", "?")
cx = x + RAIL_W // 2
cy = top + body_h // 2
p.append(f'<a href="{_host_url(name)}">')
p.append(f"<title>{_tooltip(fm)}</title>")
p.append(
f'<rect x="{x}" y="{top}" width="{RAIL_W}" height="{body_h}" '
f"fill=\"{color}\" {_stroke_attrs(fm.get('status'))}/>"
)
p.append(
f'<text x="{cx}" y="{cy}" text-anchor="middle" fill="#ffffff" '
f'transform="rotate(-90 {cx} {cy})">{_esc(name)}</text>'
)
p.append("</a>")
- Step 7: Make shelf occupants and the shelf strip links in
draw_shelf
Replace the whole draw_shelf function with this version (occupant boxes each link to their host; the two shelf strips + label are one link to the shelf host):
def draw_shelf(fm: dict) -> None:
u = fm["rack_u"]
h = fm["u_height"]
y = u_y(u)
block_h = h * U_H
strip_y = y + block_h - SHELF_STRIP_H
avail_h = block_h - SHELF_STRIP_H
shelf_color = KIND_COLORS.get("shelf", DEFAULT_COLOR)
sname = fm.get("hostname", "?")
for col_x, sface in ((front_x, "front"), (rear_x, "rear")):
occ = sorted(
(m for m in mounted
if m.get("mounted_on") == sname
and m.get("shelf_face") == sface),
key=lambda m: (m.get("shelf_slot", 0), m.get("hostname", "")),
)
n = len(occ)
for idx, m in enumerate(occ):
sub_w = COL_W // n
bx = col_x + idx * sub_w
bw = (COL_W - idx * sub_w) if idx == n - 1 else sub_w
mcolor = KIND_COLORS.get(m.get("kind", ""), DEFAULT_COLOR)
mname = m.get("hostname", "?")
p.append(f'<a href="{_host_url(mname)}">')
p.append(f"<title>{_tooltip(m)}</title>")
p.append(
f'<rect x="{bx + 1}" y="{y + 1}" width="{bw - 2}" '
f'height="{avail_h - 2}" rx="3" fill="{mcolor}" '
f"{_stroke_attrs(m.get('status'))}/>"
)
p.append(
f'<text x="{bx + bw // 2}" y="{y + avail_h // 2 + 4}" '
f'text-anchor="middle" fill="#ffffff">{_esc(mname)}</text>'
)
p.append("</a>")
p.append(f'<a href="{_host_url(sname)}">')
p.append(f"<title>{_tooltip(fm)}</title>")
for col_x in (front_x, rear_x):
p.append(
f'<rect x="{col_x}" y="{strip_y}" width="{COL_W}" '
f'height="{SHELF_STRIP_H}" fill="{shelf_color}" stroke="#333"/>'
)
p.append(
f'<text x="{front_x + COL_W // 2}" y="{strip_y + SHELF_STRIP_H - 1}" '
f'text-anchor="middle" fill="#333" font-size="9">{_esc(sname)}</text>'
)
p.append("</a>")
- Step 8: Inline the SVG in
render_pagewith a download link
In render_page, replace:
lines.append("## Elevation")
lines.append("")
lines.append(f"")
lines.append("")
with:
lines.append("## Elevation")
lines.append("")
lines.append('<div class="rack-elevation">')
lines.append(render_svg(rack, items).rstrip())
lines.append("</div>")
lines.append("")
lines.append(f"[Download SVG]({rack}-elevation.svg)")
lines.append("")
- Step 9: Run to verify pass
Run: pytest tests/test_gen_rack.py -q
Expected: PASS (all prior tests + 5 new).
- Step 10: Regenerate and verify build + drift
Run: make docs-index
Expected: Wrote rack01.md + rack01-elevation.svg (10 item(s)).
Run: grep -c 'href="/hardware/' docs/infrastructure/racks/rack01.md
Expected: ≥ 10 (one link per device box + the shelf).
Run: make test
Expected: PASS.
Run: mkdocs build --strict (or python3 -m mkdocs build --strict)
Expected: build succeeds, no warnings-as-errors.
Run: grep -c '<svg' site/infrastructure/racks/rack01/index.html && grep -c 'href="/hardware/' site/infrastructure/racks/rack01/index.html
Expected: both ≥ 1 — confirms the inline SVG and its links survived md_in_html into the built HTML (not escaped).
Run: make docs-check
Expected: exit 0.
- Step 11: Commit
git add scripts/gen_rack.py tests/test_gen_rack.py docs/infrastructure/racks/
git commit -m "feat(rack): inline interactive elevation with links, tooltips, status borders"
Task 2: Legend, both-gutter U-numbers, column frames
Files:
- Modify:
scripts/gen_rack.py(render_svglayout + legend) - Modify:
tests/test_gen_rack.py(append tests) - Regenerate:
docs/infrastructure/racks/rack01.md,rack01-elevation.svg
Interfaces:
-
Consumes:
_status_stroke(Task 1),KIND_COLORS,DEFAULT_COLOR,_esc. -
Step 1: Append failing tests to
tests/test_gen_rack.py
def test_svg_legend_shows_present_kinds():
items = [item(hostname="sw01", kind="switch", rack_u=10, u_height=1,
rack_face="front")]
svg = gen_rack.render_svg("rack01", items)
assert ">Legend<" in svg
assert ">switch<" in svg
def test_svg_legend_omits_absent_kinds():
items = [item(hostname="sw01", kind="switch", rack_u=10, u_height=1,
rack_face="front")]
svg = gen_rack.render_svg("rack01", items)
assert ">ups<" not in svg
def test_svg_u_numbers_in_both_gutters():
svg = gen_rack.render_svg("rack01", [])
assert 'text-anchor="end"' in svg # left gutter
assert 'text-anchor="start"' in svg # right gutter
def test_svg_has_column_frames():
svg = gen_rack.render_svg("rack01", [])
assert svg.count('fill="none"') >= 2 # one frame per column
- Step 2: Run to verify failure
Run: pytest tests/test_gen_rack.py -q
Expected: FAIL — >Legend<, right-gutter text-anchor="start", and fill="none" frames are not present yet.
- Step 3: Add the
LEGEND_Hconstant and grow the canvas
In render_svg, the constants block currently ends with TITLE_H = 28. Add a legend-band constant right after it:
TITLE_H = 28
LEGEND_H = 56
Then replace the height line:
height = PAD + TITLE_H + body_h + PAD
with:
height = PAD + TITLE_H + body_h + PAD + LEGEND_H
- Step 4: Add a right-side U-number gutter to the layout
Replace:
front_x = PAD + len(left_items) * RAIL_W + LABEL_W
rear_x = front_x + COL_W + GAP
width = rear_x + COL_W + len(right_items) * RAIL_W + PAD
with:
front_x = PAD + len(left_items) * RAIL_W + LABEL_W
rear_x = front_x + COL_W + GAP
right_gutter_x = rear_x + COL_W
width = right_gutter_x + LABEL_W + len(right_items) * RAIL_W + PAD
And replace the right-rail drawing loop:
for idx, fm in enumerate(right_items):
draw_rail(fm, rear_x + COL_W + idx * RAIL_W)
with:
for idx, fm in enumerate(right_items):
draw_rail(fm, right_gutter_x + LABEL_W + idx * RAIL_W)
- Step 5: Draw the column frames and the right-gutter U-numbers
The left-gutter U-number loop currently reads:
# U numbers in the gutter left of the front column.
for u in range(1, RACK_UNITS + 1):
y = u_y(u)
p.append(
f'<text x="{front_x - 4}" y="{y + 14}" text-anchor="end" '
f'fill="#999">{u}</text>'
)
Immediately after that loop, add the right-gutter numbers and the two column frames:
for u in range(1, RACK_UNITS + 1):
y = u_y(u)
p.append(
f'<text x="{right_gutter_x + 4}" y="{y + 14}" text-anchor="start" '
f'fill="#999">{u}</text>'
)
for col_x in (front_x, rear_x):
p.append(
f'<rect x="{col_x}" y="{top}" width="{COL_W}" height="{body_h}" '
f'fill="none" stroke="#999" stroke-width="1.5"/>'
)
- Step 6: Draw the legend before
</svg>
The function currently ends:
for fm in sorted(shelves, key=lambda s: s.get("hostname", "")):
draw_shelf(fm)
p.append("</svg>")
return "\n".join(p) + "\n"
Insert the legend between the shelf loop and p.append("</svg>"):
for fm in sorted(shelves, key=lambda s: s.get("hostname", "")):
draw_shelf(fm)
legend_y = top + body_h + PAD + 8
p.append(
f'<text x="{front_x}" y="{legend_y}" font-weight="bold">Legend</text>'
)
present_kinds = sorted({i.get("kind", "") for i in items if i.get("kind")})
kx = front_x
ky = legend_y + 18
for kind in present_kinds:
color = KIND_COLORS.get(kind, DEFAULT_COLOR)
p.append(
f'<rect x="{kx}" y="{ky - 10}" width="12" height="12" '
f'fill="{color}" stroke="#333"/>'
)
p.append(f'<text x="{kx + 16}" y="{ky}">{_esc(kind)}</text>')
kx += 28 + 7 * len(kind)
sx = front_x
sy = ky + 18
for label in ("in-use", "staging", "broken", "spare"):
stroke, sw, dash = _status_stroke(label)
dash_attr = f' stroke-dasharray="{dash}"' if dash else ""
p.append(
f'<rect x="{sx}" y="{sy - 10}" width="12" height="12" '
f'fill="#ffffff" stroke="{stroke}" stroke-width="{sw}"{dash_attr}/>'
)
p.append(f'<text x="{sx + 16}" y="{sy}">{_esc(label)}</text>')
sx += 28 + 7 * len(label)
p.append("</svg>")
return "\n".join(p) + "\n"
- Step 7: Run to verify pass
Run: pytest tests/test_gen_rack.py -q
Expected: PASS (all prior tests + 4 new).
- Step 8: Regenerate and verify build + drift
Run: make docs-index
Expected: Wrote rack01.md + rack01-elevation.svg (10 item(s)).
Run: grep -c ">Legend<" docs/infrastructure/racks/rack01-elevation.svg
Expected: 1.
Run: make test
Expected: PASS.
Run: mkdocs build --strict (or python3 -m mkdocs build --strict)
Expected: build succeeds.
Run: make docs-check
Expected: exit 0.
- Step 9: Commit
git add scripts/gen_rack.py tests/test_gen_rack.py docs/infrastructure/racks/
git commit -m "feat(rack): add elevation legend, both-gutter U-numbers, column frames"
Task 3: Mermaid node colours + clickable nodes
Files:
- Modify:
scripts/gen_rack.py(render_power,render_network) - Modify:
tests/test_gen_rack.py(append tests) - Regenerate:
docs/infrastructure/racks/rack01.md
Interfaces:
- Consumes:
_node_id,_host_url(Task 1),KIND_COLORS,DEFAULT_COLOR.
Note on mermaid
clickinteractivity. Node colouring (style …) always works. Whether aclickactually navigates depends on Material's mermaidsecurityLevel(defaultstrictmay render the link inert) — and this phase makes nomkdocs.ymlchange. Theclickdirective still parses and the graph still renders either way; the tests only assert the directives are emitted. In Step 6, confirm the graphs still render as diagrams (not broken code blocks). If clicks turn out inert in the built site, that is acceptable for this phase (the SVG elevation already provides clickable navigation) — note it in the report rather than adding config.
- Step 1: Append failing tests to
tests/test_gen_rack.py
def test_power_graph_colors_and_links_nodes():
items = [
item(hostname="pdu01", kind="pdu", rack_face="left", outlets=8),
item(hostname="srv01", rack_u=1, u_height=1, rack_face="front",
power=[{"pdu": "pdu01", "outlet": 1}]),
]
out = gen_rack.render_power("rack01", items)
assert "style srv01 fill:" in out
assert "style pdu01 fill:" in out
assert 'click srv01 "/hardware/srv01/"' in out
def test_network_graph_colors_and_links_nodes():
items = [
item(hostname="sw01", kind="switch", rack_u=10, u_height=1,
rack_face="front", ports=24),
item(hostname="srv01", rack_u=1, u_height=1, rack_face="front",
links=[{"local": "eth0", "peer": "sw01", "peer_port": 1}]),
]
out = gen_rack.render_network("rack01", items)
assert "style sw01 fill:" in out
assert 'click sw01 "/hardware/sw01/"' in out
assert 'click srv01 "/hardware/srv01/"' in out
- Step 2: Run to verify failure
Run: pytest tests/test_gen_rack.py -q
Expected: FAIL — style …/click … lines are not emitted yet.
- Step 3: Emit node colours + clicks in
render_power
In render_power, the function currently ends:
for pdu, outlet, device in edges:
lines.append(
f" {_node_id(pdu)} -->|outlet {outlet}| {_node_id(device)}"
)
lines.append("```")
return "\n".join(lines) + "\n"
Insert a styling/click block between the edge loop and lines.append("```"):
for pdu, outlet, device in edges:
lines.append(
f" {_node_id(pdu)} -->|outlet {outlet}| {_node_id(device)}"
)
by_host = {fm.get("hostname"): fm for fm in items}
node_hosts = sorted(set(pdus) | {fm.get("hostname", "?") for fm in powered})
for host in node_hosts:
kind = by_host.get(host, {}).get("kind", "")
color = KIND_COLORS.get(kind, DEFAULT_COLOR)
nid = _node_id(host)
lines.append(
f" style {nid} fill:{color},stroke:#333,color:#ffffff"
)
lines.append(f' click {nid} "{_host_url(host)}"')
lines.append("```")
return "\n".join(lines) + "\n"
- Step 4: Emit node colours + clicks in
render_network
In render_network, the function currently ends:
for source, local, peer, peer_port, speed in edges:
label = f"{local} → p{peer_port}"
if speed is not None:
label += f" · {speed}G"
lines.append(f" {_node_id(source)} -->|{label}| {_node_id(peer)}")
lines.append("```")
return "\n".join(lines) + "\n"
Insert a styling/click block between the edge loop and lines.append("```"):
for source, local, peer, peer_port, speed in edges:
label = f"{local} → p{peer_port}"
if speed is not None:
label += f" · {speed}G"
lines.append(f" {_node_id(source)} -->|{label}| {_node_id(peer)}")
for host in sorted(nodes):
kind = by_host.get(host, {}).get("kind", "")
color = KIND_COLORS.get(kind, DEFAULT_COLOR)
nid = _node_id(host)
lines.append(
f" style {nid} fill:{color},stroke:#333,color:#ffffff"
)
if host in by_host:
lines.append(f' click {nid} "{_host_url(host)}"')
lines.append("```")
return "\n".join(lines) + "\n"
(by_host already exists in render_network; off-rack peers — not in by_host — get a default-coloured node and no click, per the spec.)
- Step 5: Run to verify pass
Run: pytest tests/test_gen_rack.py -q
Expected: PASS (all prior tests + 2 new).
- Step 6: Regenerate and verify build + drift
Run: make docs-index
Expected: Wrote rack01.md + rack01-elevation.svg (10 item(s)).
Run: grep -c "click srv01" docs/infrastructure/racks/rack01.md
Expected: 2 (one in the power graph, one in the network graph).
Run: make test
Expected: PASS.
Run: mkdocs build --strict (or python3 -m mkdocs build --strict)
Expected: build succeeds.
Run: grep -c 'class="mermaid"' site/infrastructure/racks/rack01/index.html
Expected: ≥ 2 — confirms both graphs still render as diagrams after adding style/click (i.e. the directives did not break mermaid parsing).
Run: make docs-check
Expected: exit 0.
- Step 7: Commit
git add scripts/gen_rack.py tests/test_gen_rack.py docs/infrastructure/racks/
git commit -m "feat(rack): colour and link mermaid power/network nodes by kind"
Self-Review
Spec coverage (2026-06-24-rack-presentation-design.md):
- A. Inline SVG + clickable boxes + tooltips + standalone .svg + download link + responsive style — Task 1. ✔
- B. Status border encoding (table verbatim) — Task 1 (
_status_stroke/_stroke_attrs, applied in draw_device/draw_rail/draw_shelf). ✔ - C. Legend (present kinds + status key) — Task 2. ✔
- D. U-numbers both gutters, column frame — Task 2; mermaid node colours + clicks — Task 3. ✔
- Root-relative
/hardware/<host>/URLs — Task 1 (_host_url), used in SVG links and mermaid clicks. ✔ - md_in_html raw passthrough (
<div class="rack-elevation">, nomarkdownattr) — Task 1 Step 8, verified Task 1 Step 10 (built HTML contains<svg/href). ✔ - No mkdocs/Makefile/CI/overview_config changes; occupancy + validation unchanged — honored. ✔
Placeholder scan: No "TBD"/"handle edge cases"/"similar to Task N". Legend spacing uses a deterministic width heuristic (28 + 7*len(label)), not a placeholder.
Type consistency: _status_stroke -> tuple[str, float, str]; _stroke_attrs/_host_url/_placement/_tooltip -> str; render_svg/render_page/render_power/render_network -> str. _status_stroke defined in Task 1 is reused by Task 2's legend; _host_url defined in Task 1 is reused by Task 3's clicks. by_host/nodes/pdus/powered names in the mermaid edits match the existing function locals. Each task regenerates the artifacts so drift stays clean at every boundary.