Skip to content

Defined in: packages/core/src/config/aparte-config.ts:143

Everything a chat is configured with: the providers and the selected model, the transport and the keys, the tools, the renderers and render hooks, the Markdown and highlight providers, the locale, the icons, the avatars, the actions.

aparteGlobalConfig is the one every chat on the page reads. A chat that needs a configuration of its own takes a new AparteConfig() attached to its element or to an ancestor with attachConfig; the framework wrappers take it as their config prop.

A change to what is drawn — providers, the model, the locale, icons, avatars, actions, renderers — reaches the components already on the page: subscribe() listeners are called, and aparte-config-change is dispatched on window with the instance that changed. The Markdown, streaming-Markdown and highlight providers, the sanitizer, segment defaults, tools, tool renderers and summaries apply to what is drawn after they are set, so set them before the chat renders; the transport, the keys and the system prompt apply from the next request.

ts
new AparteConfig(): AparteConfig;

AparteConfig

ts
registerAction(action): void;

Defined in: packages/core/src/config/aparte-config.ts:270

Register a custom action button. zones places it in the message (bubble) toolbar. Re-registering the same id overwrites it. Notifies mounted elements so they re-render. A button in the composer is an <aparte-composer-action> element you write in your markup, not a registry entry.

ParameterType
actionAparteAction

void

ts
getActions(zone): AparteAction[];

Defined in: packages/core/src/config/aparte-config.ts:282

All registered actions for a zone, sorted by order (lower first).

ParameterType
zone"bubble"

AparteAction[]

ts
unregisterAction(id): void;

Defined in: packages/core/src/config/aparte-config.ts:289

Remove a custom action by id (from every zone); notifies mounted elements if it existed.

ParameterType
idstring

void

ts
setBubbleActions(config): void;

Defined in: packages/core/src/config/aparte-config.ts:307

Configure which action buttons appear in message bubbles. Unset keys keep their defaults — see APARTE_DEFAULT_BUBBLE_ACTIONS: copy only, because every other button needs a host to honor it.

ParameterType
configAparteBubbleActionsConfig

void

ts
aparteGlobalConfig.setBubbleActions({ retry: true, edit: true }) // you run AparteClient
aparteGlobalConfig.setBubbleActions({ feedback: true })          // you listen for aparte-feedback
aparteGlobalConfig.setBubbleActions({ copy: false })             // hides copy; calls merge, so anything turned on above stays on
// Explicit per-role ordered sets (replace the flag defaults for that role):
aparteGlobalConfig.setBubbleActions({ user: ['edit', 'copy'], assistant: ['copy', 'thumbUp', 'thumbDown', 'retry'] })
ts
setHostHandlers(config): void;

Defined in: packages/core/src/config/aparte-config.ts:319

Declare which host-dependent affordances your app handles. Core renders the trigger only for the ones you claim — see AparteHostHandlersConfig.

ParameterType
configAparteHostHandlersConfig

void

ts
aparteGlobalConfig.setHostHandlers({ attachmentPreview: true });
ts
getHostHandlers(): Required<AparteHostHandlersConfig>;

Defined in: packages/core/src/config/aparte-config.ts:330

Returns the resolved host-handler declarations (undeclared = false).

Required<AparteHostHandlersConfig>

ts
getBubbleActions(): {
  copy: boolean;
  retry: boolean;
  edit: boolean;
  feedback: boolean;
  info: boolean;
  user?: AparteBubbleActionName[];
  assistant?: AparteBubbleActionName[];
};

Defined in: packages/core/src/config/aparte-config.ts:337

Returns the resolved bubble actions config (flag defaults applied; per-role sets passed through).

ts
{
  copy: boolean;
  retry: boolean;
  edit: boolean;
  feedback: boolean;
  info: boolean;
  user?: AparteBubbleActionName[];
  assistant?: AparteBubbleActionName[];
}
ts
copy: boolean;
ts
retry: boolean;
ts
edit: boolean;
ts
feedback: boolean;
ts
info: boolean;
ts
optional user?: AparteBubbleActionName[];
ts
optional assistant?: AparteBubbleActionName[];
ts
setMarkdownProvider(fn): void;

Defined in: packages/core/src/config/aparte-config.ts:360

Set a custom Markdown renderer (e.g., marked, maritime-it)

ParameterType
fnAparteMarkdownProvider

void

ts
setStreamingMarkdownProvider(fn): void;

Defined in: packages/core/src/config/aparte-config.ts:375

Set an incremental (streaming) Markdown renderer provider. Optional — when set, the chat bubble uses it to render the assistant message token-by-token DURING streaming (incremental parse + DOM append, O(n)), instead of re-parsing the whole string on every token. With no one-shot setMarkdownProvider registered, it also renders a COMPLETE string (renderMarkdown): a restored reply, a non-streaming provider’s answer, a stored segment — so a reply renders the same however it arrived. A registered one-shot provider takes over for those, and re-renders the finished message at full fidelity.

ParameterType
fnAparteStreamingMarkdownProvider

void

ts
createStreamingMarkdownRenderer(target): 
  | AparteStreamingMarkdownRenderer
  | null;

Defined in: packages/core/src/config/aparte-config.ts:384

Create an incremental Markdown renderer bound to target, or null when no streaming-markdown provider is registered (the caller then falls back to the one-shot renderMarkdown).

ParameterType
targetHTMLElement

| AparteStreamingMarkdownRenderer | null

ts
setHighlightProvider(fn): void;

Defined in: packages/core/src/config/aparte-config.ts:392

Set a custom Syntax Highlighter (e.g., prism, highlight.js, shiki) Supports both synchronous and asynchronous renderers.

ParameterType
fnAparteHighlightProvider

void

ts
hasHighlightProvider(): boolean;

Defined in: packages/core/src/config/aparte-config.ts:401

Whether a syntax-highlight provider is registered. Lets consumers (e.g. the bubble) skip the highlight pass — and avoid replacing already-rendered code blocks with the plain fallback — when no highlighter is installed.

boolean

ts
setHtmlSanitizer(sanitizer): void;

Defined in: packages/core/src/config/aparte-config.ts:416

Replace or disable the HTML sanitizer applied to markdown/highlight provider output before it is injected into the DOM. Defaults to a built-in zero-dependency allowlist sanitizer.

ParameterTypeDescription
sanitizerAparteSanitizer | nullA sanitizer function (e.g. a DOMPurify wrapper) for hardened coverage, or null to DISABLE sanitization. Disabling exposes you to XSS from LLM-authored content — only do so for content you fully trust and have already sanitized upstream. null prints a console warning, once.

void

ts
aparteGlobalConfig.setHtmlSanitizer((html) => DOMPurify.sanitize(html));
ts
sanitizeHtml(html): string;

Defined in: packages/core/src/config/aparte-config.ts:440

Run the active sanitizer over provider-produced HTML. Public so a streaming-markdown provider (which appends DOM directly, bypassing renderMarkdown) can apply the same policy. Returns the input unchanged only when sanitization was disabled via setHtmlSanitizer(null).

ParameterType
htmlstring

string

ts
setSystemPrompt(template): void;

Defined in: packages/core/src/config/aparte-config.ts:448

Set the system prompt template. Supports {{key}} placeholders resolved via setSystemPromptVarsProvider. Pass undefined to clear.

ParameterType
templatestring | undefined

void

ts
getSystemPromptTemplate(): string | undefined;

Defined in: packages/core/src/config/aparte-config.ts:453

Get the raw system prompt template (with unresolved placeholders).

string | undefined

ts
setSystemPromptVarsProvider(fn): void;

Defined in: packages/core/src/config/aparte-config.ts:462

Register a function that returns a map of variable name → value. Called at request time to resolve {{key}} placeholders in the system prompt. Example: () => ({ ‘settings.lang’: ‘French’ })

ParameterType
fnAparteSystemPromptVarsProvider

void

ts
resolveSystemPrompt(): string | null;

Defined in: packages/core/src/config/aparte-config.ts:470

Resolve the system prompt template by substituting all {{key}} placeholders. Returns null if no template is set or the template is empty after trimming.

string | null

ts
setIconProvider(provider): void;

Defined in: packages/core/src/config/aparte-config.ts:481

Set a custom Icon provider (e.g., Lucide, FontAwesome, Material)

ParameterTypeDescription
providerAparteIconProviderObject implementing AparteIconProvider interface

void

ts
getIconProvider(): Required<AparteIconProvider>;

Defined in: packages/core/src/config/aparte-config.ts:501

The icon set as a complete provider: every name resolves, falling back to APARTE_DEFAULT_ICON_FALLBACKS for anything the registered provider doesn’t implement. Callers (bubble action bar, composer controls) can therefore invoke icons.copy() unconditionally.

It used to hand back the registered provider verbatim, so a provider that implemented a subset — which getIcon() has always supported, and which the type now states — crashed every other icon with “icons.retry is not a function”.

Required<AparteIconProvider>

ts
setAvatarProvider(provider): void;

Defined in: packages/core/src/config/aparte-config.ts:516

Set a custom avatar renderer. Lets framework consumers (Angular, React, Vue, …) mount live components in place of the default avatar text/image. Cleared by passing null.

ParameterType
providerAparteAvatarProvider | null

void

ts
getAvatarProvider(): AparteAvatarProvider | null;

Defined in: packages/core/src/config/aparte-config.ts:523

Returns the registered avatar provider, or null if none.

AparteAvatarProvider | null

ts
setStatusRenderer(renderer): void;

Defined in: packages/core/src/config/aparte-config.ts:533

Set a custom typing-indicator renderer. Replaces the inner markup of <aparte-chat-status> (avatar + animated dots + text) while the element keeps owning show/hide. Return a string or an HTMLElement. Cleared by passing null. Notifies mounted components so a live skin switch re-renders.

ParameterType
rendererAparteStatusRenderer | null

void

ts
getStatusRenderer(): AparteStatusRenderer | null;

Defined in: packages/core/src/config/aparte-config.ts:539

Returns the registered status renderer, or null if none.

AparteStatusRenderer | null

ts
setErrorRenderer(renderer): void;

Defined in: packages/core/src/config/aparte-config.ts:550

Set a custom error renderer. Drives the content of error bubbles (the built-in error segment) — return a string or an HTMLElement, e.g. a friendly message with a retry button. The bubble also carries data-error on its .aparte-message while errored, for CSS theming. Cleared with null. Notifies mounted components so a live change re-renders.

ParameterType
rendererAparteErrorRenderer | null

void

ts
getErrorRenderer(): AparteErrorRenderer | null;

Defined in: packages/core/src/config/aparte-config.ts:556

Returns the registered error renderer, or null if none.

AparteErrorRenderer | null

ts
setAttachmentRenderer(renderer): void;

Defined in: packages/core/src/config/aparte-config.ts:567

Set a custom attachment renderer. Replaces the chip rendered for each attachment on a user message (default: image thumbnail / file chip) — return a string or an HTMLElement, e.g. a PDF preview. You own the interactions for custom output (see AparteAttachmentRenderer). Cleared with null. Notifies mounted components so a live change re-renders.

ParameterType
rendererAparteAttachmentRenderer | null

void

ts
getAttachmentRenderer(): AparteAttachmentRenderer | null;

Defined in: packages/core/src/config/aparte-config.ts:573

