ruact 0.0.11 → 0.0.12

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.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +110 -48
  3. data/CONTRIBUTING.md +233 -0
  4. data/README.md +14 -4
  5. data/RELEASING.md +125 -139
  6. data/lib/ruact/packaging.rb +68 -0
  7. data/lib/ruact/version.rb +1 -1
  8. data/vendor/javascript/vite-plugin-ruact/flight-client.test.mjs +321 -0
  9. data/vendor/javascript/vite-plugin-ruact/package-lock.json +11 -0
  10. data/vendor/javascript/vite-plugin-ruact/package.json +1 -0
  11. data/vendor/javascript/vite-plugin-ruact/runtime/flight-client.js +14 -3
  12. metadata +6 -107
  13. data/.codecov.yml +0 -31
  14. data/.github/workflows/ci.yml +0 -284
  15. data/.github/workflows/server-functions-bench.yml +0 -54
  16. data/.rubocop.yml +0 -107
  17. data/.rubocop_todo.yml +0 -63
  18. data/Rakefile +0 -10
  19. data/bench/server_functions_dispatch_bench.rb +0 -276
  20. data/bench/server_functions_dispatch_bench.results.md +0 -150
  21. data/docs/internal/README.md +0 -9
  22. data/docs/internal/decisions/server-functions-api.md +0 -2236
  23. data/spec/benchmarks/baseline.json +0 -12
  24. data/spec/benchmarks/render_pipeline_benchmark_spec.rb +0 -109
  25. data/spec/fixtures/flight/README.md +0 -136
  26. data/spec/fixtures/flight/array.txt +0 -1
  27. data/spec/fixtures/flight/as_json_object.txt +0 -2
  28. data/spec/fixtures/flight/bigint.txt +0 -1
  29. data/spec/fixtures/flight/boolean_false.txt +0 -1
  30. data/spec/fixtures/flight/boolean_true.txt +0 -1
  31. data/spec/fixtures/flight/client_component_with_props.txt +0 -2
  32. data/spec/fixtures/flight/client_reference.txt +0 -2
  33. data/spec/fixtures/flight/hash.txt +0 -1
  34. data/spec/fixtures/flight/infinity.txt +0 -1
  35. data/spec/fixtures/flight/nan.txt +0 -1
  36. data/spec/fixtures/flight/negative_infinity.txt +0 -1
  37. data/spec/fixtures/flight/nil.txt +0 -1
  38. data/spec/fixtures/flight/number_float.txt +0 -1
  39. data/spec/fixtures/flight/number_integer.txt +0 -1
  40. data/spec/fixtures/flight/react_element_no_props.txt +0 -1
  41. data/spec/fixtures/flight/redirect_row.txt +0 -1
  42. data/spec/fixtures/flight/serializable_object.txt +0 -2
  43. data/spec/fixtures/flight/string_basic.txt +0 -1
  44. data/spec/fixtures/flight/string_dollar_escape.txt +0 -1
  45. data/spec/fixtures/flight/undefined.txt +0 -1
  46. data/spec/fixtures/readme/children-error.html.erb +0 -3
  47. data/spec/fixtures/readme/children-error.txt +0 -1
  48. data/spec/fixtures/story_7_9_views/controller_request_spec_support/demo/show.html.erb +0 -3
  49. data/spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb +0 -3
  50. data/spec/fixtures/story_7_9_views/controller_request_spec_support/exploding_layout_demo/show.html.erb +0 -3
  51. data/spec/fixtures/story_7_9_views/controller_request_spec_support/ghost_layout_demo/show.html.erb +0 -3
  52. data/spec/fixtures/story_7_9_views/controller_request_spec_support/layout_demo/show.html.erb +0 -3
  53. data/spec/fixtures/story_7_9_views/controller_request_spec_support/rootless_layout_demo/show.html.erb +0 -3
  54. data/spec/fixtures/story_7_9_views/controller_request_spec_support/unwired_layout_demo/show.html.erb +0 -3
  55. data/spec/fixtures/story_7_9_views/layouts/bare_host.html.erb +0 -16
  56. data/spec/fixtures/story_7_9_views/layouts/exploding_host.html.erb +0 -24
  57. data/spec/fixtures/story_7_9_views/layouts/rootless_host.html.erb +0 -15
  58. data/spec/fixtures/story_7_9_views/layouts/ruact_host.html.erb +0 -17
  59. data/spec/readme_demo_message_spec.rb +0 -67
  60. data/spec/readme_spec.rb +0 -282
  61. data/spec/ruact/client_manifest_spec.rb +0 -270
  62. data/spec/ruact/component_contract_spec.rb +0 -119
  63. data/spec/ruact/configuration_spec.rb +0 -518
  64. data/spec/ruact/controller_request_spec.rb +0 -671
  65. data/spec/ruact/controller_spec.rb +0 -343
  66. data/spec/ruact/doctor_spec.rb +0 -769
  67. data/spec/ruact/erb_preprocessor_hook_spec.rb +0 -55
  68. data/spec/ruact/erb_preprocessor_spec.rb +0 -361
  69. data/spec/ruact/errors_spec.rb +0 -93
  70. data/spec/ruact/flight/renderer_spec.rb +0 -133
  71. data/spec/ruact/flight/serializer_spec.rb +0 -494
  72. data/spec/ruact/html_converter_spec.rb +0 -375
  73. data/spec/ruact/install_generator_spec.rb +0 -1549
  74. data/spec/ruact/layout_source_spec.rb +0 -108
  75. data/spec/ruact/manifest_resolver_spec.rb +0 -174
  76. data/spec/ruact/query_request_spec.rb +0 -706
  77. data/spec/ruact/query_spec.rb +0 -105
  78. data/spec/ruact/railtie_spec.rb +0 -155
  79. data/spec/ruact/render_context_spec.rb +0 -58
  80. data/spec/ruact/render_pipeline_concurrency_spec.rb +0 -78
  81. data/spec/ruact/render_pipeline_spec.rb +0 -928
  82. data/spec/ruact/scaffold_generator_spec.rb +0 -1849
  83. data/spec/ruact/serializable_spec.rb +0 -179
  84. data/spec/ruact/server_bucket_request_spec.rb +0 -785
  85. data/spec/ruact/server_function_name_spec.rb +0 -53
  86. data/spec/ruact/server_functions/backtrace_cleaner_spec.rb +0 -63
  87. data/spec/ruact/server_functions/bucket_two_payload_spec.rb +0 -200
  88. data/spec/ruact/server_functions/codegen_spec.rb +0 -397
  89. data/spec/ruact/server_functions/error_payload_spec.rb +0 -222
  90. data/spec/ruact/server_functions/error_suggestion_spec.rb +0 -79
  91. data/spec/ruact/server_functions/introspection_spec.rb +0 -135
  92. data/spec/ruact/server_functions/name_bridge_spec.rb +0 -212
  93. data/spec/ruact/server_functions/query_context_spec.rb +0 -72
  94. data/spec/ruact/server_functions/query_source_spec.rb +0 -193
  95. data/spec/ruact/server_functions/railtie_integration_spec.rb +0 -215
  96. data/spec/ruact/server_functions/rake_spec.rb +0 -86
  97. data/spec/ruact/server_functions/route_source_spec.rb +0 -202
  98. data/spec/ruact/server_functions/snapshot_spec.rb +0 -96
  99. data/spec/ruact/server_functions/snapshot_writer_spec.rb +0 -71
  100. data/spec/ruact/server_rescue_request_spec.rb +0 -416
  101. data/spec/ruact/server_spec.rb +0 -179
  102. data/spec/ruact/server_upload_request_spec.rb +0 -311
  103. data/spec/ruact/signed_references_spec.rb +0 -164
  104. data/spec/ruact/string_distance_spec.rb +0 -38
  105. data/spec/ruact/tasks_json_introspection_spec.rb +0 -141
  106. data/spec/ruact/testing/have_ruact_component_spec.rb +0 -170
  107. data/spec/ruact/testing/no_production_load_spec.rb +0 -41
  108. data/spec/ruact/validation_errors_spec.rb +0 -116
  109. data/spec/ruact/view_helper_spec.rb +0 -131
  110. data/spec/spec_helper.rb +0 -77
  111. data/spec/support/fixtures/pixel.png +0 -0
  112. data/spec/support/flight_wire_parser.rb +0 -21
  113. data/spec/support/flight_wire_parser_spec.rb +0 -93
  114. data/spec/support/matchers/flight_fixture_matcher.rb +0 -130
  115. data/spec/support/matchers/flight_fixture_matcher_spec.rb +0 -250
  116. data/spec/support/rails_stub.rb +0 -115
