Back to skills
SkillHub ClubAnalyze Data & AIFull StackData / AI

castella-a2ui

Render A2UI JSON as native Castella widgets. Parse A2UI messages, handle actions, progressive rendering, data binding, and connect to A2UI-enabled agents.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
39
Hot score
90
Updated
March 20, 2026
Overall rating
C2.1
Composite score
2.1
Best-practice grade
C65.6

Install command

npx @skill-hub/cli install i2y-castella-castella-a2ui

Repository

i2y/castella

Skill path: skills/castella-a2ui

Render A2UI JSON as native Castella widgets. Parse A2UI messages, handle actions, progressive rendering, data binding, and connect to A2UI-enabled agents.

Open repository

Best for

Primary workflow: Analyze Data & AI.

Technical facets: Full Stack, Data / AI.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: i2y.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install castella-a2ui into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/i2y/castella before adding castella-a2ui to shared team environments
  • Use castella-a2ui for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: castella-a2ui
description: Render A2UI JSON as native Castella widgets. Parse A2UI messages, handle actions, progressive rendering, data binding, and connect to A2UI-enabled agents.
---

# Castella A2UI Integration

A2UI (Agent-to-User Interface) enables AI agents to generate rich, interactive UIs by outputting JSON component descriptions. Castella renders these natively across desktop, web, and terminal platforms.

**When to use**: "render A2UI JSON", "A2UI component", "A2UIRenderer", "A2UI data binding", "A2UI streaming", "connect to A2UI agent", "updateDataModel", "A2UIClient"

## Quick Start

Render A2UI JSON to a Castella widget:

```python
from castella import App
from castella.a2ui import A2UIRenderer, A2UIComponent
from castella.frame import Frame

renderer = A2UIRenderer()
widget = renderer.render_json({
    "components": [
        {"id": "root", "component": "Column", "children": {"explicitList": ["text1"]}},
        {"id": "text1", "component": "Text", "text": {"literalString": "Hello A2UI!"}}
    ],
    "rootId": "root"
})

App(Frame("A2UI Demo", 800, 600), widget).run()
```

## Core Concepts

### A2UIRenderer

The main class for converting A2UI messages to Castella widgets:

```python
from castella.a2ui import A2UIRenderer, UserAction

def on_action(action: UserAction):
    print(f"Action: {action.name}")
    print(f"Source: {action.source_component_id}")
    print(f"Context: {action.context}")

renderer = A2UIRenderer(on_action=on_action)
```

### Value Types

A2UI uses typed values for properties:

```python
# Literal values (static)
{"text": {"literalString": "Hello"}}
{"value": {"literalNumber": 42}}
{"checked": {"literalBoolean": True}}

# Data binding (dynamic, JSON Pointer RFC 6901)
{"text": {"path": "/user/name"}}
{"value": {"path": "/counter"}}
```

Helper functions:

```python
from castella.a2ui import literal, binding

literal("Hello")      # {"literalString": "Hello"}
literal(42)           # {"literalNumber": 42}
literal(True)         # {"literalBoolean": True}
binding("/counter")   # {"path": "/counter"}
```

## Supported Components

| A2UI Component | Castella Widget | Notes |
|----------------|-----------------|-------|
| Text | Text | usageHint: h1-h5, body, caption |
| Button | Button | action with context |
| TextField | Input/MultilineInput | usageHint: password, multiline |
| CheckBox | CheckBox | Two-way binding |
| Slider | Slider | min/max range |
| DateTimeInput | DateTimeInput | Date/time picker |
| ChoicePicker | RadioButtons/Column | Single/multiple selection |
| Image | NetImage | URL-based images |
| Icon | Text | Material Icons → emoji |
| Divider | Spacer | Horizontal/vertical |
| Row | Row | Horizontal layout |
| Column | Column | Vertical layout |
| Card | Column | Container with styling |
| List | Column | TemplateChildren support |
| Tabs | Tabs | Tabbed navigation |
| Modal | Modal | Overlay dialog |
| Markdown | Markdown | Rich text (Castella extension) |

See `references/components.md` for detailed component reference.

## Data Binding

Bind widget values to a data model using JSON Pointer paths:

