glib-web 7.0.1 → 7.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82ff8816e031f21b644701d78314fbba42562a8b29674a908f7f3d25932fcfdf
4
- data.tar.gz: '00383eecb92f3f6b4e94f7929cc582261b31151911110bcee36f4e52a024e5e1'
3
+ metadata.gz: f04f2fbd4180b48a75af32ed8bd22321fc818224ecfb00802edc88b04992c287
4
+ data.tar.gz: a5340d00dee67cacb6175428130344910c8ddbeaf69e73a64e0b8bfd8f13f639
5
5
  SHA512:
6
- metadata.gz: 4520d660ffd5ff5b2098a14123a4f88623590ec83e34852117c7f9b531e2065d1373be29950db8b44599ef2a8b1ef29208bd3c46ed6c53c50fc2dbc63fc93ca0
7
- data.tar.gz: 77f0fc4304dfc140ecdf4d40264d73562f49e2419f198dd0f90902e3c0083468e9801d06ad9e351621c8fc104292687cc54b35b5821c25dfa764e120a18baa63
6
+ metadata.gz: fbe295ea4776d69d9b07f9bf554be771195f1296f5a61c690274c28a72432b43d7f7093884b2fc20cf3d12502880590d86856cb965318c1b0d62383d7952801b
7
+ data.tar.gz: 7cbfcda693f301216d38735f7a288b6d0006f38f1bb59a1a5db6a6837d4ea1dbdea43f1663314b214bbdee046245e40c0a4126cceb4c7b35c2956b1541cd3140
@@ -11,7 +11,8 @@ module Glib::Params
11
11
 
12
12
  # Resolve a stringly-typed request param (or any boundary string) to one
13
13
  # symbol drawn from a closed allow-list (+allowed_symbols+), returning
14
- # +default+ when the value is missing, blank, or not recognised.
14
+ # +default+ when the value is missing, blank, or not recognised — with
15
+ # one default-config exception on mutating requests, spelled out below.
15
16
  #
16
17
  # This is the symbol-DoS-safe replacement for `params[:x].to_sym`. A bare
17
18
  # `.to_sym` on a raw value mints a symbol for every distinct string a client
@@ -30,16 +31,23 @@ module Glib::Params
30
31
  # Blank/missing values fall through to +default+ quietly on EVERY method
31
32
  # (forms legitimately post no value; no symbol's string form is blank).
32
33
  #
34
+ # +raise_on_unrecognized+ (default +true+) keeps that raise. Pass +false+
35
+ # for endpoints anyone can POST to — anonymous public endpoints where junk
36
+ # values are expected traffic: the present-but-unrecognised case then
37
+ # degrades to +default+ on every method instead of raising, so bot probes
38
+ # cannot 500 the endpoint. Endpoints whose only writers are the app's own
39
+ # UI keep the default — there the raise is the forgotten-entry detector.
40
+ #
33
41
  # +allowed_symbols+ may also contain strings (e.g. a Rails enum's `.keys`)
34
42
  # — the matched entry is symbolized on the way out, so the return value is
35
43
  # ALWAYS a symbol (or +default+). That `to_sym` runs only on the
36
44
  # developer-controlled list entry, never on +value+, so the DoS-safety
37
45
  # above is unaffected.
38
- def glib_allowlist_symbol_param(value, allowed_symbols, default:)
46
+ def glib_allowlist_symbol_param(value, allowed_symbols, default:, raise_on_unrecognized: true)
39
47
  match = allowed_symbols.find { |symbol| symbol.to_s == value.to_s }&.to_sym
40
48
  return match if match
41
49
 
42
- if value.present? && !request.get? && !request.head?
50
+ if raise_on_unrecognized && value.present? && !request.get? && !request.head?
43
51
  raise UnrecognizedValue,
44
52
  "Unrecognised #{request.request_method} param value #{value.to_s.truncate(80).inspect} " \
