Card catalog
A card is one JSON object, the same everywhere: DGI returns it as ui from dgi.chat.turn, and a Buzz actor posts it inside its buzz-ui block (Option C). This page lists every renderer, what it draws, and the answers it sends. Structured chat is part of DGI and is Alpha.
Rules for every card
- Built in code, never by a model. DGI builds each card from its typed output and from the full result of the read. Chart and KPI numbers come from data.
- Views are chosen by the shape of the data. Jev picks the view from what the read returned: a record becomes a
detail, a long list adatagrid, nodes and edges adag. An explicit request ("as a chart"), a form, a confirm or a build always wins. - Offered actions only. A card sends an action only when its
actionslist offers it. Otherwise the control shows disabled, or not at all. - Opaque refs, never ids. Rows, nodes, tables, related lists, saved queries and timeline items carry a
refsuch asr1,n2,t3,rel1orsq1. The client sends it back and never draws it. What it stands for stays on the server. - Prompts and views.
form,confirmandselectare prompts: answered once, within 30 minutes. Every other card is a view: it never expires in the client, and nothing sent from it consumes it. - Unknown renderer: show the text. The text that comes with a card always stands on its own.
Where each card is drawn
| Client | Cards |
|---|---|
| Hosted chat page (Option A) | All of them. The newer views need the current page: run buzz.space.publish-chat (with also_site_slug: true when people use the slug address) after an upgrade |
<BuzzChat> (Option B) | All of them from @ltinteg/app-component-chat 0.9.0. Earlier versions draw the newer views as text |
| Your own client (Option C, Option D) | What you implement. document is returned by dgi.chat.* only |
Prompts
form
Asks for the typed inputs of a workflow. Field types: text, textarea, number, email, url, date, time, select, multiselect, boolean, file. Required fields are marked, and submit stays disabled until they are filled. Long forms become a wizard (pending_input.steps, "Step n of m"), and a form can open in a dialog (surface: "modal"). A file field uploads through the chat's own media host and submits the resulting https URL.
| The person can | Sends |
|---|---|
| Fill and submit | submit with the values. Empty fields are left out; booleans are always sent |
| Cancel | cancel |
confirm
Shows a proposed change before it runs: a one-line summary, the workflow, and its inputs as plain labels. A destructive action is styled as a warning.
With a diff. Every update-style proposal carries pending_action.diff: the current values (read with the matching get workflow, as the caller, when one exists) next to the proposed ones. The card shows the diff in place of the plain input list. Without a matching get, the diff says the current values were not read. Ids and fields named like a secret never show. The diff is display only: the confirm still runs the stored input and its action_input_hash.
| The person can | Sends |
|---|---|
| Confirm | confirm. The server runs its own stored copy; values are ignored |
| Cancel | cancel |
| Edit, when offered | edit with the changed inputs. DGI proposes again on a new card, with a new hash |
select
Pick one option, drawn as buttons or a dropdown. Sends select with {value, label, prompt?}.
actionlist
A list of items, each with a title, a subtitle and its own buttons. Sends select with the item's index as item.
Views
table
Rows from a read, with a toolbar (Refresh, and Live in the Buzz chat) and up to 4 row actions per row, such as Details, Comment or Assign. On a narrow card each row gets a menu instead of inline buttons.
| The person can | Sends |
|---|---|
| Refresh | refresh. The card is rewritten in place |
| Live, Stop (Buzz chat only) | live, stop |
| Press a row action | row_action with {row, action}. A read shows its view; a write asks with a form or proposes a confirm |
chart
A bar, line or pie chart, drawn inline, with a legend and a hidden data table for screen readers. At most 6 series and 60 points. A read with a time column, one to six number columns and 3 to 60 rows becomes a line chart by itself. Bar and pie charts keep the top points (12 by default) and group the rest as "Other". Card actions: refresh, and in the Buzz chat live and stop.
kpi
Up to 8 tiles, each with a value, a unit, a delta and a trend arrow (up, down or flat). A total that hits the read's own limit is labeled as capped, for example "Total (capped at 500)". Card actions as for chart.
logs
Monospace text, scrollable, with a copy button. At most 12,000 characters. Card actions as for chart.
run
A live card for a workflow run DGI started in this conversation, usually right after a confirm: status, current state, steps and times. It updates in place until the run ends. A failure shows a short code, never the error text or the run's data. It accepts no answer.
link
One button that opens an https address in a new tab, for example to set up a connection in the console. Only addresses on the organization console are posted. It accepts no answer.
composition
The step list of a composition you are editing, with actions per step and for the whole plan. It appears only for replies that run as an organization member with compose on. When the steps carry edges, the plan draws as a diagram, with a switch back to the list. Step and plan actions send select (with item for a step's action).
document
The turn's markdown text, presented as a card, for answers that are a document rather than data. Returned by dgi.chat.*. It accepts no answer.
dag
A workflow diagram: nodes with a status (pending, running, succeeded, failed, skipped), edges with optional labels, laid out left to right or top to bottom. The client supports pan, zoom and fit. At most 200 nodes and 600 edges; the rest is counted. DGI draws it from a run's DAG (with each step's status), a composition plan, or any read that returns nodes and edges.
| The person can | Sends |
|---|---|
| Pick a node, when offered | select with {node: "<ref>"}. DGI opens what the node stands for |
schema
An entity diagram: each table as a field list with primary and foreign key markers, and each foreign key as an edge. At most 60 tables and 40 fields per table. DGI draws it from reads that return tables with fields, such as data.appdata.structure.query.
| The person can | Sends |
|---|---|
| Pick a table | select with {node: "<table ref>"}. A table usually opens its records |
datagrid
A grid paged by the server, for results larger than a table shows (more than 50 rows) or reads that page themselves. Columns are typed (text, number, date, bool, json, badge) and each says whether it is sortable and filterable. 50 rows a page, at most 500 rows and 40 columns per page. The client offers copy cell and a CSV export of the current page.
| The person can | Sends |
|---|---|
| Sort by a sortable column | query with {sort: {key, dir}} |
| Filter a filterable column | query with {filters: {column: text}} ({} clears) |
| Go to the next or previous page | query with {cursor}, a cursor the card issued |
| Open a row | The row action whose key is open: row_action with {row, action, ref} |
| Press another row action | row_action with {row, action, ref} |
Sorting, filtering and paging never happen in the client: the server re-runs the stored read and rewrites the card in place. A total that hit a cap reads "N of 1,000+".
query
A read-only SQL console over AppData, for organization members only. The statement editor sits above the result, drawn as a datagrid with its own paging. It always shows a "Read-only" badge; a console that is not read-only is never drawn.
| The person can | Sends |
|---|---|
| Run the statement | submit with {statement}, their own text, up to 16,384 characters |
| Pick a saved query | select with {saved: "<ref>"} |
| Sort, filter or page the result | query, as on a datagrid |
DGI never runs SQL a model wrote. When a model or Jev tries to start the query workflow, the console opens with the statement filled in, not run. A statement runs only when the person presses Run or picks a saved query. A refused or failed statement comes back on the card as error.
diff
Before and after, per field: the removed value struck through, the added one highlighted, numbers with their delta, and unchanged fields collapsed. At most 100 fields. It accepts no answer. The same shape appears inside a confirm card as pending_action.diff.
detail
One record: a title, a subtitle, up to 8 badges, up to 20 sections of labeled fields, and up to 20 related lists with a count. DGI draws it for a single record, such as the result of a *.get read.
| The person can | Sends |
|---|---|
| Open a related list | select with {related: "<ref>"} |
timeline
A waterfall: bars on a relative time axis, nested by depth (0 to 8), each with a status. An item still running draws to the end of the window. At most 300 items. DGI draws it from reads that return spans or steps with a start and an end, such as a trace.
| The person can | Sends |
|---|---|
| Pick an item | select with {item: "<ref>"} |
View controls and the query action
A read card (table, chart, kpi, datagrid, timeline) can carry controls, drawn as a compact bar under the title. DGI adds a control only for an input the read itself declares:
| Control | kind | Appears for | Sends |
|---|---|---|---|
| Date range | date_range | A since and until pair (or from and to) | {params: {<key>: {from, to}}}, in order, at most 366 days |
| Relative range | segmented | A lone since: 24h, 7d, 30d or 90d, recomputed at every rerun | {params: {<key>: "<value>"}} |
| Segmented or chips | segmented, chips | An enum input such as status (segmented includes All) | A string, or the full list for chips |
| Group by | segmented | A chart or KPI | The field to group by |
At most 6 controls of 12 options each.
Every change sends the query card action. Its rules:
- It never consumes the card. The server answers by rewriting the same card in place (same
ui_id), like a refresh. - Everything is checked against the stored card: a sort key must be a sortable column and
dirascordesc; a filter key a filterable column (up to 200 characters); a cursor one this card issued for its current page; a param key one of the card's controls and its value one of that control's options. Anything else is refused (ui_field_invalid,ui_field_unknownorui_action_not_offered). - The read is re-run with only those values. A read that pages itself gets the offset or cursor, and its own sort and filters when it declares them. Any other read is fetched whole and DGI sorts, filters and pages it, 50 rows a page.
- The card's columns, controls and row actions never move on a rerun.
- At most one query every 2 seconds per card (
ui_rate_limited).
Living Surfaces take the same query action through dgi.surface.tick signals. See Living Surfaces.
Ask your AI assistant
Using https://docs.orkestia.dev/raw/chat/card-catalog.md, write a React renderer for the datagrid card that sends the query action for sort, filter and paging and never sorts locally.
Which card will DGI return for the output of ticket.get, and what can the person do with it?
List every card that can be answered more than once, and explain why a confirm cannot.
For AI agents
| Rule | Detail |
|---|---|
| Draw | Treat every value in a card as untrusted text. Never render markdown or HTML inside a card, and open only https links |
| Refs | Send refs back unchanged. Never display them, and never put an id in their place |
| Query | Use the query action for sort, filter, paging and controls. Never sort or filter a server-paged grid in the client |
| SQL | Never run a statement for the user. Fill the console and let the person press Run |
| Confirms | A confirm card runs only when the person presses Confirm. A diff is display only |
Option D: any app or API (dgi.chat)
Put DGI's structured chat in any web app, mobile app, support widget or backend with four workflows. Save a profile, send a turn, answer the card, and read the card contract as JSON Schema. Jev first, the LLM off by default, and every write behind a stored confirm
Living Surfaces
A page of live cards that DGI grows, keeps current and retires by itself. Create, tick, signal, read, list, archive and crystallize a surface with dgi.surface.*, set its policy, run its heartbeat, and follow its patch stream over a WebSocket
