Skip to content
ts
function registerAllComponents(): void;

Defined in: packages/core/src/index.ts:471

Make sure every element core defines is in the registry, and say which are not.

Importing @aparte/core already registers all of them. Call this when a bundler with aggressive side-effect elimination, or a dynamic import(), might have dropped the modules — the call itself is what keeps them, because it reaches APARTE_ELEMENTS.

Idempotent, and safe to call more than once: it defines nothing, it reports.

void


ts
function installDialogTriggersOnce(doc?): void;

Defined in: packages/core/src/interop/dialog-triggers.ts:92

Wire data-aparte-dialog-open / -close and the backdrop click on this document. Called once by @aparte/core at import; safe to call again.

ParameterTypeDefault value
docDocumentdocument

void


ts
function applyElementProps(
   el, 
   props, 
   transformValue?): void;

Defined in: packages/core/src/interop/element-props.ts:119

Apply props to an aparté custom element. aparté elements are attribute-driven (observedAttributes): assigning a property is either a silent no-op (nothing observes it) or throws outright on a getter-only accessor — <aparte-composer>’s placeholder/disabled are exactly that. So primitives go through setAttribute; only values an attribute cannot carry (objects, functions) are handed over as properties. Keys starting with -- become CSS variables; on… function values are ignored (event forwarding is the wrapper’s job).

transformValue is applied to object/function values before they’re set as properties — Vue passes toRaw to unwrap its reactive proxy (a deep proxy breaks Maps/class internals on a plain custom element); it defaults to identity.

ParameterType
elHTMLElement
propsRecord<string, unknown>
transformValue(value) => unknown

void


ts
function copyText(text): Promise<void>;

Defined in: packages/core/src/utils/copy-text.ts:23

Copy text to the clipboard, in every context the library actually runs in.

navigator.clipboard is secure-context only — the same wall uuid.ts documents for crypto.randomUUID. On http://192.168.1.x, the archetypal deployment for this library’s own audience, the property is undefined, so navigator.clipboard.writeText(text) throws a TypeError synchronously — before any .catch() can see it. The three copy buttons core renders (the bubble’s action bar, a code block, the artifact card) each had a .catch() for a REJECTED write and nothing for a missing API: on plain http they threw in the click handler and gave no feedback at all, not even a failed one.

The fallback is document.execCommand('copy') — deprecated, but every engine still honours it from a user gesture and it needs no secure context. The text goes into an off-screen textarea, is selected, copied, and the textarea is removed; focus is handed back to where it was, so a keyboard user does not lose their place on the button. It is the path clipboard.js and copy-to-clipboard take, for the same reason.

Rejects when neither path could copy, so a caller’s “copied” confirmation can stay honest. pnpm check:secure-context refuses a bare navigator.clipboard anywhere but here, so the sweep cannot come undone one call site at a time.

ParameterType
textstring

Promise<void>


ts
function cssEscape(value): string;

Defined in: packages/core/src/utils/css-escape.ts:19

Escape a value for safe interpolation into a DOUBLE-QUOTED CSS attribute selector: [attr="${cssEscape(value)}"]. Inside a quoted CSS string only " (which would end the string) and \ (the escape char) are special, so escaping those two is sufficient AND correct here — unlike CSS.escape, which targets the UNquoted identifier context and over-escapes inside quotes (breaking the match), and which also needs the CSS global (absent in SSR / some test runtimes).

Segment/message ids are random UUIDs in the default flow, so this is defense-in-depth: a hostile, stream-supplied id containing " (e.g. a tool-call id) can no longer break out of the selector into a SyntaxError or a selector-list that mis-targets another element.

A raw newline (or CR / form feed) is not allowed inside a CSS string either — it ends the string and the selector throws the same SyntaxError. Escaped as its code point followed by a space, the form the CSS Syntax spec gives for exactly this (\a ), so an id a provider minted with a line break still matches.

ParameterType
valuestring

string


ts
function escapeHtml(value): string;

Defined in: packages/core/src/utils/escape.ts:25

HTML escaping — one implementation, for every interpolation core makes.

