feat(app): mesh bands — kind-columns, draggable, shared headers

Remote machines' deployments now sit in the same kind-columns as local (so a
cross-node edge runs straight down its column), are draggable with persisted
positions, and the lane headers lift above the topmost band so they head every
machine's columns.
This commit is contained in:
2026-07-06 17:30:22 -07:00
parent ee7543b0f7
commit 79efb006f5

View File

@@ -93,7 +93,6 @@ const PROTO_COLOR: Record<string, string> = {
system: "#6e7681", system: "#6e7681",
} }
const EXTERNAL_X = 1450 // external resources (references) — beyond Internet const EXTERNAL_X = 1450 // external resources (references) — beyond Internet
const MESH_Y = -150 // band above the local lanes for other machines' deployments
const HANDLE = { opacity: 0, width: 8, height: 8 } as const const HANDLE = { opacity: 0, width: 8, height: 8 } as const
@@ -491,33 +490,35 @@ export function SystemMapPage() {
}) })
}) })
// Other machines (mesh-discovered) — a read-only band above the local lanes, // Other machines (mesh-discovered). Each is a band above the local lanes, with
// one row per node, its deployments laid out horizontally. // its deployments in the SAME kind-columns as local, draggable, and the shared
// lane headers lifted above the topmost band so they head every machine.
const byMachine = new Map<string, MeshDeployment[]>() const byMachine = new Map<string, MeshDeployment[]>()
for (const md of meshResp?.deployments ?? []) { for (const md of meshResp?.deployments ?? []) {
const arr = byMachine.get(md.node) ?? [] const arr = byMachine.get(md.node) ?? []
arr.push(md) arr.push(md)
byMachine.set(md.node, arr) byMachine.set(md.node, arr)
} }
let mrow = 0 let bandBaseline = TOP - 70 // first band sits just above local; bands stack upward
let headersY = TOP - 40 // rises above the bands when machines are present
for (const [machine, deps] of byMachine) { for (const [machine, deps] of byMachine) {
const y = MESH_Y - mrow * (ROW_H + 8) const perLaneM: Record<string, number> = {}
nodes.push({ const laid = deps
id: `__machine_${machine}__`, .filter((md) => LANES[md.kind])
type: "lane", .map((md) => {
position: { x: PROG_X, y: y - 4 }, const lane = LANES[md.kind]
selectable: false, const row = (perLaneM[md.kind] = (perLaneM[md.kind] ?? 0) + 1) - 1
draggable: false, return { md, x: lane.x, row }
deletable: false, })
data: { label: `${machine}` }, const rows = Math.max(1, ...Object.values(perLaneM))
}) const bandTop = bandBaseline - (rows - 1) * ROW_H
deps.forEach((md, i) => { for (const { md, x, row } of laid) {
nodes.push({ nodes.push({
id: `__remote_${machine}_${md.name}__`, id: `__remote_${machine}_${md.name}__`,
type: "remote", type: "remote",
position: { x: i * 150, y }, position: { x, y: bandTop + row * ROW_H },
selectable: false, selectable: true,
draggable: false, draggable: true,
deletable: false, deletable: false,
data: { data: {
label: md.name, label: md.name,
@@ -525,8 +526,18 @@ export function SystemMapPage() {
sub: md.endpoints[0] ? `:${md.endpoints[0].port}` : undefined, sub: md.endpoints[0] ? `:${md.endpoints[0].port}` : undefined,
}, },
}) })
}
nodes.push({
id: `__machine_${machine}__`,
type: "lane",
position: { x: PROG_X, y: bandTop },
selectable: false,
draggable: false,
deletable: false,
data: { label: `${machine}` },
}) })
mrow++ headersY = Math.min(headersY, bandTop - 44)
bandBaseline = bandTop - (ROW_H + 44) // gap before the next machine (upward)
} }
const maxRows = Math.max(1, ...Object.values(perLane)) const maxRows = Math.max(1, ...Object.values(perLane))
@@ -557,7 +568,7 @@ export function SystemMapPage() {
nodes.push({ nodes.push({
id: `__lane_${title}__`, id: `__lane_${title}__`,
type: "lane", type: "lane",
position: { x, y: TOP - 40 }, position: { x, y: headersY },
data: { label: title }, data: { label: title },
selectable: false, selectable: false,
draggable: false, draggable: false,