phlex-reactive 0.13.3 → 0.13.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: 6f070adcfcb6e3061829d42551e50803c209bd895c10a612d981b0359d40f59a
4
- data.tar.gz: 3ff24e3f7c13b1aee4cf66f9187cced2ecad0cf98a69ec1a536d9eb452a86b3b
3
+ metadata.gz: ce768022637774ec42b7bca32e6533613fb563c792d9c82f58c8b07eea722e7f
4
+ data.tar.gz: 42a470435e59c6a364f3845a4626bdbb0b8e23a1cc7aadcb0c2281b89c922555
5
5
  SHA512:
6
- metadata.gz: 7166c081bba35822dd588ead985237cbee23f11dcb6ef62ffaecfb483626eb007cc8613824896c0916253bdaea034c1b0794b64adf9f73c6e30fa462f5075a15
7
- data.tar.gz: f9a8c4b4b09eec913bd4f9d0085620503e43e7d7beb5a2cb190dfb1e7fd3f23c702c6ee768fd2bb8304a76ffbd038b4bde4827e92c04f0f1a754c7fcf611c421
6
+ metadata.gz: d2f238f055aabaf9466212ab07158ae2ba40139207b77b118b12fcafb18ca43ad14a2f48b82ea666345375e19afd499bba456c9bec4e978b12875a560f215637
7
+ data.tar.gz: 19105c1e86bec4015e9be48f42b4580c0adf6fa70fa1a00d6b5e4c2c5e9a3f5f297095b4fbaf9dcca3a49a6b6c254ef1070998b7f895e4bf6de085d5d152e66d
data/CHANGELOG.md CHANGED
@@ -9,6 +9,41 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
9
 
10
10
  ### Added
11
11
 