```python
a2ui_json = {
    "components": [
        {"id": "root", "component": "Column", "children": {"explicitList": ["greeting"]}},
        {"id": "greeting", "component": "Text", "text": {"path": "/message"}}
    ],
    "rootId": "root"
}

# Provide initial data
initial_data = {"/message": "Hello, World!"}
widget = renderer.render_json(a2ui_json, initial_data=initial_data)
```

## Actions

Handle user interactions via actions:

```python
{
    "id": "submit_btn",
    "component": "Button",
    "text": {"literalString": "Submit"},
    "action": {"name": "submit", "context": ["/formData"]}
}
```

Action handler receives `UserAction`:

```python
def on_action(action: UserAction):
    print(action.name)                  # "submit"
    print(action.source_component_id)   # "submit_btn"
    print(action.context)               # ["/formData"]
```

## updateDataModel

Update bound values dynamically:

```python
renderer.handle_message({
    "updateDataModel": {
        "surfaceId": "default",
        "data": {"/message": "Updated message!"}
    }
})
```

## A2UIComponent (Reactive)

Wrap surface in A2UIComponent for automatic UI updates:

```python
from castella.a2ui import A2UIComponent

renderer.render_json(a2ui_json, initial_data=initial_data)
surface = renderer.get_surface("default")
component = A2UIComponent(surface)  # Auto-updates on data changes

App(Frame("A2UI", 800, 600), component).run()
```

## TemplateChildren (Dynamic Lists)

Render lists from data arrays:

```python
a2ui_json = {
    "components": [
        {"id": "root", "component": "Column", "children": {"explicitList": ["user_list"]}},
        {"id": "user_list", "component": "List", "children": {
            "path": "/users",           # JSON Pointer to array
            "componentId": "user_item"  # Template component
        }},
        {"id": "user_item", "component": "Text", "text": {"path": "name"}}  # Relative path
    ],
    "rootId": "root"
}

initial_data = {"/users": [{"name": "Alice"}, {"name": "Bob"}, {"name": "Charlie"}]}
widget = renderer.render_json(a2ui_json, initial_data=initial_data)
```

## ChoicePicker

Single or multiple selection:

```python
# Single selection (renders as RadioButtons)
{
    "id": "color_picker",
    "component": "ChoicePicker",
    "choices": [
        {"literalString": "Red"},
        {"literalString": "Green"},
        {"literalString": "Blue"}
    ],
    "selected": {"literalString": "Red"},
    "allowMultiple": False
}

# Multiple selection (renders as CheckBox list)
{
    "id": "toppings",
    "component": "ChoicePicker",
    "choices": [
        {"literalString": "Cheese"},
        {"literalString": "Pepperoni"},
        {"literalString": "Mushrooms"}
    ],
    "selected": {"path": "/selectedToppings"},
    "allowMultiple": True
}
```

## Progressive Rendering (Streaming)

Handle JSONL streams for incremental UI updates:

```python
# From JSONL string
jsonl_content = """
{"beginRendering": {"surfaceId": "main", "root": "root"}}
{"updateComponents": {"surfaceId": "main", "components": [...]}}
"""
surface = renderer.handle_jsonl(jsonl_content)

# From file
with open("ui.jsonl") as f:
    surface = renderer.handle_stream(f, on_update=lambda s: app.redraw())

# From async SSE stream
from castella.a2ui.transports import sse_stream
surface = await renderer.handle_stream_async(await sse_stream(url))
```

Message sequence:
1. `beginRendering` - Start progressive render
2. `updateComponents` - Add/update components
3. `updateDataModel` - Update data values

See `references/streaming.md` for transport details.

## A2UIClient

Connect to A2A agents with A2UI extension:

```python
from castella.a2ui import A2UIClient, A2UIComponent, UserAction

def on_action(action: UserAction):
    print(f"Action: {action.name}")

client = A2UIClient("http://localhost:10002", on_action=on_action)
surface = client.send("Find restaurants in Tokyo")

if surface:
    App(Frame("Restaurant Finder", 800, 600), A2UIComponent(surface)).run()
```

Async usage:

```python
async def main():
    client = A2UIClient("http://localhost:10002")
    surface = await client.send_async("Hello!")

    if surface:
        # Send action back to agent
        await client.send_action_async(action)
```

