phlex-reactive 0.13.4 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1050 -630
- data/README.md +561 -14
- data/app/controllers/phlex/reactive/actions_controller.rb +85 -16
- data/app/controllers/phlex/reactive/fragments_controller.rb +310 -0
- data/app/javascript/phlex/reactive/core.js +181 -0
- data/app/javascript/phlex/reactive/core.min.js +4 -0
- data/app/javascript/phlex/reactive/core.min.js.map +11 -0
- data/app/javascript/phlex/reactive/early.js +175 -0
- data/app/javascript/phlex/reactive/early.min.js +4 -0
- data/app/javascript/phlex/reactive/early.min.js.map +10 -0
- data/app/javascript/phlex/reactive/features/bindings.js +1511 -0
- data/app/javascript/phlex/reactive/features/bindings.min.js +4 -0
- data/app/javascript/phlex/reactive/features/bindings.min.js.map +10 -0
- data/app/javascript/phlex/reactive/features/compute.js +541 -0
- data/app/javascript/phlex/reactive/features/compute.min.js +4 -0
- data/app/javascript/phlex/reactive/features/compute.min.js.map +10 -0
- data/app/javascript/phlex/reactive/features/defer.js +726 -0
- data/app/javascript/phlex/reactive/features/defer.min.js +4 -0
- data/app/javascript/phlex/reactive/features/defer.min.js.map +10 -0
- data/app/javascript/phlex/reactive/features/devtools.js +242 -0
- data/app/javascript/phlex/reactive/features/devtools.min.js +4 -0
- data/app/javascript/phlex/reactive/features/devtools.min.js.map +10 -0
- data/app/javascript/phlex/reactive/features/effects.js +362 -0
- data/app/javascript/phlex/reactive/features/effects.min.js +4 -0
- data/app/javascript/phlex/reactive/features/effects.min.js.map +10 -0
- data/app/javascript/phlex/reactive/features/form.js +216 -0
- data/app/javascript/phlex/reactive/features/form.min.js +4 -0
- data/app/javascript/phlex/reactive/features/form.min.js.map +10 -0
- data/app/javascript/phlex/reactive/features/hints.js +193 -0
- data/app/javascript/phlex/reactive/features/hints.min.js +4 -0
- data/app/javascript/phlex/reactive/features/hints.min.js.map +10 -0
- data/app/javascript/phlex/reactive/features/persist.js +663 -0
- data/app/javascript/phlex/reactive/features/persist.min.js +4 -0
- data/app/javascript/phlex/reactive/features/persist.min.js.map +10 -0
- data/app/javascript/phlex/reactive/reactive_controller.js +85 -5821
- data/app/javascript/phlex/reactive/reactive_controller.min.js +2 -2
- data/app/javascript/phlex/reactive/reactive_controller.min.js.map +13 -4
- data/app/javascript/phlex/reactive/runtime.js +3690 -0
- data/lib/generators/phlex/reactive/install/templates/phlex_reactive.rb.erb +18 -0
- data/lib/phlex/reactive/claude/skills/phlex-reactive-debugging/SKILL.md +5 -3
- data/lib/phlex/reactive/component/dsl.rb +74 -2
- data/lib/phlex/reactive/component/helpers.rb +39 -9
- data/lib/phlex/reactive/component/lazy.rb +138 -2
- data/lib/phlex/reactive/component/registry.rb +2 -0
- data/lib/phlex/reactive/component/selection.rb +71 -0
- data/lib/phlex/reactive/doctor/fiber_isolation.rb +86 -0
- data/lib/phlex/reactive/doctor.rb +166 -8
- data/lib/phlex/reactive/dormant.rb +66 -0
- data/lib/phlex/reactive/engine.rb +63 -3
- data/lib/phlex/reactive/fragment.rb +233 -0
- data/lib/phlex/reactive/inspector/report.rb +24 -3
- data/lib/phlex/reactive/inspector.rb +23 -2
- data/lib/phlex/reactive/js.rb +17 -2
- data/lib/phlex/reactive/mcp/tools/actions_tool.rb +3 -1
- data/lib/phlex/reactive/mcp/tools/find_tool.rb +4 -1
- data/lib/phlex/reactive/param_schema.rb +68 -54
- data/lib/phlex/reactive/show_conditions.rb +84 -56
- data/lib/phlex/reactive/streamable.rb +20 -2
- data/lib/phlex/reactive/test_helpers/system/reactive_requests_matcher.rb +120 -0
- data/lib/phlex/reactive/test_helpers/system.rb +62 -0
- data/lib/phlex/reactive/version.rb +1 -1
- data/lib/phlex/reactive.rb +84 -0
- data/lib/tasks/phlex_reactive.rake +6 -2
- metadata +38 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Phlex
|
|
6
|
+
module Reactive
|
|
7
|
+
module TestHelpers
|
|
8
|
+
module System
|
|
9
|
+
# The waiting matcher behind have_reactive_requests (issue #279). Reads the
|
|
10
|
+
# client's per-kind request totals (<html data-reactive-requests>, JSON)
|
|
11
|
+
# AND the in-flight marker in ONE evaluate_script per poll, and holds once
|
|
12
|
+
# the selected total equals `expected` with the layer idle, so a request
|
|
13
|
+
# still in flight is never counted early. It cannot see a request that has
|
|
14
|
+
# not STARTED yet (a debounced trigger, or the defer an action's reply is
|
|
15
|
+
# about to start): barrier on the UI outcome first for those.
|
|
16
|
+
#
|
|
17
|
+
# Totals only grow until reset_reactive_requests!, so a total ABOVE the
|
|
18
|
+
# expectation is terminal: the matcher fails at once instead of waiting out
|
|
19
|
+
# the budget. A plain class (no RSpec::Matchers.define), like
|
|
20
|
+
# ReactiveValueMatcher, so System stays loadable under Capybara alone.
|
|
21
|
+
class ReactiveRequestsMatcher
|
|
22
|
+
KINDS = %i[action defer].freeze
|
|
23
|
+
|
|
24
|
+
def initialize(expected, kind: nil, wait: nil)
|
|
25
|
+
unless kind.nil? || KINDS.include?(kind)
|
|
26
|
+
raise ArgumentError, "unknown reactive request kind #{kind.inspect} (expected one of #{KINDS.inspect} or nil)"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
@expected = Integer(expected)
|
|
30
|
+
@kind = kind
|
|
31
|
+
@wait = wait
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def matches?(page)
|
|
35
|
+
@page = page
|
|
36
|
+
poll_until do
|
|
37
|
+
read_state
|
|
38
|
+
break false if selected > @expected
|
|
39
|
+
|
|
40
|
+
selected == @expected && !@active
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# RSpec's required negated-matcher protocol name, not a predicate we pick.
|
|
45
|
+
# rubocop:disable-next Naming/PredicatePrefix
|
|
46
|
+
def does_not_match?(page)
|
|
47
|
+
@page = page
|
|
48
|
+
poll_until do
|
|
49
|
+
read_state
|
|
50
|
+
selected != @expected && !@active
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def failure_message
|
|
55
|
+
with_gate_hint(
|
|
56
|
+
"expected #{@expected} reactive #{noun} (#{scope}), got #{selected} — totals #{totals_str}"
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def failure_message_when_negated
|
|
61
|
+
with_gate_hint(
|
|
62
|
+
"expected the reactive request count (#{scope}) NOT to be #{@expected}, but it was — totals #{totals_str}"
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# A closed verbose gate is the usual cause of an unexpected zero: say so.
|
|
69
|
+
def with_gate_hint(message)
|
|
70
|
+
return message if @verbose
|
|
71
|
+
|
|
72
|
+
"#{message}. The verbose gate is closed, so the client is not counting: it writes " \
|
|
73
|
+
"<html #{REQUESTS_ATTR}> only under Phlex::Reactive.verbose_errors (on in dev/test " \
|
|
74
|
+
"by default) or data-reactive-verbose on <html>"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def read_state
|
|
78
|
+
raw = @page.evaluate_script(<<~JS)
|
|
79
|
+
(() => {
|
|
80
|
+
const html = document.documentElement
|
|
81
|
+
return {
|
|
82
|
+
requests: html.getAttribute(#{REQUESTS_ATTR.to_json}),
|
|
83
|
+
active: html.hasAttribute(#{ACTIVE_MARKER.to_json}),
|
|
84
|
+
verbose: html.hasAttribute("data-reactive-verbose") ||
|
|
85
|
+
!!document.querySelector('[data-reactive-verbose="true"]'),
|
|
86
|
+
}
|
|
87
|
+
})()
|
|
88
|
+
JS
|
|
89
|
+
raw = {} unless raw.is_a?(::Hash)
|
|
90
|
+
@verbose = raw["verbose"] == true
|
|
91
|
+
@totals = System.parse_reactive_requests(raw["requests"])
|
|
92
|
+
@active = raw["active"] == true
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def selected = @kind ? @totals[@kind] : @totals.values.sum
|
|
96
|
+
|
|
97
|
+
def scope = @kind ? "kind: #{@kind.inspect}" : "any kind"
|
|
98
|
+
|
|
99
|
+
def noun = @expected == 1 ? "request" : "requests"
|
|
100
|
+
|
|
101
|
+
def totals_str = "{#{@totals.map { |k, v| "#{k}: #{v}" }.join(", ")}}"
|
|
102
|
+
|
|
103
|
+
# Same bounded, monotonic-clock poll as ReactiveValueMatcher. A `break
|
|
104
|
+
# false` inside the block ends the poll early (a terminal mismatch).
|
|
105
|
+
def poll_until
|
|
106
|
+
deadline = now + (@wait || ::Capybara.default_max_wait_time)
|
|
107
|
+
loop do
|
|
108
|
+
return true if yield
|
|
109
|
+
return false if now >= deadline
|
|
110
|
+
|
|
111
|
+
sleep 0.05
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -36,6 +36,30 @@ module Phlex
|
|
|
36
36
|
# twin of wait_for_turbo watching the Turbo progress bar.
|
|
37
37
|
ACTIVE_MARKER = "data-reactive-active"
|
|
38
38
|
|
|
39
|
+
# The <html> attribute the client keeps its per-kind request totals in
|
|
40
|
+
# (issue #279), as JSON ({"action":1,"defer":0}). Lockstep with
|
|
41
|
+
# reactive_controller.js REQUESTS_ATTR. Written only under the verbose
|
|
42
|
+
# gate (dev/test by default).
|
|
43
|
+
REQUESTS_ATTR = "data-reactive-requests"
|
|
44
|
+
|
|
45
|
+
# Parse the attribute value into { action:, defer: } Integers — zeros for
|
|
46
|
+
# an absent or garbled value. Shared by the helpers and
|
|
47
|
+
# ReactiveRequestsMatcher.
|
|
48
|
+
def self.parse_reactive_requests(raw)
|
|
49
|
+
parsed = raw.is_a?(::String) ? ::JSON.parse(raw) : {}
|
|
50
|
+
parsed = {} unless parsed.is_a?(::Hash)
|
|
51
|
+
{ action: reactive_request_total(parsed["action"]), defer: reactive_request_total(parsed["defer"]) }
|
|
52
|
+
rescue ::JSON::ParserError
|
|
53
|
+
{ action: 0, defer: 0 }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# One total as an Integer. Only a number or a numeric string counts; any
|
|
57
|
+
# other JSON value (array, object, boolean, null) is garbled, so zero.
|
|
58
|
+
def self.reactive_request_total(value)
|
|
59
|
+
value.is_a?(::Numeric) || value.is_a?(::String) ? value.to_i : 0
|
|
60
|
+
end
|
|
61
|
+
private_class_method :reactive_request_total
|
|
62
|
+
|
|
39
63
|
# Block until the reactive layer is IDLE — every dispatch round trip and
|
|
40
64
|
# deferred render has settled and the <html data-reactive-active> marker is
|
|
41
65
|
# gone. The system-test twin of wait_for_turbo (which watches the Turbo
|
|
@@ -92,6 +116,42 @@ module Phlex
|
|
|
92
116
|
def have_reactive_text(id, value, **)
|
|
93
117
|
have_css("##{id}", text: value, **)
|
|
94
118
|
end
|
|
119
|
+
|
|
120
|
+
# How many reactive requests the page has made since the last
|
|
121
|
+
# reset_reactive_requests! (or since the page loaded), per kind (issue
|
|
122
|
+
# #279):
|
|
123
|
+
#
|
|
124
|
+
# reactive_request_count # => { action: 1, defer: 0 }
|
|
125
|
+
#
|
|
126
|
+
# A snapshot, not a wait — barrier on wait_for_reactive (or use
|
|
127
|
+
# have_reactive_requests) before reading it after a gesture.
|
|
128
|
+
def reactive_request_count
|
|
129
|
+
System.parse_reactive_requests(
|
|
130
|
+
page.evaluate_script("document.documentElement.getAttribute(#{REQUESTS_ATTR.to_json})")
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Baseline the request totals to zero. The attribute IS the client's
|
|
135
|
+
# store, so writing zeros re-baselines it — including after a Turbo Drive
|
|
136
|
+
# visit, where the <html> element (and its totals) survives navigation.
|
|
137
|
+
def reset_reactive_requests!
|
|
138
|
+
zeros = { action: 0, defer: 0 }.to_json
|
|
139
|
+
page.execute_script("document.documentElement.setAttribute(#{REQUESTS_ATTR.to_json}, '#{zeros}')")
|
|
140
|
+
nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Assert (waiting) that exactly `count` reactive requests were made — of
|
|
144
|
+
# `kind` (:action or :defer), or of any kind — since the last reset, AND
|
|
145
|
+
# that nothing is still in flight. Fails at once if the count overshoots
|
|
146
|
+
# (totals only grow until a reset).
|
|
147
|
+
#
|
|
148
|
+
# reset_reactive_requests!
|
|
149
|
+
# find("#open-panel").click
|
|
150
|
+
# expect(page).to have_reactive_requests(1)
|
|
151
|
+
# expect(page).to have_reactive_requests(0, kind: :defer)
|
|
152
|
+
def have_reactive_requests(count, kind: nil, timeout: nil, wait: nil)
|
|
153
|
+
ReactiveRequestsMatcher.new(count, kind:, wait: timeout || wait)
|
|
154
|
+
end
|
|
95
155
|
# rubocop:enable Naming/PredicatePrefix
|
|
96
156
|
|
|
97
157
|
# A waiting matcher that asserts a field's live `.value` PROPERTY (issue
|
|
@@ -190,3 +250,5 @@ module Phlex
|
|
|
190
250
|
end
|
|
191
251
|
end
|
|
192
252
|
end
|
|
253
|
+
|
|
254
|
+
require "phlex/reactive/test_helpers/system/reactive_requests_matcher"
|
data/lib/phlex/reactive.rb
CHANGED
|
@@ -133,6 +133,12 @@ module Phlex
|
|
|
133
133
|
# ActionPolicy::Unauthorized, ...).
|
|
134
134
|
attr_accessor :authorization_errors
|
|
135
135
|
|
|
136
|
+
# Import maps `phlex_reactive:doctor` checks for the `phlex/reactive/early`
|
|
137
|
+
# pin (issue #307): a callable returning `{ name => Importmap::Map }`, for
|
|
138
|
+
# an app with more than one map (one per entry point). nil (the default)
|
|
139
|
+
# checks `Rails.application.importmap` only.
|
|
140
|
+
attr_accessor :importmaps
|
|
141
|
+
|
|
136
142
|
# The path the action endpoint is mounted at. Default "/reactive/actions".
|
|
137
143
|
# Set before boot if it collides with an app route. The client runtime
|
|
138
144
|
# reads it from a <meta name="phlex-reactive-action-path"> tag if present,
|
|
@@ -532,6 +538,29 @@ module Phlex
|
|
|
532
538
|
instance.view_context
|
|
533
539
|
end
|
|
534
540
|
|
|
541
|
+
# --- Early triggers (issue #273) -----------------------------------
|
|
542
|
+
|
|
543
|
+
# How long (ms) a trigger captured by phlex/reactive/early before the
|
|
544
|
+
# controller connected stays replayable. Older entries are dropped on
|
|
545
|
+
# connect (warned under data-reactive-verbose): a click from long ago must
|
|
546
|
+
# not fire a request out of nowhere. The client reads it from a meta tag
|
|
547
|
+
# in the host layout (the engine can't inject into it):
|
|
548
|
+
# <meta name="phlex-reactive-early-ttl" content="<%= Phlex::Reactive.early_event_ttl_ms %>">
|
|
549
|
+
# Without the meta the client uses the same 10 s default. nil resets.
|
|
550
|
+
# Validated at assignment: the client reads a non-positive TTL as "use
|
|
551
|
+
# the default", so 0 can never mean "replay nothing" — fail here instead.
|
|
552
|
+
def early_event_ttl_ms=(value)
|
|
553
|
+
unless value.nil? || (value.is_a?(Integer) && value.positive?)
|
|
554
|
+
raise ArgumentError, "early_event_ttl_ms must be a positive Integer (milliseconds) or nil, got #{value.inspect}"
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
@early_event_ttl_ms = value
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def early_event_ttl_ms
|
|
561
|
+
@early_event_ttl_ms ||= 10_000
|
|
562
|
+
end
|
|
563
|
+
|
|
535
564
|
# --- Deferred reply segments (issue #165) --------------------------
|
|
536
565
|
|
|
537
566
|
# Lifetime (seconds) of a defer token. It only needs to cover the
|
|
@@ -552,6 +581,44 @@ module Phlex
|
|
|
552
581
|
@defer_path ||= "/reactive/defer"
|
|
553
582
|
end
|
|
554
583
|
|
|
584
|
+
# The path prefix of the cacheable-fragment GET endpoint (issue #277):
|
|
585
|
+
# GET <fragment_path>/<signed id>. Default "/reactive/fragment"; set
|
|
586
|
+
# before boot if it collides. The shell renders the full URL, but the
|
|
587
|
+
# client only fetches one under the path it knows: when you change this,
|
|
588
|
+
# also render <meta name="phlex-reactive-fragment-path" content="…">.
|
|
589
|
+
attr_writer :fragment_path
|
|
590
|
+
|
|
591
|
+
def fragment_path
|
|
592
|
+
@fragment_path ||= "/reactive/fragment"
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
# The longest max-age (seconds) a `reactive_lazy cache:` component may
|
|
596
|
+
# ask the browser to keep its fragment for. A component declaring more
|
|
597
|
+
# is answered with this cap. Within max-age the browser reuses its copy
|
|
598
|
+
# WITHOUT asking the server — so this also bounds how long a revoked
|
|
599
|
+
# permission can keep showing a stale fragment. Default 1 hour; nil
|
|
600
|
+
# restores it; 0 makes every cached fragment revalidate on each use.
|
|
601
|
+
def fragment_cache_max_age_limit=(value)
|
|
602
|
+
seconds = value.is_a?(ActiveSupport::Duration) && value.to_i == value ? value.to_i : value
|
|
603
|
+
unless seconds.nil? || (seconds.is_a?(::Integer) && !seconds.negative?)
|
|
604
|
+
raise ArgumentError,
|
|
605
|
+
"Phlex::Reactive.fragment_cache_max_age_limit must be a whole, non-negative number of seconds " \
|
|
606
|
+
"(or nil for the default) — got #{value.inspect}"
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
@fragment_cache_max_age_limit = seconds
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
def fragment_cache_max_age_limit
|
|
613
|
+
@fragment_cache_max_age_limit ||= 3600
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
# The deterministic, no-expiry, purpose-scoped fragment id of an identity
|
|
617
|
+
# payload, and its verification (issue #277). See Phlex::Reactive::Fragment.
|
|
618
|
+
def sign_fragment(payload) = Fragment.sign(payload)
|
|
619
|
+
|
|
620
|
+
def verify_fragment(id) = Fragment.verify(id)
|
|
621
|
+
|
|
555
622
|
# How deferred segments reach the actor: :auto (push iff capable, else
|
|
556
623
|
# pull), :fetch (always pull), :stream (push; degrades to pull with a
|
|
557
624
|
# warning when the capability is absent). Validated at assignment — a
|
|
@@ -1056,6 +1123,7 @@ module Phlex
|
|
|
1056
1123
|
# The controller a correctly-mounted action path resolves to. Used by the
|
|
1057
1124
|
# route guard below.
|
|
1058
1125
|
ACTIONS_CONTROLLER = "phlex/reactive/actions"
|
|
1126
|
+
FRAGMENTS_CONTROLLER = "phlex/reactive/fragments"
|
|
1059
1127
|
|
|
1060
1128
|
# True when a POST to `path` resolves to the gem's ActionsController. A host
|
|
1061
1129
|
# catch-all route (match "*path", ...) appended above the engine's route
|
|
@@ -1076,6 +1144,20 @@ module Phlex
|
|
|
1076
1144
|
false
|
|
1077
1145
|
end
|
|
1078
1146
|
|
|
1147
|
+
# Does GET <fragment_path>/<id> resolve to the gem's FragmentsController
|
|
1148
|
+
# (issue #277)? A host GET catch-all shadows it otherwise, and every
|
|
1149
|
+
# `reactive_lazy cache:` fetch then gets the host's fallback page. The
|
|
1150
|
+
# doctor reports it, like the defer route.
|
|
1151
|
+
def fragment_route_ok?(path = fragment_path)
|
|
1152
|
+
return false unless defined?(::Rails) && ::Rails.application
|
|
1153
|
+
|
|
1154
|
+
ensure_routes_loaded
|
|
1155
|
+
recognized = ::Rails.application.routes.recognize_path("#{path}/probe", method: :get)
|
|
1156
|
+
recognized[:controller] == FRAGMENTS_CONTROLLER
|
|
1157
|
+
rescue ActionController::RoutingError, ActiveRecord::RecordNotFound
|
|
1158
|
+
false
|
|
1159
|
+
end
|
|
1160
|
+
|
|
1079
1161
|
# Log a clear warning (once, at boot) when the action path doesn't resolve
|
|
1080
1162
|
# to the gem controller — pointing at the catch-all shadow rather than
|
|
1081
1163
|
# leaving an adopter to guess. Called from the engine's after_initialize.
|
|
@@ -1184,6 +1266,8 @@ loader.ignore("#{lib}/phlex/reactive/test_helpers/matchers.rb")
|
|
|
1184
1266
|
# only when Capybara is present, so the loader must ignore it (otherwise an
|
|
1185
1267
|
# eager-load in production would define browser helpers with no Capybara).
|
|
1186
1268
|
loader.ignore("#{lib}/phlex/reactive/test_helpers/system.rb")
|
|
1269
|
+
# ...and its matcher subdirectory (issue #279), required by system.rb itself.
|
|
1270
|
+
loader.ignore("#{lib}/phlex/reactive/test_helpers/system")
|
|
1187
1271
|
# The MCP diagnostic tool tree (issue #168) subclasses the OPTIONAL `mcp` gem's
|
|
1188
1272
|
# constants (MCP::Tool) at class-definition time, so the whole mcp/ subdirectory
|
|
1189
1273
|
# must stay out of the autoloader — Phlex::Reactive::MCP.load! requires it in
|
|
@@ -12,14 +12,18 @@ namespace :phlex_reactive do
|
|
|
12
12
|
abort unless Phlex::Reactive::Doctor.run
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
desc "List every declared reactive action (component | action | params | file:line | auth);
|
|
15
|
+
desc "List every declared reactive action (component | action | params | file:line | auth); " \
|
|
16
|
+
"FORMAT=json for tooling, UNVERIFIED=1 for only the unverified rows"
|
|
16
17
|
task actions: :environment do
|
|
17
18
|
# eager_load! so every app component is in the Streamable registry the
|
|
18
19
|
# Inspector reads. Plain text by default (no ANSI, like the doctor);
|
|
19
20
|
# FORMAT=json emits a parseable array for tooling. Names/paths/schemas only.
|
|
21
|
+
# UNVERIFIED=1 keeps only actions with neither a detected authorization call
|
|
22
|
+
# nor a skip_verify_authorized — the review queue (issue #278).
|
|
20
23
|
Rails.application.eager_load!
|
|
21
24
|
format = ENV["FORMAT"].to_s.downcase == "json" ? :json : :text
|
|
22
|
-
|
|
25
|
+
unverified_only = %w[1 true yes].include?(ENV["UNVERIFIED"].to_s.downcase)
|
|
26
|
+
puts Phlex::Reactive::Inspector::Report.actions(Phlex::Reactive::Inspector.components, format:, unverified_only:)
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
desc "Fuzzy-find a reactive component and print its actions with method-definition source — phlex_reactive:find[query]"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: phlex-reactive
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -118,6 +118,7 @@ files:
|
|
|
118
118
|
- README.md
|
|
119
119
|
- app/assets/stylesheets/phlex/reactive/effects.css
|
|
120
120
|
- app/controllers/phlex/reactive/actions_controller.rb
|
|
121
|
+
- app/controllers/phlex/reactive/fragments_controller.rb
|
|
121
122
|
- app/javascript/phlex/reactive/compute.js
|
|
122
123
|
- app/javascript/phlex/reactive/compute.min.js
|
|
123
124
|
- app/javascript/phlex/reactive/compute.min.js.map
|
|
@@ -127,12 +128,43 @@ files:
|
|
|
127
128
|
- app/javascript/phlex/reactive/confirm_predicate.js
|
|
128
129
|
- app/javascript/phlex/reactive/confirm_predicate.min.js
|
|
129
130
|
- app/javascript/phlex/reactive/confirm_predicate.min.js.map
|
|
131
|
+
- app/javascript/phlex/reactive/core.js
|
|
132
|
+
- app/javascript/phlex/reactive/core.min.js
|
|
133
|
+
- app/javascript/phlex/reactive/core.min.js.map
|
|
134
|
+
- app/javascript/phlex/reactive/early.js
|
|
135
|
+
- app/javascript/phlex/reactive/early.min.js
|
|
136
|
+
- app/javascript/phlex/reactive/early.min.js.map
|
|
137
|
+
- app/javascript/phlex/reactive/features/bindings.js
|
|
138
|
+
- app/javascript/phlex/reactive/features/bindings.min.js
|
|
139
|
+
- app/javascript/phlex/reactive/features/bindings.min.js.map
|
|
140
|
+
- app/javascript/phlex/reactive/features/compute.js
|
|
141
|
+
- app/javascript/phlex/reactive/features/compute.min.js
|
|
142
|
+
- app/javascript/phlex/reactive/features/compute.min.js.map
|
|
143
|
+
- app/javascript/phlex/reactive/features/defer.js
|
|
144
|
+
- app/javascript/phlex/reactive/features/defer.min.js
|
|
145
|
+
- app/javascript/phlex/reactive/features/defer.min.js.map
|
|
146
|
+
- app/javascript/phlex/reactive/features/devtools.js
|
|
147
|
+
- app/javascript/phlex/reactive/features/devtools.min.js
|
|
148
|
+
- app/javascript/phlex/reactive/features/devtools.min.js.map
|
|
149
|
+
- app/javascript/phlex/reactive/features/effects.js
|
|
150
|
+
- app/javascript/phlex/reactive/features/effects.min.js
|
|
151
|
+
- app/javascript/phlex/reactive/features/effects.min.js.map
|
|
152
|
+
- app/javascript/phlex/reactive/features/form.js
|
|
153
|
+
- app/javascript/phlex/reactive/features/form.min.js
|
|
154
|
+
- app/javascript/phlex/reactive/features/form.min.js.map
|
|
155
|
+
- app/javascript/phlex/reactive/features/hints.js
|
|
156
|
+
- app/javascript/phlex/reactive/features/hints.min.js
|
|
157
|
+
- app/javascript/phlex/reactive/features/hints.min.js.map
|
|
158
|
+
- app/javascript/phlex/reactive/features/persist.js
|
|
159
|
+
- app/javascript/phlex/reactive/features/persist.min.js
|
|
160
|
+
- app/javascript/phlex/reactive/features/persist.min.js.map
|
|
130
161
|
- app/javascript/phlex/reactive/inspect.js
|
|
131
162
|
- app/javascript/phlex/reactive/inspect.min.js
|
|
132
163
|
- app/javascript/phlex/reactive/inspect.min.js.map
|
|
133
164
|
- app/javascript/phlex/reactive/reactive_controller.js
|
|
134
165
|
- app/javascript/phlex/reactive/reactive_controller.min.js
|
|
135
166
|
- app/javascript/phlex/reactive/reactive_controller.min.js.map
|
|
167
|
+
- app/javascript/phlex/reactive/runtime.js
|
|
136
168
|
- lib/generators/phlex/reactive/claude/USAGE
|
|
137
169
|
- lib/generators/phlex/reactive/claude/claude_generator.rb
|
|
138
170
|
- lib/generators/phlex/reactive/component/USAGE
|
|
@@ -160,11 +192,15 @@ files:
|
|
|
160
192
|
- lib/phlex/reactive/component/identity.rb
|
|
161
193
|
- lib/phlex/reactive/component/lazy.rb
|
|
162
194
|
- lib/phlex/reactive/component/registry.rb
|
|
195
|
+
- lib/phlex/reactive/component/selection.rb
|
|
163
196
|
- lib/phlex/reactive/defer.rb
|
|
164
197
|
- lib/phlex/reactive/deferred_render_job.rb
|
|
165
198
|
- lib/phlex/reactive/doctor.rb
|
|
199
|
+
- lib/phlex/reactive/doctor/fiber_isolation.rb
|
|
200
|
+
- lib/phlex/reactive/dormant.rb
|
|
166
201
|
- lib/phlex/reactive/effects.rb
|
|
167
202
|
- lib/phlex/reactive/engine.rb
|
|
203
|
+
- lib/phlex/reactive/fragment.rb
|
|
168
204
|
- lib/phlex/reactive/inspector.rb
|
|
169
205
|
- lib/phlex/reactive/inspector/report.rb
|
|
170
206
|
- lib/phlex/reactive/js.rb
|
|
@@ -191,6 +227,7 @@ files:
|
|
|
191
227
|
- lib/phlex/reactive/test_helpers.rb
|
|
192
228
|
- lib/phlex/reactive/test_helpers/matchers.rb
|
|
193
229
|
- lib/phlex/reactive/test_helpers/system.rb
|
|
230
|
+
- lib/phlex/reactive/test_helpers/system/reactive_requests_matcher.rb
|
|
194
231
|
- lib/phlex/reactive/version.rb
|
|
195
232
|
- lib/tasks/phlex_reactive.rake
|
|
196
233
|
homepage: https://github.com/zoolutions/phlex-reactive
|