# Garphield agent manual This is the operational contract for agents driving Garphield. HTTP-only clients can read this manual, the command schema, and graph files. Python and R can validate, convert, fingerprint, and encode projects without a renderer. `window.garphield`, WebMCP, embeds, notebook controls, layout, audit, and rendered export require a mounted browser renderer. ## Quick start: drive Garphield with Playwright Use a mounted browser for commands and rendered state. The following program opens the production workbench, waits for the API, loads a known sample, requests the desired layout and awaits that request's settlement, then obtains a real graph node ID from node-link export, selects and frames its final coordinates, applies the quality gate, and always closes the browser: ```js import { chromium } from "playwright"; const browser = await chromium.launch({ headless: true }); try { const page = await browser.newPage(); await page.goto("https://garphield.com/", { waitUntil: "domcontentloaded", }); await page.waitForFunction( () => typeof window.garphield?.run === "function", undefined, { timeout: 30_000 }, ); await page.evaluate(async () => { await Promise.resolve( window.garphield.run("sample.load", { id: "petersen" }), ); }); await page.waitForFunction( () => window.garphield?.getState?.().dataset?.id === "petersen", undefined, { timeout: 30_000 }, ); await page.evaluate(async () => { const layout = await Promise.resolve( window.garphield.run("layout.set", { mode: "force" }), ); if (layout && !layout.ok) { throw new Error(layout.error?.message ?? "Layout mode failed"); } const settled = await Promise.resolve( window.garphield.run("layout.wait"), ); if (settled && !settled.ok) { throw new Error(settled.error?.message ?? "Layout did not settle"); } }); const id = await page.evaluate(async () => { const graph = JSON.parse(window.garphield.export("nodeLink")); const id = graph.nodes?.[0]?.id; if (typeof id !== "string") throw new Error("The graph has no node ID"); const selected = await Promise.resolve( window.garphield.run("selection.select", { id }), ); if (selected && !selected.ok) { throw new Error(selected.error?.message ?? "Selection failed"); } return id; }); await page.evaluate(async (id) => { const framed = await Promise.resolve( window.garphield.run("camera.frame", { nodeIds: [id], durationMs: 0, }), ); if (!framed?.ok) { throw new Error(framed?.error?.message ?? "Frame failed"); } const audit = window.garphield.audit(); const quality = window.garphield.quality(); if (!audit.ok) { throw new Error("Unsafe view"); } if (quality.verdict !== "good") { console.warn("View needs review", quality); if (quality.verdict === "poor") throw new Error("Unsafe view"); } }, id); } finally { await browser.close(); } ``` `window.garphield` is created when the workbench mounts. Look it up again after navigation. `schema()` is the live command descriptor list; the generated schema at the end of this file is a build-time snapshot that can be read without mounting a browser. ## Load a graph Use `sample.load` for a bundled sample or `file.load` for a hosted graph, a `data:` URL, or a `blob:` URL created in the same browser context. Cross-origin `http:` and `https:` requests need CORS; same-origin URLs, `data:`, and same-context `blob:` sources do not. An explicit `format` is authoritative for extensionless or ambiguous URLs. Supported values are `dot`, `gexf`, `graphml`, `gml`, `nodeLink`, `compactJson`, `csv`, `edgeList`, `matrixMarket`, `graph6`, `sparse6`, `digraph6`, and `gph`. The MIME type does not select the parser. Always await the observable load result. A graph file resolves with `status: "loaded"` after the graph store changes and reserves the layout epoch that follows that mutation. A flat table resolves with `status: "construction-required"`; choose an explicit construction recipe before assuming that a graph exists. For a loaded graph, call `layout.wait` immediately after `file.load` to await that exact layout request before selecting or framing coordinates. ```js const result = await Promise.resolve( window.garphield.run("file.load", { url, format: "gexf" }), ); if (!result?.ok) throw new Error(result?.error?.message ?? "Load failed"); if (result.value.status === "construction-required") { throw new Error("This table needs an explicit graph construction recipe"); } const settled = await Promise.resolve( window.garphield.run("layout.wait"), ); if (!settled?.ok) throw new Error(settled?.error?.message ?? "Layout did not settle"); ``` `file.load` returns `FileLoadResultV1` in the standard `{ok, value}` or `{ok: false, error}` envelope. A fetch, network, or CORS acquisition failure uses `LOAD_FAILED` with `retryable: true`. After bytes/string acquisition, decode, parse, schema, and format failures use `LOAD_FAILED` with `retryable: false`; the error message and `{url}` details are preserved and the current graph remains in place. Activity superseded by a newer graph operation uses `STALE_STATE` with `retryable: true` and no obsolete toast. A successful `file.load` does not imply that a later layout has finished. ## Command results and failures Call every command as `await Promise.resolve(window.garphield.run(id, params))`. This works whether a command returns synchronously or returns a promise. Use the generated result delivery marker: envelope results branch on `ok`, while raw results are consumed directly. Unknown command IDs and invalid parameters can throw synchronously. Void commands return `undefined` through direct same-frame JavaScript and resolve as `null` on JSON transports. ```js const response = await Promise.resolve( window.garphield.run("camera.frame", { nodeIds: ["3"], durationMs: 0 }), ); if (!response?.ok) { console.error(response.error.code, response.error.message, response.error.retryable); } ``` `delivery: "envelope"` means the command returns `{ok: true, value}` or `{ok: false, error}`; branch on `ok` and preserve the typed `value`. `file.load` and camera/layout/selection result commands use this delivery. `delivery: "raw"` means the command returns its typed value directly: for example, `artifact.describe` resolves to the `ArtifactFormatV1[]` array, so consume it as an array and do not look for `ok`. Raw `artifact.create` and `artifact.download` similarly return their typed objects directly. A raw command's transport promise may reject, and synchronous validation or an outer transport error may throw/reject; these are not converted into a `CommandResult` envelope. Do not infer success from a toast or from a resolved promise alone. Use the generated command schema for current IDs, parameter names, enum values, result kinds, and the result delivery marker. Layout mutations (`layout.set`, a successful graph `file.load`, and an `apply()` that changes the graph or layout) reserve a layout epoch synchronously. Call `layout.wait` immediately after the mutation without an `epoch` parameter to await that reserved request; an explicit `layout.wait({ epoch })` remains available when the epoch is tracked separately. ## Read and apply complete view state `getState()` returns a complete serializable StateDoc for view configuration. Clone before changing it, then validate and dispatch the complete document with `apply()`: ```js const next = structuredClone(window.garphield.getState()); next.layout = "levels"; const accepted = window.garphield.apply(next); if (!accepted) throw new Error("StateDoc was rejected"); const settled = await Promise.resolve( window.garphield.run("layout.wait"), ); if (!settled?.ok) throw new Error(settled?.error?.message ?? "Layout did not settle"); const nodeLink = JSON.parse(window.garphield.export("nodeLink")); const id = nodeLink.nodes?.[0]?.id; if (typeof id !== "string") throw new Error("The graph has no node ID"); const framed = await Promise.resolve( window.garphield.run("camera.frame", { nodeIds: [id], durationMs: 0 }), ); if (!framed?.ok) throw new Error(framed?.error?.message ?? "Frame failed"); ``` The published [StateDoc JSON Schema](https://garphield.com/schemas/state-doc.schema.json) is the validation contract. This is a minimal complete StateDoc using canonical values: ```js const minimalStateDoc = { v: 1, dataset: { kind: "sample", id: "petersen" }, layout: "force", tapered: false, theme: "dark", bindings: [], filterStack: [], selection: { primary: null, set: [], target: null }, camera: null, panel: { bottomOpen: false, bottom: 240, floating: false }, }; if (!window.garphield.apply(minimalStateDoc)) { throw new Error("Minimal StateDoc was rejected"); } ``` `apply()` returns `true` only when validation and dispatch succeed. `false` means the value is invalid and inert: existing state is unchanged. A StateDoc carries view configuration plus a sample, generator, or file reference; file graph bytes are not applied, and positions are not applied. Full graph bytes and position/session restoration belong to `.gph` project loading. When `apply()` changes the graph or layout, it reserves a layout epoch; await `layout.wait` immediately, then select or frame final coordinates. A `true` return is not layout settlement; await `layout.wait` or another appropriate settlement result when geometry matters. The canonical layout values are `force`, `levels`, and `geo`. Older documents may normalize the removed `quality` value to `force`, but new StateDocs and commands must use only the canonical values. ## Node IDs and history IDs Graph node IDs are data identifiers. Read them from node-link export and pass them to selection and camera commands; do not invent an ID or confuse one with a history identifier: ```js const nodeLink = JSON.parse(window.garphield.export("nodeLink")); const graphNodeId = nodeLink.nodes[0].id; await Promise.resolve( window.garphield.run("camera.frame", { nodeIds: [graphNodeId] }), ); ``` History IDs are provenance-operation IDs returned by `history.tree()`. They identify entries in the history tree, not graph nodes, and are the values for `history.jump` and `history.diff`: ```js const tree = await Promise.resolve(window.garphield.run("history.tree")); const historyId = tree.nodes[0].id; await Promise.resolve( window.garphield.run("history.jump", { nodeId: historyId }), ); ``` ## Audit, quality, and export Use `audit().ok` from the accessibility audit and `quality().verdict` from the graph-drawing quality report as explicit gates. Surface every non-good quality verdict explicitly: ```js const audit = window.garphield.audit(); const quality = window.garphield.quality(); if (!audit.ok) throw new Error("Unsafe view"); if (quality.verdict !== "good") { console.warn("View needs review", quality); if (quality.verdict === "poor") throw new Error("Unsafe view"); } ``` `audit.ok` is false only for a critical automated accessibility heuristic; manual checks are findings, not automatic failures. `quality().verdict` is `good`, `warn`, `poor`, `info`, or `na`: reject `poor`, surface `warn` for review, and treat `na` as ungraded rather than passed. A quality result is not a certification. `export()` supports `gexf`, `graphml`, `gml`, and `nodeLink`; PNG export requires a mounted renderer. ## Diagnose hosted URL failures Probe the URL in the Garphield browser context so that browser CORS behavior, rather than a server-side request, is tested. A browser CORS preflight is an `OPTIONS` request caused by a non-safelisted method, a request header such as `Authorization`, or a non-safelisted content type. A normal `file.load` GET is usually a simple request, but it still needs `Access-Control-Allow-Origin`. The server must answer a preflight with an allowed origin and requested method/headers. ```js const probe = await fetch(url, { mode: "cors" }); if (!probe.ok) throw new Error(`HTTP ${probe.status}`); await probe.arrayBuffer(); ``` For an optional response-header check, run: ```bash curl -sS -D - -o /dev/null -H 'Origin: https://garphield.com' "$GRAPH_URL" ``` The response must include `Access-Control-Allow-Origin: *` or `Access-Control-Allow-Origin: https://garphield.com`. A rejected preflight or missing ACAO makes the direct browser fetch reject or reveals the missing header. `file.load` returns `LOAD_FAILED` with the failure message and URL details and preserves the current graph; fetch/CORS failures have `retryable: true`, while malformed content has `retryable: false`. A successful curl body fetch alone does not prove that a browser at Garphield can read the response. Check the browser console and the `file.load` error envelope for the actionable CORS, fetch, or parser failure. For human background, use the [small manual](https://garphield.com/docs/llms-small.txt) or the [full manual](https://garphield.com/docs/llms-full.txt). --- # Automation command schema This is the build-time snapshot of `window.garphield.schema()`. Use these command ids and parameter descriptors with `window.garphield.run(id, params)`; discovering the available commands does not require browser access. ```json [ { "id": "artifact.describe", "label": "Describe export formats", "group": "Export", "description": "Describe available reproducible artifact formats.", "params": [], "result": { "kind": "array", "name": "ArtifactFormatV1", "delivery": "raw" } }, { "id": "artifact.create", "label": "Create artifact", "group": "Export", "description": "Create an artifact and return its provenance manifest.", "params": [ { "name": "format", "type": "enum", "options": [ "gph", "png", "html", "graphml", "gexf", "gml", "node-link", "node-csv", "edge-csv", "analysis-bundle" ], "required": true } ], "result": { "kind": "object", "name": "ArtifactWireResultV1", "delivery": "raw" } }, { "id": "artifact.download", "label": "Download artifact", "group": "Export", "description": "Create an artifact and download it in the browser.", "params": [ { "name": "format", "type": "enum", "options": [ "gph", "png", "html", "graphml", "gexf", "gml", "node-link", "node-csv", "edge-csv", "analysis-bundle" ], "required": true } ], "result": { "kind": "object", "name": "ArtifactManifestV1", "delivery": "raw" } }, { "id": "layout.pause", "label": "Pause layout", "group": "Layout", "description": "Pause the active layout when the backend supports pausing.", "params": [], "result": { "kind": "object", "name": "LayoutSettlementV1", "delivery": "envelope" } }, { "id": "layout.resume", "label": "Resume layout", "group": "Layout", "description": "Resume a paused layout.", "params": [], "result": { "kind": "object", "name": "LayoutSettlementV1", "delivery": "envelope" } }, { "id": "layout.cancel", "label": "Cancel layout", "group": "Layout", "description": "Cancel the active layout and preserve the last settlement.", "params": [], "result": { "kind": "object", "name": "LayoutSettlementV1", "delivery": "envelope" } }, { "id": "layout.wait", "label": "Wait for layout", "group": "Layout", "description": "Wait for the latest mutation-reserved layout settlement epoch, or an explicit epoch when provided.", "params": [ { "name": "epoch", "type": "number" }, { "name": "timeoutMs", "type": "number" } ], "result": { "kind": "object", "name": "LayoutSettlementV1", "delivery": "envelope" } }, { "id": "layout.set", "label": "Switch layout", "group": "Layout", "description": "Switch layout and reserve its settlement epoch: Force (automatic), Levels, or Geo (basemap; auto-detects lat/lng fields). Call layout.wait immediately afterward to await this request.", "params": [ { "name": "mode", "type": "enum", "options": [ "force", "levels", "geo" ], "required": true } ] }, { "id": "edges.style", "label": "Edge style", "group": "Layout", "description": "Line, tapered, or arrow edges (direction styles are directed only).", "params": [ { "name": "style", "type": "enum", "options": [ "line", "tapered", "arrow" ], "required": true } ] }, { "id": "edges.color", "label": "Edge color mode", "group": "Layout", "description": "Flat edge color or source→target node-color gradient (gh #140).", "params": [ { "name": "mode", "type": "enum", "options": [ "flat", "gradient" ], "required": true } ] }, { "id": "theme.set", "label": "Theme", "group": "View", "description": "Choose the light, graphite dark, or seafoam theme.", "params": [ { "name": "theme", "type": "enum", "options": [ "light", "dark", "seafoam" ], "required": true } ] }, { "id": "panel.table.toggle", "label": "Toggle table panel", "group": "View", "description": "Show or hide the bottom table dock.", "params": [] }, { "id": "view.fisheye", "label": "Set fisheye lens", "group": "View", "description": "Enable or disable the pointer fisheye and optionally set its magnification and viewport-relative radius.", "params": [ { "name": "enabled", "type": "boolean", "required": true }, { "name": "magnification", "type": "number", "required": false }, { "name": "radiusScale", "type": "number", "required": false } ], "result": { "kind": "object", "name": "FisheyeViewV1", "delivery": "envelope" } }, { "id": "view.detail", "label": "Set graph detail", "group": "View", "description": "Draw the full network or a backbone Overview. Presentation only: it records no history and changes no export.", "params": [ { "name": "mode", "type": "enum", "options": [ "full", "overview" ], "required": true }, { "name": "latent", "type": "enum", "options": [ "auto", "hidden", "faded" ], "required": false } ] }, { "id": "layout.relax", "label": "Relax layout around the selection", "group": "Layout", "description": "Re-settle the drawing while holding some nodes still. hold=selection keeps what you selected exactly where it is and lets the rest settle around it; hold=others tidies the selection and leaves everything else alone. Force layout only.", "params": [ { "name": "hold", "type": "enum", "options": [ "selection", "others" ], "required": false } ] }, { "id": "minimap.toggle", "label": "Toggle minimap", "group": "View", "description": "Show or hide the minimap (Overview / Groups modes).", "params": [] }, { "id": "minimap.set", "label": "Show or hide the minimap", "group": "View", "description": "Set the minimap's visibility outright. Say which state you want rather than toggling from one you have to already know. Presentation only: it records no history and changes no export.", "params": [ { "name": "open", "type": "boolean", "required": true } ] }, { "id": "sources.browse", "label": "Browse all sources", "group": "Channels", "description": "Open the searchable draggable source browser in the Viz sidebar.", "params": [] }, { "id": "audit.a11y", "label": "Accessibility audit", "group": "View", "description": "Grade the current view's accessibility (Chartability heuristics) and open the report pane. Read the full report via window.garphield.audit().", "params": [] }, { "id": "quality.report", "label": "Graph-drawing quality", "group": "View", "description": "Grade the current layout's drawing quality (stress, edge crossings, node overlap, …) and open the report pane. Read the full report via window.garphield.quality().", "params": [] }, { "id": "camera.fit", "label": "Zoom to fit", "group": "View", "description": "Reframe the camera on the whole graph.", "params": [], "result": { "kind": "object", "name": "CameraSettlementV1", "delivery": "envelope" } }, { "id": "camera.frame", "label": "Frame / zoom to", "group": "View", "description": "Centre and zoom the camera on nodes, bounds, or the current selection — directs attention, unlike fit.", "params": [ { "name": "nodeIds", "type": "string[]" }, { "name": "bounds", "type": "bounds" }, { "name": "padding", "type": "number" }, { "name": "durationMs", "type": "number" } ], "result": { "kind": "object", "name": "CameraSettlementV1", "delivery": "envelope" } }, { "id": "camera.focus", "label": "Focus node", "group": "View", "description": "Centre and zoom the camera on one node.", "params": [ { "name": "id", "type": "string", "required": true }, { "name": "durationMs", "type": "number" } ], "result": { "kind": "object", "name": "CameraSettlementV1", "delivery": "envelope" } }, { "id": "sample.load", "label": "Load sample", "group": "Data", "description": "Replace the graph with a data-library sample.", "params": [ { "name": "id", "type": "enum", "options": [ "karate", "temporal-collaboration", "country-borders", "dag", "les_mis", "davis", "florentine", "krackhardt", "petersen", "dodecahedral", "icosahedral", "octahedral", "tutte", "chvatal", "frucht", "heawood", "bull", "diamond", "house", "complete8", "wheel12", "ws", "ba500", "ba2000", "er1000", "ws1000" ], "required": true } ] }, { "id": "file.load", "label": "Load graph from URL", "group": "Data", "description": "Fetch and load a graph from an http:, https:, data:, or same-context blob: URL. Cross-origin HTTP(S) requires CORS; data: and blob: resolve client-side. A recognized filename suffix selects the parser; use format for extensionless or ambiguous URLs, including compactJson, graph6, sparse6, and digraph6. The graph6 family carries topology only. Replaces the current graph; the URL rides the share link.", "params": [ { "name": "url", "type": "string", "required": true, "description": "Graph URL using http:, https:, data:, or a blob: URL from this browser context." }, { "name": "format", "type": "enum", "options": [ "dot", "gexf", "graphml", "gml", "nodeLink", "compactJson", "csv", "edgeList", "matrixMarket", "graph6", "sparse6", "digraph6", "gph" ], "required": false, "description": "Authoritative format for extensionless or ambiguous URLs." } ], "result": { "kind": "object", "name": "FileLoadResultV1", "delivery": "envelope" } }, { "id": "generator.create", "label": "Generate graph", "group": "Data", "description": "Replace the graph with a freshly generated one (ER/BA/WS, lattices, trees…). Numeric params fall back to each generator's defaults; reproducible from the seed.", "params": [ { "name": "type", "type": "enum", "options": [ "erdos_renyi", "barabasi_albert", "watts_strogatz", "complete", "cycle", "star", "wheel", "grid", "balanced_tree", "barbell", "complete_bipartite" ], "required": true }, { "name": "n", "type": "number" }, { "name": "m", "type": "number" }, { "name": "p", "type": "number" }, { "name": "k", "type": "number" }, { "name": "seed", "type": "number" }, { "name": "rows", "type": "number" }, { "name": "cols", "type": "number" }, { "name": "r", "type": "number" }, { "name": "h", "type": "number" }, { "name": "m1", "type": "number" }, { "name": "m2", "type": "number" }, { "name": "n1", "type": "number" }, { "name": "n2", "type": "number" } ] }, { "id": "channel.bind", "label": "Bind channel", "group": "Channels", "description": "Bind an algorithm or data field to a visual channel (size, color, …).", "params": [ { "name": "channel", "type": "enum", "options": [ "size", "color", "shape", "image", "nodeLabel", "border", "pieSlices", "edgeColor", "edgeWidth", "edgeStyle", "edgeLabel", "hull", "contour", "heatmap" ], "required": true }, { "name": "kind", "type": "enum", "options": [ "algorithm", "field", "set" ], "required": true }, { "name": "source", "type": "string", "required": true, "description": "Algorithm id (degree, betweenness, closeness, pagerank, eigenvector, harmonic, …), field id (field: / efield:, also accepts a bare field name), or a manual set id." }, { "name": "resolution", "type": "number", "description": "Community resolution (Louvain/Leiden only; <1 fewer/larger, >1 more/smaller). Ignored by other sources." } ] }, { "id": "channel.unbind", "label": "Unbind channel", "group": "Channels", "description": "Remove the binding on a channel.", "params": [ { "name": "channel", "type": "enum", "options": [ "size", "color", "shape", "image", "nodeLabel", "border", "pieSlices", "edgeColor", "edgeWidth", "edgeStyle", "edgeLabel", "hull", "contour", "heatmap" ], "required": true } ] }, { "id": "filter.add", "label": "Add source filter", "group": "Filter", "description": "Append an algorithm, field, or transformation to the filter stack.", "params": [ { "name": "kind", "type": "enum", "options": [ "algorithm", "field", "transform" ], "required": true }, { "name": "source", "type": "string", "required": true, "description": "Source id or field name." } ] }, { "id": "transform.add", "label": "Add transformation", "group": "Filter", "description": "Append a transformation to the filter stack (hide mode).", "params": [ { "name": "id", "type": "enum", "options": [ "largest_wcc", "spanning_tree", "k_core_extract", "disparity_filter" ], "required": true }, { "name": "entryId", "type": "string", "required": false }, { "name": "mode", "type": "enum", "options": [ "fade", "hide" ], "required": false }, { "name": "enabled", "type": "boolean", "required": false }, { "name": "k", "type": "number", "required": false }, { "name": "alpha", "type": "number", "required": false } ] }, { "id": "transform.remove", "label": "Remove transformation", "group": "Filter", "description": "Remove one transformation recipe by its stable entry id.", "params": [ { "name": "id", "type": "string", "required": true } ] }, { "id": "transform.clear", "label": "Clear transformations", "group": "Filter", "description": "Empty the filter stack.", "params": [] }, { "id": "history.pin", "label": "Pin current state", "group": "History", "description": "Label and permanently keep the current point in history, capturing a full snapshot you can jump back to even after it's pruned from the default undo/redo timeline. Agent primitive — the UI itself always routes saves through storyboard.capture instead.", "params": [ { "name": "label", "type": "string", "required": true } ], "agentOnly": true }, { "id": "history.jump", "label": "Jump to history point", "group": "History", "description": "Move to any point in the provenance tree by node id, crossing branches if needed. Pinned points restore instantly from their snapshot.", "params": [ { "name": "nodeId", "type": "string", "required": true } ] }, { "id": "history.tree", "label": "Describe history tree", "group": "History", "description": "Return the full provenance tree (every branch, not just the undo/redo path) as serializable data: nodes, parents, lanes, pins, and the current cursor. Includes detached pins (label/id only — jump by id to restore) for parity with the live tree.", "params": [] }, { "id": "history.diff", "label": "Diff two history points", "group": "History", "description": "Compute what changed between two history points (tree nodes or pins): the op trail, binding/filter-stack/set deltas, and a capped node/edge change summary. `to` defaults to the current cursor. Sections degrade to an explicit unavailable marker when the path crosses unreadable history, or when either point is a detached pin (trail/graph-content sections only).", "params": [ { "name": "from", "type": "string", "required": true }, { "name": "to", "type": "string" } ] }, { "id": "set.add", "label": "Add set", "group": "Narrative", "description": "Save the selected node(s) as a set, with an optional note anchored to them (undoable).", "params": [ { "name": "label", "type": "string" }, { "name": "note", "type": "string" } ] }, { "id": "set.update", "label": "Update set", "group": "Narrative", "description": "Rename a set or edit its note by id (undoable).", "params": [ { "name": "id", "type": "string", "required": true }, { "name": "label", "type": "string" }, { "name": "note", "type": "string" } ] }, { "id": "set.remove", "label": "Remove set", "group": "Narrative", "description": "Remove a set by id (undoable).", "params": [ { "name": "id", "type": "string", "required": true } ] }, { "id": "storyboard.capture", "label": "Capture scene", "group": "Narrative", "description": "Save a history node (the current one by default, or nodeId) to the storyboard reel — pins it and appends a scene referencing it. The single 'save' action both the History and Storyboard panels use.", "params": [ { "name": "title", "type": "string" }, { "name": "nodeId", "type": "string" } ] }, { "id": "storyboard.play", "label": "Play storyboard", "group": "Narrative", "description": "Page through captured scenes (←/→ to navigate, Esc to exit).", "params": [] }, { "id": "storyboard.next", "label": "Next scene", "group": "Narrative", "description": "Advance to the next captured scene. Starts at the first scene if the player isn't already open.", "params": [] }, { "id": "storyboard.prev", "label": "Previous scene", "group": "Narrative", "description": "Go back to the previous captured scene.", "params": [] }, { "id": "storyboard.goto", "label": "Go to scene", "group": "Narrative", "description": "Jump directly to a captured scene by its position in the reel (0-based).", "params": [ { "name": "index", "type": "number", "required": true, "description": "0-based position in the live reel." } ] }, { "id": "storyboard.clear", "label": "Clear storyboard (un-saves every pin)", "group": "Narrative", "description": "Un-save every captured scene — their pins are removed too (unless something else still references one). Irreversible and outside undo by design, so it requires confirm: true.", "params": [ { "name": "confirm", "type": "boolean", "required": true, "description": "Must be true — un-saves every captured scene's pin, outside undo." } ] }, { "id": "storyboard.share", "label": "Copy storyboard link", "group": "Narrative", "description": "Copy a URL that replays the captured storyboard (the reel rides the #sb= hash).", "params": [] }, { "id": "session.restoreAutosave", "label": "Restore autosaved session", "group": "Session", "description": "Restore the most recently autosaved session — graph, view, and full provenance tree — into the workbench.", "params": [] }, { "id": "session.clearAutosave", "label": "Clear autosaved session", "group": "Session", "description": "Delete the autosaved session slot.", "params": [] }, { "id": "selection.mode", "label": "Selection mode", "group": "Selection", "description": "Choose pointer, marquee, or lasso selection mode.", "params": [ { "name": "mode", "type": "enum", "options": [ "pointer", "marquee", "lasso" ], "required": true } ], "result": { "kind": "object", "name": "SelectionModeV1", "delivery": "envelope" } }, { "id": "selection.polygon", "label": "Select polygon", "group": "Selection", "description": "Select nodes whose rendered centers are inside a polygon.", "params": [ { "name": "points", "type": "json", "required": true }, { "name": "additive", "type": "boolean" } ], "result": { "kind": "object", "name": "SelectionPolygonResultV1", "delivery": "envelope" } }, { "id": "selection.path", "label": "Select directed path", "group": "Selection", "description": "Set source and target endpoints and emphasize their exact directed shortest path.", "params": [ { "name": "source", "type": "string", "required": true }, { "name": "target", "type": "string", "required": true } ], "result": { "kind": "object", "name": "SelectionPathV1", "delivery": "envelope" } }, { "id": "selection.select", "label": "Select node", "group": "Selection", "description": "Select a node by id (clears the multi-selection).", "params": [ { "name": "id", "type": "string" }, { "name": "ids", "type": "string[]" } ] }, { "id": "selection.grow", "label": "Grow selection", "group": "Selection", "description": "Add direct neighbours (component: true grows to the full component).", "params": [ { "name": "component", "type": "boolean" } ] }, { "id": "selection.shrink", "label": "Shrink selection", "group": "Selection", "description": "Peel the boundary — the inverse of grow.", "params": [] }, { "id": "selection.invert", "label": "Invert selection", "group": "Selection", "description": "Select the complement of the current selection.", "params": [] }, { "id": "selection.clear", "label": "Clear selection", "group": "Selection", "description": "Clear both the primary and multi-selection.", "params": [] } ] ```