45
53
  "(allowed: #{allowed_symbols.map(&:to_sym).inspect})"
@@ -29,12 +29,18 @@ module Glib
29
29
  if defined?(Rollbar)
30
30
  # `GLib.log.warn` reports arrive with level=warning; everything else
31
31
  # (uncaught errors, `GLib.log.error`) is an error. Prefixed so
32
- # browser-side reports are distinguishable from server exceptions;
32
+ # client-side reports are distinguishable from server exceptions;
33
33
  # the details ride along as custom data for grouping.
34
34
  rollbar_level = params[:level] == 'warning' ? :warning : :error
35
+ # The prefix names the reporting runtime: the npm client sends no
36
+ # platform and stays "JS"; the Android renderer sends "ANDROID"
37
+ # (spec: thread 174). A platform TAG, not free text -- truncated so
38
+ # a crafted value cannot smuggle a payload into the message, and
39
+ # absent/blank degrades to the historical JS label.
40
+ platform = params[:platform].to_s[0, 16].presence || 'JS'
35
41
  Rollbar.public_send(
36
42
  rollbar_level,
37
- "JS: #{params[:message]}",
43
+ "#{platform}: #{params[:message]}",
38
44
  stack: params[:stack],
39
45
  url: params[:url],
40
46
  context: params[:context],
@@ -39,10 +39,10 @@ module Glib
39
39
  add_element_to_array 'action', m, *args
40
40
  else
41
41
  if !Rails.env.production? && @singleton_action_set
42
- raise "Multiple actions called on a singleton ActionBuilder. " \
42
+ raise 'Multiple actions called on a singleton ActionBuilder. ' \
43
43
  "Only one action is allowed. Previous action: '#{@singleton_action_name}', " \
44
44
  "current action: '#{m}'. Use `run_multiple` with `childActions` or " \
45
- "chain actions using `onClose` callbacks."
45
+ 'chain actions using `onClose` callbacks.'
46
46
  end
47
47
  @singleton_action_set = true
48
48
  @singleton_action_name = m
@@ -170,13 +170,6 @@ module Glib
170
170
  end
171
171
  end
172
172
 
173
- module Labels
174
- class Set < Action
175
- string :text
176
- action :onSet
177
- end
178
- end
179
-
180
173
  module Lists
181
174
  class Append < Action
182
175
  hash :row
@@ -452,47 +452,6 @@ class Glib::JsonUi::ViewBuilder
452
452
  end
453
453
  end
454
454
 
455
- class Table2 < View
456
- attr_reader :template
457
-
458
- hash :nextPage
459
- action :onScrollToTop
460
- action :onScrollToBottom
461
-
462
- def initialize(json, page)
463
- super
464
- @template = Glib::JsonUi::TableBuilders::Template.new(json, self)
465
- end
466
-
467
- def header(options = {})
468
- json.header do
469
- json.backgroundColor options.delete(:backgroundColor)
470
- json.cellViews do
471
- options.delete(:cellViews)&.call page.view_builder
472
- end
473
-
474
- json.colSpans options.delete(:colSpans)
475
- end
476
-
477
- raise "Invalid properties: #{options.keys}" if options.size > 0
478
- end
479
-
480
- def rows(options = {})
481
- block = options.delete(:builder)
482
- json.rows do
483
- if (objects = options.delete(:objects))
484
- objects.each_with_index do |object, index|
485
- block&.call template, object, index
486
- end
487
- else
488
- block&.call template
489
- end
490
- end
491
-
492
- raise "Invalid properties: #{options.keys}" if options.size > 0
493
- end
494
- end
495
-
496
455
  # The generic bulk-edit table: form-peer submission, per-row POSTs,
497
456
  # viewCells/viewHeaders, plus the full parse/gate/dedup config so any
498
457
  # consumer can compose specialized import behavior (it replaced the
@@ -382,12 +382,6 @@ module Glib
382
382
  end
383
383
  end
384
384
 
385
- class Datetime < View
386
- date_time :value
387
- string :format
388
- bool :tick
389
- end
390
-
391
385
  class ProgressBar < View
392
386
  float :value
393
387
  color :color
@@ -0,0 +1,92 @@
1
+ # Top-level (NOT namespaced under Glib::) by design — same constraint as
2
+ # SnapshotBlobReference: a top-level name keeps the facade's call-site
3
+ # ergonomics (`DLog.error` → `GLog.error`, one character per site), and the
4
+ # engine's autoloader resolves app/models files as top-level constants anyway.
5
+ #
6
+ # A routing facade, nothing more. The value is the seam: one place where
7
+ # destination, level policy, and (later) redaction live, and call sites that
8
+ # name their destination (`GLog.local_info` vs `GLog.error`).
9
+ module GLog
10
+ # Use this to log functional errors that should not happen, but could happen
11
+ # since it is dependent on external factors.
12
+ def self.error(*args)
13
+ remote(:error, *args)
14
+ end
15
+
16
+ def self.warn(*args)
17
+ remote(:warn, *args)
18
+ end
19
+
20
+ def self.info(*args)
21
+ remote(:info, *args)
22
+ end
23
+
24
+ def self.error_excluding_test(*args)
25
+ if Rails.env.test?
26
+ warn(*args)
27
+ else
28
+ error(*args)
29
+ end
30
+ end
31
+
32
+ def self.local_debug(*args)
33
+ Rails.logger.debug(*args)
34
+ end
35
+
36
+ def self.local_info(*args)
37
+ Rails.logger.info(*args)
38
+ end
39
+
40
+ def self.local_warn(*args)
41
+ Rails.logger.warn(*args)
42
+ end
43
+
44
+ def self.local_error(*args)
45
+ Rails.logger.error(*args)
46
+ end
47
+
48
+ # Remote methods route to Rollbar when the host defines it. The gem itself
49
+ # declares no rollbar dependency (see glib-web.gemspec), so without it they
50
+ # fall through to the local logger tagged [GLog:remote] — non-Rollbar
51
+ # consumers still see the message, and tests can exercise both modes.
52
+ # Tagged receives the bare tag (tagged adds its own brackets), and a logger
53
+ # without `tagged` (a plain BroadcastLogger, no TaggedLogging) gets a
54
+ # prefixed message instead: the fallback must never raise on the logger
55
+ # shape the host configured.
56
+ def self.remote(level, *args)
57
+ if defined?(Rollbar)
58
+ Rollbar.public_send(level, *args)
59
+ else
60
+ log_remotely_locally(level, args)
61
+ end
62
+ end
63
+
64
+ def self.log_remotely_locally(level, args)
65
+ message = format_remote_args(args)
66
+ logger = Rails.logger
67
+ if logger.respond_to?(:tagged)
68
+ logger.tagged('GLog:remote') { |tagged_logger| tagged_logger.public_send(level, message) }
69
+ else
70
+ logger.public_send(level, "[GLog:remote] #{message}")
71
+ end
72
+ end
73
+
74
+ # Rollbar's API is variadic (`Rollbar.error(e, 'msg', extra)`), and the
75
+ # facade inherits that shape; the local logger's level methods take ONE
76
+ # message. Render the argument list into a single line: exceptions as
77
+ # `Class: message`, strings as-is, everything else (context hashes, kwargs
78
+ # collected into a trailing hash) via inspect, joined with ' | '.
79
+ def self.format_remote_args(args)
80
+ args.map do |arg|
81
+ if arg.is_a?(Exception)
82
+ "#{arg.class}: #{arg.message}"
83
+ elsif arg.is_a?(String)
84
+ arg
85
+ else
86
+ arg.inspect
87
+ end
88
+ end.join(' | ')
89
+ end
90
+
91
+ private_class_method :remote, :log_remotely_locally, :format_remote_args
92
+ end
@@ -0,0 +1,69 @@
1
+ json.title 'Test Page (Bottom Banners)'
2
+
3
+ page = json_ui_page json
4
+
5
+ render 'json_ui/garage/test_page/header', json: json, page: page
6
+
7
+ page.body(
8
+ childViews: ->(body) do
9
+ body.panels_responsive(
10
+ padding: glib_json_padding_body,
11
+ childViews: ->(res) do
12
+ res.h2 text: 'Bottom Banners'
13
+ res.spacer height: 8
14
+ res.button text: "bottomBanners/open targetId: 'dock1'", onClick: ->(action) do
15
+ action.bottomBanners_open targetId: 'dock1', newView: ->(view) do
16
+ view.panels_responsive(
17
+ styleClass: ['frameless'],
18
+ childViews: ->(panel) do
19
+ panel.label text: 'Upload in progress'
20
+ panel.spacer height: 4
21
+ panel.button text: 'Details', onClick: ->(subaction) do
22
+ subaction.dialogs_alert message: 'Upload details'
23
+ end
24
+ end
25
+ )
26
+ end
27
+ end
28
+ res.spacer height: 4
29
+ res.button text: "bottomBanners/open targetId: 'dock2'", onClick: ->(action) do
30
+ action.bottomBanners_open targetId: 'dock2', newView: ->(view) do
31
+ view.panels_responsive(
32
+ styleClass: ['frameless'],
33
+ childViews: ->(panel) do
34
+ panel.banners_alert title: 'Background task', message: 'A long-running task is still active.'
35
+ end
36
+ )
37
+ end
38
+ end
39
+ res.spacer height: 4
40
+ res.button text: "bottomBanners/open replace 'dock1'", onClick: ->(action) do
41
+ action.bottomBanners_open targetId: 'dock1', newView: ->(view) do
42
+ view.panels_responsive(
43
+ styleClass: ['frameless'],
44
+ childViews: ->(panel) do
45
+ panel.label text: 'Upload complete'
46
+ panel.spacer height: 4
47
+ panel.button text: 'Dismiss', onClick: ->(subaction) do
48
+ subaction.bottomBanners_close targetId: 'dock1'
49
+ end
50
+ end
51
+ )
52
+ end
53
+ end
54
+ res.spacer height: 4
55
+ res.button text: "bottomBanners/close targetId: 'dock1'", onClick: ->(action) do
56
+ action.bottomBanners_close targetId: 'dock1'
57
+ end
58
+ res.spacer height: 4
59
+ res.button text: "bottomBanners/close targetId: 'dock2'", onClick: ->(action) do
60
+ action.bottomBanners_close targetId: 'dock2'
61
+ end
62
+ res.spacer height: 4
63
+ res.button text: 'bottomBanners/close (no targetId)', onClick: ->(action) do
64
+ action.bottomBanners_close
65
+ end
66
+ end
67
+ )
68
+ end
69
+ )
@@ -10,15 +10,20 @@ page.body childViews: ->(body) do
10
10
  res.h2 text: 'Overview'
11
11
  res.p text: 'Web panels embed external web content within a panel container.'
12
12
 
13
- url_primary = 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf'
14
- url_alt = 'https://example.com'
13
+ # Hermetic content on the serving host itself (spec 169, Decision 5):
14
+ # the recording host's public/ carries the two deterministic pages, so
15
+ # the emitted URLs bake an origin the cassette replay can rewrite and
16
+ # serve — an external URL would leave the emulator for the real network
17
+ # and no golden could depend on it.
18
+ url_content = "#{request.base_url}/g-web-panel.html"
19
+ url_alt = "#{request.base_url}/g-web-panel-alt.html"
15
20
 
16
21
  res.spacer height: 12
17
22
  res.hr width: 'matchParent'
18
23
 
19
24
  res.h2 text: 'Basic'
20
25
  res.spacer height: 8
21
- res.panels_web id: 'web_main', width: 'matchParent', height: 300, url: url_primary
26
+ res.panels_web id: 'web_main', width: 'matchParent', height: 300, url: url_content
22
27
 
23
28
  res.spacer height: 12
24
29
  res.hr width: 'matchParent'
@@ -26,11 +31,11 @@ page.body childViews: ->(body) do
26
31
  res.h2 text: 'Variants and Props'
27
32
  res.spacer height: 8
28
33
  res.panels_flow innerPadding: { bottom: 0 }, childViews: ->(flow) do
29
- flow.button text: 'PDF', onClick: ->(action) do
30
- action.logics_set targetId: 'web_main', data: { url: url_primary }
34
+ flow.button text: 'Content A', onClick: ->(action) do
35
+ action.logics_set targetId: 'web_main', data: { url: url_content }
31
36
  end
32
37
  flow.spacer width: 4
33
- flow.button text: 'Example.com', onClick: ->(action) do
38
+ flow.button text: 'Content B', onClick: ->(action) do
34
39
  action.logics_set targetId: 'web_main', data: { url: url_alt }
35
40
  end
36
41
  flow.spacer width: 4
@@ -60,6 +65,6 @@ page.body childViews: ->(body) do
60
65
  res.h2 text: 'Edge and Advanced'
61
66
  res.spacer height: 8
62
67
  res.label text: 'Small viewport'
63
- res.panels_web width: 'matchParent', height: 120, url: url_primary
68
+ res.panels_web width: 'matchParent', height: 120, url: url_content
64
69
  end
65
70
  end
@@ -14,12 +14,6 @@ json_ui_page json do |page|
14
14
  # ul.label text: 'Label'
15
15
  # end
16
16
 
17
- scroll.spacer height: 20
18
- scroll.h2 text: 'Time'
19
- scroll.spacer height: 6
20
- scroll.datetime value: 1.week.ago, format: 'YYYY-MM-DD'
21
- scroll.datetime value: DateTime.current, format: 'hh:mm:ss', tick: true
22
-
23
17
  # scroll.spacer height: 20
24
18
  # scroll.h2 text: 'Map'
25
19
  # scroll.spacer height: 6
@@ -30,5 +24,15 @@ json_ui_page json do |page|
30
24
  scroll.spacer height: 6
31
25
  scroll.calendar width: 'matchParent', height: 500, dataUrl: json_ui_garage_url(path: 'views/calendar_data')
32
26
 
27
+ # h5/h6 exercised here rather than views/texts: the texts page is pinned
28
+ # by the client suite's golden, and a non-corpus page covers the headings
29
+ # without shifting it (component inventory, thread 195).
30
+ scroll.spacer height: 20
31
+ scroll.h2 text: 'Sub-headings'
32
+ scroll.spacer height: 6
33
+ scroll.h5 text: 'Heading 5'
34
+ scroll.spacer height: 6
35
+ scroll.h6 text: 'Heading 6'
36
+
33
37
  end
34
38
  end
@@ -3,22 +3,21 @@ module Glib
3
3
  # Global JSON UI settings.
4
4
  #
5
5
  # `name_separator` controls the character that joins the namespace and the
6
- # component/action name in the generated JSON, e.g. `panels/responsive`
7
- # (default) vs `panels_responsive`.
6
+ # component/action name in the generated JSON, e.g. `panels_responsive`
7
+ # (default since 7.0) vs `panels/responsive`.
8
8
  #
9
- # The default stays `/` for backwards compatibility; `_` becomes the default
10
- # in the next major version. Apps opt in early via an initializer:
9
+ # Apps that need the legacy slash names opt back in via an initializer:
11
10
  #
12
11
  # # config/initializers/glib.rb
13
- # Glib::JsonUi::Config.name_separator = '_'
12
+ # Glib::JsonUi::Config.name_separator = '/'
14
13
  #
15
- # Frontends built on glib-web-npm accept either form, so flipping this is a
16
- # server-side-only change.
14
+ # Frontends built on glib-web-npm accept either form, so this is a
15
+ # server-side-only setting.
17
16
  class Config
18
17
  SEPARATORS = ['/', '_'].freeze
19
18
 
20
- # The shipped default. Stays '/' until the next major version, when it
21
- # becomes '_'. Apps opt in early via `Config.name_separator = '_'`.
19
+ # The shipped default since the 7.0 major version. Apps that need the
20
+ # legacy names opt back in via `Config.name_separator = '/'`.
22
21
  DEFAULT_NAME_SEPARATOR = '_'
23
22
 
24
23
  @@name_separator = DEFAULT_NAME_SEPARATOR
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glib-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.1
4
+ version: 7.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-10-13 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: activestorage
@@ -191,6 +192,7 @@ dependencies:
191
192
  - - ">="
192
193
  - !ruby/object:Gem::Version
193
194
  version: '0'
195
+ description:
194
196
  email: ''
195
197
  executables: []
196
198
  extensions: []
@@ -234,7 +236,6 @@ files:
234
236
  - app/helpers/glib/json_ui/action_builder/dialogs.rb
235
237
  - app/helpers/glib/json_ui/action_builder/fields.rb
236
238
  - app/helpers/glib/json_ui/action_builder/files.rb
237
- - app/helpers/glib/json_ui/action_builder/global_states.rb
238
239
  - app/helpers/glib/json_ui/action_builder/http.rb
239
240
  - app/helpers/glib/json_ui/action_builder/iap.rb
240
241
  - app/helpers/glib/json_ui/action_builder/logics.rb
@@ -271,6 +272,7 @@ files:
271
272
  - app/models/concerns/glib/nilify_blanks.rb
272
273
  - app/models/concerns/glib/snapshot_v2.rb
273
274
  - app/models/concerns/glib/soft_deletable.rb
275
+ - app/models/g_log.rb
274
276
  - app/models/glib/active_storage/attachment.rb
275
277
  - app/models/glib/active_storage/blob.rb
276
278
  - app/models/glib/application_record.rb
@@ -438,6 +440,7 @@ files:
438
440
  - app/views/json_ui/garage/test_page/_header.json.jbuilder
439
441
  - app/views/json_ui/garage/test_page/auth.json.jbuilder
440
442
  - app/views/json_ui/garage/test_page/auto_validate.json.jbuilder
443
+ - app/views/json_ui/garage/test_page/bottom_banners.json.jbuilder
441
444
  - app/views/json_ui/garage/test_page/browsers.json.jbuilder
442
445
  - app/views/json_ui/garage/test_page/calendar.json.jbuilder
443
446
  - app/views/json_ui/garage/test_page/calendar_empty_data.json.jbuilder
@@ -604,8 +607,10 @@ files:
604
607
  - lib/glib/time_returning_mailer.rb
605
608
  - lib/glib/value.rb
606
609
  - lib/tasks/db.rake
610
+ homepage:
607
611
  licenses: []
608
612
  metadata: {}
613
+ post_install_message:
609
614
  rdoc_options: []
610
615
  require_paths:
611
616
  - lib
@@ -620,7 +625,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
620
625
  - !ruby/object:Gem::Version
621
626
  version: '0'
622
627
  requirements: []
623
- rubygems_version: 4.0.16
628
+ rubygems_version: 3.4.6
629
+ signing_key:
624
630
  specification_version: 4
625
631
  summary: ''
626
632
  test_files: []
@@ -1,14 +0,0 @@
1
- class Glib::JsonUi::ActionBuilder
2
- module GlobalStates
3
- # Experimental
4
- class Watch < Action
5
- string :state
6
- action :onChange
7
- end
8
-
9
- class Set < Action
10
- string :state
11
- hash :data
12
- end
13
- end
14
- end