Returns the registered attachment renderer, or null if none.

AparteAttachmentRenderer | null

ts
setSiblingNavRenderer(renderer): void;

Defined in: packages/core/src/config/aparte-config.ts:583

Set a custom sibling (branch) position indicator. Replaces the ‹ N / M › counter between the prev/next arrows — e.g. dots. Return a string or an HTMLElement (see AparteSiblingNavRenderer); the arrows keep their behavior. Cleared with null. Notifies mounted components.

ParameterType
rendererAparteSiblingNavRenderer | null

void

ts
getSiblingNavRenderer(): AparteSiblingNavRenderer | null;

Defined in: packages/core/src/config/aparte-config.ts:589

Returns the registered sibling-nav renderer, or null if none.

AparteSiblingNavRenderer | null

ts
setBubbleShellRenderer(renderer): void;

Defined in: packages/core/src/config/aparte-config.ts:600

Set a custom bubble shell renderer (advanced) — replaces the structural skeleton of <aparte-chat-bubble> while keeping its behavior. The shell must honor the class-hook contract (root .aparte-message, region hooks) — see AparteBubbleShellRenderer. For a fully custom element use renderBubble (wrapper) instead. Cleared with null. Notifies mounted components.

ParameterType
rendererAparteBubbleShellRenderer | null

void

ts
getBubbleShellRenderer(): AparteBubbleShellRenderer | null;

Defined in: packages/core/src/config/aparte-config.ts:606

Returns the registered bubble-shell renderer, or null if none.

AparteBubbleShellRenderer | null

ts
setLocale(locale): void;

Defined in: packages/core/src/config/aparte-config.ts:614

Set the current locale

ParameterTypeDescription
localeAparteLocale & AparteLocaleExtensionsAparteLocale object defining all strings

void

ts
resetLocale(): void;

Defined in: packages/core/src/config/aparte-config.ts:626

Restore the built-in English locale (the same APARTE_DEFAULT_LOCALE core ships with). Counterpart of setLocale for language toggles — avoids having to import APARTE_DEFAULT_LOCALE yourself.

void

ts
getLocale(): AparteLocale & AparteLocaleExtensions;

Defined in: packages/core/src/config/aparte-config.ts:644

Get the current locale.

Typed as the closed list PLUS the open half, and the asymmetry is the point: t() keys on AparteLocale alone — a typo there is a compile error — while a plugin reading its OWN key off this object gets string | undefined and supplies its own default at the call site.

t() is the one that has to be narrow, because it is the one that fails silently: it returns '' for a key nobody declared, and an empty label is invisible on screen and in the console. A misspelt read here is undefined, which the ?? 'fallback' every caller already writes turns into the default.

AparteLocale & AparteLocaleExtensions

ts
extendLocale(translations): void;

Defined in: packages/core/src/config/aparte-config.ts:653

Extend the current locale with partial translations. Useful for plugins to register their own strings.

ParameterTypeDescription
translationsPartial<AparteLocale> & AparteLocaleExtensionsPartial locale object to merge

void

ts
getIcon(name): string;

Defined in: packages/core/src/config/aparte-config.ts:662

Get icon HTML string by name Falls back to textual representation if no provider is set

ParameterType
namekeyof AparteIconProvider

string

ts
setKeyProvider(provider): void;

Defined in: packages/core/src/config/aparte-config.ts:671

Set a custom Key provider (e.g., AparteVault override)

ParameterType
providerAparteKeyProvider

void

ts
registerKeyProvider(provider): () => void;

Defined in: packages/core/src/config/aparte-config.ts:687

Add a key source beside the one setKeyProvider() holds, and return the function that removes it. The sources are asked in the order they were added, then setKeyProvider’s, and the first answer wins. AparteClient’s keyResolver is registered this way, so the model selector can list a cloud provider’s models too. Each call is a source of its own, even with the same function.

ParameterType
providerAparteKeyProvider

() => void

ts
const release = aparteGlobalConfig.registerKeyProvider((providerId) =>
    providerId === 'openrouter' ? localStorage.getItem('openrouter-key') : undefined);
// when what owns the key goes away:
release();
ts
getKey(providerId): Promise<string | Record<string, string> | undefined>;

Defined in: packages/core/src/config/aparte-config.ts:713

The credentials for a provider: a key, or the { apiKey, endpoint } record the transport and fetchModels both read.

ONE resolution order, so the chat and the model list cannot disagree about what an endpoint is: a registered resolver (AparteClient’s keyResolver) first — which is the precedence the client has always documented — then setKeyProvider.

Registered sources answer NEWEST first. A page that mounts a second chat, or remounts one to swap its options — the swap the wrappers document — leaves more than one source here, and insertion order made the first resolver the page ever registered answer for every later one, so the key could not be changed. The snapshot is deliberate too: a source torn down while an earlier one is being awaited must not shift the iteration underneath it.

ParameterType
providerIdstring

Promise<string | Record<string, string> | undefined>

ts
refreshProviderModels(providerId): Promise<AparteAIModel[]>;

Defined in: packages/core/src/config/aparte-config.ts:730

Fetch a provider’s models, with the key and endpoint its key sources answer, so a component that lists models never handles a key. The list is kept, and getCurrentModel() reads the selected model’s capabilities from it. Answers [] for an unknown provider, one with no fetchModels, or a fetch that fails (with a console warning).

ParameterType
providerIdstring

Promise<AparteAIModel[]>

ts
registerAIProvider(...providers): void;

Defined in: packages/core/src/config/aparte-config.ts:768

Register one or more AI providers (e.g., OpenRouter, Gemini, Anthropic).

When this leaves exactly one provider registered, offering exactly one model it knows without a request (a scripted or an in-browser provider), and no model is selected, that model is selected. A provider that lists its models over the network selects nothing: name one with setModelConfig(), or let the person pick in <aparte-model-selector>.

ParameterTypeDescription
…providersAparteAIProvider[]AparteAIProvider implementations

void

ts
aparteGlobalConfig.registerAIProvider(OpenRouterProvider);
aparteGlobalConfig.registerAIProvider(GeminiProvider, AnthropicProvider);
ts
unregisterAIProvider(id): void;

Defined in: packages/core/src/config/aparte-config.ts:798

Unregister an AI provider

ParameterType
idstring

void

ts
getAIProviders(): AparteAIProvider[];

Defined in: packages/core/src/config/aparte-config.ts:809

Get all registered AI providers Optionally filtered by enabled providers in config

AparteAIProvider[]

ts
getAIProvider(id): AparteAIProvider | undefined;

Defined in: packages/core/src/config/aparte-config.ts:822

Get a specific AI provider by ID

ParameterType
idstring

AparteAIProvider | undefined

ts
setTransport(transport): void;

Defined in: packages/core/src/config/aparte-config.ts:832

Set the transport that decides where chat requests go and how auth is handled. Defaults to AparteDirectTransport (browser-direct — BYOK/local). Use a AparteBackendTransport to keep API keys server-side (recommended for production).

ParameterType
transportAparteTransport

void

ts
getTransport(): AparteTransport;

Defined in: packages/core/src/config/aparte-config.ts:837

Get the active transport (AparteDirectTransport by default).

AparteTransport

ts
setModelPreferenceProvider(provider): void;

Defined in: packages/core/src/config/aparte-config.ts:850

Register a model preference provider for agnostic persistence. The host app decides how/where to store the selected provider & model.

ParameterType
providerAparteModelPreferenceProvider

void

ts
aparteGlobalConfig.setModelPreferenceProvider({
  save: (p, m) => localStorage.setItem('model', JSON.stringify({p, m})),
  load: () => JSON.parse(localStorage.getItem('model') ?? 'null')
});
ts
setApprovalPolicy(policy): void;

Defined in: packages/core/src/config/aparte-config.ts:876

Decide per CALL whether a tool runs, asks, or is refused — a mode, not a flag.

With a policy registered, the client’s default approval channel evaluates it for every tool call: allow runs without asking, ask puts the call to the person exactly as a needsApproval tool is, deny refuses it with the policy’s own reason as what the model reads. undefined leaves the tool’s needsApproval to decide, as before. null removes the policy.

@aparte/plugin-approval builds one from a classification of your tool names and a switchable mode (plan / ask / auto-edit / auto); a host that owns its own approvalResolver on AparteClientOptions is not affected — that resolver already decides everything.

ParameterType
policyAparteApprovalPolicy | null

void

ts
aparteGlobalConfig.setApprovalPolicy((call) =>
  call.name === 'run_command'
    ? { verdict: 'ask' }
    : { verdict: 'allow' });
ts
getApprovalPolicy(): AparteApprovalPolicy | null;

Defined in: packages/core/src/config/aparte-config.ts:884

The registered approval policy, or null.

AparteApprovalPolicy | null

ts
ruleOnToolCall(call): AparteApprovalRuling;

Defined in: packages/core/src/config/aparte-config.ts:893

What the policy says about one call, with the tool’s own needsApproval as the answer when it has no opinion. The one place the two are combined, so the client’s gate predicate and its approval channel cannot disagree.

ParameterType
callAparteToolCall

AparteApprovalRuling

ts
restoreModelPreference(): AparteModelPreference | null;

Defined in: packages/core/src/config/aparte-config.ts:906

Restore previously saved model preference via the registered provider. Should be called once at app startup, before any component mounts. No-op if no provider is registered or nothing was saved. The saved selection is applied without being saved again.

AparteModelPreference | null

ts
setModelConfig(config): void;

Defined in: packages/core/src/config/aparte-config.ts:922

Select the default provider and model, merged over the current selection. With a model-preference provider registered and both set, the selection is saved through it.

ParameterType
configAparteModelConfig

void

ts
subscribe(callback): () => void;

Defined in: packages/core/src/config/aparte-config.ts:937

Subscribe to configuration changes

ParameterType
callback() => void

Unsubscribe function

() => void

ts
getModelConfig(): AparteModelConfig;

Defined in: packages/core/src/config/aparte-config.ts:964

Get current model configuration

AparteModelConfig

ts
hasSelectedModel(): boolean;

Defined in: packages/core/src/config/aparte-config.ts:972

True when the model config has BOTH a provider and a model selected — i.e. the chat can actually send. Used by the composer’s require-model gate.

boolean

ts
setElicitationOptions(options): void;

Defined in: packages/core/src/config/aparte-config.ts:1009

Elicitation policy: whether a choice offers a free-text “Other…” escape.

This is the HOST’s decision, not the model’s. ask_user used to carry an allow_other field in the schema it hands the model, which meant the model decided your UX — and a small model fills a field it does not understand: one sent two questions with allow_other: true and no options at all, so the panel rendered a radio list whose only entry was “Other…”, twice.

Every serious implementation of this pattern makes the escape hatch a property of the surface rather than of the request. Default true, which is what the panel did before, so nothing a user sees changes — only who gets to say so. A direct requestUserInput caller can still set allowOther per field; that is the app talking, and it wins.

layout decides how a form of SEVERAL questions is presented. 'stepped' (default) asks them one at a time with a chip per question; 'stacked' puts them all in the panel at once. Stacked was the only shape, inherited unexamined from MCP elicitation — which describes a FORM for collecting structured data, not two different questions asked mid-conversation. No product asks a person two questions by stacking them in one box; it is kept as an option because the form case is real, not because it was the right default.

