Data artifact views — the spec, and the assistant-ui decision
The authoritative chain
DataArtifactSpec (apps/api/src/investigations/data-artifact.contract.ts),
a versioned discriminated union. A view names FIELDS and an allowlisted
aggregation; it never embeds a second copy of the dataset, and it never carries
markup. The server validates every spec before it leaves: the dataset belongs to
the workspace, referenced fields exist with compatible types, the aggregation is
one of count | sum | avg | min | max, and grouping cardinality and row limits
are bounded. An invalid spec fails closed with a typed error rather than
rendering partially.
The assistant-ui compatibility spike
What was evaluated, and when
@assistant-ui/react-generative-ui, against the repository’s pinned
@assistant-ui/react@0.15.8, on 2026-08-16.
No version conflict blocks adoption. Every peer is satisfied and the age
guard admits
0.0.13 without an exclusion. The decision below is not about
versions.
What its API actually is
Read from the published package (package.json exports, README.md,
dist/index.d.ts, src/), the library’s shape is:
new JSONGenerativeUI({ library })declares the components a MODEL may render;generativeUI.present()returns a tool the model calls, whose parameters are the UI tree;- the model emits
{ $type, ...props }and the tree is rendered against the library, mounted as<Tools toolkit={…}>inside<Thread>; streamPropertiesrenders from PARTIAL props as they stream in, viaassistant-stream.
renderGenerativeUI / generativeUIToJSX — that
renders a node tree without the tool, which is the only shape an adapter could
use.
Its vocabulary (ALERT_TONES, ALIGNS, BUTTON_STYLES, COLORS, ICON_NAMES,
IMAGE_SIZE_TOKENS, TEXT_SIZES, WEIGHTS, JUSTIFIES) is presentational.
It contains no table, metric, chart or evidence primitive.
The decision
Implement a localDataArtifactRenderer over Driftless components. Do not add
the dependency. The spec stays Driftless-owned and versioned, so the renderer
remains replaceable if this changes.
The gate asks whether it “adds less maintenance than it removes”. It does not:
- The six views would still be ours. Its vocabulary has no data-display
primitive, so
DataTableView,MetricGridView,BarChartView,LineChartView,EvidenceListViewandComparisonVieware components we write either way — and then additionally register in its library. - It would add a second representation of the same thing. Adopting it means
translating a validated
DataArtifactSpecinto its IR node tree at render time. The program’s rule is that assistant-ui’s experimental format must not become the domain contract; a runtime translation layer is that coupling arriving through the renderer instead of through storage. - Its transport assumption is the opposite of ours. It exists to render UI a model is streaming, from partial props, inside a conversation. Our chain renders a server-validated spec over rows that are already persisted. Nothing in our path is partial, and nothing in it is model-authored.
- Its lifecycle would become ours. A
0.0.xpackage at ~1.4 releases per week, whose newest release is permanently ~7 days out of reach under our own supply-chain guard, makes every upgrade a review — for components we wrote.
What this spike did NOT verify
Steps 3–5 of the phase’s spike (install0.0.13, render a static
MetricGrid + BarChart + Table from fixtures inside the existing
ExternalStoreRuntime path, and prove build, SSR assumptions, CSS isolation,
accessibility, lazy-chunk impact and that no request reaches Assistant Cloud)
were NOT RUN. The package was inspected as published, not installed.
That is enough for the decision recorded here, because the decision rests on API
shape and dependency lifecycle rather than on integration mechanics — but it is
not enough to reverse it. Adopting the adapter later requires actually running
those steps.
Truthful visualization rules the renderer enforces
- Every chart carries a textual/table alternative and an accessible name.
- A value exists somewhere other than a tooltip.
- A truncated category list states how many of the total are shown.
- A missing value stays missing. It is never coerced to zero — a bar of height zero and a bar that does not exist are different claims.
- Mixed currencies or amount scopes never share a series.
- A chart title states the metric, the scope and the period.
- Colour never carries status alone.
