Add `currentlyResolvable: boolean` per entry in `MeshNodeStatus.optedInStacks`,
derived from the existing alias cache so the new field stays consistent with
`/api/mesh/aliases` without any extra Dockerode or cross-node inspect on the
status path. The Routing tab renders an amber `suspended` pill for entries
whose stack is opted in but currently has no running services, plus a single
explanatory caption below the suspended list.
Resolves the contradictory `Mesh stacks: 1 / Aliases: 0 / No mesh services
on this node yet` copy on the node card when a meshed stack's container has
been stopped; the misleading line is now only shown when the node truly has
no opt-ins. The opt-in itself remains sticky: when the stack starts again,
its aliases reappear automatically on the next refresh.
Defensive de-dup in the UI filters suspended entries against the live alias
snapshot to handle the transient gap where `/mesh/status` and `/mesh/aliases`
return slightly inconsistent views from their separate fetches.
Tests:
- new `mesh-status-resolvability.test.ts` (6 cases) locks the resolvable /
suspended / mixed / empty / per-node-scoping / stale-alias-no-phantom
invariants for `getStatus`.
- `mesh-topology-layout.test.ts` gains a `stacksKey` resolvability-flip case
and a `meshNodeStateEqual` case asserting a resolvability flip on an
otherwise identical stack registers as a state change so the topology
layout re-runs.
Operator docs gain one new troubleshooting accordion in
`/docs/features/sencho-mesh.mdx` explaining the suspended state.
Adds a visibility-aware 30s poll on the Routing tab so the graph reflects
tunnel state, alias publishes, and remote-node disconnects without a
manual reload. Polling pauses while the tab is hidden and wakes
immediately on focus via the shared visibilityInterval helper.
Tightens nodeStateEqual so a stack swap on the same node (opt out A, opt
in B) is detected even when the count is unchanged. Extracts the helper,
the minimap colour mapping, and the minimap colour literals to
mesh-topology-layout so they can be unit-tested and to drop a now-unused
isOwnerView field from MeshNodeData.
Persists the Table/Graph and Tunnels/Aliases toggles to localStorage so a
Routing tab session keeps the operator's last-used view.
Adds a legend line to the per-stack topology sheet clarifying that
consumer edges mean meshed peers that could reach the aliases via DNS;
whether containers dial them depends on each consumer's own opt-in
stacks.
Adds unit tests for stacksKey, meshNodeStateEqual, and miniMapColorFor,
plus troubleshooting entries and a refresh-cadence note to the mesh docs.
Adds a Table/Graph toggle to Fleet → Traffic. Graph mode draws the fleet
as a ReactFlow diagram with a second toggle that switches edge encoding
between Tunnels (one edge per node pair, coloured by tunnel state) and
Aliases (same edges labelled with alias counts). Clicking a node card
opens the existing opt-in sheet, where each opted-in stack now has a
Topology button that opens a focused side sheet showing the stack at
the centre, the aliases it publishes, and the meshed consumer nodes
with per-tunnel state.
Reuses the existing /mesh/status and /mesh/aliases endpoints; no
backend changes. Inherits the Admiral gate from the parent Routing
tab. Layout helpers and the per-stack sheet are unit-tested.