UI Schema ========= Declarative JSON tree produced by Django / host apps and consumed by **agency-ui** ``UiRenderer``. Types → components: :doc:`frontend`. Envelope -------- :: { "version": "1.0", "root": { "type": "desktop", "id": "home-desktop", "children": [ … ] } } Every node:: { "type": "…", "id": "optional", "channel": "…", "uuid": "…", "name": "…", "props": { }, "children": [ ], "action": {…} } ``id`` is chrome (window store). The address space is ``channel`` + ``uuid`` (+ optional ``name``). TypeScript: ``agency-ui/src/schema/ui.ts`` and ``address.ts``. The ``type`` field is a closed union (``SCHEMA_TYPES``); ``isValidNode`` rejects unknown names (including Vuetify). A new type is ``ui.ts`` + ``SCHEMA_TYPES`` + ``registry`` + Django ``KNOWN_UI_TYPES`` + Sphinx. Python: ``agency.ui.address``, ``agency.ui.schema``. Address space ------------- Like Home Assistant. A domain instance is ``channel`` + ``uuid``: :: { "type": "agency-mission", "channel": "agency.mission", "uuid": "00000000-0000-4000-8000-000000000001", "children": [ { "type": "agency-button", "name": "execute", "props": { "label": "Execute" } } ] } ``entity_id`` is derived, never written as a composed path on the child: - parent → ``agency.mission.{uuid}`` - named control → ``agency.mission.{uuid}.execute`` ``UiRenderer`` injects the prefix. Built-ins read it; they do not invent ids or compose REST / socket URLs from the tree. The Agency Bus **topic is the entity_id**. Reconnect rehydrates the document over REST — the WebSocket is not the source of truth. Live overlay ------------ Two layers, one mutation owner each: - **Schema** (shape): persisted document — ``type``, children, label, icon. - **Overlay** (live): ``entity_id → attrs`` (``disabled``, ``hidden``, ``label``, ``value``). Closed set; window chrome (``x`` / ``y`` / ``open``) and pose are ``windowsStore``, not here. A ``state`` envelope on the entity topic carries ``{ "attrs": { "disabled": true } }``. The overlay projector patches the map; it does **not** call ``replaceDocument`` / ``updateNode``. Built-ins paint ``schema.props + overlay``. Reconnect: ``GET /api/ui/overlay/`` (and bootstrap ``overlay``) **replaceAll**. An attr that existed only on the socket evaporates — that is the test that it did not belong only on the WS. Django ``agency.ui.overlay.live_overlay`` is the place to derive attrs from domain facts (a running execution disables execute). Until a producer fills it, the snapshot is empty. Bot panels ---------- A bot composes a screen from the **registered** palette and persists a ``UIDocument`` (Django ``UiPanel``). It does not send Vue, invent a ``type``, or upload JavaScript. - Scope: ``session`` (owner), ``workspace`` (active workspace), or ``ephemeral`` (still in the DB so reconnect works; not a workspace catalog item). - Address: window chrome ``panel.{uuid}``; nodes inside use ``channel`` ``agency.panel`` + the panel uuid (children declare ``name`` only). Same ``entity_id`` the operator and the bot talk to. - Write path validates with ``is_valid_node`` / ``KNOWN_UI_TYPES``. - REST: ``GET/POST /api/ui/panels/``, ``GET/PATCH /api/ui/panels//``. Bootstrap includes ``panels``. ``open_panel`` publishes desktop visibility; the shell ``openAddress`` loads the document from REST. - Tools: ``create_panel``, ``patch_panel``, ``open_panel``. Do not address nodes by ``children`` index. Do not put a composed ``channel`` on a child (that reintroduces a Vue↔path map). Pinned fixture: ``agency-ui/src/fixtures/address-space.json`` (same bytes as ``agency-django/src/agency/data/address_space.json``). Important contracts ------------------- **Split** — ``props.direction`` is semantic: - ``horizontal`` → panes side-by-side (left/right) - ``vertical`` → panes stacked (top/bottom) **Window** — ``type: window`` with ``title``, ``icon``, ``width``, ``height``, ``x``, ``y``, chrome flags (``noResize``, ``noFull``, ``noMax``, …). Maximize is on by default; ``noFull`` or ``noMax`` hides the button. Pose ---- Structured transform. Numbers only — Vue writes one CSS ``transform``. Never a ``rotateY(...)`` string on the wire. :: { "x": 0, "y": 0, "z": -80, "rx": 0, "ry": 12, "rz": 0, "scale": 1 } Three mutation owners: 1. **Window chrome** — ``windowsStore`` + desktop snapshot (``prefs.desktop``). ``x`` / ``y`` stay ``left`` / ``top``. Depth, rotation, and scale are ``z`` / ``rx`` / ``ry`` / ``rz`` / ``scale``. Not overlay. 2. **Content viewport** — a later overlay bag (``scale`` / pan). Leaflet keeps its own zoom. Do not reuse chrome pose keys here. 3. **Public scene** (#22) — optional ``pose`` on the node. The kiosk host writes the matrix. ``UiRenderer`` does not wrap every semantic node in 3D. DSM projection is ``desktop.projection``: ``flat`` (today's MDI) or ``perspective`` (stage). The toggle is in the drawer chrome. Open windows tilt toward the vanishing point from the window center; the focused window stays almost frontal and comes forward. Minimize in 2D shelves the window into a top-right quarter-width column (the stack scales if it does not fit; click to restore). On the 3D stage, minimize is a bottom carousel. Window **body** is ``transform-style: flat`` so grid / xterm / Leaflet still hit-test. The operator owns an FPS helm: triple glass, seated eye ``{0, -400, 72}``, Space mouselook (no RMB, no pistol, no orbit). WASD walks; wheel is head height; Ctrl crouches from the current Y. Sit / Home resets the camera. The bot does not move it. Windows hop between panes via ``screen``. See agency-ui#26. Python: ``agency.ui.pose.parse_pose``. TypeScript: ``agency-ui/src/schema/pose.ts``. **Explorers** — interactive CRUD surfaces (``*-explorer``): list left, detail right; mutate via REST; live rows via observe (:doc:`python`). **System** — typically a ``log-stream`` node inside a System window. The Info tab is three sibling collapsibles: Versions, Inference, System. **Singletons** — schema windows with a stable address, same as ``system``: ``media`` (operator player, ``agency-media``), ``map`` (operator chart). Veronica opens them with ``open_window``, not a kiosk assign. Schema events (``ui.*``) ------------------------ The user WebSocket (``/channel/``) can emit schema mutations (``ui.update``, ``ui.append``, …) and Agency Bus envelopes. ``open_window`` publishes ``desktop.*.visibility.changed`` (handled by the desktop projector + ``openAddress``), not ``ui.open_window``. Schema ``ui.*`` is still handled by ``uiStore`` — separate from domain ``message`` / ``monitor_event`` traffic. See ``agency-ui/src/schema/ui.ts`` and ``renderer/registry.ts``. Legacy / ops logs ----------------- Standalone log stream outside the SPA: ``/logs/`` (SSE via ``/stream/logs/``, session required). System → Logs uses the same **ops** feed: errors, warnings, tracebacks, process restarts, GPU updates. It tails ``wsgi/asgi/work/beat`` ``system.log`` / ``app.log`` plus gunicorn — not docker/nginx/Ollama stdout.