plushie 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/AGENTS.md +595 -0
  4. data/CHANGELOG.md +147 -3
  5. data/CLAUDE.md +1 -0
  6. data/README.md +93 -227
  7. data/Steepfile +46 -2
  8. data/docs/README.md +56 -0
  9. data/docs/guides/01-introduction.md +266 -0
  10. data/docs/guides/02-getting-started.md +472 -0
  11. data/docs/guides/03-your-first-app.md +516 -0
  12. data/docs/guides/04-the-development-loop.md +499 -0
  13. data/docs/guides/05-events.md +548 -0
  14. data/docs/guides/06-lists-and-inputs.md +605 -0
  15. data/docs/guides/07-layout.md +530 -0
  16. data/docs/guides/08-styling.md +599 -0
  17. data/docs/guides/09-animation.md +516 -0
  18. data/docs/guides/10-subscriptions.md +470 -0
  19. data/docs/guides/11-async-and-effects.md +531 -0
  20. data/docs/guides/12-canvas.md +583 -0
  21. data/docs/guides/13-custom-widgets.md +705 -0
  22. data/docs/guides/14-state-management.md +600 -0
  23. data/docs/guides/15-testing.md +649 -0
  24. data/docs/guides/16-shared-state.md +567 -0
  25. data/docs/guides/17-packaging.md +549 -0
  26. data/docs/reference/accessibility.md +420 -0
  27. data/docs/reference/animation.md +482 -0
  28. data/docs/reference/app-lifecycle.md +504 -0
  29. data/docs/reference/built-in-widgets.md +702 -0
  30. data/docs/reference/canvas.md +589 -0
  31. data/docs/reference/commands.md +376 -0
  32. data/docs/reference/composition-patterns.md +816 -0
  33. data/docs/reference/configuration.md +302 -0
  34. data/docs/reference/custom-types.md +203 -0
  35. data/docs/reference/custom-widgets.md +681 -0
  36. data/docs/reference/dsl.md +500 -0
  37. data/docs/reference/events.md +596 -0
  38. data/docs/reference/native-extension.md +636 -0
  39. data/docs/reference/rake-tasks.md +440 -0
  40. data/docs/reference/scoped-ids.md +336 -0
  41. data/docs/reference/subscriptions.md +248 -0
  42. data/docs/reference/testing.md +448 -0
  43. data/docs/reference/themes-and-styling.md +511 -0
  44. data/docs/reference/versioning.md +174 -0
  45. data/docs/reference/windows-and-layout.md +692 -0
  46. data/docs/reference/wire-protocol.md +328 -0
  47. data/docs/stewardship/README.md +109 -0
  48. data/docs/stewardship/concurrency-shape.md +208 -0
  49. data/docs/stewardship/dsl-discipline.md +204 -0
  50. data/docs/stewardship/elm-invariants.md +196 -0
  51. data/docs/stewardship/goals-and-non-goals.md +95 -0
  52. data/docs/stewardship/performance-bar.md +157 -0
  53. data/docs/stewardship/posture.md +118 -0
  54. data/docs/stewardship/resilience.md +159 -0
  55. data/docs/stewardship/roadmap/README.md +20 -0
  56. data/docs/stewardship/simplicity.md +182 -0
  57. data/docs/stewardship/test-discipline.md +154 -0
  58. data/docs/stewardship/triage.md +153 -0
  59. data/docs/stewardship/trust-model.md +112 -0
  60. data/examples/README.md +2 -2
  61. data/examples/async_fetch.rb +2 -2
  62. data/examples/color_picker.rb +19 -69
  63. data/examples/counter.rb +4 -4
  64. data/examples/notes.rb +2 -2
  65. data/examples/rate_plushie.rb +135 -103
  66. data/examples/shortcuts.rb +1 -1
  67. data/examples/widgets/color_picker_widget.rb +254 -27
  68. data/examples/widgets/star_rating.rb +90 -44
  69. data/examples/widgets/theme_toggle.rb +79 -33
  70. data/lib/plushie/animation/sequence.rb +58 -0
  71. data/lib/plushie/animation/spring.rb +88 -0
  72. data/lib/plushie/animation/transition.rb +86 -0
  73. data/lib/plushie/animation/tween.rb +264 -0
  74. data/lib/plushie/animation.rb +19 -239
  75. data/lib/plushie/app.rb +8 -1
  76. data/lib/plushie/binary.rb +45 -10
  77. data/lib/plushie/bounded_queue.rb +32 -0
  78. data/lib/plushie/bridge.rb +148 -34
  79. data/lib/plushie/canvas/shape/group.rb +17 -1
  80. data/lib/plushie/canvas/shape/rect.rb +7 -2
  81. data/lib/plushie/canvas/shape/transform.rb +4 -2
  82. data/lib/plushie/canvas/shape.rb +19 -2
  83. data/lib/plushie/canvas_widget.rb +508 -0
  84. data/lib/plushie/cargo_plushie.rb +121 -0
  85. data/lib/plushie/command/image.rb +78 -0
  86. data/lib/plushie/command/scroll.rb +51 -0
  87. data/lib/plushie/command/text.rb +55 -0
  88. data/lib/plushie/command/window.rb +146 -0
  89. data/lib/plushie/command/window_query.rb +54 -0
  90. data/lib/plushie/command.rb +194 -297
  91. data/lib/plushie/connection.rb +68 -15
  92. data/lib/plushie/dev_server.rb +35 -8
  93. data/lib/plushie/effect.rb +170 -0
  94. data/lib/plushie/encode.rb +32 -12
  95. data/lib/plushie/event/diagnostic.rb +221 -0
  96. data/lib/plushie/event/specs.rb +211 -0
  97. data/lib/plushie/event.rb +253 -162
  98. data/lib/plushie/key_modifiers.rb +5 -5
  99. data/lib/plushie/node.rb +9 -5
  100. data/lib/plushie/protocol/decode.rb +624 -209
  101. data/lib/plushie/protocol/encode.rb +141 -43
  102. data/lib/plushie/protocol.rb +3 -3
  103. data/lib/plushie/rake.rb +69 -62
  104. data/lib/plushie/renderer_env.rb +14 -5
  105. data/lib/plushie/renderer_exit.rb +41 -0
  106. data/lib/plushie/runtime/commands.rb +150 -39
  107. data/lib/plushie/runtime/subscriptions.rb +34 -17
  108. data/lib/plushie/runtime/windows.rb +178 -0
  109. data/lib/plushie/runtime.rb +1076 -73
  110. data/lib/plushie/selection.rb +4 -4
  111. data/lib/plushie/subscription.rb +101 -90
  112. data/lib/plushie/test/case.rb +2 -1
  113. data/lib/plushie/test/helpers.rb +170 -10
  114. data/lib/plushie/test/script.rb +1 -1
  115. data/lib/plushie/test/session.rb +272 -58
  116. data/lib/plushie/test/session_pool.rb +27 -11
  117. data/lib/plushie/test/snapshot.rb +18 -0
  118. data/lib/plushie/test.rb +0 -1
  119. data/lib/plushie/thread_pool.rb +1 -1
  120. data/lib/plushie/timer_scheduler.rb +103 -0
  121. data/lib/plushie/transport/framing.rb +40 -1
  122. data/lib/plushie/transport/tcp_adapter.rb +7 -0
  123. data/lib/plushie/tree/diff.rb +208 -0
  124. data/lib/plushie/tree/search.rb +98 -0
  125. data/lib/plushie/tree.rb +684 -170
  126. data/lib/plushie/type/a11y.rb +17 -2
  127. data/lib/plushie/type/alignment.rb +4 -4
  128. data/lib/plushie/type/border.rb +16 -1
  129. data/lib/plushie/type/color.rb +1 -1
  130. data/lib/plushie/type/font.rb +5 -9
  131. data/lib/plushie/type/gradient.rb +47 -10
  132. data/lib/plushie/type/line_height.rb +42 -0
  133. data/lib/plushie/type/padding.rb +21 -14
  134. data/lib/plushie/type/style_map.rb +9 -9
  135. data/lib/plushie/type/theme.rb +71 -0
  136. data/lib/plushie/ui.rb +222 -156
  137. data/lib/plushie/undo.rb +46 -12
  138. data/lib/plushie/version.rb +6 -3
  139. data/lib/plushie/widget/build.rb +67 -6
  140. data/lib/plushie/widget/button.rb +9 -52
  141. data/lib/plushie/widget/canvas.rb +28 -58
  142. data/lib/plushie/widget/checkbox.rb +26 -45
  143. data/lib/plushie/widget/column.rb +5 -41
  144. data/lib/plushie/widget/combo_box.rb +27 -55
  145. data/lib/plushie/widget/container.rb +11 -49
  146. data/lib/plushie/widget/floating.rb +10 -47
  147. data/lib/plushie/widget/grid.rb +14 -52
  148. data/lib/plushie/widget/image.rb +23 -50
  149. data/lib/plushie/widget/keyed_column.rb +10 -47
  150. data/lib/plushie/widget/markdown.rb +18 -47
  151. data/lib/plushie/widget/native_build.rb +333 -0
  152. data/lib/plushie/widget/overlay.rb +12 -49
  153. data/lib/plushie/widget/pane_grid.rb +15 -52
  154. data/lib/plushie/widget/pick_list.rb +26 -54
  155. data/lib/plushie/widget/pin.rb +9 -46
  156. data/lib/plushie/widget/pointer_area.rb +38 -0
  157. data/lib/plushie/widget/progress_bar.rb +15 -46
  158. data/lib/plushie/widget/qr_code.rb +15 -44
  159. data/lib/plushie/widget/radio.rb +22 -53
  160. data/lib/plushie/widget/responsive.rb +7 -44
  161. data/lib/plushie/widget/rich_text.rb +68 -35
  162. data/lib/plushie/widget/row.rb +5 -41
  163. data/lib/plushie/widget/rule.rb +10 -36
  164. data/lib/plushie/widget/scrollable.rb +20 -59
  165. data/lib/plushie/widget/sensor.rb +8 -46
  166. data/lib/plushie/widget/slider.rb +22 -53
  167. data/lib/plushie/widget/space.rb +7 -35
  168. data/lib/plushie/widget/stack.rb +8 -45
  169. data/lib/plushie/widget/svg.rb +18 -47
  170. data/lib/plushie/widget/table.rb +62 -54
  171. data/lib/plushie/widget/text.rb +7 -44
  172. data/lib/plushie/widget/text_editor.rb +28 -54
  173. data/lib/plushie/widget/text_input.rb +9 -41
  174. data/lib/plushie/widget/themer.rb +7 -46
  175. data/lib/plushie/widget/toggler.rb +22 -50
  176. data/lib/plushie/widget/tooltip.rb +15 -53
  177. data/lib/plushie/widget/vertical_slider.rb +20 -52
  178. data/lib/plushie/widget/window.rb +13 -43
  179. data/lib/plushie/widget.rb +760 -0
  180. data/lib/plushie/widget_set.rb +82 -0
  181. data/lib/plushie.rb +45 -17
  182. data/sig/base64.rbs +6 -0
  183. data/sig/msgpack.rbs +13 -0
  184. data/sig/plushie/animation.rbs +74 -33
  185. data/sig/plushie/app.rbs +1 -1
  186. data/sig/plushie/bounded_queue.rbs +13 -0
  187. data/sig/plushie/bridge.rbs +14 -4
  188. data/sig/plushie/canvas/shape.rbs +3 -2
  189. data/sig/plushie/canvas_widget.rbs +55 -0
  190. data/sig/plushie/cargo_plushie.rbs +11 -0
  191. data/sig/plushie/command/image.rbs +11 -0
  192. data/sig/plushie/command/scroll.rbs +10 -0
  193. data/sig/plushie/command/text.rbs +11 -0
  194. data/sig/plushie/command/window.rbs +29 -0
  195. data/sig/plushie/command/window_query.rbs +14 -0
  196. data/sig/plushie/command.rbs +32 -42
  197. data/sig/plushie/effect.rbs +31 -0
  198. data/sig/plushie/encode.rbs +5 -0
  199. data/sig/plushie/event.rbs +170 -86
  200. data/sig/plushie/key_modifiers.rbs +1 -0
  201. data/sig/plushie/node.rbs +9 -2
  202. data/sig/plushie/protocol.rbs +58 -6
  203. data/sig/plushie/route.rbs +2 -2
  204. data/sig/plushie/runtime/windows.rbs +32 -0
  205. data/sig/plushie/runtime.rbs +100 -6
  206. data/sig/plushie/subscription.rbs +24 -20
  207. data/sig/plushie/timer_scheduler.rbs +19 -0
  208. data/sig/plushie/tree/diff.rbs +13 -0
  209. data/sig/plushie/tree/search.rbs +12 -0
  210. data/sig/plushie/tree.rbs +44 -3
  211. data/sig/plushie/type/a11y.rbs +44 -0
  212. data/sig/plushie/type/font.rbs +23 -0
  213. data/sig/plushie/type/gradient.rbs +9 -0
  214. data/sig/plushie/type/line_height.rbs +10 -0
  215. data/sig/plushie/ui.rbs +9 -1
  216. data/sig/plushie/undo.rbs +14 -6
  217. data/sig/plushie/widget/build.rbs +18 -0
  218. data/sig/plushie/widget_dsl.rbs +47 -0
  219. data/sig/plushie/widget_set.rbs +5 -0
  220. data/sig/plushie.rbs +17 -1
  221. metadata +104 -26
  222. data/docs/accessibility.md +0 -489
  223. data/docs/app-behaviour.md +0 -614
  224. data/docs/commands.md +0 -882
  225. data/docs/composition-patterns.md +0 -1037
  226. data/docs/dsl-internals.md +0 -343
  227. data/docs/effects.md +0 -108
  228. data/docs/events.md +0 -653
  229. data/docs/extensions.md +0 -1791
  230. data/docs/getting-started.md +0 -237
  231. data/docs/layout.md +0 -266
  232. data/docs/running.md +0 -356
  233. data/docs/scoped-ids.md +0 -200
  234. data/docs/testing.md +0 -844
  235. data/docs/theming.md +0 -292
  236. data/docs/tutorial.md +0 -369
  237. data/examples/catalog.rb +0 -448
  238. data/lib/plushie/effects.rb +0 -169
  239. data/lib/plushie/extension/build.rb +0 -293
  240. data/lib/plushie/extension.rb +0 -339
  241. data/lib/plushie/test/event_decoder.rb +0 -118
  242. data/lib/plushie/widget/mouse_area.rb +0 -72
  243. data/sig/plushie/effects.rbs +0 -31
  244. data/sig/plushie/extension.rbs +0 -40
