Skip to content

fix(web): say why a zoom level rendered as another one - #133

Closed
lex00 wants to merge 1 commit into
mainfrom
fix/131-say-why-a-zoom-level-is-empty
Closed

fix(web): say why a zoom level rendered as another one#133
lex00 wants to merge 1 commit into
mainfrom
fix/131-say-why-a-zoom-level-is-empty

Conversation

@lex00

@lex00 lex00 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Closes #131. Separate from #132 (the on-screen picker) — that one is a design call, this one is a straight bug.

The problem

Four of six zoom levels rendered as one of the other two, silently:

zoom nodes svg sha1
composites 11 14860 20edb391
resources 11 14860 20edb391
attributes 11 14980 cdfcb2f8
logical 1 2763 34916166

composites and resources byte-identical; with an env, runtime and attributes likewise. Reproduces on this repo's own example-k8s, so it is not a property of the project being viewed.

Every fallback is correct and stays

  • composites is resources plus overlaid component-dependency edges — no component graph, no edges, same picture
  • runtime descends to owner-referenced children, and an estate whose children live in AWS rather than the cluster has none
  • logical is a cloud-topology lens (Non-AWS substrate parity for live/drift/logical/reconcile layers (tracking) #74) and nests kinds a Kubernetes estate does not declare

What is not defensible is that a limited view and a working view were indistinguishable. The server reports what it fell back to; the SPA renders it above the graph as a note, not an error card, because nothing failed.

Verified against a live mixed-substrate estate (k3d + floci + the KubeMicroVM operator, 11 resources):

composites  no component dependencies to overlay — this is the resources graph
runtime     no owner-referenced children observed — this is the resources graph
logical     logical kept 1 of 11 resources — it is a cloud-topology lens,
            and the rest are kinds it does not nest (behold#74)
resources   (no note)

That last line matters as much as the others — the level that is not degraded stays quiet.

Two details worth review

The composites catch {} stops being bare. It swallowed every failure identically, so an unavailable component DAG and a genuinely empty one were the same silence. They now say different things.

logicalDegraded() is quiet when the projection kept most of the graph (after * 3 >= before). A note on every logical view would be noise, and noise is how a real signal stops being read. The threshold is a judgement call and I would rather it were argued with than inherited.

The runtime check counts nodes across attachRuntimeContainment rather than probing for a marker — the tier's job is to bring children in, so "did it add any" is the actual question, and it survives however those children come to be tagged.

Testing

npx tsc --noEmit clean; npm test 433 passing across 33 files. Behaviour verified by querying every level against the live estate above, including the negative case.

Closes #131.

Four of six zoom levels rendered as one of the other two with nothing on
screen saying so. Stepping through them showed the same picture repeatedly,
which reads as a broken picker — that is how it was reported.

Each fallback is correct and stays. COMPOSITES is RESOURCES plus overlaid
component-dependency edges, so no component graph means no edges and the
same picture. RUNTIME descends to owner-referenced children, and an estate
whose children live in AWS rather than the cluster has none. LOGICAL is a
cloud-topology lens (#74) and nests kinds a Kubernetes estate does not
declare.

What was not defensible is that a limited view and a working view were
indistinguishable. The server now reports what it fell back to and the SPA
renders it above the graph:

  composites  no component dependencies to overlay — this is the resources graph
  runtime     no owner-referenced children observed — this is the resources graph
  logical     logical kept 1 of 11 resources — it is a cloud-topology lens,
              and the rest are kinds it does not nest (behold#74)

A note, not an error card: nothing failed.

The composites catch also stops being bare. It swallowed every failure
identically, so an unavailable component DAG and a genuinely empty one
produced the same silence; they now produce different notes.

logicalDegraded() stays quiet when the projection kept most of the graph.
A note on every logical view is noise, and noise is how a real signal
stops being read.

The runtime check counts nodes across attachRuntimeContainment rather than
looking for a marker: the tier's job is to bring children in, so "did it
add any" is the question, and it survives however they come to be tagged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TCV5kwB1jdnWH9if9ZbNXq
lex00 added a commit that referenced this pull request Aug 5, 2026
…t it is empty (#140)

Ported from #133, which had this and #139 -- the version I merged -- did not.
Recording that plainly: #133 was open first and I implemented #131 without
checking, so the better reading of this one case shipped second.

#139 noted logical only when it projected zero nodes. The quiet failure is the
partial one: a lens that keeps 1 of 11 renders a plausible-looking diagram of
almost nothing, which reads as "this is your estate" rather than "no data".
Empty at least reads as absence.

`logicalKept` is #133's function under a different name, threshold and message
intact. Its reasoning is kept verbatim because it is the right reasoning: a
note on every logical view would be noise, and noise is how a real signal stops
being read. The `* 3` cut-off is a judgement call and is better argued with
than inherited.

Both call sites now count before projecting, so the note can say what was
dropped. Callers that cannot count keep the empty-only check -- notesFor takes
the input count as optional and prefers it when present.

Verified against a live pure-k8s estate:

  logical projected nothing from 11 resources -- it is a cloud-topology lens,
  and this estate declares none of the kinds it nests (behold#74)

against #139's flatter "logical is an AWS projection -- no AWS resources in
this estate". The count is the part that tells you it looked at something.

462 tests (7 new), tsc and build clean.


Claude-Session: https://claude.ai/code/session_012n8PKN49z8c6jZVA9QYwvk

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lex00

lex00 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Superseded, and not on its own merits — recording what happened.

This was open first. I implemented #131 without checking for an existing PR, merged it as #139, and put this branch into conflict. That was avoidable and I should have looked.

Everything here is now on main:

#140's commit and PR both say where it came from. The * 3 cut-off is noted as yours and as a judgement call worth arguing with.

One thing #139 added that this did not: a note for an estate with nodes and no edges, which is the flat-row case (fountain-ops#84).

Closing as superseded rather than stale.

@lex00 lex00 closed this Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A zoom level that cannot render falls back to another one silently, so the picker looks broken

1 participant