POST /view-command moves the canvas, and GET /view-state reads it back. Commands travel to the app over Realtime.
Drive the view from the conversation, not only on explicit commands. When the user wants to look at, focus on, or compare something, answer and move their canvas: selectIteration to surface a phase, zoomToNode or selectNode to put a node in front of them, setViewMode to switch between tree and matrix.
Send a command
success: true means enqueued, not applied. A connected client applies it within about two seconds.
Command types
An unknown
type returns 400 { "error": "Invalid command", "validTypes": [...] }.
Unresolved targets
The API checks the target before enqueueing. A phase, node or goal that does not exist in the project returns HTTP200 with success: false and a _meta._tip explaining why, so a typo never becomes a silent no-op:
selectIteration requires a non-empty iterationName and focusGoals a non-empty goalIds; sending null is refused the same way.
View-aware targeting
selectNodeandzoomToNodeauto-switch to matrix when the target is a job or task, the only view that renders them. Pass"autoSwitch": falsein the command to stay in the current view.- Goals, needs and approaches are not auto-moved. Which view and expansion shows one best is your call: compose
setViewMode,selectIteration,focusGoals,expandNodeyourself. The_tiptells you the target’s type.
Read the view state
view_mode: "tree", empty lists, last_applied_command: null, command_seq: 0, clients_connected: false.
Confirming a command landed
POST /view-commandreturns acommandId.- Re-read
GET /view-stateand checklast_applied_command.id === commandId(it equalscommand_seq). The id advances on every applied command, including a no-op, so this works even when the view did not visibly change. clients_connected: falsemeans no live client (no heartbeat in the last 30 seconds). The command is queued but cannot apply until one connects, andlast_applied_commandwill not advance.

