phlex-reactive 0.12.3 → 0.12.5

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: 8d6ffd3dcf058d459bb58de296bf1d5cb9dd0808164d35c71ffbf60f437e0301
4
- data.tar.gz: 11fc37b314bb89a87fa45db4c34328e2976dc82e00106383328b0fb18dd16097
3
+ metadata.gz: ec14089cc43f0bf439305f48b9fca3ee8b0fe2ba83b44b341e22370ad11fc22d
4
+ data.tar.gz: 101ae52fce38a343185a2699082c79709dadf609d9401f199b9b9c5f181dda9c
5
5
  SHA512:
6
- metadata.gz: 1625e8fb285dc9ca91d73609896452ff5998a6fcb90eca3e3f7ef98a1397db764a966a161f81db9795b0489afd193035a62255bf7223c72271515573e10ecd32
7
- data.tar.gz: 6c1448c90d1aad7e1814938b363c28108b924a20ef7bb7a8a4f67b1cfaa2706f62cc3724a43f72aa7aedeb28da2aaf177924268972dd965b261a305381244529
6
+ metadata.gz: '083b640658e403acc1bfeb311d0c22051cd3a2b275a4f44084dfb9c52755fab481eb63c7458e593eb43741d6b3726b8c8e7dadeb2059bcd9b9818cae64768937'
7
+ data.tar.gz: 3984b48e58eb838ed9bfb009d50444babd518831eb5d2fc7c0f77dc577060b146729f428e7bf1acead2281dd86547fc86130c43ba245b6a4956acf0d5e39d04f
data/CHANGELOG.md CHANGED
@@ -8,6 +8,54 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
9
  ### Added
10
10
 
