ruact 0.0.11 → 0.0.13

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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +147 -48
  3. data/CONTRIBUTING.md +233 -0
  4. data/README.md +18 -8
  5. data/RELEASING.md +125 -139
  6. data/lib/generators/ruact/install/install_generator.rb +305 -126
  7. data/lib/generators/ruact/install/templates/AGENTS.md.tt +14 -13
  8. data/lib/generators/ruact/install/templates/initializer.rb.tt +29 -7
  9. data/lib/generators/ruact/install/templates/tsconfig.json.tt +3 -0
  10. data/lib/generators/ruact/layout/layout_generator.rb +52 -0
  11. data/lib/generators/ruact/scaffold/templates/controller.rb.tt +5 -3
  12. data/lib/ruact/configuration.rb +65 -13
  13. data/lib/ruact/controller/document_rendering.rb +72 -16
  14. data/lib/ruact/controller/page_rendering.rb +134 -0
  15. data/lib/ruact/controller/pages.rb +116 -0
  16. data/lib/ruact/controller.rb +78 -11
  17. data/lib/ruact/doctor.rb +233 -25
  18. data/lib/ruact/layout_source.rb +29 -7
  19. data/lib/ruact/navigation_boundary.rb +240 -0
  20. data/lib/ruact/packaging.rb +68 -0
  21. data/lib/ruact/railtie.rb +30 -0
  22. data/lib/ruact/server.rb +10 -1
  23. data/lib/ruact/version.rb +1 -1
  24. data/lib/ruact/view_helper.rb +158 -1
  25. data/lib/ruact/views/layouts/ruact.html.erb +32 -0
  26. data/lib/ruact.rb +29 -0
  27. data/vendor/javascript/vite-plugin-ruact/flight-client.test.mjs +321 -0
  28. data/vendor/javascript/vite-plugin-ruact/package-lock.json +11 -0
  29. data/vendor/javascript/vite-plugin-ruact/package.json +1 -0
  30. data/vendor/javascript/vite-plugin-ruact/ruact-router.test.mjs +433 -0
  31. data/vendor/javascript/vite-plugin-ruact/runtime/flight-client.js +14 -3
  32. data/vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js +170 -7
  33. metadata +12 -107
  34. data/.codecov.yml +0 -31
  35. data/.github/workflows/ci.yml +0 -284
  36. data/.github/workflows/server-functions-bench.yml +0 -54
  37. data/.rubocop.yml +0 -107
  38. data/.rubocop_todo.yml +0 -63
  39. data/Rakefile +0 -10
  40. data/bench/server_functions_dispatch_bench.rb +0 -276
  41. data/bench/server_functions_dispatch_bench.results.md +0 -150
  42. data/docs/internal/README.md +0 -9
  43. data/docs/internal/decisions/server-functions-api.md +0 -2236
  44. data/spec/benchmarks/baseline.json +0 -12
  45. data/spec/benchmarks/render_pipeline_benchmark_spec.rb +0 -109
  46. data/spec/fixtures/flight/README.md +0 -136
  47. data/spec/fixtures/flight/array.txt +0 -1
  48. data/spec/fixtures/flight/as_json_object.txt +0 -2
  49. data/spec/fixtures/flight/bigint.txt +0 -1
  50. data/spec/fixtures/flight/boolean_false.txt +0 -1
  51. data/spec/fixtures/flight/boolean_true.txt +0 -1
  52. data/spec/fixtures/flight/client_component_with_props.txt +0 -2
  53. data/spec/fixtures/flight/client_reference.txt +0 -2
  54. data/spec/fixtures/flight/hash.txt +0 -1
  55. data/spec/fixtures/flight/infinity.txt +0 -1
  56. data/spec/fixtures/flight/nan.txt +0 -1
  57. data/spec/fixtures/flight/negative_infinity.txt +0 -1
  58. data/spec/fixtures/flight/nil.txt +0 -1
  59. data/spec/fixtures/flight/number_float.txt +0 -1
  60. data/spec/fixtures/flight/number_integer.txt +0 -1
  61. data/spec/fixtures/flight/react_element_no_props.txt +0 -1
  62. data/spec/fixtures/flight/redirect_row.txt +0 -1
  63. data/spec/fixtures/flight/serializable_object.txt +0 -2
  64. data/spec/fixtures/flight/string_basic.txt +0 -1
  65. data/spec/fixtures/flight/string_dollar_escape.txt +0 -1
  66. data/spec/fixtures/flight/undefined.txt +0 -1
  67. data/spec/fixtures/readme/children-error.html.erb +0 -3
  68. data/spec/fixtures/readme/children-error.txt +0 -1
  69. data/spec/fixtures/story_7_9_views/controller_request_spec_support/demo/show.html.erb +0 -3
  70. data/spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb +0 -3
  71. data/spec/fixtures/story_7_9_views/controller_request_spec_support/exploding_layout_demo/show.html.erb +0 -3
  72. data/spec/fixtures/story_7_9_views/controller_request_spec_support/ghost_layout_demo/show.html.erb +0 -3
  73. data/spec/fixtures/story_7_9_views/controller_request_spec_support/layout_demo/show.html.erb +0 -3
  74. data/spec/fixtures/story_7_9_views/controller_request_spec_support/rootless_layout_demo/show.html.erb +0 -3
  75. data/spec/fixtures/story_7_9_views/controller_request_spec_support/unwired_layout_demo/show.html.erb +0 -3
  76. data/spec/fixtures/story_7_9_views/layouts/bare_host.html.erb +0 -16
  77. data/spec/fixtures/story_7_9_views/layouts/exploding_host.html.erb +0 -24
  78. data/spec/fixtures/story_7_9_views/layouts/rootless_host.html.erb +0 -15
  79. data/spec/fixtures/story_7_9_views/layouts/ruact_host.html.erb +0 -17
  80. data/spec/readme_demo_message_spec.rb +0 -67
  81. data/spec/readme_spec.rb +0 -282
  82. data/spec/ruact/client_manifest_spec.rb +0 -270
  83. data/spec/ruact/component_contract_spec.rb +0 -119
  84. data/spec/ruact/configuration_spec.rb +0 -518
  85. data/spec/ruact/controller_request_spec.rb +0 -671
  86. data/spec/ruact/controller_spec.rb +0 -343
  87. data/spec/ruact/doctor_spec.rb +0 -769
  88. data/spec/ruact/erb_preprocessor_hook_spec.rb +0 -55
  89. data/spec/ruact/erb_preprocessor_spec.rb +0 -361
  90. data/spec/ruact/errors_spec.rb +0 -93
  91. data/spec/ruact/flight/renderer_spec.rb +0 -133
  92. data/spec/ruact/flight/serializer_spec.rb +0 -494
  93. data/spec/ruact/html_converter_spec.rb +0 -375
  94. data/spec/ruact/install_generator_spec.rb +0 -1549
  95. data/spec/ruact/layout_source_spec.rb +0 -108
  96. data/spec/ruact/manifest_resolver_spec.rb +0 -174
  97. data/spec/ruact/query_request_spec.rb +0 -706
  98. data/spec/ruact/query_spec.rb +0 -105
  99. data/spec/ruact/railtie_spec.rb +0 -155
  100. data/spec/ruact/render_context_spec.rb +0 -58
  101. data/spec/ruact/render_pipeline_concurrency_spec.rb +0 -78
  102. data/spec/ruact/render_pipeline_spec.rb +0 -928
  103. data/spec/ruact/scaffold_generator_spec.rb +0 -1849
  104. data/spec/ruact/serializable_spec.rb +0 -179
  105. data/spec/ruact/server_bucket_request_spec.rb +0 -785
  106. data/spec/ruact/server_function_name_spec.rb +0 -53
  107. data/spec/ruact/server_functions/backtrace_cleaner_spec.rb +0 -63
  108. data/spec/ruact/server_functions/bucket_two_payload_spec.rb +0 -200
  109. data/spec/ruact/server_functions/codegen_spec.rb +0 -397
  110. data/spec/ruact/server_functions/error_payload_spec.rb +0 -222
  111. data/spec/ruact/server_functions/error_suggestion_spec.rb +0 -79
  112. data/spec/ruact/server_functions/introspection_spec.rb +0 -135
  113. data/spec/ruact/server_functions/name_bridge_spec.rb +0 -212
  114. data/spec/ruact/server_functions/query_context_spec.rb +0 -72
  115. data/spec/ruact/server_functions/query_source_spec.rb +0 -193
  116. data/spec/ruact/server_functions/railtie_integration_spec.rb +0 -215
  117. data/spec/ruact/server_functions/rake_spec.rb +0 -86
  118. data/spec/ruact/server_functions/route_source_spec.rb +0 -202
  119. data/spec/ruact/server_functions/snapshot_spec.rb +0 -96
  120. data/spec/ruact/server_functions/snapshot_writer_spec.rb +0 -71
  121. data/spec/ruact/server_rescue_request_spec.rb +0 -416
  122. data/spec/ruact/server_spec.rb +0 -179
  123. data/spec/ruact/server_upload_request_spec.rb +0 -311
  124. data/spec/ruact/signed_references_spec.rb +0 -164
  125. data/spec/ruact/string_distance_spec.rb +0 -38
  126. data/spec/ruact/tasks_json_introspection_spec.rb +0 -141
  127. data/spec/ruact/testing/have_ruact_component_spec.rb +0 -170
  128. data/spec/ruact/testing/no_production_load_spec.rb +0 -41
  129. data/spec/ruact/validation_errors_spec.rb +0 -116
  130. data/spec/ruact/view_helper_spec.rb +0 -131
  131. data/spec/spec_helper.rb +0 -77
  132. data/spec/support/fixtures/pixel.png +0 -0
  133. data/spec/support/flight_wire_parser.rb +0 -21
  134. data/spec/support/flight_wire_parser_spec.rb +0 -93
  135. data/spec/support/matchers/flight_fixture_matcher.rb +0 -130
  136. data/spec/support/matchers/flight_fixture_matcher_spec.rb +0 -250
  137. data/spec/support/rails_stub.rb +0 -115