There used to be SEVEN copies of this across the package (_escapeHtml in the bubble and in the config, _escapeAttr in the chat shell, the composer action and the composer input, plus escapeHtml and escapeAttr in the segment renderers). They had already drifted: the chat shell’s copy escaped only &, " and <, leaving ' and > through, so it was strictly weaker than its six siblings while looking identical at the call site. That is the failure mode a duplicated security helper always ends in — one of them stops matching, and nothing tells you which.

Escapes all five characters that matter, which makes the result safe in BOTH text position and quoted-attribute position (single or double quotes). Callers that read better saying “attribute” use escapeAttr, which is this same function under a second name — an alias, deliberately not a second body.

PUBLIC. Both names are exported from the barrel and documented in the customization guide, because a render hook that returns a string needs them and telling an author to “escape it yourself” without naming the function is how a capability becomes invisible. An earlier version of this comment claimed the opposite (“not exported from the package barrel… not a public API”) while index.ts exported both — the export was real, the comment was not.

ParameterType
valuestring

string


ts
function filesToAttachments(files): AparteAttachment[];

Defined in: packages/core/src/utils/files-to-attachments.ts:35

Turn the File[] an aparte-send event carries into the AparteAttachment[] a message renders.

The composer hands you raw Files; a bubble needs an id, a MIME type and a url to show a chip or a thumbnail. The ConversationController (used by the framework wrappers) does this internally — this is the same conversion, exported so a raw-core consumer driving appendMessage() itself doesn’t have to hand-roll object URLs:

ts
chat.addEventListener('aparte-send', (e) => {
  const { content, files } = e.detail;
  chat.viewport?.appendMessage({
    id: uuid(), role: 'user', content, timestamp: Date.now(),
    ...(files?.length ? { attachments: filesToAttachments(files) } : {}),
  });
});

The url comes from URL.createObjectURL, so it lives as long as the document; the raw File rides along on blob for storage adapters that persist attachments and rebuild the url on reload.

That lifetime is deliberate for a rendered attachment — but it is also a leak once the attachment is gone: a long session that sends many files retains every File for the life of the page. Call revokeAttachmentUrls when you know they are no longer rendered; core does it on clearAll().

ParameterType
filesreadonly File[]

AparteAttachment[]


ts
function revokeAttachmentUrls(attachments): void;

Defined in: packages/core/src/utils/files-to-attachments.ts:57

Release the object URLs of attachments that are no longer on screen.

Nothing revoked them, so every File a session sent stayed reachable until the page went away. Only the caller knows when an attachment stops being rendered — a persisted conversation may re-render one much later — so this is a function rather than something filesToAttachments could schedule.

Safe to call twice: revoking an already-revoked or foreign URL is a no-op, and a blob is left in place so a storage adapter can still rebuild the url.

ParameterType
attachmentsreadonly AparteAttachment[] | undefined

void


ts
function isAwaitingReply(message): boolean;

Defined in: packages/core/src/utils/is-awaiting-reply.ts:22

Is this message waiting for a reply — i.e. should the bubble render as in-progress (waiting indicator, aria-busy, no action bar) rather than as a finished answer?

Two clauses:

  1. Stated: status is 'streaming' or 'pending'. This is exactly the condition every call site used before this helper existed, role included, so no existing behaviour shifts.
  2. Inferred: an assistant message with no status at all and nothing in it — no content, no segments. That is the empty shell a token stream is about to fill (appendMessage({ role: 'assistant', content: '' }) from a hand-rolled loop). It used to render as a completed reply, action bar and all, on a message that had never said anything.

Only silence is interpreted. An explicit status — including 'completed' on an empty message — is the application talking, and is believed.

ParameterType
messagePick<AparteMessage, "role" | "status" | "content" | "segments">

boolean


ts
function segmentTiming(segment): AparteSegmentTiming | undefined;

Defined in: packages/core/src/utils/segments.ts:159

Read core’s own measurements off a segment.

A function rather than a property access because it is read from six places, and segment.meta?.aparte spelled six times is six chances to spell it differently — which is exactly how this repo’s recurring bug starts.

ParameterType
segmentPick<AparteSegment, "meta">

AparteSegmentTiming | undefined


ts
function isSegmentSettled(segment): boolean;

Defined in: packages/core/src/utils/segments.ts:219

Has this segment settled? The trigger for endedAt.