## A2UI 0.9 Compatibility

Castella auto-normalizes A2UI 0.9 spec format:

```python
# A2UI 0.9 format (plain values) - accepted
{"text": "Hello", "children": ["a", "b"]}

# Castella internal format (wrapped) - also accepted
{"text": {"literalString": "Hello"}, "children": {"explicitList": ["a", "b"]}}
```

Key normalizations:
- `text: "Hello"` → `text: {literalString: "Hello"}`
- `children: ["a", "b"]` → `children: {explicitList: ["a", "b"]}`
- `usageHint: "shortText"` → `usageHint: "text"`
- `usageHint: "obscured"` → `usageHint: "password"`

## TextField usageHint

```python
# Password field (masked ●●●●)
{"id": "password", "component": "TextField", "usageHint": "password"}

# Multiline field
{"id": "comments", "component": "TextField", "usageHint": "multiline"}
```

## Best Practices

1. **Use A2UIComponent wrapper** for reactive data binding
2. **Provide initial_data** for TemplateChildren/List components
3. **Handle actions** to update data model dynamically
4. **Use semantic IDs** - A2UI component IDs become MCP semantic IDs
5. **Test with mock data** before connecting to live agents

## Installation

```bash
uv sync --extra agent   # A2UI + A2A support
```

## Reference

- `references/components.md` - Complete A2UI component reference
- `references/messages.md` - A2UI message types
- `references/streaming.md` - Streaming and transports
- `scripts/` - Executable examples (basic_a2ui.py, a2ui_form.py, a2ui_list.py)
- A2UI Specification: https://a2ui.org/specification/v0.9-a2ui/


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/components.md

```markdown
# A2UI Component Reference

Complete reference for all 17 A2UI components supported by Castella.

## Text Components

### Text

Display text content with optional styling.

```python
{
    "id": "title",
    "component": "Text",
    "text": {"literalString": "Hello, World!"},
    "usageHint": "h1"  # Optional: h1, h2, h3, h4, h5, body, caption
}
```

**Properties:**
- `text` - String value (literal or binding)
- `usageHint` - Styling hint: h1-h5 (headings), body, caption

### Icon

Display icon (Material Icons mapped to emoji).

```python
{
    "id": "icon1",
    "component": "Icon",
    "name": {"literalString": "home"}
}
```

**Icon mappings:**
- `home` → 🏠
- `search` → 🔍
- `settings` → ⚙️
- `person` → 👤
- `email` → 📧
- `phone` → 📱
- `star` → ⭐
- `check` → ✓
- `close` → ✕
- `arrow_back` → ←
- `arrow_forward` → →

### Markdown

Rich markdown rendering (Castella extension).

```python
{
    "id": "content",
    "component": "Markdown",
    "text": {"literalString": "# Title\n\n**Bold** text"}
}
```

## Input Components

### TextField

Single or multi-line text input.

```python
# Single line
{
    "id": "name_input",
    "component": "TextField",
    "text": {"path": "/name"},
    "usageHint": "text"
}

# Password (masked)
{
    "id": "password",
    "component": "TextField",
    "text": {"path": "/password"},
    "usageHint": "password"
}

# Multiline
{
    "id": "comments",
    "component": "TextField",
    "text": {"path": "/comments"},
    "usageHint": "multiline"
}
```

**usageHint values:**
- `text` (default) - Single line
- `password` / `obscured` - Masked input
- `multiline` - Multi-line editor

### CheckBox

Toggle checkbox.

```python
{
    "id": "agree",
    "component": "CheckBox",
    "checked": {"path": "/agreed"},
    "label": {"literalString": "I agree to the terms"}
}
```

### Slider

Range slider.

```python
{
    "id": "volume",
    "component": "Slider",
    "value": {"path": "/volume"},
    "min": {"literalNumber": 0},
    "max": {"literalNumber": 100}
}
```

### DateTimeInput

Date and/or time picker.

```python
{
    "id": "appointment",
    "component": "DateTimeInput",
    "value": {"path": "/datetime"},
    "enableDate": True,
    "enableTime": True
}
```

### ChoicePicker

Single or multiple selection from options.

```python
# Single selection (RadioButtons)
{
    "id": "size",
    "component": "ChoicePicker",
    "choices": [
        {"literalString": "Small"},
        {"literalString": "Medium"},
        {"literalString": "Large"}
    ],
    "selected": {"literalString": "Medium"},
    "allowMultiple": False
}