answerOnClick decides how a question asked ON ITS OWN with a single choice — an enum without multiple or a default, a boolean without a default — is answered. true (default) renders the options as buttons and the click is the answer: one decision, one gesture, the shape every chat product uses for “which one?”. false keeps the radios and the composer’s button, so a person can change their mind before committing — the shape a form or a screen reader flow may prefer, and the one a host that wants a uniform “select, then send” across every question asks for. A form of several questions always collects and submits, whatever this says.

ParameterType
options{ allowOther?: boolean; layout?: "stepped" | "stacked"; answerOnClick?: boolean; }
options.allowOther?boolean
options.layout?"stepped" | "stacked"
options.answerOnClick?boolean

void

ts
getElicitationOptions(): {
  allowOther: boolean;
  layout: "stepped" | "stacked";
  answerOnClick: boolean;
};

Defined in: packages/core/src/config/aparte-config.ts:1017

The elicitation policy (see setElicitationOptions).

ts
{
  allowOther: boolean;
  layout: "stepped" | "stacked";
  answerOnClick: boolean;
}
ts
allowOther: boolean;
ts
layout: "stepped" | "stacked";
ts
answerOnClick: boolean;
ts
setElicitationFieldRenderer(fn): void;

Defined in: packages/core/src/config/aparte-config.ts:1028

Render one field of the question panel yourself.

See AparteElicitationFieldRenderer. Return null for a field to let the built-in render it, which is what makes overriding a single kind practical. Pass null here to remove the renderer.

ParameterType
fn| AparteElicitationFieldRenderer | null

void

ts
getElicitationFieldRenderer(): 
  | AparteElicitationFieldRenderer
  | undefined;

Defined in: packages/core/src/config/aparte-config.ts:1034

The custom field renderer, if one is registered.

| AparteElicitationFieldRenderer | undefined

ts
setRequireModelSelection(required): void;

Defined in: packages/core/src/config/aparte-config.ts:1044

Opt-in UX: when enabled, <aparte-composer> blocks sending and greys out until hasSelectedModel is true (e.g. while the model selector is still fetching its list). Off by default so single-model / backend setups that never select a model are unaffected.

ParameterType
requiredboolean

void

ts
getRequireModelSelection(): boolean;

Defined in: packages/core/src/config/aparte-config.ts:1051

Whether the composer should gate on model selection (see setRequireModelSelection).

boolean

ts
getCurrentModel(): AparteAIModel | undefined;

Defined in: packages/core/src/config/aparte-config.ts:1060

Get the currently selected model object, if available synchronously. Returns undefined if no provider/model is selected, or if the provider’s models are only available asynchronously (fetchModels).

AparteAIModel | undefined

ts
t(key): string;

Defined in: packages/core/src/config/aparte-config.ts:1093

The locale’s string for key, or the default (English) locale’s when the locale leaves it out; '' for a key neither declares.

ParameterType
keykeyof AparteLocale

string

ts
renderMarkdown(raw): string;

Defined in: packages/core/src/config/aparte-config.ts:1112

Render a complete Markdown string to HTML: the one-shot provider when one is registered, else the incremental provider run over the whole string, else core’s default (escape + <br>).

The middle step is what makes the incremental provider enough on its own. Without it a reply rendered two ways depending on how it arrived: rich while it streamed, escaped source once it came back complete — a restored conversation, a non-streaming provider, a stored segment. Same parser, same sanitizer, so the two cannot disagree. It needs a document to write into; without one (a server render) the default answers, as before.

ParameterType
rawstring

string

ts
highlightCode(code, lang): Promise<string>;

Defined in: packages/core/src/config/aparte-config.ts:1141

Highlight code block Fallback: Returns raw code wrapped in <pre><code> Supports Promise if provider is async/streaming

ParameterType
codestring
langstring

Promise<string>

ts
registerTool(tool, handler): void;

Defined in: packages/core/src/config/aparte-config.ts:1165

Register a tool and its handler together. The handler is called when the AI invokes the tool during streaming.

ParameterType
toolAparteTool
handlerAparteToolHandler

void

ts
aparteGlobalConfig.registerTool(askUserTool, askUserHandler);
ts
unregisterTool(name): void;

Defined in: packages/core/src/config/aparte-config.ts:1170

Unregister a tool by name

ParameterType
namestring

void

ts
getTools(): AparteTool[];

Defined in: packages/core/src/config/aparte-config.ts:1175

Get all registered tool definitions (to pass in AparteChatRequest)

AparteTool[]

ts
resolveToolSystemPrompts(): string | null;

Defined in: packages/core/src/config/aparte-config.ts:1186

The registered tools’ own systemPrompts, in registration order with a blank line between, or null if none sets one. AparteClient sends them with every request, as a system message after the app’s own (resolveSystemPrompt()); a request you assemble yourself should send them the same way, or the model loses the tools’ instructions.

string | null

ts
getToolHandler(name): AparteToolHandler | undefined;

Defined in: packages/core/src/config/aparte-config.ts:1194

Get the handler for a tool by name

ParameterType
namestring

AparteToolHandler | undefined

ts
registerToolRenderer(toolName, renderer): void;

Defined in: packages/core/src/config/aparte-config.ts:1218

Register a per-tool segment renderer. Controls what appears in the chat bubble when the AI calls this tool. Use this instead of the generic tool_call segment renderer for tool-specific UI.

A renderer that needs a popover (a <aparte-select>, a menu) cannot open one inside the row: the bubble is content-visibility: auto, which makes it the containing block for position: fixed descendants and clips a popover to its own box. Mount it on document.body and position it from the trigger’s rect instead — see Custom tool renderer.

ParameterType
toolNamestring
rendererAparteToolRenderer

void

ts
// Hide the segment entirely (UI-only tool like ask_user)
aparteGlobalConfig.registerToolRenderer('ask_user', { render: () => '' });
ts
// Custom pill for a web-search tool
aparteGlobalConfig.registerToolRenderer('web_search', { render: (seg) => `<div class="aparte-tool-label">Searching...</div>` });
ts
setSegmentDefaults(type, defaults): void;

Defined in: packages/core/src/config/aparte-config.ts:1249

Field defaults for a segment TYPE, filled in when a segment enters a message.

One function for every type rather than one per field: a setThinkingOpen() would need a sibling the next time any type wanted a default, and the type key is a string, so a consumer’s own segment type is covered by the same call.

The problem it solves: a consumer streaming a reply does not CONSTRUCT its segments — the parser does — so there was no hook at all for “reasoning blocks open in my app”. Per-segment fields only reach segments you build yourself.

Applied where identity is stamped (utils/segments.ts), so it covers every path a segment can arrive by — the parser, addSegment, and the segments seeded on an appendMessage — and no renderer has to look anything up.

A field the producer set always wins, and identity is never defaulted. Read at insertion and baked in: changing a default later does not reach segments already on screen, because a block the reader opened has state the data does not.

ParameterType
typestring
defaultsAparteSegmentDefaults

void

ts
// Reasoning blocks arrive open in this app, streaming or settled.
aparteGlobalConfig.setSegmentDefaults('thinking', { collapsed: false });
ts
// A consumer's own type, same call.
aparteGlobalConfig.setSegmentDefaults('my-chart', { theme: 'dark' });
ts
getSegmentDefaults(type): Readonly<Record<string, unknown>> | undefined;

Defined in: packages/core/src/config/aparte-config.ts:1256

The defaults registered for a type, or undefined.

ParameterType
typestring

Readonly<Record<string, unknown>> | undefined

ts
clearSegmentDefaults(type): void;

Defined in: packages/core/src/config/aparte-config.ts:1261

Drop the defaults for a type.

ParameterType
typestring

void

ts
unregisterToolRenderer(toolName): void;

Defined in: packages/core/src/config/aparte-config.ts:1266

Unregister a per-tool renderer

ParameterType
toolNamestring

void

ts
getToolRenderer(toolName): AparteToolRenderer | undefined;

Defined in: packages/core/src/config/aparte-config.ts:1271

Get the renderer for a specific tool name. Returns undefined if none registered.

ParameterType
toolNamestring

AparteToolRenderer | undefined

ts
registerToolSummary(toolName, summary): void;

Defined in: packages/core/src/config/aparte-config.ts:1295

Say what a call targets, in the default row, after the tool’s name.

The default row names the tool and keeps its arguments behind a disclosure, so a reader saw that a file was read, not which one. Replacing the row with registerToolRenderer for that one line meant re-implementing its six states, the spinner, the disclosure and the relabel contract. This keeps all of them and adds the line. A tool with its own renderer draws its own row, so its summary is never read.

The text is written as text, never parsed as HTML. It is read when the row renders, on every update to the call (its arguments may still be arriving), and on every config change — so registering one after a row is on screen shows up on that row’s next update.

ParameterType
toolNamestring
summaryAparteToolSummary

void

ts
// read_file skills.json
aparteGlobalConfig.registerToolSummary('read_file', (segment) =>
    String(segment.toolCall.input['path'] ?? ''));
ts
unregisterToolSummary(toolName): void;

Defined in: packages/core/src/config/aparte-config.ts:1300

Unregister a per-tool summary.

ParameterType
toolNamestring

void

ts
getToolSummary(toolName): AparteToolSummary | undefined;

Defined in: packages/core/src/config/aparte-config.ts:1305

Get the summary for a specific tool name. Returns undefined if none registered.

ParameterType
toolNamestring

AparteToolSummary | undefined

ts
registerStreamBlock(block): void;

Defined in: packages/core/src/config/aparte-config.ts:1329

Teach the stream parser a tagged block: <tag attr="…">…</tag> in the model’s prose becomes the segment toSegment builds, streamed delta by delta.

The parser does the work once for every grammar — the earliest opening tag wins against a code fence and a reasoning delimiter, a tag cut at a chunk boundary is held back, attributes are parsed quoted or bare, and the block is closed at </tag> or at the end of the stream. What the segment IS is yours: a type of your own with a renderer from registerSegmentRenderer, or a built-in one. One grammar per tag; registering the same tag again replaces it.

Read by the stream adapter when a turn starts, so a block registered mid-turn applies from the next turn. See AparteStreamBlock.

ParameterType
blockAparteStreamBlock

void

ts
aparteGlobalConfig.registerStreamBlock({
  tag: 'cite',
  toSegment: ({ attrs, id }) => ({ id, type: 'citation', url: attrs['url'] ?? '', content: '' }),
});
ts
unregisterStreamBlock(tag): void;

Defined in: packages/core/src/config/aparte-config.ts:1335

Forget a block grammar by its tag.

ParameterType
tagstring

void

ts
getStreamBlocks(): AparteStreamBlock[];

Defined in: packages/core/src/config/aparte-config.ts:1340

The registered block grammars, in registration order.

AparteStreamBlock[]

ts
setConversationManager(manager): void;

Defined in: packages/core/src/config/aparte-config.ts:1352

Register a AparteConversationManager so any UI controller can persist & load conversations without coupling to a framework wrapper.

ParameterType
managerAparteConversationManager

void

ts
getConversationManager(): AparteConversationManager | undefined;

Defined in: packages/core/src/config/aparte-config.ts:1357