@@ -1,12 +0,0 @@
1
- {
2
- "typical_allocations": 1792,
3
- "heavy_allocations": 8669,
4
- "_measured_on": "2026-08-25, Ruby 3.4.5 / Rails 8.1, gem at v0.0.10",
5
- "_history": {
6
- "2026-04-19": {
7
- "typical_allocations": 1623,
8
- "heavy_allocations": 7764,
9
- "note": "Original baseline, written by the spec's first run at the initial commit. Superseded 2026-08-25: four months of pipeline work (route-driven codegen, contract validation, error suggestion) grew the typical render past the 20% tolerance measured against April's code. The overshoot was cumulative and legitimate, not a regression from any single change; it surfaced as an intermittently red matrix cell because ONE global baseline cannot absorb the ~9% spread between Ruby/Rails combinations (Ruby 3.3 / Rails 7.1 measured ~1958 while Ruby 3.4 / Rails 8.1 measured 1792). Growth since April: typical +10.4%, heavy +11.7%."
10
- }
11
- }
12
- }
@@ -1,109 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
- require "rspec-benchmark"
5
- require "memory_profiler"
6
- require "json"
7
-
8
- BENCHMARK_BASELINE_FILE = File.expand_path("baseline.json", __dir__)
9
-
10
- # The baseline is a SINGLE measurement shared by every matrix cell, while the
11
- # same render allocates differently per Ruby/Rails combination — measured at
12
- # ~9% spread (Ruby 3.4/Rails 8.1: 1792; Ruby 3.3/Rails 7.1: ~1958). The x1.20
13
- # tolerance therefore absorbs two different things at once: that cross-version
14
- # spread AND real growth. When the two together cross the limit, ONE cell goes
15
- # red while the rest stay green, which reads like flakiness and is not — rerunning
16
- # does not fix it.
17
- #
18
- # Before rebaselining, check WHICH it is. A number that moved with a code change
19
- # is a regression and belongs in the diff, not in this file. A number that drifted
20
- # across many changes has outgrown its anchor, and the honest move is to rebaseline
21
- # and record the growth in baseline.json's `_history` (see the 2026-08-25 entry).
22
- #
23
- # To regenerate: delete baseline.json and run this file TWICE (the first run writes
24
- # `typical_allocations`, the second fills `heavy_allocations`), then restore the
25
- # `_measured_on` / `_history` keys, which the spec does not write.
26
-
27
- RSpec.describe "RenderPipeline benchmark" do
28
- include RSpec::Benchmark::Matchers
29
-
30
- let(:baseline_file) { BENCHMARK_BASELINE_FILE }
31
-
32
- let(:manifest) do
33
- entries = (1..20).to_h do |i|
34
- ["Component#{i}", {
35
- "id" => "/assets/Component#{i}.js",
36
- "name" => "Component#{i}",
37
- "chunks" => ["/assets/Component#{i}.js"]
38
- }]
39
- end
40
- Ruact::ClientManifest.from_hash(entries)
41
- end
42
-
43
- let(:pipeline) { Ruact::RenderPipeline.new(manifest) }
44
-
45
- def make_erb(count)
46
- components = (1..count).map { |i| "<Component#{i} index={#{i}} />" }.join("\n")
47
- "<div>\n#{components}\n</div>"
48
- end
49
-
50
- def render_erb(erb_source, active_pipeline = pipeline)
51
- ctx = Object.new
52
- active_pipeline.render({ erb: erb_source, binding: ctx.instance_eval { binding } }, mode: :string)
53
- end
54
-
55
- describe "typical view (20 components)" do
56
- let(:erb_typical) { make_erb(20) }
57
-
58
- it "allocates fewer than the baseline × 1.20 objects" do
59
- report = MemoryProfiler.report { render_erb(erb_typical) }
60
- allocations = report.total_allocated
61
-
62
- if File.exist?(baseline_file)
63
- baseline = JSON.parse(File.read(baseline_file))
64
- limit = (baseline["typical_allocations"] * 1.20).ceil
65
- expect(allocations).to be <= limit,
66
- "Typical view allocations #{allocations} exceed baseline limit #{limit} " \
67
- "(baseline: #{baseline['typical_allocations']})"
68
- else
69
- # First run — establish baseline
70
- File.write(baseline_file, JSON.generate("typical_allocations" => allocations,
71
- "heavy_allocations" => nil))
72
- end
73
- end
74
- end
75
-
76
- describe "heavy view (100 components)" do
77
- let(:manifest_heavy) do
78
- entries = (1..100).to_h do |i|
79
- ["Component#{i}", {
80
- "id" => "/assets/Component#{i}.js",
81
- "name" => "Component#{i}",
82
- "chunks" => ["/assets/Component#{i}.js"]
83
- }]
84
- end
85
- Ruact::ClientManifest.from_hash(entries)
86
- end
87
-
88
- let(:pipeline_heavy) { Ruact::RenderPipeline.new(manifest_heavy) }
89
- let(:erb_heavy) { make_erb(100) }
90
-
91
- it "allocates fewer than the baseline × 1.20 objects" do
92
- report = MemoryProfiler.report { render_erb(erb_heavy, pipeline_heavy) }
93
- allocations = report.total_allocated
94
-
95
- if File.exist?(baseline_file)
96
- data = JSON.parse(File.read(baseline_file))
97
- if data["heavy_allocations"]
98
- limit = (data["heavy_allocations"] * 1.20).ceil
99
- expect(allocations).to be <= limit,
100
- "Heavy view allocations #{allocations} exceed baseline limit #{limit} " \
101
- "(baseline: #{data['heavy_allocations']})"
102
- else
103
- data["heavy_allocations"] = allocations
104
- File.write(baseline_file, JSON.generate(data))
105
- end
106
- end
107
- end
108
- end
109
- end
@@ -1,136 +0,0 @@
1
- # Flight Wire Format Fixtures
2
-
3
- This directory contains fixture files used to test the Flight wire format serializer. Each `.txt` file contains the exact byte output that `Ruact::Flight::Renderer.render` is expected to produce for a given Ruby input.
4
-
5
- ---
6
-
7
- ## Wire Format Reference
8
-
9
- The React Flight wire protocol encodes a component tree as a series of newline-terminated rows:
10
-
11
- ```
12
- <hex-id>:<payload>\n # model row — a JSON value at position <hex-id>
13
- <hex-id>:I<json-array>\n # import row — registers a client module: [moduleId, exportName, chunks]
14
- <hex-id>:E<json-error>\n # error row — encodes a serialized error object
15
- ```
16
-
17
- - **Row `0`** is always the root — the main React element tree returned to the client.
18
- - **Import rows (`I` rows)** always appear *before* the model rows that reference them.
19
- - Hex IDs start at `0` for the root and increment (`1`, `2`, `a`, `b`, …) for each additional row.
20
-
21
- ### Worked Example — `client_reference.txt`
22
-
23
- Ruby input:
24
- ```ruby
25
- manifest = Ruact::ClientManifest.new({"LikeButton" => {moduleId: "/LikeButton.jsx", chunks: ["/LikeButton.jsx"]}})
26
- ref = manifest.reference_for("LikeButton")
27
- Ruact::Flight::Renderer.render(ref, manifest)
28
- ```
29
-
30
- Expected output (`client_reference.txt`):
31
- ```
32
- 1:I["/LikeButton.jsx","LikeButton",["/LikeButton.jsx"]]
33
- 0:["$","$L1",null,{}]
34
- ```
35
-
36
- - Row `1` is the import row — it tells React where to find `LikeButton`.
37
- - Row `0` is the root element — `["$","$L1",null,{}]` is a React element whose type is `$L1` (a reference to import row 1), with `null` key and empty props `{}`.
38
-
39
- ---
40
-
41
- ## How to assert on Flight output
42
-
43
- Three RSpec matcher modes live in `spec/support/matchers/flight_fixture_matcher.rb`. Pick the mode that names what you actually care about — defaulting to `match_flight_structure` for new specs avoids the cosmetic-change brittleness that bit Phase 1 (a `JSON.generate` tweak broke 30+ specs at once).
44
-
45
- | Mode | When to use | What it tolerates | Failure shape |
46
- |---|---|---|---|
47
- | `match_flight_fixture(name)` | The wire format itself is the contract — bytes-for-bytes guard. Use for serializer escape rules, ordering invariants, or any test where "the output looks exactly like this" is the assertion. | Nothing — exact `==` against the fixture file. | Inspected expected vs. actual strings (whitespace and escapes visible). |
48
- | `match_flight_structure(expected)` | The parsed semantics are the contract — change-tolerant. Use for round-tripping tests, scaffolding, end-to-end render tests, and any case where JSON key reordering or whitespace tweaks should not break the spec. | JSON key insertion order inside payload hashes; whitespace inside JSON; relative ordering among `:import` rows (which Flight treats as an unordered set). Cross-class row order (imports → models → deferred → errors) is **preserved** because the wire protocol depends on it. | Row-indexed diff naming the differing field path (e.g. `Row 0 (model) differs at .payload[0]:`). |
49
- | `include_flight_row(predicate)` | A specific shape must appear regardless of order or siblings. Use for concurrency tests, partial assertions in multi-row outputs, or "this row exists somewhere" checks. | Sibling rows; ordering. Predicate is subset-matched (only the keys you list are compared). | Predicate inspect + a list of every parsed row with id/class/payload preview. |
50
-
51
- ### Worked examples
52
-
53
- Each example is a complete RSpec assertion you can paste into a spec file (above `RSpec.describe` blocks where the matchers are autoloaded by `spec_helper`).
54
-
55
- **`match_flight_fixture` — wire-format contract guard:**
56
- ```ruby
57
- serialized = "0:\"$$danger\"\n"
58
- expect(serialized).to match_flight_fixture("string_dollar_escape")
59
- # Asserts byte-for-byte against `string_dollar_escape.txt` — proves the `$` → `$$` escape rule.
60
- ```
61
-
62
- **`match_flight_structure` — parsed-semantics assertion:**
63
- ```ruby
64
- wire = %(1:I["/LikeButton.jsx","LikeButton",["/LikeButton.jsx"]]\n) +
65
- %(0:["$","$L1",null,{"postId":42}]\n)
66
-
67
- expect(wire).to match_flight_structure([
68
- { id: 1, class: :import, payload: ["/LikeButton.jsx", "LikeButton", ["/LikeButton.jsx"]] },
69
- { id: 0, class: :model, payload: ["$", "$L1", nil, { "postId" => 42 }] }
70
- ])
71
- # Passes even if `JSON.generate` re-orders props later — what matters is that postId=42 round-trips.
72
- ```
73
-
74
- **`include_flight_row` — ordering-independent presence check:**
75
- ```ruby
76
- wire = %(0:["$","CounterButton",null,{"initialCount":0}]\n)
77
-
78
- expect(wire).to include_flight_row(
79
- class: :model,
80
- payload: hash_including("initialCount" => 0)
81
- )
82
- # Used in concurrency specs where multiple threads emit interleaved rows; only the row's shape matters.
83
- ```
84
-
85
- ### ❌ Avoid / ✅ Prefer
86
-
87
- ```ruby
88
- ❌ expect(output).to include('"postId":42') # cosmetic-change brittle
89
- ✅ expect(output).to include_flight_row(class: :model, payload: hash_including("postId" => 42))
90
- ```
91
-
92
- A future `JSON.generate` change that emits keys in different insertion order (`{"label":"x","postId":42}` instead of `{"postId":42,"label":"x"}`) breaks the literal substring match. The structural form parses the row and compares hashes by key-set, so it survives the cosmetic change.
93
-
94
- ### When a fixture spec fails after a serializer change
95
-
96
- If `match_flight_fixture` fails after touching `Ruact::Flight::Serializer`, that's by design — the wire format **is** the contract for those fixtures. Either the change is intentional (update the fixture file and the CHANGELOG) or it isn't (revert). Don't switch the spec to `match_flight_structure` to "make it green"; that hides the regression that fixture mode is designed to catch.
97
-
98
- ---
99
-
100
- ## Fixture File Inventory
101
-
102
- | File | What it tests |
103
- |------|---------------|
104
- | `nil.txt` | Ruby `nil` serializes to the JSON `null` literal in row 0 |
105
- | `boolean_true.txt` | Ruby `true` serializes to the JSON `true` literal |
106
- | `boolean_false.txt` | Ruby `false` serializes to the JSON `false` literal |
107
- | `number_integer.txt` | Ruby integer (e.g. `42`) serializes to a bare JSON number |
108
- | `number_float.txt` | Ruby float (e.g. `3.14`) serializes to a bare JSON float |
109
- | `bigint.txt` | Ruby integer above `MAX_SAFE_INTEGER` (`2**53`) serializes to `"$n<decimal>"` so the JS client can rebuild a `BigInt` |
110
- | `nan.txt` | `Float::NAN` serializes to `"$NaN"` (JSON has no NaN literal; Flight uses the `$`-prefix sentinel) |
111
- | `infinity.txt` | `Float::INFINITY` serializes to `"$Infinity"` |
112
- | `negative_infinity.txt` | `-Float::INFINITY` serializes to `"$-Infinity"` |
113
- | `undefined.txt` | The `:undefined` symbol sentinel serializes to `"$undefined"` so the JS client decodes it as `undefined` (JSON has no undefined literal) |
114
- | `string_basic.txt` | Plain Ruby string serializes to a JSON double-quoted string |
115
- | `string_dollar_escape.txt` | Strings starting with `$` are escaped to `$$…` to avoid collision with Flight's `$L` reference syntax |
116
- | `array.txt` | Ruby array serializes to a JSON array in row 0 |
117
- | `hash.txt` | Ruby hash serializes to a JSON object in row 0 |
118
- | `client_reference.txt` | A `ClientReference` (no props) produces an import row (`I`) + root element referencing `$L1` |
119
- | `client_component_with_props.txt` | A `ClientReference` with props passes them as the fourth element of the root array |
120
- | `react_element_no_props.txt` | A `ReactElement` with no props produces `["$","<tag>",null,{}]` in row 0 |
121
- | `as_json_object.txt` | An object responding to `as_json` is serialized via that method; if it resolves to a `ClientReference`, import + root rows are emitted |
122
- | `serializable_object.txt` | An object including `Ruact::Serializable` and declaring `ruact_props` serializes only the declared props |
123
- | `redirect_row.txt` | A redirect instruction serializes to a JSON object with `redirectUrl` and `redirectType` keys in row 0 |
124
-
125
- ---
126
-
127
- ## Adding a New Fixture
128
-
129
- See the [Fixture-First Workflow](../../../CONTRIBUTING.md#fixture-first-workflow-adding-a-new-serializable-type) section in `CONTRIBUTING.md` for the full four-step process.
130
-
131
- Quick reference:
132
-
133
- 1. Create `spec/fixtures/flight/<type_name>.txt` with the expected wire bytes.
134
- 2. Write a failing spec using `match_flight_fixture("<type_name>")`.
135
- 3. Implement the type handler in `flight/serializer.rb`.
136
- 4. Run `bundle exec rspec` — the new spec must pass, full suite must have no regressions.
@@ -1 +0,0 @@
1
- 0:[1,"a",true,null]
@@ -1,2 +0,0 @@
1
- 1:I["/PostCard.jsx","PostCard",["/PostCard.jsx"]]
2
- 0:["$","$L1",null,{"post":{"id":1,"title":"Hello","author":"Alice","likesCount":5}}]
@@ -1 +0,0 @@
1
- 0:"$n9007199254740992"
@@ -1 +0,0 @@
1
- 0:false
@@ -1 +0,0 @@
1
- 0:true
@@ -1,2 +0,0 @@
1
- 1:I["/CounterButton.jsx","CounterButton",["/CounterButton.jsx"]]
2
- 0:["$","$L1",null,{"initialCount":0,"label":"Click me","disabled":false}]
@@ -1,2 +0,0 @@
1
- 1:I["/LikeButton.jsx","LikeButton",["/LikeButton.jsx"]]
2
- 0:["$","$L1",null,{}]
@@ -1 +0,0 @@
1
- 0:{"debug":true,"count":5,"label":"x"}
@@ -1 +0,0 @@
1
- 0:"$Infinity"
@@ -1 +0,0 @@
1
- 0:"$NaN"
@@ -1 +0,0 @@
1
- 0:"$-Infinity"
@@ -1 +0,0 @@
1
- 0:null
@@ -1 +0,0 @@
1
- 0:3.14
@@ -1 +0,0 @@
1
- 0:42
@@ -1 +0,0 @@
1
- 0:["$","div",null,{}]
@@ -1 +0,0 @@
1
- 0:{"redirectUrl":"/posts/1","redirectType":"push"}
@@ -1,2 +0,0 @@
1
- 1:I["/PostCard.jsx","PostCard",["/PostCard.jsx"]]
2
- 0:["$","$L1",null,{"post":{"id":1,"title":"Hello"}}]
@@ -1 +0,0 @@
1
- 0:"hello"
@@ -1 +0,0 @@
1
- 0:"$$danger"
@@ -1 +0,0 @@
1
- 0:"$undefined"
@@ -1,3 +0,0 @@
1
- <h1><%= @greeting %></h1>
2
-
3
- <LikeButton likes={@likes}>Like</LikeButton>
@@ -1 +0,0 @@
1
- ruact: <LikeButton> at app/views/home/index.html.erb:3 children are not supported — pass content as a prop, e.g. `<LikeButton content={...} />`.
@@ -1,3 +0,0 @@
1
- <div>
2
- <DemoButton label={"hello"} />
3
- </div>
@@ -1,3 +0,0 @@
1
- <div>
2
- <DemoButton label={"new-post"} errors={ruact_errors} />
3
- </div>
@@ -1,3 +0,0 @@
1
- <div>
2
- <DemoButton label={"hello"} />
3
- </div>
@@ -1,3 +0,0 @@
1
- <div>
2
- <DemoButton label={"hello"} />
3
- </div>
@@ -1,3 +0,0 @@
1
- <div>
2
- <DemoButton label={"hello"} />
3
- </div>
@@ -1,3 +0,0 @@
1
- <div>
2
- <DemoButton label={"hello"} />
3
- </div>
@@ -1,3 +0,0 @@
1
- <div>
2
- <DemoButton label={"hello"} />
3
- </div>
@@ -1,16 +0,0 @@
1
- <%# An UNMIGRATED host layout: it has the React root but never calls
2
- `ruact_js_assets`, so rendering the page through it would produce a
3
- document with no bootstrap entry and no Flight payload — a blank page.
4
- ruact must detect that and fall back to its built-in shell instead. %>
5
- <!DOCTYPE html>
6
- <html>
7
- <head>
8
- <link rel="stylesheet" href="/host-app.css" />
9
- <title>Host App Title</title>
10
- </head>
11
- <body>
12
- <%= yield %>
13
- <%# ruact: root %>
14
- <div id="root"></div>
15
- </body>
16
- </html>
@@ -1,24 +0,0 @@
1
- <%# An UNMIGRATED layout that cannot survive being rendered on a ruact page.
2
- It reads an ivar that only a plain Rails action would have set — exactly the
3
- shape of a real pre-migration app, whose layout has never once run on a
4
- ruact-rendered page. If ruact renders this speculatively just to discover it
5
- lacks `ruact_js_assets`, the app 500s on a page that used to work.
6
-
7
- NOTE: this comment MENTIONS the helper name on purpose — a readiness check
8
- that greps for the bare string counts this layout as wired and renders it. %>
9
- <%# <%= ruact_js_assets %> %>
10
- <%# ...and the line above is a genuinely COMMENTED-OUT call, which is the
11
- sharper version of the same trap: ERB comments do not nest, so it emits
12
- nothing, but a check that only looks for `<%=` sees a call. Both shapes must
13
- read as UNMIGRATED, or this layout gets rendered and 500s below. %>
14
- <!DOCTYPE html>
15
- <html>
16
- <head>
17
- <title><%= @page_title.upcase %></title>
18
- </head>
19
- <body>
20
- <%= yield %>
21
- <%# ruact: root %>
22
- <div id="root"></div>
23
- </body>
24
- </html>
@@ -1,15 +0,0 @@
1
- <%# A layout that calls `ruact_js_assets` but never got the React root div.
2
- The payload and the bootstrap both ship, so an assets-only readiness check
3
- accepts it — and the browser boots React with nothing to mount into,
4
- producing a silently blank page. %>
5
- <!DOCTYPE html>
6
- <html>
7
- <head>
8
- <link rel="stylesheet" href="/host-app.css" />
9
- <title>Host App Title</title>
10
- </head>
11
- <body>
12
- <%= yield %>
13
- <%= ruact_js_assets %>
14
- </body>
15
- </html>
@@ -1,17 +0,0 @@
1
- <%# A MIGRATED host layout: the app owns the document. The stylesheet link is
2
- the whole point — it is the thing ruact's built-in shell has no slot for,
3
- so a spec asserting it appears in the response is asserting that the host
4
- app's CSS actually reaches a ruact page. %>
5
- <!DOCTYPE html>
6
- <html>
7
- <head>
8
- <link rel="stylesheet" href="/host-app.css" />
9
- <title>Host App Title</title>
10
- </head>
11
- <body>
12
- <%= yield %>
13
- <%# ruact: root %>
14
- <div id="root"></div>
15
- <%= ruact_js_assets %>
16
- </body>
17
- </html>
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
- require "pathname"
5
-
6
- # Story 5.2 — the anti-rot gate for the README's write→verify demo.
7
- #
8
- # ⚠️ THE README DEMO SHOWS THIS MESSAGE. If this spec goes red, the recording
9
- # at https://ruact.dev/readme-write-verify.gif is now showing a message the
10
- # gem no longer produces — RE-RECORD IT.
11
- # Sources: design-artifacts/E-Assets/readme-write-verify/ in the planning
12
- # repository (`./build-app.sh && ./record.sh && ./assemble.sh`).
13
- #
14
- # The fixture is PRODUCED BY THE GEM, never transcribed. It was written by
15
- # running the preprocessor over the committed `.html.erb` beside it:
16
- #
17
- # ruby -Ilib -rruact -e 'begin
18
- # Ruact::ErbPreprocessor.transform(
19
- # File.read("spec/fixtures/readme/children-error.html.erb"),
20
- # identifier: "app/views/home/index.html.erb")
21
- # rescue Ruact::ChildrenNotSupportedError => e
22
- # File.write("spec/fixtures/readme/children-error.txt", e.message + "\n")
23
- # end'
24
- #
25
- # The identifier is the guide's own template path, so the fixture is verbatim
26
- # the line the recording shows (modulo the absolute prefix Rails prepends in a
27
- # real app). The monorepo owns the other half of this gate: the same failure
28
- # reached through a real Rails render, in
29
- # docs/examples/getting-started/harness/spec/requests/getting_started_spec.rb.
30
- # That one cannot live here — the gem repository is public and reads nothing
31
- # from the private planning repository — which is exactly why this spec exists:
32
- # the published artifact stays verifiable in its own repository.
33
- RSpec.describe "the README demo's error message", :story_5_2 do
34
- let(:root) { Pathname.new(File.expand_path("..", __dir__)) }
35
- let(:fixtures) { root.join("spec/fixtures/readme") }
36
- let(:source) { fixtures.join("children-error.html.erb").read }
37
- let(:expected) { fixtures.join("children-error.txt").read.strip }
38
-
39
- # The exact call the recording's third beat makes, one layer down: a template
40
- # whose PascalCase tag has children, compiled with the guide's template path.
41
- def message
42
- Ruact::ErbPreprocessor.transform(source, identifier: "app/views/home/index.html.erb")
43
- raise "expected Ruact::ChildrenNotSupportedError, none was raised"
44
- rescue Ruact::ChildrenNotSupportedError => e
45
- e.message
46
- end
47
-
48
- it "is byte-for-byte what the gem produces today" do
49
- expect(message).to eq(expected),
50
- "the loud-children message changed. The README demo at " \
51
- "https://ruact.dev/readme-write-verify.gif shows the old one — " \
52
- "regenerate the fixture, then RE-RECORD the demo."
53
- end
54
-
55
- it "names the component, the file:line and the fix — the three things the demo is about" do
56
- expect(expected).to include("<LikeButton>")
57
- expect(expected).to include("app/views/home/index.html.erb:3")
58
- expect(expected).to include("<LikeButton content={...} />")
59
- end
60
-
61
- it "is the message the README's demo is claimed to show" do
62
- readme = root.join("README.md").read
63
-
64
- expect(readme).to include("https://ruact.dev/readme-write-verify.gif")
65
- expect(readme).to include("Ruact::ChildrenNotSupportedError")
66
- end
67
- end