# Multiple selection (CheckBox list)
{
    "id": "toppings",
    "component": "ChoicePicker",
    "choices": [
        {"literalString": "Cheese"},
        {"literalString": "Pepperoni"},
        {"literalString": "Mushrooms"}
    ],
    "selected": {"path": "/selectedToppings"},
    "allowMultiple": True
}
```

## Button Component

### Button

Clickable button with action.

```python
{
    "id": "submit_btn",
    "component": "Button",
    "text": {"literalString": "Submit"},
    "action": {
        "name": "submit",
        "context": ["/formData"]
    }
}
```

**Properties:**
- `text` - Button label
- `action` - Action definition
  - `name` - Action identifier
  - `context` - Array of data paths to include

## Layout Components

### Row

Horizontal layout.

```python
{
    "id": "row1",
    "component": "Row",
    "children": {"explicitList": ["child1", "child2", "child3"]}
}
```

### Column

Vertical layout.

```python
{
    "id": "col1",
    "component": "Column",
    "children": {"explicitList": ["child1", "child2"]}
}
```

### Card

Container with visual styling.

```python
{
    "id": "card1",
    "component": "Card",
    "children": {"explicitList": ["content"]},
    "title": {"literalString": "Card Title"}
}
```

### Divider

Visual separator.

```python
{
    "id": "divider1",
    "component": "Divider",
    "orientation": "horizontal"  # or "vertical"
}
```

## List Component

### List

Dynamic list with template children.

```python
{
    "id": "user_list",
    "component": "List",
    "children": {
        "path": "/users",           # Data array path
        "componentId": "user_item"  # Template component ID
    }
}

# Template component (relative paths)
{
    "id": "user_item",
    "component": "Row",
    "children": {"explicitList": ["user_name", "user_email"]}
}
{
    "id": "user_name",
    "component": "Text",
    "text": {"path": "name"}  # Relative to list item
}
{
    "id": "user_email",
    "component": "Text",
    "text": {"path": "email"}
}
```

**Initial data:**
```python
{
    "/users": [
        {"name": "Alice", "email": "[email protected]"},
        {"name": "Bob", "email": "[email protected]"}
    ]
}
```

## Navigation Components

### Tabs

Tabbed navigation.

```python
{
    "id": "tabs1",
    "component": "Tabs",
    "tabs": [
        {"id": "home", "label": {"literalString": "Home"}, "content": "home_content"},
        {"id": "settings", "label": {"literalString": "Settings"}, "content": "settings_content"}
    ],
    "selected": {"literalString": "home"}
}
```

### Modal

Overlay dialog.

```python
{
    "id": "modal1",
    "component": "Modal",
    "title": {"literalString": "Confirm"},
    "content": {"explicitList": ["modal_content"]},
    "open": {"path": "/modalOpen"}
}
```

## Media Components

### Image

Display image from URL.

```python
{
    "id": "photo",
    "component": "Image",
    "src": {"literalString": "https://example.com/image.png"},
    "alt": {"literalString": "Photo description"}
}
```

## Value Types Summary

### Literal Values

```python
{"literalString": "text"}     # String
{"literalNumber": 42}         # Number
{"literalBoolean": True}      # Boolean
```

### Data Binding

```python
{"path": "/absolute/path"}    # Absolute JSON Pointer
{"path": "relative/path"}     # Relative (in List templates)
```

### Children

```python
{"explicitList": ["id1", "id2"]}  # Static children

{"path": "/items", "componentId": "template"}  # TemplateChildren
```

```

### references/streaming.md

```markdown
# A2UI Streaming and Transports

Progressive rendering with JSONL streams and various transport protocols.

## JSONL Format

A2UI messages stream as newline-delimited JSON:

```
{"beginRendering": {"surfaceId": "main", "root": "root"}}
{"updateComponents": {"surfaceId": "main", "components": [...]}}
{"updateComponents": {"surfaceId": "main", "components": [...]}}
{"updateDataModel": {"surfaceId": "main", "data": {...}}}
```

Each line is a complete JSON message.

## JSONLParser

Parse JSONL strings:

```python
from castella.a2ui import parse_jsonl_string

