expo_turbo-rails 0.1.6 → 0.1.7

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: bb4fd4c1b7fceb63b0692da829272a1797823fb9670f8f58f349826efce4ad3d
4
- data.tar.gz: cf76144bc5449ed6f89cd65d5907af98eb6cae1503c451a27485d0a88d0d67de
3
+ metadata.gz: 2c37a46309254f182317989ff379dda0157142f6057027541d0efcfe57d41c1b
4
+ data.tar.gz: ef34d5364e16bce90e21e3a861a7347d06a8908fbc56beec0da0c6a151329e72
5
5
  SHA512:
6
- metadata.gz: bffd867d2a666b87fbe1a7f860223ce89456ddac0f3a4a253bcd38e2df502004bd842b0ed45e9cbeb0a5f7eea1ce2759850df641edff877c264435abccd8fafb
7
- data.tar.gz: e458ed58f94910046557ad34f6876bd850e40ff5aebd36243a3fefe006bb57c23bf81151004aa145b56bf6e8f5f5a6c4efee58a60361ab5dd22d80df941b58ee
6
+ metadata.gz: c40f69d53c21e181a20a8b0cedd5d8d5299ea4561dbc76588169ec075d7a1f90dcdeef01e7e39e7352421b2e664d156756c61b45cd8eae40119f6a29f4dcf6f8
7
+ data.tar.gz: 5b9e77f8c89554f9a6dcabe7c7c2caba3f5fb0c6ad781b1f68d2b5e58d124b38af49520fe2d5e0ffb6f4bd288cdb4163bc65b71768839723e8b124b02f7561cc
data/README.md CHANGED
@@ -5,10 +5,10 @@ The Rails package for Expo Turbo. It registers the distinct `application/vnd.exp
5
5
  The package validates rendered Expo Turbo documents structurally and rejects blank or duplicate literal IDs across the complete response, including Frame IDs. A controller must declare the components and style tokens it is allowed to render documents; when it does, the same policy also applies to its Frame, Stream, and raw controller-broadcast output. Its optional protected Cable boundary delegates all credentials and resource policy to the host.
6
6
 
7
7
  > [!IMPORTANT]
8
- > Version [`0.1.6`](https://rubygems.org/gems/expo_turbo-rails) is the stable
8
+ > Version [`0.1.7`](https://rubygems.org/gems/expo_turbo-rails) is the stable
9
9
  > release published on 2026-07-31. Manual VoiceOver, TalkBack, and browser
10
10
  > screen-reader evidence remains an explicit follow-up and is not claimed by
11
- > the `0.1.6` compatibility surface.
11
+ > the `0.1.7` compatibility surface.
12
12
 
13
13
  ```ruby
14
14
  gem "expo_turbo-rails"
@@ -54,7 +54,7 @@ class ExpoTurboController < ActionController::API
54
54
  end