Exported on purpose: a consumer rendering its own “thought for 8 s” line needs the same predicate, and a rule buried inside a component method is a rule that gets re-derived slightly differently somewhere else.

A segment with no streaming flag at all is NOT settled. That is deliberate: a reply that arrived whole never had a span to measure, and stamping an endedAt equal to its startedAt would dress a zero up as a measurement.

ParameterType
segmentAparteSegment

boolean


ts
function segmentDuration(segment): number | undefined;

Defined in: packages/core/src/utils/segments.ts:326

How long this segment took, or undefined when that is not knowable yet.

The five identity/measurement fields are optional because they describe a LIFECYCLE: a segment built by hand or freshly emitted by the parser has not been inserted yet, so it has no start, and an open segment has no end. Making them required would either force every creation site to stamp — six of them, which is the forgotten-sibling shape this seam exists to prevent — or force a hand-written literal to invent values the owner then refuses to overwrite.

The cost of that optionality lands on the reader, and this is what removes it. The guard it replaces was written three times in this repo before it earned a function:

if (!isSegmentSettled(seg) || !seg.startedAt || !seg.endedAt) return;

— three conditions to get right, and the last two are wrong at epoch 0, where a valid timestamp is falsy. Nobody streams in 1970, but the test suite sets the clock to 0 routinely, so the trap was already in the repo waiting for its first victim.

It deliberately does NOT ask whether the segment is finished: during a turn a growing duration is exactly what a “thinking…” counter shows. Pair it with isSegmentSettled when you need the final number.

ParameterType
segmentAparteSegment

number | undefined


ts
function uuid(): string;

Defined in: packages/core/src/utils/uuid.ts:25

A unique id, in every context the library actually runs in.

crypto.randomUUID is secure-context only. On http:// — which includes http://192.168.1.x, the archetypal deployment for this library’s own audience (bring-your-own-key, a local model on the LAN box) — it does not exist, and calling it throws TypeError: crypto.randomUUID is not a function.

That was not a theoretical edge. A cold audit found 27 call sites across ten files and exactly TWO of them guarded, and the first thing to break was the stream parser: _generateId() runs from _createTextSegment, so parser.parse('hello') threw on the very first token of the very first reply. The two guarded sites — conversation-controller.ts and files-to-attachments.ts — prove the hazard was known and the sweep was never finished. This is that sweep, in one place.

The fallback is NOT cryptographic and does not need to be: these ids address DOM nodes and map keys inside one page. Where a real UUID matters (a persisted conversation id a consumer may treat as stable), crypto.randomUUID is still used first, so a secure context gets the strong value.

pnpm check:secure-context refuses a bare crypto.randomUUID anywhere but here, so the sweep cannot come undone one call site at a time.

string

ts
const APARTE_DEFAULT_UI_EVENTS: readonly ["aparte-send", "aparte-cancel", "aparte-composer-change", "aparte-composer-submit", "aparte-action-click", "aparte-attachment-preview", "aparte-action", "aparte-retry", "aparte-edit", "aparte-feedback", "aparte-message-info", "aparte-branch-navigate", "aparte-link-click", "aparte-segment-update", "aparte-reset-done", "aparte-path-changed", "aparte-conversation-select", "aparte-conversation-delete", "aparte-conversation-archive", "aparte-conversation-unarchive", "aparte-conversation-rename", "aparte-conversation-pin", "aparte-conversation-unpin", "aparte-conversation-loaded", "aparte-select-change", "aparte-select-open", "aparte-select-close", "aparte-optgroup-toggle", "aparte-suggestion", "aparte-context-threshold", "aparte-scroll-rail-jump", "aparte-sidebar-toggle", "aparte-split-resize", "aparte-message-start", "aparte-message-done", "aparte-message-error", "aparte-message-aborted", "aparte-message-paused", "aparte-tool-approval-request"];

Defined in: packages/core/src/interop/element-props.ts:41

Every custom event an aparté element dispatches on ITSELF — the set AparteUi forwards through its single event output, in all four wrappers.

It said “verified against core” while carrying seven of twenty-five, and the gap was not academic: aparte-model-change was missing, which is the event the ONE example in the wrappers’ own docs exists to receive (<aparte-ui name="aparte-model-selector" …>). The documented usage could not hear the thing it was documenting. Re-derived from the generated manifest, which now records every element’s events because their docblocks were reattached.

