All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog.
[0.7.2] - 2026-05-09
Fixed
mix plushie.guicrashed on every invocation withBadBooleanErrorbecauseresolve_binary!/1used the strictand/notoperators with keyword list lookups that returnnilwhen a flag is absent. Replaced with&&/!. This mademix plushie.gui(and any task that callsresolve_binary!/1) completely unusable in 0.7.1 without--releaseor--buildflags.
[0.7.1] - 2026-05-09
Fixed
- Screenshot requests now include the required
idfield, matching the renderer 0.7.1 wire protocol. Without it the renderer logged a decode error andBridge.screenshottimed out after 30 seconds. - Screenshot assertions now save a PNG alongside the
.sha256hash. The PNG is written whenrgba_datais present (headless and windowed backends). Passpng: falsetoassert_screenshot/2orassert_screenshot_match/3to write the hash only. The.sha256file is the actual CI assertion; the PNG is a human convenience for visual inspection. SocketAdapterJSON buffer accumulation is now O(n) instead of O(n^2). Incoming TCP chunks are accumulated in an iolist and only materialized to binary when a newline arrives. The overflow check now runs against the tracked size before any allocation, preventing timeouts on slow networks or tight socket buffers.
[0.7.0] - 2026-05-09
Breaking
BINARY_VERSIONrenamed toPLUSHIE_RUST_VERSION. The version file now makes its purpose explicit: it pins the plushie-rust release this SDK targets, independent of the SDK's own semver.Plushie.Binary.plushie_rust_version/0replaces the previousbinary_version/0.PLUSHIE_SOURCE_PATHrenamed toPLUSHIE_RUST_SOURCE_PATH. The application config key:source_pathis unchanged.Command.async/1,2renamed toCommand.task/1,2. Converges with Python, TypeScript, Gleam, and Ruby SDKs. The internal:asynccommand type atom becomes:task.AsyncEventis unchanged.grid.columnsrenamed togrid.num_columns. Matches the renderer field name and avoids the collision withtable.columns(column spec lists).- Window query functions drop the
get_prefix.Command.get_window_size->Command.window_size,Command.get_window_position->Command.window_position,Command.get_mode->Command.window_mode,Command.get_scale_factor->Command.scale_factor,Command.get_system_theme->Command.system_theme,Command.get_system_info->Command.system_info. WidgetCommandErrorrenamed toCommandError. Fields renamed:node_id->id,op->family. Update pattern matches and any@impl Plushie.Appclauses that matched the old struct.SystemEvent.datarenamed toSystemEvent.value. Consistent withWidgetEvent.valueandStreamEvent.value.- Angles throughout the canvas API are now in degrees. Previously
Plushie.Canvas.Anglenormalized to radians; it now normalizes to degrees. Thearc,ellipse, androtatebuilders and theimage.rotationfield all send degrees to the renderer. UsePlushie.Canvas.Angle.to_radians/1when a radian value is needed. - Renderer subscriptions drop the tag parameter. Key, pointer,
window, and touch subscriptions now take keyword opts only:
Subscription.on_key_press(window: "main", max_rate: 60). The oldtag:opt was management-only and never appeared in events. Timer subscriptions retain their positional tag. - Command renames.
Command.request_user_attention/1->Command.request_attention/1,Command.done/2->Command.dispatch/2,Command.widget_commands/1->Command.widget_batch/1. ime_purposerenamed toinput_purposeonTextInputandTextEditor, matching the renderer field rename.data:renamed tofields:in inline widget event declarations. The widget DSLevent :name, data: [field: type]form now usesfields:. Do-block event declarations are unaffected.LinearGradientrenamed toPlushie.Canvas.Gradient. The module wasPlushie.Canvas.Shape.LinearGradient. ThePlushie.Type.Gradientmodule (widget background gradients) is unchanged.Command.scroll_tosignature changed.(id, offset)->(id, x, y)with separate typed float parameters.snap_to/2andscroll_by/2lose their default-argument overloads.Command.select_rangefield names changed tostart_posandend_pos(the old names shadow Elixir reserved words in pattern matching contexts).- Table rows are now children, not a prop. The
rows:keyword prop is removed. Usetable_row "id" do cell "col", content endDSL macros in a do-block. Theseparatorfield changed from boolean to float (thickness in pixels). Table gainsselected,striped, andheightfields and arow_clickevent. gain_focus/1renamed tofocus_window/1. The deprecated alias will be removed in a future release.- Renderer binary version bumped to 0.7.0. Run
mix plushie.download --forceafter updating.
Added
memo/2DSL primitive for subtree memoization. Wraps a widget view with acache_key; the subtree is not re-rendered when the key is unchanged. Combine withcache_keystate declaration for widget-level view caching.- Typed
RichText.Spanbuilder.Plushie.RichText.Spanprovides a typed struct instead of raw maps. :link_clickevent type on rich_text. Clicked links deliver aWidgetEventwithtype: :link_click.on_focus/on_blurprops on text_input and text_editor;:focusedand:blurredevent types delivered on focus change.pointer_captured,pointer_lost,coordsfields on pointer events where the renderer provides them.- Typed effect result variants.
EffectEvent.resultis a tagged tuple per kind (e.g.,{:file_open, {:ok, path}}). - Typed diagnostic exceptions.
Plushie.Runtime.BufferOverflowErrorandPlushie.Runtime.VersionMismatchErrorraised as structured exceptions. rule.thicknessprop - direction-agnostic line thickness; avoids choosing betweenwidthandheightbased on orientation.- Full
input_purposeset on text_input and text_editor. Supports all nine renderer purposes:normal,secure,terminal,number,decimal,phone,email,url,search. - A11y improvements.
resolved_a11y/1helper for scoped ref resolution; builder defaults populateroleautomatically; canvas radio groups auto-inferposition_in_set/size_of_set; focus commands scoped to canvas elements;announcepoliteness ona11yprops. Plushie.Canvas.Angletype for explicit angle values withto_degrees/1andto_radians/1converters.- Bridge heartbeat watchdog. Detects unresponsive renderers and triggers restart rather than hanging indefinitely.
use Plushie.Commandstandalone DSL for defining command modules with the samecommand/fieldmacro infrastructure as native widgets.:pagescroll unit. Scroll events now carryunit: :pagefor page-delta events from the renderer.- Telemetry counters on normalize, diff, command execution, and subscription lifecycle.
Fixed
default_fontalways emitted as a family object.- Image
list/clearsent through the typedimage_opchannel. op_query_responsedecoded from the canonical"data"field.window_openeddecoded from the top-levelx/yfields.prop_validationevents from debug renderer builds silently dropped rather than logged as protocol errors.
Changed
mix plushie.builddelegates tocargo-plushie. Workspace generation, widget collision checks, main.rs emission, and Cargo.lock shepherding moved to thecargo-plushieCargo subcommand in the plushie-rust workspace. The Mix task writes a "renderer spec" Cargo.toml listing native widget crates and shells out. Widget crates must declare[package.metadata.plushie.widget]in their own Cargo.toml for discovery. Install withcargo install cargo-plushie --version 0.7.0 --locked. Seedocs/versioning.mdfor the version correspondence table.Plushie.Dev.DevServernow triggers incremental Rust builds throughcargo plushie buildinstead of drivingcargo builddirectly.
Removed
native/plushie/Cargo.lockstash. cargo-plushie preserves its own lockfile across runs; no SDK-level stash is needed.
[0.6.0] - 2026-04-02
Breaking
Unified pointer events. 14 device/widget-specific event types replaced with 8 generic types:
:press,:release,:move,:scroll,:enter,:exit,:double_click,:resize. All carrypointertype (:mouse/:touch/:pen),modifiersstate, and optionalfingerID for touch. Removed types:canvas_press,canvas_release,canvas_move,canvas_scroll,mouse_right_press,mouse_right_release,mouse_middle_press,mouse_middle_release,mouse_move,mouse_scroll,mouse_enter,mouse_exit,mouse_double_click,sensor_resize.Canvas element events unified.
canvas_element_enter/leave/focused/blurred/drag/drag_end/key_press/key_releaseandcanvas_element_clickreplaced with standard types using scoped IDs. Canvas elements look like regular widgets from the SDK's perspective.MouseEventandTouchEventremoved. Subscription pointer events are now delivered asWidgetEventstructs withidset to the window ID andscopeof[].mouse_arearenamed topointer_area. The widget, DSL macro, and wire type all use the new name.Window ID in scope chain. Window IDs are appended to the end of the scope list. Pattern matching with
| _at the end of scope naturally ignores the window for single-window apps.:scrollvs:scrolled.:scrollis pointer wheel input (with coordinates and deltas).:scrolledis scrollable container viewport state change. Previously both used:scroll.:start/:endalignment aliases removed. Use:left/:right/:top/:bottom/:center.Subscription functions renamed.
on_mouse_move->on_pointer_move,on_mouse_button->on_pointer_button,on_mouse_scroll->on_pointer_scroll,on_touch->on_pointer_touch.Canvas auto-consumption removed. Canvas background pointer events now reach
update/2when opted in viaon_press/on_move/etc.Renderer binary version bumped to 0.6.0.
Added
Device awareness on pointer events.
Plushie.Type.Pointermodule withpointer_type(:mouse/:touch/:pen) andbuttontypes. Every pointer event includes pointer type, modifier state, and finger ID for touch.Window-qualified selector syntax.
"main#form/save"targets a widget in a specific window. Works in test selectors and commands (Command.focus,Command.scroll_to, etc.).Widget state re-render. When a widget's
handle_event/2returns{:update_state, new_state}, the view is immediately re-rendered. Previously required an unrelated event to trigger the re-render.Mock canvas element click.
click("#canvas-id/element-id")works in mock mode tests by detecting scoped IDs and verifying element existence.Mock sequential click fix. Sequential clicks on different widgets now work reliably in mock mode (synthetic event path replaces fragile focus+space approach).
Coalescing for pointer events.
:move(Replace),:scroll(Accumulate deltas),:scrolled(Replace),:resize(Replace).
Fixed
- Widget re-render on state change with window sync and error revert.
- Pre-existing decoder bugs:
transition_completemissing from specs and missing scope extraction,sortdata shape mismatch,pane_focus_cyclespec/decoder mismatch. plushie.buildpatches vendored iced subcrates when local source checkout exists. Handles file read errors in Cargo.toml parsing.
Changed
- Documentation overhaul. README rewritten,
docs/README.mdindex added for hexdocs,CONTRIBUTING.mdcreated. All em-dashes replaced with single dashes. Comprehensive docs for pointer events, device awareness, canvas touch, modifier patterns, window scope, and selector syntax.
[0.5.0] - 2026-03-23
Breaking
- Renderer binary renamed from
plushietoplushie-renderer. The binary resolution chain, download task, and build task all use the new name. Thebin/plushie-renderersymlink replaces the oldbin/plushie. - WASM files renamed from
plushie_wasm.*toplushie_renderer_wasm.*. Update any HTML script tags that reference the old names.
Added
--bin-file PATHand--wasm-dir PATHoptions onmix plushie.build, matching the existing options onmix plushie.download. Override output locations for both stock and extension builds.- Canvas group redesign - groups now support
roleandarrow_modeprops for accessible tree/list patterns in canvas. - Block-form options on Canvas widget -
roleandarrow_modecan be set via the DSL block form. - Focus ring support -
focus_ring_radiuson canvas groups, focus ring padding on interactive canvas widgets. - Expanded test helpers - additional utility functions for test sessions.
- Demo project links in docs - extensions guide, testing guide, running guide, getting-started guide, and examples README all link to the plushie-demos repo.
Changed
- Renderer binary version bumped to 0.5.1.
- Download URL updated from
plushie-ui/plushietoplushie-ui/plushie-rendererreleases. - Rust crate references updated throughout for the
plushie-rendererworkspace split (plushie-ext,plushie-core,plushie-renderer-lib). - Canvas element terminology updated across docs and code.
Fixed
- Protocol dispatch warning in
Canvas.build/1. - Credo string literal warnings in Canvas doc comments.
- Star rating
focus_styleuses correct stroke object format. - Button style map removed from RatePlushie (uses default theme).
- Review form theme contrast in RatePlushie.
- Canvas scope walker wraps transform/clip directives as metadata.
- Heading text colors restored for contrast.
- Vestigial
interactivefield removed from leaf shape structs.
[0.4.0] - 2026-03-22
Breaking
- Project renamed from toddy to plushie. All module names (
Toddy.*->Plushie.*), config keys (:toddy->:plushie), environment variables (TODDY_*->PLUSHIE_*), and mix tasks (toddy.*->plushie.*) have changed. - Canvas shapes are now typed structs instead of plain maps. Builder
functions (
rect,circle,line,text,path,image,svg,stroke,linear_gradient) return struct instances (%Rect{},%Circle{}, etc.) withPlushie.Encodeprotocol implementations. Code that pattern-matched on%{type: "rect"}must use%Rect{}. import Plushie.Canvas.Shapeno longer needed forgroup,layer, orinteractivein canvas blocks. Useimport Plushie.UIinstead. Canvastext,image, andsvgcalls are automatically resolved inside canvas/layer/group blocks.- Test backend renamed:
:pooled_mock->:mock,Plushie.Test.Backend.Pooled->Plushie.Test.Backend.MockRenderer. The env var value changes:PLUSHIE_TEST_BACKEND=mock(waspooled_mock).Plushie.Test.MockBridgerenamed toPlushie.Test.InternalMockBridge(@moduledoc false). Padding.encode/1renamed toPadding.cast/1(normalization, not wire encoding).
Added
- Block-form options for all widgets. Every leaf widget and canvas
shape supports a do-block syntax for declaring options:
button "save", "Save" do style :primary padding %{top: 10, bottom: 10} end - Container inline props. Container widgets (column, row, container,
etc.) accept option declarations directly in their do-blocks, mixed
with children:
column do spacing 8 padding 16 text("Hello") end - Nested do-blocks for struct-typed options. Options like
padding,a11y,border,shadow, andstylesupport nested do-blocks that construct typed structs:container "card" do border do width 1 color "#ddd" rounded 8 end shadow do color "#0000001a" offset_y 2 blur_radius 8 end text("Content") end interactivedirective with id-first syntax, keyword form, block form, and pipe form for canvas shape interactivity.Plushie.DSL.Buildablebehaviour - formal contract for types participating in the DSL block-form pattern (from_opts/1,__field_keys__/0,__field_types__/0).- Compile-time validation everywhere. All widget block forms validate option keys at compile time. Using an option that doesn't belong to the current widget produces a helpful error. Canvas blocks validate every call against its context (canvas/layer/group).
- Context-aware
canvas_scopewalker validates and rewrites calls inside canvas blocks. Wrong-aritytext/image/svgcalls, widget macros, and misplaced shapes produce compile-time errors. - Context-aware
container_scopewalker validates container options. Using an option on the wrong container lists which containers support it. - New value structs -
ShapeStyle(hover/pressed overrides),DragBounds,HitRect,Dash, plusPaddingandFontconverted from utility types to proper structs. - Extension DSL integration - extension widgets automatically
generate
Buildablecallbacks and option metadata frompropdeclarations. - Tree normalizer leak detection - shape structs and DSL metadata tuples in the widget tree produce clear error messages.
- Event coalescing -
max_rateon subscriptions,event_rateon widgets, host-side pending coalesce buffer for mouse moves and sensor resizes. - Three transport modes -
:spawn(default),:stdio(forplushie --exec), and{:iostream, pid}(for SSH/TCP/custom). - Canvas interactive shapes - renderer-side hit testing with click,
hover, drag, focus events via the
interactivefield on shapes. docs/dsl-internals.md- maintainer guide for the DSL architecture, Buildable behaviour, and scope walkers.--wasmflag formix plushie.downloadandmix plushie.build.bin/plushiesymlink created bymix plushie.downloadfor stable path references without the platform-specific name.mix plushie.connectreplacesmix plushie.stdio. Connects to the renderer via Unix socket or TCP instead of stdin/stdout. Token auth via Settings message.- Doc-sync tests linking doc code blocks to test functions via HTML comment markers.
Changed
Downloaded binaries moved from
priv/bin/to_build/plushie/bin/. Build artifacts belong in_build/wheremix cleanremoves them.mix plushie.stdiorenamed tomix plushie.connect. The old stdin/stdout transport is still available as a fallback whenPLUSHIE_SOCKETis not set.Plushie.UIis now the single macro/DSL layer. All shape macros (rect,circle,group,layer, etc.), path commands, transforms, clips, and gradients are available viaimport Plushie.UI.Plushie.Canvas.Shapeis now a pure-function module (no macros). Import it directly only for helper functions outside canvas blocks.All
Encodeprotocol implementations moved to their respective struct module files.Plushie.Encodecontains only the protocol definition and primitive implementations.Widget struct field types tightened to reference specific type modules (e.g.,
Plushie.Type.Padding.t()instead ofterm()).Canvas widget type annotations use
canvas_shape()union type.@widget_callsderived from component lists instead of manually maintained.Doc code examples use
use Plushie.Appinstead of@behaviour Plushie.App(the latter misses default implementations of optional callbacks likewindow_config/1).
[0.3.0] - 2026-03-19
Initial public release.
Added
- Elm architecture -
init/1,update/2,view/1, optionalsubscribe/1callbacks via thePlushie.Appbehaviour. - 38 built-in widget types - layout (column, row, container, scrollable, stack, grid, pane_grid), display (text, rich_text, markdown, image, svg, progress_bar, qr_code, rule, canvas), input (button, text_input, text_editor, checkbox, radio, toggler, slider, vertical_slider, pick_list, combo_box, table), and wrappers (tooltip, pointer_area, sensor, overlay, responsive, themer, keyed_column, space, floating, pin, window).
- 22 built-in themes - light, dark, dracula, nord, solarized,
gruvbox, catppuccin, tokyo night, kanagawa, moonfly, nightfly,
oxocarbon, ferra. Custom palettes and per-widget style overrides
via
Plushie.Type.StyleMap. - Multi-window - declare window nodes in the widget tree; the framework manages open/close/update automatically.
- Platform effects - native file dialogs, clipboard (text, HTML, primary selection), OS notifications.
- Accessibility - screen reader support via accesskit on all platforms. A11y props on all widgets.
- Commands - async work, streaming, timers, widget ops (focus, scroll, select), window management (25+ operations), image management, platform effects, extension commands.
- Subscriptions - timers, keyboard, mouse, touch, IME, window lifecycle, animation frames, system theme changes.
- 16 typed event structs - Widget, Key, Mouse, Touch, Ime, Window, Canvas, PointerArea, Pane, Sensor, Effect, System, Timer, Async, Stream, Modifiers.
- Scoped widget IDs - containers namespace children's IDs automatically. Pattern match on local ID or scope chain.
- Three-backend test framework - mocked (fast, no display), headless (real rendering via tiny-skia, screenshots), windowed (real GPU windows). Same API across all three.
- Extension system - pure Elixir composite widgets or Rust-backed
native widgets via
Plushie.Extensionmacro DSL. - Live reload - file watching in dev mode, enabled by default
via
mix plushie.gui. State preserved across reloads. - Daemon mode -
Plushie.start_link(MyApp, daemon: true)keeps the process running after the last window closes. - Precompiled binaries -
mix plushie.downloadfetches platform-specific binaries with mandatory SHA256 verification. - Build from source -
mix plushie.buildcompiles the plushie binary, with optional extension workspace generation. - State helpers -
Plushie.State(revision tracking),Plushie.Undo(undo/redo),Plushie.Selection(single/multi/range),Plushie.Route(navigation),Plushie.Data(query pipeline),Plushie.Animation(easing functions). - Canvas drawing - shape primitives (rect, circle, arc, path, text, image) with layers, gradients, opacity, and caching.
- 8 example apps - Counter, Todo, Notes, Clock, Shortcuts, AsyncFetch, ColorPicker, Catalog.