Returns the registered AparteConversationManager, or undefined if none.

AparteConversationManager | undefined

ts
setElicitationPresenter(presenter, owner?): void;

Defined in: packages/core/src/config/aparte-config.ts:1377

Register the presenter that renders typed input requests (a choice, a confirmation, a text field, a form) and resolves with the user’s answer. The <aparte-elicitation> Web Component registers itself here; an app can override with its own framework-native presenter. Pass null to clear.

Several can be registered, one per chat: owner is an element of the chat the presenter belongs to (<aparte-elicitation> passes itself). A request whose target is inside that chat reaches its presenter; a request with no target, or from a chat with no presenter of its own, reaches the most recently registered one. Registering a presenter again makes it the most recent.

ParameterType
presenterAparteElicitationPresenter | null
owner?HTMLElement

void

ts
removeElicitationPresenter(presenter): void;

Defined in: packages/core/src/config/aparte-config.ts:1397

Withdraw ONE presenter, named, leaving any others registered.

This is what an unmounting <aparte-elicitation> needs and what setElicitationPresenter(null) could not express: clearing the slot took every other mounted chat’s presenter down with it. Removing a presenter that is not registered is a no-op.

ParameterType
presenterAparteElicitationPresenter

void

ts
getElicitationPresenter(): AparteElicitationPresenter | undefined;

Defined in: packages/core/src/config/aparte-config.ts:1405

The presenter a request with no target would reach — the most recently registered one. Kept for the callers that ask “is anybody able to present?”.

AparteElicitationPresenter | undefined

ts
requestUserInput(request): Promise<AparteElicitationResult>;

Defined in: packages/core/src/config/aparte-config.ts:1451

Ask the person for typed input in the middle of a run, and wait for the answer. The primitive behind ask_user and tool approval: the kind of question is its schema.

Resolves accept with the value, or decline when the person declines.

Rejects, with no answer, with an AparteElicitationAbortError (its name is 'AbortError') whose reason is 'aborted' — the turn was stopped, the signal fired, or another request took the question away — or 'no-presenter', when nothing is mounted to ask it (with a console warning, once). await it in a try, or attach a .catch().

The question goes to the presenter of the chat that holds request.target (see setElicitationPresenter()); <aparte-elicitation> is the default one, and registers itself once it is in the DOM. A chat shows one question at a time: a second request in the same chat waits until the first is answered or ends, and requests with no target share one queue.

ParameterType
requestAparteElicitationRequest

Promise<AparteElicitationResult>

ts
reset(): void;

Defined in: packages/core/src/config/aparte-config.ts:1536

Put the configuration back to its defaults: the providers and the selected model, the tools with their renderers and summaries, the renderers and render hooks, the Markdown and highlight providers, the sanitizer, the system prompt, the locale, icons, avatars, actions and host handlers, the conversation manager, the question presenters and their queue. Three things stay: the transport, the key sources added with registerKeyProvider() (an AparteClient’s keyResolver among them), and the subscribe() listeners.

void


ts
function attachConfig(el, config): void;

Defined in: packages/core/src/config/config-context.ts:83

Attach an instance config to el, making it a resolution boundary: every component inside el resolves to config instead of the global singleton. Idempotent; pass the same element to replace its config.

Anything already mounted inside el that implements AparteConfigAware is told, because attaching a boundary silently rewrites what those children resolve to and a registration they already made is now on the wrong object.

ParameterType
elHTMLElement
configAparteConfig

void


ts
function detachConfig(el): void;

Defined in: packages/core/src/config/config-context.ts:101

Remove the instance-config boundary from el (e.g. in disconnectedCallback). Components under el fall back to the next boundary up, or the global.

Registrants are told BEFORE they would silently re-resolve elsewhere, which is the half that leaked: a wrapper’s unbind() detaches the boundary before the framework removes the DOM, so by the time disconnectedCallback ran, the element’s own resolveConfig(this) already returned the global — its “am I still the registered one?” guard compared against the wrong config, found no match, and left the instance config holding a removed element for good.

ParameterType
elHTMLElement

void


ts
function resolveConfig(el): AparteConfig;

Defined in: packages/core/src/config/config-context.ts:134

Resolve the config governing el: the nearest ancestor boundary’s instance config (including el itself), or aparteGlobalConfig when none is present. Cheap — a single closest().

DO NOT cache this at connectedCallback. An earlier version of this comment advised exactly that, and two elements followed it into a real bug: a framework wrapper calls AparteChatHost.bind() — which is what runs attachConfig — from its POST-mount hook (React useEffect, Vue onMounted, Svelte onMount, Angular ngAfterViewInit), so children mounted in the same commit connect BEFORE the boundary exists. A connect-time cache therefore freezes the global config forever, and the config prop every wrapper advertises silently does nothing for that element.

Resolve it live, in a getter. closest() on a shallow tree is not a cost worth a correctness bug — aparte-chat-bubble has done it that way from the start, and it is the one element that was never affected.

That advice covers READERS, and it is not enough on its own. An element that WRITES to its config — registering a presenter, a renderer, a keyed listener — cannot fix the ordering by resolving live, because the write already happened. Those implement AparteConfigAware and re-register when told. The reader/writer distinction is the one this file missed the first time round: the sweep that applied the advice above fixed every reader and left both writers broken.

ParameterType
elElement | null | undefined

AparteConfig


ts
function runWithConfig<T>(config, fn): T;

Defined in: packages/core/src/config/config-context.ts:149

Run fn with config as the ambient render config (restored after).

Type Parameter
T
ParameterType
configAparteConfig
fn() => T

T


ts
function contextConfig(el?): AparteConfig;

Defined in: packages/core/src/config/config-context.ts:164

The ambient render config set by runWithConfig, else the config resolved from el (when provided), else the global singleton. Capture it synchronously at the top of a renderer — never after an await.

ParameterType
el?Element | null

AparteConfig


ts
function subscribeConfigChange(el, handler): () => void;

Defined in: packages/core/src/config/config-subscribe.ts:36

ParameterType
elElement
handler() => void

() => void


ts
function isSafeUrl(value, tag): boolean;

Defined in: packages/core/src/config/sanitize.ts:166

ParameterType
valuestring
tagstring

boolean


ts
const APARTE_DEFAULT_BUBBLE_ACTIONS: {
  copy: true;
  retry: false;
  edit: false;
  feedback: false;
  info: false;
};

Defined in: packages/core/src/config/aparte-config.ts:70

The bubble-action defaults — copy on, everything else off.

The rule: an affordance core cannot honor end-to-end does not appear by default. Core can copy text by itself; retry and edit only do something when a host (AparteClient) re-sends and rewrites, and feedback/info only when the app listens for aparte-feedback / aparte-message-info. Rendering them unconditionally showed dead buttons in every display-only integration — ours included, which is how we found it.

Exported so a consumer can read the defaults instead of hard-coding them.

ts
readonly copy: true = true;
ts
readonly retry: false = false;
ts
readonly edit: false = false;
ts
readonly feedback: false = false;
ts
readonly info: false = false;

ts
const APARTE_DEFAULT_HOST_HANDLERS: {
  attachmentPreview: false;
};

Defined in: packages/core/src/config/aparte-config.ts:86

The host-handler declarations — nothing declared.

Same rule as APARTE_DEFAULT_BUBBLE_ACTIONS, applied outside the action bar: an image tile you can click is a request core forwards to the app. Undeclared, it is not rendered (and the tile is not even signalled as clickable) instead of doing nothing.

ts
readonly attachmentPreview: false = false;

ts
const aparteGlobalConfig: AparteConfig;

Defined in: packages/core/src/config/aparte-config.ts:1641

The page-wide configuration: every chat reads it unless an ancestor carries an instance of its own (attachConfig). There is one per window, kept on window, so two copies of the package on one page share it; off the browser (a server render, a test) each import creates its own.


ts
const APARTE_HOST_ATTR: "data-aparte-host" = 'data-aparte-host';

Defined in: packages/core/src/config/config-context.ts:22

Marks an element as an instance-config boundary (used by closest()).


ts
const APARTE_CONFIG_CHANGE: "aparte-config-change" = 'aparte-config-change';

Defined in: packages/core/src/config/config-subscribe.ts:34

The event AparteConfig._notify() dispatches on window.


ts
const APARTE_DEFAULT_ICON_FALLBACKS: Record<AparteIconName, string> = APARTE_ICON_GLYPHS;

Defined in: packages/core/src/config/icon-provider.ts:99

The default drawing for every name, assembled from icons/glyphs.ts.

getIcon() reads this by a COMPUTED key, so a bundler cannot tell which entries a build reaches and keeps the object whole. That is the constraint that shapes the icon set: what belongs here is what core itself draws, and nothing else — an icon nobody renders would still ship to everybody. Extra glyphs are individual exports a consumer imports by name, never members of this record.


ts
const APARTE_DEFAULT_LOCALE: AparteLocale;

Defined in: packages/core/src/config/locale.ts:461

Default English Locale (Zero-dependency) Hardcoded to ensure the core works out-of-the-box.


ts
const defaultSanitizer: AparteSanitizer;

Defined in: packages/core/src/config/sanitize.ts:423

The built-in sanitizer. Parses html with the platform DOMParser, rebuilds an allowlisted tree, and re-serializes it. Falls back to a regex scrub only when no DOMParser exists (non-browser runtime).


Defined in: packages/core/src/config/action-provider.ts:34

A custom action button, registered once via aparteGlobalConfig.registerAction(...) and placed in the message (bubble) toolbar via zones.

Declarative and framework-agnostic: clicking it emits an aparte-action CustomEvent (bubbles, composed) carrying { actionId, zone, … }, exactly like the built-in retry/feedback buttons — so you wire it the same way in React/Vue/Svelte/Angular and in vanilla. An optional onClick callback fires alongside the event for imperative convenience.

For a button in the composer, write an <aparte-composer-action> in your markup and listen for aparte-action-click — see that element’s docs.

ts
aparteGlobalConfig.registerAction({
  id: 'share', icon: '<svg>…</svg>', label: 'Share',
  zones: ['bubble'], bubble: { roles: ['assistant'] },
});
chatEl.addEventListener('aparte-action', (e) => {
  if (e.detail.actionId === 'share') share(e.detail.messageId);
});
ts
id: string;

Defined in: packages/core/src/config/action-provider.ts:36

Stable id — echoed as actionId in the aparte-action event.

ts
icon: string;

Defined in: packages/core/src/config/action-provider.ts:43

Icon for the button:

  • If it starts with <, it is treated as raw HTML/SVG and used directly.
  • Otherwise it is treated as a key for aparteGlobalConfig.getIcon(). If the provider doesn’t have the key, falls back to iconFallback or id.
ts
optional iconFallback?: string;

Defined in: packages/core/src/config/action-provider.ts:45

Inline SVG/HTML fallback used when the icon provider doesn’t have the key.

ts
label: string;

Defined in: packages/core/src/config/action-provider.ts:47

Accessible label (sets aria-label + title) — a locale key or a raw string.

ts
optional order?: number;

Defined in: packages/core/src/config/action-provider.ts:49

Lower renders first among custom actions (they follow the built-ins).

ts
zones: "bubble"[];

Defined in: packages/core/src/config/action-provider.ts:51

Which zones this action appears in.

