[0.6.0] - 2026-05-09
Large SDK convergence release. Breaking changes align the Python SDK with
plushie-elixir, plushie-rust, plushie-ruby, and plushie-ts on naming,
event shapes, and wire protocol.
Breaking changes
Commands
Command.donerenamed toCommand.dispatch.Command.widget_commandsrenamed toCommand.widget_batch.Command.load_font(data)now requires a leadingfamilyargument:
Command.load_font(family, data).Command.close_window(window_id)is now awindow_op(op: "close")
rather than awidget_op(op: "close_window").Command.gain_focusrenamed toCommand.focus_window.Command.request_user_attentionrenamed toCommand.request_attention.- Query commands drop the
get_prefix:get_window_size->
window_size,get_window_position->window_position,
get_mode->window_mode,get_scale_factor->scale_factor,
get_system_theme->system_theme,get_system_info->system_info. - Widget ops (
focus,scroll_to,pane_split, etc.) move from the
widget_openvelope to the unified command format. Extension commands
move fromextension_command/extension_commandstocommand/
commands. WidgetCommandErrorrenamed toCommandError; its fields are now
idandfamily(wasnode_idandop).
Effects
- Effects now take a
tagas first argument. Starting a new effect with
the same tag cancels the previous one.EffectResultcarriestag
for matching; internal request IDs are no longer exposed.
Events
ImeOpenrenamed toImeOpened.KeyPressandKeyReleasecollapse intoKeyEvent(type, key, modifiers, window_id, id, scope)with atypediscriminator.ImeOpened,ImePreedit,ImeCommit,ImeClosecollapse into
ImeEvent(type, ...).- Ten window-lifecycle types (
WindowResized,WindowMoved, etc.)
collapse intoWindowEvent(type, ...). - Fourteen canvas pointer types consolidate into
Press,Release,
Move,Scroll,DoubleClick,Resize,Enter,Exit; all carry
full pointer metadata (device type, button, modifiers, finger_id).
Removed:CanvasPress,CanvasRelease,MouseAreaPress,
MouseAreaMove,MouseAreaScroll,MouseAreaDoubleClick,
SensorResize,MouseAreaEnter,MouseAreaExit, and variants. - Mouse and touch subscription events unified into pointer events.
Removed:MouseMove,MouseEnter,MouseLeave,MouseButtonPress,
MouseButtonRelease,MouseWheel,TouchPress,TouchMove,
TouchLift,TouchLost. Subscription methods renamed:
on_mouse_move->on_pointer_move,on_mouse_button->
on_pointer_button,on_mouse_scroll->on_pointer_scroll,
on_touch->on_pointer_touch. PointerScrollrenamed toScroll; oldScroll(viewport-scroll
event) renamed toScrolled.- Effect stub ack event types renamed to
effect_stub_register_ackand
effect_stub_unregister_ack(waseffect_stub_registered/
effect_stub_unregistered).
UI builders
mouse_arearenamed topointer_area.Alignmentsplits intoAlignX(left/center/right) and
AlignY(top/center/bottom). The oldstart/end
vocabulary is gone.grid(columns=N)renamed togrid(num_columns=N).ime_purposeproperty renamed toinput_purpose.- App views must return explicit
windownode(s) at the top level.
Returning a bare widget tree is no longer accepted.
Custom widgets
ExtensionDefrenamed toNativeWidgetDef;CanvasWidgetDef
consolidated intoWidgetDef. Both old names are re-exported as
compatibility shims but will be removed in a future release.WidgetDef.render()callback renamed toWidgetDef.view().Command.extension_command()renamed toCommand.widget_command().ExtensionCommandErrorrenamed toWidgetCommandError(distinct from
the command-levelCommandErrorabove).extension_configsettings key renamed towidget_config.connection(expected_extensions=...)parameter renamed to
expected_widgets.generate_cargo_toml,generate_main_rs, andvalidate_allhelpers
inplushie.native_widgetremoved; cross-widget collision detection
moved tocargo-plushie.
Wire / connection
- Wire format auto-detection removed.
Connection.open(),
StdioConnection.__init__, andIoStreamAdapter.__init__now take an
explicitformat="json" | "msgpack"parameter. - Op-specific fields in
window_op,system_op,system_query, and
image_opmessages now nest under apayloadkey alongsideop. - Environment variable renamed:
PLUSHIE_SOURCE_PATHis now
PLUSHIE_RUST_SOURCE_PATH. The old name is no longer recognized. - Python constant renamed:
plushie.binary.BINARY_VERSIONis now
plushie.binary.PLUSHIE_RUST_VERSION. python -m plushie buildnow delegates tocargo-plushie. Install
withcargo install cargo-plushie --version <PLUSHIE_RUST_VERSION> --locked, or setPLUSHIE_RUST_SOURCE_PATHfor local development.
Added
Events
LinkClicked(id, link, window_id, scope)typed event for rich text and
markdown hyperlink clicks.RendererExitInfodataclass (type,message,details) and
RecoveryFailedevent dispatched whenhandle_renderer_exitraises.SessionErrorandSessionClosedtyped events for multiplexed mode.
EffectStubAckreplaces the raw dict previously delivered for stub ack
messages.CanvasElementKeyPressandCanvasElementKeyReleaseevents (carry
modifiersfield matching the unified key event shape).SpanandSpanHighlightfrozen dataclasses for rich text; instances
auto-encoded alongside plain dicts inrich_textspans lists.SessionError.codefield exposed on the typed dataclass.- Typed diagnostic variants:
DiagnosticMessagewraps session, level,
and a structured variant. IncludesBufferOverflowErrorand
ProtocolVersionMismatchError. TransitionComplete(tag, prop)event delivered at end of
renderer-side animations.- Pointer
EnterandExitevents now carryx,y, andcaptured.
Press,Release,Move, andScrollcarrycaptured;Scroll
also carriesunit;Releasecarrieslost.
UI builders
table_row()andcell()container builders for children-based table
rows;expand_rows(data, row_fn)converts a data sequence into
table_row/table_cellchildren.Angle(degrees)orAngle(value, "rad")type for canvas rotation and
arc operations.LineHeighttype with relative multiplier and explicit pixel forms.- Per-corner radius support in the canvas
rectshape.
Types and protocol
WireEncodableruntime-checkable protocol: any type with ato_wire()
method is automatically recognized during tree normalization. Replaces
the internal_WIRE_TYPESregistration; third-party types can satisfy
the protocol without SDK changes.ScopedIdtype and window-qualifiedwindow_id#widget_pathselector
syntax across commands, test helpers, and subscriptions.AlignXandAlignYaxis-split alignment types.
Animation
- Renderer-side animation system:
Transition(timed easing),
Spring(physics-based, withgentle/snappy/bouncy/
stiff/molassespresets),Sequence(chained animations). Set
these as prop values; the renderer drives them frame-by-frame. Tween.repeat(n),Tween.auto_reverse, andTween.looping()for
SDK-side interpolators.- 31 named easing curves and
cubic_bezierwith solver.
Subscriptions
- Window-scoped subscriptions:
Subscription.on_key_press("tag", window="editor")andSubscription.for_window("editor", [...])
grouped scoping. Renderer filters events by window scope. - All key, pointer, touch, IME, and modifier-changed events now carry a
window_idfield.
Runtime
await_async(tag, timeout)method onRuntimefor blocking until an
async task completes.interact()method on the productionRuntimefor scripted
interaction outside tests.- Heartbeat watchdog: detects an unresponsive renderer and triggers the
recovery path. Command.dispatchchain depth capped;DispatchLoopExceeded
diagnostic emitted when exceeded.- Frozen-UI dev overlay: after five consecutive
view()failures a red
status bar is injected; clears on success or reconnect. - Max tree depth validation: warn at depth 200, raise at depth 256
(prevents infinite recursion in composite widgets).
Custom widgets
WidgetDef.handle_eventis now optional; default returns
EventAction.ignored().- Canvas-internal events are auto-consumed after widget dispatch unless
explicitly captured by the handler. WidgetDefsupports composition of built-in widgets in addition to
canvas rendering.handle_eventpresence signals interactivity;
subscribeenables scoped subscriptions.- Typed event declarations:
event_specs: ClassVar[dict[str, EventSpec]]
with emit-time validation (undeclared events, missing required fields,
wrong types). - Typed widget event routing: built-in families (
click,select,
toggle,input) produce typed dataclasses; custom event names still
produceWidgetEvent(kind, data).
Accessibility
resolved_a11y()test helper andAppFixture.resolved_a11ypipeline
for inspecting inferred a11y annotations.Command.focus_next_within(scope),focus_previous_within(scope).Command.announce(message, politeness="polite" | "assertive").- Scope-rewritten cross-widget refs; dangling refs emit warnings.
- Per-widget a11y defaults (role, has_popup) applied during normalize.
active_descendantandradio_groupfields onA11ydataclass.- Implicit
radio_grouppositions inferred from thegroupprop.
Connection
SocketAdapterfor TCP (host:portor:port) and Unix domain
socket (/path) connections viaConnection.from_iostream().preflightrebuildsplushie-rendererfrom source when
PLUSHIE_RUST_SOURCE_PATHis set, then exportsPLUSHIE_BINARY_PATH
so the test run uses the fresh binary.resolve_cargo_plushie()helper inplushie.cargo_plushie.plushie.renderer_buildmodule orchestrating the cargo-plushie build
flow.
Testing
assert_tree_hashandassert_screenshotgolden-file assertion
helpers.AppFixture.assert_a11y,assert_role,assert_no_diagnostics,
resolved_a11y.- Diagnostic events captured automatically in
AppFixture. WidgetFixturefor isolated canvas widget testing.case-insensitivekey resolution in test helpers.- ID selectors validated before interact; unresolved
#idraises
ValueErrorlisting available IDs. interact()raisesRuntimeErroron concurrent calls or renderer exit
during interaction,TimeoutErroron renderer timeout.- Unified selector string syntax: ID, pseudo-class, and attribute
selectors ([text=Save],[role=button],[label=Name]). - Per-field required/optional control in
EventSpec.
Documentation
- Full reference documentation set (events, commands, subscriptions,
effects, built-in widgets, canvas, animation, windows and layout,
themes and styling, testing, composition patterns, custom widgets,
Python typing, app lifecycle). - 16 guide chapters covering the full SDK surface.
Fixed
window_openedevents parsexandyfrom top-level fields;
previous decoder read from a nestedpositionkey the renderer no
longer emits.default_fontsent as canonical{"family": ...}object; bare
strings rewritten before the wire.Command.load_fontsends as a typed top-level message with{family, data}payload instead of awidget_openvelope.Command.list_images_queryandCommand.clear_imagesemit the typed
image_openvelope (op: "list"/op: "clear") instead of the
widget_opform the renderer no longer accepts.- Padding encoding: tuples were silently dropped; now properly encoded
throughencode_padding. - Gradient wire format redesigned from angle-based to coordinate-based,
matching the renderer. - Font weight, style, and stretch encoded as snake_case on the wire.
- Text command wire format aligned with renderer expectations.
- Widget ID validation: empty strings, non-printable ASCII, and IDs
exceeding the max length now raise at build time. - Pixel buffer size validated in
create_image_rgbaand
update_image_rgba. flush_pending_effectspreserves effects registered during flush;
subscription spec filtering corrected.- Strict
hellodecoder validates all required fields. - Widget state changes now trigger a re-render; registry reverts on view
error during state re-render. - Theme prop on
windowwidget properly tracked and diffed. - Renderer reconnect resets the consecutive view-error counter.
- Renderer binary version mismatch logged as a warning during handshake.
- Native widget type names that shadow built-in widgets are rejected.
- Pending
interactfails cleanly when the renderer exits or restarts. - Concurrent stub ack /
await_asyncfor the same key raises
RuntimeErrorinstead of silently dropping one caller. - Protocol parity gaps closed:
captured,unit,lostfields added;
AnimationFrame.timestampwidened tofloat;Subscription.batch()
validated. - Non-finite floats normalized at framing layer rather than silently
producing invalid JSON. - JSON feed loop guards partial tail to prevent buffer overflow from
unterminated lines. - Canvas key events carry
modifiersfield. - A11y cross-widget refs rewritten through scope-prefix logic; missing
accessible names detected. - Timer subscription and runtime control operations synchronized.
Changed
- Pinned renderer advanced to plushie-rust 0.7.1. Run
python -m plushie download --forceto pull the updated binary. SessionPooldefault session count lowered from 32 to 8 (aligns with
all other SDKs).HelloInfonow includesnative_widgetsandwidgetsfields from the
renderer hello.- Memo body meta key renamed to
__memo_fun__for cross-SDK parity. - Memo subtrees now cached by dependency key; widget view expansion and
ID-keyed list diffs enabled in the tree differ. - Coalescing keyed by
(window_id, target_id)for correct per-widget
deduplication across windows. WidgetDefconsolidatesCanvasWidgetDef;canvas_widget.pyis now
a compatibility re-export shim.NativeWidgetDefreplacesExtensionDef;native_widget.pyshim
preserved for one release cycle.plushie.treesplit intonormalize,diff,search, anda11y
submodules; public imports unchanged.