data/RELEASING.md CHANGED
@@ -1,209 +1,195 @@
1
1
  # Releasing ruact
2
2
 
3
- This document describes the complete release process for `ruact`. Following these steps in order enables any maintainer to cut a release independently.
3
+ A release publishes exactly one artifact: the `ruact` gem, to RubyGems.
4
4
 
5
- The gem and the `vite-plugin-ruact` npm package share the same version number and are **always released together** as a single operation.
5
+ The Vite plugin is not a second artifact. It ships **inside** the gem, under `vendor/javascript/`, together with
6
+ the browser runtime; a generated app imports the plugin by filesystem path off the installed gem. Neither
7
+ bundled package is published, so neither is co-versioned with anything — whatever version number their
8
+ `package.json` files carry is internal to the gem and is not a release. The standalone `vite-plugin-ruact`
9
+ package on npm is a superseded artifact from before the plugin was vendored; no ruact release touches it, and
10
+ nothing this document describes puts anything there.
6
11
 
7
- ---
12
+ **The pull request is the release.** You write the version into `lib/ruact/version.rb` and stamp the CHANGELOG
13
+ in the same pull request; merging it is what publishes. There is no switch to turn on beforehand and none to
14
+ remember afterwards, and a merge that carries no new version publishes nothing — its content accumulates under
15
+ `[Unreleased]` until somebody decides to cut a release.
16
+
17
+ That is the whole of this document, and it is why almost every hand step this file used to prescribe has been
18
+ deleted rather than corrected: the workflow builds, uploads and tags, and it writes nothing you would otherwise
19
+ write yourself.
8
20
 
