# Mesh connections
> The devices of a private network and how this device reaches each one, directly or through a relay; upgrades let go of the relay with a wobble.
- React: `import { GeoMesh } from "@/components/lumesec/geo-mesh"`
- Collection: Location (https://elements.lumesec.ai/components/location)
- Data: takes your data (`data`); shows demo data until you pass it
- Registry item: https://elements.lumesec.ai/r/geo-mesh.json
- Page: https://elements.lumesec.ai/components/location/mesh



Live preview: https://elements.lumesec.ai/view/geo-mesh

Demo source:

```tsx
"use client";

import * as React from "react";

import { GeoMesh, type MeshDevice, type MeshPath, type MeshRelay } from "@/components/lumesec/geo-mesh";
import { distanceKm } from "@/lib/lumesec/geo";
import { demoLatency } from "@/lib/lumesec/geo-data";
import { useInView } from "@/lib/lumesec/motion";

const DEVICE: MeshDevice = { id: "laptop", label: "Work laptop", location: "Amsterdam", lat: 52.3676, lon: 4.9041 };

const PEERS: readonly MeshDevice[] = [
  { id: "build", label: "Build server", location: "Milan", lat: 45.4642, lon: 9.19 },
  { id: "nas", label: "NAS", location: "Dublin", lat: 53.3498, lon: -6.2603 },
  { id: "phone", label: "Phone", location: "Madrid", lat: 40.4168, lon: -3.7038 },
  { id: "desktop", label: "Desktop", location: "Toronto", lat: 43.6532, lon: -79.3832 },
  { id: "ci", label: "CI runner", location: "Chicago", lat: 41.8781, lon: -87.6298 },
  { id: "staging", label: "Staging server", location: "Helsinki", lat: 60.1699, lon: 24.9384 },
];

const RELAYS: readonly MeshRelay[] = [
  { id: "lhr", label: "London", lat: 51.5072, lon: -0.1276 },
  { id: "iad", label: "Ashburn", lat: 39.0438, lon: -77.4874 },
  { id: "waw", label: "Warsaw", lat: 52.2297, lon: 21.0122 },
];

const TRAFFIC: Readonly<Record<string, number>> = { build: 0.8, nas: 0.45, phone: 0.2, desktop: 0.35, ci: 0.65, staging: 0.3 };

/** The order the peers open direct paths in. The phone's network never allows one, so it stays relayed. */
const UPGRADES = ["desktop", "build", "staging", "ci", "nas"];
/** The direct peer that falls back to its relay for 3 s in each 14 s round. */
const FALLBACKS = ["ci", "build", "desktop", "staging", "nas"];
const FIRST_MS = 1500;
const STEP_MS = 1100;
const ROUND_MS = 14000;
const FALLBACK_MS = 3000;

/** A peer's home relay, the one nearest to it: its connections run through it until a direct path opens. */
function homeRelay(peer: MeshDevice) {
  return RELAYS.reduce((best, relay) => (distanceKm(peer, relay) < distanceKm(peer, best) ? relay : best));
}

function pathFor(peer: MeshDevice, direct: boolean): MeshPath {
  const traffic = TRAFFIC[peer.id];
  if (direct) return { peer: peer.id, relay: null, ms: demoLatency(DEVICE, peer), traffic };
  const relay = homeRelay(peer);
  return { peer: peer.id, relay: relay.id, ms: demoLatency(DEVICE, relay) + demoLatency(relay, peer) + 4, traffic };
}

/** The peers that are direct at a time in ms since the last reconnect. */
function directAt(time: number): ReadonlySet<string> {
  const direct = new Set(UPGRADES.filter((_, i) => time >= FIRST_MS + i * STEP_MS));
  const round = Math.floor(time / ROUND_MS);
  if (round >= 1 && time < round * ROUND_MS + FALLBACK_MS) direct.delete(FALLBACKS[(round - 1) % FALLBACKS.length]);
  return direct;
}

/** The next time after `time` at which a path changes. */
function nextChange(time: number) {
  const round = Math.floor(time / ROUND_MS);
  const events = [...UPGRADES.map((_, i) => FIRST_MS + i * STEP_MS), round * ROUND_MS + FALLBACK_MS, (round + 1) * ROUND_MS];
  return Math.min(...events.filter((event) => event > time));
}

export default function GeoMeshDemo() {
  const rootRef = React.useRef<HTMLDivElement>(null);
  const inView = useInView(rootRef, "80px");
  // simulated connection events on a clock that pauses off screen
  const [time, setTime] = React.useState(0);

  React.useEffect(() => {
    if (!inView) return;
    const next = nextChange(time);
    const timer = window.setTimeout(() => setTime(next), next - time);
    return () => window.clearTimeout(timer);
  }, [inView, time]);

  const paths = React.useMemo(() => {
    const direct = directAt(time);
    return PEERS.map((peer) => pathFor(peer, direct.has(peer.id)));
  }, [time]);

  return (
    <div ref={rootRef} className="mx-auto grid w-full max-w-[660px] gap-3">
      <GeoMesh device={DEVICE} peers={PEERS} relays={RELAYS} paths={paths} />
      <div className="flex items-center gap-3">
        <button
          type="button"
          onClick={() => setTime(0)}
          className="h-[30px] cursor-pointer rounded-lg border border-border bg-card px-[11px] font-[inherit] text-[12.5px] font-medium text-foreground outline-none transition-colors hover:border-[color-mix(in_srgb,var(--lumesec)_50%,var(--border))] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-lumesec motion-reduce:transition-none"
        >
          Reconnect all
        </button>
        <span className="ml-auto text-[12px] text-muted-foreground">Demo data</span>
      </div>
    </div>
  );
}
```

> **Your data:** Pass this device as `device`, the other devices as `peers`, your relay servers as `relays` and the current path to each peer as `paths`. Without `peers` and `paths` the component shows a demo laptop in Amsterdam with 6 demo peers, 3 demo relays and invented latencies, and plays a seeded sequence of upgrades and fallbacks. See [Your data](/docs/data).

## Playground

Change a prop and the component re-renders. Props marked remounts set an initial value, so the component starts over.

## Installation

```bash
npx shadcn@latest add @lumesec/geo-mesh
```

First time with the @lumesec registry? Register it once, or install by URL:

```bash
npx shadcn@latest registry add @lumesec=https://elements.lumesec.ai/r/{name}.json
npx shadcn@latest add https://elements.lumesec.ai/r/geo-mesh.json
```

## Usage

React:

```tsx
"use client";

import * as React from "react";

import { GeoMesh, type MeshDevice, type MeshPath, type MeshRelay } from "@/components/lumesec/geo-mesh";

const device: MeshDevice = { id: "laptop", label: "Work laptop", location: "Amsterdam", lat: 52.37, lon: 4.9 };

const peers: MeshDevice[] = [
  { id: "build", label: "Build server", location: "Milan", lat: 45.46, lon: 9.19 },
  { id: "desktop", label: "Desktop", location: "Toronto", lat: 43.65, lon: -79.38 },
];

const relays: MeshRelay[] = [
  { id: "lhr", label: "London", lat: 51.51, lon: -0.13 },
  { id: "iad", label: "Ashburn", lat: 39.04, lon: -77.49 },
];

export function Example({ paths }: { paths: MeshPath[] }) {
  const [peer, setPeer] = React.useState<string | null>(null);
  return <GeoMesh device={device} peers={peers} relays={relays} paths={paths} value={peer} onValueChange={setPeer} />;
}
```

## Behaviour

The devices of a private network and how this device reaches each of them: directly, or through a relay server when a direct path cannot be opened. Relayed connections run as two legs through the relay; direct ones as a single line, with traffic moving both ways.

When a relayed connection becomes direct, two probes meet in the middle, the path lets go of the relay and straightens with a slight wobble, and the latency rolls down to the new value. A connection that falls back to a relay bends back through it.

## API reference

### Props

Also accepts every prop of `<div>` (`React.ComponentProps<"div">`), spread onto the root element.

| Prop            | Type                              | Default              | Description                                                                                                                                                                                                                                                                           |
| --------------- | --------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `device`        | `MeshDevice`                      | `demo laptop`        | This device: `{ id, label, location?, lat, lon }`. Every path starts here, and the header names it. The demo laptop in Amsterdam is used only while `peers` and `paths` are not given; without a device no paths are drawn.                                                           |
| `peers`         | `readonly MeshDevice[]`           | `6 demo peers`       | The other devices: `{ id, label, location?, lat, lon }`. `location` is shown under the name and read in the row's accessible name. Peers with a repeated id, the device's id or a non-finite coordinate are skipped.                                                                  |
| `relays`        | `readonly MeshRelay[]`            | `3 demo relays`      | Relay servers: `{ id, label, lat, lon }`. The `id` is shown on the map and in the row chip (`via lhr`), the `label` in the detail line (`Relayed through London`). The demo relays are used only while `peers` and `paths` are not given.                                             |
| `paths`         | `readonly MeshPath[]`             | —                    | The current path to each peer: `{ peer, relay: string \| null, ms, traffic? }`. `relay: null` is a direct connection; `ms` is the round-trip latency; `traffic` from 0 to 1 sets the packet rate. A peer without a path is offline. When a path changes, the map animates the change. |
| `value`         | `string \| null`                  | —                    | Selected peer id (controlled), or null for none. Use with `onValueChange`.                                                                                                                                                                                                            |
| `defaultValue`  | `string \| null`                  | `null`               | Selected peer when uncontrolled.                                                                                                                                                                                                                                                      |
| `onValueChange` | `(value: string \| null) => void` | —                    | Called with the peer id when a peer is selected, and with null when the selection is cleared.                                                                                                                                                                                         |
| `live`          | `boolean`                         | `true`               | Runs the demo upgrades and fallbacks while the map is on screen. Has no effect when `peers` or `paths` is given.                                                                                                                                                                      |
| `label`         | `string`                          | `"Mesh connections"` | Accessible name of the map. Its `aria-label` adds the device name and the number of direct, relayed and offline peers.                                                                                                                                                                |

### Ref

`ref` points at the root `HTMLDivElement`.

## Keyboard

| Keys                   | Action                                                                                              |
| ---------------------- | --------------------------------------------------------------------------------------------------- |
| Tab                    | Moves to the device list. The rows share one tab stop.                                              |
| ArrowUp / ArrowDown    | Moves focus one line up or down in the list, which has one to three columns depending on its width. |
| ArrowLeft / ArrowRight | Moves focus to the previous or next device.                                                         |
| Home / End             | Moves focus to the first or last device.                                                            |
| Enter / Space          | Selects the focused device, or clears the selection when it is already selected.                    |
| Escape                 | Clears the selection.                                                                               |

## Accessibility

* The peers are a `ul` labelled "Devices" of `button` rows with `aria-pressed` and one roving tab stop. Each row's accessible name reads like "Build server, Frankfurt, direct, 9 milliseconds", "NAS, Dublin, relayed through London, 20 milliseconds" or "Phone, Madrid, offline".
* The map has `role="img"` and an `aria-label` such as "Mesh connections from Work laptop: 5 direct, 1 relayed." Its markers are `aria-hidden`; a click or tap within 14 px of a peer marker selects that peer for pointer users, and a click on the empty map clears the selection.
* A polite live region announces path changes such as "Build server is now direct, 13 ms." at most every 5 s. Changes in between are collected, and the latest three are read together.
* Every name, count, latency and relay id is DOM text. The canvases are `aria-hidden`.
* Reduced motion: there are no probes, flash, wobble, sparks, selection pulse or packets: a changed path crossfades from its old to its new shape over 150 ms, and the counts, chips and latencies swap without rolling.

## Theming

Styled with Tailwind classes on your shadcn theme tokens, so light and dark follow your theme. The accent comes from `--lumesec`. See [Theming](/docs/theming).

This component reads `--lumesec`, `--lumesec-glint`, `--lumesec-shine`, `--lumesec-info`, `--lumesec-warning`, `--foreground`, `--muted-foreground`, `--muted`, `--card` and `--border`.

## Notes

* A relayed connection runs as two legs through its relay, drawn as grey dashes (two dots on, one off) with a gap around the relay square. A direct connection is a single line of accent dots. Every path starts at this device and bulges upward like a flight arc.
* Packets run both ways on every path: outbound in `--lumesec`, inbound in `--lumesec-info`, at 0.5 + 2.5 × `traffic` per second in each direction, with at most 160 in flight.
* When a relayed path becomes direct, two probes run toward each other along the direct line (at most 0.45 s). Where they meet the dot grid flashes in an uneven ring, the path lets go of the relay on an under-damped spring that overshoots by about 18 % and settles in about 0.7 s, and 12 sparks leave the relay in the direction the path pulled away. The row's chip and latency roll to the new values at that moment; until then the list keeps the relayed values.
* When a direct path falls back, it bends back through its relay without overshoot and is tinted `--lumesec-warning` for 1.2 s; the row's chip flashes in the same colour. A change of relay blends between the two relayed shapes.
* Selecting a peer keeps its path and packets at full strength, dims the other paths to 0.3 and stops their packets, sends a short pulse from this device to the peer and names it on the map. The line under the map then reads like "Direct, 9 ms" or "Relayed through London, 32 ms"; with nothing selected it lists the relays.
* Peers missing from `paths` are offline: a hollow dot, no path, an `offline` chip and no latency. A `relay` id that is not in `relays` shows as `via <id>` and its path is drawn in the relayed style along the direct line. With no peers the list reads "No other devices".
* The map uses the Equal Earth projection centred on this device's meridian, cropped to 50° S to 72° N and zoomed to fit every device, relay and possible path (up to 6 times), so the view stays put while paths change. Its height follows its width; set only the width. Below 420 px the markers get smaller and the relay names leave the line under the map.
* The animation runs only while the map is on screen. Colours follow the theme and are re-read when it changes.


