PXI · UI code mode · Proxy to RPC

How a property path becomes a message

ui is not an object with methods. It is a chain of Proxy objects that remember the path you typed. Only the final call does anything: it posts one message to the main thread and hands the script a Promise keyed by the call id.

How a property path becomes a message Walkthrough of await ui.dataset.create({ name }) inside the worker. Four cards show ui as a root proxy, .dataset and .create as get traps that only extend a path, and the call as the apply trap that joins the path into dataset.create. Below, the apply trap returns Promise number 3 and posts a call message that crosses to the main thread, where dispatch checks it and the page handler runs; a callResult message with the same id returns and resolves the promise. The script writes await ui.dataset.create({ name }). Read left to right: WORKER · THE SCRIPT'S REALM MAIN THREAD · OWNS THE PAGE ROOT PROXYuipath: [ ]a Proxy around an empty function GET TRAP.datasetpath: ["dataset"]returns another proxy · nothing runs GET TRAP.createpath: ["dataset", "create"]still nothing runs APPLY TRAP({ name })path.join(".") → "dataset.create"the only step that does anything RETURNS AWAIT Promise #3 resolver kept in pendingCalls POST MSG { type: "call", callId: 3, operationName: "dataset.create", input } STRUCTURED CLONE · JSON ONLY GATE Dispatch 6 checks · then the handler APP Page handler dataset.create · creates it MSG { type: "callResult", callId: 3, result } SAME ID · RESOLVES #3 Reading a property builds a name. Calling it sends a message. Nothing else crosses. Unknown names still return a callable, so a typo becomes a did-you-mean error from dispatch, not a crash inside the script. LEGEND The step that does work Message worker → main Message main → worker Plain JSON, matched by callId
←/→ step · Space play/pause · R replay · Home/End jump

Each get trap returns a new proxy with one more segment. The apply trap joins the segments into the operation name, stores a resolver under a fresh callId, and calls postMessage. When the main thread answers with the same id, the resolver runs and the await continues. This is why the worker needs no DOM, no network, and no imports.