9
- ## Pre-Release Checklist
21
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the checks that run on every push and pull request and how to run
22
+ them locally — this document deliberately describes none of them, so the two cannot drift apart.
23
+
24
+ ---
10
25
 
11
- Before starting, confirm all of the following:
26
+ ## Who owns what
12
27
 
13
- - [ ] All CI jobs are green on `main` (rspec matrix, rubocop, yard, benchmark, e2e)
14
- - [ ] No open issues or PRs labelled `release-blocker`
15
- - [ ] `gem/ruact.gemspec` has no TODO placeholder values (`summary`, `homepage_uri`, `source_code_uri`, `allowed_push_host` must all be set to real values before `gem build` will succeed)
16
- - [ ] You have a RubyGems account with push access to `ruact` and an OTP authenticator configured (MFA is required — `rubygems_mfa_required: true`)
17
- - [ ] You have an npm account with publish access to `vite-plugin-ruact`
18
- - [ ] You have GPG or SSH commit signing configured (recommended)
28
+ | | Owner |
29
+ |---|---|
30
+ | Whether this merge publishes | you — by putting a new version in the pull request, or not |
31
+ | The version number | you — typed, in `lib/ruact/version.rb` |
32
+ | What the CHANGELOG entry says | you, in the same pull request |
33
+ | The build, the upload to RubyGems, the tag | the `release` job, on the push to `main` |
34
+ | Anything committed to `main` | nobody but a merge — the job pushes a tag and nothing else |
19
35
 
