Recipe: Graph + Request/Reply Content

Goal: a force-directed graph (Pro) whose nodes, when tapped, either fire an action or fetch content from your server and open it in a markdown sheet. This is the canonical use of mode: "request" and route_msg. Concepts: Data Flow — sync & actions, The MeshSocket Protocol.

Feeding the graph (server → device)

The graph reads its data as a nodes/edges object via sync:

{ "type":"graph", "id":"topology", "position":[0,0], "span":[4,4], "label":"Network",
  "sync":[{ "method":"meshsocket","type":"listen","event":"broadcast",
            "filter":{"msg_type":"graph"}, "valuePath":"data" }],
  "graphConfig": {
    "nodeAction": { "method":"meshsocket","mode":"request","event":"route_msg",
                    "payload": { "target_name":"Graph Server","type":"get_doc",
                                 "payload": { "id":"{{value}}" } } }
  } }

Server broadcasts the graph:

await socket.emit("broadcast_request", { "msg_type":"graph", "data": {
  "nodes":[{"id":"hub","label":"Hub","group":"core"},
           {"id":"cam1","label":"Camera 1","group":"device"}],
  "edges":[{"from":"hub","to":"cam1"}] }})

Node taps: action vs content

A node tap resolves in this order:

  1. Per-node onTap (set on the node data) — overrides the graph default.
  2. graphConfig.nodeAction — the default for every node.

{{value}} in either is the tapped node's id. Two onTap shapes:

  • Action — fire a MeshSocket action (no panel):

json "onTap": { "type":"action","method":"meshsocket","mode":"request","event":"route_msg", "payload": { "target_name":"Graph Server","type":"select","payload": { "id":"{{value}}" } } }

  • Content — request markdown and open it in a sheet:

json "onTap": { "type":"content","event":"get_doc","payload": { "id":"{{value}}" } }

Answering the request (server)

A mode: "request" action makes the device await a reply. Register a handler for the request type and return the response; the library turns your return value into the reply (raw clients echo reply_to — see Building a Server in Any Language (Raw Protocol)).

@socket.on("get_doc")
async def on_get_doc(payload):
    node_id = (payload or {}).get("id", "")
    return { "title": node_id.title(),
             "content": f"# {node_id}\n\nStatus: online\n\n- uptime: 4d\n- load: 0.3" }

The device accepts either:

  • an object { "title", "content" } → titled markdown sheet, or
  • a bare string → markdown sheet titled with the node's label.

Why request/reply here

Telemetry is broadcast (fan-out, no reply). But "give me the details for this node" is a question with one answer for one caller — that's route_msg + mode: "request". Use request/reply whenever the device needs a value back (content panels, validations, one-shot queries); use broadcast for streaming state.

Next: generate UI from data — Recipe: Server-Driven (Dynamic) UI.