---
title: "ui.layout-suggestion@1 · OpenVibe.Services"
canonical: "https://openvibe.services/docs/contracts/ui.layout-suggestion"
updated: null
---

# ui.layout-suggestion@1 · OpenVibe.Services

HTML: [ui.layout-suggestion@1 · OpenVibe.Services](https://openvibe.services/docs/contracts/ui.layout-suggestion)

# UiLayoutSuggestion `ui.layout-suggestion@1`

Generated at 2026-11-13 06:25:48 UTC from [`openvibe-contracts v0.419.0`](https://github.com/OpenVibers/OpenVibe.Contracts/tree/v0.419.0) and [`openvibe-sdk v0.161.0`](https://github.com/OpenVibers/OpenVibe.SDK/tree/v0.161.0).

- Version: 1.0.0
- Owner: contracts
- Visibility: public
- Status: active
- Compatibility: backward
- Schema: [`https://openvibe.network/contracts/ui/layout-suggestion.v1.json`](/docs/contracts/ui.layout-suggestion.json)

ui.layout-suggestion@1: OpenVibe.AI's answer to ui.layout.suggest. ok true carries the layout, already checked by openvibe-sdk/ui-layout against the catalog sent with the request, and a one-sentence summary of what changed plus the model's reasons. ok false is an honest refusal: no layout, one sentence saying what it could not do, and the validator's reasons in plain words (for example "Stop has to stay visible."). A receipt names the model that answered, with the run and the route. The suggestion is previewed, never applied silently; Network or the product stores it only after the person accepts it.

## Fields

| Field | Type | Required | Description | Constraints |
| --- | --- | --- | --- | --- |
| `ok` | boolean | yes |  |  |
| `layout` | one of | yes | The validated layout when ok is true; null when it is false. |  |
| `summary` | string | yes | One sentence saying what changed, or what could not be done. | - `minLength 1`<br>- `maxLength 300` |
| `reasons` | array of string | yes | Why this arrangement, or the validator's reasons for refusing one. | - `maxItems 20`<br>- `items: minLength 1`<br>- `items: maxLength 300` |
| `receipt` | object |  | Which model answered: the same receipt every AI run carries, at the task's own shape. | - `no other fields` |
| `receipt.model` | string | yes |  | - `minLength 1`<br>- `maxLength 200` |
| `receipt.provider` | string |  |  | - `minLength 1`<br>- `maxLength 100` |
| `receipt.route` | string |  |  | - `minLength 1`<br>- `maxLength 200` |
| `receipt.run_id` | string |  |  | - `minLength 1`<br>- `maxLength 80` |
| `receipt.cost_usd` | number |  |  | - `minimum 0` |

## Examples

From the contract's own test fixtures: valid ones validate, rejected ones must fail.

 Valid: couldnt

~~~~
{
  "ok": false,
  "layout": null,
  "summary": "Couldn't do that: Stop has to stay visible.",
  "reasons": [
    "Stop has to stay visible.",
    "Every pressed part has to be at least 44 px."
  ],
  "receipt": {
    "model": "deepseek-flash",
    "provider": "deepseek",
    "route": "ui.layout.suggest",
    "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPR"
  }
}
~~~~

Valid: suggestion

~~~~
{
  "ok": true,
  "layout": {
    "surface": "bot.controls",
    "surface_version": 1,
    "device": "phone",
    "name": "One-thumb controls",
    "author": {
      "name": "Ana"
    },
    "sharing": "private",
    "parts": [
      {
        "id": "stop",
        "visible": true,
        "place": {
          "x": 0,
          "y": 0,
          "w": 4,
          "h": 2
        },
        "style": {
          "tint": "danger",
          "shape": "round",
          "opacity": 1,
          "label": true
        },
        "bindings": {
          "keys": [
            "Space"
          ],
          "gamepad": [
            "south"
          ]
        }
      },
      {
        "id": "stick",
        "visible": true,
        "place": {
          "x": 0,
          "y": 2,
          "w": 4,
          "h": 3
        },
        "style": {
          "tint": "surface",
          "shape": "round",
          "opacity": 1,
          "label": false
        },
        "bindings": {
          "keys": [
            "W",
            "A",
            "S",
            "D"
          ],
          "gamepad": [
            "left-stick"
          ]
        }
      }
    ]
  },
  "summary": "Put Stop across the top and the drive stick below it where a thumb reaches.",
  "reasons": [
    "Stop had to stay visible, so it became the top row.",
    "The stick moved under the thumb."
  ],
  "receipt": {
    "model": "deepseek-flash",
    "provider": "deepseek",
    "route": "ui.layout.suggest",
    "run_id": "run_01JAB2C3D4E5F6G7H8J9K0MNPQ",
    "cost_usd": 0.0004
  }
}
~~~~

Rejected: ok-false-with-layout

~~~~
{
  "ok": false,
  "layout": {
    "surface": "bot.controls",
    "surface_version": 1,
    "device": "phone",
    "name": "Still here",
    "author": {
      "name": "Ana"
    },
    "sharing": "private",
    "parts": [
      {
        "id": "stop",
        "visible": true,
        "place": {
          "x": 0,
          "y": 0,
          "w": 2,
          "h": 2
        },
        "style": {
          "tint": "danger",
          "shape": "round",
          "opacity": 1,
          "label": true
        },
        "bindings": {
          "keys": [],
          "gamepad": []
        }
      }
    ]
  },
  "summary": "Couldn't do that.",
  "reasons": [
    "Stop has to stay visible."
  ]
}
~~~~

Rejected: ok-true-without-layout

~~~~
{
  "ok": true,
  "layout": null,
  "summary": "Put Stop on the left.",
  "reasons": []
}
~~~~

## Validate

~~~~
const contracts = require('openvibe-contracts');
contracts.validate('ui.layout-suggestion@1', value);   // { valid, errors: [{ path, message }] }
~~~~