20
36
  ---
21
37
 
22
- ## Version Decision (SemVer)
38
+ ## 1. Decide the version
23
39
 
24
- Given the current version `X.Y.Z`, choose the next version:
40
+ SemVer, against the public API: a breaking change to what consumers call (a renamed public method, a changed
41
+ signature, a removed configuration option, an incompatible change to the Flight wire format) is a major; a
42
+ backwards-compatible feature is a minor; everything else is a patch.
25
43
 
26
- | Change type | Version bump | Example |
27
- |---|---|---|
28
- | Breaking change (see below) | Major: `X+1.0.0` | `0.1.0` → `1.0.0` |
29
- | New feature, backwards-compatible | Minor: `X.Y+1.0` | `0.1.0` → `0.2.0` |
30
- | Bug fix, patch | Patch: `X.Y.Z+1` | `0.1.0` → `0.1.1` |
44
+ Nothing computes this for you and nothing defaults. That is deliberate: the number used to be derived from a
45
+ default plus a marker in a commit message, which meant a minor could arrive as a side effect of how a merge was
46
+ worded — and the first one would have landed on the version this project has reserved for a milestone it has
47
+ not reached.
31
48
 
32
- **What counts as a breaking change**: any change to the public API that requires consumers to update their code — e.g. renaming a public method, changing method signatures, removing a configuration option, or changing the Flight wire format in a non-backwards-compatible way.
49
+ A minor or a major also moves the supported-versions table in [SECURITY.md](SECURITY.md). Do it in the same
50
+ pull request; nothing checks it.
33
51
 
34
52
  ---
35
53
 
36
- ## Release Steps
37
-
38
- ### 1. Create a release branch
54
+ ## 2. Prepare the release
39
55
 
40
56
  ```bash
41
- git checkout -b release/v{X.Y.Z}
57
+ bin/release X.Y.Z
42
58
  ```
43
59
 
44
- ### 2. Bump the gem version
60
+ It refuses a dirty checkout, and one that is not `main` exactly as the remote has it — a branch is never
61
+ *behind* `main` either, and cutting a release from one would put whatever it carries into the release. It
62
+ refuses a version that is not one step from the current one, and one that is already tagged. Then it writes `lib/ruact/version.rb`, re-resolves `Gemfile.lock` against
63
+ it, moves the accumulated `[Unreleased]` content in [CHANGELOG.md](CHANGELOG.md) under a dated heading, opens a
64
+ fresh empty `[Unreleased]`, rewrites both link references, proves the result against the changelog checks and
65
+ the release gate, commits, pushes, and opens the pull request.
45
66
 
46
- Edit `gem/lib/ruact/version.rb`:
67
+ **The pull-request body it writes is the checklist**, with the version already substituted. There is no
68
+ checklist document in this repository on purpose: a checklist is a second copy of the process in the
69
+ imperative, and it rots the way every other second copy does. This one is generated by the thing that performs
70
+ the process, on every release.
47
71
 
