Skip to content

Embedded records

Three display-only widgets show related information inside a resolver form. Their keys are dropped from the submitted payload, they stay out of the schema’s required array, and they are declared readOnly: true. The invoke and roster widgets follow the same rules.

Widget Tokens What it shows
link x-lt-href A named link to any dashboard view, with a pre-applied deep-link query
escalation x-lt-source, x-lt-fields One embedded escalation record with a facts panel
escalation-list x-lt-query, x-lt-columns, x-lt-actions A compact list of escalations from a facet query, with optional inline actions

The top-level x-lt-submit-guard holds the resolve while a related query still returns rows.

Renders a named anchor whose URL is resolved from a {{domain.path}} template at render time. Use it to give reviewers a one-click path to a related queue, filtered to the current item.

{
"originator_queue": {
"type": "string",
"readOnly": true,
"x-lt-widget": "link",
"x-lt-href": "/escalations/available?role=rel-originator&facets={\"orderId\":\"{{metadata.orderId}}\"}",
"x-lt-span": 2,
"x-lt-section": "Related",
"title": "View originator queue for this order",
"description": "Opens the originator queue filtered to this order"
}
}

Required. A URL template. {{domain.path}} tokens interpolate from the same five domains as x-lt-help (escalation, metadata, envelope, payload, resolver), resolved against the current form’s escalation context.

A path starting with / navigates inside the dashboard. Any other URL opens in a new tab with rel="noreferrer". A URL that interpolates to blank renders a muted “No link configured” placeholder.

The field’s title is the link label, and description renders as a one-line helper beneath it. Without a title, description is the label.

Embeds one escalation record as a compact card. x-lt-source resolves the escalation ID from the current form’s context; x-lt-fields picks the facts the card shows.

{
"parent_escalation": {
"type": "string",
"readOnly": true,
"x-lt-widget": "escalation",
"x-lt-source": "metadata.parent_escalation_id",
"x-lt-fields": [
{ "label": "Order ID", "value": "{{metadata.orderId}}" },
{ "label": "Decision", "value": "{{resolver.decision}}" },
{ "label": "Reason", "value": "{{resolver.reason}}" },
{ "label": "Age", "value": "{{escalation.created_at}}", "format": "age" }
],
"x-lt-span": 2,
"x-lt-section": "Related",
"title": "Originating request",
"description": "The escalation that created this item"
}
}

Required. A "domain.path" expression, the same convention as the checklist widget, resolved against the current form’s context to the embedded escalation’s UUID. "metadata.parent_escalation_id" reads parent_escalation_id from the row’s metadata.

When the path resolves to nothing, or the escalation is missing or outside the viewer’s roles, the widget renders a quiet “No linked record” placeholder.

Optional. An array of { label, value, format? }, the same shape as x-lt-active.fields and x-lt-card.fields in list schemas. The value tokens resolve against the embedded escalation’s own data:

Domain Resolves against
escalation The embedded escalation’s columns: escalation.status, escalation.role, escalation.type
metadata Its metadata
envelope Its workflow envelope
payload Its context payload
resolver Its resolver payload

format: "age" renders an ISO timestamp as a compact relative age (12m, 3h). Without x-lt-fields, the card shows the type and status header, the description and the footer.

esc-type · subtype [status]
─────────────────────────────
The escalation description
─────────────────────────────
Order ID ORD-123
Decision Approved
─────────────────────────────
rel-originator · 2h ago Detail →

Embeds a compact, unpaginated list of escalations from a facet query. Use it to show sibling items: pending work in a related role, filtered to the same customer, order or any other metadata facet.

{
"sibling_items": {
"type": "string",
"readOnly": true,
"x-lt-widget": "escalation-list",
"x-lt-query": {
"role": "rel-originator",
"facets": { "customerId": "{{metadata.customerId}}" },
"status": "pending",
"limit": 5
},
"x-lt-columns": [
{ "label": "Order", "value": "{{metadata.orderId}}" },
{ "label": "Status", "value": "{{escalation.status}}" },
{ "label": "Age", "value": "{{escalation.created_at}}", "format": "age" }
],
"x-lt-span": 2,
"x-lt-section": "Related",
"title": "Other pending items for this customer",
"description": "Up to 5 items waiting in the originator queue"
}
}

Required. String values inside facets interpolate {{domain.path}} tokens against the current item’s context.

Field Type Description
role string Role queue to query
status string pending (default), resolved, cancelled, expired
facets Record<string, string> Metadata containment filter; values interpolate tokens
assigned "me" | "any" Ownership scope, below
limit number Max rows to show (default 5)
available boolean Legacy availability flag, honored only when assigned is omitted

The widget stays idle (no list, no request) until role or a facet value is set, which keeps every query bounded.

Ownership is one more dimension of the same faceted query. “Claimed” is the implied status: pending, with a live claim window.

  • "me": rows claimed by the viewing user. An earlier step assigned a set of order items to whoever claimed the batch; the follow-up form shows that person their own items with working inline actions.
  • "any": every matching row, whatever its claim state.
  • Omitted: available rows only (unclaimed, or the claim window lapsed).

The escalation-list widget, its x-lt-actions and x-lt-submit-guard share one mapping from query to rows, so a guard and an embed declaring the same query always agree on count and rows. Inline actions on rows claimed by the viewer resolve through the standard endpoint as the claim holder, with RBAC and enforce_schema applied.

"x-lt-query": {
"role": "order-items",
"facets": { "orderId": "{{metadata.orderId}}" },
"assigned": "me"
}