data/CHANGELOG.md CHANGED
@@ -4,6 +4,150 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
+ ## [0.6.0] - 2026-05-09
8
+
9
+ Targets plushie-renderer 0.7.1.
10
+
11
+ ### Breaking changes
12
+
13
+ - Environment variable `PLUSHIE_SOURCE_PATH` renamed to
14
+ `PLUSHIE_RUST_SOURCE_PATH`. Update shell profiles and CI configs.
15
+ - Constant `Plushie::BINARY_VERSION` renamed to
16
+ `Plushie::PLUSHIE_RUST_VERSION` to match the plushie-rust release
17
+ identifier. Anything referencing the old name needs updating.
18
+ - `Command.async` renamed to `Command.task` for cross-SDK consistency
19
+ (`async` is reserved in Python and Rust). `Command.done` renamed to
20
+ `Command.dispatch` to reflect the "send through update" semantic.
21
+ `Command.widget_commands` renamed to `Command.widget_batch` to match
22
+ the Elixir SDK and disambiguate from the `Plushie::Command` type.
23
+ - Grid widget `columns` prop renamed to `num_columns`.
24
+ - `Command.Image.create_image` and `update_image` no longer accept
25
+ `pixels:`, `width:`, `height:` keyword arguments for raw RGBA data.
26
+ Use the new dedicated `create_image_rgba(handle, width, height, pixels)`
27
+ and `update_image_rgba(handle, width, height, pixels)` constructors
28
+ instead. The blob-data form of `create_image` and `update_image`
29
+ (passing raw binary data directly) is unchanged.
30
+ - Table widget `:selected` and `:striped` props removed; they were
31
+ dead code with no renderer-side effect.
32
+
33
+ ### Added
34
+
35
+ - `Plushie::CargoPlushie.resolve` helper that locates a usable
36
+ cargo-plushie via `PLUSHIE_RUST_SOURCE_PATH`, falling back to a
37
+ version-matched binary on `PATH`, otherwise raising with
38
+ `cargo install cargo-plushie --version <version> --locked`
39
+ guidance.
40
+ - `docs/versioning.md` documenting the `PLUSHIE_RUST_VERSION` pin and
41
+ SDK-vs-plushie-rust versioning rules.
42
+ - `Event::SessionError` and `Event::SessionClosed` typed variants.
43
+ `SessionError` includes a `code` field carrying the numeric error code
44
+ from the renderer.
45
+ - `Event::Diagnostic` typed variants: `crash`, `update_panicked`,
46
+ `renderer_error`, `protocol_error`, and `unknown`. Previously all
47
+ diagnostics arrived as the generic `Diagnostic` struct; each kind
48
+ now has its own Data class with kind-specific fields.
49
+ - `Plushie::BufferOverflowError` and `Plushie::ProtocolVersionMismatchError`
50
+ typed error classes raised on the matching renderer-side conditions.
51
+ - `Event::Effect` typed per-kind result variants. Effect callbacks now
52
+ receive a typed struct rather than a raw Hash.
53
+ - `RichText::Span` typed Data class for inline text spans.
54
+ - `link_click` events decoded as a typed `:link_click` variant on
55
+ `Event::Widget`.
56
+ - Touch release events carry a `lost` flag when the pointer left the
57
+ window before the release.
58
+ - `Rule` widget `thickness` prop as a direction-agnostic alternative to
59
+ the existing `width`/`height` split.
60
+ - SDK-side event coalescing: high-frequency events declared coalesable
61
+ are deduplicated in the bounded queue before reaching `update`.
62
+ - `Command.dispatch` chain depth is now capped; dispatching beyond the
63
+ limit raises rather than looping indefinitely.
64
+ - `Binary.resolve` falls back to a locally built renderer binary under
65
+ `../plushie-rust/target/{release,debug}/plushie-renderer` when
66
+ `PLUSHIE_RUST_SOURCE_PATH` is set and no explicit path is configured.
67
+ - `rake plushie:preflight` rebuilds the renderer binary from the local
68
+ plushie-rust checkout when `PLUSHIE_RUST_SOURCE_PATH` is set.
69
+ - Negative padding, border width, and border radius values are now
70
+ rejected at build time with an `ArgumentError`.
71
+
72
+ ### Fixed
73
+
74
+ - Subscription `key_press` and `key_release` events now read the
75
+ structured key payload (`key`, `modified_key`, `physical_key`,
76
+ `location`, `text`, `repeat`) from the `value` field. The previous
77
+ fallback to top-level message fields read modifiers from the wrong
78
+ location when value was non-Hash and would silently misread future
79
+ shape changes.
80
+ - `animation_frame` and `theme_changed` no longer carry dead fallback
81
+ reads alongside the canonical `value` access; the fallbacks could
82
+ never fire against the real renderer and masked the intended source
83
+ field.
84
+ - `ime_preedit` and `ime_commit` now raise `ArgumentError` when the
85
+ `value` payload is missing or non-Hash, surfacing wire-shape drift
86
+ instead of producing an `Event::Ime` with nil text and cursor.
87
+ - Concurrency bugs in the runtime, bridge, and session pool: a race in
88
+ the timer scheduler, a missing mutex on effect-kind state, and a
89
+ session-pool drain condition that could deadlock under session churn.
90
+ - Cancelled async threads are now properly released; previously a
91
+ cancelled task could hold its thread until the pool was torn down.
92
+ - Effect cancellation events are dispatched to `update` during SDK
93
+ shutdown so apps can clean up transient state.
94
+ - `image_list` and `image_clear` now route through the typed `image_op`
95
+ wire channel rather than a generic command envelope.
96
+ - `default_font` is always encoded as a `{ family: ... }` object; the
97
+ previous path emitted a bare string that the renderer rejected.
98
+ - `window_opened` position fields are now read from top-level `x`/`y`
99
+ keys as the protocol specifies; the previous path read from a nested
100
+ `position` map that does not exist.
101
+ - Effect tag supersession correctly clears the effect-kinds index when
102
+ a new registration replaces an existing one under the same tag.
103
+ - Unknown event families tolerate a missing `window_id` field instead
104
+ of raising a `KeyError`.
105
+ - Wayland-specific environment variables are now forwarded to the
106
+ renderer subprocess.
107
+ - `Plushie.configure { |c| c.log_level = ... }` is now honoured; the
108
+ previous path set the logger level only on the initial connection,
109
+ which was overwritten on restart.
110
+ - Renderer build lookup no longer raises on a missing `_build` directory;
111
+ it falls through to the next resolution step.
112
+ - Runtime event queue is bounded; a stalled app no longer causes
113
+ unbounded memory growth under high-frequency event sources.
114
+ - Widget state callbacks are no longer inherited across unrelated widget
115
+ subclasses defined with `Widget.define`.
116
+ - Effects are cancelled and their callbacks suppressed when the SDK
117
+ shuts down.
118
+ - Timeout error messages include the action and selector for easier
119
+ diagnosis in test output.
120
+ - Widget set override names are validated to prevent accidental
121
+ shadowing of built-in DSL methods.
122
+ - Renderer exit details are sanitised before appearing in error messages
123
+ and logs.
124
+
125
+ ### Changed
126
+
127
+ - Native widget builds now delegate workspace generation and
128
+ `cargo build` to [cargo-plushie](https://crates.io/crates/cargo-plushie).
129
+ The Ruby SDK writes a minimal virtual app manifest under
130
+ `_build/plushie-renderer-spec/` and shells out to
131
+ `cargo plushie build`. Widget discovery, [patch.crates-io] forwarding,
132
+ collision checks, and constructor validation now live in
133
+ cargo-plushie and are shared across host SDKs.
134
+ - Native widget crates must now declare
135
+ `[package.metadata.plushie.widget] { type_name, constructor }` in
136
+ their Cargo.toml. cargo-plushie uses that table for discovery.
137
+ - Renderer subprocess environment is now filtered to variables with a
138
+ `PLUSHIE_` prefix plus a small display-server allowlist. Previously
139
+ the full shell environment was forwarded.
140
+ - Per-timer threads replaced with a single `TimerScheduler` thread
141
+ using deadline-based `IO.select`. Timer count no longer scales the
142
+ thread count.
143
+ - Renderer restart backoff parameters, nil-view handling, and frozen
144
+ overlay semantics aligned with the Elixir and other SDKs.
145
+
146
+ ### Removed
147
+
148
+ - The checked-in `native/plushie/Cargo.lock` stash. cargo-plushie
149
+ manages the scratch workspace's lock file.
150
+
7
151
  ## [0.5.0] - 2026-03-23
8
152
 
9
153
  Initial release. Targets plushie-renderer 0.5.0.
@@ -35,9 +179,9 @@ Initial release. Targets plushie-renderer 0.5.0.
35
179
  generates Cargo workspace, validates crate paths and constructors,
36
180
  detects type name and crate collisions, builds custom renderer binary
37
181
  - `Plushie.configure` block for SDK-wide configuration: `binary_path`,
38
- `source_path`, `build_name`, `extensions`, `extension_config`,
182
+ `source_path`, `build_name`, `widgets`, `widget_config`,
39
183
  `test_backend`
40
- - `extension_config` runtime configuration passed to Rust extensions
184
+ - `widget_config` runtime configuration passed to native widgets
41
185
  via the Settings wire message and `InitCtx`
42
186
  - WASM renderer download via `rake plushie:download[wasm]`
43
187
  - `PLUSHIE_BIN_FILE` and `PLUSHIE_WASM_DIR` env vars for overriding
@@ -54,7 +198,7 @@ Initial release. Targets plushie-renderer 0.5.0.
54
198
  - Minitest and RSpec integration
55
199
  - 100% YARD documentation coverage with zero warnings
56
200
  - RBS type signatures for all modules
57
- - GitHub Actions CI workflow (Ruby 3.2 + 3.3 matrix)
201
+ - GitHub Actions CI workflow (Ruby 3.2 + 3.3 + 4.0 matrix)
58
202
  - CONTRIBUTING.md with commit conventions and development guide
59
203
  - Rake tasks: download, build, run, connect, inspect, script, replay,
60
204
  preflight
data/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ AGENTS.md
data/README.md CHANGED
@@ -1,12 +1,21 @@
1
1
  # plushie
2
2
 
3
- Build native desktop apps in Ruby. **Pre-1.0**
3
+ Build native desktop apps in Ruby. **[Pre-1.0](#status)**
4
4
 
5
- Plushie is a desktop GUI framework that allows you to write your entire
6
- application in Ruby -- state, events, UI -- and get native windows
7
- on Linux, macOS, and Windows. Rendering is powered by
8
- [iced](https://github.com/iced-rs/iced), a cross-platform GUI library
9
- for Rust, which plushie drives as a precompiled binary behind the scenes.
5
+ Write your entire application in Ruby (state, events, UI) and get
6
+ native windows on Linux, macOS, and Windows. Available on
7
+ [RubyGems](https://rubygems.org/gems/plushie) as `plushie`. The
8
+ [renderer](https://github.com/plushie-ui/plushie-rust) is built on
9
+ [Iced](https://github.com/iced-rs/iced) and ships as a precompiled
10
+ binary, no Rust toolchain required.
11
+
12
+ SDKs are also available for
13
+ [Elixir](https://github.com/plushie-ui/plushie-elixir),
14
+ [Gleam](https://github.com/plushie-ui/plushie-gleam),
15
+ [Python](https://github.com/plushie-ui/plushie-python), and
16
+ [TypeScript](https://github.com/plushie-ui/plushie-typescript).
17
+
18
+ ## Quick start
10
19
 
11
20
  ```ruby
12
21
  class Counter
@@ -43,257 +52,114 @@ end
43
52
  Plushie.run(Counter)
44
53
  ```
45
54
 
46
- This is one of [8 examples](examples/) included in the repo, from a
47
- minimal counter to a full widget catalog. For complete project demos,
48
- including native Rust extensions, see the
49
- [plushie-demos](https://github.com/plushie-ui/plushie-demos/tree/main/ruby)
50
- repository.
51
-
52
- ## Getting started
53
-
54
- Add plushie to your Gemfile:
55
+ Add plushie to your Gemfile and download the renderer:
55
56
 
56
- ```ruby
57
+ ```bash
58
+ # Gemfile
57
59
  gem "plushie", "== 0.1.0"
58
60
  ```
59
61
 
60
- Then:
61
-
62
62
  ```bash
63
63
  bundle install
64
64
  rake plushie:download # download precompiled renderer binary
65
- ```
66
-
67
- Requires Ruby 3.2+. The precompiled binary requires no Rust toolchain.
68
-
69
- ### Your first app
70
-
71
- Create `lib/counter.rb`:
72
-
73
- ```ruby
74
- require "plushie"
75
-
76
- class Counter
77
- include Plushie::App
78
-
79
- Model = Plushie::Model.define(:count)
80
-
81
- def init(_opts) = Model.new(count: 0)
82
-
83
- def update(model, event)
84
- case event
85
- in Event::Widget[type: :click, id: "increment"]
86
- model.with(count: model.count + 1)
87
- in Event::Widget[type: :click, id: "decrement"]
88
- model.with(count: model.count - 1)
89
- else
90
- model
91
- end
92
- end
93
-
94
- def view(model)
95
- window("main", title: "Counter") do
96
- column(padding: 16, spacing: 8) do
97
- text("count", "Count: #{model.count}", size: 20)
98
- row(spacing: 8) do
99
- button("increment", "+")
100
- button("decrement", "-")
101
- end
102
- end
103
- end
104
- end
105
- end
106
-
107
- Plushie.run(Counter)
108
- ```
109
-
110
- Run it:
111
-
112
- ```bash
113
65
  ruby lib/counter.rb
114
66
  ```
115
67
 
116
- ## The Elm architecture
117
-
118
- Plushie follows the Elm architecture. Your app implements four callbacks:
119
-
120
- - **`init(opts)`** -- returns the initial model (any Ruby object, ideally
121
- immutable via `Plushie::Model.define`).
122
- - **`update(model, event)`** -- receives the current model and an event,
123
- returns the new model. Pure function. Return `[model, command]` for
124
- side effects.
125
- - **`view(model)`** -- receives the model, returns a UI tree using the
126
- block DSL. The runtime diffs trees and sends patches to the renderer.
127
- - **`subscribe(model)`** (optional) -- returns active subscriptions
128
- (timers, keyboard/mouse events).
129
-
130
- ## Features
131
-
132
- - **39 built-in widget types** -- buttons, text inputs, sliders, tables,
133
- markdown, canvas, pane grids, and more.
134
- - **22 built-in themes** -- light, dark, dracula, nord, catppuccin,
135
- tokyo night, kanagawa, and more.
136
- - **Multi-window** -- declare window nodes in your widget tree; the
137
- framework manages them automatically.
138
- - **Platform effects** -- native file dialogs, clipboard, OS
139
- notifications.
140
- - **Accessibility** -- screen reader support via accesskit.
141
- - **Live reload** -- `Plushie.run(MyApp, dev: true)` watches lib/ and
142
- reloads on file changes. Model state is preserved.
143
- - **Remote rendering** -- native desktop UI for server-side Ruby apps
144
- over SSH. Your init/update/view code doesn't change.
145
- - **Widget extensions** -- pure Ruby composites or native Rust-backed
146
- custom widgets via `include Plushie::Extension`.
147
- - **Configuration system** -- `Plushie.configure` for binary paths,
148
- extensions, test backends, and extension runtime config.
149
- - **WASM renderer** -- `rake plushie:download[wasm]` downloads a WASM
150
- build of the renderer for browser targets.
151
-
152
- ## Documentation
153
-
154
- **Guides:**
155
-
156
- - [Getting started](docs/getting-started.md) -- setup, first app, rake tasks, dev mode
157
- - [Tutorial: building a todo app](docs/tutorial.md) -- step-by-step walkthrough
158
- - [App behaviour](docs/app-behaviour.md) -- init, update, view, subscribe callbacks
159
- - [Layout](docs/layout.md) -- column, row, container, spacing, alignment
160
- - [Events](docs/events.md) -- widget, keyboard, mouse, window, canvas events
161
- - [Commands](docs/commands.md) -- async, timers, widget ops, effects, batching
162
- - [Effects](docs/effects.md) -- file dialogs, clipboard, notifications
163
- - [Scoped IDs](docs/scoped-ids.md) -- how container nesting scopes widget IDs
164
-
165
- **Advanced:**
68
+ Requires Ruby 3.2+. The repo includes
69
+ [several other examples](examples/) you can try. For multi-file
70
+ projects (custom widgets, native Rust extensions, real project
71
+ scaffolding), see the
72
+ [plushie-demos](https://github.com/plushie-ui/plushie-demos/tree/main/ruby)
73
+ repo.
166
74
 
167
- - [Running](docs/running.md) -- transports, remote rendering, WASM
168
- - [Theming](docs/theming.md) -- built-in themes and custom styling
169
- - [Testing](docs/testing.md) -- mock, headless, and windowed backends
170
- - [Composition patterns](docs/composition-patterns.md) -- tabs, modals, forms, lists
171
- - [Accessibility](docs/accessibility.md) -- screen reader support, roles, labels
172
- - [Extensions](docs/extensions.md) -- Ruby composites and native Rust widgets
173
- - [DSL internals](docs/dsl-internals.md) -- how the UI builder works under the hood
75
+ To add Plushie to your own project, see the
76
+ [getting started guide](docs/getting-started.md), or browse the
77
+ [docs](docs/) for all guides and references.
174
78
 
175
- **API reference:** [rubydoc.info/gems/plushie](https://www.rubydoc.info/gems/plushie)
79
+ ## How it works
176
80
 
177
- ## Testing
81
+ Your Ruby application and the renderer run as two OS processes
82
+ that exchange messages. Think of it like talking to a database,
83
+ except the database is a GPU-accelerated GUI toolkit. The SDK builds
84
+ UI trees and handles events; the renderer draws native windows and
85
+ captures input.
178
86
 
179
- All testing goes through the renderer binary. No Ruby-side mocks.
180
- The mock backend runs at millisecond speed.
87
+ The SDK diffs each new tree against the previous one and sends only
88
+ the changes. If the renderer crashes, Plushie restarts it and
89
+ re-syncs your state. If your code raises, the SDK reverts to the
90
+ last good state. Neither process can take the other down.
181
91
 
182
- Add `require "plushie/test"` to your test helper:
92
+ The same protocol works over a local pipe, an SSH connection, or
93
+ any bidirectional byte stream. Your code doesn't need to change.
183
94
 
184
- ```ruby
185
- # test/test_helper.rb
186
- require "plushie"
187
- require "plushie/test"
188
- require "minitest/autorun"
189
- ```
95
+ ## Features
190
96
 
191
- Then write tests:
97
+ - **Elm architecture** - init, update, view. State lives in Ruby,
98
+ pure functions, predictable updates
99
+ - **Block DSL** - nested Ruby blocks build the widget tree with
100
+ natural indentation, no templates or markup
101
+ - **Built-in widgets** - layout, input, display, and interactive
102
+ widgets out of the box
103
+ - **Canvas** - shapes, paths, gradients, transforms, and
104
+ interactive elements for custom 2D drawing
105
+ - **Themes** - dark, light, nord, catppuccin, tokyo night, and
106
+ more, with custom palettes and per-widget style overrides
107
+ - **Animation** - renderer-side transitions, springs, and
108
+ sequences with no wire traffic per frame
109
+ - **Multi-window** - declare windows in your view; the framework
110
+ manages the rest
111
+ - **Platform effects** - native file dialogs, clipboard, OS
112
+ notifications
113
+ - **Accessibility** - keyboard navigation, screen readers, and
114
+ focus management via [AccessKit](https://accesskit.dev)
115
+ - **Custom widgets** - compose existing widgets in pure Ruby,
116
+ draw on the canvas, or extend with native Rust
117
+ - **Hot reload** - `Plushie.run(MyApp, dev: true)` watches lib/
118
+ and reloads on file changes with full state preservation
119
+ - **Remote rendering** - app on a server or embedded device,
120
+ renderer on a display machine over SSH or any byte stream
121
+ - **Fault-tolerant** - renderer crashes auto-recover; app
122
+ exceptions are caught and state reverted
123
+ - **Configuration system** - `Plushie.configure` for binary paths,
124
+ extensions, test backends, and widget runtime config
125
+ - **WASM renderer** - `rake plushie:download[wasm]` for browser
126
+ targets
127
+
128
+ ## Testing and automation
129
+
130
+ Tests run through the real renderer binary, not mocks. Interact like
131
+ a user: click, type, find elements, assert on text. All backends
132
+ support concurrent test execution to keep your suite fast as it
133
+ grows. Three interchangeable backends:
134
+
135
+ - **Mock** - millisecond tests, no display server
136
+ - **Headless** - real rendering via
137
+ [tiny-skia](https://github.com/linebender/tiny-skia), supports
138
+ screenshots for pixel regression in CI
139
+ - **Windowed** - real windows with GPU rendering, platform effects,
140
+ real input
192
141
 
193
142
  ```ruby
194
143
  class CounterTest < Plushie::Test::Case
195
144
  app Counter
196
145
 
197
146
  def test_clicking_increment_updates_counter
198
- click("#increment")
147
+ click("#inc")
199
148
  assert_text "#count", "Count: 1"
200
149
  end
201
150
  end
202
151
  ```
203
152
 
204
- Three interchangeable backends:
205
-
206
- - **Mock** (`PLUSHIE_TEST_BACKEND=mock`) -- millisecond tests, no display.
207
- Default.
208
- - **Headless** (`PLUSHIE_TEST_BACKEND=headless`) -- real rendering via
209
- tiny-skia, no display server. Pixel screenshots.
210
- - **Windowed** (`PLUSHIE_TEST_BACKEND=windowed`) -- real windows with GPU.
211
- Needs display server (Xvfb in CI).
212
-
213
- ## How it works
214
-
215
- Under the hood, a renderer built on iced handles window drawing and
216
- platform integration. Your Ruby code sends widget trees to the
217
- renderer over stdin; the renderer draws native windows and sends user
218
- events back over stdout.
219
-
220
- You don't need Rust to use plushie. The renderer is a precompiled
221
- binary, similar to how your app talks to a database without you
222
- writing C. If you need custom native rendering, the extension system
223
- lets you write Rust for just those parts.
224
-
225
- The same protocol works over a local pipe, an SSH connection, or any
226
- bidirectional byte stream.
227
-
228
- ## State helpers
229
-
230
- Plushie ships optional state management utilities:
231
-
232
- - **`Plushie::Animation`** -- easing functions and interpolation
233
- - **`Plushie::Route`** -- navigation stack for multi-view apps
234
- - **`Plushie::Selection`** -- single, multi, and range selection
235
- - **`Plushie::Undo`** -- undo/redo stacks with coalescing
236
- - **`Plushie::DataQuery`** -- filter, search, sort, paginate collections
237
- - **`Plushie::State`** -- path-based state with revision tracking
238
-
239
- ## Development
240
-
241
- ```bash
242
- bundle exec rake # tests + linter + type check
243
- bundle exec rake test # tests only
244
- bundle exec rake standard # linter only
245
- bundle exec rake steep # type check only
246
- bundle exec rake yard # generate API docs to doc/
247
- ```
248
-
249
- See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development guide.
250
-
251
- Rake tasks (add `require "plushie/rake"` to your Rakefile):
252
-
253
- ```bash
254
- rake plushie:download # download precompiled binary
255
- rake plushie:download[wasm] # download WASM renderer
256
- rake plushie:build # build from Rust source (with extensions if configured)
257
- rake plushie:run[Counter] # run an app
258
- rake plushie:connect[Counter] # connect to renderer via stdio
259
- rake plushie:inspect[Counter] # print UI tree as JSON
260
- rake plushie:script # run .plushie test scripts
261
- rake plushie:replay[path] # replay a script with real windows
262
- rake plushie:preflight # run all CI checks
263
- ```
264
-
265
- Configure the SDK programmatically:
266
-
267
- ```ruby
268
- Plushie.configure do |config|
269
- config.binary_path = "/opt/plushie/bin/plushie"
270
- config.extensions = [MyGauge]
271
- config.test_backend = :headless
272
- end
273
- ```
274
-
275
- ## System requirements
276
-
277
- The precompiled binary has no additional dependencies. To build from
278
- source, install a Rust toolchain via [rustup](https://rustup.rs/) and
279
- the platform-specific libraries:
280
-
281
- - **Linux (Debian/Ubuntu):**
282
- `sudo apt-get install libxkbcommon-dev libwayland-dev libx11-dev cmake fontconfig pkg-config`
283
- - **Linux (Arch):**
284
- `sudo pacman -S libxkbcommon wayland libx11 cmake fontconfig pkgconf`
285
- - **macOS:** `xcode-select --install`
286
- - **Windows:**
287
- [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/)
288
- with "Desktop development with C++"
153
+ The same interaction API is available outside Minitest via
154
+ `Plushie::Automation::Session`. Attach to any running app and drive
155
+ it programmatically. Agent-friendly by design.
289
156
 
290
- ## Links
157
+ ## Status
291
158
 
292
- | | |
293
- |---|---|
294
- | Ruby SDK | [github.com/plushie-ui/plushie-ruby](https://github.com/plushie-ui/plushie-ruby) |
295
- | Elixir SDK | [github.com/plushie-ui/plushie-elixir](https://github.com/plushie-ui/plushie-elixir) |
296
- | Renderer | [github.com/plushie-ui/plushie](https://github.com/plushie-ui/plushie) |
159
+ Pre-1.0. The core works (built-in widgets, event system, themes,
160
+ multi-window, testing framework, accessibility) but the API is
161
+ still evolving. Pin to an exact version and read the
162
+ [CHANGELOG](CHANGELOG.md) when upgrading.
297
163
 
298
164
  ## License
299
165
 
data/Steepfile CHANGED
@@ -1,8 +1,52 @@
1
1
  # Steepfile
2
+ #
3
+ # Files listed here are type-checked against their RBS declarations.
4
+ # Files with heavy metaprogramming (widget.rb define_method closures,
5
+ # Widget.define class_eval blocks) are excluded because Steep cannot
6
+ # analyze their dynamic method generation.
2
7
  target :lib do
3
8
  signature "sig"
4
- check "lib"
5
- library "msgpack"
9
+
10
+ # Core modules
11
+ check "lib/plushie/app.rb"
12
+ check "lib/plushie/encode.rb"
13
+ check "lib/plushie/event/specs.rb"
14
+ check "lib/plushie/key_modifiers.rb"
15
+ check "lib/plushie/route.rb"
16
+ check "lib/plushie/selection.rb"
17
+ check "lib/plushie/undo.rb"
18
+ check "lib/plushie/version.rb"
19
+
20
+ # Excluded from checking (RBS declarations retained for consumers):
21
+ #
22
+ # - model.rb: Extensions#with is defined inside a Data.define block;
23
+ # Steep resolves `self` to the enclosing module, not the Data class.
24
+ # - node.rb: Node = Data.define block; same self-resolution issue.
25
+ # - subscription.rb: Sub = Data.define block; same self-resolution issue.
26
+ # - event.rb: Widget/Key/Ime/Window/Modifiers/CommandError/System are
27
+ # all Data.define with block overrides; same self-resolution issue.
28
+
29
+ # Tree (normalization, search, diff)
30
+ check "lib/plushie/tree.rb"
31
+ check "lib/plushie/tree/search.rb"
32
+ check "lib/plushie/tree/diff.rb"
33
+
34
+ # Protocol (decode is the most type-critical)
35
+ check "lib/plushie/protocol.rb"
36
+ check "lib/plushie/protocol/decode.rb"
37
+
38
+ # Runtime
39
+ check "lib/plushie/runtime.rb"
40
+ check "lib/plushie/runtime/commands.rb"
41
+ check "lib/plushie/animation.rb"
42
+
43
+ # Widget build helpers
44
+ check "lib/plushie/widget/build.rb"
45
+
46
+ # Type modules
47
+ check "lib/plushie/type/line_height.rb"
48
+
6
49
  library "json"
7
50
  library "logger"
51
+ library "securerandom"
8
52
  end
data/docs/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # Documentation
2
+
3
+ ## Guides
4
+
5
+ Sequential chapters that build on each other. Start here if you're
6
+ new to Plushie.
7
+
8
+ 1. [Introduction](guides/01-introduction.md) - what Plushie is and how it works
9
+ 2. [Getting Started](guides/02-getting-started.md) - installation, binary setup, first run
10
+ 3. [Your First App](guides/03-your-first-app.md) - building a counter with the Elm architecture
11
+ 4. [The Development Loop](guides/04-the-development-loop.md) - hot reload, IRB, debugging
12
+ 5. [Events](guides/05-events.md) - widget events, keyboard, pointer, pattern matching
13
+ 6. [Lists and Inputs](guides/06-lists-and-inputs.md) - dynamic lists, text inputs, forms
14
+ 7. [Layout](guides/07-layout.md) - rows, columns, containers, responsive sizing
15
+ 8. [Styling](guides/08-styling.md) - themes, colors, fonts, per-widget style overrides
16
+ 9. [Animation and Transitions](guides/09-animation.md) - transitions, springs, tweens, easing
17
+ 10. [Subscriptions](guides/10-subscriptions.md) - timers, global key/pointer events, window events
18
+ 11. [Async and Effects](guides/11-async-and-effects.md) - tasks, streams, platform effects
19
+ 12. [Canvas](guides/12-canvas.md) - shapes, layers, transforms, interactive elements
20
+ 13. [Custom Widgets](guides/13-custom-widgets.md) - composing widgets, canvas widgets, native Rust widgets
21
+ 14. [State Management](guides/14-state-management.md) - routing, undo/redo, selection, data pipelines
22
+ 15. [Testing](guides/15-testing.md) - test framework, backends, selectors, screenshots
23
+ 16. [Shared State](guides/16-shared-state.md) - multi-session apps over SSH
24
+ 17. [Packaging](guides/17-packaging.md) - publishing a gem and shipping a native renderer
25
+
26
+ ## Reference
27
+
28
+ Lookup material organized by topic. Each page is self-contained.
29
+
30
+ - [Accessibility](reference/accessibility.md) - AccessKit integration, roles, labels, keyboard navigation
31
+ - [Animation](reference/animation.md) - transitions, springs, sequences, easing curves, animatable props
32
+ - [App Lifecycle](reference/app-lifecycle.md) - init/update/view callbacks, startup, renderer restart
33
+ - [Built-in Widgets](reference/built-in-widgets.md) - every widget with props, events, and examples
34
+ - [Canvas](reference/canvas.md) - shapes, layers, groups, transforms, clips, gradients
35
+ - [Commands and Effects](reference/commands.md) - async, focus, scroll, window ops, platform effects
36
+ - [Composition Patterns](reference/composition-patterns.md) - reusable components, overlays, navigation, state-helper integration
37
+ - [Configuration](reference/configuration.md) - Plushie.configure, environment variables, runtime options
38
+ - [Custom Types](reference/custom-types.md) - shared prop-type catalog
39
+ - [Custom Widgets](reference/custom-widgets.md) - Widget.define, behavioral widgets, canvas widgets, native crates
40
+ - [DSL](reference/dsl.md) - block DSL mechanics, context stack, auto-IDs, memo caching
41
+ - [Events](reference/events.md) - every event class, widget event taxonomy, pattern-matching cookbook
42
+ - [Native Extensions](reference/native-extension.md) - authoring a Rust widget crate and shipping it alongside a Ruby gem
43
+ - [Rake Tasks](reference/rake-tasks.md) - plushie:download, plushie:build, plushie:run, preflight
44
+ - [Scoped IDs](reference/scoped-ids.md) - ID scoping rules, scope matching, command paths
45
+ - [Subscriptions](reference/subscriptions.md) - timer, keyboard, pointer, window, catch-all subscriptions
46
+ - [Testing](reference/testing.md) - Plushie::Test::Case, RSpec helpers, backends, snapshots, scripts
47
+ - [Themes and Styling](reference/themes-and-styling.md) - built-in themes, custom palettes, style maps, gradients
48
+ - [Versioning](reference/versioning.md) - SDK version, PLUSHIE_RUST_VERSION pinning, Ruby version floor
49
+ - [Windows and Layout](reference/windows-and-layout.md) - window props, layout containers, Length/Padding/Alignment/Border/Shadow
50
+ - [Wire Protocol](reference/wire-protocol.md) - MessagePack/JSONL framing, handshake, session multiplexing
51
+
52
+ ## Other resources
53
+
54
+ - [Examples](https://github.com/plushie-ui/plushie-ruby/tree/main/examples) - example apps included in the repo
55
+ - [Changelog](../CHANGELOG.md) - version history and migration notes
56
+ - [Demo apps](https://github.com/plushie-ui/plushie-demos/tree/main/ruby) - multi-file projects with custom widgets and real scaffolding