48
- ```ruby
49
- module Ruact
50
- VERSION = "{X.Y.Z}"
51
- end
52
- ```
72
+ You can do all of it by hand — the script performs no step you could not — but then you own the parts it
73
+ proves, and the gate on the pull request is where you find out.
53
74
 
54
- ### 3. Bump the npm package version
75
+ CHANGELOG.md deserves one warning of its own, because it has three readers who share no directory: this
76
+ repository, a `.gem` unpacked on somebody's disk, and the generated changelog page on the documentation site.
77
+ An entry may name a file but must not **link** one relatively, because a target that resolves here 404s in the
78
+ other two. Absolute URLs only, or no link at all.
55
79
 
56
- Edit `packages/vite-plugin-ruact/package.json`:
80
+ ---
57
81
 
58
- ```json
59
- {
60
- "version": "{X.Y.Z}"
61
- }
62
- ```
82
+ ## 3. Merge
63
83
 
64
- ### 4. Update `gem/CHANGELOG.md`
84
+ On the resulting push to `main`, the `release` job — which waits on every other job in the workflow — asks one
85
+ question: **does `lib/ruact/version.rb` name a version that has no tag?**
65
86
 
66
- 1. Move all items under `## [Unreleased]` into a new section `## [{X.Y.Z}] - {YYYY-MM-DD}`.
67
- 2. Add a new empty `## [Unreleased]` section at the very top (above the new release section).
68
- 3. Add or update the link footer at the bottom:
69
- ```
70
- [Unreleased]: https://github.com/luizcg/ruact/compare/v{X.Y.Z}...HEAD
71
- [{X.Y.Z}]: https://github.com/luizcg/ruact/compare/v{PREV}...v{X.Y.Z}
72
- ```
73
- 4. If this release contains breaking changes, ensure the section includes a `[BREAKING]` subsection with a **Migration Guide** (see "Breaking Changes" section below).
87
+ If it does not, the job reports that there is nothing to publish and ends green. That is the ordinary case;
88
+ most merges are not releases.
74
89
 
75
- ### 5. Update `packages/vite-plugin-ruact/CHANGELOG.md`
90
+ If it does, the job authenticates to RubyGems through Trusted Publishing (OIDC) — a short-lived credential
91
+ minted for that run, no stored secret, nothing interactive — then builds the gem, uploads it, and **tags last**.
76
92
 
77
- Same format as step 4 for the npm package changelog.
93
+ **Why the tag comes last.** The tag is the record of a publish that finished. An upload that fails leaves no
94
+ tag, so the version is still untagged, so the next green push to `main` tries again — automatically, with
95
+ nothing to unwind and nobody to remember. The cost of that order is a re-run that meets a version already on
96
+ RubyGems, which is why a duplicate-version rejection is treated as *already done* rather than as a failure.
78
97
 
79
- ### 6. Commit the release
98
+ **Why a question about state rather than about this push.** A trigger that fired on "the version changed in
99
+ this push" would be lost forever whenever no run is created for a push — which happens, merge commits
100
+ included. The version would then say one thing, RubyGems another, and the next release would skip the number
101
+ entirely. Asking about state instead makes the job idempotent and self-healing: run it twice and the second
102
+ does nothing; miss it once and the next green push repairs it.
80
103
 
81
- ```bash
82
- git add gem/lib/ruact/version.rb \
83
- packages/vite-plugin-ruact/package.json \
84
- gem/CHANGELOG.md \
85
- packages/vite-plugin-ruact/CHANGELOG.md
86
- git commit -m "Release v{X.Y.Z}"
87
- ```
104
+ ---
88
105
 
89
- ### 7. Push the release branch and open a PR
106
+ ## 4. Verify
90
107
 
91
108
  ```bash
92
- git push origin release/v{X.Y.Z}
109
+ curl -s https://rubygems.org/api/v1/versions/ruact/latest.json
93
110
  ```
94
111
 
95
- Open a PR from `release/v{X.Y.Z}` → `main`. Wait for CI to go green before continuing.
112
+ Use that endpoint. The other one — `/gems/ruact.json` — is CDN-cached and keeps serving the previous version
113
+ for minutes after a successful publish, which reads exactly like a release that did not happen.
96
114
 
