Changelog
Version 0.2.0-alpha.0
Every @aparte/* package ships at this version (they are released in lockstep).
Minor Changes
-
930a108: Harden the server-side
createAparteChatHandler: add an optionalauthorize(req)gate that runs before any work (returnfalsefor a 401, aResponsefor a custom rejection, ortrueto proceed) so you can put auth in front of the key-spending/api/chatroute, and guard the vendor URL build against an adapter returning a non-rooted request path (SSRF) by rejecting anything that isn’t a single-rooted path.@aparte/core -
4aac26d: Add the
<aparte-chat>shell — the container element for a chat. Wrap a viewport and a composer in it and it lays them out as a flex column; leave it empty and it fills in a default composition:<!-- default composition --><aparte-chatcenter-emptyplaceholder="Say something…"style="height: 600px"></aparte-chat><!-- or your own primitives inside, still laid out + center-empty --><aparte-chat center-empty><aparte-chat-viewport></aparte-chat-viewport><aparte-composer>…</aparte-composer></aparte-chat>Being a component, it owns behaviour a wrapper
<div>can’t: with the opt-incenter-emptyattribute it watches its own viewport and keeps the composer centered as a welcome state until the first message, then slides to the normal layout — no external JavaScript. Presentational only (no transport wiring);placeholder/disabledforward to the composer, and.viewport/.composergetters expose the composed elements.@aparte/core -
a2ed74b: Ship clean inline-SVG default icons (copy, retry, edit, send, thumbs up/down, and the rest) in
DEFAULT_ICON_FALLBACKS, so the chat looks right out of the box with no icon plugin — still zero runtime dependencies, since an inline SVG is just a string. Override any icon viasetIconProviderwith any HTML (SVG, an icon-font<i>, an emoji or an<img>— the value is treated as trusted markup).@aparte/core -
a6ed936: One canonical imperative contract for
<AparteChat>across the four wrappers.@aparte/corenow exportsAparteChatImperativeApi— the ~20-method surface every framework handle delegates toAparteChatHost. React’sAparteChatHandleand Vue/Svelte’sAparteChatInstanceare now type aliases of it, and the Angular componentimplementsit, so any per-wrapper drift (a missing or mistyped method) is a compile error instead of a silent divergence.Angular parity: adds the imperative
setConversationId(id)method (theconversationId@Inputremains the declarative path), closing the one gap where Angular’s handle differed from the other three.@aparte/core,@aparte/angular -
7157ad5: Unify every custom DOM event to one kebab-case convention and type it.
The public event surface used three conventions — kebab (
aparte-send), colon (aparte:retry,aparte:action,aparte:artifact-*, …) and separatorless (apartemessagestart/done/error/aborted). They are now all kebab-case:aparte:*→aparte-*(e.g.aparte:retry→aparte-retry,aparte:action→aparte-action,aparte:tool-decision→aparte-tool-decision).apartemessagestart|done|error|aborted→aparte-message-start|done|error|aborted.- Already-kebab events (
aparte-send,aparte-select-*,aparte-model-change, …) are unchanged.
Kebab is the only convention every framework can bind in a template — Angular parses a
:in an event name as atarget:eventselector, so colon events could never be(aparte:x)-bound there.New: an
HTMLElementEventMapaugmentation ships with@aparte/core, soelement.addEventListener('aparte-retry', e => e.detail)gives a typede.detail(no more(e as CustomEvent<…>).detailcast) for the public bubble / lifecycle / artifact / tool events.Breaking: any consumer listening on the old colon or separatorless names must rename to kebab. Pre-1.0, so shipped as minor.
@aparte/core,@aparte/plugin-model-selector,@aparte/angular,@aparte/react,@aparte/svelte,@aparte/vue -
69525ad: Zero-dependency web components for AI chat: bubble, composer, viewport, conversation list, and elicitation, with a transport seam (
DirectTransport/BackendTransport) and a customization surface (render hooks, action registry, theming via CSS custom properties). Ships ESM plus a Node/SSR-safe entry and a custom-elements manifest.@aparte/core -
d31f681: Give the base chat container layout to both host shapes core already recognises, from one rule. Core resolves the chat host via the selector
aparte-chat, [data-aparte-chat](the vanilla<aparte-chat>element and the<div data-aparte-chat>roots the framework wrappers render); the base flex-column layout (fill the parent, viewport scrolls internally, composer pinned to the bottom) now keys on that same selector inaparte.css. This fixes React, whose wrapper container previously had no base layout, and lets the Vue and Svelte wrappers drop their scoped component CSS — every wrapper gets consistent layout from the one stylesheet consumers already import, with no wrapper-specific class.@aparte/core -
e69435f: Make the
<aparte-chat>shell framework-safe: it no longer injects its default viewport + composer when the element carriesframework-managed. A framework wrapper whose component selector isaparte-chat(the Angular one) has its host upgraded by core, and its children only render afterconnectedCallback— so the existing “author-provided composition wins” check cannot see them, and the default composition was being injected underneath the wrapper’s own. Reuses the sameframework-managedsignal<aparte-chat-viewport>already takes.@aparte/core -
bfa9901: Theme every part of the chat from CSS. The message surface is now a
.aparte-message-contentregion (attachments sit above it as a sibling, the avatar is opt-in — empty by default), and every theme value flows through a CSS custom property: colour, spacing, font size / weight / line-height, control sizes, radii and border widths. No hardcoded theme literals remain — only structural geometry (100%,50%radii, the spinner stroke). New scales:--aparte-space-*,--aparte-font-size-*,--aparte-font-weight-*,--aparte-line-height-*.BREAKING: the
--aparte-bubble-*theme variables are renamed to--aparte-message-content-*.@aparte/core -
554e4e9: Remove the deprecated
<aparte-chat-input>element (AparteChatInput). It was the legacy monolithic composer — 653 lines ofinnerHTML-heavy code that auto-registered on import into the zero-dep core, was untested, and predated the modern<aparte-composer>+<aparte-chat>composition. It is no longer exported, registered, or styled; the elicitation panel and the client’s target resolution already preferred<aparte-composer>and simply drop the legacy fallback. Reclaims bundle size and removes an untested surface from core.Breaking (pre-1.0, shipped minor): consumers still on
<aparte-chat-input>should move to<aparte-chat>(or<aparte-composer>directly). TheAparteInputConfigtype stays.@aparte/core -
f8a6dd7: De-duplicate the wrappers’
AparteUiprop-applier. The four wrappers each carried a byte-identical vanilla-DOM prop applier + event list; they’re now in@aparte/coreasapplyElementProps(el, props, transformValue?)andDEFAULT_UI_EVENTS. Vue passestoRawas the transform to unwrap its reactive proxy. No public wrapper API change.@aparte/core -
d60e2c8: Type the request
_metachannel.AparteChatRequest._metais nowAparteRequestMetainstead ofRecord<string, unknown>: the five well-known keys (pipeline,prefixSegments,artifactHint,artifactRaw,artifactXml) are typed and documented, while an open index signature keeps it a channel for consumer-specific context. New exported types:AparteRequestMeta,ApartePipelinePhase,AparteArtifactHint.@aparte/core -
e8d9b32: Unify custom action registration into one zoned API.
A single
registerAction(action)now places a button viazones: ('composer' | 'bubble')[], with per-zone options (composer: { position, hidden },bubble: { roles }). Every action emits the declarativeaparte-actionevent (now carryingzone), with an optionalonClickcallback fired alongside for convenience.Breaking:
registerBubbleAction,getRegisteredBubbleActionsandunregisterBubbleActionare removed, and theAparteBubbleActiontype is merged intoAparteAction(usezones: ['bubble']+bubble.roles).getActions(zone)now requires a zone argument.@aparte/core -
1573645: One imperative API across the four wrappers:
injectTokenStreamnow takes the cross-wrapperAsyncIterable<string>contract on Angular too (the RxJSObservable<string>shape still works — it’s a union).- Angular
provideAparte()auto-connects the client on app init (autoConnect: falseto opt out); no more manualAparteAiService.connect()in components.connect()stays as the idempotent escape hatch. - The viewport accessor is
getViewport(): HTMLElement | nulleverywhere. Breaking: it replaces React’shandle.viewportproperty and Vue’s exposedviewportref. - The Vue/Svelte
AparteChatInstanceinterfaces now include the full imperative surface (scrollToBottom,focusInput,getViewport). AparteUiHandle(andAparteUiPropswhere idiomatic) exported from every barrel, not just React’s.@aparte/angular,@aparte/react,@aparte/svelte,@aparte/vue
Patch Changes
-
6ab5682: Round-3 audit follow-ups (bounded fixes):
- Cross-wrapper parity is now compile-enforced on all four wrappers (was only React +
Angular): Vue’s
defineExposeusessatisfies AparteChatImperativeApi, Svelte adds a type-checked parity factory. A dropped/mistyped method is now a build error in every wrapper — and theAparteChatImperativeApiJSDoc no longer overstates the guarantee. - core:
AparteConfig.unregisterAIProvidernow_notify()s (a mounted model-selector drops the removed provider instead of showing a stale list);<aparte-select>resolves its selected label by iterating options instead of an interpolated attribute selector (a model id containing"/]no longer throwsSyntaxError). - docs/JSDoc hygiene: removed three shipped references to non-existent
@aparte/plugin-{skeleton,icons}-defaultpackages; fixed theuseAparteChat@examplethat re-appended the user message (double-append); the three AI-provider READMEs now call@aparte/corea required peer dependency (it’s a runtime import), not an “optional peer”.@aparte/core,@aparte/react,@aparte/svelte,@aparte/vue
- Cross-wrapper parity is now compile-enforced on all four wrappers (was only React +
Angular): Vue’s
-
4065fd6: Bound the binary-artifact preview cache.
_binaryArtifactCacheheld full file buffers (pdf/xlsx/docx) keyed by segment id and was never evicted, so a long session generating many binary artifacts grew memory for the page’s lifetime. It’s now capped (LRU-ish: cap 24, oldest evicted on insert, re-insert refreshes recency).@aparte/core -
307039b: Fix a small memory leak in the segment renderers: two internal per-segment throttle maps (syntax-highlight and artifact-dispatch debouncing) grew one entry per streamed segment for the page’s lifetime. They’re now bounded and evict oldest like the neighbouring binary-artifact cache, so long-running sessions no longer accumulate them.
@aparte/core -
333d301: Tighten the client’s typing: the four near-identical local target interfaces (
AparteChatElement/RetryTarget/EditTarget/CompactTarget) are consolidated into the one module-levelAparteChatTargetElement, which removes ~two dozen gratuitous(target as any).methodcasts; the threecatch (err: any)becomecatch (err: unknown)with narrowing; and(segment as any).contentreads become a typed{ content?: string }cast. No behaviour change — pure typing rigor (theas anys were papering over methods the element already declares). Drops the repo’sno-explicit-anywarning count from ~63 to ~39.@aparte/core -
14f1f1d: Collapse the triplicated send / retry / edit tail into one
_streamTurnhelper._handleSend,_handleRetryand_handleEditeach re-implemented the same provider → tools → request-interceptor →toolChoice:'none'strip → reset-abort →aparte-message-start→_streamLoop→aparte-message-done/ lifecycle-error sequence. They now share one private method, so that flow can’t drift between the three entry points. As part of it,_handleSenduses the shared_resolveAuthhelper and resets the abort flag before streaming — the two divergences the audit flagged (a documented past drift). No behavior change on the happy path (verified: 867 unit incl. the retry/edit suites + parity, and 27/27 browser E2E).@aparte/core -
18d2065: Enforce lint at zero warnings (
eslint . --max-warnings 0) and clear the 37no-explicit-anybacklog — each replaced with a precise type or, where DOM / custom-element interop genuinely requires it, a structuralunknowncast (no blanketanydisables). A few public types are tightened fromanyto a precise type orunknown(e.g.AparteCustomSegment.data,AparteErrorcontext) — a type-safety improvement with no runtime change.@aparte/core -
6d6123e: Fix an XSS sink: the chat bubble’s public
nameattribute was interpolated raw intoinnerHTMLon initial render, while every sibling field (attachment names, etc.) was escaped. An app that binds an untrusted author/persona name intonamewould ship a script injection. Escaped it, consistent with the other fields, + a regression test.@aparte/core -
97bd6c5: Escape three more consumer/stream-supplied fields that reached innerHTML unescaped: the composer action
labeland inputplaceholder(attribute positions) and amessage-idCSS attribute-selector in the viewport (nowcssEscaped like its siblings). Harden the bubble / conversation-list / attachment escape helpers to also escape'. Add a best-effort.catchto the fire-and-forget syntax-highlight and clipboard promises so a rejecting highlighter or clipboard write degrades silently instead of an unhandled rejection.@aparte/core -
8417976: Harden the internal
[data-segment-id]/[message-id]attribute-selector lookups in the bubble and viewport against a hostile, stream-supplied id: interpolated ids are now escaped for the quoted-attribute context (via a smallcssEscapehelper that needs noCSSglobal, so it also works in SSR/test runtimes). An id containing"(e.g. a provider-supplied tool-call id) can no longer throw aSyntaxErrorthat drops a render update, nor form a selector list that mis-targets another element. Ids are random UUIDs by default, so this is defense-in-depth.@aparte/core -
1f6c43e: Escape the
thinkingsegment’slabelbefore it reachesinnerHTML(the adjacentcontentwas already escaped). Built-in callers always pass a hardcoded label, but a host rendering a model-derived label into a thinking segment would otherwise have a stored-XSS sink — closed defensively, consistent with the other renderer escapes.@aparte/core -
2efef6f: Extract
_streamLoop’s ~190-linetool_usecase into a_handleToolUseEventhelper (built-increate_artifact, per-tool renderer, the human-in-the-loop approval gate, and the handler run with its timeout/abort). The loop now delegates and reads the continue/stop signal back. Behaviour-preserving — proven by the engine parity golden-master that drives the real_streamLoop, plus the client tool/HITL suites (869 tests, 27/27 e2e).@aparte/core -
0aefd9b: Robustness fixes surfaced by the code audit:
- core
AparteConfig—_notifyisolates each subscriber in try/catch (one throwing listener no longer aborts the loop and starves the others);setLocale/extendLocale/setAvatarProvidernow notify subscribers like every other live setter, so a runtime locale/avatar swap propagates to already-mounted components;refreshProviderModelsis typedPromise<AparteAIModel[]>instead ofPromise<any[]>. - engine — a tool handler is no longer invoked when the run’s
AbortSignalwas already aborted before the call (a pastabortevent never re-fires on the fresh listener). - provider-openai-compat — malformed tool-call arguments JSON at
finish_reason: 'tool_calls'and unparseable SSE data lines now log a breadcrumb instead of being dropped silently.@aparte/core,@aparte/engine,@aparte/provider-openai-compat
- core
-
0aefd9b: Escape untrusted model output before it reaches
innerHTML(two DOM-XSS paths):- core — the code-segment
language(the```langfence tag, LLM-authored and prompt-injectable) is now HTML-escaped in both the label text and theclass="language-…"attribute; the file-tree nodestatustoo. - core primitives —
<aparte-select>and<aparte-optgroup>build their labels viatextContent, notinnerHTML, matching their own update paths. - plugin-model-selector — remote model names/ids and provider labels are escaped before the option list is (re)built.
Reachable from a hostile/aggregating
/modelsendpoint or a prompt-injected code fence.@aparte/core,@aparte/plugin-model-selector - core — the code-segment
-
9568c6b: Escape
data-segment-idin every segment renderer. A segment id can embed an untrusted tool-call id (tool-${toolCallId}, taken verbatim from the endpoint’s SSEtool_calls[].id), so the tool-call renderer — and, defense-in-depth, all other renderers plus the ask-question receipt — now HTML-escape it before it reachesinnerHTML. Closes a DOM-XSS reachable from a hostile OpenAI-compatible endpoint (the same class as the code-fencelanguagefix, in a sibling sink). Regression test added.@aparte/core,@aparte/plugin-ask-user -
7e5cfb7: Teardown + sanitizer hardening from the audit:
- core
AparteChatHost.streamTokensnow races eachnext()against the abort signal and callsiterator.return()on abort. An idle token source (notably the Angular Observable→AsyncIterable adapter parked on a pendingnext()) previously left the loop and the underlying subscription alive afterstopTokenStream(); it now unwinds and cleans up promptly. Fixes the zombie-subscription leak for every wrapper. Regression test added. @aparte/angularAparteChatComponentnow unsubscribes itsbubbleRefs.changessubscription inngOnDestroy(previously leaked one live subscription per mount across SPA route churn).- core sanitizer drops the legacy
nameattribute from<a>— obsolete and a DOM-clobbering vector (id/stylekept: they carry legitimate aria/anchor/highlight uses; a property-levelstyleallowlist is a separate pass).@aparte/core,@aparte/angular
- core
-
75af64a: Fix two browser-only defects surfaced by the new cross-framework browser E2E suite (both passed the jsdom unit tests):
- Standalone send, retry and edit now resolve the viewport. In the
documented flat layout (
<aparte-chat>wrapping<aparte-chat-viewport>), the client matched the shell first and — finding noappendMessageon it — silently dropped the reply (send) or no-op’d (retry/edit). A shared resolver now scans candidates for the one that can actually render, following the shell’s delegation to its viewport, so a bare-shell chat streams, regenerates and edits out of the box. - The model-gate style applies to every host. The
data-model-gatedopacity rule had been mis-scoped (a comment split the selector list), leaving the vanilla composer permanently dimmed and greying only a[data-aparte-chat]direct child when gated. It is now an unscopedaparte-composer[data-model-gated]rule that dims any gated composer, in every wrapper and the vanilla shell.@aparte/core
- Standalone send, retry and edit now resolve the viewport. In the
documented flat layout (
-
fa5a3f8: Message editing now reuses the composer’s contenteditable input instead of a bespoke
<textarea>, so editing a message is iso with composing one:- Same input primitive (
<aparte-composer-input>): autosize, IME handling, paste, placeholder and styling are shared. The edit box is styled like the composer shell. Entersaves,Shift+Enterinserts a newline (wasCtrl/Cmd+Enter);Escstill cancels.- The save/cancel icons route through the icon provider (
getIcon('check')/getIcon('close')), sosetIconProvideroverrides them too; their colours stay themable via--aparte-success/--aparte-error.
<aparte-composer-input>is now usable standalone: with no<aparte-composer>parent it emits a bubblingaparte-composer-submitevent on submit instead of no-op-ing, and gains afocusEnd()method (focus with the caret at the end of the content). Its contenteditable also handles newlines robustly now —Shift+Enterinserts a single deletable<br>(no<div>wrappers), an empty field can’t start with a blank line, andgetValue()preserves newlines (<br>→\n).The
aparte-editevent contract is unchanged.Also fixes
<aparte-chat center-empty>: the empty/welcome state centers again. The viewport’s standaloneheight: 100%(for the scroll chain) was defeatingflex-grow: 0, so the composer couldn’t center; it’s released only while empty.@aparte/core - Same input primitive (
-
8a3890b: Isolate streaming state between multiple chats on one page. Lifecycle events (
aparte-message-start/done/error/aborted) andaparte-abortnow carry the target host’stargetId, and a composer only reacts to its own host’s turn. Before this, streaming in one chat flipped every composer to the “Stop” state, adonein one reset the others (hiding an active elicitation panel), and cancelling one aborted every scoped client. Id-less single-instance pages still broadcast unchanged.@aparte/core -
49f4d70: Robustness hardening: bound the file-generation handler map so a generation that never terminates (e.g. the conversation is cleared mid-flight) can no longer leak its window listeners for the page’s lifetime; add a compile-time exhaustiveness guard on the stream-event switch so a new event variant fails the typecheck instead of being silently ignored; and mark every intentional fire-and-forget promise in the streaming / render paths explicitly (type-aware lint now guards against unhandled rejections).
@aparte/core -
fcff831: Re-export the
AparteSystemPromptVarsProvidertype from the package root (both the browser and Node entries) so consumers can type the argument of the publicAparteConfig.setSystemPromptVarsProvider()without reaching into a deep import.@aparte/core -
455fc81: Branch + shell fixes:
- Message tree:
MessageRepository._relinkno longer corrupts the old parent’s active branch when the moved node was that parent’s active child (it walkedfindHeadinto the subtree being moved and left a dangling pointer). Only reached on re-parenting the active child; retry/edit flows were unaffected. <aparte-chat>shell scroll: the shell now setsheight: 100%so the inner scroll container has a definite height to resolve against. Inside a flex column with no definite ancestor height the container grew with content and nothing scrolled (messages/action-bars spilled below the view). Give the shell — or a parent — a definite height and it scrolls internally; the styled scrollbar, wheel, auto-scroll and scroll button stay wired to the same inner container. The scroll-to-bottom button is also re-derived from real geometry after a path re-render (a branch swap rebuilds the DOM with noscrollevent, so it could otherwise stay stale).@aparte/core
- Message tree:
-
6a50004: Harden the default sanitizer’s residual defense-in-depth gaps:
- Inline
styleis now a property allowlist (colours, weights, decoration — what highlighters emit) instead of a scheme blocklist. Layout/positioning properties (position/z-index/inset/width/…) are dropped, so hostile markup can no longer build a full-viewport click-jacking overlay, andurl()beacons are rejected on any property. Safe declarations survive even when a dangerous one sits beside them (previously the whole attribute was dropped all-or-nothing). id/nameare no longer allowlisted — they enable DOM clobbering and LLM-authored markup has no legitimate need for author-controlled ids.- The js-artifact preview’s
</script>escaper now matches</scriptfollowed by any spec terminator (whitespace///>), not only the exact</script>(still inside the sandboxed,allow-scripts-without-allow-same-originiframe).@aparte/core
- Inline
-
9ce7978: Fix a server-side-rendering crash on the framework wrappers. The Node/SSR entry (resolved via the
nodeexport condition) was missingapplyElementPropsandDEFAULT_UI_EVENTS— two DOM-free interop helpers that every wrapper’sAparteUiimports as values. Because each wrapper barrel re-exportsAparteUi, importing anything (even justAparteChat) from@aparte/react/@aparte/vue/@aparte/svelte/@aparte/angularunder SSR (Next.js, Nuxt, SvelteKit, Angular Universal) crashed the whole barrel withdoes not provide an export named 'applyElementProps'.The Node/SSR entry now mirrors the browser barrel’s full non-DOM surface — also exposing
DirectTransport,BackendTransport,isFormatAdapter,parseAparteEventStream, and the render-hook / transport / tool-resolver types that were only on the browser entry — and a new parity test enumerates that surface so the two barrels can never silently drift again.@aparte/core -
e96920a: Type
aparte-composer-changein theHTMLElementEventMapaugmentation, soel.addEventListener('aparte-composer-change', e => e.detail)is typed like the other public events (it’s inDEFAULT_UI_EVENTS, so the wrappers already forward it). Closes the gap where a forwarded, typed event was missing from the event map.@aparte/core -
71c9167: Packaging fixes surfaced by wiring
publint+are-the-types-wronginto CI:@aparte/engine: its emitted.d.tsre-exported submodules without.jsextensions, sonode16/nodenextconsumers got unresolved types (bundlers hid it). Added the extensions — the types now resolve under every module mode.@aparte/plugin-ask-question: declared"sideEffects": true. Importing the package registers<aparte-ask-question>as an import-time side effect, which a tree-shaking bundler could otherwise legally drop.@aparte/engine,@aparte/plugin-ask-user
-
f2d75b0: Fix four teardown/cancellation bugs: the model selector could permanently lock itself out of re-rendering if its render threw (now
try/finally); the Angular Observable to async-iterator adapter could hang forever if torn down mid-await(itsreturn()now settles the pending read); and the OpenAI-compat and AI-SDK providers nowcancel()the underlying stream on consumer cancel instead of draining the vendor body to the end (AI-SDK also can no longer process a second terminal event).@aparte/provider-ai-sdk,@aparte/provider-openai-compat,@aparte/plugin-model-selector,@aparte/angular -
aff7e98: Cancelling a local-model stream now actually STOPS generation: the worker runs each generate under an
InterruptableStoppingCriteriaand the stream’scancel()interrupts it, instead of letting the model run tomax_new_tokensoff-thread after the consumer aborted.@aparte/provider-transformers -
056dafd: Raise the monorepo TypeScript strictness floor:
noUncheckedIndexedAccessandnoUnusedParametersmove intotsconfig.base.json, so every package inherits them (core / engine / providers already opted in locally; plugins / wrappers / locales now do too). The new floor surfaced — and this fixes — real unchecked index accesses inmodel-selector(auto-select + single-provider option list) andask-question(single-question path): each now guards the array element instead of assuming it exists.@aparte/plugin-ask-user,@aparte/plugin-model-selector -
0aefd9b: README quick-start no longer re-adds the user message in the
messageSent/onSendhandler: the chat appends it automatically on send, so the previous example rendered every sent message twice (Angular: discarded the optimistic message via a[messages]round-trip). Now aligned with the wrapper JSDoc and the tested playgrounds.@aparte/angular,@aparte/react,@aparte/svelte,@aparte/vue -
f8a6dd7: De-duplicate the wrappers’
AparteUiprop-applier. The four wrappers each carried a byte-identical vanilla-DOM prop applier + event list; they’re now in@aparte/coreasapplyElementProps(el, props, transformValue?)andDEFAULT_UI_EVENTS. Vue passestoRawas the transform to unwrap its reactive proxy. No public wrapper API change.@aparte/angular,@aparte/react,@aparte/svelte,@aparte/vue -
a6ed936: One canonical imperative contract for
<AparteChat>across the four wrappers.@aparte/corenow exportsAparteChatImperativeApi— the ~20-method surface every framework handle delegates toAparteChatHost. React’sAparteChatHandleand Vue/Svelte’sAparteChatInstanceare now type aliases of it, and the Angular componentimplementsit, so any per-wrapper drift (a missing or mistyped method) is a compile error instead of a silent divergence.Angular parity: adds the imperative
setConversationId(id)method (theconversationId@Inputremains the declarative path), closing the one gap where Angular’s handle differed from the other three.@aparte/react,@aparte/svelte,@aparte/vue
Version-only bumps (no changes of their own): @aparte/plugin-marked, @aparte/plugin-shiki, @aparte/plugin-streaming-markdown, @aparte/locale-fr.