The custom segment — your own message content (generative UI)
Custom segment - for framework-specific views (Onboarding, Tools, etc.)
A segment is data, not an element — it has no tag and dispatches nothing. This one is
AparteCustomSegment, and it is the object you push into a bubble; something else draws it.
Example
Section titled “Example”// With no renderer registered for `subType`, core draws `fallback` — which is why the// field exists: an unknown view degrades to a sentence instead of to nothing.{ id: 's1', type: 'custom', subType: 'weather-widget', data: { city: 'Lille', celsius: 11 }, fallback: 'Lille — 11°C, overcast.',}Discriminated by type: 'custom'.
| Field | Type | Required | Description |
|---|---|---|---|
subType | string | yes | Unique string structure to identify the view (e.g. ‘onboarding’, ‘webcontainer’, ‘weather-widget’) |
data | unknown | — | Arbitrary data payload for the component |
fallback | string | — | Optional fallback text representation |
Shared by every segment
Section titled “Shared by every segment”From AparteSegmentBase, which is also what a segment type of your own extends.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Unique segment identifier |
type | string | yes | Segment type discriminator |
isStreaming | boolean | — | Whether segment is currently being streamed |
messageId | string | — | Id of the message this segment belongs to. Stamped on insertion. |
index | number | — | Position in the owning message’s segments[], maintained across insertions and removals. |
meta | Record<string, unknown> & { aparte?: AparteSegmentTiming } | — | Extras the producer of the segment knows — token counts, cost, compute device — plus aparte, the one sub-object core writes. More |
Notes on individual fields
Section titled “Notes on individual fields”Extras the producer of the segment knows — token counts, cost, compute
device — plus aparte, the one sub-object core writes.
Everything except meta.aparte is yours: fill it with
updateSegment(segmentId, { meta }), which MERGES rather than replaces.
Mirrors AparteMessage.metadata.
Who emits it
Section titled “Who emits it”An app pushes one with addSegment() on the viewport, or a parser produces it while a
reply streams. Core stamps its identity and timing on insert, so an emitted segment does not
have to carry them.
Who draws it
Section titled “Who draws it”Nothing in core draws this one — it is the escape hatch, and the renderer is yours. Without
one registered, core falls back: it draws the segment’s fallback string when there is one, and
otherwise a [Unknown segment type: custom] placeholder with a console warning naming the type.
import { registerSegmentRenderer } from '@aparte/core';import type { AparteCustomSegment } from '@aparte/core';
registerSegmentRenderer({ type: 'custom', render: (segment: AparteCustomSegment) => { const el = document.createElement('div'); el.textContent = JSON.stringify(segment); return el; },});See Customization for the whole renderer seam, and bring your own loop for emitting segments yourself.