11
+ - **Dev-mode warning when a client op resolves zero targets (#237).** A client
12
+ op that matches nothing is indistinguishable from a working no-op, and the
13
+ three documented scoping traps — the nested-root ownership filter, a selector
14
+ that matches the component's own root (root-scoped resolution never includes
15
+ the root itself), and the op stream's default target scope — all present
16
+ exactly that way. Now `verbose_errors` (on by default in dev/test) reaches
17
+ the client as `data-reactive-verbose="true"` on reactive roots and on
18
+ server-emitted `reactive:js` streams, and under that gate (or full
19
+ `debug` mode) the interpreter `console.warn`s once per unique
20
+ (op, selector, scope) naming the op, the selector, and the scoping root —
21
+ plus the documented escape (`to: :root` / `global: true`) as a hint when the
22
+ element exists in the DOM but sits outside the op's scope. Covers `on_client`
23
+ ops, reducer `$ops`, `reply…js(...)` / `broadcast_js_to` streams (including a
24
+ stream whose `target` root id has left the DOM), and busy/optimistic hint
25
+ targets. Production (flag off) renders no attribute and stays byte-identical
26
+ and silent; resolution behavior itself is unchanged everywhere.
27
+
28
+ - **`js.paste_into(selector)` — the clipboard-source trigger (#228).** A field
29
+ whose real `<input>` is visually hidden (an OTP cell UI painted by a
30
+ `reactive_compute` reducer) has no mouse-reachable paste path: right-click →
31
+ Paste targets the decorative cells, never the off-screen input. One declared
32
+ line — `button(hidden: true, **on_client(:click, js.paste_into("[name=code]")))
33
+ { "Paste code" }` — replaces the bespoke Stimulus controller:
34
+ - **The op** (the one async, value-reading member of the vocabulary): on the
35
+ user's gesture it starts `navigator.clipboard.readText()` fire-and-forget
36
+ (the browser's own permission UX — Chromium prompts, Safari shows its
37
+ paste pill) and, when the read resolves, feeds the text through the
38
+ **normal `input` pipeline** — set `.value`,
39
+ dispatch a bubbling `input` (reducers / `reactive_show` /
40
+ `reactive_on_complete` run exactly as if typed), then focus the field so a
41
+ partial paste continues from the caret. A denied/dismissed read, empty
42
+ text, or a missing API is a **silent no-op**; the op is fire-and-forget,
43
+ so chained siblings never wait.
44
+ - **Availability gating**: `on_client` marks a paste trigger with
45
+ `data-reactive-clipboard`; on connect (and every `turbo:morph-element`)
46
+ the controller sets `hidden = !available` on owned markers. Author the
47
+ trigger `hidden` and it is revealed only where the Async Clipboard API
48
+ exists — a dead button never shows in insecure contexts or webviews.
49
+ - **Actor-only, default-deny**: `paste_into` joins `focus`/`focus_first`/
50
+ `submit` in `BROADCAST_REFUSED_OPS` — `broadcast_to(js:)` raises
51
+ (a broadcast that reads every subscriber's clipboard would be hostile);
52
+ `reply.js` and gesture paths remain allowed.
53
+ - Dummy flagship: the `VerificationCodeComponent` OTP field gains the
54
+ hidden "Paste code" trigger — paste drives the otp reducer's normalize +
55
+ `$ops` auto-submit end to end, with a real-browser system spec covering
56
+ reveal-on-connect, dirty-paste auto-commit, partial-paste focus, and the
57
+ denied-read no-op.
58
+
11
59
  - **Reducer-emitted client ops (`$ops`), a `submit` op, and declarative
12
60
  `reactive_on_complete` (#226).** The "normalize on input, commit when
13
61
  complete" field (a one-time-code entry being the canonical case) is now
@@ -284,6 +332,53 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
284
332
 
285
333
  ### Fixed
286
334
 
335
+ - **`Component::Action` renamed `ActionDefinition` — it shadowed a host kit
336
+ component named `Action` under dev autoloading (#233).** The mixin sits in
337
+ every reactive component's ancestry, so its bare `Action` constant satisfied
338
+ Phlex::Kit's LazyLoader `const_get` check with the wrong class: the kit's
339
+ real `Action` component never autoloaded and the call fell through to
340
+ `NoMethodError` — intermittently (any unrelated load of the kit class masks
341
+ it until the next reload) and only under lazy autoloading, so an
342
+ eager-loaded test suite stays green. The rename matches its siblings
343
+ (`ComputeDefinition`, `OnCompleteDefinition`, `CollectionDefinition`) and
344
+ removes the constant entirely — **deliberately no alias**, since any
345
+ `Action` constant in the ancestry re-creates the shadowing. Breaking only
346
+ for code referencing the internal
347
+ `Phlex::Reactive::Component::Action` Data class directly; the `action :name`
348
+ DSL and `reactive_actions` registry are unchanged. A regression spec guards
349
+ the whole component ancestry against `:Action` coming back.
350
+
351
+ - **Multipart path corrupted/dropped fields with Rails-bracketed names (#231).**
352
+ `#buildFormData` wrapped every collected field/file name verbatim as
353
+ `params[<name>]`, so `blog_post[summary]` went out as the Rack-unparseable
354
+ `params[blog_post[summary]]` — the scalar arrived as `{"]" => value}` (and a
355
+ writer stringified that hash into the record, with a 200), and a `:file` under
356
+ a bracketed name (`blog_post[image]`) never reached its schema key (silent
357
+ drop). It only fired when a file input was populated — the JSON path expands
358
+ the same names server-side — which is why flat-named forms never caught it.
359
+ The client now bracket-expands the field **name** into wire segments
360
+ (`params[blog_post][summary]`, `params[blog_post][image]`, and
361
+ `params[photos][]` for a `multiple` picker named `photos[]`), mirroring the
362
+ server's `bracket_path` exactly, so a multipart body and a JSON body coerce
363
+ identically for the same fields. Flat names are byte-identical to before.
364
+
365
+ - **Reply-rendered components now generate absolute URLs with the REQUESTING
366
+ host, not the process default (#232).** Actor replies (`reply.replace`,
367
+ `reply.streams(...)`, the implicit replace) render through the memoized
368
+ off-request view context, whose `url_options` were process defaults — so any
369
+ absolute URL helper (`image_tag` on an Active Storage attachment being the
370
+ everyday case) rendered the wrong host on a multi-host app: a broken image
371
+ after every save, healed by the next full page load. The action endpoint now
372
+ threads the request's protocol/host/port into the reply render (the
373
+ `ActiveStorage::SetCurrent` move) via a thread-local the memoized context
374
+ merges per call — the context is never rebuilt, so the render memoization
375
+ win is untouched (allocations byte-identical, throughput flat within noise).
376
+ The defer **pull** endpoint gets the same treatment (it is an actor request).
377
+ **Broadcasts are unchanged by design** — there is no request and subscribers
378
+ can be on different hosts, so a broadcast fired *inside* an action explicitly
379
+ clears the actor's url_options around its render; "URLs in broadcast-rendered
380
+ components must be host-relative" is now documented in the Broadcasting guide.
381
+
287
382
  - **`rake bench` ran the removed `broadcast_replace_to` API and exited 1.**
288
383
  `benchmark/micro/broadcast.rb` migrated to the #185 spellings
289
384
  (`broadcast_to(*key, replace:)` / `broadcast_to(each:, replace:)`); the
data/README.md CHANGED
@@ -385,7 +385,7 @@ Use in controllers: `render turbo_stream: Counter.replace(counter)`.
385
385
  | `busy_on(:save)` | Mark any element so it carries `data-reactive-busy` **only while `save` is in flight** — a spinner styled with pure CSS, zero Ruby. See [Loading states](#declarative-loading-states-loading--disable_with). |
386
386
  | `on(:action, once: true)` | Fire at most once, then unbind (Stimulus's native `:once`). |
387
387
  | `on_client(:click, js.toggle("#menu"))` | **Client-only** trigger: applies declared DOM ops with ZERO round trip — no token, no POST, ever. Takes the same `window:`/`once:`/`outside:` modifiers. See [Client-only ops](#client-only-ops-on_client--js--zero-round-trips). |
388
- | `js` | The immutable op builder behind `on_client`: `show`/`hide`/`toggle` (the `hidden` attribute, with an optional `transition:`), `add_class`/`remove_class`/`toggle_class`, `set_attr`/`remove_attr`/`toggle_attr` (allowlisted names), `focus`/`focus_first`, `text` (set `textContent` — XSS-safe), and `dispatch` — chainable. |
388
+ | `js` | The immutable op builder behind `on_client`: `show`/`hide`/`toggle` (the `hidden` attribute, with an optional `transition:`), `add_class`/`remove_class`/`toggle_class`, `set_attr`/`remove_attr`/`toggle_attr` (allowlisted names), `focus`/`focus_first`, `text` (set `textContent` — XSS-safe), `dispatch`, `submit` (requestSubmit the target's own form), and `paste_into` (read the clipboard into a field, gesture-gated) — chainable. |
389
389
  | `reactive_field(:param, **attrs)` | The attribute hash that binds a control to an action param (no magic `name:`) — spread onto any control: `input(**reactive_field(:value, value: @record.name))`, `select(**reactive_field(:status)) { … }`. |
390
390
  | `reactive_text(:name, initial)` | Mirror a compute output (or a declared input) into a **text node** — a live preview heading, a character counter, `"Hello, {name}"` — via `textContent` (XSS-safe). The text sibling of `reactive_field`; carries no `name`, so it's never POSTed. See [Client-side computes](#client-side-computes-reactive_compute--reactive_text). |
391
391
  | `reactive_show(if:/if_any:/unless:)` | **Value-conditional visibility** (the `x-show`/`data-show` case): spread onto the element to show/hide — it toggles `hidden` from the fields' **current values**, client-only, zero round trip. One conditions language: a **Hash is an AND**, an **Array is membership**, a **Range is a threshold**, `if_any:` is OR-of-AND, `unless:` negates. `reactive_values` computes first paint; `disable:` disables a hidden section's controls. See [Value-conditional visibility](#value-conditional-visibility-reactive_show). |
@@ -458,7 +458,10 @@ a populated `<input type="file">`, the client sends the action as multipart
458
458
  `FormData` (instead of JSON) — `token` + `act` as fields, scalar params as fields,
459
459
  any nested/array params bracket-expanded into `params[key][sub]` /
460
460
  `params[key][index]` fields (the same Rails-form shape, so a JSON body and a
461
- multipart body coerce identically — #39), and the file(s) appended; the endpoint
461
+ multipart body coerce identically — #39), and the file(s) appended. A
462
+ Rails-bracketed field **name** (`blog_post[summary]`, `blog_post[image]`)
463
+ expands the same way — `params[blog_post][summary]`, never the unparseable
464
+ `params[blog_post[summary]]` (#231). The endpoint
462
465
  coerces `:file` to the `ActionDispatch::Http::UploadedFile`, passed through
463
466
  untouched. A non-file value sent to a `:file` param is dropped (the keyword
464
467
  default applies — never a fabricated file). Token threading and the
@@ -822,6 +825,14 @@ field collection); `:root` targets the root element itself; `global: true` on
822
825
  an op escapes to the whole document. An op name the client doesn't recognize
823
826
  logs a warning and is skipped — the rest of the chain still applies.
824
827
 
828
+ In development and test (`verbose_errors`), an op whose selector resolves to
829
+ **zero elements** also warns once per unique (op, selector, scope) — with a
830
+ targeted hint when the element exists but sits outside the op's scope: "use
831
+ `to: :root`" when the selector matches the component's own root (root-scoped
832
+ resolution never includes the root itself), or "use `global: true`" when it
833
+ matches only inside a nested reactive root or elsewhere in the document.
834
+ Production stays silent — a zero-match no-op is legitimate there.
835
+
825
836
  **Attributes, focus, dispatch, and transitions.** Beyond visibility and classes,
826
837
  the same chain covers the rest of the client-only vocabulary:
827
838
 
@@ -863,6 +874,25 @@ button(**on_client(:click, js
863
874
  `broadcast_to(js:)` (a broadcast would force-submit every subscriber's form).
864
875
  Binding a submit op to the `submit` event itself raises at render — it would
865
876
  re-fire itself forever.
877
+ - **`paste_into(to)`** reads the clipboard into a field on a **user gesture**
878
+ (issue #228): `navigator.clipboard.readText()`, then the field gets the text
879
+ through the **normal `input` pipeline** — `.value` is set, a bubbling `input`
880
+ event fires (compute reducers, `reactive_show`, `reactive_on_complete` all run
881
+ exactly as if the user had typed), and the field is focused so a partial paste
882
+ continues from the caret. Built for fields whose real `<input>` is visually
883
+ hidden (an OTP cell UI), where right-click → Paste can't reach the editable
884
+ input. The permission UX is the **browser's own** (Chromium prompts, Safari
885
+ shows its paste pill); a denied/dismissed read, empty text, or a missing API
886
+ is a **silent no-op**. `on_client` marks the trigger with
887
+ `data-reactive-clipboard` and the controller sets `hidden = !available` on
888
+ connect — author the trigger `hidden` and it is revealed only where the
889
+ clipboard API exists, so a dead button never shows. The gate **owns** the
890
+ trigger's `hidden` flag: render the trigger unconditionally and don't also
891
+ bind `reactive_show` to it (the two passes would fight over the same
892
+ attribute). **Actor-only like focus/submit**: refused in `broadcast_to(js:)`
893
+ (a broadcast that reads every subscriber's clipboard would be hostile). The
894
+ op is async fire-and-forget — chained siblings apply immediately, never
895
+ waiting for the read.
866
896
  - **`transition: { during:, from:, to: }`** on `show`/`hide`/`toggle` animates the
867
897
  visibility flip: `during`+`from` are applied, then `from`→`to` swaps on the next
868
898
  frame, and the helper classes are cleaned up on `animationend` (with a timeout
@@ -1136,7 +1166,9 @@ setComputeReducer("preview", ({ title }) => ({
1136
1166
  write fields and text; the reserved **`$ops`** key lets it emit a **conditional
1137
1167
  side effect** — the missing piece that used to force a bespoke controller next
1138
1168
  to an otherwise-declarative compute. Return an op chain (the `ops` builder
1139
- mirrors the Ruby `js` verbs, or use a raw `[[op, args], …]` array) and the
1169
+ mirrors the Ruby `js` verbs minus `paste_into`, deliberately: a reducer runs
1170
+ on every input event, and a clipboard read per keystroke would spam permission
1171
+ prompts — or use a raw `[[op, args], …]` array) and the
1140
1172
  controller runs it through the **same frozen op whitelist** `on_client` uses,
1141
1173
  as a final phase **after** the field writes, text sinks, and their dispatched
1142
1174
  `input` events settle. The canonical one-time-code field:
@@ -1977,10 +2009,12 @@ Notifications::Badge.broadcast_to(user, :alerts,
1977
2009
  js: js.add_class("#bell", "has-unread"), exclude: reactive_connection_id)
1978
2010
  ```
1979
2011
 
1980
- `broadcast_to` with `js:` **refuses focus-class ops** (`focus`/`focus_first` raise
1981
- `ArgumentError`): broadcasting focus would steal it in every subscriber's tab, so
1982
- focus is an actor-reply concern only. Everything else is a fair broadcast (class
1983
- and attribute toggles, `dispatch`). As with `on_client`, the ops are
2012
+ `broadcast_to` with `js:` **refuses the actor-only ops** (`focus`/`focus_first`/
2013
+ `submit`/`paste_into` raise `ArgumentError`): broadcasting focus would steal it
2014
+ in every subscriber's tab, a broadcast submit would force-submit every
2015
+ subscriber's form, and a broadcast clipboard read would be hostile these
2016
+ belong to the actor's own reply or gesture. Everything else is a fair broadcast
2017
+ (class and attribute toggles, `text`, `dispatch`). As with `on_client`, the ops are
1984
2018
  whitelist-interpreted client-side — an unknown op warns and is skipped — and the
1985
2019
  ops attribute is HTML-escaped, so a value can't break out of it. `reactive:js`
1986
2020
  is not a self-render: it never counts toward the token refresh, so the reply's
@@ -2577,6 +2611,19 @@ helper — a child row rendering a trigger for its container's action) is skippe
2577
2611
  `on_client` triggers are never checked (they aren't declared actions). The
2578
2612
  server's default-deny stays the security boundary — this is a dev-time courtesy.
2579
2613
 
2614
+ The flag reaches the **client** too: reactive roots (and server-emitted
2615
+ `reactive:js` streams) carry `data-reactive-verbose="true"` when it is on, and
2616
+ the client op interpreter uses that gate to `console.warn` when a client op —
2617
+ `on_client`, a reducer's `$ops`, `reply…js(...)`, `broadcast_js_to`, or a
2618
+ busy/optimistic hint — resolves **zero targets**. The warning names the op, the
2619
+ selector, and the scoping root, dedupes per unique case per page, and hints the
2620
+ documented escape (`to: :root` / `global: true`) when the element exists but
2621
+ was filtered out by root scoping or the nested-root ownership rule — the trap
2622
+ where the element is right there in the DOM, just outside the op's scope. A
2623
+ `reactive:js` stream whose `target` root id has left the DOM warns the same
2624
+ way instead of silently dropping its ops. Production (flag off) renders no
2625
+ attribute and stays byte-identical and silent.
2626
+
2580
2627
  See [docs/security.md](https://phlex-reactive.zoolutions.llc/docs/security) for the threat model and a checklist.
2581
2628
 
2582
2629
  ---
@@ -39,9 +39,17 @@ module Phlex
39
39
  # #165 security), so the defer endpoint accepts them ONLY back from this
40
40
  # same actor. The defer token is built in response_streams (inside this
41
41
  # block via the Defer builder), so the binding must be established here.
42
- Phlex::Reactive.with_defer_binding(Phlex::Reactive.defer_binding_for(request)) do
43
- Phlex::Reactive.instrument("action", event) do
44
- create_action(event)
42
+ # Thread the ACTOR's url_options (protocol/host/port) into the reply
43
+ # render (issue #232): the reply renders through the memoized
44
+ # off-request view context, whose process-default url_options emit the
45
+ # wrong host for absolute URL helpers on a multi-host app. Broadcasts
46
+ # fired inside the action are exempted at their render (see
47
+ # Streamable.broadcast_component) — subscribers can be on other hosts.
48
+ Phlex::Reactive.with_url_options(Phlex::Reactive.url_options_for(request)) do
49
+ Phlex::Reactive.with_defer_binding(Phlex::Reactive.defer_binding_for(request)) do
50
+ Phlex::Reactive.instrument("action", event) do
51
+ create_action(event)
52
+ end
45
53
  end
46
54
  end
47
55
  end
@@ -62,9 +70,14 @@ module Phlex
62
70
  # so a leaked token can't be exchanged here for this actor's render (and
63
71
  # its embedded fresh identity token). Unbound requests (no session) are
64
72
  # unchanged.
65
- Phlex::Reactive.with_defer_binding(Phlex::Reactive.defer_binding_for(request)) do
66
- Phlex::Reactive.instrument("defer", event) do
67
- deferred_action(event)
73
+ # The defer PULL is an actor request too (issue #232) — its render gets
74
+ # the same request-derived url_options as the action reply. The PUSH
75
+ # lane (job → SSE) has no request and stays on process defaults.
76
+ Phlex::Reactive.with_url_options(Phlex::Reactive.url_options_for(request)) do
77
+ Phlex::Reactive.with_defer_binding(Phlex::Reactive.defer_binding_for(request)) do
78
+ Phlex::Reactive.instrument("defer", event) do
79
+ deferred_action(event)
80
+ end
68
81
  end
69
82
  end
70
83
  end
@@ -103,6 +103,11 @@
103
103
  // event fires, so an on(:verify, event: "submit") interception (or a native/
104
104
  // Turbo form) handles it exactly like a user submit. submit/focus are
105
105
  // ACTOR-ONLY: usable here and in on_client/reply.js, refused in broadcasts.
106
+ // paste_into (issue #228) is actor-only too but DELIBERATELY absent from this
107
+ // builder: a reducer runs on every input event, and a clipboard read per
108
+ // keystroke (each changed chain fires) would spam permission prompts. Use it
109
+ // from on_client / reply.js / reactive_on_complete instead; a raw
110
+ // [["paste_into", …]] pair still interprets if you truly need it.
106
111
 
107
112
  const reducers = new Map()
108
113
 
@@ -127,7 +132,8 @@ export function __resetComputeRegistryForTest() {
127
132
 
128
133
  // --- The reducer-side op-chain builder (issue #226) --------------------------
129
134
  //
130
- // A thin, IMMUTABLE mirror of the Ruby Phlex::Reactive::JS builder: verbs carry
135
+ // A thin, IMMUTABLE mirror of the Ruby Phlex::Reactive::JS builder (minus
136
+ // paste_into — see the actor-only note above): verbs carry
131
137
  // the WIRE op names (snake_case) and append [name, args] pairs; every verb
132
138
  // returns a NEW instance, so a chain held in a constant can never be mutated by
133
139
  // later use. `.ops` exposes the raw [[name, args], ...] list the controller
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["compute.js"],
4
4
  "sourcesContent": [
5
- "// The client-side compute (data-binding) registry — the \"instant\" half of the\n// new/unpersisted-record UX.\n//\n// A record-backed reactive component round-trips every change to the server\n// (the signed identity re-finds the record; the server re-renders). A NEW,\n// unpersisted record has no such server truth to re-render against on every\n// keystroke — the classic answer is a bespoke Stimulus controller doing the math\n// in the browser (carlqvist's new_order_controller.js). This registry lets that\n// math be a DECLARED part of the component instead: `reactive_compute :name,\n// inputs:, outputs:` (Ruby) names a reducer registered here, and the generic\n// reactive controller runs it on `input` — writing the outputs with NO round\n// trip. When the component ALSO carries on(...) (a persisted record, or a draft\n// you sync), the debounced POST reconciles from the authoritative server reply.\n//\n// The seam mirrors confirm.js: a settable registry with a lookup the controller\n// calls. Register once at boot:\n//\n// import { setComputeReducer } from \"phlex/reactive/compute\"\n// setComputeReducer(\"payment_split\", ({ allowance, cash, leasing, total }) => ({\n// allowance, leasing, cash: total - allowance - leasing,\n// }))\n//\n// The reducer's signature is (values, meta):\n//\n// values — a plain object of { inputName: value } over the declared inputs.\n// Untyped inputs (the array form) AND :number-typed inputs arrive as\n// Numbers (blank/NaN → 0); :string-typed inputs (the hash form,\n// issue #104) arrive as the RAW string. `reactive_compute :x, inputs:\n// { title: :string, qty: :number }` is what selects per-input types;\n// `inputs: %i[a b]` stays all-numeric (backward compatible).\n// meta — { changed }: the name (string) of the declared input the\n// triggering event edited, or null (a direct recompute() call, or a\n// target this root doesn't own / didn't declare as an input).\n//\n// OUTPUTS may be a form FIELD or a TEXT NODE (issue #104). An output whose name\n// matches an owned control writes its .value (+ the change-guarded input\n// dispatch below). An output with NO matching field writes textContent to every\n// owned [data-reactive-text=\"<name>\"] node (reactive_text(:name)) — XSS-safe by\n// construction, change-guarded, NO input dispatch (a text node has no listener\n// contract). A declared INPUT also mirrors into its own text node on every\n// input via an always-run pass — so reactive_text(:title) is a live field\n// preview with NO registered reducer at all.\n//\n// It returns a plain object of { outputName: value } — only the outputs it\n// names are written, so it can leave the edited field (and its caret)\n// untouched. A one-argument reducer keeps working unchanged (it just ignores\n// meta). `changed` is what makes a MULTI-WAY / MUTUAL rebalance expressible as\n// one reducer (issue #75) — branch on which field the user edited:\n//\n// setComputeReducer(\"three_way_split\", ({ field_a, field_b, field_c, total }, { changed }) => {\n// if (changed === \"field_c\") return { field_a: total - field_c - field_b }\n// return { field_c: total - field_a - field_b }\n// })\n//\n// Output writes are CHANGE-GUARDED: the controller writes a field and\n// dispatches a bubbling `input` event on it ONLY when the new value differs\n// from the field's current value (real browsers never fire `input` on a\n// programmatic .value write, so the controller dispatches explicitly — that's\n// what drives a chained summary repaint, matching the server's set_value +\n// dispatch(\"input\") contract). Returning the SAME value a field already holds\n// is skipped entirely — no write, no event — which is why a reducer with\n// overlapping inputs/outputs (like payment_split above) settles instead of\n// re-entering itself forever.\n//\n// CONVERGENCE REQUIREMENT: because an output write dispatches a REAL input\n// event (issue #76), recompute re-enters with changed = that OUTPUT field's\n// name (when it's also a declared input). A branching reducer must therefore\n// be convergent: the re-entrant pass must compute values EQUAL to what the\n// first pass already wrote to the DOM, so the change guard settles the chain.\n// The three_way_split above is: after `changed === \"field_a\"` writes field_c,\n// the re-entrant `changed === \"field_c\"` pass derives field_a back to the\n// value it already holds — no write, no event, settled in one bounce.\n\n// THE RESERVED `$ops` OUTPUT (issue #226). Besides field/text outputs, a\n// reducer may return the reserved key `$ops` holding a chain of client DOM\n// ops — built with the `ops` builder below, or a raw [[name, args], ...]\n// array. The controller consumes it as a PHASE 4 of the single-pass write set:\n// the ops run AFTER the field writes, text sinks, and phase-3 input dispatches\n// settle, through the SAME frozen CLIENT_OPS whitelist on_client uses (an\n// unknown op warns + is skipped). `null`/`undefined`/absent = no effect.\n//\n// RISING-EDGE semantics, keyed on CONTENT: the chain runs only when it\n// DIFFERS from the previous pass's chain (including from \"absent\"), and only\n// on EVENT-DRIVEN passes. Returning the SAME chain again is settled — no\n// re-fire (a 7th keystroke capped back to the same complete value can't\n// re-submit), mirroring the change-guarded field writes. Returning a\n// DIFFERENT chain fires again — a multi-box reducer advancing focus\n// box-by-box emits a new focus target per digit, each a new intent. The\n// connect/morph SEED pass (issue #199 — recompute with no event) ARMS the\n// latch but never fires — a form re-rendered with an already-complete value\n// (a validation-error morph, a browser restore) must not auto-fire; that is\n// what breaks the submit → error re-render → re-seed → submit loop. A later\n// pass returning no $ops re-arms. The canonical use — a one-time-code field\n// that normalizes on input and commits when complete:\n//\n// import { setComputeReducer, ops } from \"phlex/reactive/compute\"\n// setComputeReducer(\"otp\", ({ code }) => {\n// const digits = code.replace(/\\D/g, \"\").slice(0, 6)\n// return { code: digits, $ops: digits.length === 6 ? ops.submit() : null }\n// })\n//\n// `submit` commits the target's own form via requestSubmit() — the real submit\n// event fires, so an on(:verify, event: \"submit\") interception (or a native/\n// Turbo form) handles it exactly like a user submit. submit/focus are\n// ACTOR-ONLY: usable here and in on_client/reply.js, refused in broadcasts.\n\nconst reducers = new Map()\n\n// Register (or replace) the reducer for `key`. `fn` is\n// (values: Record<string, number>, meta: { changed: string | null })\n// => Record<string, unknown>.\nexport function setComputeReducer(key, fn) {\n reducers.set(key, fn)\n}\n\n// Look up a registered reducer; undefined when none — the controller then makes\n// #recompute a no-op rather than throwing (a missing reducer must not break the\n// page; it just means no client-side binding for that root).\nexport function computeReducer(key) {\n return reducers.get(key)\n}\n\n// Test seam: clear the registry so a reducer registered in one test can't leak.\nexport function __resetComputeRegistryForTest() {\n reducers.clear()\n}\n\n// --- The reducer-side op-chain builder (issue #226) --------------------------\n//\n// A thin, IMMUTABLE mirror of the Ruby Phlex::Reactive::JS builder: verbs carry\n// the WIRE op names (snake_case) and append [name, args] pairs; every verb\n// returns a NEW instance, so a chain held in a constant can never be mutated by\n// later use. `.ops` exposes the raw [[name, args], ...] list the controller\n// interprets (and toJSON serializes it, so a chain can also feed a hand-built\n// ops attr). Targets: a CSS selector string, or omit for \"@root\" (the\n// component's own root). No build-time attr validation here — the interpreter's\n// allowlist (guardAttr) is the enforcement point; this builder only shapes the\n// wire.\nconst ROOT_SENTINEL = \"@root\"\n\nfunction targetArgs(to, { global, transition } = {}) {\n const args = { to: to ?? ROOT_SENTINEL }\n if (global) args.global = true\n if (transition) args.transition = normalizeTransition(transition)\n return args\n}\n\n// Named legs { during, from, to } → the [during, from, to] wire array (the\n// issue #186 vocabulary). Loud at authoring time, like the Ruby builder.\n// Frozen, like every nested payload — the chain's immutability contract must\n// hold all the way down (the Ruby twin freezes its legs array too).\nfunction normalizeTransition(transition) {\n const named =\n transition && typeof transition === \"object\" && !Array.isArray(transition) &&\n [\"during\", \"from\", \"to\"].every((k) => k in transition)\n if (!named) throw new Error(\"[phlex-reactive] ops transition takes named legs { during, from, to }\")\n return Object.freeze([String(transition.during), String(transition.from), String(transition.to)])\n}\n\nclass OpsChain {\n constructor(list = Object.freeze([])) {\n this.ops = list\n Object.freeze(this)\n }\n\n show(to, opts) {\n return this.#append(\"show\", targetArgs(to, opts))\n }\n\n hide(to, opts) {\n return this.#append(\"hide\", targetArgs(to, opts))\n }\n\n toggle(to, opts) {\n return this.#append(\"toggle\", targetArgs(to, opts))\n }\n\n add_class(to, classes, opts) {\n return this.#append(\"add_class\", classArgs(to, classes, opts))\n }\n\n remove_class(to, classes, opts) {\n return this.#append(\"remove_class\", classArgs(to, classes, opts))\n }\n\n toggle_class(to, classes, opts) {\n return this.#append(\"toggle_class\", classArgs(to, classes, opts))\n }\n\n set_attr(to, name, value, opts) {\n return this.#append(\"set_attr\", { ...targetArgs(to, opts), name: String(name), value: String(value) })\n }\n\n remove_attr(to, name, opts) {\n return this.#append(\"remove_attr\", { ...targetArgs(to, opts), name: String(name) })\n }\n\n toggle_attr(to, name, opts) {\n return this.#append(\"toggle_attr\", { ...targetArgs(to, opts), name: String(name) })\n }\n\n focus(to, opts) {\n return this.#append(\"focus\", targetArgs(to, opts))\n }\n\n focus_first(to, opts) {\n return this.#append(\"focus_first\", targetArgs(to, opts))\n }\n\n text(to, value, opts) {\n return this.#append(\"text\", { ...targetArgs(to, opts), value: String(value ?? \"\") })\n }\n\n dispatch(name, { to, detail, global } = {}) {\n const args = { name: String(name), to: to ?? ROOT_SENTINEL, detail: detail ?? {} }\n if (global) args.global = true\n return this.#append(\"dispatch\", args)\n }\n\n submit(to, opts) {\n return this.#append(\"submit\", targetArgs(to, opts))\n }\n\n toJSON() {\n return this.ops\n }\n\n #append(name, args) {\n return new OpsChain(Object.freeze([...this.ops, Object.freeze([name, Object.freeze(args)])]))\n }\n}\n\n// classes: one class string or an array of them (never whitespace-split — a\n// classList token can't contain spaces, so splitting would only mask a bug).\n// Loud on an empty/missing list, and frozen (the Ruby twin freezes its class\n// list too) — a chain held in a constant must stay immutable all the way down.\nfunction classArgs(to, classes, opts) {\n const list = classes == null ? [] : (Array.isArray(classes) ? classes : [classes]).map(String)\n if (list.length === 0) throw new Error(\"[phlex-reactive] a class op needs at least one class\")\n return { ...targetArgs(to, opts), classes: Object.freeze(list) }\n}\n\n// The shared empty chain — start every reducer effect from here:\n// $ops: done ? ops.dispatch(\"code:complete\").submit() : null\nexport const ops = new OpsChain()\n"
5
+ "// The client-side compute (data-binding) registry — the \"instant\" half of the\n// new/unpersisted-record UX.\n//\n// A record-backed reactive component round-trips every change to the server\n// (the signed identity re-finds the record; the server re-renders). A NEW,\n// unpersisted record has no such server truth to re-render against on every\n// keystroke — the classic answer is a bespoke Stimulus controller doing the math\n// in the browser (carlqvist's new_order_controller.js). This registry lets that\n// math be a DECLARED part of the component instead: `reactive_compute :name,\n// inputs:, outputs:` (Ruby) names a reducer registered here, and the generic\n// reactive controller runs it on `input` — writing the outputs with NO round\n// trip. When the component ALSO carries on(...) (a persisted record, or a draft\n// you sync), the debounced POST reconciles from the authoritative server reply.\n//\n// The seam mirrors confirm.js: a settable registry with a lookup the controller\n// calls. Register once at boot:\n//\n// import { setComputeReducer } from \"phlex/reactive/compute\"\n// setComputeReducer(\"payment_split\", ({ allowance, cash, leasing, total }) => ({\n// allowance, leasing, cash: total - allowance - leasing,\n// }))\n//\n// The reducer's signature is (values, meta):\n//\n// values — a plain object of { inputName: value } over the declared inputs.\n// Untyped inputs (the array form) AND :number-typed inputs arrive as\n// Numbers (blank/NaN → 0); :string-typed inputs (the hash form,\n// issue #104) arrive as the RAW string. `reactive_compute :x, inputs:\n// { title: :string, qty: :number }` is what selects per-input types;\n// `inputs: %i[a b]` stays all-numeric (backward compatible).\n// meta — { changed }: the name (string) of the declared input the\n// triggering event edited, or null (a direct recompute() call, or a\n// target this root doesn't own / didn't declare as an input).\n//\n// OUTPUTS may be a form FIELD or a TEXT NODE (issue #104). An output whose name\n// matches an owned control writes its .value (+ the change-guarded input\n// dispatch below). An output with NO matching field writes textContent to every\n// owned [data-reactive-text=\"<name>\"] node (reactive_text(:name)) — XSS-safe by\n// construction, change-guarded, NO input dispatch (a text node has no listener\n// contract). A declared INPUT also mirrors into its own text node on every\n// input via an always-run pass — so reactive_text(:title) is a live field\n// preview with NO registered reducer at all.\n//\n// It returns a plain object of { outputName: value } — only the outputs it\n// names are written, so it can leave the edited field (and its caret)\n// untouched. A one-argument reducer keeps working unchanged (it just ignores\n// meta). `changed` is what makes a MULTI-WAY / MUTUAL rebalance expressible as\n// one reducer (issue #75) — branch on which field the user edited:\n//\n// setComputeReducer(\"three_way_split\", ({ field_a, field_b, field_c, total }, { changed }) => {\n// if (changed === \"field_c\") return { field_a: total - field_c - field_b }\n// return { field_c: total - field_a - field_b }\n// })\n//\n// Output writes are CHANGE-GUARDED: the controller writes a field and\n// dispatches a bubbling `input` event on it ONLY when the new value differs\n// from the field's current value (real browsers never fire `input` on a\n// programmatic .value write, so the controller dispatches explicitly — that's\n// what drives a chained summary repaint, matching the server's set_value +\n// dispatch(\"input\") contract). Returning the SAME value a field already holds\n// is skipped entirely — no write, no event — which is why a reducer with\n// overlapping inputs/outputs (like payment_split above) settles instead of\n// re-entering itself forever.\n//\n// CONVERGENCE REQUIREMENT: because an output write dispatches a REAL input\n// event (issue #76), recompute re-enters with changed = that OUTPUT field's\n// name (when it's also a declared input). A branching reducer must therefore\n// be convergent: the re-entrant pass must compute values EQUAL to what the\n// first pass already wrote to the DOM, so the change guard settles the chain.\n// The three_way_split above is: after `changed === \"field_a\"` writes field_c,\n// the re-entrant `changed === \"field_c\"` pass derives field_a back to the\n// value it already holds — no write, no event, settled in one bounce.\n\n// THE RESERVED `$ops` OUTPUT (issue #226). Besides field/text outputs, a\n// reducer may return the reserved key `$ops` holding a chain of client DOM\n// ops — built with the `ops` builder below, or a raw [[name, args], ...]\n// array. The controller consumes it as a PHASE 4 of the single-pass write set:\n// the ops run AFTER the field writes, text sinks, and phase-3 input dispatches\n// settle, through the SAME frozen CLIENT_OPS whitelist on_client uses (an\n// unknown op warns + is skipped). `null`/`undefined`/absent = no effect.\n//\n// RISING-EDGE semantics, keyed on CONTENT: the chain runs only when it\n// DIFFERS from the previous pass's chain (including from \"absent\"), and only\n// on EVENT-DRIVEN passes. Returning the SAME chain again is settled — no\n// re-fire (a 7th keystroke capped back to the same complete value can't\n// re-submit), mirroring the change-guarded field writes. Returning a\n// DIFFERENT chain fires again — a multi-box reducer advancing focus\n// box-by-box emits a new focus target per digit, each a new intent. The\n// connect/morph SEED pass (issue #199 — recompute with no event) ARMS the\n// latch but never fires — a form re-rendered with an already-complete value\n// (a validation-error morph, a browser restore) must not auto-fire; that is\n// what breaks the submit → error re-render → re-seed → submit loop. A later\n// pass returning no $ops re-arms. The canonical use — a one-time-code field\n// that normalizes on input and commits when complete:\n//\n// import { setComputeReducer, ops } from \"phlex/reactive/compute\"\n// setComputeReducer(\"otp\", ({ code }) => {\n// const digits = code.replace(/\\D/g, \"\").slice(0, 6)\n// return { code: digits, $ops: digits.length === 6 ? ops.submit() : null }\n// })\n//\n// `submit` commits the target's own form via requestSubmit() — the real submit\n// event fires, so an on(:verify, event: \"submit\") interception (or a native/\n// Turbo form) handles it exactly like a user submit. submit/focus are\n// ACTOR-ONLY: usable here and in on_client/reply.js, refused in broadcasts.\n// paste_into (issue #228) is actor-only too but DELIBERATELY absent from this\n// builder: a reducer runs on every input event, and a clipboard read per\n// keystroke (each changed chain fires) would spam permission prompts. Use it\n// from on_client / reply.js / reactive_on_complete instead; a raw\n// [[\"paste_into\", …]] pair still interprets if you truly need it.\n\nconst reducers = new Map()\n\n// Register (or replace) the reducer for `key`. `fn` is\n// (values: Record<string, number>, meta: { changed: string | null })\n// => Record<string, unknown>.\nexport function setComputeReducer(key, fn) {\n reducers.set(key, fn)\n}\n\n// Look up a registered reducer; undefined when none — the controller then makes\n// #recompute a no-op rather than throwing (a missing reducer must not break the\n// page; it just means no client-side binding for that root).\nexport function computeReducer(key) {\n return reducers.get(key)\n}\n\n// Test seam: clear the registry so a reducer registered in one test can't leak.\nexport function __resetComputeRegistryForTest() {\n reducers.clear()\n}\n\n// --- The reducer-side op-chain builder (issue #226) --------------------------\n//\n// A thin, IMMUTABLE mirror of the Ruby Phlex::Reactive::JS builder (minus\n// paste_into — see the actor-only note above): verbs carry\n// the WIRE op names (snake_case) and append [name, args] pairs; every verb\n// returns a NEW instance, so a chain held in a constant can never be mutated by\n// later use. `.ops` exposes the raw [[name, args], ...] list the controller\n// interprets (and toJSON serializes it, so a chain can also feed a hand-built\n// ops attr). Targets: a CSS selector string, or omit for \"@root\" (the\n// component's own root). No build-time attr validation here — the interpreter's\n// allowlist (guardAttr) is the enforcement point; this builder only shapes the\n// wire.\nconst ROOT_SENTINEL = \"@root\"\n\nfunction targetArgs(to, { global, transition } = {}) {\n const args = { to: to ?? ROOT_SENTINEL }\n if (global) args.global = true\n if (transition) args.transition = normalizeTransition(transition)\n return args\n}\n\n// Named legs { during, from, to } → the [during, from, to] wire array (the\n// issue #186 vocabulary). Loud at authoring time, like the Ruby builder.\n// Frozen, like every nested payload — the chain's immutability contract must\n// hold all the way down (the Ruby twin freezes its legs array too).\nfunction normalizeTransition(transition) {\n const named =\n transition && typeof transition === \"object\" && !Array.isArray(transition) &&\n [\"during\", \"from\", \"to\"].every((k) => k in transition)\n if (!named) throw new Error(\"[phlex-reactive] ops transition takes named legs { during, from, to }\")\n return Object.freeze([String(transition.during), String(transition.from), String(transition.to)])\n}\n\nclass OpsChain {\n constructor(list = Object.freeze([])) {\n this.ops = list\n Object.freeze(this)\n }\n\n show(to, opts) {\n return this.#append(\"show\", targetArgs(to, opts))\n }\n\n hide(to, opts) {\n return this.#append(\"hide\", targetArgs(to, opts))\n }\n\n toggle(to, opts) {\n return this.#append(\"toggle\", targetArgs(to, opts))\n }\n\n add_class(to, classes, opts) {\n return this.#append(\"add_class\", classArgs(to, classes, opts))\n }\n\n remove_class(to, classes, opts) {\n return this.#append(\"remove_class\", classArgs(to, classes, opts))\n }\n\n toggle_class(to, classes, opts) {\n return this.#append(\"toggle_class\", classArgs(to, classes, opts))\n }\n\n set_attr(to, name, value, opts) {\n return this.#append(\"set_attr\", { ...targetArgs(to, opts), name: String(name), value: String(value) })\n }\n\n remove_attr(to, name, opts) {\n return this.#append(\"remove_attr\", { ...targetArgs(to, opts), name: String(name) })\n }\n\n toggle_attr(to, name, opts) {\n return this.#append(\"toggle_attr\", { ...targetArgs(to, opts), name: String(name) })\n }\n\n focus(to, opts) {\n return this.#append(\"focus\", targetArgs(to, opts))\n }\n\n focus_first(to, opts) {\n return this.#append(\"focus_first\", targetArgs(to, opts))\n }\n\n text(to, value, opts) {\n return this.#append(\"text\", { ...targetArgs(to, opts), value: String(value ?? \"\") })\n }\n\n dispatch(name, { to, detail, global } = {}) {\n const args = { name: String(name), to: to ?? ROOT_SENTINEL, detail: detail ?? {} }\n if (global) args.global = true\n return this.#append(\"dispatch\", args)\n }\n\n submit(to, opts) {\n return this.#append(\"submit\", targetArgs(to, opts))\n }\n\n toJSON() {\n return this.ops\n }\n\n #append(name, args) {\n return new OpsChain(Object.freeze([...this.ops, Object.freeze([name, Object.freeze(args)])]))\n }\n}\n\n// classes: one class string or an array of them (never whitespace-split — a\n// classList token can't contain spaces, so splitting would only mask a bug).\n// Loud on an empty/missing list, and frozen (the Ruby twin freezes its class\n// list too) — a chain held in a constant must stay immutable all the way down.\nfunction classArgs(to, classes, opts) {\n const list = classes == null ? [] : (Array.isArray(classes) ? classes : [classes]).map(String)\n if (list.length === 0) throw new Error(\"[phlex-reactive] a class op needs at least one class\")\n return { ...targetArgs(to, opts), classes: Object.freeze(list) }\n}\n\n// The shared empty chain — start every reducer effect from here:\n// $ops: done ? ops.dispatch(\"code:complete\").submit() : null\nexport const ops = new OpsChain()\n"
6
6
  ],
7
- "mappings": "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",
7
+ "mappings": "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",
8
8
  "debugId": "3CB48262B60C70DD64756E2164756E21",
9
9
  "names": []
10
10
  }