97
- ### 8. Merge, verify CI, and tag
98
-
99
- Merge the PR. Confirm all CI jobs pass on the merge commit, then tag the verified merge commit:
115
+ ---
100
116
 
101
- ```bash
102
- git checkout main
103
- git pull origin main
104
- git tag v{X.Y.Z}
105
- git push origin v{X.Y.Z}
106
- ```
117
+ ## 5. When nothing was published and nothing said so
107
118
 
108
- > **Why tag after merge?** Tagging after CI passes on the merge commit ensures the tag always points to a verified, releasable commit. Tagging the branch commit before merge risks tagging code that fails CI after merge.
119
+ From the outside, a release that did not happen and one that did look identical until you check RubyGems. Every
120
+ case below shares one recovery, and it is the reason the trigger has the shape it has: **the version is still
121
+ untagged, so the next green push to `main` publishes it.** Nothing has to be unwound and nothing has to be
122
+ remembered.
109
123
 
110
- ### 9. Publish the gem to RubyGems
124
+ **A job the `release` job waits on went red on the push to `main`.** The release job never ran, the merge has
125
+ landed, and no error anywhere says "no release happened". The run that matters is the one on `main`, not the
126
+ one on the pull request:
111
127
 
112
128
  ```bash
113
- cd gem
114
- gem build ruact.gemspec
115
- gem push ruact-{X.Y.Z}.gem
116
- # Enter OTP when prompted (MFA is required)
117
- rm ruact-{X.Y.Z}.gem
129
+ gh run list --branch main -L 3
130
+ gh run rerun <run-id> --failed
118
131
  ```
119
132
 
120
- > **Note**: `spec.files` in the gemspec is populated via `git ls-files -z`. The files `CHANGELOG.md`, `RELEASING.md`, and `SECURITY.md` must be committed to git to appear in the gem tarball. Verify with:
121
- > ```bash
122
- > gem contents ruact-{X.Y.Z} | grep -E 'CHANGELOG|RELEASING|SECURITY'
123
- > ```
133
+ **No run was created for the push at all.** Same state, same repair; there is simply no run to re-run. Push
134
+ something green to `main` — the next merge does it on its own.
124
135
 
125
- ### 10. Publish the npm package
136
+ **The upload failed, or Trusted Publishing rejected the run.** Nothing was tagged, because the tag is written
137
+ after the upload. Fix the cause and let the next green push reconcile it.
126
138
 
127
- ```bash
128
- cd packages/vite-plugin-ruact
129
- npm publish
130
- ```
139
+ **The version is on RubyGems but the API still shows the old one.** CDN cache; see §4.
131
140
 
132
- ### 11. Create a GitHub Release
141
+ **A release was prepared and should not go out.** If it has not merged, close the pull request — nothing was
142
+ released and nothing has to be undone. If it has, see §6.
133
143
 