Optional. An array of { label, value, format? }, the same shape as x-lt-columns in list schemas. Tokens resolve against each displayed escalation’s own context (its row, metadata, envelope, payload, resolver). Without it, three default columns render: Description, Role, Age. Each row links to the escalation’s detail page.

Optional. Inline row actions: each row gains a button that resolves that escalation with a canned payload through the standard resolve endpoint. RBAC and enforce_schema apply exactly as for a full-form resolve.

"x-lt-actions": [
{
"label": "Picked ✓",
"resolverPayload": {
"approved": true,
"checks": { "picked": true, "scanned": true },
"orderId": "{{metadata.orderId}}"
},
"confirm": "Mark {{metadata.sku}} picked for {{metadata.orderId}}?"
}
]
Field Type Description
label string Button text
resolverPayload object The canned payload. String leaves interpolate {{domain.path}} against the row’s own context; booleans and numbers pass through typed
confirm string Optional confirmation asked beside the row before firing; tokens interpolate per row

On success the row updates in place. A rejected resolve (validation failure, lost claim) shows the server’s message in the row, and the detail link stays available for rejects and anything the canned payload cannot express.

Declare one action for the common path and leave exceptions to the full form. A batch of N sibling items becomes N clicks on one page.

An action limited by roles belongs on a roster, whose actions run on the server where the limit is checked. The escalation list offers actions without roles.

  • Loading: three skeleton rows.
  • Empty: a muted “No items found” message.

A top-level form schema token, a peer of x-lt-help, that holds the resolve while related work is open. The submit stays disabled while the declared query returns rows, with the message and live count beside it. When the last row resolves, the live update reruns the query and the button enables.

{
"x-lt-submit-guard": {
"query": {
"role": "order-items",
"facets": { "orderId": "{{metadata.orderId}}" },
"assigned": "me"
},
"mustBeEmpty": true,
"message": "{{count}} item(s) still pending. Pick them before closing the order.",
"autoResolveWhenEmpty": true
},
"type": "object",
"properties": {}
}
Field Type Description
query object Same shape as x-lt-query, including assigned; facet values interpolate against the host escalation
mustBeEmpty boolean The gate condition (default true)
message string Shown beside the disabled submit; {{count}} is the live row count, and {{domain.path}} tokens interpolate
autoResolveWhenEmpty boolean Submit the claimed parent the moment the query is confirmed empty
roster string In place of query: a roster field whose outstanding entries hold the submit; {{count}} is the outstanding count. A roster guard does not auto-submit

Pair the guard with an escalation-list embed on the same query so the operator sees which rows hold the gate.

Only a confirmed empty read clears the gate: a successful query returning zero rows. While loading, or on an error or a 403, the submit stays disabled.

Enforced on both sides. The dashboard disables the submit. For enforce_schema roles the API runs the same query and answers a resolve with a canonical 422 while it returns rows, so the rule holds through the raw API. The server counts the true children whatever the resolver’s read scope, so a role that cannot see them cannot clear the gate. A resolve with no resolving user (an MCP resolve of an assigned: "me" guard) and roles without enforce_schema rely on the consuming workflow’s own check (reject and re-park with the remainder). Send to Triage stays available, for when the guarded work is itself the problem.

autoResolveWhenEmpty closes the loop. The claimed parent submits itself the moment the query is confirmed empty, rechecked on page load and after each inline child resolve. A person clears the children in place with x-lt-actions, and the parent closes with no extra click. A parent launched from a list with x-lt-submit-on-claim claims, shows its children and closes as they drain; a parent with an x-lt-transition then hands off to the follow-on. The parent’s own form must still validate; an incomplete parent waits for the person.

The embed widgets compose with the rest of the vocabulary:

{
"related_link": {
"type": "string",
"readOnly": true,
"x-lt-widget": "link",
"x-lt-href": "/escalations/available?role=rel-originator&facets={\"orderId\":\"{{metadata.orderId}}\"}",
"x-lt-span": 2,
"x-lt-section": "Related resources",
"x-lt-showIf": "metadata.orderId",
"title": "Open originator queue for this order"
},
"parent_esc": {
"type": "string",
"readOnly": true,
"x-lt-widget": "escalation",
"x-lt-source": "metadata.parent_escalation_id",
"x-lt-span": 2,
"x-lt-section": "Related resources",
"x-lt-showIf": "metadata.parent_escalation_id",
"title": "Originating escalation"
}
}

x-lt-showIf hides an embed when its data is absent: the embedded escalation when parent_escalation_id is missing, or the list when metadata carries no customerId.

RBAC is enforced on the server. The escalation and escalation-list widgets render only what the viewer’s roles permit. An escalation the viewer cannot access renders “No linked record”, the same as an unknown ID.

The link widget builds a client-side URL; the queue it opens applies its own RBAC.

The related-escalations example workflow in the long-tail repo uses all three widgets in a two-stage review followed by a claimed walk (forms.ts and forms-walk.ts hold the schemas). The walk is the ownership-scope reference:

  1. Three item rows park in one role, each faceted with the walk’s originId.
  2. A walk-claim row parks in a second role. Resolving it is the “start walk” button: the resolver’s identity reaches the workflow through $resolution, and one atomic bulk assign claims every item for that person.
  3. The closeout row declares the walk query once, { role, facets: { originId: "{{metadata.originId}}" }, assigned: "me" }, consumed by the embedded list, its inline actions and the submit guard. The walker marks each item in place, the count beside the locked submit falls with each click, and the last item unlocks the submit.

The journey is two form submits and N inline clicks on one page.