ts
optional bubble?: {
  roles?: ("user" | "assistant")[];
};

Defined in: packages/core/src/config/action-provider.ts:53

Bubble-toolbar placement (used when zones includes 'bubble').

ts
optional roles?: ("user" | "assistant")[];

Bubble roles this action shows on. Default: both user and assistant.

ts
optional onClick?: (event, context?) => void;

Defined in: packages/core/src/config/action-provider.ts:58

Optional imperative callback, fired alongside the aparte-action event.

ParameterType
eventEvent
context?unknown

void


Defined in: packages/core/src/config/aparte-config.ts:90

ts
provider: string;

Defined in: packages/core/src/config/aparte-config.ts:91

ts
model: string;

Defined in: packages/core/src/config/aparte-config.ts:92


Defined in: packages/core/src/config/aparte-config.ts:95

ts
save: (provider, model) => void;

Defined in: packages/core/src/config/aparte-config.ts:97

Called whenever the selected provider+model changes

ParameterType
providerstring
modelstring

void

ts
load: () => AparteModelPreference | null;

Defined in: packages/core/src/config/aparte-config.ts:99

Called on init to restore the previously saved selection. Return null if nothing stored.

AparteModelPreference | null


Defined in: packages/core/src/config/aparte-config.ts:108

Incremental Markdown renderer bound to a target element. Created once per streaming message: tokens are fed via write() and parsed + appended to the DOM incrementally (O(n) total — no full re-parse / innerHTML rebuild on every token). end() flushes any pending text when the stream completes.

ts
write(chunk): void;

Defined in: packages/core/src/config/aparte-config.ts:110

Append a chunk of Markdown text — parsed incrementally, appended as DOM nodes.

ParameterType
chunkstring

void

ts
end(): void;

Defined in: packages/core/src/config/aparte-config.ts:112

Finalize the stream (flush any pending text).

void


Defined in: packages/core/src/config/avatar-provider.ts:22

Avatar Provider Interface

Defines the contract for plugging custom avatar rendering into chat messages. Unlike the icon provider (which returns HTML strings), the avatar provider receives the live host element and fills it with whatever DOM the consumer wants — including framework components mounted via createComponent (Angular), createRoot (React), etc.

ts
aparteGlobalConfig.setAvatarProvider({
  render: (role, host) => {
    if (role === 'assistant') {
      const ref = createComponent(MascotComponent, { hostElement: host });
      ref.setInput('variant', 'classic');
      return () => ref.destroy();
    }
    host.textContent = 'You';
  },
});
ts
render(role, host): void | (() => void);

Defined in: packages/core/src/config/avatar-provider.ts:33

Fill the avatar host element with custom DOM.

ParameterTypeDescription
role"user" | "assistant"The message role this avatar represents.
hostHTMLElementThe .aparte-avatar element. Already styled (size, radius) via CSS variables. The provider owns its inner content.

void | (() => void)

  • Optional cleanup function. Called when the message is detached or re-rendered, so live components can be disposed cleanly.

Defined in: packages/core/src/config/config-context.ts:52

Implemented by an element that REGISTERS something on its config rather than only reading from it — an elicitation presenter, a renderer, a listener keyed on the config object.

Reading is safe without this hook: resolveConfig(this) at every use site always sees the current boundary. WRITING is not. A write happens once, into whichever config was resolvable at that moment — and under all four wrappers that moment is before the boundary exists, because AparteChatHost.bind() runs from a post-mount hook. So the write lands on the global singleton and the instance config never gets it.

That is not hypothetical: <aparte-elicitation> registered its presenter at connect, so under every wrapper requestUserInput() found no presenter on the instance config and answered the model cancel — the model was told the user refused while the user was shown nothing at all.

Implement this and re-do the registration: move it off previous, put it on next. Both attachConfig and detachConfig call it, so the same method handles teardown, which is where the mirror-image leak lived.

ts
aparteConfigChanged(next, previous): void;

Defined in: packages/core/src/config/config-context.ts:53

ParameterType
nextAparteConfig
previousAparteConfig

void


Defined in: packages/core/src/config/elicitation-field-renderer.ts:6

What the panel gives a custom field renderer.

ts
readonly optional key?: string;

Defined in: packages/core/src/config/elicitation-field-renderer.ts:12

The form key this field answers, in an object (multi-question) schema. undefined for a single-field request, where the panel’s message is the question.

ts
notifyChange(): void;

Defined in: packages/core/src/config/elicitation-field-renderer.ts:20

Call whenever your field’s value changes.

The panel re-reads isComplete() on every change to enable or disable the send button — a field that never notifies is a field whose answer can never be submitted.

void


Defined in: packages/core/src/config/elicitation-field-renderer.ts:26

A field you rendered, and the three things the panel needs from it.

ts
readonly el: HTMLElement;

Defined in: packages/core/src/config/elicitation-field-renderer.ts:28

The element to place in the panel.

ts
getValue(): unknown;

Defined in: packages/core/src/config/elicitation-field-renderer.ts:30

The current answer, shaped as the field’s schema kind implies.

unknown

ts
isComplete(): boolean;

Defined in: packages/core/src/config/elicitation-field-renderer.ts:32

Whether the answer is usable — gates the send button.

boolean

ts
optional focus(): void;

Defined in: packages/core/src/config/elicitation-field-renderer.ts:34

Focus the input. Called when the panel opens on this field.

void


Defined in: packages/core/src/config/icon-provider.ts:17

ts
optional copy?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:24

Copy button icon

string

ts
optional check?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:26

Success/check icon

string

ts
optional send?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:28

Send message icon

string

ts
optional loading?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:30

Loading/spinner indicator

string

ts
optional error?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:32

Error indicator

string

ts
optional expand?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:34

Expand/show more icon

string

ts
optional collapse?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:36

Collapse/show less icon

string

ts
optional terminal?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:38

Terminal/command prompt icon

string

ts
optional paperclip?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:40

File attachment / paperclip icon (used by upload plugin)

string

ts
optional image?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:42

Image file icon (used by upload plugin)

string

ts
optional file?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:44

Generic file icon (used by upload plugin)

string

ts
optional scrollDown?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:46

Scroll to bottom button icon

string

ts
optional retry?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:48

Retry / regenerate icon

string

ts
optional edit?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:50

Edit / pencil icon

string

ts
optional thumbUp?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:52

Thumbs up / positive feedback icon

string

ts
optional thumbDown?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:54

Thumbs down / negative feedback icon

string

ts
optional prevBranch?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:56

Previous branch arrow

string

ts
optional nextBranch?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:58

Next branch arrow

string

ts
optional tool?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:60

Tool call indicator (wrench)

string

ts
optional close?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:62

Close / failed / rejected (✕)

string

ts
optional stop?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:64

Stop / halt streaming (■)

string

ts
optional info?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:66

Informational notice (ⓘ)

string

ts
optional archive?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:68

Archive a conversation

string

ts
optional unarchive?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:70

Restore an archived conversation

string

ts
optional download?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:72

Download / save to disk

string

ts
optional more?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:74

A row’s actions button (⋯)

string

ts
optional pin?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:76

Pin a conversation

string

ts
optional trash?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:78

Delete (a bin)

string

ts
optional menu?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:80

Open a navigation drawer (☰) — the app header’s toggle

string

ts
optional alertTriangle?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:82

A warning triangle — the alert recipe’s icon

string

ts
optional pause?: () => string;

Defined in: packages/core/src/config/icon-provider.ts:84

Paused — the tool row’s state while a person is asked to decide

string


ts
type AparteActionZone = "bubble";

Defined in: packages/core/src/config/action-provider.ts:10

Zones where a custom action can appear — the message (bubble) toolbar, and only it.

The type is a single member on purpose: a composer button is an element the consumer writes in their own markup (<aparte-composer-action>), not a registry entry, so the composer never asked the registry for anything. The zone stays a named type rather than the bare literal because getActions(zone) reads as a lookup, and a second zone (a toolbar of core’s own, say) would be added here.


ts
type AparteMarkdownProvider = (raw) => string;

Defined in: packages/core/src/config/aparte-config.ts:38

ParameterType
rawstring

string


ts
type AparteHighlightProvider = 
  | ((code, lang) => string)
  | ((code, lang) => Promise<string>);

Defined in: packages/core/src/config/aparte-config.ts:39


ts
type AparteSystemPromptVarsProvider = () => Record<string, string>;

Defined in: packages/core/src/config/aparte-config.ts:42

Record<string, string>


ts
type AparteKeyProvider = (providerId) => 
  | string
  | Record<string, string>
  | undefined
  | null
| Promise<string | Record<string, string> | undefined | null>;

Defined in: packages/core/src/config/aparte-config.ts:54

How the page answers “what are this provider’s credentials?”.

A bare key, or the same { apiKey, endpoint } record the transport reads (readAuth) and fetchModels accepts. The record is not a nicety: a consumer who points a preset at their own host — a corporate proxy, a self-hosted vLLM, an LM Studio box — configures it as endpoint, and while this channel could only carry a string, refreshProviderModels sent GET {vendor default}/models with the key that host had been given.

ParameterType
providerIdstring

| string | Record<string, string> | undefined | null | Promise<string | Record<string, string> | undefined | null>


ts
type AparteStreamingMarkdownProvider = (target) => AparteStreamingMarkdownRenderer;

Defined in: packages/core/src/config/aparte-config.ts:121

Factory for an AparteStreamingMarkdownRenderer: given a target element, returns a renderer that appends parsed Markdown into it. Supplied by an opt-in provider package (e.g. @aparte/provider-streaming-markdown); when none is registered the chat falls back to the one-shot AparteMarkdownProvider.

ParameterType
targetHTMLElement

AparteStreamingMarkdownRenderer


ts
type AparteAttachmentRenderer = (attachment) => string | HTMLElement;

Defined in: packages/core/src/config/attachment-renderer.ts:28

Attachment Renderer

Replace the chip rendered for each attachment on a user message — the default is an image thumbnail or a file chip (extension + name). Return an HTML string or a ready HTMLElement (charter §6 render hooks: string | HTMLElement), e.g. a PDF page preview, an audio player, or a richer card. Called once per attachment.

When you provide a renderer you own the markup AND the interactions: the built-in image-tile click that dispatches aparte-attachment-preview is NOT wired for custom output — dispatch it yourself (bubbles, composed) or handle clicks your own way.

ParameterType
attachmentAparteAttachment

string | HTMLElement

ts
aparteGlobalConfig.setAttachmentRenderer((att) => {
  if (att.type === 'application/pdf') {
    const el = document.createElement('div');
    el.className = 'my-pdf-chip';
    el.textContent = att.name;
    return el;
  }
  return `<div class="my-file">${att.name}</div>`;
});

ts
type AparteBubbleShellRenderer = (ctx) => string | HTMLElement;

Defined in: packages/core/src/config/bubble-shell-renderer.ts:74

Bubble-Shell Renderer (advanced)

Replace the structural skeleton of <aparte-chat-bubble> — the markup the bubble renders once and then populates — while keeping all of its behavior (segments, streaming, action bar, avatar, branch picker). This is the whole-structure override that renderBubble (wrapper-level, replaces the element entirely) is not: here the native bubble stays in charge.

