AI Progress & Confirmation components
Part of the tool-ui component set (Batch AB4-B). Four vanilla ES modules in site/frontend/ai/components/progress/ that render tool-call payloads as rich UI inside an AI thread. Each exports a def object the tool-call runtime uses to match and render payloads.
Component contract. Every file exportsconst def = { name, category, title, match(payload), render(payload, ctx), example }.matchreturnstruewhen the payload belongs to this component.renderreturns a DOMElement.ctxis feature-detected (may beundefinedwhen previewing outside a runtime).
Progress components
Progress components are read-only — they display state but do not send receipts.
Plan
File: site/frontend/ai/components/progress/plan.js Name: plan | Category: Progress
Renders a labelled checklist with status icons for each step.
Payload
{
"title": "string (optional)",
"steps": [
{ "label": "string", "status": "pending | active | done | failed" }
]
}
| Field | Type | Required | Notes | |---|---|---|---| | title | string | no | Card heading | | steps | array | yes | At least one element | | steps[].label | string | yes | Step description | | steps[].status | "pending" \| "active" \| "done" \| "failed" | yes | Drives icon + colour |
Status icons: ✓ done · ▶ active · ○ pending · ✗ failed
Receipt: none (read-only)
Example
{
"title": "Deploy pipeline",
"steps": [
{ "label": "Build binary", "status": "done" },
{ "label": "Run tests", "status": "done" },
{ "label": "Upload artifact", "status": "active" },
{ "label": "Restart service", "status": "pending" },
{ "label": "Smoke check", "status": "pending" }
]
}
Progress Tracker
File: site/frontend/ai/components/progress/progress_tracker.js Name: progress_tracker | Category: Progress
Renders a labelled progress bar. Accepts a fractional value (value / total) or a direct percentage. Optionally renders step labels below the bar.
Payload — fractional form
{ "label": "string (optional)", "value": 3, "total": 10 }
Payload — percent form
{ "label": "string (optional)", "percent": 30 }
Payload — with step labels
{
"label": "Processing files",
"value": 3,
"total": 10,
"steps": ["Parse", "Validate", "Transform", "Upload"]
}
| Field | Type | Required | Notes | |---|---|---|---| | label | string | no | Displayed above the bar | | value | number | one of | Used with total for fraction | | total | number | no | Denominator; shows value / total counter | | percent | number | one of | 0–100; takes precedence when present | | steps | string[] | no | Labels shown beneath bar as a stepper |
When neither value nor total can yield a percent, the bar renders as indeterminate (animated).
Receipt: none (read-only)
Example
{
"label": "Processing files",
"value": 3,
"total": 10,
"steps": ["Parse", "Validate", "Transform", "Upload"]
}
Confirmation components
Confirmation components are interactive. On the user's decision they call ctx.sendReceipt(obj) (feature-detected) and dispatch a CustomEvent('toolui:confirm', { detail, bubbles: true }) on the root element. After a decision the buttons are disabled and a badge shows the outcome.
Approval Card
File: site/frontend/ai/components/progress/approval_card.js Name: approval_card | Category: Confirmation
Renders a titled card with Approve / Reject buttons. Set danger: true for destructive actions — the card border turns red and the Approve button renders in danger styling.
Payload
{
"title": "Deploy to production?",
"summary": "This will restart the server and apply the pending migration.",
"details": "Estimated downtime: ~30 seconds. 3 migrations will run.",
"danger": true
}
| Field | Type | Required | Notes | |---|---|---|---| | title | string | yes | Card heading | | summary | string | yes | Short description of the action | | details | string | no | Secondary detail text; shown below a divider | | danger | boolean | no | Enables danger styling; defaults to false |
Receipt
{ "approved": true }
or
{ "approved": false }
Sent via ctx.sendReceipt and CustomEvent('toolui:confirm').
Example
{
"title": "Deploy to production?",
"summary": "This will restart the server and apply the pending migration.",
"details": "Estimated downtime: ~30 seconds. 3 migrations will run.",
"danger": true
}
Order Summary
File: site/frontend/ai/components/progress/order_summary.js Name: order_summary | Category: Confirmation
Renders an itemized order table with subtotal, tax, and total rows, followed by a Confirm button. Amounts are formatted with Intl.NumberFormat for the page locale.
Payload
{
"items": [
{ "name": "Widget Pro", "qty": 2, "price": 9.99 },
{ "name": "Gadget Plus", "qty": 1, "price": 24.50 }
],
"subtotal": 44.48,
"tax": 4.00,
"total": 48.48,
"currency": "USD"
}
| Field | Type | Required | Notes | |---|---|---|---| | items | array | yes | At least one item | | items[].name | string | yes | Item name | | items[].qty | number | yes | Quantity | | items[].price | number | yes | Unit price | | subtotal | number | no | Shown above tax row when present | | tax | number | no | Shown above total when present | | total | number | yes | Bold total row | | currency | string | no | ISO 4217 code; defaults to "USD" |
Receipt
{ "confirmed": true }
Sent via ctx.sendReceipt and CustomEvent('toolui:confirm').
Example
{
"items": [
{ "name": "Widget Pro", "qty": 2, "price": 9.99 },
{ "name": "Gadget Plus", "qty": 1, "price": 24.50 }
],
"subtotal": 44.48,
"tax": 4.00,
"total": 48.48,
"currency": "USD"
}
Registering components
Import the def object and register it with the tool-call runtime:
import { def as plan } from '/ai/components/progress/plan.js';
import { def as progressTracker } from '/ai/components/progress/progress_tracker.js';
import { def as approvalCard } from '/ai/components/progress/approval_card.js';
import { def as orderSummary } from '/ai/components/progress/order_summary.js';
runtime.register(plan, progressTracker, approvalCard, orderSummary);
The runtime calls def.match(payload) in registration order; the first match wins. On no match the runtime falls back to a JSON code view.