134
- 1. Go to [Releases](https://github.com/luizcg/ruact/releases/new)
135
- 2. Select tag `v{X.Y.Z}`
136
- 3. Title: `v{X.Y.Z}`
137
- 4. Body: paste the `## [{X.Y.Z}]` section from `gem/CHANGELOG.md`
138
- 5. Publish
144
+ **The suite is red on `main` because the record and the code disagree.** The changelog checks run in a job the
145
+ release job waits on, so once `lib/ruact/version.rb` and `CHANGELOG.md` stop naming the same version, **every**
146
+ later release waits too. The failure names which of the two moved.
139
147
 
140
148
  ---
141
149
 
142
- ## Breaking Changes
143
-
144
- When a release contains a breaking change:
150
+ ## 6. Stopping a release that has already merged
145
151
 
146
- 1. Bump the **major** version (e.g. `0.1.0` → `1.0.0`).
147
- 2. Mark the CHANGELOG entry with `[BREAKING]` and include a **Migration Guide** sub-section:
152
+ Publication is irreversible, so there is a stop — a repository variable, read by the job itself:
148
153
 
149
- ```markdown
150
- ## [1.0.0] - YYYY-MM-DD
154
+ ```bash
155
+ gh variable set RUACT_RELEASE_HALT -b stop -R luizcg/ruact
156
+ ```
151
157
 
152
- ### Changed
153
- - [BREAKING] `Ruact::Serializable.ruact_props` now rejects non-Symbol arguments at class-load time
158
+ Any non-empty value stops it. Unset — the normal, permanent state — publishes.
154
159
 
155
- #### Migration Guide
160
+ **A halted run fails red, naming the version it refused.** That is the point of it: the stop is a state
161
+ somebody has to undo, and a stop that produced a quiet, successful-looking run would suppress every later
162
+ release invisibly. This one reddens `main` on the next merge instead, which is a thing you find out about.
156
163
 
157
- **Before:**
158
- ```ruby
159
- class Post
160
- include Ruact::Serializable
161
- ruact_props "id", "title" # strings silently coerced
162
- end
163
- ```
164
- **After:**
165
- ```ruby
166
- class Post
167
- include Ruact::Serializable
168
- ruact_props :id, :title # must be Symbols
169
- end
170
- ```
171
- ```
164
+ ```bash
165
+ gh variable delete RUACT_RELEASE_HALT -R luizcg/ruact
166
+ ```
172
167
 
173
- 3. Announce the breaking change prominently in the GitHub Release body.
168
+ The version is still untagged while the halt is in place, so clearing it does not require another bump: the
169
+ next green push publishes what was held.
174
170
 
175
171
  ---
176
172
 
177
- ## Rollback
173
+ ## 7. Rollback
178
174
 
179
- If a release contains a critical defect and must be pulled:
175
+ If a published version has a critical defect:
180
176
 
181
- **RubyGems** (within 30 days):
182
177
  ```bash
183
- gem yank ruact -v {X.Y.Z}
178
+ gem yank ruact -v X.Y.Z
184
179
  ```
185
180
 
186
- **npm** (within 72 hours):
187
- ```bash
188
- npm unpublish vite-plugin-ruact@{X.Y.Z}
189
- ```
181
+ This is the one RubyGems command still run by a person rather than by the workflow, and it is the one place the
182
+ "nothing interactive" of §3 does not apply: the gem declares `rubygems_mfa_required`, so yanking needs your own
183
+ RubyGems credential and a one-time code. The workflow's OIDC credential is minted for its own run and is no
184
+ help here.
190
185
 
191
- After yanking, cut a patch release (`{X.Y.Z+1}`) with the fix immediately. Do not leave the version yanked without a replacement.
186
+ Then cut a patch release with the fix immediately. A yanked version with no replacement leaves anyone who
187
+ pinned it with nothing to move to. There is nothing to unpublish anywhere else — this process publishes to
188
+ RubyGems only.
192
189
 
193
190
  ---
194
191
 
195
- ## Troubleshooting
196
-
197
- **`gem push` fails with "MFA required"**: Run `gem signin` first and ensure your OTP authenticator is set up at https://rubygems.org/settings/edit.
192
+ ## Where this ends
198
193
 
199
- **Files missing from gem tarball**: The gemspec uses `git ls-files -z`. Ensure all new files (e.g. `CHANGELOG.md`) are committed to git before running `gem build`.
200
-
201
- **CI fails on release branch**: Fix the issue on the release branch and push the fix. Wait for CI to pass before tagging. If you already pushed a tag pointing to a broken commit, delete it and recreate after the fix:
202
- ```bash
203
- git tag -d v{X.Y.Z} # delete local tag
204
- git push origin :refs/tags/v{X.Y.Z} # delete remote tag
205
- # fix the issue, merge, then re-tag from the correct commit
206
- git checkout main && git pull origin main
207
- git tag v{X.Y.Z} && git push origin v{X.Y.Z}
208
- ```
209
- Do not use `git push --force` on tags — force-pushing a tag rewrite history for anyone who has already fetched it.
194
+ The gem is published and verified. The rest of a release — the private planning side — is outside this
195
+ repository and outside this document.