Return an HTML string or a ready HTMLElement (charter §6: string | HTMLElement). Because the bubble queries known class hooks after rendering the shell, your shell MUST honor this contract:

  • Root element must be .aparte-message (it carries data-role and receives data-streaming / data-error; the styles target it).
  • Include the region hooks you want the bubble to populate, in the order the default shell renders them. Any you omit simply stay empty (every lookup is null-guarded — graceful degradation):
    • .aparte-avatar — filled by the avatar provider / initial
    • .aparte-name — the display name (you set it from ctx.name)
    • .aparte-timestamp — the formatted time
    • .aparte-attachments — user-message attachment chips
    • .aparte-message-content — the painted content box, and it HOLDS the next three (.aparte-segments, .aparte-content, .aparte-waiting): the background, the radius and the padding of a user message are declared on this box, so rendering the three as its siblings leaves a message with no bubble — every behaviour intact, the look gone. The bubble hides the box when the turn has nothing to show, so an attachments-only message does not draw an empty coloured rectangle
    • .aparte-segments — streamed/structured segments
    • .aparte-content — simple markdown content
    • .aparte-waiting — the dots between “user sends” and the first token. The bubble only SHOWS and HIDES this region; the markup that paints is yours: a .aparte-dots span holding three .aparte-dot spans (the stylesheet animates them), and a .aparte-sr-only span for the label the screen reader hears. With the label alone the region has nothing to draw and the reader watches an empty box while the model composes
    • .aparte-footer — the row that holds the branch picker and the actions
    • .aparte-branch-picker — with .aparte-branch-prev / .aparte-branch-label / .aparte-branch-next for sibling navigation, and .aparte-branch-status (a .aparte-sr-only span with aria-live="polite") for the announcement
    • .aparte-action-bar — copy/retry/edit/… + custom actions

Omitting most of these costs you something you can see. The two that fail silently are .aparte-waiting and .aparte-branch-status: without the first there is no “thinking” state at all while the model composes, and without the second a screen-reader user gets no word that the branch moved — the arrows deliberately do not take focus, so nothing else announces it.

ARIA is the one thing this list does NOT ask of you. A shell is markup, and a contract that also demanded a role and a name would lose both the first time an author forgot one, silently. So the bubble writes role="article" and the accessible name onto your root itself, after your shell renders — unless the root already carries a role, which is how you override it: the bubble then leaves the accessible name to you as well.

Two more classes carry the DEFAULT LAYOUT and nothing else — the bubble never queries them, so a shell that lays itself out differently is free to drop both: .aparte-body (the column beside the avatar, and the containing block the footer floats in) and .aparte-header (the name and time row). Keep them and the default stylesheet lays your shell out like the native bubble.

Prefer renderBubble (wrapper) when you want a fully custom element; use this when you want to keep the native bubble’s machinery but reshape its layout.

ParameterTypeDescription
ctx{ role: "user" | "assistant"; name: string; avatarInitial: string; }-
ctx.role"user" | "assistant"’user'
ctx.namestringDisplay name for the header.
ctx.avatarInitialstringDefault one-letter avatar fallback.

string | HTMLElement

HTML string or a DOM element whose root is .aparte-message.


ts
type AparteElicitationFieldRenderer = (field, ctx) => 
  | AparteElicitationFieldControl
  | null;

Defined in: packages/core/src/config/elicitation-field-renderer.ts:75

Elicitation Field Renderer

Replace ONE field of the question panel — a choice, a yes/no, a free-text answer — while the panel keeps everything around it: placement in the composer, the accept/decline/cancel contract, the send-button gating, focus, and the teardown when a turn is stopped.

Until this existed the surface was all-or-nothing: the built-in panel, or setElicitationPresenter and you reimplemented every one of those. Every other customisation point in this library is a hook (charter §6), and this one was missing.

Return null to let the built-in render that field, which is what makes it practical to override a single kind:

ParameterType
fieldAparteElicitationField
ctxAparteElicitationFieldContext

| AparteElicitationFieldControl | null

ts
aparteGlobalConfig.setElicitationFieldRenderer((field, ctx) => {
  if (field.type !== 'enum') return null;          // built-in handles the rest
  const el = document.createElement('div');
  el.className = 'my-chips';
  let picked = '';
  for (const opt of field.options) {
    const b = document.createElement('button');
    b.type = 'button';
    b.textContent = opt.label ?? opt.value;
    b.onclick = () => { picked = opt.value; ctx.notifyChange(); };
    el.appendChild(b);
  }
  return { el, getValue: () => picked, isComplete: () => picked !== '' };
});

**Why this returns a control and not `string | HTMLElement`** like the render
hooks in charter §6: a field has to hand back a VALUE. A hook that must also read
the user's input is a control, not a decoration, and pretending otherwise would
mean the panel scraping your markup for inputs by convention — which is a
contract that breaks silently the first time someone styles it differently.

ts
type AparteErrorRenderer = (ctx) => string | HTMLElement;

Defined in: packages/core/src/config/error-renderer.ts:20

Error Renderer

Replace the content of an error bubble — the default is an icon + “Error” title

  • message (+ details). Return an HTML string or a ready HTMLElement (charter §6 render hooks: string | HTMLElement), e.g. a friendly message with your own “Try again” button. This drives the built-in error segment renderer, so it’s the one place to customize error UI (rather than registering a segment renderer for the error type yourself). The bubble also reflects data-error on its .aparte-message while an error segment is present, for CSS theming.
ParameterTypeDescription
ctx{ message: string; details?: string; }-
ctx.messagestringHuman-readable error message (the error segment’s content).
ctx.details?stringOptional error code/details (the error segment’s details).

string | HTMLElement

ts
aparteGlobalConfig.setErrorRenderer(({ message }) => {
  const el = document.createElement('div');
  el.className = 'my-error';
  el.textContent = `Something went wrong: ${message}`;
  return el;
});

ts
type AparteIconName = keyof AparteIconProvider;

Defined in: packages/core/src/config/icon-provider.ts:88

Icon names available in the provider


ts
type AparteLocale = {
  inputPlaceholder: string;
  sendButton: string;
  copy: string;
  copied: string;
  retry: string;
  thinking: string;
  typing: string;
  error: string;
  running: string;
  run: string;
  file: string;
  roleNameUser: string;
  roleNameAssistant: string;
  yourMessage: string;
  assistantResponse: string;
  messageActions: string;
  edit: string;
  editConfirm: string;
  editCancel: string;
  feedbackPositive: string;
  feedbackNegative: string;
  previousResponse: string;
  nextResponse: string;
  approveTool?: string;
  rejectTool?: string;
  messageInfo?: string;
  newChat: string;
  loadingConversations: string;
  conversationList?: string;
  loadingConversation: string;
  deleteConversation: string;
  archiveConversation?: string;
  unarchiveConversation?: string;
  conversationActions?: string;
  renameConversation?: string;
  conversationTitle?: string;
  pinConversation?: string;
  unpinConversation?: string;
  deleteConversationConfirm?: string;
  cancel?: string;
  conversationGroupPinned?: string;
  conversationGroupToday?: string;
  conversationGroupYesterday?: string;
  conversationGroupWeek?: string;
  conversationGroupMonth?: string;
  scrollRailLabel?: string;
  sidebarLabel?: string;
  splitHandleLabel?: string;
  transcript?: string;
  elicitationOther?: string;
  elicitationOtherPlaceholder?: string;
  elicitationOtherLabel?: string;
  submitButton?: string;
  stopButton?: string;
  elicitationRecommended?: string;
  elicitationSkip?: string;
  elicitationYes?: string;
  elicitationNo?: string;
  elicitationAnswerLabel?: string;
  approvalInstructionPlaceholder?: string;
  approvalAsk?: string;
  scrollToBottom?: string;
  suggestionsLabel?: string;
  contextLabel?: string;
  compactionSummaryTitle?: string;
  approvalWaiting?: string;
  toolRunning?: string;
  toolCompleted?: string;
  toolRejected?: string;
  toolStopped?: string;
  toolFailed?: string;
  toolInput?: string;
  toolOutput?: string;
  approvalOptionsLabel?: string;
  approvalArgsLabel?: string;
  actionUpload?: string;
  removeAttachment?: string;
  selectSearchPlaceholder?: string;
  selectSearchLabel?: string;
  loading?: string;
  modelSelectorPlaceholder?: string;
  approvalModeLabel?: string;
  download?: string;
  preview?: string;
  code?: string;
  generating?: string;
  rebuildingPreview?: string;
  previewPending?: string;
  sandboxError?: string;
  sandboxErrorHint?: string;
  tag?: string;
  direction?: "ltr" | "rtl";
};

Defined in: packages/core/src/config/locale.ts:33

Every translatable string core itself renders — the CLOSED list.

Core keeps only the English default in memory (APARTE_DEFAULT_LOCALE); other languages arrive through aparteGlobalConfig.setLocale().

Closed, and an alias rather than an interface, on purpose. This used to end with [key: string]: string | undefined, and that one line disabled the only compile-time check the locale had: AparteConfig.t(key: keyof AparteLocale) looks airtight, but with an index signature keyof widens to string and EVERY literal typechecks. A renamed key compiled clean, t() returned '' at runtime, and the UI rendered an empty label with no error and no warning — which is how submitButton, stopButton and actionUpload each shipped read-but-never-declared, the last one reported by a user from a live language switcher.

The open half is AparteLocaleExtensions, and an ALIAS is what lets the two meet: TypeScript gives a type alias an implicit index signature and an interface none, so only this form is assignable to the extensions record that setLocale/extendLocale accept and getLocale returns.

Eleven keys here are rendered by no core file — the artifact card’s six, its two sandbox lines, the compaction summary’s title, the approval mode picker’s label and the model selector’s placeholder, all drawn by a plugin. They belong in this bag anyway: the locale is one flat object every package shares, so a translator fills one record and @aparte/locale-fr needs no companion per plugin. They are the exception rather than the pattern, though, and a closed one: a plugin of your own reads its strings off getLocale() and defaults them at the call site (the localization guide shows it), and check:locale-keys holds those eleven in PLUGIN_OWNED and refuses a twelfth. The reason is that a key here is a published name before it is a word: it ships in every locale package, and the frozen surface then keeps it for two releases.

ts
inputPlaceholder: string;

Defined in: packages/core/src/config/locale.ts:35

ts
sendButton: string;

Defined in: packages/core/src/config/locale.ts:36

ts
copy: string;

Defined in: packages/core/src/config/locale.ts:39

ts
copied: string;

Defined in: packages/core/src/config/locale.ts:40

ts
retry: string;

Defined in: packages/core/src/config/locale.ts:41

ts
thinking: string;

Defined in: packages/core/src/config/locale.ts:45

The label of a reasoning block, live or settled (default: “Thinking”)

ts
typing: string;

Defined in: packages/core/src/config/locale.ts:47

What the status line says while a reply is being written, when it carries no text (default: “Typing”)

ts
error: string;

Defined in: packages/core/src/config/locale.ts:48

ts
running: string;

Defined in: packages/core/src/config/locale.ts:49

ts
run: string;

Defined in: packages/core/src/config/locale.ts:52

ts
file: string;

Defined in: packages/core/src/config/locale.ts:55

ts
roleNameUser: string;

Defined in: packages/core/src/config/locale.ts:59

