carlos / platform Public

console: clamp layout dimensions for an empty graph, pin grouped-record label text

Review fixes on Task 6:

- archlayout.go: layers and maxRows were 0 for a graph with no drawable
  nodes (an ungranted console with no boxes and no inventory, day one),
  driving Width negative (padding*2 + 0*nodeW + (0-1)*gapX = -42) and
  producing an invalid viewBox on the emptiest page rather than the
  busiest. Clamp both to at least 1 before computing Width/Height.
  TestLayoutEmptyGraphHasValidViewBox builds BuildArch(ArchInput{})
  (zero nodes) and asserts positive Width/Height plus a blunt string
  check that viewBox never carries a negative dimension.
- archgraph_test.go: TestArchGroupsRecordsBySameZoneAndTarget now also
  asserts the grouped node's Title ("21 records") and Subtitle (3
  sorted example names + "+18"), pinning the half of Ruling 19 that
  was previously only exercised by node/row counts.

Verified: go build ./..., go vet ./..., go test ./... -count=1 (all 28
packages), gofmt -l internal/console, GOOS=darwin go build ./... — all
clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 649cf60d659aaa33e682ceac642925dcc0d6d74f parent 442323a
3 files changed, +49 −0
  • internal/console/archgraph_test.go +19 −0
  • internal/console/archlayout.go +10 −0
  • internal/console/archlayout_test.go +20 −0
diff --git a/internal/console/archgraph_test.go b/internal/console/archgraph_test.go
index b3dce8c09..272ee4b89 100644
--- a/internal/console/archgraph_test.go
+++ b/internal/console/archgraph_test.go
@@ -425,6 +425,25 @@ func TestArchGroupsRecordsBySameZoneAndTarget(t *testing.T) {
t.Errorf("grouped node must use its lowest-id member's id: %+v", v.Nodes)
}
+ // Ruling 19 asked the grouped node to carry the count AND up to 3
+ // example names — pin the label text, not just the node count.
+ var recordNode *Node
+ for i := range v.Nodes {
+ if v.Nodes[i].Kind == "record" {
+ recordNode = &v.Nodes[i]
+ }
+ }
+ if recordNode == nil {
+ t.Fatal("no record node drawn")
+ }
+ if recordNode.Title != "21 records" {
+ t.Errorf("Title = %q, want %q", recordNode.Title, "21 records")
+ }
+ wantSub := "app00.carloku.com, app01.carloku.com, app02.carloku.com, +18"
+ if recordNode.Subtitle != wantSub {
+ t.Errorf("Subtitle = %q, want %q (3 sorted example names + the remainder)", recordNode.Subtitle, wantSub)
+ }
+
var dnsRows int
for _, g := range v.Groups {
if g.Title == "DNS" {
diff --git a/internal/console/archlayout.go b/internal/console/archlayout.go
index dd37133f5..fa5ddb166 100644
--- a/internal/console/archlayout.go
+++ b/internal/console/archlayout.go
@@ -139,6 +139,16 @@ func layout(v *ArchView) {
layers = l + 1
}
}
+ // An empty graph (no boxes, no inventory yet — an ungranted console on
+ // day one) still has to carry a valid, positive viewBox: it is the
+ // FIRST page an operator sees, not the busiest. Without this clamp
+ // layers==0 and maxRows==0 drive Width/Height negative.
+ if layers < 1 {
+ layers = 1
+ }
+ if maxRows < 1 {
+ maxRows = 1
+ }
v.Width = padding*2 + layers*nodeW + (layers-1)*gapX
v.Height = padding*2 + maxRows*nodeH + (maxRows-1)*gapY
}
diff --git a/internal/console/archlayout_test.go b/internal/console/archlayout_test.go
index d113e5792..579be79fc 100644
--- a/internal/console/archlayout_test.go
+++ b/internal/console/archlayout_test.go
@@ -70,3 +70,23 @@ func TestSVGIsSelfContainedAndEscaped(t *testing.T) {
t.Error("a box label must never reach the SVG unescaped")
}
}
+
+// A deployment with no boxes and no inventory yet — an ungranted console on
+// day one — must still draw a valid, positive viewBox. This is the FIRST
+// page an operator sees after this ships, not a contrived corner case.
+func TestLayoutEmptyGraphHasValidViewBox(t *testing.T) {
+ v := BuildArch(ArchInput{})
+ if len(v.Nodes) != 0 {
+ t.Fatalf("this fixture is expected to draw no nodes at all, got %d", len(v.Nodes))
+ }
+ if v.Width <= 0 || v.Height <= 0 {
+ t.Fatalf("Width/Height must be positive even with zero nodes: %dx%d", v.Width, v.Height)
+ }
+ out := string(RenderArchSVG(v))
+ if !strings.Contains(out, "viewBox=") {
+ t.Error("SVG must carry a viewBox even when the graph is empty")
+ }
+ if strings.Contains(out, `viewBox="0 0 -`) {
+ t.Error("viewBox must never carry a negative dimension")
+ }
+}