messages = parse_jsonl_string(jsonl_content)
for msg in messages:
    print(msg)  # Dict
```

## Streaming from File

```python
from castella.a2ui import A2UIRenderer

renderer = A2UIRenderer()

with open("ui.jsonl") as f:
    surface = renderer.handle_stream(
        f,
        on_update=lambda s: app.redraw()  # Called after each message
    )

widget = surface.root_widget
```

## SSE Transport

Server-Sent Events for HTTP streaming:

```python
from castella.a2ui import A2UIRenderer
from castella.a2ui.transports import sse_stream

renderer = A2UIRenderer()

async def main():
    stream = await sse_stream("http://agent.example.com/ui")
    surface = await renderer.handle_stream_async(stream)
    return surface.root_widget
```

**Requirements:**
```bash
uv sync --extra agent  # Includes httpx
```

## WebSocket Transport

Bidirectional WebSocket streaming:

```python
from castella.a2ui.transports import websocket_stream

async def main():
    stream = await websocket_stream("ws://agent.example.com/ui")
    surface = await renderer.handle_stream_async(stream)
```

**Requirements:**
```bash
pip install websockets
```

## Custom Stream

Create custom async generator:

```python
async def my_stream():
    async for chunk in some_source:
        yield chunk

surface = await renderer.handle_stream_async(my_stream())
```

## A2UIClient Streaming

Connect to A2A agents with streaming:

```python
from castella.a2ui import A2UIClient

client = A2UIClient("http://localhost:10002")

# Streaming response
async for update in client.send_stream("Tell me a story"):
    # update is surface with incremental changes
    app.redraw()
```

## Sync Stream Handling

For synchronous code:

```python
def stream_generator():
    for line in response.iter_lines():
        yield line.decode()

surface = renderer.handle_stream(
    stream_generator(),
    on_update=lambda s: print("Updated!")
)
```

## Progressive Rendering Pattern

```python
from castella import App
from castella.a2ui import A2UIRenderer, A2UIComponent
from castella.frame import Frame

renderer = A2UIRenderer()
app = None

def on_update(surface):
    if app:
        app.redraw()

async def start_stream():
    with open("stream.jsonl") as f:
        surface = renderer.handle_stream(f, on_update=on_update)
    return A2UIComponent(surface)

# Initialize with placeholder, update with stream
component = A2UIComponent(renderer.get_surface("default"))
app = App(Frame("Streaming", 800, 600), component)

# Start stream in background
import asyncio
asyncio.create_task(start_stream())

app.run()
```

## Error Handling

```python
from castella.a2ui import A2UIConnectionError, A2UIParseError

try:
    surface = await renderer.handle_stream_async(stream)
except A2UIConnectionError as e:
    print(f"Connection failed: {e}")
except A2UIParseError as e:
    print(f"Invalid JSONL: {e}")
```

## Performance Tips

1. **Batch component updates** - Send multiple components per `updateComponents`
2. **Use data binding** - `updateDataModel` is cheaper than `updateComponents`
3. **Minimize re-renders** - Group related updates in single message
4. **Buffer appropriately** - Don't send too many tiny messages

## SSE Server Example (Python)

Simple SSE server for testing:

```python
from flask import Flask, Response
import json
import time

app = Flask(__name__)

@app.route('/ui')
def stream_ui():
    def generate():
        yield f"data: {json.dumps({'beginRendering': {'surfaceId': 'main', 'root': 'root'}})}\n\n"

        components = [
            {"id": "root", "component": "Column", "children": {"explicitList": ["msg"]}},
            {"id": "msg", "component": "Text", "text": {"literalString": "Loading..."}}
        ]
        yield f"data: {json.dumps({'updateComponents': {'surfaceId': 'main', 'components': components}})}\n\n"

        time.sleep(1)

        yield f"data: {json.dumps({'updateDataModel': {'surfaceId': 'main', 'data': {'/status': 'Complete!'}}})}\n\n"

    return Response(generate(), mimetype='text/event-stream')

