`) for `LayoutNode` objects received in `Layout` messages.
+- **Widget Tree Builder:** Constructs the widget tree. Once the `LayoutRoot` message arrives, the builder can start assembling the tree using the nodes in the buffer.
+- **State Manager:** Holds the state object, initialized from the `StreamHeader`'s `initialState`, and applies subsequent `StateUpdate` messages.
+- **Binding Processor:** Resolves bindings by inspecting the `properties` of a `LayoutNode`. If a property's value is an object with a `"$bind"` key, it's treated as a dynamic binding. Otherwise, it's a static value.
+
+### **7.2. Progressive Rendering**
+
+The primary advantage of the streaming approach is the ability to render the UI as it arrives:
+
+1. The client begins parsing the JSONL stream.
+2. As `LayoutNode` messages are received, they are stored in the buffer.
+3. When the `LayoutRoot` message arrives, the client can immediately start building the widget tree, starting from the specified root ID.
+4. If the root node is already in the buffer, it is rendered. If not, the client waits for it to arrive.
+5. As more `LayoutNode` messages arrive, the client can fill in the missing parts of the widget tree. The UI will appear to "build itself" on the screen.
+
+### **7.3. Performance Considerations**
+
+- **Efficient List Building:** Use the underlying UI framework's mechanisms for efficient list building (e.g., `ListView.builder` in Flutter) to ensure that list items are only built as they are scrolled into view.
+- **`const` Widgets:** Use `const` widgets in the client-side mapping code wherever possible.
+
+### **7.4. Error Handling**
+
+- **Invalid Payloads:** Handle malformed JSON or messages that do not conform to the schema.
+- **Catalog Violations:** If the server sends a layout that violates the `WidgetCatalog`, the client should log the error and display a fallback UI.
+- **Broken Bindings:** If a binding path is invalid, the client should use a default value or display a visual error indicator.
+
+## **Appendix A: Delta Updates**
+
+For performance-critical applications where sending the entire UI definition on every interaction is too costly, GSP supports an optional delta update mechanism. This allows the server to send only the changes to the layout or state.
+
+### **A.1. Server-to-Client: The `StateUpdate` Payload**
+
+To change only dynamic data, the server can send a `StateUpdate` payload. This payload uses a simplified model that is easy for AI to generate and avoids the complexity of index- or key-based list modifications.
+
+The payload contains a single `operations` array, where each object is a specific command.
+
+#### **A.1.1. The `stateSet` Operation**
+
+This operation replaces the value at a given absolute path. If the path points to a list, the entire list is replaced. This is the primary mechanism for updating state.
+
+- `op`: `"stateSet"`
+- `path`: An absolute JSON Pointer-like path to a value in the state (e.g., `/todoItems`, `/user/name`).
+- `value`: The new value to set at the specified path.
+
+```json
+{
+ "op": "stateSet",
+ "path": "/todoItems",
+ "value": [
+ { "id": "todo-123", "text": "Buy almond milk", "isCompleted": true },
+ { "id": "todo-456", "text": "Call the bank", "isCompleted": false }
+ ]
+}
+```
+
+#### **A.1.2. The `listAppend` Operation**
+
+This operation provides a convenient shortcut for appending one or more items to the end of a list without needing to send the entire list.
+
+- `op`: `"listAppend"`
+- `path`: An absolute path to the target list.
+- `items`: An array of full item objects to add.
+
+```json
+{
+ "op": "listAppend",
+ "path": "/todoItems",
+ "items": [
+ { "id": "todo-789", "text": "Schedule appointment", "isCompleted": false }
+ ]
+}
+```
+
+### **A.2. Server-to-Client: The `LayoutUpdate` Payload**
+
+For surgical modifications to the UI's structure, the server sends a `LayoutUpdate` payload with a simpler, custom operation set.
+
+- `operations`: An array of layout modification objects. Each object specifies an `op` (`add`, `remove`, `replace`), the nodes to modify, and targeting information.
+
+### **A.3. Delta Update Schemas**
+
+```json
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://example.com/GSP-delta-payloads-schema-v2.json",
+ "title": "GSP Delta Update Payloads Schema",
+ "description": "A collection of schemas for delta updates in the GSP protocol.",
+ "type": "object",
+ "$defs": {
+ "StateUpdate": {
+ "type": "object",
+ "properties": {
+ "operations": {
+ "type": "array",
+ "description": "A series of operations to apply to the state.",
+ "items": {
+ "oneOf": [
+ {
+ "type": "object",
+ "title": "State Set Operation",
+ "properties": {
+ "op": { "const": "stateSet" },
+ "path": { "type": "string" },
+ "value": {}
+ },
+ "required": ["op", "path", "value"]
+ },
+ {
+ "type": "object",
+ "title": "List Append Operation",
+ "properties": {
+ "op": { "const": "listAppend" },
+ "path": { "type": "string" },
+ "items": { "type": "array" }
+ },
+ "required": ["op", "path", "items"]
+ }
+ ]
+ }
+ }
+ },
+ "required": ["operations"]
+ },
+ "LayoutUpdate": {
+ "type": "object",
+ "properties": {
+ "operations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "op": {
+ "type": "string",
+ "enum": ["add", "remove", "replace"]
+ },
+ "nodes": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/LayoutNode"
+ }
+ },
+ "nodeIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "targetNodeId": {
+ "type": "string"
+ },
+ "targetProperty": {
+ "type": "string"
+ }
+ },
+ "required": ["op"]
+ }
+ }
+ },
+ "required": ["operations"]
+ }
+ }
+}
+```
+
+### **A.4. Client-Side Implementation for Delta Updates**
+
+When implementing client-side support for delta updates, the following components are necessary:
+
+- **Update Applier:** A component that processes incoming `StateUpdate` and `LayoutUpdate` payloads, applying the specified operations to the client's internal representation of the state and layout.
+- **Granular Rebuilds:** The client should be able to trigger a rebuild of only the specific widgets affected by a state change, rather than rebuilding the entire tree. This is a key performance benefit of the delta update mechanism.
diff --git a/packages/spikes/gulf_client/docs/gulf_protocol.md b/packages/spikes/gulf_client/docs/gulf_protocol.md
new file mode 100644
index 000000000..0387c5dde
--- /dev/null
+++ b/packages/spikes/gulf_client/docs/gulf_protocol.md
@@ -0,0 +1,1146 @@
+# GULF (Generative UI Language Format) Protocol
+
+A Specification for a JSONL-Based, Streaming UI Protocol
+
+Created: Sep 19, 2025
+
+## Design Requirements
+
+The GULF protocol should be a system where an LLM can stream a platform-agnostic, abstract UI definition to a client, which then renders it progressively using a native widget set. Every major design choice is traced back to the core challenges of LLM generation, perceived performance, and platform independence.
+
+### Requirement: The protocol must be easily generated by a Transformer Large Language Model (LLM)
+
+This is the most critical driver. "LLM-friendliness" is explicitly mentioned. This requirement directly leads to several design choices:
+
+Declarative, Simple Structure: The protocol should use a straightforward, declarative format ("this is a column with these children") rather than an imperative one ("now, add a column; then, append a text widget to it"). LLMs excel at generating structured, declarative data.
+
+Flat Component List (Adjacency List): Requiring an LLM to generate a perfectly nested JSON tree in a single pass is difficult and error-prone. A flat list of components, where relationships are defined by simple string IDs, is much easier to generate piece by piece. The model can "think" of a component, give it an ID, and then reference that ID later without worrying about tree depth or object nesting.
+
+Stateless Messages: Each JSONL message is a self-contained unit of information (componentUpdate, dataModelUpdate). This is ideal for a streaming LLM, which can output these messages incrementally as it processes a request.
+
+### Requirement: The UI must render progressively for a fast, responsive user experience
+
+The system must feel fast to the user, even if the full UI is complex and takes time to generate.
+
+Streaming via JSONL/SSE: This is a direct solution. The client doesn't have to wait for a single, massive JSON payload. It can start receiving and processing UI components immediately, improving perceived performance.
+
+### Requirement: The protocol must be platform-agnostic
+
+The same server-side logic should be able to render a UI on a Flutter app, a web browser, or potentially other platforms without modification.
+
+Client-Defined widget catalog: This is the core of the platform-agnostic design. The protocol should define an abstract component tree (e.g., "I need a Card with a Row inside"). It is the client's responsibility to map these abstract types to its native widget implementations (a Flutter Card widget, an HTML `` with card styling, etc.). The server only needs to know the names of the components the client supports.
+
+### Requirement: State management must be efficient and decoupled from the UI structure
+
+Changing a piece of text in the UI should not require resending the entire UI definition.
+
+Separation of Data and Components: Having distinct componentUpdate and data model update messages is key. The UI structure can be sent once, and subsequent updates can be small dataModelUpdate messages that only contain the changed data.
+
+### Requirement: The communication architecture must be robust and scalable
+
+The system needs a clear, reliable way to handle both server-pushed UI and client-initiated events.
+
+Unidirectional UI Stream: Using a one-way stream (SSE) for UI updates simplifies the client's logic. It only needs to listen and react. This is a more robust pattern for server-push than trying to manage a complex bidirectional channel.
+
+Out-of-Band Event Handling (REST API): Using a standard, stateless REST endpoint for user actions leverages a well-understood, highly scalable, and reliable web technology. It cleanly separates the concerns of UI rendering from event processing.
+
+## Introduction
+
+The GULF (Generative UI Language Format) Protocol is a protocol designed for rendering user interfaces from a stream of JSON objects sent from a server. Its core philosophy emphasizes a clean separation of UI structure and application data, enabling progressive rendering as the client processes each message.
+
+The protocol is designed to be "LLM-friendly," meaning its structure is declarative and straightforward, making it easy for a generative model to produce.
+
+Communication occurs via a JSON Lines (JSONL) stream. The client parses each line as a distinct message and incrementally builds the UI. The server-to-client protocol defines four message types:
+
+- `streamHeader`: Identifies the protocol version. This is always the first message.
+- `componentUpdate`: Provides a list of component definitions to be added to the client's component buffer.
+- `dataModelUpdate`: Provides new data to be inserted into or to replace the client's data model.
+- `beginRendering`: Signals to the client that it has enough information to perform the initial render, specifying the ID of the root component.
+
+Client-to-server communication for user interactions is handled separately via a `ClientEvent` message, sent as a JSON payload to a REST API. This keeps the primary data stream unidirectional.
+
+## Section 1: Foundational Architecture and Data Flow
+
+This document specifies the architecture and data formats for the GULF protocol. The design is guided by principles of strict separation of concerns, versioning, and progressive rendering.
+
+### 1.1. Core Philosophy: Decoupling and Contracts
+
+The central philosophy of GULF is the decoupling of three key elements:
+
+1. **The Component Tree (The Structure):** A server-provided tree of abstract components that describes the UI's structure. This is defined by `componentUpdate` messages.
+2. **The Data Model (The State):** A server-provided JSON object containing the dynamic values that populate the UI, such as text, booleans, or lists. This is managed via `dataModelUpdate` messages.
+3. **The Widget Registry (The "Catalog"):** A client-defined mapping of component types (e.g., "Row", "Text") to concrete, native widget implementations. This registry is **part of the client application**, not the protocol stream. The server must generate components that the target client's registry understands.
+
+### 1.2. The JSONL Stream: The Unit of Communication
+
+All UI descriptions are transmitted from the server to the client as a stream of JSON objects, formatted as JSON Lines (JSONL). Each line is a separate, compact JSON object representing a single message. This allows the client to parse and process each part of the UI definition as it arrives, enabling progressive rendering.
+
+### 1.3. Data Flow Model
+
+The GULF protocol is composed of a server-to-client stream describing UI and individual events sent to the server. The client consumes the stream, builds the UI, and renders it. Communication occurs via a JSON Lines (JSONL) stream, typically transported over **Server-Sent Events (SSE)**.
+
+1. **Server Stream:** The server begins sending the JSONL stream over an SSE connection.
+2. **Client-Side Buffering:** The client receives messages and buffers them:
+
+ - `streamHeader`: The client validates the version.
+ - `componentUpdate`: Component definitions are stored in a `Map`.
+ - `dataModelUpdate`: The client's internal JSON data model is built or updated.
+
+3. **Render Signal:** The server sends a `beginRendering` message with the `root` component's ID. This prevents a "flash of incomplete content." The client buffers incoming components and data but waits for this explicit signal before attempting the first render, ensuring the initial view is coherent.
+4. **Client-Side Rendering:** The client, now in a "ready" state, starts at the `root` component. It recursively walks the component tree by looking up component IDs in its buffer. It resolves any data bindings against the data model and uses its `WidgetRegistry` to instantiate native widgets.
+5. **User Interaction and Event Handling:** The user interacts with a rendered widget (e.g., taps a button). The client constructs a `ClientEvent` JSON payload, resolving any data bindings from the component's `action.context`. It sends this payload to a pre-configured REST API endpoint on the server via a `POST` request.
+6. **Dynamic Updates:** The server processes the `ClientEvent`. If the UI needs to change in response, the server sends new `componentUpdate` and `dataModelUpdate` messages over the original SSE stream. As these arrive, the client updates its component buffer and data model, and the UI re-renders to reflect the changes.
+
+```mermaid
+sequenceDiagram
+ participant Server
+ participant Client
+
+ Server->>+Client: SSE Connection (JSONL Stream)
+ Client->>Client: 1. Parse JSONL message
+ loop Until 'beginRendering'
+ Client->>Client: 2a. Process streamHeader (check version)
+ Client->>Client: 2b. Process componentUpdate (store in component map)
+ Client->>Client: 2c. Process dataModelUpdate (update data model)
+ end
+ Client->>Client: 3. Process beginRendering (rootId: 'root', isReady: true)
+ Note right of Client: 4. Triggers UI build
+ Client->>Client: 5. Build widget tree from 'root'
+ Client->>Client: 6. Resolve data bindings
+ Client->>Client: 7. Look up widgets in WidgetRegistry
+ Client-->>-Server: (UI is rendered)
+
+ Note over Client: 8. User interacts with UI (e.g., clicks button)
+ Client->>Client: 9. Construct ClientEvent payload
+ Client->>+Server: 10. POST /event (ClientEvent JSON)
+ Server-->>-Client: 11. HTTP 200 OK
+
+ loop Dynamic Updates in Response to Event
+ Server->>+Client: componentUpdate or dataModelUpdate (via SSE)
+ Client->>Client: Update component map or data model
+ Note right of Client: Triggers UI rebuild
+ Client-->>-Server: (UI is updated)
+ end
+```
+
+### 1.4. Full Stream Example
+
+The following is a complete, minimal example of a JSONL stream that renders a user profile card.
+
+```jsonl
+{"streamHeader": {"version": "1.0.0"}}
+{"componentUpdate": {"components": [{"id": "root", "componentProperties": {"Column": {"children": {"explicitList": ["profile_card"]}}}}]}}
+{"componentUpdate": {"components": [{"id": "profile_card", "componentProperties": {"Card": {"child": "card_content"}}}]}}
+{"componentUpdate": {"components": [{"id": "card_content", "componentProperties": {"Column": {"children": {"explicitList": ["header_row", "bio_text"]}}}}]}}
+{"componentUpdate": {"components": [{"id": "header_row", "componentProperties": {"Row": {"alignment": "center", "children": {"explicitList": ["avatar", "name_column"]}}}}]}}
+{"componentUpdate": {"components": [{"id": "avatar", "componentProperties": {"Image": {"url": {"literalString": "[https://www.example.com/profile.jpg)"}}}}]}}
+{"componentUpdate": {"components": [{"id": "name_column", "componentProperties": {"Column": {"alignment": "start", "children": {"explicitList": ["name_text", "handle_text"]}}}}]}}
+{"componentUpdate": {"components": [{"id": "name_text", "componentProperties": {"Heading": {"level": "3", "text": {"literalString": "Flutter Fan"}}}}]}}
+{"componentUpdate": {"components": [{"id": "handle_text", "componentProperties": {"Text": {"text": {"literalString": "@flutterdev"}}}}]}}
+{"componentUpdate": {"components": [{"id": "bio_text", "componentProperties": {"Text": {"text": {"literalString": "Building beautiful apps from a single codebase."}}}}]}}
+{"dataModelUpdate": {"contents": {}}}
+{"beginRendering": {"root": "root"}}
+```
+
+## Section 2: The Component Model
+
+Unlike protocols with an explicit catalog, GULF relies on a **client-defined** `WidgetRegistry`. The set of available component types (e.g., `Row`, `Text`, `Card`) and their supported properties is defined by the native client application. The server must generate `componentUpdate` messages that conform to the component set expected by the client.
+
+### 2.1. The `componentUpdate` Message
+
+This message is the primary way UI structure is defined. It contains a `components` array, which is a flat list of component instances.
+
+```json
+{
+ "componentUpdate": {
+ "components": [
+ {
+ "id": "unique-component-id",
+ "weight": 1.0,
+ "componentProperties": {
+ "Text": {
+ "text": { "literalString": "Hello, World!" }
+ }
+ }
+ },
+ {
+ "id": "another-component-id",
+ "componentProperties": { ... }
+ }
+ ]
+ }
+}
+```
+
+### 2.2. The Component Object
+
+Each object in the `components` array has the following structure:
+
+- `id`: A required, unique string that identifies this specific component instance. This is used for parent-child references.
+- `weight`: An optional number used by `Row` and `Column` containers to determine proportional sizing.
+- `componentProperties`: A required object that defines the component's type and properties.
+
+### 2.3.`componentProperties` (Discriminated Union)
+
+This object uses a "discriminated union" pattern. It **must** contain exactly one key, where the key is the string name of the component type (e.g., `"Text"`, `"Row"`, `"Button"`). The value of that key is an object containing the specific properties for that component.
+
+**Example:** A `Text` component:
+
+```json
+"componentProperties": {
+ "Text": {
+ "text": { "literalString": "This is text" }
+ }
+}
+```
+
+A `Button` component:
+
+```json
+"componentProperties": {
+ "Button": {
+ "label": { "literalString": "Click Me" },
+ "action": { "action": "submit_form" }
+ }
+}
+```
+
+The full set of available component types and their properties is defined by the `gulf_schema.json`.
+
+## Section 3: UI Composition
+
+### 3.1. The Adjacency List Model
+
+The GULF protocol defines the UI as a flat list of components. The tree structure is built implicitly using ID references. This is known as an adjacency list model.
+
+Container components (like `Row`, `Column`, `List`, `Card`) have properties that reference the `id` of their child component(s). The client is responsible for storing all components in a map (e.g., `Map`) and recreating the tree structure at render time.
+
+This model allows the server to send component definitions in any order, as long as all necessary components are present by the time `beginRendering` is sent.
+
+```mermaid
+flowchart TD
+ subgraph "Server Stream (JSONL)"
+ A("componentUpdate
components: [root, title, button]")
+ B("beginRendering
root: 'root'")
+ end
+
+ subgraph "Client-Side Buffer (Map)"
+ C("root: {id: 'root', type: Column, children: ['title', 'button']}")
+ D("title: {id: 'title', type: Text, text: 'Welcome'}")
+ E("button: {id: 'button', type: Button, label: 'Go'}")
+ end
+
+ subgraph "Rendered Widget Tree"
+ F(Column) --> G(Text: 'Welcome')
+ F --> H(Button: 'Go')
+ end
+
+ A -- "Parsed and stored" --> C
+ A -- "Parsed and stored" --> D
+ A -- "Parsed and stored" --> E
+
+ B -- "Triggers build from buffer" --> F
+```
+
+### 3.2. Container Children: `explicitList` vs. `template`
+
+Container components (`Row`, `Column`, `List`) define their children using a `children` object, which must contain _either_ `explicitList` or `template`.
+
+- `explicitList`: An array of component `id` strings. This is used for static, known children.
+- `template`: An object used to render a dynamic list of children from a data-bound list.
+
+```json
+// Example: Children property from gulf_schema.json
+"children": {
+ "description": "Defines the children... You MUST define EITHER 'explicitList' OR 'template'",
+ "properties": {
+ "explicitList": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "template": {
+ "type": "object",
+ "properties": {
+ "componentId": { "type": "string" },
+ "dataBinding": { "type": "string" }
+ },
+ "required": ["componentId", "dataBinding"]
+ }
+ }
+}
+```
+
+### 3.3. Dynamic List Rendering with `template`
+
+To render dynamic lists, a container uses the `template` property.
+
+1. `dataBinding`: A path to a list in the data model (e.g., `user.posts`).
+2. `componentId`: The `id` of another component in the buffer to use as a template for each item in the list.
+
+The client will iterate over the list at `dataBinding` and, for each item, render the component specified by `componentId`. The item's data is made available to the template component for relative data binding.
+
+## Section 4: Dynamic Data & State Management
+
+GULF enforces a clean separation between the UI's structure (components) and its dynamic data (data model).
+
+### 4.1. The `dataModelUpdate` Message
+
+This message is the only way to modify the client's data model.
+
+- `contents`: The JSON content to be inserted.
+- `path`: An optional, dot-separated path string.
+
+ - If `path` is `null` or empty, the `contents` will **completely replace** the entire data model.
+ - If `path` is provided (e.g., `user.name` or `user.addresses[0].street`), the client will traverse the data model and insert the `contents` at that specific location, creating nested objects/lists as needed.
+
+#### Example 1: Replacing the root data model
+
+```json
+{
+ "dataModelUpdate": {
+ "contents": {
+ "user": { "name": "Alice" },
+ "posts": []
+ }
+ }
+}
+```
+
+#### Example 2: Updating a specific path
+
+```json
+{
+ "dataModelUpdate": {
+ "path": "user.name",
+ "contents": "Bob"
+ }
+}
+```
+
+### 4.2. Data Binding (The `BoundValue` Object)
+
+Components connect to the data model through binding. Any property that can be data-bound (like `text` on a `Text` component) accepts an object that defines either a literal value or a data path.
+
+From `gulf_schema.json`, a bound `text` property looks like this:
+
+```json
+"text": {
+ "properties": {
+ "path": { "type": "string" },
+ "literalString": { "type": "string" }
+ }
+}
+```
+
+A component can also bind to numbers (`literalNumber`), booleans (`literalBoolean`), or arrays (`literalArray`).
+
+- If `literalString` is provided, the value is static.
+
+ ```json
+ "text": { "literalString": "Hello" }
+ ```
+
+- If `path` is provided, the value is dynamic and resolved from the data model.
+
+ ```json
+ "text": { "path": "user.name" }
+ ```
+
+The client's interpreter is responsible for resolving these paths against the data model before rendering. The GULF protocol supports direct 1:1 binding; it does not include transformers (e.g., formatters, conditionals). Any data transformation must be performed by the server before sending it in a `dataModelUpdate`.
+
+## Section 5: Event Handling
+
+While the server-to-client UI definition is a one-way stream (e.g., over SSE), user interactions are communicated back to the server using a separate, out-of-band mechanism. This is typically a standard REST API endpoint where the client sends a `POST` request.
+
+### 5.1. The `ClientEvent` Message
+
+When a user interacts with a component (like a `Button`), the client is responsible for constructing and sending a `ClientEvent` message as a JSON payload to a pre-configured API endpoint.
+
+The `ClientEvent` object has the following structure:
+
+- `actionName` (string, required): The name of the action, taken directly from the `action.action` property of the component (e.g., "submit_form").
+- `sourceComponentId` (string, required): The `id` of the component that triggered the event (e.g., "my_button").
+- `timestamp` (string, required): An ISO 8601 timestamp of when the event occurred (e.g., "2025-09-19T17:01:00Z").
+- `resolvedContext` (object, required): A JSON object containing the key-value pairs from the component's `action.context`, after resolving all `BoundValue`s against the data model.
+
+### 5.2. Resolving the `action.context`
+
+The `action` property on components like `Button` (as defined in `gulf_schema.json`) contains an optional `context` array.
+
+```json
+"action": {
+ "description": "Represents a user-initiated action.",
+ "properties": {
+ "action": { "type": "string" },
+ "context": {
+ "type": "array",
+ "items": {
+ "properties": {
+ "key": { "type": "string" },
+ "value": {
+ "description": "A BoundValue (path or literal)"
+ ...
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+Before sending the `ClientEvent`, the client **must** iterate over this `context` array and build the `resolvedContext` object.
+
+1. For each item in the `context` array:
+2. Use the item's `key` as the key in the `resolvedContext` object.
+3. Resolve the item's `value` (a `BoundValue`):
+
+ - If it's a `literalString`, `literalNumber`, etc., use that value directly.
+ - If it's a `path`, resolve that path against the current data model.
+ - If the path is invalid, the value should be `null`.
+
+### 5.3. Event Flow Example
+
+1. **Component Definition** (from `componentUpdate`):
+
+ ```json
+ {
+ "id": "submit_btn",
+ "componentProperties": {
+ "Button": {
+ "label": { "literalString": "Submit" },
+ "action": {
+ "action": "submit_form",
+ "context": [
+ { "key": "userInput", "value": { "path": "form.textField" } },
+ { "key": "formId", "value": { "literalString": "f-123" } }
+ ]
+ }
+ }
+ }
+ }
+ ```
+
+2. **Data Model** (from `dataModelUpdate`):
+
+ ```json
+ {
+ "form": {
+ "textField": "User input text"
+ }
+ }
+ ```
+
+3. **User Action:** The user taps the "submit_btn" button.
+4. **Client-Side Resolution:** The client resolves the `action.context`:
+
+ - `userInput`: Resolves `form.textField` to `"User input text"`.
+ - `formId`: Resolves `literalString` to `"f-123"`.
+
+5. **Client-to-Server Request:** The client sends a `POST` request to `https://api.example.com/handle_event` with the following JSON body:
+
+ ```json
+ {
+ "actionName": "submit_form",
+ "sourceComponentId": "submit_btn",
+ "timestamp": "2025-09-19T17:05:00Z",
+ "resolvedContext": {
+ "userInput": "User input text",
+ "formId": "f-123"
+ }
+ }
+ ```
+
+6. **Server Response:** The server processes this event. If the UI needs to change as a result, the server sends new `componentUpdate` or `dataModelUpdate` messages over the **separate SSE stream**.
+
+## Section 6: Client-Side Implementation
+
+A robust client-side interpreter for GULF should be composed of several key components:
+
+- **JSONL Parser:** A parser capable of reading the stream line by line and decoding each line as a separate JSON object.
+- **Message Dispatcher:** A mechanism (e.g., a `switch` statement) to identify the message type (`streamHeader`, `componentUpdate`, etc.) and route it to the correct handler.
+- **Component Buffer:** A `Map` that stores all component instances by their `id`. This is populated by `componentUpdate` messages.
+- **Data Model Store:** A `Map` (or similar) that holds the application state. This is built and modified by `dataModelUpdate` messages.
+- **Interpreter State:** A state machine to track if the client is ready to render (e.g., a `_isReadyToRender` boolean that is set to `true` by `beginRendering`).
+- **`**WidgetRegistry**`**:\*\*\*\* A developer-provided map (e.g., `Map`) that associates component type strings ("Row", "Text") with functions that build native widgets.
+- **Binding Resolver:** A utility that can take a `BoundValue` (e.g., `{ "path": "user.name" }`) and resolve it against the Data Model Store.
+- **Event Handler:** A function, exposed to the `WidgetRegistry`, that constructs and sends the `ClientEvent` message to the configured REST API endpoint.
+
+## Section 7: Complete GULF JSON Schema
+
+This section provides the formal, consolidated JSON Schema (`gulf_schema.json`) for a single message in the GULF JSONL stream. Each line in the stream must be a valid JSON object that conforms to this schema.
+
+```json
+{
+ "title": "A2A UI Protocol Message",
+ "description": "A single message in the A2A streaming UI protocol. Exactly ONE of the properties in this object must be set, corresponding to the specific message type.",
+ "type": "object",
+ "properties": {
+ "streamHeader": {
+ "title": "StreamHeader Message",
+ "description": "A schema for a StreamHeader message in the A2A streaming UI protocol.",
+ "type": "object",
+ "properties": {
+ "version": {
+ "type": "string",
+ "description": "The version of the protocol. This property is REQUIRED."
+ }
+ },
+ "required": ["version"]
+ },
+ "beginRendering": {
+ "title": "BeginRendering Message",
+ "description": "A schema for a BeginRendering message in the A2A streaming UI protocol. This message signals that the UI can now be rendered and provides initial root component and styling information.",
+ "type": "object",
+ "properties": {
+ "root": {
+ "type": "string",
+ "description": "The ID of the root component from which rendering should begin. This is a reference to a component instance by its unique ID. This property is REQUIRED."
+ },
+ "styles": {
+ "type": "object",
+ "description": "An object containing styling information for the UI.",
+ "properties": {
+ "font": {
+ "type": "string",
+ "description": "The primary font to be used throughout the UI."
+ },
+ "logoUrl": {
+ "type": "string",
+ "description": "A URL pointing to the logo image to be displayed."
+ },
+ "primaryColor": {
+ "type": "string",
+ "description": "The primary color for the UI, specified as a hexadecimal color code (e.g., '#00BFFF').",
+ "pattern": "^#[0-9a-fA-F]{6}$"
+ }
+ }
+ }
+ },
+ "required": ["root"]
+ },
+ "componentUpdate": {
+ "title": "ComponentUpdate Message",
+ "description": "A schema for a ComponentUpdate message in the A2A streaming UI protocol.",
+ "type": "object",
+ "properties": {
+ "components": {
+ "type": "array",
+ "description": "A flat list of all component instances available for rendering. Components reference each other by ID. This property is REQUIRED.",
+ "items": {
+ "description": "A specific instance of a ComponentType with its own unique ID and properties.",
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "A unique identifier for this component instance. This property is REQUIRED."
+ },
+ "weight": {
+ "type": "number",
+ "description": "The proportional weight of this component within its container. This property may ONLY be used when the component is direct child of either a Row or a Column"
+ },
+ "componentProperties": {
+ "type": "object",
+ "description": "Defines the properties for a specific component type. Exactly ONE of the properties in this object must be set.",
+ "properties": {
+ "Heading": {
+ "type": "object",
+ "properties": {
+ "text": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalString": {
+ "type": "string",
+ "description": "A fixed, hardcoded string value."
+ }
+ }
+ },
+ "level": {
+ "type": "string",
+ "enum": ["1", "2", "3", "4", "5"],
+ "description": "The semantic importance level."
+ }
+ },
+ "required": ["text"]
+ },
+ "Text": {
+ "type": "object",
+ "properties": {
+ "text": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalString": {
+ "type": "string",
+ "description": "A fixed, hardcoded string value."
+ }
+ }
+ }
+ },
+ "required": ["text"]
+ },
+ "Image": {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalString": {
+ "type": "string",
+ "description": "A fixed, hardcoded string value."
+ }
+ }
+ }
+ },
+ "required": ["url"]
+ },
+ "Video": {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalString": {
+ "type": "string",
+ "description": "A fixed, hardcoded string value."
+ }
+ }
+ }
+ },
+ "required": ["url"]
+ },
+ "AudioPlayer": {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalString": {
+ "type": "string",
+ "description": "A fixed, hardcoded string value."
+ }
+ }
+ },
+ "description": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalString": {
+ "type": "string",
+ "description": "A fixed, hardcoded string value."
+ }
+ },
+ "description": "A label, title, or placeholder text."
+ }
+ },
+ "required": ["url"]
+ },
+ "Row": {
+ "type": "object",
+ "properties": {
+ "children": {
+ "type": "object",
+ "description": "Defines the children of the container. You MUST define EITHER 'explicitList' OR 'template', but not both.",
+ "properties": {
+ "explicitList": {
+ "type": "array",
+ "description": "An explicit list of component instance IDs.",
+ "items": {
+ "type": "string",
+ "description": "A reference to a component instance by its unique ID."
+ }
+ },
+ "template": {
+ "type": "object",
+ "description": "A template to be rendered for each item in a data-bound list.",
+ "properties": {
+ "componentId": {
+ "type": "string",
+ "description": "The ID of the component (from the main 'components' list) to use as a template for each item."
+ },
+ "dataBinding": {
+ "type": "string",
+ "description": "A data binding reference to a list within the data model (e.g., '/user/posts')."
+ }
+ },
+ "required": ["componentId", "dataBinding"]
+ }
+ }
+ },
+ "distribution": {
+ "type": "string",
+ "enum": [
+ "start",
+ "center",
+ "end",
+ "spaceBetween",
+ "spaceAround",
+ "spaceEvenly"
+ ],
+ "description": "Distribution of items along the main axis."
+ },
+ "alignment": {
+ "type": "string",
+ "enum": ["start", "center", "end", "stretch"],
+ "description": "Alignment of items/child along the cross axis."
+ }
+ },
+ "required": ["children"]
+ },
+ "Column": {
+ "type": "object",
+ "properties": {
+ "children": {
+ "type": "object",
+ "description": "Defines the children of the container. You MUST define EITHER 'explicitList' OR 'template', but not both.",
+ "properties": {
+ "explicitList": {
+ "type": "array",
+ "description": "An explicit list of component instance IDs.",
+ "items": {
+ "type": "string",
+ "description": "A reference to a component instance by its unique ID."
+ }
+ },
+ "template": {
+ "type": "object",
+ "description": "A template to be rendered for each item in a data-bound list.",
+ "properties": {
+ "componentId": {
+ "type": "string",
+ "description": "The ID of the component (from the main 'components' list) to use as a template for each item."
+ },
+ "dataBinding": {
+ "type": "string",
+ "description": "A data binding reference to a list within the data model (e.g., '/user/posts')."
+ }
+ },
+ "required": ["componentId", "dataBinding"]
+ }
+ }
+ },
+ "distribution": {
+ "type": "string",
+ "enum": [
+ "start",
+ "center",
+ "end",
+ "spaceBetween",
+ "spaceAround",
+ "spaceEvenly"
+ ],
+ "description": "Distribution of items along the main axis."
+ },
+ "alignment": {
+ "type": "string",
+ "enum": ["start", "center", "end", "stretch"],
+ "description": "Alignment of items/child along the cross axis."
+ }
+ },
+ "required": ["children"]
+ },
+ "List": {
+ "type": "object",
+ "properties": {
+ "children": {
+ "type": "object",
+ "description": "Defines the children of the container. You MUST define EITHER 'explicitList' OR 'template', but not both.",
+ "properties": {
+ "explicitList": {
+ "type": "array",
+ "description": "An explicit list of component instance IDs.",
+ "items": {
+ "type": "string",
+ "description": "A reference to a component instance by its unique ID."
+ }
+ },
+ "template": {
+ "type": "object",
+ "description": "A template to be rendered for each item in a data-bound list.",
+ "properties": {
+ "componentId": {
+ "type": "string",
+ "description": "The ID of the component (from the main 'components' list) to use as a template for each item."
+ },
+ "dataBinding": {
+ "type": "string",
+ "description": "A data binding reference to a list within the data model (e.g., '/user/posts')."
+ }
+ },
+ "required": ["componentId", "dataBinding"]
+ }
+ }
+ },
+ "direction": {
+ "type": "string",
+ "enum": ["vertical", "horizontal"],
+ "default": "vertical",
+ "description": "The direction of the list."
+ },
+ "alignment": {
+ "type": "string",
+ "enum": ["start", "center", "end", "stretch"],
+ "description": "Alignment of items/child along the cross axis."
+ }
+ },
+ "required": ["children"]
+ },
+ "Card": {
+ "type": "object",
+ "properties": {
+ "child": {
+ "type": "string",
+ "description": "A reference to a single component instance by its unique ID."
+ }
+ },
+ "required": ["child"]
+ },
+ "Tabs": {
+ "type": "object",
+ "properties": {
+ "tabItems": {
+ "type": "array",
+ "description": "A list of tabs, each with a title and a child component ID.",
+ "items": {
+ "type": "object",
+ "properties": {
+ "title": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalString": {
+ "type": "string",
+ "description": "A fixed, hardcoded string value."
+ }
+ },
+ "description": "The title of the tab."
+ },
+ "child": {
+ "type": "string",
+ "description": "A reference to a component instance by its unique ID."
+ }
+ },
+ "required": ["title", "child"]
+ }
+ }
+ },
+ "required": ["tabItems"]
+ },
+ "Divider": {
+ "type": "object",
+ "properties": {
+ "axis": {
+ "type": "string",
+ "enum": ["horizontal", "vertical"],
+ "default": "horizontal",
+ "description": "The orientation."
+ }
+ }
+ },
+ "Modal": {
+ "type": "object",
+ "properties": {
+ "entryPointChild": {
+ "type": "string",
+ "description": "The ID of the component (e.g., a button) that triggers the modal."
+ },
+ "contentChild": {
+ "type": "string",
+ "description": "The ID of the component to display as the modal's content."
+ }
+ },
+ "required": ["entryPointChild", "contentChild"]
+ },
+ "Button": {
+ "type": "object",
+ "properties": {
+ "label": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalString": {
+ "type": "string",
+ "description": "A fixed, hardcoded string value."
+ }
+ }
+ },
+ "action": {
+ "type": "object",
+ "description": "Represents a user-initiated action.",
+ "properties": {
+ "action": {
+ "type": "string",
+ "description": "A unique name identifying the action (e.g., 'submitForm')."
+ },
+ "context": {
+ "type": "array",
+ "description": "A key-value map of data bindings to be resolved when the action is triggered.",
+ "items": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string"
+ },
+ "value": {
+ "type": "object",
+ "description": "The dynamic value. Define EXACTLY ONE of the nested properties.",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalString": {
+ "type": "string",
+ "description": "A fixed, hardcoded string value."
+ },
+ "literalNumber": {
+ "type": "number"
+ },
+ "literalBoolean": {
+ "type": "boolean"
+ }
+ }
+ }
+ },
+ "required": ["key", "value"]
+ }
+ }
+ },
+ "required": ["action"]
+ }
+ },
+ "required": ["label", "action"]
+ },
+ "CheckBox": {
+ "type": "object",
+ "properties": {
+ "label": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalString": {
+ "type": "string",
+ "description": "A fixed, hardcoded string value."
+ }
+ }
+ },
+ "value": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalBoolean": {
+ "type": "boolean"
+ }
+ }
+ }
+ },
+ "required": ["label", "value"]
+ },
+ "TextField": {
+ "type": "object",
+ "properties": {
+ "text": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalString": {
+ "type": "string",
+ "description": "A fixed, hardcoded string value."
+ }
+ }
+ },
+ "label": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalString": {
+ "type": "string",
+ "description": "A fixed, hardcoded string value."
+ }
+ },
+ "description": "A label, title, or placeholder text."
+ },
+ "type": {
+ "type": "string",
+ "enum": ["shortText", "number", "date", "longText"]
+ },
+ "validationRegexp": {
+ "type": "string",
+ "description": "A regex string to validate the input."
+ }
+ },
+ "required": ["label"]
+ },
+ "DateTimeInput": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalString": {
+ "type": "string",
+ "description": "A fixed, hardcoded string value."
+ }
+ }
+ },
+ "enableDate": {
+ "type": "boolean",
+ "default": true
+ },
+ "enableTime": {
+ "type": "boolean",
+ "default": false
+ },
+ "outputFormat": {
+ "type": "string",
+ "description": "The string format for the output (e.g., 'YYYY-MM-DD')."
+ }
+ },
+ "required": ["value"]
+ },
+ "MultipleChoice": {
+ "type": "object",
+ "properties": {
+ "selections": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalArray": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "options": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "label": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalString": {
+ "type": "string",
+ "description": "A fixed, hardcoded string value."
+ }
+ }
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": ["label", "value"]
+ }
+ },
+ "maxAllowedSelections": {
+ "type": "integer",
+ "default": 1
+ }
+ },
+ "required": ["selections"]
+ },
+ "Slider": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "A data binding reference to a location in the data model (e.g., '/user/name')."
+ },
+ "literalNumber": {
+ "type": "number"
+ }
+ }
+ },
+ "minValue": {
+ "type": "number",
+ "default": 0
+ },
+ "maxValue": {
+ "type": "number",
+ "default": 100
+ }
+ },
+ "required": ["value"]
+ }
+ }
+ }
+ },
+ "required": ["id", "componentProperties"]
+ }
+ }
+ },
+ "required": ["components"]
+ },
+ "dataModelUpdate": {
+ "title": "Data model update",
+ "description": "Sets or replaces the data model at a specified path with new content.",
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "An optional path to a location within the data model where the content should be inserted or replaced. The path is represented as a dot-separated string and can include array indexing (e.g., 'user.addresses[0].street'). If this field is omitted, the entire data model will be replaced with the provided 'contents'."
+ },
+ "contents": {
+ "description": "The JSON content to be placed at the specified path. This property is REQUIRED. This can be any valid JSON value (object, array, string, number, boolean, or null). The content at the target path will be completely replaced by this new value."
+ }
+ },
+ "required": ["contents"]
+ }
+ }
+}
+```