glib-web 5.1.5 → 6.3.1
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/app/controllers/concerns/glib/json/ui.rb +10 -0
- data/app/controllers/glib/errors_controller.rb +57 -1
- data/app/helpers/glib/json_ui/action_builder/logics.rb +14 -0
- data/app/helpers/glib/json_ui/action_builder/windows.rb +20 -5
- data/app/helpers/glib/json_ui/json_logic_lint.rb +82 -0
- data/app/helpers/glib/json_ui/view_builder/panels.rb +14 -0
- data/app/views/json_ui/garage/test_page/fields_upload.json.jbuilder +1 -1
- data/app/views/json_ui/garage/test_page/form_dirty_hooks.json.jbuilder +31 -0
- data/app/views/json_ui/garage/test_page/form_dynamic_dirty.json.jbuilder +41 -0
- data/app/views/json_ui/garage/test_page/form_removed_field.json.jbuilder +55 -0
- data/app/views/json_ui/garage/test_page/form_removed_field_onload.json.jbuilder +34 -0
- data/app/views/json_ui/garage/test_page/form_touched_validation.json.jbuilder +35 -0
- data/app/views/json_ui/garage/test_page/logics_run.json.jbuilder +79 -0
- data/app/views/json_ui/garage/test_page/logics_run_multi_form.json.jbuilder +45 -0
- data/app/views/json_ui/garage/test_page/logics_set.json.jbuilder +19 -0
- data/app/views/json_ui/garage/test_page/panels_bulkEdit2.json.jbuilder +3 -0
- data/app/views/json_ui/garage/test_page/run_multiple.json.jbuilder +31 -0
- data/app/views/json_ui/garage/test_page/windows_reload.json.jbuilder +57 -0
- data/lib/glib/engine.rb +132 -0
- data/lib/glib/rubocop.rb +0 -1
- data/lib/glib/snapshot.rb +57 -12
- data/lib/glib/test/parallel_coverage.rb +16 -0
- data/lib/tasks/db.rake +6 -0
- metadata +12 -3
- data/lib/glib/rubocop/cops/multiline_method_call_style.rb +0 -250
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
json.title 'Test Page (Run Multiple)'
|
|
2
|
+
|
|
3
|
+
page = json_ui_page json
|
|
4
|
+
|
|
5
|
+
render 'json_ui/garage/test_page/header', json: json, page: page
|
|
6
|
+
|
|
7
|
+
page.body childViews: ->(body) do
|
|
8
|
+
body.panels_responsive padding: glib_json_padding_body, childViews: ->(res) do
|
|
9
|
+
res.h2 text: 'runMultiple ordering'
|
|
10
|
+
res.spacer height: 8
|
|
11
|
+
res.button \
|
|
12
|
+
text: 'Run sequence',
|
|
13
|
+
onClick: ->(action) do
|
|
14
|
+
action.runMultiple childActions: ->(multiple) do
|
|
15
|
+
# Two writes to the SAME target: the final text proves in-order,
|
|
16
|
+
# sequential execution (last write wins).
|
|
17
|
+
multiple.logics_set targetId: 'sequence_status', data: { text: 'STEP 1' }
|
|
18
|
+
multiple.logics_set targetId: 'sequence_status', data: { text: 'STEP 2' }
|
|
19
|
+
# Nested runMultiple must execute within the outer sequence.
|
|
20
|
+
multiple.runMultiple childActions: ->(nested) do
|
|
21
|
+
nested.logics_set targetId: 'nested_status', data: { text: 'NESTED DONE' }
|
|
22
|
+
end
|
|
23
|
+
multiple.logics_set targetId: 'sequence_status', data: { text: 'STEP 3' }
|
|
24
|
+
multiple.snackbars_alert message: 'Sequence finished'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
res.spacer height: 8
|
|
28
|
+
res.label id: 'sequence_status', text: 'PENDING'
|
|
29
|
+
res.label id: 'nested_status', text: 'PENDING'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
json.title 'Test Page (Windows Reload)'
|
|
2
|
+
|
|
3
|
+
page = json_ui_page json
|
|
4
|
+
|
|
5
|
+
render 'json_ui/garage/test_page/header', json: json, page: page
|
|
6
|
+
|
|
7
|
+
# Contract page for `windows_reload` (see the npm package's
|
|
8
|
+
# actions/windows/reload.js): reload re-renders the CURRENT page from the
|
|
9
|
+
# server in place -- user input is discarded (guarded by the dirty prompt),
|
|
10
|
+
# scroll position is preserved, and the history entry is REPLACED, so Back
|
|
11
|
+
# skips the pre-reload state.
|
|
12
|
+
page.body childViews: ->(body) do
|
|
13
|
+
body.panels_responsive padding: glib_json_padding_body, childViews: ->(res) do
|
|
14
|
+
res.h2 text: 'Windows reload contract'
|
|
15
|
+
|
|
16
|
+
# Changes on every render so specs can detect that a reload completed
|
|
17
|
+
# (a bare reload keeps the same URL, so the URL proves nothing).
|
|
18
|
+
res.label text: "Render stamp: #{SecureRandom.hex(8)}"
|
|
19
|
+
res.label text: "Step: #{params[:step] || 'initial'}"
|
|
20
|
+
res.spacer height: 8
|
|
21
|
+
|
|
22
|
+
res.panels_form \
|
|
23
|
+
url: json_ui_garage_url(path: 'forms/generic_post'),
|
|
24
|
+
method: 'post',
|
|
25
|
+
childViews: ->(form) do
|
|
26
|
+
form.fields_text width: 'matchParent', name: 'user[nickname]', label: 'Nickname'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
res.spacer height: 8
|
|
30
|
+
res.button \
|
|
31
|
+
text: 'windows/reload',
|
|
32
|
+
onClick: ->(action) do
|
|
33
|
+
action.windows_reload
|
|
34
|
+
end
|
|
35
|
+
res.spacer height: 8
|
|
36
|
+
res.button \
|
|
37
|
+
text: 'windows/open (step two)',
|
|
38
|
+
onClick: ->(action) do
|
|
39
|
+
action.windows_open url: json_ui_garage_url(path: 'test_page/windows_reload', step: 'two')
|
|
40
|
+
end
|
|
41
|
+
res.spacer height: 8
|
|
42
|
+
res.button \
|
|
43
|
+
text: 'windows/reload (step three)',
|
|
44
|
+
onClick: ->(action) do
|
|
45
|
+
action.windows_reload url: json_ui_garage_url(path: 'test_page/windows_reload', step: 'three')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Tall filler so the page scrolls; the bottom button lets specs trigger a
|
|
49
|
+
# reload without Cypress scrolling back up to reach a button.
|
|
50
|
+
res.spacer height: 2000
|
|
51
|
+
res.button \
|
|
52
|
+
text: 'windows/reload (from bottom)',
|
|
53
|
+
onClick: ->(action) do
|
|
54
|
+
action.windows_reload
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/glib/engine.rb
CHANGED
|
@@ -1,7 +1,139 @@
|
|
|
1
|
+
require 'active_snapshot/models/snapshot'
|
|
2
|
+
|
|
1
3
|
module Glib
|
|
2
4
|
module Web
|
|
3
5
|
class Engine < ::Rails::Engine
|
|
4
6
|
# isolate_namespace Glib::Web
|
|
7
|
+
|
|
8
|
+
# Extends ActiveSnapshot::Snapshot (the snapshot record class) with a structured-diff
|
|
9
|
+
# reader so consumers don't have to re-parse raw Hashdiff tuples from the `diff` column.
|
|
10
|
+
# Runs as an engine initializer (not at file-load time) because `scope` and other AR
|
|
11
|
+
# class methods aren't available until ActiveRecord is fully loaded.
|
|
12
|
+
initializer 'glib.active_snapshot_ext' do
|
|
13
|
+
ActiveSnapshot::Snapshot.class_eval do
|
|
14
|
+
unless const_defined?(:Change)
|
|
15
|
+
const_set(:Change, Struct.new(:index, :action, :from, :to))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Mirror `Glib::ApplicationRecord.created_desc` so call sites can use the codebase-wide
|
|
19
|
+
# convention instead of `order(created_at: :desc)`.
|
|
20
|
+
unless respond_to?(:created_desc)
|
|
21
|
+
scope :created_desc, -> { order(created_at: :desc) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Parses the stored diff into a flat hash of Change structs:
|
|
25
|
+
# { 'item' => [Change, ...], 'association_name' => [Change, ...], ... }
|
|
26
|
+
# Returns an empty structure if no diff exists (e.g. the first snapshot).
|
|
27
|
+
# Reads from a dedicated `diff` column if the table has one (app override),
|
|
28
|
+
# otherwise falls back to `metadata['diff']` (glib-web's default storage).
|
|
29
|
+
#
|
|
30
|
+
# JSONB round-trips every value as a string, so datetime/date column values arrive as
|
|
31
|
+
# ISO8601 strings instead of Time/Date objects. The cast_* methods resolve the column
|
|
32
|
+
# type from the live model schema and cast the value back so consumers (e.g. timeline
|
|
33
|
+
# helpers) receive typed values. Casts are guarded:
|
|
34
|
+
# - Deleted column → type_for_attribute returns a default Value type (type=nil) → skipped
|
|
35
|
+
# - Deleted model → safe_constantize returns nil → skipped
|
|
36
|
+
# - Deleted association → reflect_on_association returns nil → skipped
|
|
37
|
+
# - Changed column type → type.cast returns nil for unparseable values (no crash)
|
|
38
|
+
# - Exotic type failure → rescue returns the raw value (defense-in-depth)
|
|
39
|
+
def changes_from_prev_version
|
|
40
|
+
diff = self[:diff] || metadata&.dig('diff')
|
|
41
|
+
return { 'item' => [] } if diff.nil?
|
|
42
|
+
|
|
43
|
+
root = item_type.to_s.safe_constantize
|
|
44
|
+
|
|
45
|
+
result = {
|
|
46
|
+
'item' => (diff['item'] || []).map { |data| cast_item_change(build_change(data), root) }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
(diff['associations'] || {}).each do |association, tuples|
|
|
50
|
+
assoc_model = root&.reflect_on_association(association.to_s)&.klass
|
|
51
|
+
result[association.to_s] = tuples.map { |data| cast_association_change(build_change(data), assoc_model) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
result
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Converts a single raw Hashdiff tuple (e.g. ["~", "field", old, new]) into a Change struct.
|
|
58
|
+
# For item-level tuples, `data[1]` is the column name (e.g. "title").
|
|
59
|
+
# For association tuples, `data[1]` is a numeric path like "[0]" (position in the collection).
|
|
60
|
+
# Both are captured as `index` so consumers can label rows via humanize.
|
|
61
|
+
def build_change(data)
|
|
62
|
+
actions = {
|
|
63
|
+
'+' => 'create',
|
|
64
|
+
'-' => 'destroy',
|
|
65
|
+
'~' => 'update'
|
|
66
|
+
}.freeze
|
|
67
|
+
|
|
68
|
+
change = ActiveSnapshot::Snapshot.const_get(:Change).new
|
|
69
|
+
change.action = actions[data[0].to_s]
|
|
70
|
+
|
|
71
|
+
path = data[1].to_s
|
|
72
|
+
bracket_match = path.match(/\[(?<index>[^\]]+)\]/)
|
|
73
|
+
change.index = bracket_match ? bracket_match[:index] : path
|
|
74
|
+
|
|
75
|
+
payload = data[2]
|
|
76
|
+
case change.action
|
|
77
|
+
when 'create'
|
|
78
|
+
change.from = nil
|
|
79
|
+
change.to = payload
|
|
80
|
+
when 'destroy'
|
|
81
|
+
change.from = payload
|
|
82
|
+
change.to = nil
|
|
83
|
+
when 'update'
|
|
84
|
+
change.from = payload
|
|
85
|
+
change.to = data[3]
|
|
86
|
+
else
|
|
87
|
+
raise "Unexpected Hashdiff op: #{data[0].inspect}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
change
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
# Item-level change: from/to are scalar column values. Cast each to the column's real type.
|
|
95
|
+
def cast_item_change(change, root)
|
|
96
|
+
change.from = cast_column_value(root, change.index, change.from)
|
|
97
|
+
change.to = cast_column_value(root, change.index, change.to)
|
|
98
|
+
change
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Association-level change: from/to are attribute hashes. Cast each attribute by the
|
|
102
|
+
# association model's column types.
|
|
103
|
+
def cast_association_change(change, assoc_model)
|
|
104
|
+
change.from = cast_attributes(assoc_model, change.from)
|
|
105
|
+
change.to = cast_attributes(assoc_model, change.to)
|
|
106
|
+
change
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Cast a single value when `column` is a date/time column on `model`; otherwise return it
|
|
110
|
+
# unchanged. Safeguards for schema drift after the snapshot was taken:
|
|
111
|
+
# - Deleted column → type_for_attribute returns a default type (type=nil) → skipped
|
|
112
|
+
# - Changed column type → type.cast may return nil for the old value → return raw value
|
|
113
|
+
# - Exotic failure → rescue returns raw value
|
|
114
|
+
# In all fallback cases the raw value is returned so the timeline never loses data.
|
|
115
|
+
def cast_column_value(model, column, value)
|
|
116
|
+
return value if value.nil? || model.nil?
|
|
117
|
+
|
|
118
|
+
type = model.type_for_attribute(column.to_s)
|
|
119
|
+
return value unless %i[datetime date time].include?(type.type)
|
|
120
|
+
|
|
121
|
+
casted = type.cast(value)
|
|
122
|
+
# If the column type changed after the snapshot was taken, the old value may not
|
|
123
|
+
# be parseable as the new type — type.cast returns nil silently. Return the raw
|
|
124
|
+
# value so the timeline still shows the original data instead of "<not set>".
|
|
125
|
+
casted.nil? ? value : casted
|
|
126
|
+
rescue StandardError
|
|
127
|
+
value
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def cast_attributes(model, attrs)
|
|
131
|
+
return attrs unless model && attrs.is_a?(Hash)
|
|
132
|
+
|
|
133
|
+
attrs.to_h { |column, value| [column, cast_column_value(model, column, value)] }
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
5
137
|
end
|
|
6
138
|
end
|
|
7
139
|
end
|
data/lib/glib/rubocop.rb
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
require_relative 'rubocop/cops/localize'
|
|
2
|
-
require_relative 'rubocop/cops/multiline_method_call_style'
|
|
3
2
|
require_relative 'rubocop/cops/json_ui/base_nested_parameter'
|
|
4
3
|
require_relative 'rubocop/cops/json_ui/nested_block_parameter'
|
|
5
4
|
require_relative 'rubocop/cops/json_ui/nested_action_parameter'
|
data/lib/glib/snapshot.rb
CHANGED
|
@@ -95,8 +95,20 @@ module Glib
|
|
|
95
95
|
|
|
96
96
|
raise 'unknown action' if !known_actions.include?(action.to_s)
|
|
97
97
|
|
|
98
|
+
# Cheap pre-check WITHOUT a lock — the overwhelmingly common case is "nothing
|
|
99
|
+
# changed". same_as_before? is a pure read (it re-queries snapshot_prev and
|
|
100
|
+
# associations each call) and cannot produce a false "same", so returning nil
|
|
101
|
+
# here is always safe and skips the row lock entirely.
|
|
102
|
+
return nil if same_as_before?
|
|
103
|
+
|
|
98
104
|
result = nil
|
|
99
105
|
with_lock do
|
|
106
|
+
# Re-check under the lock: another writer may have snapshotted this exact
|
|
107
|
+
# change between the unlocked pre-check and here. NOT optional — without it,
|
|
108
|
+
# every concurrent writer that passed the pre-check would compute
|
|
109
|
+
# last_version + 1 and insert a duplicate snapshot of the same change.
|
|
110
|
+
next if same_as_before?
|
|
111
|
+
|
|
100
112
|
version = last_version + 1
|
|
101
113
|
metadata = {
|
|
102
114
|
action: action,
|
|
@@ -114,11 +126,8 @@ module Glib
|
|
|
114
126
|
snapshot_obj.delete(:user)
|
|
115
127
|
end
|
|
116
128
|
|
|
117
|
-
|
|
118
|
-
if
|
|
119
|
-
result = create_snapshot!(**snapshot_obj)
|
|
120
|
-
remove_old_snapshot if result.present?
|
|
121
|
-
end
|
|
129
|
+
result = create_snapshot!(**snapshot_obj)
|
|
130
|
+
remove_old_snapshot if result.present?
|
|
122
131
|
end
|
|
123
132
|
result
|
|
124
133
|
end
|
|
@@ -151,11 +160,13 @@ module Glib
|
|
|
151
160
|
associations = {}
|
|
152
161
|
end
|
|
153
162
|
|
|
163
|
+
item_attrs = normalize_attributes(item.attributes.except(*ignore_keys))
|
|
164
|
+
current_attrs = normalize_attributes(attributes.except(*ignore_keys))
|
|
154
165
|
obj = {
|
|
155
|
-
'item' => ::Hashdiff.diff(
|
|
166
|
+
'item' => ::Hashdiff.diff(item_attrs, current_attrs)
|
|
156
167
|
}
|
|
157
168
|
|
|
158
|
-
obj['associations'] = associations_for_snapshot.reduce({}) do |prev, curr|
|
|
169
|
+
obj['associations'] = associations_for_snapshot.reduce({}) do |prev, curr|
|
|
159
170
|
association_records = records_for_snapshot(curr)
|
|
160
171
|
first_record_off_same_collection = association_records.first
|
|
161
172
|
|
|
@@ -183,22 +194,29 @@ module Glib
|
|
|
183
194
|
now = association_records
|
|
184
195
|
|
|
185
196
|
if before.blank?
|
|
186
|
-
prev.merge(
|
|
197
|
+
prev.merge(
|
|
198
|
+
curr.to_s => ::Hashdiff.diff(
|
|
199
|
+
[],
|
|
200
|
+
now.map { |record| normalize_attributes(record.attributes.except(*association_ignored_keys)) }
|
|
201
|
+
)
|
|
202
|
+
)
|
|
187
203
|
else
|
|
188
204
|
diff = before.map.with_index do |record_before, index|
|
|
189
205
|
record_now = now.find_by(id: record_before.id)
|
|
190
206
|
if record_now.blank?
|
|
191
|
-
['-', "[#{index}]", record_before.attributes.except(*association_ignored_keys)]
|
|
207
|
+
['-', "[#{index}]", normalize_attributes(record_before.attributes.except(*association_ignored_keys))]
|
|
192
208
|
elsif record_now.present?
|
|
193
|
-
|
|
194
|
-
|
|
209
|
+
before_attrs = normalize_attributes(record_before.attributes.except(*association_ignored_keys))
|
|
210
|
+
now_attrs = normalize_attributes(record_now.attributes.except(*association_ignored_keys))
|
|
211
|
+
next if Hashdiff.diff(before_attrs, now_attrs).blank?
|
|
212
|
+
['~', "[#{index}]", before_attrs, now_attrs]
|
|
195
213
|
end
|
|
196
214
|
end.compact_blank
|
|
197
215
|
|
|
198
216
|
diff2 = now.map.with_index do |record_now, index|
|
|
199
217
|
record_before = before.detect { |record| record.id == record_now.id }
|
|
200
218
|
if record_before.blank?
|
|
201
|
-
['+', "[#{index}]", record_now.attributes.except(*association_ignored_keys)]
|
|
219
|
+
['+', "[#{index}]", normalize_attributes(record_now.attributes.except(*association_ignored_keys))]
|
|
202
220
|
end
|
|
203
221
|
end.compact_blank
|
|
204
222
|
|
|
@@ -296,5 +314,32 @@ module Glib
|
|
|
296
314
|
Array(records)
|
|
297
315
|
end
|
|
298
316
|
end
|
|
317
|
+
|
|
318
|
+
# Datetimes lose sub-millisecond precision on the snapshot store→reify round-trip:
|
|
319
|
+
# the JSON column encodes Time via ActiveSupport::JSON::Encoding.time_precision
|
|
320
|
+
# (default 3 = milliseconds, truncated not rounded), and reify casts that string
|
|
321
|
+
# back to a Time whose usec is a multiple of 1000. The live DB value keeps full
|
|
322
|
+
# microsecond precision, so Hashdiff (which compares Time values with ==) reports a
|
|
323
|
+
# phantom change on every re-snapshot of any watched sub-second datetime. Floor both
|
|
324
|
+
# sides to the same precision before the compare. The divisor is derived from
|
|
325
|
+
# time_precision so a consumer who raises it to 6 (microseconds) is not truncated.
|
|
326
|
+
# Because Hashdiff emits these floored values, the stored `metadata['diff']` also
|
|
327
|
+
# carries ms-precision datetimes (not just the in-memory comparison) — readers that
|
|
328
|
+
# introspect the raw diff see `.123000` even when the column holds `.123456`.
|
|
329
|
+
def normalize_attributes(hash)
|
|
330
|
+
divisor = snapshot_time_divisor
|
|
331
|
+
hash.transform_values { |value| normalize_datetime(value, divisor) }
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def normalize_datetime(value, divisor = snapshot_time_divisor)
|
|
335
|
+
return value unless value.respond_to?(:usec)
|
|
336
|
+
return value if divisor <= 1
|
|
337
|
+
|
|
338
|
+
value.change(usec: value.usec - value.usec % divisor)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def snapshot_time_divisor
|
|
342
|
+
10 ** (6 - ActiveSupport::JSON::Encoding.time_precision)
|
|
343
|
+
end
|
|
299
344
|
end
|
|
300
345
|
end
|
|
@@ -14,6 +14,7 @@ module Glib
|
|
|
14
14
|
# end
|
|
15
15
|
#
|
|
16
16
|
# This will:
|
|
17
|
+
# - Build Vite assets (if the app uses Vite) once, before workers fork
|
|
17
18
|
# - Enable parallel test execution using all available processors
|
|
18
19
|
# - Configure SimpleCov to track each worker separately
|
|
19
20
|
# - Automatically merge coverage results from all workers
|
|
@@ -21,6 +22,21 @@ module Glib
|
|
|
21
22
|
extend ActiveSupport::Concern
|
|
22
23
|
|
|
23
24
|
included do
|
|
25
|
+
# Vite's lazy autoBuild is not fork-safe: its build mutex is a thread
|
|
26
|
+
# Mutex, so when the test manifest is stale, every parallel worker's
|
|
27
|
+
# first asset lookup spawns its own full `vite build` (a node process
|
|
28
|
+
# using several hundred MB). With :number_of_processors workers that
|
|
29
|
+
# is enough concurrent builds to exhaust RAM and swap and OOM the
|
|
30
|
+
# machine. Build once here, in the parent process before forking, so
|
|
31
|
+
# the workers' freshness checks all pass without building. The guard
|
|
32
|
+
# mirrors the lazy path's own `should_build?` so this stays a no-op
|
|
33
|
+
# wherever the workers would never have built either (autoBuild off,
|
|
34
|
+
# or a dev server serving the test mode).
|
|
35
|
+
if defined?(ViteRuby) && ViteRuby.config.auto_build && !ViteRuby.instance.dev_server_running?
|
|
36
|
+
ViteRuby.commands.build ||
|
|
37
|
+
raise('vite build failed (a cached failed build also lands here; see log or run bin/vite build)')
|
|
38
|
+
end
|
|
39
|
+
|
|
24
40
|
# Set threshold for force parallelization.
|
|
25
41
|
parallelize(workers: :number_of_processors, threshold: 1)
|
|
26
42
|
|
data/lib/tasks/db.rake
CHANGED
|
@@ -81,6 +81,12 @@ namespace :db do
|
|
|
81
81
|
attrs[key] = value.to_json
|
|
82
82
|
elsif value.is_a? BigDecimal
|
|
83
83
|
attrs[key] = value.to_f
|
|
84
|
+
elsif value.is_a?(IPAddr)
|
|
85
|
+
# inet/cidr columns deserialize to IPAddr. Dump as a plain string so
|
|
86
|
+
# fixtures stay portable: IPAddr's internal `family` constant differs
|
|
87
|
+
# between Linux/WSL and macOS, so a YAML-dumped IPAddr object raises
|
|
88
|
+
# when loaded on the other OS. Strings re-parse via cast_value on any platform.
|
|
89
|
+
attrs[key] = value.to_s
|
|
84
90
|
end
|
|
85
91
|
end
|
|
86
92
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: glib-web
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 6.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ''
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-07-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activestorage
|
|
@@ -206,6 +206,7 @@ files:
|
|
|
206
206
|
- app/helpers/glib/json_ui/default.rb
|
|
207
207
|
- app/helpers/glib/json_ui/dynamic_field_builders.rb
|
|
208
208
|
- app/helpers/glib/json_ui/generic_builders.rb
|
|
209
|
+
- app/helpers/glib/json_ui/json_logic_lint.rb
|
|
209
210
|
- app/helpers/glib/json_ui/list_builders.rb
|
|
210
211
|
- app/helpers/glib/json_ui/menu_builder.rb
|
|
211
212
|
- app/helpers/glib/json_ui/page_helper.rb
|
|
@@ -420,7 +421,12 @@ files:
|
|
|
420
421
|
- app/views/json_ui/garage/test_page/fields_url_fragment.json.jbuilder
|
|
421
422
|
- app/views/json_ui/garage/test_page/flow.json.jbuilder
|
|
422
423
|
- app/views/json_ui/garage/test_page/form.json.jbuilder
|
|
424
|
+
- app/views/json_ui/garage/test_page/form_dirty_hooks.json.jbuilder
|
|
423
425
|
- app/views/json_ui/garage/test_page/form_dynamic.json.jbuilder
|
|
426
|
+
- app/views/json_ui/garage/test_page/form_dynamic_dirty.json.jbuilder
|
|
427
|
+
- app/views/json_ui/garage/test_page/form_removed_field.json.jbuilder
|
|
428
|
+
- app/views/json_ui/garage/test_page/form_removed_field_onload.json.jbuilder
|
|
429
|
+
- app/views/json_ui/garage/test_page/form_touched_validation.json.jbuilder
|
|
424
430
|
- app/views/json_ui/garage/test_page/forms.json.jbuilder
|
|
425
431
|
- app/views/json_ui/garage/test_page/grid.json.jbuilder
|
|
426
432
|
- app/views/json_ui/garage/test_page/horizontal.json.jbuilder
|
|
@@ -429,6 +435,8 @@ files:
|
|
|
429
435
|
- app/views/json_ui/garage/test_page/lifecycle.json.jbuilder
|
|
430
436
|
- app/views/json_ui/garage/test_page/list.json.jbuilder
|
|
431
437
|
- app/views/json_ui/garage/test_page/list_editable.json.jbuilder
|
|
438
|
+
- app/views/json_ui/garage/test_page/logics_run.json.jbuilder
|
|
439
|
+
- app/views/json_ui/garage/test_page/logics_run_multi_form.json.jbuilder
|
|
432
440
|
- app/views/json_ui/garage/test_page/logics_set.json.jbuilder
|
|
433
441
|
- app/views/json_ui/garage/test_page/multimedia_video.json.jbuilder
|
|
434
442
|
- app/views/json_ui/garage/test_page/pagination.json.jbuilder
|
|
@@ -437,6 +445,7 @@ files:
|
|
|
437
445
|
- app/views/json_ui/garage/test_page/popovers.json.jbuilder
|
|
438
446
|
- app/views/json_ui/garage/test_page/progressCircle.json.jbuilder
|
|
439
447
|
- app/views/json_ui/garage/test_page/responsive.json.jbuilder
|
|
448
|
+
- app/views/json_ui/garage/test_page/run_multiple.json.jbuilder
|
|
440
449
|
- app/views/json_ui/garage/test_page/scroll.json.jbuilder
|
|
441
450
|
- app/views/json_ui/garage/test_page/selectable.json.jbuilder
|
|
442
451
|
- app/views/json_ui/garage/test_page/sheets.json.jbuilder
|
|
@@ -453,6 +462,7 @@ files:
|
|
|
453
462
|
- app/views/json_ui/garage/test_page/web.json.jbuilder
|
|
454
463
|
- app/views/json_ui/garage/test_page/window.json.jbuilder
|
|
455
464
|
- app/views/json_ui/garage/test_page/windows.json.jbuilder
|
|
465
|
+
- app/views/json_ui/garage/test_page/windows_reload.json.jbuilder
|
|
456
466
|
- app/views/json_ui/garage/views/_chart_data.json.jbuilder
|
|
457
467
|
- app/views/json_ui/garage/views/_checkbox_expand.json.jbuilder
|
|
458
468
|
- app/views/json_ui/garage/views/_expand_shrink_ex.json.jbuilder
|
|
@@ -522,7 +532,6 @@ files:
|
|
|
522
532
|
- lib/glib/rubocop/cops/json_ui/nested_action_parameter.rb
|
|
523
533
|
- lib/glib/rubocop/cops/json_ui/nested_block_parameter.rb
|
|
524
534
|
- lib/glib/rubocop/cops/localize.rb
|
|
525
|
-
- lib/glib/rubocop/cops/multiline_method_call_style.rb
|
|
526
535
|
- lib/glib/rubocop/cops/test_name_parentheses.rb
|
|
527
536
|
- lib/glib/snapshot.rb
|
|
528
537
|
- lib/glib/test/changed_files_rubocop.rb
|