phlex-reactive 0.2.3 → 0.2.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5644272b11bc8ff62496e05c81f01a92b1925ffe2b471ab123c05df573ee1343
4
- data.tar.gz: 56d13ad853e1b6da4e40e8e239838514171b9e908a59ad6db60f6f4a4b199b0e
3
+ metadata.gz: cd797b375cedb73b2a6f5695bf38a7eddd0a939aca5f8602c661537f6407ad7b
4
+ data.tar.gz: 994da28bed60c495d87d01fb736e80f19439ca52417f2c70f61a17aa7d15b351
5
5
  SHA512:
6
- metadata.gz: a489e7a7cdce253735c59c4b5196c52d2051e83f7fb783e7c892cbbd8b9be98934141392d7a84b58b938e80bfd5aba6e4bfe1995888b2b322d232a3fdc207a4a
7
- data.tar.gz: b9126364a92d1be370e6db639969803c25936e61910967fe05cac00ce908216dd5fa6f6f2f42f16d3c3c23c813d4e2e1c54614babb96f162f77bd71b6e3a5f6b
6
+ metadata.gz: 11e13357335ec795cc5d0da9b60da2ce059128fed4f355f3864d037fbf5c0dab692844d3b8dd9d10f0b386e7bccd86ae635b2d2bc17a24e4ed1988bd37ce014e
7
+ data.tar.gz: 11c0e74b06f6d55818fc65d83336d9ec77eee65422adfe477a5ebc8a83d6dda4ae6f1dad3c6fdf17a9e215cdef5bcc958bcbaa43c3e6d32b02adb15124c372e1
data/CHANGELOG.md CHANGED
@@ -8,6 +8,24 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
9
  ### Fixed
10
10
 
11
+ - **Reactive save silently dropped rich-text / custom-editor fields.** The
12
+ client's field auto-collection (`#collectFields`) queried only
13
+ `input[name], select[name], textarea[name]`, so a named rich-text editor
14
+ (`lexxy-editor`, `trix-editor`) or any `[contenteditable]` was skipped — a
15
+ reactive `save` posted an empty value and silently wiped the field, with no
16
+ error. `#collectFields` now also reads named custom editors and contenteditable
17
+ elements (by `name` *attribute*, since a plain element has no `name` IDL
18
+ property), reading the serialized `.value` else the contenteditable text. It
19
+ only fills a name the standard controls left absent or empty, so a hidden input
20
+ a rich editor mirrors into (e.g. Trix) still wins when populated. The vendored
21
+ client copy the system suite runs (`spec/dummy/public/vendor/...`) is now kept
22
+ byte-identical to source by a guard spec, so the browser tests never validate
23
+ stale client code again. Closes #8.
24
+
25
+ ## [0.2.3] - 2026-06-24
26
+
27
+ ### Fixed
28
+
11
29
  - **Record-backed components silently lost `reactive_state` every action.** When
12
30
  a component declared BOTH `reactive_record` and `reactive_state`, the state
13
31
  branch was dead: `reactive_token` signed only the record GID and
@@ -23,6 +41,16 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
23
41
  round trip (only a genuinely absent value falls back to the `initialize`
24
42
  default). No API changes. Closes #6.
25
43
 