55
55
  ```
56
56
 
57
- The template argument is relative to the configured root; absolute paths, traversal, missing files, and symlink escapes are rejected. The resolved `.xml.erb` source is evaluated as ERB with layouts disabled, rather than served as raw file content. Before it renders, the exact output must be a strict UTF-8 XML document: one root, valid namespaces and attributes, no DTD or processing instruction, and an optional leading UTF-8 XML declaration only. Every literal `id` must also be nonblank and unique across the complete rendered document, including nested Frames. The capability declaration then admits only its exact components (and explicit aliases), exact unprefixed `turbo-frame`, `turbo-stream`, `template`, and `turbo-cable-stream-source` wrappers (including default-namespace elements), and declared `style-tokens`. Style-token lists use the same JavaScript whitespace split, count, duplicate, component, and group-conflict rules as the native adapter. A component must opt into the `style-tokens` attribute, and style-token component lists are canonicalized through aliases. A generated registry manifest also rejects undeclared component attributes and missing required attributes. Shared protocol attributes such as `id`, `class`, `data-*`, and `xml:space` stay available. The host declaration must mirror its installed registry and style adapter. Attribute values still receive their full codec and Zod validation on the client. Validation never serializes the output, so it does not alter preserved XML text.
57
+ The template argument is relative to the configured root; absolute paths, traversal, missing files, and symlink escapes are rejected. The resolved `.xml.erb` source is evaluated as ERB with layouts disabled, rather than served as raw file content. Before it renders, the exact output must be a strict UTF-8 XML document: one root, valid namespaces and attributes, no DTD or processing instruction, and an optional leading UTF-8 XML declaration only. Every literal `id` must also be nonblank and unique across the complete rendered document, including nested Frames. The capability declaration then admits only its exact components (and explicit aliases), exact unprefixed `turbo-frame`, `turbo-stream`, `template`, and `turbo-cable-stream-source` wrappers (including default-namespace elements), and declared `style-tokens`. Style-token lists use the same JavaScript whitespace split, count, duplicate, component, and group-conflict rules as the native adapter. A component must opt into the `style-tokens` attribute, and style-token component lists are canonicalized through aliases. A generated registry manifest also rejects undeclared component attributes and missing required attributes. Shared protocol attributes such as `id`, `class`, `data-*`, and `xml:space` stay available. Components with `formOwner: true` also admit the form protocol attributes `action`, `enctype`, `method`, `novalidate`, and `target` without declaring them as component props. The host declaration must mirror its installed registry and style adapter. Attribute values still receive their full codec and Zod validation on the client. Validation never serializes the output, so it does not alter preserved XML text.
58
58
 
59
59
  Do not put multiline values in XML attributes with ordinary ERB interpolation. The client XML parser changes raw tabs and line breaks in an attribute to spaces before component decoding. A later form submission can then save the changed value. Use `expo_turbo_attribute` for each value that can contain this whitespace:
60
60
 
@@ -8,6 +8,7 @@ module ExpoTurbo
8
8
  MANIFEST_VERSION = 1
9
9
  PROTOCOL_ELEMENTS = %w[turbo-cable-stream-source turbo-frame turbo-stream template].freeze
10
10
  RESERVED_COMPONENT_NAMES = [*PROTOCOL_ELEMENTS, "expo-turbo-fragment"].freeze
11
+ FORM_OWNER_ATTRIBUTE_NAMES = %w[action enctype method novalidate target].freeze
11
12
  SHARED_ATTRIBUTE_NAMES = %w[autofocus class dir dirname form id xml:space xmlns].freeze
12
13
  TOKEN_PATTERN = /\A[a-z][a-z0-9-]*(?::[a-z][a-z0-9-]*)*\z/
13
14
  MAX_TOKEN_LENGTH = 64
@@ -124,6 +125,9 @@ module ExpoTurbo
124
125
  unless component["aliases"].all? { |alias_name| alias_name.is_a?(String) }
125
126
  raise ConfigurationError, "Expo Turbo capability manifest aliases must be strings"
126
127
  end
128
+ if component.key?("formOwner") && ![true, false].include?(component["formOwner"])
129
+ raise ConfigurationError, "Expo Turbo capability manifest form ownership must be boolean"
130
+ end
127
131
 
128
132
  attributes = component["attributes"]
129
133
  valid_attributes = attributes.is_a?(Array) && attributes.all? do |attribute|
@@ -145,6 +149,7 @@ module ExpoTurbo
145
149
  components[component["tag"]] = {
146
150
  aliases: component["aliases"],
147
151
  attributes: attribute_names,
152
+ form_owner: component["formOwner"] == true,
148
153
  required_attributes: attributes.filter_map { |attribute| attribute["name"] if attribute["required"] },
149
154
  style_tokens: attribute_names.include?("style-tokens")
150
155
  }
@@ -164,6 +169,7 @@ module ExpoTurbo
164
169
  if manifest_backed
165
170
  component_attributes[tag] = {
166
171
  allowed: configuration[:attributes].to_h { |name| [name, true] }.freeze,
172
+ form_owner: configuration[:form_owner],
167
173
  required: configuration[:required_attributes].to_h { |name| [name, true] }.freeze
168
174
  }.freeze
169
175
  end
@@ -176,7 +182,7 @@ module ExpoTurbo
176
182
  def normalize_component_configuration(tag, configuration, manifest_backed:)
177
183
  configuration = {} if configuration.nil?
178
184
  allowed_keys = %i[aliases style_tokens]
179
- allowed_keys.concat(%i[attributes required_attributes]) if manifest_backed
185
+ allowed_keys.concat(%i[attributes form_owner required_attributes]) if manifest_backed
180
186
  unless configuration.is_a?(Hash) && (configuration.keys - allowed_keys).empty?
181
187
  raise ConfigurationError, "Expo Turbo component #{tag.inspect} accepts only aliases and style_tokens"
182
188
  end
@@ -197,10 +203,12 @@ module ExpoTurbo
197
203
  end
198
204
 
199
205
  attributes = manifest_backed ? configuration.fetch(:attributes) : []
206
+ form_owner = manifest_backed ? configuration.fetch(:form_owner) : false
200
207
  required_attributes = manifest_backed ? configuration.fetch(:required_attributes) : []
201
208
  {
202
209
  aliases: aliases.freeze,
203
210
  attributes: attributes.freeze,
211
+ form_owner:,
204
212
  required_attributes: required_attributes.freeze,
205
213
  style_tokens:
206
214
  }.freeze
@@ -336,7 +344,9 @@ module ExpoTurbo
336
344
  element.attribute_nodes.each do |attribute|
337
345
  name = qualified_attribute_name(attribute)
338
346
  present[name] = true
339
- next if shared_attribute_name?(name) || capabilities[:allowed].key?(name)
347
+ next if shared_attribute_name?(name) ||
348
+ (capabilities[:form_owner] && FORM_OWNER_ATTRIBUTE_NAMES.include?(name)) ||
349
+ capabilities[:allowed].key?(name)
340
350
 
341
351
  raise ValidationError, "Expo Turbo template contains an undeclared component attribute"
342
352
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ExpoTurbo
4
4
  module Rails
5
- VERSION = "0.1.6"
5
+ VERSION = "0.1.7"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: expo_turbo-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - NoScrubs