Display name shown above the user’s messages (default: “You”)

ts
roleNameAssistant: string;

Defined in: packages/core/src/config/locale.ts:61

Display name shown above the assistant’s messages (default: “Assistant”)

ts
yourMessage: string;

Defined in: packages/core/src/config/locale.ts:64

ts
assistantResponse: string;

Defined in: packages/core/src/config/locale.ts:65

ts
messageActions: string;

Defined in: packages/core/src/config/locale.ts:66

ts
edit: string;

Defined in: packages/core/src/config/locale.ts:69

ts
editConfirm: string;

Defined in: packages/core/src/config/locale.ts:70

ts
editCancel: string;

Defined in: packages/core/src/config/locale.ts:71

ts
feedbackPositive: string;

Defined in: packages/core/src/config/locale.ts:72

ts
feedbackNegative: string;

Defined in: packages/core/src/config/locale.ts:73

ts
previousResponse: string;

Defined in: packages/core/src/config/locale.ts:74

ts
nextResponse: string;

Defined in: packages/core/src/config/locale.ts:75

ts
optional approveTool?: string;

Defined in: packages/core/src/config/locale.ts:77

Approve button on a tool awaiting human approval (default: “Approve”)

ts
optional rejectTool?: string;

Defined in: packages/core/src/config/locale.ts:79

Reject button on a tool awaiting human approval (default: “Reject”)

ts
optional messageInfo?: string;

Defined in: packages/core/src/config/locale.ts:81

Aria-label / tooltip for the message info (“i”) action button (default: “Details”)

ts
newChat: string;

Defined in: packages/core/src/config/locale.ts:85

Default title for a new conversation (default: “New Chat”)

ts
loadingConversations: string;

Defined in: packages/core/src/config/locale.ts:87

Read by a screen reader while the conversation list is on its way (default: “Loading conversations”)

ts
optional conversationList?: string;

Defined in: packages/core/src/config/locale.ts:89

Accessible name of the conversation list, a navigation landmark (default: “Conversations”)

ts
loadingConversation: string;

Defined in: packages/core/src/config/locale.ts:91

Read by a screen reader while a conversation’s messages are on their way (default: “Loading the conversation”)

ts
deleteConversation: string;

Defined in: packages/core/src/config/locale.ts:97

The “Delete” item of a row’s menu (default: “Delete”). Used to be the aria-label of a permanent ✕ on every row (“Delete conversation”); the menu is named after the row, so the item carries only the verb.

ts
optional archiveConversation?: string;

Defined in: packages/core/src/config/locale.ts:99

The “Archive” item of a row’s menu (default: “Archive”)

ts
optional unarchiveConversation?: string;

Defined in: packages/core/src/config/locale.ts:101

The same item on an archived row (default: “Unarchive”)

ts
optional conversationActions?: string;

Defined in: packages/core/src/config/locale.ts:103

Accessible name of the ⋯ button that opens a row’s menu (default: “Conversation actions”)

ts
optional renameConversation?: string;

Defined in: packages/core/src/config/locale.ts:105

The “Rename” item of a row’s menu (default: “Rename”)

ts
optional conversationTitle?: string;

Defined in: packages/core/src/config/locale.ts:107

Accessible name of the inline input a rename opens (default: “Conversation title”)

ts
optional pinConversation?: string;

Defined in: packages/core/src/config/locale.ts:109

The “Pin” item of a row’s menu (default: “Pin”)

ts
optional unpinConversation?: string;

Defined in: packages/core/src/config/locale.ts:111

The same item on a pinned row (default: “Unpin”)

ts
optional deleteConversationConfirm?: string;

Defined in: packages/core/src/config/locale.ts:117

The question asked before a delete (default: “Delete “{title}”?”). {title} is replaced with the row’s title; a delete is the one action of the menu that asks first, because it is the one that cannot be undone.

ts
optional cancel?: string;

Defined in: packages/core/src/config/locale.ts:119

The button that declines that question (default: “Cancel”)

ts
optional conversationGroupPinned?: string;

Defined in: packages/core/src/config/locale.ts:121

Heading of the pinned rows, which come first (default: “Pinned”)

ts
optional conversationGroupToday?: string;

Defined in: packages/core/src/config/locale.ts:123

Heading of the rows updated today (default: “Today”)

ts
optional conversationGroupYesterday?: string;

Defined in: packages/core/src/config/locale.ts:125

… yesterday (default: “Yesterday”)

ts
optional conversationGroupWeek?: string;

Defined in: packages/core/src/config/locale.ts:127

… in the seven days before that (default: “Previous 7 days”)

ts
optional conversationGroupMonth?: string;

Defined in: packages/core/src/config/locale.ts:129

… in the thirty days before that (default: “Previous 30 days”). Older rows are headed by their month, formatted with tag.

ts
optional scrollRailLabel?: string;

Defined in: packages/core/src/config/locale.ts:133

Accessible name of <aparte-scroll-rail>, the ticks beside the transcript (default: “Conversation outline”)

ts
optional sidebarLabel?: string;

Defined in: packages/core/src/config/locale.ts:135

Accessible name of <aparte-sidebar> (default: “Conversations”)

ts
optional splitHandleLabel?: string;

Defined in: packages/core/src/config/locale.ts:137

Accessible name of the seam between a split’s two panes (default: “Resize the panes”)

ts
optional transcript?: string;

Defined in: packages/core/src/config/locale.ts:145

Accessible name of the transcript itself (default: “Transcript”).

The scroll surface is a tab stop, because in WebKit a scrollable region that is not focusable cannot be scrolled from the keyboard at all. A tab stop with no name is announced as “group”, so the name ships with the tab stop, not after it.

ts
optional elicitationOther?: string;

Defined in: packages/core/src/config/locale.ts:149

The free-text fallback option in a choice (default: “Other…“)

ts
optional elicitationOtherPlaceholder?: string;

Defined in: packages/core/src/config/locale.ts:151

Placeholder of the free-text input that option reveals (default: “Type your answer…“)

ts
optional elicitationOtherLabel?: string;

Defined in: packages/core/src/config/locale.ts:153

Accessible name of that input, which has no visible label (default: “Custom answer”)

ts
optional submitButton?: string;

Defined in: packages/core/src/config/locale.ts:162

The send button while a question panel is open, where it submits an ANSWER rather than sending a message (default: “Submit”).

The component has been reading this key since the panel API existed; it was never declared, so t() returned "" and the hardcoded fallback showed. A key read and never declared is worse than a literal: it looks translated.

ts
optional stopButton?: string;

Defined in: packages/core/src/config/locale.ts:178

The cancel/stop button that replaces send while a turn is streaming (default: “Stop”).

aparte-composer-cancel has been reading this key since it existed and it was never declared — the same defect this file already records for submitButton one entry up, found again while auditing a different question. So it has never been translatable in ANY language, not even after a full reload: t() returned nothing and the || 'Stop' fallback rendered every time.

The button carries no visible text — this string is its aria-label and its title. Which is exactly why the gap survived: nothing on screen was in the wrong language, so only a screen-reader user or someone hovering would ever have met it. Most of the composer’s translatable surface is like this.

ts
optional elicitationRecommended?: string;

Defined in: packages/core/src/config/locale.ts:188

The tag a recommended option wears beside its label (default: “Recommended”). Said, not only tinted: a tint is a hint the eye may miss and a screen reader never gets.

(elicitationNext lived here until 2026-08-28, for the composer’s button while a form had questions ahead. That meaning is gone with the button’s “advance” mode — the chips are the navigation — so the key went with it, not deprecated.)

ts
optional elicitationSkip?: string;

Defined in: packages/core/src/config/locale.ts:199

The affordance that declines the request (default: “Skip”).

“Skip” reads as “skip THIS one” on a form of several questions, while it declines the whole request — including questions already answered. That is MCP’s decline and the right behaviour. It was briefly renamed to carry the warning; reverted, because the RECEIPT now shows the outcome unambiguously (one declined row, no question attached), so the label no longer has to do that work — and “Skip” is the short conventional word for it.

ts
optional elicitationYes?: string;

Defined in: packages/core/src/config/locale.ts:201

Affirmative choice of a yes/no question (default: “Yes”)

ts
optional elicitationNo?: string;

Defined in: packages/core/src/config/locale.ts:203

Negative choice of a yes/no question (default: “No”)

ts
optional elicitationAnswerLabel?: string;

Defined in: packages/core/src/config/locale.ts:205

Last-resort accessible name for a free-text answer (default: “Your answer”)

ts
optional approvalInstructionPlaceholder?: string;

Defined in: packages/core/src/config/locale.ts:213

Placeholder for the free-text arm of an approval (default: “Or tell it what to do instead…”).

The refusal’s REASON, in the user’s words, which the model now reads because a refusal hands it a turn. Before that this text had nowhere to go.

ts
optional approvalAsk?: string;

Defined in: packages/core/src/config/locale.ts:221

The question an approval asks (default: “Run {tool}?”).

{tool} is replaced with the tool’s name. A placeholder rather than three concatenated fragments, because word order is the first thing a translation changes and “Run” + name + ”?” only works in English.

ts
optional scrollToBottom?: string;

Defined in: packages/core/src/config/locale.ts:231

Accessible name of the viewport’s scroll-to-bottom button (default: “Scroll to bottom”).

It was hardcoded English at both sites that build the button — the one chrome string a screen-reader user in another language always met. Same class of defect as the undeclared-key trio (submitButton, stopButton, actionUpload): the component looked done, the locale looked complete.

ts
optional suggestionsLabel?: string;

Defined in: packages/core/src/config/locale.ts:237

Accessible name of the <aparte-suggestions> group of prompt starters. Optional for the same reason as the keys above: a locale written before the element existed keeps working, and the built-in English fills the gap.

ts
optional contextLabel?: string;

Defined in: packages/core/src/config/locale.ts:239

Accessible name of the <aparte-context> gauge. Optional, like the keys above.

ts
optional compactionSummaryTitle?: string;

Defined in: packages/core/src/config/locale.ts:247

Title line of the summary message @aparte/plugin-compaction injects in place of the summarised turns (default: “Conversation summary”). The plugin reads it through config.t(), so the notice follows the locale like every other string the user reads — it used to be hardcoded, with an emoji, so a French chat compacted into an English header.

ts
optional approvalWaiting?: string;

Defined in: packages/core/src/config/locale.ts:256

What the pill says while the decision is being made elsewhere (default: “Waiting”).

The pill is the anchor, not the control — so it has to say WHY nothing is happening. A pill that just sits there while the answer is expected at the composer is the one thing this placement can get wrong.

ts
optional toolRunning?: string;

Defined in: packages/core/src/config/locale.ts:265

The words in a tool call’s state badge.

A WORD and not only a glyph, which is what every current implementation shows (“Pending”, “Running”, “Completed”, “Error”). A bare cross beside a name reads as a button that removes something — the state was being mistaken for an affordance, which is ratified decision #8 read backwards.

ts
optional toolCompleted?: string;

Defined in: packages/core/src/config/locale.ts:266

ts
optional toolRejected?: string;

Defined in: packages/core/src/config/locale.ts:267

ts
optional toolStopped?: string;

Defined in: packages/core/src/config/locale.ts:268

ts
optional toolFailed?: string;

