From efdaa87ee22803d637f4f4c750380baec837e6f9 Mon Sep 17 00:00:00 2001 From: anti Date: Fri, 24 Apr 2026 22:27:40 -0400 Subject: [PATCH] feat(web/mazenet): amber-tint pending LAN placeholders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre: optimistic placeholders for enqueued LAN-add mutations were indistinguishable from regular not-yet-deployed nets — same dim mono chrome, same dotted border. User couldn't tell whether a drop had been queued or had silently failed and re-stacked over an existing LAN. Tag the placeholder with `pending: true`, render it in the same amber the REAP button uses (var(--warn, #e0a040)) with a 'PENDING' chip-mini in the head. Visual is loud enough that there is no chance of confusion with INACTIVE (dimmed) or regular pending-state LANs (mono). Reconciliation is the existing refetch pumping setNets(h.nets) on SSE — no extra plumbing needed; placeholders disappear naturally when the mutator's applied event lands and the canvas re-hydrates from the server. --- decnet_web/src/components/MazeNET/MazeNET.css | 15 +++++++++++++++ decnet_web/src/components/MazeNET/MazeNET.tsx | 2 +- decnet_web/src/components/MazeNET/NetBox.tsx | 11 ++++++++++- decnet_web/src/components/MazeNET/types.ts | 5 +++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/decnet_web/src/components/MazeNET/MazeNET.css b/decnet_web/src/components/MazeNET/MazeNET.css index a8c98492..1f21e5a5 100644 --- a/decnet_web/src/components/MazeNET/MazeNET.css +++ b/decnet_web/src/components/MazeNET/MazeNET.css @@ -199,6 +199,21 @@ body.maze-fullscreen .maze-shell { opacity: 0.42; filter: grayscale(0.7); border-style: dotted; } .maze-net-box.inactive .maze-net-box-head { color: rgba(255, 255, 255, 0.5); } +/* Optimistic placeholder for an enqueued LAN-add. Amber tint matches + * the REAP button voice — clearly "in-flight, not committed" without + * collapsing into the dimmed-out 'inactive' style which means the + * opposite (no traffic on a deployed LAN). */ +.maze-net-box.pending { + border-color: var(--warn, #e0a040); + background: rgba(224, 160, 64, 0.04); + border-style: dashed; + filter: none; opacity: 1; +} +.maze-net-box.pending .maze-net-box-head { + color: var(--warn, #e0a040); + border-bottom-color: rgba(224, 160, 64, 0.45); +} +.maze-net-box.pending .cidr { color: rgba(224, 160, 64, 0.7); } .maze-net-box-head { position: absolute; top: 0; left: 0; right: 0; padding: 6px 12px; border-bottom: 1px dashed var(--border); diff --git a/decnet_web/src/components/MazeNET/MazeNET.tsx b/decnet_web/src/components/MazeNET/MazeNET.tsx index 13dedb99..f2f4d080 100644 --- a/decnet_web/src/components/MazeNET/MazeNET.tsx +++ b/decnet_web/src/components/MazeNET/MazeNET.tsx @@ -138,7 +138,7 @@ const MazeNET: React.FC = () => { setNets((p) => [...p, { id: tempId, name, label: name.toUpperCase(), cidr: subnet ?? '', kind: isDmz ? 'dmz' : 'subnet', - x, y, w, h, + x, y, w, h, pending: true, }]); return; } diff --git a/decnet_web/src/components/MazeNET/NetBox.tsx b/decnet_web/src/components/MazeNET/NetBox.tsx index c8787398..666bb837 100644 --- a/decnet_web/src/components/MazeNET/NetBox.tsx +++ b/decnet_web/src/components/MazeNET/NetBox.tsx @@ -27,6 +27,7 @@ const NetBox: React.FC = ({ dropTarget ? 'drop-target' : '', inactive ? 'inactive' : '', deployed ? 'deployed' : '', + net.pending ? 'pending' : '', ].filter(Boolean).join(' '); const Icon = net.kind === 'internet' ? Globe : net.kind === 'dmz' ? ShieldAlert : GitMerge; @@ -53,12 +54,20 @@ const NetBox: React.FC = ({
{net.label} - {inactive && ( + {inactive && !net.pending && ( INACTIVE )} + {net.pending && ( + + PENDING + + )}
{net.cidr} diff --git a/decnet_web/src/components/MazeNET/types.ts b/decnet_web/src/components/MazeNET/types.ts index c113d238..a25d9e76 100644 --- a/decnet_web/src/components/MazeNET/types.ts +++ b/decnet_web/src/components/MazeNET/types.ts @@ -15,6 +15,11 @@ export interface Net { y: number; w: number; h: number; + /** Optimistic placeholder for an enqueued mutation on a live + * topology. Replaced on next refetch when the mutator emits the + * applied event. Rendered with an amber tint so the user can tell + * it's a queued add, not a regular non-deployed LAN. */ + pending?: boolean; } export type NodeKind = 'decky' | 'observed';