Skip to content

The code segment — code blocks in an AI chat, highlighted

Code segment - syntax highlighted code block

Highlighting is a plugin you opt into; with none installed the block renders plain, which is what core’s zero-dependency promise costs here.

A segment is data, not an element — it has no tag and dispatches nothing. This one is AparteCodeSegment, and it is the object you push into a bubble; something else draws it.

Width Open in a tab
Core's own renderer, drawing the example printed below inside a real viewport. A segment is data — this is what the library makes of it, and it looks the same in every framework.
{
id: 's1',
type: 'code',
language: 'ts',
filename: 'transport.ts',
content: "const transport = new AparteBackendTransport({ endpoint: '/api/chat' });",
}

Discriminated by type: 'code'.

FieldTypeRequiredDescription
contentstringyes
languagestring—
filenamestring—
showLineNumbersboolean—

From AparteSegmentBase, which is also what a segment type of your own extends.

FieldTypeRequiredDescription
idstringyesUnique segment identifier
typestringyesSegment type discriminator
isStreamingboolean—Whether segment is currently being streamed
fallbackstring—One sentence standing in for the segment when no renderer claims its type — DRAWN for every type, not only for custom. More
messageIdstring—Id of the message this segment belongs to. Stamped on insertion.
indexnumber—Position in the owning message’s segments[], maintained across insertions and removals.
metaRecord<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

One sentence standing in for the segment when no renderer claims its type — DRAWN for every type, not only for custom.

It is the answer to a real moment: a conversation replayed in another app, a client that loads its views lazily, an export. Core draws it as text (no markup) under .aparte-segment-fallback. Supplying one also silences the “no renderer” developer warning: an author who wrote a fallback has already said this can happen.

In the HISTORY sent back to the model it stands in for the segment only where core does not serialise the type itself — custom, and any type core does not know (a registered block grammar’s, a plugin’s). A text or a code segment contributes its content and nothing else; thinking and tool_call are left out of the history on purpose (_segmentsToText in AparteClient states why), and so is error.

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.

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.

A built-in renderer draws this one, from packages/core/src/renderers/segments/code.ts. It installs itself the first time a segment of this type needs it, so a chat renders it with no setup.

Replace it with your own for this config only:

import { registerSegmentRenderer } from '@aparte/core';
import type { AparteCodeSegment } from '@aparte/core';
registerSegmentRenderer({
type: 'code',
render: (segment: AparteCodeSegment) => {
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.