A PLUGIN’s events are not here either, and aparte-model-change was until this line was written. The reason is the same one that keeps plugin tags out of the element registry: this default is core’s, and a third-party plugin’s author cannot add to it, so listing ours would privilege our packages over theirs. Pass the names you need — events: ['aparte-model-change'] — which is one line for everyone equally.

Three events are deliberately NOT here for a different reason: aparte-abort, aparte-compact and aparte-config-change go out through window.dispatchEvent — they concern the whole page, not one subtree — so an element-level listener can never receive them and listing them would promise a forward that cannot happen.

aparte-message-aborted used to be named in that sentence, and it was wrong. dispatchLifecycleEvent dispatches it — with the other four lifecycle events — on the HOST ELEMENT, bubbling and composed (client/lifecycle-events.ts), and the composer’s window dispatch is a SECOND, page-wide broadcast, not the only one. So the proxy could have forwarded it all along, and a wrapper consumer watching a turn end had to reach past <AparteUi> to window for no reason. The five lifecycle events are here now; the sentence above lists only what a window dispatch is the sole path for.

scripts/check-event-map.mjs now asserts this list against every non-window dispatch in core, so “verified against core” is a check rather than a claim — which is what the first version of this docblock said while carrying seven of twenty-five.


ts
const escapeAttr: (value) => string = escapeHtml;

Defined in: packages/core/src/utils/escape.ts:58

The same escaping as escapeHtml, named for the position it guards.

An attribute value needs exactly the same five characters escaped as a text node — quotes to keep the value from ending early, angle brackets so a broken value cannot start a tag, and the ampersand so none of it can be smuggled back in as an entity. Two names, one body.

REQUIRES A QUOTED ATTRIBUTE. title="${escapeAttr(v)}" and title='…' are both safe; bare title=${escapeAttr(v)} is NOT, because a space, tab, newline or backtick in the value ends an unquoted attribute and starts the next one — and none of those five escaped characters is one of them. An audit raised this as a latent gap (no unquoted interpolation exists in the library today).

The fix is to quote the attribute, not to escape harder: encoding spaces would make every legitimate title="Hello world" read Hello&#32;world. So pnpm check:attr-escaping now rejects an unquoted attribute interpolation outright rather than trusting this function to cover a position it cannot.

HTML escaping — one implementation, for every interpolation core makes.

There used to be SEVEN copies of this across the package (_escapeHtml in the bubble and in the config, _escapeAttr in the chat shell, the composer action and the composer input, plus escapeHtml and escapeAttr in the segment renderers). They had already drifted: the chat shell’s copy escaped only &, " and <, leaving ' and > through, so it was strictly weaker than its six siblings while looking identical at the call site. That is the failure mode a duplicated security helper always ends in — one of them stops matching, and nothing tells you which.

Escapes all five characters that matter, which makes the result safe in BOTH text position and quoted-attribute position (single or double quotes). Callers that read better saying “attribute” use escapeAttr, which is this same function under a second name — an alias, deliberately not a second body.

PUBLIC. Both names are exported from the barrel and documented in the customization guide, because a render hook that returns a string needs them and telling an author to “escape it yourself” without naming the function is how a capability becomes invisible. An earlier version of this comment claimed the opposite (“not exported from the package barrel… not a public API”) while index.ts exported both — the export was real, the comment was not.

ParameterType
valuestring

string

ts
type AparteUiEventName = typeof APARTE_DEFAULT_UI_EVENTS[number];

Defined in: packages/core/src/interop/element-props.ts:103

Every event name aparté dispatches ON an element, as a type.

as const rather than readonly string[] because a TYPE needs the literals: the Svelte wrapper derives its on: surface from HTMLElementEventMap, which deliberately omits the five detail-less events (a map entry would type e.detail as null and gain nothing). That omission was harmless while it only governed addEventListener; once the wrapper DECLARED the tags it removed SvelteHTMLElements’ catch-all index signature, and on:aparte-cancel — the stop button — stopped type-checking. This list already knows those five, so it is the honest source for “every event you can listen for”.