if __name__ == '__main__':
    app.run(port=8080)
```

```

### references/messages.md

```markdown
# A2UI Message Types

Reference for all A2UI message types and their usage.

## createSurface

Create a complete UI surface at once.

```python
{
    "createSurface": {
        "surfaceId": "main",
        "components": [
            {"id": "root", "component": "Column", ...},
            {"id": "text1", "component": "Text", ...}
        ],
        "rootId": "root"
    }
}
```

**Fields:**
- `surfaceId` - Unique identifier for the surface
- `components` - Array of component definitions
- `rootId` - ID of the root component

## beginRendering

Signal the start of progressive rendering.

```python
{
    "beginRendering": {
        "surfaceId": "main",
        "root": "root"
    }
}
```

**Fields:**
- `surfaceId` - Surface identifier
- `root` - Root component ID

Use with `updateComponents` for incremental UI construction.

## updateComponents

Add or update components (progressive rendering).

```python
{
    "updateComponents": {
        "surfaceId": "main",
        "components": [
            {"id": "header", "component": "Text", "text": {"literalString": "Header"}},
            {"id": "content", "component": "Column", "children": {"explicitList": ["item1"]}}
        ]
    }
}
```

**Fields:**
- `surfaceId` - Surface identifier
- `components` - Array of component definitions to add/update

Components with existing IDs are replaced.

## updateDataModel

Update data binding values.

```python
{
    "updateDataModel": {
        "surfaceId": "main",
        "data": {
            "/counter": 42,
            "/user/name": "Alice",
            "/items": [{"id": 1}, {"id": 2}]
        }
    }
}
```

**Fields:**
- `surfaceId` - Surface identifier
- `data` - Object with JSON Pointer paths as keys

All bound widgets update automatically.

## deleteSurface

Remove a surface.

```python
{
    "deleteSurface": {
        "surfaceId": "main"
    }
}
```

## Message Handling

### render_json()

Create surface from single JSON:

```python
renderer = A2UIRenderer()
widget = renderer.render_json({
    "components": [...],
    "rootId": "root"
}, initial_data={"/counter": 0})
```

### handle_message()

Process individual messages:

```python
renderer.handle_message({
    "updateDataModel": {
        "surfaceId": "default",
        "data": {"/counter": 10}
    }
})
```

### handle_jsonl()

Process JSONL string:

```python
jsonl = """
{"beginRendering": {"surfaceId": "main", "root": "root"}}
{"updateComponents": {"surfaceId": "main", "components": [...]}}
"""
surface = renderer.handle_jsonl(jsonl)
```

### handle_stream()

Process stream (file, generator):

```python
with open("ui.jsonl") as f:
    surface = renderer.handle_stream(f, on_update=callback)
```

### handle_stream_async()

Process async stream:

```python
surface = await renderer.handle_stream_async(async_stream)
```

## Surface Management

### get_surface()

Retrieve surface by ID:

```python
surface = renderer.get_surface("main")
if surface:
    widget = surface.root_widget
    data = surface.data_model
```

### Surface Properties

```python
surface.id              # Surface ID
surface.root_widget     # Root Castella widget
surface.data_model      # Current data model dict
surface.components      # Component definitions
```

## Action Messages

When user interacts with action-enabled components:

```python
def on_action(action: UserAction):
    # action.name - Action name (e.g., "submit")
    # action.source_component_id - Component ID that triggered
    # action.context - Context data from action definition

    # Respond with data update
    renderer.handle_message({
        "updateDataModel": {
            "surfaceId": "default",
            "data": {"/status": "Submitted!"}
        }
    })
```

## Progressive Rendering Sequence

Typical streaming sequence:

```
1. {"beginRendering": {"surfaceId": "chat", "root": "root"}}
2. {"updateComponents": {"surfaceId": "chat", "components": [root, header]}}
3. {"updateComponents": {"surfaceId": "chat", "components": [message1]}}
4. {"updateComponents": {"surfaceId": "chat", "components": [message2]}}
5. {"updateDataModel": {"surfaceId": "chat", "data": {"/status": "Complete"}}}
```

Each `updateComponents` triggers UI refresh with new components.

```

castella-a2ui | SkillHub