12
+ - **Several `on_client` bindings on one element (#271).** Two `on_client`
13
+ calls merged with `mix` now both work — e.g. a root that closes a menu on an
14
+ outside click AND on Escape. Each call carries its own descriptor, flags and
15
+ `confirm:`, and the client runs only the binding whose event (type, key
16
+ filter, window-boundness) fired. An outside guard or confirm never leaks onto
17
+ a sibling binding.
18
+
19
+ - **`toggle_attr(to, name, on, off)` — flip between two values (#271).**
20
+ `js.toggle_attr("#trigger", "aria-expanded", "true", "false")` keeps a
21
+ two-valued attribute (`aria-expanded`, `aria-pressed`, `data-state`) honest;
22
+ an absent attribute becomes `on`. One value, or two equal values, raise at
23
+ build time. Without values it is the presence toggle it always was.
24
+
25
+ - **`expanded:` on `show`/`hide`/`toggle` (#271).** `js.toggle("#menu",
26
+ expanded: "#trigger")` writes the op's intended state into the trigger's
27
+ `aria-expanded` (`show` → `"true"`, `hide` → `"false"`, `toggle` → the
28
+ state after the flip: `"true"` when the target was hidden), before any
29
+ transition. Resolved with the op's own scoping.
30
+
31
+ - **`reactive_listnav(focus: true)` — roving focus for menus (#271).** Spread
32
+ on a `role="menu"` container: Arrow Down/Up move real focus among
33
+ `[role=menuitem]` (wrapping), Home/End jump to the edges. Enter/Space stay
34
+ native activation; Escape is the caller's `on_client("keydown.esc", …)`.
35
+ Without `focus:` the helper is byte-identical to before.
36
+
37
+ - **`pending_template` — opt-in pending MARKUP for `reply.pending` (#249).** A
38
+ row component that defines `pending_template` now has its markup swapped by
39
+ `reply.pending` (a real "Queued" badge, a genuinely removed button), in
40
+ addition to the #248 markers. The swap renders a memoized anonymous subclass
41
+ whose `.name` is the row's own, so a row that signs its token still verifies
42
+ on its next action; the memo resets on Rails code reload. A template that
43
+ drops the row's `id` raises before anything is enqueued. Rows without the
44
+ hook are byte-identical to before. The swapped markup is undone only by a
45
+ settle that replaces or removes the row.
46
+
12
47
  - **`bin/release` — the release front door, ported from pgbus.** Works out the
13
48
  next version (`patch` by default, `minor`, `major`, or an explicit `X.Y.Z`
14
49
  with an optional `v`), prints the commits since the last tag, and hands off
@@ -757,6 +792,18 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
757
792
 
758
793
  ### Changed
759
794
 
795
+ - **`on_client` emits a binding record (#271).** `data-reactive-ops-param` now
796
+ holds `{"on":…,"ops":[…], "window"?, "outside"?, "confirm"?, "confirmWhen"?}`
797
+ instead of the bare `[[op, args]]` list, with every space written as
798
+ `\u0020`. `on_client` no longer writes the element-wide
799
+ `data-reactive-outside-param` / `-window-param` / `-confirm-param` /
800
+ `-confirm-when-param` (`on(...)` still does). A hand-built legacy
801
+ `[[op, args]]` attribute still runs, reading its flags from those params as
802
+ before. **Upgrade note:** apps served by the engine's importmap pin get the
803
+ matching client automatically; an app that bundles its own copy of
804
+ `reactive_controller.js` must rebuild from the shipped source, or its
805
+ `on_client` triggers go dead (the old client parses only arrays).
806
+
760
807
  - **Client build toolchain: bun 1.3.14 → 1.4.0.** `.bun-version`, the root `engines.bun`
761
808
  floor, and the docs `packageManager` pin all move together. The shipped
762
809
  `*.min.js` / `.map` artifacts (and their vendored twins under
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), `dispatch`, `submit` (requestSubmit the target's own form), `paste_into` (read the clipboard into a field, gesture-gated), and `persist_state`/`persist_clear` (the `reactive_persist` draft) — chainable. |
388
+ | `js` | The immutable op builder behind `on_client`: `show`/`hide`/`toggle` (the `hidden` attribute, with an optional `transition:` and `expanded:`), `add_class`/`remove_class`/`toggle_class`, `set_attr`/`remove_attr`/`toggle_attr` (allowlisted names; `toggle_attr` also flips between two values), `focus`/`focus_first`, `text` (set `textContent` — XSS-safe), `dispatch`, `submit` (requestSubmit the target's own form), `paste_into` (read the clipboard into a field, gesture-gated), and `persist_state`/`persist_clear` (the `reactive_persist` draft) — 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). |
@@ -393,7 +393,7 @@ Use in controllers: `render turbo_stream: Counter.replace(counter)`.
393
393
  | `reactive_persist(key:, ttl: 7.days)` | **Client-only drafts**: spread on the **root** (once) and the generic controller keeps a `localStorage` draft of every **owned** control — debounced write on `input`, immediate on `change`, flushed on disconnect, restored into **blank** controls on the next connect (`restore: :always` lets the draft win), cleared by a successful Turbo submit / `ttl` / `js.persist_clear`. Never hidden/file/password; `reactive_persist_skip` opts a control out; `fields:` narrows. See [Client-only drafts](#client-only-drafts-reactive_persist). |
394
394
  | `js.persist_state(step: 2)` / `js.persist_clear` | The draft ops (actor-only): merge a flat state bag into the draft (restored as `data-reactive-persist-state` + the `reactive:persist-restored` event) / forget the draft. |
395
395
  | `reactive_filter(:field, option: nil, group: nil, empty: nil)` | **Client-side option filtering** for a preloaded combobox: spread onto the root and name the **field** that drives it — `reactive_filter(:q)` compiles `:q` to `[name="q"]` (scope-aware) and typing shows/hides the options by their `data-reactive-filter-text` haystack, **zero round trips**. `option:` defaults to `[role=option]`; optional `group:` collapses an all-hidden group header; `empty:` reveals a no-matches node. `input:` is the escape hatch — a raw CSS selector for a **name-less** driving input (`input: "#tags_query"`), the form-builder case. See [Client-side option filtering](#client-side-option-filtering-reactive_filter). |
396
- | `reactive_listnav("[role=option]")` | The **standalone** combobox keyboard wiring (Arrow/Enter/Escape) for an input that fires **no action** — the preload-and-filter case. Same behavior as `on(…, listnav:)`, minus the POST. |
396
+ | `reactive_listnav("[role=option]")` | The **standalone** combobox keyboard wiring (Arrow/Enter/Escape) for an input that fires **no action** — the preload-and-filter case. Same behavior as `on(…, listnav:)`, minus the POST. `reactive_listnav(focus: true)` is the **roving-focus** variant for a `role="menu"` container: Arrow Down/Up move real focus among `[role=menuitem]` (wrapping), Home/End jump to the edges; Enter/Space stay native and Escape is yours to bind. |
397
397
  | `reactive_tags(:tags)` | **Tag-chip input** (the combobox/tags widget): spread onto the root and name the hidden field that stores the **comma-joined** value — the client maintains that field + the chip list entirely client-side (form state, zero round trips), rebuilding chips from your server-owned `<template>`. Composes with `reactive_filter` (type to narrow) and `reactive_listnav` (Enter picks the highlighted option). `name:` is the escape hatch — a **verbatim** wire name (`name: "user[tags]"`, never re-scoped), the form-builder case. See [Tag-chip input](#tag-chip-input-reactive_tags). |
398
398
  | `reactive_tags_add` / `reactive_tags_option(tag)` / `reactive_tags_remove(tag)` | The tags triggers, all **client-only**: `reactive_tags_add` on the query input adds the typed text on Enter (mix it **after** `reactive_listnav`; Enter never submits the enclosing form); `reactive_tags_option` makes a preloaded suggestion add its declared tag on click; `reactive_tags_remove` is a chip's remove button (no arg inside the template — the client fills the tag per chip). |
399
399
  | `reactive_compute :name, inputs: { title: :string, qty: :number }, outputs:` | **Typed** inputs: a `:string` reaches the JS reducer raw, a `:number` is coerced through `Number`, a `:boolean` is a checkbox's checked state. The array form (`inputs: %i[a b]`) stays all-numeric; the **permit-style** form (`inputs: [:qty, title: :string]`) mixes both — bare symbols default `:number`, a trailing hash types the exceptions. `outputs:` is the field allowlist; a reducer-result key also paints any owned `reactive_text` node by presence and any `mirror:` id, so an `outputs:` entry that exists only to reach a text node is redundant (harmless — a widening). |
@@ -892,7 +892,10 @@ button(**on_client(:click, js
892
892
  ```
893
893
 
894
894
  - **`set_attr(to, name, value)` / `remove_attr(to, name)` / `toggle_attr(to, name)`**
895
- mutate an attribute. The **attribute name is allowlisted, enforced twice** — at
895
+ mutate an attribute. `toggle_attr(to, name, on, off)` (#271) flips **between two
896
+ values** instead of toggling presence — `toggle_attr("#trigger", "aria-expanded",
897
+ "true", "false")`; an absent attribute becomes `on`, and one value or two equal
898
+ values raise at build time. The **attribute name is allowlisted, enforced twice** — at
896
899
  build time in Ruby (an offending name raises) and again in the client
897
900
  interpreter (a hand-built op is warned and skipped). Refused: **event handlers**
898
901
  (`on*` → XSS), **URL-bearing** names (`href`, `src`, `srcdoc`, `action`,
@@ -940,6 +943,14 @@ button(**on_client(:click, js
940
943
  (a broadcast that reads every subscriber's clipboard would be hostile). The
941
944
  op is async fire-and-forget — chained siblings apply immediately, never
942
945
  waiting for the read.
946
+ - **`expanded:`** on `show`/`hide`/`toggle` (#271) keeps a disclosure trigger's
947
+ `aria-expanded` honest: `js.toggle("#menu", expanded: "#trigger")` writes the
948
+ op's **intended** state (`show` → `"true"`, `hide` → `"false"`, `toggle` → the
949
+ state after the flip: `"true"` when the target was hidden, `"false"` when it
950
+ was visible) onto the target — set before any transition runs. It resolves
951
+ with the op's own scoping (`:root`, a root-scoped selector, or document-wide
952
+ under `global: true`). Pair it with one disclosure target; with several, the
953
+ last one wins.
943
954
  - **`transition: { during:, from:, to: }`** on `show`/`hide`/`toggle` animates the
944
955
  visibility flip: `during`+`from` are applied, then `from`→`to` swaps on the next
945
956
  frame, and the helper classes are cleaned up on `animationend` (with a timeout
@@ -950,6 +961,32 @@ button(**on_client(:click, js
950
961
  modifiers: the dropdown above closes on any click outside the component, and
951
962
  window-bound triggers never `preventDefault`, so links elsewhere keep working.
952
963
 
964
+ **Several bindings on one element (#271).** Each `on_client` call emits one
965
+ self-describing **binding record** — its descriptor, flags, `confirm:` and ops —
966
+ so two calls compose through `mix`. The descriptors token-join; the records
967
+ space-join (spaces inside a record ride as `\u0020`); the client runs only the
968
+ record whose descriptor matches the firing event (event type, key filter,
969
+ window-boundness):
970
+
971
+ ```ruby
972
+ div(**mix(reactive_root,
973
+ on_client(:click, js.hide("#menu", expanded: "#trigger"), outside: true),
974
+ on_client("keydown.esc", js.hide("#menu", expanded: "#trigger").focus("#trigger"))))
975
+ # data-action="click@window->reactive#runOps keydown.esc->reactive#runOps"
976
+ # data-reactive-ops-param='{"on":"click","window":true,"outside":true,"ops":[…]} {"on":"keydown.esc","ops":[…]}'
977
+ ```
978
+
979
+ An outside guard or a `confirm:` belongs to its own binding only — it never
980
+ gates a sibling. Two element-bound `:click` calls on the same `<button>` still
981
+ collide on the forced `type="button"` (`mix` renders `type="button button"`):
982
+ chain the ops into one call instead.
983
+
984
+ **An accessible menu with no custom JS.** Pair the above with
985
+ `reactive_listnav(focus: true)` on the `role="menu"` list — Arrow Down/Up rove
986
+ real focus among `[role=menuitem]` (wrapping), Home/End jump to the edges — and
987
+ the whole WAI-ARIA menu-button pattern is declared ops. See the
988
+ [client-only ops example](https://phlex-reactive.zoolutions.llc/docs/example-client-ops).
989
+
953
990
  **The general autosubmit story.** With `submit` in the vocabulary, the classic
954
991
  `onchange="this.form.requestSubmit()"` filter form is one declared line — no
955
992
  bespoke controller, no reactive action, and Turbo Drive turns the resulting
@@ -2635,6 +2672,44 @@ bug). It emits:
2635
2672
  3. an inert `reactive:token` refresh, so the container's signed token rolls
2636
2673
  forward and the list is not act-once-only.
2637
2674
 
2675
+ #### Honest pending markup: `pending_template` (opt-in)
2676
+
2677
+ The markers dim a *live* row. When the pending state should be real markup — a
2678
+ "Queued" badge, a button that is genuinely gone rather than hidden by
2679
+ `pointer-events: none` — define `pending_template` on the row component (private
2680
+ is fine). `reply.pending` then also **replaces** each such row with it, just
2681
+ before marking it:
2682
+
2683
+ ```ruby
2684
+ class TransferRow < ApplicationComponent
2685
+ include Phlex::Reactive::Streamable
2686
+ include Phlex::Reactive::Component
2687
+
2688
+ def view_template
2689
+ li(**mix(reactive_attrs, id:)) { span { @transfer.label }; button(**on(:retry)) { "Retry" } }
2690
+ end
2691
+
2692
+ private
2693
+
2694
+ def pending_template
2695
+ li(**mix(reactive_attrs, id:)) { span { @transfer.label }; span(class: "badge") { "Queued" } }
2696
+ end
2697
+ end
2698
+ ```
2699
+
2700
+ A row without the hook is byte-identical to the marker-only reply.
2701
+
2702
+ - **Keep the root's `id`.** The settle targets it; a `pending_template` without
2703
+ `id="<the row's id>"` raises before anything is enqueued.
2704
+ - **Keep `reactive_attrs`** if the row carries its own token — the swapped row
2705
+ signs the row class's real name, so its next action verifies as usual.
2706
+ - **Un-pending is the settle's job.** Markers are attributes any settle strips;
2707
+ swapped markup is only undone by a settle that replaces (`s.replace`) or
2708
+ removes (`s.remove`) the row. A flash-only settle, `finish: true`, or a failed job clears the markers
2709
+ but leaves the pending markup in place.
2710
+ - It is a *template* method, unlike `deferred_placeholder` (which returns inner
2711
+ content for a shell the gem owns): here the row's own root is swapped.
2712
+
2638
2713
  | `reply.pending(...)` | |
2639
2714
  |---|---|
2640
2715
  | `records` | one record, an enumerable of records, or built Streamable components |
@@ -163,20 +163,32 @@ function eventOf(dataAction) {
163
163
  }
164
164
 
165
165
  // Client-only op triggers ([data-reactive-ops-param]) — a menu toggle, a DOM op
166
- // chain that never round-trips. Report the parsed ops (names) when decodable.
166
+ // chain that never round-trips. Issue #271: the attr holds one on_client
167
+ // binding record per call ({on, window?, ops, …}, space-joined when mix-ed),
168
+ // reported as one { event, ops } entry each. A legacy [[op, args]] list keeps
169
+ // the data-action prefix as its event; an undecodable attr is reported raw.
167
170
  function ownedClientOps(root, allRoots) {
168
- return scopedQuery(root, "[data-reactive-ops-param]", allRoots).map((el) => {
171
+ return scopedQuery(root, "[data-reactive-ops-param]", allRoots).flatMap((el) => {
169
172
  const raw = el.getAttribute("data-reactive-ops-param")
170
- let ops = raw
171
- try {
172
- ops = JSON.parse(raw)
173
- } catch {
174
- // leave the raw string — a malformed ops attr shouldn't break the scan
175
- }
176
- return { event: eventOf(el.getAttribute("data-action")), ops }
173
+ const event = eventOf(el.getAttribute("data-action"))
174
+ const legacy = parseJson(raw)
175
+ if (Array.isArray(legacy)) return [{ event, ops: legacy }]
176
+ const records = (raw ?? "").split(" ").map(parseJson).filter((r) => r && typeof r === "object" && !Array.isArray(r))
177
+ if (records.length === 0) return [{ event, ops: raw }]
178
+ return records.map((r) => ({ event: r.on ? `${r.on}${r.window ? "@window" : ""}` : event, ops: r.ops }))
177
179
  })
178
180
  }
179
181
 
182
+ // JSON.parse that answers undefined instead of throwing — a malformed attr
183
+ // must never break the scan.
184
+ function parseJson(raw) {
185
+ try {
186
+ return JSON.parse(raw)
187
+ } catch {
188
+ return undefined
189
+ }
190
+ }
191
+
180
192
  // Compute reducers bound on this root ([data-reactive-compute-reducer-param]) —
181
193
  // the client-side data-binding twins of reactive_compute. Report reducer names.
182
194
  function ownedComputes(root, allRoots) {
@@ -1,4 +1,4 @@
1
- function u(t=document){let e=Array.from(t.querySelectorAll('[data-controller~="reactive"]'));return e.map((a)=>c(a,e))}function w(t=document){let e=u(t),a=globalThis.console;if(a&&typeof a.log==="function")a.log(`[phlex-reactive] ${e.length} reactive root(s)`);if(a&&typeof a.table==="function")a.table(e.map((r)=>({id:r.id,component:r.component??(r.opaque?"(opaque token)":null),actions:r.triggers.map((i)=>i.action).join(", "),fields:r.fields.join(", "),computes:r.computes.join(", ")})));return e}function c(t,e){let a=t.getAttribute("data-reactive-token-value"),r=s(a);return{id:t.id||null,component:r.opaque?null:r.c??null,gid:r.gid??null,stateKeys:r.s?Object.keys(r.s):[],tokenVersion:r.v??null,opaque:!!r.opaque,status:p(t),triggers:g(t,e),clientOps:b(t,e),computes:l(t,e),fields:v(t,e),show:A(t,e),text:y(t,e),filter:h(t)}}function s(t){if(!t)return{opaque:!0};try{let e=t.split("--")[0],a=d(e),r=JSON.parse(a),i=r&&r._rails&&r._rails.data?r._rails.data:r;if(!i||typeof i!=="object")return{opaque:!0};return i}catch{return{opaque:!0}}}function d(t){return(globalThis.atob||((a)=>Buffer.from(a,"base64").toString("binary")))(t)}function p(t){return{error:t.getAttribute("data-reactive-error"),busy:t.getAttribute("aria-busy")==="true"||t.hasAttribute("data-reactive-busy"),dirty:t.hasAttribute("data-reactive-dirty")}}var f='[data-controller~="reactive"]';function m(t,e){return t.closest(f)===e}function n(t,e,a){return Array.from(t.querySelectorAll(e)).filter((r)=>m(r,t))}function g(t,e){return n(t,"[data-reactive-action-param]",e).map((a)=>({action:a.getAttribute("data-reactive-action-param"),event:o(a.getAttribute("data-action")),params:a.getAttribute("data-reactive-params-param"),debounce:a.getAttribute("data-reactive-debounce-param"),throttle:a.getAttribute("data-reactive-throttle-param"),confirm:a.getAttribute("data-reactive-confirm-param"),optimistic:a.getAttribute("data-reactive-optimistic-param"),loading:a.getAttribute("data-reactive-loading-param")}))}function o(t){if(!t)return null;let e=t.indexOf("->");return e===-1?null:t.slice(0,e)}function b(t,e){return n(t,"[data-reactive-ops-param]",e).map((a)=>{let r=a.getAttribute("data-reactive-ops-param"),i=r;try{i=JSON.parse(r)}catch{}return{event:o(a.getAttribute("data-action")),ops:i}})}function l(t,e){return n(t,"[data-reactive-compute-reducer-param]",e).map((a)=>a.getAttribute("data-reactive-compute-reducer-param"))}function v(t,e){return n(t,"input[name], select[name], textarea[name]",e).map((a)=>a.getAttribute("name"))}function A(t,e){return n(t,"[data-reactive-show-field]",e).map((a)=>({field:a.getAttribute("data-reactive-show-field"),equals:a.getAttribute("data-reactive-show-equals"),not:a.getAttribute("data-reactive-show-not"),in:a.getAttribute("data-reactive-show-in")}))}function y(t,e){return n(t,"[data-reactive-text]",e).map((a)=>a.getAttribute("data-reactive-text"))}function h(t){let e=t.getAttribute("data-reactive-filter-input");if(!e)return null;return{input:e,option:t.getAttribute("data-reactive-filter-option"),group:t.getAttribute("data-reactive-filter-group"),empty:t.getAttribute("data-reactive-filter-empty")}}export{w as report,u as scan};
1
+ function p(t=document){let e=Array.from(t.querySelectorAll('[data-controller~="reactive"]'));return e.map((r)=>f(r,e))}function S(t=document){let e=p(t),r=globalThis.console;if(r&&typeof r.log==="function")r.log(`[phlex-reactive] ${e.length} reactive root(s)`);if(r&&typeof r.table==="function")r.table(e.map((a)=>({id:a.id,component:a.component??(a.opaque?"(opaque token)":null),actions:a.triggers.map((n)=>n.action).join(", "),fields:a.fields.join(", "),computes:a.computes.join(", ")})));return e}function f(t,e){let r=t.getAttribute("data-reactive-token-value"),a=m(r);return{id:t.id||null,component:a.opaque?null:a.c??null,gid:a.gid??null,stateKeys:a.s?Object.keys(a.s):[],tokenVersion:a.v??null,opaque:!!a.opaque,status:b(t),triggers:A(t,e),clientOps:y(t,e),computes:w(t,e),fields:h(t,e),show:q(t,e),text:O(t,e),filter:x(t)}}function m(t){if(!t)return{opaque:!0};try{let e=t.split("--")[0],r=g(e),a=JSON.parse(r),n=a&&a._rails&&a._rails.data?a._rails.data:a;if(!n||typeof n!=="object")return{opaque:!0};return n}catch{return{opaque:!0}}}function g(t){return(globalThis.atob||((r)=>Buffer.from(r,"base64").toString("binary")))(t)}function b(t){return{error:t.getAttribute("data-reactive-error"),busy:t.getAttribute("aria-busy")==="true"||t.hasAttribute("data-reactive-busy"),dirty:t.hasAttribute("data-reactive-dirty")}}var l='[data-controller~="reactive"]';function v(t,e){return t.closest(l)===e}function o(t,e,r){return Array.from(t.querySelectorAll(e)).filter((a)=>v(a,t))}function A(t,e){return o(t,"[data-reactive-action-param]",e).map((r)=>({action:r.getAttribute("data-reactive-action-param"),event:d(r.getAttribute("data-action")),params:r.getAttribute("data-reactive-params-param"),debounce:r.getAttribute("data-reactive-debounce-param"),throttle:r.getAttribute("data-reactive-throttle-param"),confirm:r.getAttribute("data-reactive-confirm-param"),optimistic:r.getAttribute("data-reactive-optimistic-param"),loading:r.getAttribute("data-reactive-loading-param")}))}function d(t){if(!t)return null;let e=t.indexOf("->");return e===-1?null:t.slice(0,e)}function y(t,e){return o(t,"[data-reactive-ops-param]",e).flatMap((r)=>{let a=r.getAttribute("data-reactive-ops-param"),n=d(r.getAttribute("data-action")),u=s(a);if(Array.isArray(u))return[{event:n,ops:u}];let c=(a??"").split(" ").map(s).filter((i)=>i&&typeof i==="object"&&!Array.isArray(i));if(c.length===0)return[{event:n,ops:a}];return c.map((i)=>({event:i.on?`${i.on}${i.window?"@window":""}`:n,ops:i.ops}))})}function s(t){try{return JSON.parse(t)}catch{return}}function w(t,e){return o(t,"[data-reactive-compute-reducer-param]",e).map((r)=>r.getAttribute("data-reactive-compute-reducer-param"))}function h(t,e){return o(t,"input[name], select[name], textarea[name]",e).map((r)=>r.getAttribute("name"))}function q(t,e){return o(t,"[data-reactive-show-field]",e).map((r)=>({field:r.getAttribute("data-reactive-show-field"),equals:r.getAttribute("data-reactive-show-equals"),not:r.getAttribute("data-reactive-show-not"),in:r.getAttribute("data-reactive-show-in")}))}function O(t,e){return o(t,"[data-reactive-text]",e).map((r)=>r.getAttribute("data-reactive-text"))}function x(t){let e=t.getAttribute("data-reactive-filter-input");if(!e)return null;return{input:e,option:t.getAttribute("data-reactive-filter-option"),group:t.getAttribute("data-reactive-filter-group"),empty:t.getAttribute("data-reactive-filter-empty")}}export{S as report,p as scan};
2
2
 
3
- //# debugId=C4F6AC69EE45C88764756E2164756E21
3
+ //# debugId=041E72D906D0F77464756E2164756E21
4
4
  //# sourceMappingURL=inspect.min.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["inspect.js"],
4
4
  "sourcesContent": [
5
- "// The on-demand client inspector (issue #168) — the browser half of the\n// debugging toolkit, the client twin of `phlex_reactive:actions` / the MCP tools.\n//\n// It is a STANDALONE module (the confirm.js / compute.js precedent): it does NOT\n// touch the hot-path reactive_controller.js and costs NOTHING until you import\n// it. Loaded on demand from the console (the engine pins it), it scans the live\n// DOM and maps every reactive root + bound trigger back to the server\n// Component#action names — so you can answer \"what's reactive on this page, what\n// does each control POST, and which component owns it?\" without reading source.\n//\n// (await import(\"phlex/reactive/inspect\")).report() // console.table\n// const roots = (await import(\"phlex/reactive/inspect\")).scan() // data\n//\n// The server↔client mapping is BY NAME: scan()'s `component` + each trigger's\n// `action` are exactly the Component#action identifiers `phlex_reactive:actions`\n// and the MCP tools list. The docs page shows the two side by side.\n//\n// It reads the SAME DOM contract reactive_controller.js writes (roots\n// [data-controller~=\"reactive\"] with id + data-reactive-token-value; triggers\n// [data-reactive-action-param] with data-action/params/modifiers; client-only\n// [data-reactive-ops-param]; computes [data-reactive-compute-reducer-param]; the\n// show/filter/text binding families). It never mutates anything — pure read.\n\n// Scan `root` (default the whole document) and return one entry per reactive\n// root, deepest-scoped: each trigger is attributed to its NEAREST reactive root,\n// so a nested root doesn't double-count its parent's triggers.\nexport function scan(root = document) {\n const roots = Array.from(root.querySelectorAll('[data-controller~=\"reactive\"]'))\n return roots.map((el) => inspectRoot(el, roots))\n}\n\n// console.table the inventory (roots + a flattened trigger table), and return\n// the same data scan() does so `report()` is also usable programmatically. Safe\n// on an empty document (logs an empty table, throws nothing).\nexport function report(root = document) {\n const roots = scan(root)\n const console = globalThis.console\n\n // Guard each console method independently — a host page (or a test harness)\n // may ship a partial console, and report() must never throw over logging.\n if (console && typeof console.log === \"function\") {\n console.log(`[phlex-reactive] ${roots.length} reactive root(s)`)\n }\n if (console && typeof console.table === \"function\") {\n console.table(\n roots.map((r) => ({\n id: r.id,\n component: r.component ?? (r.opaque ? \"(opaque token)\" : null),\n actions: r.triggers.map((t) => t.action).join(\", \"),\n fields: r.fields.join(\", \"),\n computes: r.computes.join(\", \"),\n })),\n )\n }\n return roots\n}\n\n// --- one root --------------------------------------------------------------\n\nfunction inspectRoot(el, allRoots) {\n const token = el.getAttribute(\"data-reactive-token-value\")\n const payload = decodeToken(token)\n\n return {\n id: el.id || null,\n component: payload.opaque ? null : (payload.c ?? null),\n gid: payload.gid ?? null,\n stateKeys: payload.s ? Object.keys(payload.s) : [],\n tokenVersion: payload.v ?? null,\n opaque: !!payload.opaque,\n status: readStatus(el),\n triggers: ownedTriggers(el, allRoots),\n clientOps: ownedClientOps(el, allRoots),\n computes: ownedComputes(el, allRoots),\n fields: ownedFields(el, allRoots),\n show: ownedShow(el, allRoots),\n text: ownedText(el, allRoots),\n filter: readFilter(el),\n }\n}\n\n// The signed identity is base64(JSON)--signature under the Rails 7.1+ JSON\n// message serializer. Decode the segment BEFORE the \"--\", JSON-parse it, and\n// unwrap the MessageVerifier envelope ({_rails:{data:…}}) to the {c,gid,s,v}\n// payload. Degrade to { opaque: true } on anything that isn't JSON-decodable —\n// a Marshal-serialized payload (older apps) simply isn't readable client-side,\n// and that's fine: the inspector shows \"(opaque token)\" rather than throwing.\nfunction decodeToken(token) {\n if (!token) return { opaque: true }\n\n try {\n const segment = token.split(\"--\")[0]\n const json = atobUtf8(segment)\n const parsed = JSON.parse(json)\n const data = parsed && parsed._rails && parsed._rails.data ? parsed._rails.data : parsed\n if (!data || typeof data !== \"object\") return { opaque: true }\n return data\n } catch {\n return { opaque: true }\n }\n}\n\n// base64 → string. Uses the platform atob; a malformed/urlsafe segment throws,\n// which decodeToken catches and reports as opaque.\nfunction atobUtf8(b64) {\n const decode = globalThis.atob || ((s) => Buffer.from(s, \"base64\").toString(\"binary\"))\n return decode(b64)\n}\n\n// The at-a-glance runtime state the controller stamps on the root. Names/flags\n// only — never a value that could be sensitive.\nfunction readStatus(el) {\n return {\n error: el.getAttribute(\"data-reactive-error\"),\n busy: el.getAttribute(\"aria-busy\") === \"true\" || el.hasAttribute(\"data-reactive-busy\"),\n dirty: el.hasAttribute(\"data-reactive-dirty\"),\n }\n}\n\n// --- ownership scoping -----------------------------------------------------\n//\n// An element belongs to a root when its NEAREST reactive-root ancestor is that\n// root (matching the controller's own ownership walk). closest() finds the\n// nearest ancestor matching the selector; the element itself can be the root\n// (for a root that also carries a binding), so we start the walk from the\n// element and accept `el` as its own owner.\n\nconst ROOT_SELECTOR = '[data-controller~=\"reactive\"]'\n\nfunction ownedBy(node, root) {\n return node.closest(ROOT_SELECTOR) === root\n}\n\n// Query `root` for `selector`, keeping only elements whose nearest reactive\n// root IS `root` — so a nested root's matches are excluded here and attributed\n// to that inner root instead.\nfunction scopedQuery(root, selector, allRoots) {\n return Array.from(root.querySelectorAll(selector)).filter((node) => ownedBy(node, root))\n}\n\n// --- the binding families --------------------------------------------------\n\nfunction ownedTriggers(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-action-param]\", allRoots).map((el) => ({\n action: el.getAttribute(\"data-reactive-action-param\"),\n event: eventOf(el.getAttribute(\"data-action\")),\n params: el.getAttribute(\"data-reactive-params-param\"),\n debounce: el.getAttribute(\"data-reactive-debounce-param\"),\n throttle: el.getAttribute(\"data-reactive-throttle-param\"),\n confirm: el.getAttribute(\"data-reactive-confirm-param\"),\n optimistic: el.getAttribute(\"data-reactive-optimistic-param\"),\n loading: el.getAttribute(\"data-reactive-loading-param\"),\n }))\n}\n\n// The Stimulus data-action descriptor is \"event->controller#method\"; the event\n// is the part before \"->\" (a bare \"controller#method\" has an implicit default\n// event, which we report as null).\nfunction eventOf(dataAction) {\n if (!dataAction) return null\n const arrow = dataAction.indexOf(\"->\")\n return arrow === -1 ? null : dataAction.slice(0, arrow)\n}\n\n// Client-only op triggers ([data-reactive-ops-param]) — a menu toggle, a DOM op\n// chain that never round-trips. Report the parsed ops (names) when decodable.\nfunction ownedClientOps(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-ops-param]\", allRoots).map((el) => {\n const raw = el.getAttribute(\"data-reactive-ops-param\")\n let ops = raw\n try {\n ops = JSON.parse(raw)\n } catch {\n // leave the raw string — a malformed ops attr shouldn't break the scan\n }\n return { event: eventOf(el.getAttribute(\"data-action\")), ops }\n })\n}\n\n// Compute reducers bound on this root ([data-reactive-compute-reducer-param]) —\n// the client-side data-binding twins of reactive_compute. Report reducer names.\nfunction ownedComputes(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-compute-reducer-param]\", allRoots).map((el) =>\n el.getAttribute(\"data-reactive-compute-reducer-param\"),\n )\n}\n\n// The named form controls the dispatch would collect and send — the same\n// selector the controller's field walk uses. Names only, never values.\nfunction ownedFields(root, allRoots) {\n return scopedQuery(root, \"input[name], select[name], textarea[name]\", allRoots).map((el) =>\n el.getAttribute(\"name\"),\n )\n}\n\n// reactive_show bindings: which owned field controls each element's visibility.\nfunction ownedShow(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-show-field]\", allRoots).map((el) => ({\n field: el.getAttribute(\"data-reactive-show-field\"),\n equals: el.getAttribute(\"data-reactive-show-equals\"),\n not: el.getAttribute(\"data-reactive-show-not\"),\n in: el.getAttribute(\"data-reactive-show-in\"),\n }))\n}\n\n// reactive_text spans (data-reactive-text=<name>) the client writes via\n// textContent — report the binding names.\nfunction ownedText(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-text]\", allRoots).map((el) =>\n el.getAttribute(\"data-reactive-text\"),\n )\n}\n\n// reactive_filter lives on the ROOT itself (the input/option/group/empty\n// selectors it narrows client-side). Null when the root declares no filter.\nfunction readFilter(el) {\n const input = el.getAttribute(\"data-reactive-filter-input\")\n if (!input) return null\n return {\n input,\n option: el.getAttribute(\"data-reactive-filter-option\"),\n group: el.getAttribute(\"data-reactive-filter-group\"),\n empty: el.getAttribute(\"data-reactive-filter-empty\"),\n }\n}\n"
5
+ "// The on-demand client inspector (issue #168) — the browser half of the\n// debugging toolkit, the client twin of `phlex_reactive:actions` / the MCP tools.\n//\n// It is a STANDALONE module (the confirm.js / compute.js precedent): it does NOT\n// touch the hot-path reactive_controller.js and costs NOTHING until you import\n// it. Loaded on demand from the console (the engine pins it), it scans the live\n// DOM and maps every reactive root + bound trigger back to the server\n// Component#action names — so you can answer \"what's reactive on this page, what\n// does each control POST, and which component owns it?\" without reading source.\n//\n// (await import(\"phlex/reactive/inspect\")).report() // console.table\n// const roots = (await import(\"phlex/reactive/inspect\")).scan() // data\n//\n// The server↔client mapping is BY NAME: scan()'s `component` + each trigger's\n// `action` are exactly the Component#action identifiers `phlex_reactive:actions`\n// and the MCP tools list. The docs page shows the two side by side.\n//\n// It reads the SAME DOM contract reactive_controller.js writes (roots\n// [data-controller~=\"reactive\"] with id + data-reactive-token-value; triggers\n// [data-reactive-action-param] with data-action/params/modifiers; client-only\n// [data-reactive-ops-param]; computes [data-reactive-compute-reducer-param]; the\n// show/filter/text binding families). It never mutates anything — pure read.\n\n// Scan `root` (default the whole document) and return one entry per reactive\n// root, deepest-scoped: each trigger is attributed to its NEAREST reactive root,\n// so a nested root doesn't double-count its parent's triggers.\nexport function scan(root = document) {\n const roots = Array.from(root.querySelectorAll('[data-controller~=\"reactive\"]'))\n return roots.map((el) => inspectRoot(el, roots))\n}\n\n// console.table the inventory (roots + a flattened trigger table), and return\n// the same data scan() does so `report()` is also usable programmatically. Safe\n// on an empty document (logs an empty table, throws nothing).\nexport function report(root = document) {\n const roots = scan(root)\n const console = globalThis.console\n\n // Guard each console method independently — a host page (or a test harness)\n // may ship a partial console, and report() must never throw over logging.\n if (console && typeof console.log === \"function\") {\n console.log(`[phlex-reactive] ${roots.length} reactive root(s)`)\n }\n if (console && typeof console.table === \"function\") {\n console.table(\n roots.map((r) => ({\n id: r.id,\n component: r.component ?? (r.opaque ? \"(opaque token)\" : null),\n actions: r.triggers.map((t) => t.action).join(\", \"),\n fields: r.fields.join(\", \"),\n computes: r.computes.join(\", \"),\n })),\n )\n }\n return roots\n}\n\n// --- one root --------------------------------------------------------------\n\nfunction inspectRoot(el, allRoots) {\n const token = el.getAttribute(\"data-reactive-token-value\")\n const payload = decodeToken(token)\n\n return {\n id: el.id || null,\n component: payload.opaque ? null : (payload.c ?? null),\n gid: payload.gid ?? null,\n stateKeys: payload.s ? Object.keys(payload.s) : [],\n tokenVersion: payload.v ?? null,\n opaque: !!payload.opaque,\n status: readStatus(el),\n triggers: ownedTriggers(el, allRoots),\n clientOps: ownedClientOps(el, allRoots),\n computes: ownedComputes(el, allRoots),\n fields: ownedFields(el, allRoots),\n show: ownedShow(el, allRoots),\n text: ownedText(el, allRoots),\n filter: readFilter(el),\n }\n}\n\n// The signed identity is base64(JSON)--signature under the Rails 7.1+ JSON\n// message serializer. Decode the segment BEFORE the \"--\", JSON-parse it, and\n// unwrap the MessageVerifier envelope ({_rails:{data:…}}) to the {c,gid,s,v}\n// payload. Degrade to { opaque: true } on anything that isn't JSON-decodable —\n// a Marshal-serialized payload (older apps) simply isn't readable client-side,\n// and that's fine: the inspector shows \"(opaque token)\" rather than throwing.\nfunction decodeToken(token) {\n if (!token) return { opaque: true }\n\n try {\n const segment = token.split(\"--\")[0]\n const json = atobUtf8(segment)\n const parsed = JSON.parse(json)\n const data = parsed && parsed._rails && parsed._rails.data ? parsed._rails.data : parsed\n if (!data || typeof data !== \"object\") return { opaque: true }\n return data\n } catch {\n return { opaque: true }\n }\n}\n\n// base64 → string. Uses the platform atob; a malformed/urlsafe segment throws,\n// which decodeToken catches and reports as opaque.\nfunction atobUtf8(b64) {\n const decode = globalThis.atob || ((s) => Buffer.from(s, \"base64\").toString(\"binary\"))\n return decode(b64)\n}\n\n// The at-a-glance runtime state the controller stamps on the root. Names/flags\n// only — never a value that could be sensitive.\nfunction readStatus(el) {\n return {\n error: el.getAttribute(\"data-reactive-error\"),\n busy: el.getAttribute(\"aria-busy\") === \"true\" || el.hasAttribute(\"data-reactive-busy\"),\n dirty: el.hasAttribute(\"data-reactive-dirty\"),\n }\n}\n\n// --- ownership scoping -----------------------------------------------------\n//\n// An element belongs to a root when its NEAREST reactive-root ancestor is that\n// root (matching the controller's own ownership walk). closest() finds the\n// nearest ancestor matching the selector; the element itself can be the root\n// (for a root that also carries a binding), so we start the walk from the\n// element and accept `el` as its own owner.\n\nconst ROOT_SELECTOR = '[data-controller~=\"reactive\"]'\n\nfunction ownedBy(node, root) {\n return node.closest(ROOT_SELECTOR) === root\n}\n\n// Query `root` for `selector`, keeping only elements whose nearest reactive\n// root IS `root` — so a nested root's matches are excluded here and attributed\n// to that inner root instead.\nfunction scopedQuery(root, selector, allRoots) {\n return Array.from(root.querySelectorAll(selector)).filter((node) => ownedBy(node, root))\n}\n\n// --- the binding families --------------------------------------------------\n\nfunction ownedTriggers(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-action-param]\", allRoots).map((el) => ({\n action: el.getAttribute(\"data-reactive-action-param\"),\n event: eventOf(el.getAttribute(\"data-action\")),\n params: el.getAttribute(\"data-reactive-params-param\"),\n debounce: el.getAttribute(\"data-reactive-debounce-param\"),\n throttle: el.getAttribute(\"data-reactive-throttle-param\"),\n confirm: el.getAttribute(\"data-reactive-confirm-param\"),\n optimistic: el.getAttribute(\"data-reactive-optimistic-param\"),\n loading: el.getAttribute(\"data-reactive-loading-param\"),\n }))\n}\n\n// The Stimulus data-action descriptor is \"event->controller#method\"; the event\n// is the part before \"->\" (a bare \"controller#method\" has an implicit default\n// event, which we report as null).\nfunction eventOf(dataAction) {\n if (!dataAction) return null\n const arrow = dataAction.indexOf(\"->\")\n return arrow === -1 ? null : dataAction.slice(0, arrow)\n}\n\n// Client-only op triggers ([data-reactive-ops-param]) — a menu toggle, a DOM op\n// chain that never round-trips. Issue #271: the attr holds one on_client\n// binding record per call ({on, window?, ops, …}, space-joined when mix-ed),\n// reported as one { event, ops } entry each. A legacy [[op, args]] list keeps\n// the data-action prefix as its event; an undecodable attr is reported raw.\nfunction ownedClientOps(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-ops-param]\", allRoots).flatMap((el) => {\n const raw = el.getAttribute(\"data-reactive-ops-param\")\n const event = eventOf(el.getAttribute(\"data-action\"))\n const legacy = parseJson(raw)\n if (Array.isArray(legacy)) return [{ event, ops: legacy }]\n const records = (raw ?? \"\").split(\" \").map(parseJson).filter((r) => r && typeof r === \"object\" && !Array.isArray(r))\n if (records.length === 0) return [{ event, ops: raw }]\n return records.map((r) => ({ event: r.on ? `${r.on}${r.window ? \"@window\" : \"\"}` : event, ops: r.ops }))\n })\n}\n\n// JSON.parse that answers undefined instead of throwing — a malformed attr\n// must never break the scan.\nfunction parseJson(raw) {\n try {\n return JSON.parse(raw)\n } catch {\n return undefined\n }\n}\n\n// Compute reducers bound on this root ([data-reactive-compute-reducer-param]) —\n// the client-side data-binding twins of reactive_compute. Report reducer names.\nfunction ownedComputes(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-compute-reducer-param]\", allRoots).map((el) =>\n el.getAttribute(\"data-reactive-compute-reducer-param\"),\n )\n}\n\n// The named form controls the dispatch would collect and send — the same\n// selector the controller's field walk uses. Names only, never values.\nfunction ownedFields(root, allRoots) {\n return scopedQuery(root, \"input[name], select[name], textarea[name]\", allRoots).map((el) =>\n el.getAttribute(\"name\"),\n )\n}\n\n// reactive_show bindings: which owned field controls each element's visibility.\nfunction ownedShow(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-show-field]\", allRoots).map((el) => ({\n field: el.getAttribute(\"data-reactive-show-field\"),\n equals: el.getAttribute(\"data-reactive-show-equals\"),\n not: el.getAttribute(\"data-reactive-show-not\"),\n in: el.getAttribute(\"data-reactive-show-in\"),\n }))\n}\n\n// reactive_text spans (data-reactive-text=<name>) the client writes via\n// textContent — report the binding names.\nfunction ownedText(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-text]\", allRoots).map((el) =>\n el.getAttribute(\"data-reactive-text\"),\n )\n}\n\n// reactive_filter lives on the ROOT itself (the input/option/group/empty\n// selectors it narrows client-side). Null when the root declares no filter.\nfunction readFilter(el) {\n const input = el.getAttribute(\"data-reactive-filter-input\")\n if (!input) return null\n return {\n input,\n option: el.getAttribute(\"data-reactive-filter-option\"),\n group: el.getAttribute(\"data-reactive-filter-group\"),\n empty: el.getAttribute(\"data-reactive-filter-empty\"),\n }\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "C4F6AC69EE45C88764756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "041E72D906D0F77464756E2164756E21",
9
9
  "names": []
10
10
  }