Defined in: packages/core/src/config/locale.ts:270

The tool row’s word when the handler threw — a crash, not a refusal nor a stop (default: “Failed”).

ts
optional toolInput?: string;

Defined in: packages/core/src/config/locale.ts:272

Label above a tool call’s arguments (default: “Input”)

ts
optional toolOutput?: string;

Defined in: packages/core/src/config/locale.ts:274

Label above a tool call’s result (default: “Output”)

ts
optional approvalOptionsLabel?: string;

Defined in: packages/core/src/config/locale.ts:276

Accessible name for the group of approval options (default: “Your decision”)

ts
optional approvalArgsLabel?: string;

Defined in: packages/core/src/config/locale.ts:285

Heading over the call’s arguments in the approval panel (default: “Arguments”).

The panel named the tool and nothing else, while the arguments — the thing actually being approved — stayed in the transcript row behind a closed disclosure. Same word family as toolInput and deliberately not that key: that one heads a section of a transcript row, this one heads a decision.

ts
optional actionUpload?: string;

Defined in: packages/core/src/config/locale.ts:301

The composer’s attach-file button (default: “Attach file”).

aparte-composer-add-attachment has been READING t('actionUpload') since it existed, and the key was never declared — so t() returned ” and the || 'Attach file' fallback rendered in every language, after every reload. The third instance of this exact defect in this file, after submitButton and stopButton, and the one a user reported from the live language switcher.

A key that is read and not declared cannot be found by reading either side: the component looks correct, the locale looks complete. Cross-checking the two lists is what finds them — t() calls and getLocale().x reads on one side, these declarations on the other.

ts
optional removeAttachment?: string;

Defined in: packages/core/src/config/locale.ts:310

The ✕ on a pending attachment, named after the file it drops (default: “Remove {name}”).

{name} is substituted with the file’s own name — the convention approvalAsk and deleteConversationConfirm already use. The button is icon-only, so this string is the whole of what a screen-reader user hears.

ts
optional selectSearchPlaceholder?: string;

Defined in: packages/core/src/config/locale.ts:320

Placeholder of a searchable select’s filter field (default: “Search…”).

VISIBLE text, which is what separates it from most of this file: it was a literal in the primitive, so a French page opened the model picker and read “Search…” in the box.

ts
optional selectSearchLabel?: string;

Defined in: packages/core/src/config/locale.ts:322

Accessible name of that filter field, which has no visible label (default: “Search options”)

ts
optional loading?: string;

Defined in: packages/core/src/config/locale.ts:324

What an option group shows while its options are being fetched (default: “Loading…“)

ts
optional modelSelectorPlaceholder?: string;

Defined in: packages/core/src/config/locale.ts:336

The <aparte-model-selector> trigger before a model is picked (default: “Select a model…”). The one string that element takes from the locale.

ts
optional approvalModeLabel?: string;

Defined in: packages/core/src/config/locale.ts:342

Accessible name of @aparte/plugin-approval’s mode switch (default: “Approval mode”). The switch is an <aparte-select> with no visible label, so this string is the whole of what a screen-reader user hears there.

ts
optional download?: string;

Defined in: packages/core/src/config/locale.ts:355

The artifact card’s download button (default: “Download”).

Like stopButton, this string is a title and an aria-label on an icon-only button — which is why it stayed English through every locale this project shipped. Nothing on screen was in the wrong language.

ts
optional preview?: string;

Defined in: packages/core/src/config/locale.ts:363

The artifact card’s two tabs (defaults: “Preview” / “Code”).

These two ARE visible text, on a card the landing page leads with, and they were still literals. The card was written before the locale had a home for anything but chrome.

ts
optional code?: string;

Defined in: packages/core/src/config/locale.ts:364

ts
optional generating?: string;

Defined in: packages/core/src/config/locale.ts:373

A binary artifact being produced, and the same artifact’s preview being rebuilt after a reload (defaults: “Generating…” / “Rebuilding preview…”).

generating was also the aria-label of the former pipeline-waiting segment, which is the ONLY thing a screen-reader user hears while a turn is in flight. It was hardcoded, so that announcement was English in every locale.

ts
optional rebuildingPreview?: string;

Defined in: packages/core/src/config/locale.ts:374

ts
optional previewPending?: string;

Defined in: packages/core/src/config/locale.ts:383

What the preview pane says before anybody presses Preview (default: “Press Preview to run this artifact.”).

The pane is empty on purpose — a previewable artifact is model-authored code and mounting it unasked would execute it (ratified decision #8) — so this sentence is the whole of what the reader sees there, and it was a literal.

ts
optional sandboxError?: string;

Defined in: packages/core/src/config/locale.ts:391

A binary artifact whose sandbox run failed: the heading, and the hint under it (defaults: “The sandbox failed during generation.” / “Common cause: …”).

The message BETWEEN them is the sandbox’s own error text and stays untranslated on purpose — it is the tool’s output, not the library’s copy.

ts
optional sandboxErrorHint?: string;

Defined in: packages/core/src/config/locale.ts:392

ts
optional tag?: string;

Defined in: packages/core/src/config/locale.ts:416

BCP-47 language tag for Intl formatting — 'fr-FR', 'ja-JP', 'en-US'.

A locale is otherwise a bag of STRINGS, and a clock is not a string: it is a format. So setLocale(fr) moved fifty strings and left the timestamp above every message reading 7:32 PM, because the only Intl call in the library passed undefined — “use the BROWSER’s locale” — which is not the locale the app just chose. French is 24-hour; the browser was not asked.

A tag and NOT an hour12 flag, deliberately. A flag answers one question for one call site; a tag answers every question Intl can be asked — hour cycle, date order, month names, decimal separator, relative time, list joining — for every locale, including the ones nobody here can enumerate. direction next door is the precedent: this section holds how a language BEHAVES, not what its words are.

Undefined on purpose in the English default: undefined means “follow the browser”, which is the right default for a library and the behaviour every consumer has today. A locale that declares a tag pins the formatting — if you have chosen French strings, French formatting is what you meant.

ts
optional direction?: "ltr" | "rtl";

Defined in: packages/core/src/config/locale.ts:434

Reading direction — 'ltr' or 'rtl'. Written as dir onto the viewport’s scroll container and onto <aparte-composer>, so everything inside inherits it.

Undefined on purpose in the English default, for the reason tag above is: undefined means “follow the host”. The default used to be the literal 'ltr', which meant core wrote dir="ltr" on its own DOM — so a page that had said <html dir="rtl"> got a mirrored app shell around a chat that was still left-to-right, and nothing said why. Not declaring it lets the host’s direction inherit, which is what a library should do with a decision the page has already taken.

A locale that wants the direction PINNED still declares it, and that is the case it exists for: @aparte/locale-ar would say 'rtl' whatever the page around it does. setLocale replaces the object wholesale, so nothing merges a default back in.


ts
type AparteLocaleExtensions = Record<string, string | undefined>;

Defined in: packages/core/src/config/locale.ts:455

The extension half: any other key a plugin or an app wants to carry.

This used to be an index signature ON AparteLocale — [key: string]: string | undefined — and it silently disabled the only compile-time check the locale had. AparteConfig.t(key: keyof AparteLocale) looks airtight; with an index signature, keyof widens to string and EVERY literal typechecks. A renamed key (t('copy') typed as t('copyCodeBlock')) compiled clean, t() returned '' at runtime, and the UI rendered an empty label with no error and no warning. It was one of the six deliberate mistakes an audit planted, and the only guard that could have caught it was this type.

So the two halves are separate now: AparteLocale is CLOSED — it is the list t() keys on — and this is the open bag beside it. AparteConfig.setLocale and AparteConfig.extendLocale accept the intersection, so a plugin still ships its own strings in the same object; only t() stays narrow, which is where the typo has to be caught.


ts
type AparteSanitizer = (html) => string;

Defined in: packages/core/src/config/sanitize.ts:18

Zero-dependency HTML sanitizer for provider output.

Aparte renders LLM-authored content: a markdown or highlight provider turns the assistant’s text into HTML that is then injected via innerHTML. Prompt injection can make a model emit arbitrary markup (<img onerror=…>, <script>, javascript: URLs), so that HTML is untrusted and must be scrubbed before it touches the DOM.

This is an allowlist sanitizer built on the browser’s own parser: the HTML is parsed into an inert document (scripts never execute during DOMParser.parseFromString), the tree is rebuilt keeping only known-safe tags and attributes, then re-serialized. It is deliberately conservative and covers the realistic threat model (LLM-emitted markup). For hardened, audited coverage, register DOMPurify via aparteGlobalConfig.setHtmlSanitizer.

ParameterType
htmlstring

string


ts
type AparteSiblingNavRenderer = (ctx) => string | HTMLElement;

Defined in: packages/core/src/config/sibling-nav-renderer.ts:23

Sibling-Nav Renderer

Replace the branch position indicator — the default ‹ N / M › counter between the prev/next arrows — e.g. with a row of dots. Return an HTML string (multiple roots allowed, set via innerHTML) or a single HTMLElement (charter §6 render hooks: string | HTMLElement). Called whenever the active sibling / count changes.

The prev/next arrows and their behavior (dispatching aparte-branch-navigate) are kept — this hook customizes only the indicator. For click-to-jump navigation, dispatch your own events / use the imperative API from your rendered output.

ParameterTypeDescription
ctx{ count: number; index: number; }-
ctx.countnumberTotal number of siblings (> 1 when a picker shows).
ctx.indexnumber0-based index of the active sibling.

string | HTMLElement

HTML string or a DOM element for the indicator.

ts
aparteGlobalConfig.setSiblingNavRenderer(({ count, index }) =>
  Array.from({ length: count }, (_, i) =>
    `<span class="dot${i === index ? ' active' : ''}"></span>`).join(''));

ts
type AparteStatusRenderer = (text) => string | HTMLElement;

Defined in: packages/core/src/config/status-renderer.ts:22

Status Renderer

Replace the typing indicator’s inner markup — the default is an assistant avatar slot + animated dots + optional text. Return an HTML string or a ready HTMLElement (charter §6 render hooks: string | HTMLElement). The <aparte-chat-status> container keeps owning show/hide (via the visible attribute) and the accessible name, so you supply only the visual.

ParameterTypeDescription
textstringThe typing text (from the text attribute, default “Typing”).

string | HTMLElement

HTML string or a DOM element to place inside the status container.

ts
// A custom spinner, driven by the typing text.
aparteGlobalConfig.setStatusRenderer((text) => {
  const el = document.createElement('div');
  el.className = 'my-typing';
  el.textContent = text;
  return el;
});

Defined in: packages/core/src/config/aparte-config.ts:1657

Detail payload for aparte-config-change, dispatched on window whenever any provider, locale, action or model setting changes.

Five core components listen for it — the widest listener footprint of any event that had no declared type. config is what makes per-instance config work: a component resolving to a different instance compares it and skips the rebuild instead of every chat on the page reacting to every config’s change.

aparte-config-change

ts
config: AparteConfig;

Defined in: packages/core/src/config/aparte-config.ts:1659

The config instance that changed — compare against your own before reacting.

ts
modelConfig: AparteModelConfig;

Defined in: packages/core/src/config/aparte-config.ts:1661

Its model configuration, after the change.