Changelog
Version 0.12.0
Every @aparte/* package ships at this version (they are released in lockstep).
Minor Changes
-
2ac6080: Four critical defects from the 0.11.0 cold audit. Each one is a place where two things had to agree and nothing checked that they did.
A refusal’s words now reach the model on the engine path too.
runStreamAgenthardcoded “Tool execution was rejected by the user.” and never readdecision.instruction— which its own resolver type declares and core’s inline loop uses. So on the recommended path (ratified decision #6), a user who refused a tool and typed “use the staging bucket instead” had those words dropped before the model saw them. Handing the model a turn after a refusal exists so that it reads the refusal; there was nothing to read.The parity suite could not see it: its resolver returned
{ approved }and never an instruction, so both loops agreed on a case neither ran. It supplies one now, and asserts the sentence survives rather than only that the two sides match — agreement alone passes if both drop it.Two chats on one page no longer fight over the presenter.
<aparte-elicitation>entered<aparte-chat>’s default composition in 0.11.0, so two plain chats each registered on the same config — which held ONE slot. The second clobbered the first (chat A’s approval opened under chat B, and answering it there decided A’s tool call), and when B unmounted it cleared the slot, leaving A mounted with a working presenter that never re-registered: every later approval and everyask_userin A rejectedno-presenterfor the life of the page, silently, because that warning fires once per config.It is a stack now, each entry carrying the element that registered it, and a request naming a
targetis routed to the presenter in the same chat.AparteElicitationRequest.targetwas already documented as “used to resolve WHICH instance presents”; the single slot is what made that impossible.New:
AparteConfig.removeElicitationPresenter(fn)withdraws one presenter by name.setElicitationPresenter(presenter, owner?)takes an optional owner element.setElicitationPresenter(null)still means “turn it off” and clears all.hidePanel()no longer orphans an open request. The no-token form called the silent teardown, which nullsonEvictwithout calling it — so the documented public call closed an approval panel without telling its owner. The promise stayed pending, the approvalawaithas no timeout, and becauserequestUserInputchains each request on the previous one, no further question or approval on that config was ever presented again. The old JSDoc justified the silent branch as “whatreset()needs”;reset()calls_evictPanel(), which notifies. Both forms notify now, except a token-matched close — that is the owner closing its own panel, and it already knows.Breaking, pre-1.0:
[multiple]="false"used to turn multi-file selection ON. The bindings generator modelled every boolean attribute as a presence attribute, and core has two that are three-state — default on, off only via the literal"false":multipleon<aparte-composer-add-attachment>andsubmit-on-enteron<aparte-composer>. For those,falseREMOVED the attribute, which the element reads as true. The only value that turned it off was a string no binding could produce and no template type could express.truenow writes the empty presence value andfalsewrites"false", and the attribute type widens toboolean | 'false'so OFF is expressible in React, Vue and Svelte as well. If you passed''/undefinedto force one of these off, it never worked; passfalse(Angular) or'false'(templates).Two attributes join the typed surface, having been read lazily via
getAttributeand so invisible to the manifest:submit-on-enteron<aparte-composer>andaction-idon<aparte-composer-action>— the latter being the only way to tell two custom composer buttons apart.And
aparte-action-clicknow declares its detail type, so Angular’s(actionClick)emitsAparteActionClickEventDetailinstead ofvoidwith$eventdiscarded.@aparte/core,@aparte/engine,@aparte/angular -
2ac6080: Seven behavioural defects from the same audit.
A tool call keeps its state word through a config change.
relabelrebuilt the row’s badge as the icon alone, so anysetLocale,setIconProvider,registerToolorreset()deleted the localized word beside it — permanently, because a settled call gets no furtherupdate(). “Done” went bare and pending’s “Running” went empty; four of five statuses regressed. It now goes throughstateBadge, whose own docblock claims “one function sorenderandupdatecannot disagree” and whichrelabelhad never been folded into.Breaking, pre-1.0: the last bare CSS class is prefixed. The unknown-segment fallback emitted
class="segment aparte-segment-unknown"— the rename had prefixed the second token and left the first, so 0.11.0’s claim that every class core emits is prefixed was false..segmentis Semantic UI’s base layout class, which is one of the two reasons that rename happened. If you styled.segment, it is.aparte-segmentnow.Three CSS rules no longer reach out of core.
[data-status="resolved"] .aparte-tool-stateand its two siblings were the only rules in the stylesheet whose leftmost compound is an unprefixed non-element selector. A host wrapping the chat in<div data-status="rejected">re-tinted every completed tool call’s word red. Now scoped to.aparte-segment-tool-call.Stop in one chat no longer tears down another chat’s open question. The receive side already resolved its own chat host when no
targetattribute is set — which is all of raw core, since the documented markup sets none. The send side read the attribute only, so the abort carriedtargetId: undefined, and a missing id means “for everyone”.A number binding with no value no longer writes
"NaN". Angular’snumberAttributereturnsNaNfor undefined, null,''and any non-numeric expression, so[scrollThreshold]="cfg.threshold"on an unset field wrotescroll-threshold="NaN"— andparseInt('NaN' || '50', 10)is NaN because'NaN'is truthy, so the transcript stopped following a streaming reply and the scroll-to-bottom button never hid.applyElementPropsremoves the attribute instead, restoring the documented default.0still writes, because 0% is a value.A CSS variable with no value is removed rather than stringified.
props={{ '--aparte-select-bg': theme.selectBg }}on an optional field set the property to the tokenundefined— worse than leaving it alone, because a property that is set makes everyvar(--x, default)skip its fallback and become invalid at computed-value time, so the declaration is dropped and the control renders unstyled. An object became[object Object]the same way.@aparte/svelte: five events are bindable again. Theon:surface derives fromHTMLElementEventMap, which deliberately omits the events carrying no detail. Harmless while that only governedaddEventListener— but declaring the tags removedSvelteHTMLElements’ catch-all index signature, soon:aparte-cancel(the stop button),on:aparte-composer-submit,on:aparte-reset-done,on:aparte-select-openandon:aparte-select-closestopped type-checking. All five are Angular@Output()s, so the wrappers were not at parity.Closed by deriving from core’s proxy list as well, which already enumerates every event an element dispatches on itself.
APARTE_DEFAULT_UI_EVENTSbecomesas constso the literals exist at the type level, and core exports the new typeAparteUiEventName.@aparte/plugin-model-selector: the framework peer ranges are fixed.react: "^19.2.7"andsvelte: "^4.2.0"were copied from the package’s own devDependency pins, excluding React 18 and Svelte 5 — both supported by the matching wrappers. An out-of-range peer that is present is an ERESOLVE conflict whether or not it is optional, so installing this plugin in a Svelte 5 app failed. Now^18.0.0 || ^19.0.0and^4.0.0 || ^5.0.0, matching the wrappers.@aparte/core,@aparte/svelte
Patch Changes
-
2ac6080: Seven behavioural defects from the same audit.
A tool call keeps its state word through a config change.
relabelrebuilt the row’s badge as the icon alone, so anysetLocale,setIconProvider,registerToolorreset()deleted the localized word beside it — permanently, because a settled call gets no furtherupdate(). “Done” went bare and pending’s “Running” went empty; four of five statuses regressed. It now goes throughstateBadge, whose own docblock claims “one function sorenderandupdatecannot disagree” and whichrelabelhad never been folded into.Breaking, pre-1.0: the last bare CSS class is prefixed. The unknown-segment fallback emitted
class="segment aparte-segment-unknown"— the rename had prefixed the second token and left the first, so 0.11.0’s claim that every class core emits is prefixed was false..segmentis Semantic UI’s base layout class, which is one of the two reasons that rename happened. If you styled.segment, it is.aparte-segmentnow.Three CSS rules no longer reach out of core.
[data-status="resolved"] .aparte-tool-stateand its two siblings were the only rules in the stylesheet whose leftmost compound is an unprefixed non-element selector. A host wrapping the chat in<div data-status="rejected">re-tinted every completed tool call’s word red. Now scoped to.aparte-segment-tool-call.Stop in one chat no longer tears down another chat’s open question. The receive side already resolved its own chat host when no
targetattribute is set — which is all of raw core, since the documented markup sets none. The send side read the attribute only, so the abort carriedtargetId: undefined, and a missing id means “for everyone”.A number binding with no value no longer writes
"NaN". Angular’snumberAttributereturnsNaNfor undefined, null,''and any non-numeric expression, so[scrollThreshold]="cfg.threshold"on an unset field wrotescroll-threshold="NaN"— andparseInt('NaN' || '50', 10)is NaN because'NaN'is truthy, so the transcript stopped following a streaming reply and the scroll-to-bottom button never hid.applyElementPropsremoves the attribute instead, restoring the documented default.0still writes, because 0% is a value.A CSS variable with no value is removed rather than stringified.
props={{ '--aparte-select-bg': theme.selectBg }}on an optional field set the property to the tokenundefined— worse than leaving it alone, because a property that is set makes everyvar(--x, default)skip its fallback and become invalid at computed-value time, so the declaration is dropped and the control renders unstyled. An object became[object Object]the same way.@aparte/svelte: five events are bindable again. Theon:surface derives fromHTMLElementEventMap, which deliberately omits the events carrying no detail. Harmless while that only governedaddEventListener— but declaring the tags removedSvelteHTMLElements’ catch-all index signature, soon:aparte-cancel(the stop button),on:aparte-composer-submit,on:aparte-reset-done,on:aparte-select-openandon:aparte-select-closestopped type-checking. All five are Angular@Output()s, so the wrappers were not at parity.Closed by deriving from core’s proxy list as well, which already enumerates every event an element dispatches on itself.
APARTE_DEFAULT_UI_EVENTSbecomesas constso the literals exist at the type level, and core exports the new typeAparteUiEventName.@aparte/plugin-model-selector: the framework peer ranges are fixed.react: "^19.2.7"andsvelte: "^4.2.0"were copied from the package’s own devDependency pins, excluding React 18 and Svelte 5 — both supported by the matching wrappers. An out-of-range peer that is present is an ERESOLVE conflict whether or not it is optional, so installing this plugin in a Svelte 5 app failed. Now^18.0.0 || ^19.0.0and^4.0.0 || ^5.0.0, matching the wrappers.@aparte/plugin-model-selector
Version-only bumps (no changes of their own): @aparte/provider-ai-sdk, @aparte/provider-openai-compat, @aparte/provider-transformers, @aparte/plugin-ask-user, @aparte/plugin-marked, @aparte/plugin-shiki, @aparte/plugin-streaming-markdown, @aparte/react, @aparte/vue, @aparte/locale-fr.