44
+ ### Documentation
45
+
46
+ - Documented the combined record + state identity (the `{c, gid, s}` token
47
+ shape): the README, `docs/architecture.md`, and `docs/security.md` no longer
48
+ frame `reactive_record` and `reactive_state` as mutually exclusive. (#9)
49
+
50
+ ## [0.2.2] - 2026-06-24
51
+
52
+ ### Fixed
53
+
26
54
  - **Record-backed component built with a different init keyword by the action
27
55
  endpoint vs the broadcast path.** The click path (`Component.from_identity`)
28
56
  builds with `reactive_record_key` (the `reactive_record :name`), but the
@@ -37,7 +65,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
37
65
  an explicit `def self.model_param_name` override still wins. No API changes.
38
66
  Closes #4.
39
67
 
40
- ## [0.2.1]
68
+ ## [0.2.1] - 2026-06-24
41
69
 
42
70
  ### Fixed
43
71
 
@@ -52,7 +80,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
52
80
  the gem eager-loads cleanly. Added a regression spec that calls
53
81
  `eager_load_all`. No API changes.
54
82
 
55
- ## [0.2.0]
83
+ ## [0.2.0] - 2026-06-24
56
84
 
57
85
  ### Added
58
86
 
@@ -71,7 +99,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
71
99
  helpers. phlex-reactive still works on Action Cable without pgbus; the
72
100
  `exclude:` argument is simply ignored there.
73
101
 
74
- ## [0.1.0]
102
+ ## [0.1.0] - 2026-06-20
75
103
 
76
104
  ### Added
77
105
 
@@ -95,7 +123,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
95
123
  scaffolds a reactive component (and an RSpec spec when the app uses RSpec),
96
124
  state-backed by default or record-backed with `--record`.
97
125
 
98
- [Unreleased]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.1...HEAD
126
+ [Unreleased]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.2...HEAD
127
+ [0.2.3]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.2...v0.2.3
128
+ [0.2.2]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.1...v0.2.2
99
129
  [0.2.1]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.0...v0.2.1
100
130
  [0.2.0]: https://github.com/mhenrixon/phlex-reactive/compare/v0.1.0...v0.2.0
101
131
  [0.1.0]: https://github.com/mhenrixon/phlex-reactive/releases/tag/v0.1.0
data/README.md CHANGED
@@ -211,15 +211,21 @@ class Todos::Item < ApplicationComponent
211
211
  end
212
212
  ```
213
213
 
214
- ### 2. State-backed (record-less widgets)
214
+ ### 2. State-backed (signed instance vars)
215
215
 
216
- No database row e.g. a counter or a wizard step. The listed instance vars are
217
- signed into the token. Keep state small and JSON-serializable.
216
+ Sign small, JSON-serializable instance vars into the token. Use it **alone** for
217
+ a record-less widget (a counter, a wizard step), or **alongside `reactive_record`**
218
+ to carry transient UI state — which field, what mode — next to the row. Both the
219
+ record's GlobalID and the state are signed into one token and rebuilt on each
220
+ action. Keep state small and JSON-serializable.
218
221
 
219
222
  ```ruby
220
223
  reactive_state :count, :step # signed; rebuilt on each action
221
224
  ```
222
225
 
226
+ The [inline edit example](docs/examples/inline_edit.md) combines both: a
227
+ `reactive_record :record` plus `reactive_state :attribute, :editing`.
228
+
223
229
  ---
224
230
 
225
231
  ## Concrete examples
@@ -257,7 +263,7 @@ Use in controllers: `render turbo_stream: Counter.replace(counter)`.
257
263
  | Macro / helper | Use |
258
264
  |---|---|
259
265
  | `reactive_record :name` | Record-backed identity (GlobalID). State = the DB. |
260
- | `reactive_state :a, :b` | State-backed identity (signed instance vars). Record-less only. |
266
+ | `reactive_state :a, :b` | Signed instance-var identity. Standalone, or combined with `reactive_record` to sign transient UI state alongside the row. |
261
267
  | `action :name, params: { x: :integer }` | Declare a client-invokable action + its param schema. **Default-deny.** |
262
268
  | `reactive_attrs` | Spread onto the root element: marks it reactive + carries the signed token. |
263
269
  | `on(:action, event: "click", **params)` | Spread onto a trigger element. Adds `type=button` for clicks. |
@@ -314,8 +320,10 @@ phlex-reactive is built so the easy path is the safe path — but the boundary i
314
320
  real, so read this once.
315
321
 
316
322
  - **State is never trusted from the client.** The DOM holds a `MessageVerifier`-
317
- signed identity (`{component, gid}` or `{component, state}`), not raw state. A
318
- tampered class, record, or state value fails signature verification 400.
323
+ signed identity `{component, gid}` (record-backed), `{component, state}`
324
+ (state-backed), or `{component, gid, state}` when a component declares both
325
+ not raw state. A tampered class, record, or state value fails signature
326
+ verification → 400.
319
327
  - **Actions are default-deny.** Only methods declared with `action :name` are
320
328
  invokable. A public method without `action` is unreachable.
321
329
  - **You must authorize.** The signature proves the *token is yours*, not that
@@ -122,6 +122,7 @@ export default class extends Controller {
122
122
 
123
123
  #collectFields() {
124
124
  const fields = {}
125
+ // Standard form controls.
125
126
  this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((field) => {
126
127
  if (field.type === "checkbox") {
127
128
  fields[field.name] = field.checked
@@ -131,6 +132,25 @@ export default class extends Controller {
131
132
  fields[field.name] = field.value
132
133
  }
133
134
  })
135
+ // Named rich-text / custom editors (lexxy-editor, trix-editor) and bare
136
+ // [contenteditable]. These aren't input/select/textarea, so the query above
137
+ // skips them — without this, a reactive save posts an empty value and
138
+ // silently wipes the field (issue #8). Read whatever the element exposes:
139
+ // a custom editor's serialized `.value`, else its contenteditable text.
140
+ // Only fill a name the standard controls left absent or empty, so a synced
141
+ // hidden input (e.g. Trix mirrors into one) still wins when populated.
142
+ this.element
143
+ .querySelectorAll("[name]:is(lexxy-editor, trix-editor, [contenteditable=''], [contenteditable=true], [contenteditable=plaintext-only])")
144
+ .forEach((el) => {
145
+ // A plain element (e.g. a <div contenteditable>) has no `name` IDL
146
+ // property — only the attribute — so read getAttribute, not el.name.
147
+ const name = el.getAttribute("name")
148
+ if (!name) return
149
+ const existing = fields[name]
150
+ if (existing == null || existing === "") {
151
+ fields[name] = el.value ?? el.textContent ?? el.innerHTML ?? ""
152
+ }
153
+ })
134
154
  return fields
135
155
  }
136
156
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Phlex
4
4
  module Reactive
5
- VERSION = "0.2.3"
5
+ VERSION = "0.2.4"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phlex-reactive
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson