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 +4 -4
- data/CHANGELOG.md +47 -0
- data/README.md +78 -3
- data/app/javascript/phlex/reactive/inspect.js +21 -9
- data/app/javascript/phlex/reactive/inspect.min.js +2 -2
- data/app/javascript/phlex/reactive/inspect.min.js.map +3 -3
- data/app/javascript/phlex/reactive/reactive_controller.js +259 -18
- data/app/javascript/phlex/reactive/reactive_controller.min.js +2 -2
- data/app/javascript/phlex/reactive/reactive_controller.min.js.map +3 -3
- data/lib/phlex/reactive/component/helpers.rb +65 -19
- data/lib/phlex/reactive/engine.rb +1 -0
- data/lib/phlex/reactive/js.rb +44 -10
- data/lib/phlex/reactive/pending/markup.rb +128 -0
- data/lib/phlex/reactive/pending.rb +25 -21
- data/lib/phlex/reactive/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ce768022637774ec42b7bca32e6533613fb563c792d9c82f58c8b07eea722e7f
|
|
4
|
+
data.tar.gz: 42a470435e59c6a364f3845a4626bdbb0b8e23a1cc7aadcb0c2281b89c922555
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
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).
|
|
171
|
+
return scopedQuery(root, "[data-reactive-ops-param]", allRoots).flatMap((el) => {
|
|
169
172
|
const raw = el.getAttribute("data-reactive-ops-param")
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
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=
|
|
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": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "041E72D906D0F77464756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|