hibiki_rails 0.2.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +234 -0
- data/README.md +67 -2
- data/app/assets/javascripts/hibiki.js +404 -20
- data/lib/generators/hibiki/rails/css_variant.rb +189 -0
- data/lib/generators/hibiki/rails/generator_helpers.rb +20 -1
- data/lib/generators/hibiki/rails/island/island_generator.rb +0 -8
- data/lib/generators/hibiki/rails/scaffold/USAGE +32 -0
- data/lib/generators/hibiki/rails/scaffold/scaffold_generator.rb +73 -0
- data/lib/generators/hibiki/rails/scaffold_controller/USAGE +34 -0
- data/lib/generators/hibiki/rails/scaffold_controller/scaffold_controller_generator.rb +252 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/daisyui/views/_pagination.html.erb.tt +72 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/none/views/_pagination.html.erb.tt +60 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/channel.rb.tt +330 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/controller.rb.tt +77 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/form.rb.tt +40 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/member_channel.rb.tt +62 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/query.rb.tt +204 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/row.rb.tt +27 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_busy.html.erb.tt +103 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_controls.html.erb.tt +87 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_field_error.html.erb.tt +8 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_form.html.erb.tt +52 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_list.html.erb.tt +62 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_row.html.erb.tt +67 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_row_form.html.erb.tt +85 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/edit.html.erb.tt +13 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/index.html.erb.tt +47 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/new.html.erb.tt +12 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/show.html.erb.tt +27 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/tailwind/views/_pagination.html.erb.tt +66 -0
- data/lib/generators/hibiki/rails/scaffold_helpers.rb +161 -0
- data/lib/generators/hibiki/rails/scaffold_model_injection.rb +102 -0
- data/lib/generators/hibiki/rails/scaffold_parent_injection.rb +185 -0
- data/lib/generators/hibiki/rails/scaffold_parent_notices.rb +96 -0
- data/lib/generators/hibiki/rails/scaffold_post_install.rb +176 -0
- data/lib/generators/hibiki/rails/scaffold_schema.rb +541 -0
- data/lib/generators/hibiki/rails/scaffold_view_helpers.rb +166 -0
- data/lib/hibiki/rails/channel.rb +63 -8
- data/lib/hibiki/rails/graph_actor.rb +15 -1
- data/lib/hibiki/rails/helpers.rb +102 -14
- data/lib/hibiki/rails/reactive_form.rb +163 -0
- data/lib/hibiki/rails/version.rb +1 -1
- data/lib/hibiki/rails.rb +19 -1
- metadata +38 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3b21e4afc4b4e74e30f3c32286a1a75b30052e55145d25210bb3cecfbb3bc24c
|
|
4
|
+
data.tar.gz: 5c26612918d2b81c3a6a6a134710331ba656b25e9441856627ddf6b3ee093e71
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: acf106a0cda97184c5bd9a988bab71efb8be65c76e1f5b08785f96795d7a9dc21f67a5511a9a10ffa7b5fee49d498891ce7516b926eec1868d76820fd7c42c48
|
|
7
|
+
data.tar.gz: 5e8d7b8aba5eaf643e5c6f82dcde5c824bd8f9eb1594163dd872bc0a304a6b37a796069385ec8fdc78e8bfe48dd7d9345d4f60a20b77fe3f72760d02e9b0157e
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
The gem and the npm package are released in lockstep and share these version
|
|
4
|
+
numbers — `app/assets/javascripts/hibiki.js` is a single copy served both ways,
|
|
5
|
+
so importmap and bundler apps always resolve identical client code.
|
|
6
|
+
|
|
7
|
+
## 0.4.0 — 2026-08-03
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
**Loading and connection state, stamped by the client.** The first growth of the
|
|
12
|
+
`data-hibiki-*` protocol since 0.3.0, and the first attributes in it that no Ruby
|
|
13
|
+
helper emits — the client writes them at runtime and app CSS reads them:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
island root data-hibiki-busy present while an action is in flight
|
|
17
|
+
aria-busy="true" the same fact, for assistive tech
|
|
18
|
+
data-hibiki-state connecting | ready | offline | stalled
|
|
19
|
+
firing control data-hibiki-busy on the control that started it
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Everything an app wants out of that is a descendant selector —
|
|
23
|
+
`[data-hibiki-busy] .spinner { display: inline-block }` — so per-row and
|
|
24
|
+
per-button feedback needs no server state and no `{#if loading}` branch. The
|
|
25
|
+
Ruby surface is unchanged: no new option on `on`, `hibiki_island` or `reactive`.
|
|
26
|
+
|
|
27
|
+
**Actions are acknowledged once their batch has run**, and the ack is what clears
|
|
28
|
+
the indicator. It cannot be "clear on the next render": the core's equality gate
|
|
29
|
+
(hibiki 0.2.0) lets an ordinary action produce zero bytes — paging to the page
|
|
30
|
+
you are already on, a search that does not change the query, a destroy of a row
|
|
31
|
+
another tab already deleted — so a render-based rule hangs forever on gestures
|
|
32
|
+
users make all the time. The ack sits in an `ensure`, so a raising action stops
|
|
33
|
+
the spinner too, and a subscription that has gone away acks `dropped: true` from
|
|
34
|
+
the cable thread instead, which is what lets the client tell "late" from "never".
|
|
35
|
+
|
|
36
|
+
A page running the 0.3.0 client sends no sequence number and gets no ack, so
|
|
37
|
+
nothing about this reaches an app that has not upgraded both halves.
|
|
38
|
+
|
|
39
|
+
**Actions performed before the subscription confirms are queued, not dropped.**
|
|
40
|
+
ActionCable's `Subscription#perform` silently returns false on a socket that is
|
|
41
|
+
not open yet, and on the Turbo-broadcast path that window is about three
|
|
42
|
+
serialised round trips — a click in it used to vanish. The queue covers the first
|
|
43
|
+
connect window only: after a reconnect the server rebuilds the graph with default
|
|
44
|
+
state, so replaying intent formed against the old one is worse than dropping it,
|
|
45
|
+
and the island reads `offline` for the whole gap instead.
|
|
46
|
+
|
|
47
|
+
Three class properties on `ChannelController` are the entire tuning surface —
|
|
48
|
+
`busyDelay` (150 ms before a trip is worth mentioning), `busyGrace` (60 ms for a
|
|
49
|
+
broadcast still in flight after its ack), `busyCeiling` (10 s before a trip is
|
|
50
|
+
declared stalled rather than cleared silently). Deliberately not Stimulus values
|
|
51
|
+
and not helper options; an app that wants different numbers subclasses and
|
|
52
|
+
re-registers.
|
|
53
|
+
|
|
54
|
+
The scaffold generators wire five sites to all of this — the counts line, the
|
|
55
|
+
pagination bar, the infinite-scroll sentinel, the destroy button and the
|
|
56
|
+
inline-edit Save — through a generated `_busy.html.erb` the app owns.
|
|
57
|
+
|
|
58
|
+
**`hibiki:rails:scaffold` and `hibiki:rails:scaffold_controller`** — a reactive
|
|
59
|
+
CRUD resource generated the way `rails g scaffold` generates a plain one.
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
bin/rails g hibiki:rails:scaffold Book title:string author:references
|
|
63
|
+
bin/rails g hibiki:rails:scaffold_controller Book # an existing model
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The generated index is live: search, filter, sort and pagination are signal
|
|
67
|
+
state rather than page loads, rows edit in place, and a write from anywhere —
|
|
68
|
+
another tab, another user, the plain controller, a console — repaints every open
|
|
69
|
+
list. Everything is derived from the model's schema (columns, types, `belongs_to`
|
|
70
|
+
reflections, validators) or from the same `NAME field:type` argument list Rails'
|
|
71
|
+
own scaffold takes.
|
|
72
|
+
|
|
73
|
+
Stock `rails g scaffold` is untouched; these live under their own namespace.
|
|
74
|
+
|
|
75
|
+
Live per-field validation is derived only from rules the form can actually
|
|
76
|
+
evaluate before a round trip: presence, length, and numericality bounds that
|
|
77
|
+
carry no `allow_nil:`/`allow_blank:` exemption for the value in hand. A
|
|
78
|
+
validator gated on `if:`, `unless:` or `on:` depends on the record rather than
|
|
79
|
+
the field, so it contributes nothing live — it still runs at commit and still
|
|
80
|
+
lands in `#errors`, which the same per-field slots mirror. The clauses are
|
|
81
|
+
generated once; add validators and re-run `scaffold_controller` to pick them up.
|
|
82
|
+
|
|
83
|
+
**Field order is yours, and choosing it costs nothing.** With no field list the
|
|
84
|
+
columns follow the schema, which for an app built from `schema.rb` means
|
|
85
|
+
alphabetical. Passing them explicitly picks the order — and against a model that
|
|
86
|
+
already exists the generator still reads that model for everything else, so the
|
|
87
|
+
live clauses, a number field's `min:`/`max:` and a `belongs_to`'s display label
|
|
88
|
+
all survive the choice:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
bin/rails g hibiki:rails:scaffold_controller Author name:string bio:text age:integer
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
A field the model has no column for is still generated, from the argument list
|
|
95
|
+
alone, and named in the post-install output — it may be a column whose migration
|
|
96
|
+
is still to come, and a silently missing field is the worse failure.
|
|
97
|
+
|
|
98
|
+
**A unique index with no uniqueness validator is called out.** A database
|
|
99
|
+
constraint is not a validator, and the generated form can only mirror what the
|
|
100
|
+
model checks — so without one, a duplicate raises `ActiveRecord::RecordNotUnique`
|
|
101
|
+
on the graph thread instead of showing a field error, and the round trip
|
|
102
|
+
completes having saved nothing. The generator names the column and the exact
|
|
103
|
+
`validates` line, for single and composite indexes alike.
|
|
104
|
+
|
|
105
|
+
Options: `--css=daisyui|tailwind|none` (detected when absent),
|
|
106
|
+
`--infinite-scroll`, `--skip-pagination`, `--skip-search`, `--page-size=N`,
|
|
107
|
+
`--skip-routes`.
|
|
108
|
+
|
|
109
|
+
Three notes on what it does to an app you already have.
|
|
110
|
+
|
|
111
|
+
**The model is modified** — one `delegate` per `belongs_to`, plus the
|
|
112
|
+
`after_commit` broadcast the whole thing hangs off.
|
|
113
|
+
|
|
114
|
+
**So is each model a `belongs_to` points at.** It gains the `has_many` half
|
|
115
|
+
Rails' own scaffold never writes (without it the generated destroy button raises
|
|
116
|
+
`InvalidForeignKey`) and a ping of its own, because a row prints the parent's
|
|
117
|
+
label rather than its id — rename an author and every open books index would
|
|
118
|
+
otherwise keep the old name. `dependent:` follows the association: `:destroy`
|
|
119
|
+
when it is required, `:nullify` when it is `optional: true`. That ping is
|
|
120
|
+
collection-grained, so an open *show* page keeps the old label until reload.
|
|
121
|
+
|
|
122
|
+
Both injections are idempotent and announced, and anything you already declared
|
|
123
|
+
is left alone — including a `dependent:` you chose yourself.
|
|
124
|
+
|
|
125
|
+
And **restart the server afterwards**: `app/forms/` is likely new, and Rails
|
|
126
|
+
computes autoload paths from the `app/*` glob at boot.
|
|
127
|
+
|
|
128
|
+
### Fixed
|
|
129
|
+
|
|
130
|
+
**The model injection missed every namespaced model.** Thor anchors
|
|
131
|
+
`inject_into_class` on the class name as the file spells it, and the generator
|
|
132
|
+
passed the demodulized one — so `app/models/admin/book.rb`, which Rails writes as
|
|
133
|
+
`class Admin::Book < ApplicationRecord`, never matched. Silently: Thor rewrote
|
|
134
|
+
the file byte-identical and the generator reported a modification. The delegate
|
|
135
|
+
never landed, so the show page raised on arrival.
|
|
136
|
+
|
|
137
|
+
## 0.3.0 — 2026-07-28
|
|
138
|
+
|
|
139
|
+
### Security
|
|
140
|
+
|
|
141
|
+
**Channel lifecycle methods were client-invocable on Rails 7.1 and 7.2.**
|
|
142
|
+
Upgrade if you run hibiki_rails on either. Rails 8.x apps were never affected.
|
|
143
|
+
|
|
144
|
+
ActionCable builds a channel's client-invocable actions from the public methods
|
|
145
|
+
the class adds. The gem subtracted its lifecycle hooks through ActionCable's
|
|
146
|
+
`internal_methods` hook — but **that hook only exists on Rails 8.x**; on 7.1 and
|
|
147
|
+
7.2 `action_methods` never consults it, so the override was silently inert and
|
|
148
|
+
the hooks stayed exposed.
|
|
149
|
+
|
|
150
|
+
Reachable by any client that can open a subscription, against its own
|
|
151
|
+
connection's graph. There is no cross-connection or cross-user data exposure;
|
|
152
|
+
the impact is resource exhaustion:
|
|
153
|
+
|
|
154
|
+
- `perform("build_graph")` — exposed on **every** affected app, since
|
|
155
|
+
`#build_graph` is always public. It re-runs the graph build outside
|
|
156
|
+
`Hibiki.root`, so the effects it creates are unowned and the dispose on
|
|
157
|
+
unsubscribe never reaches them, while the previous root is still held. Each
|
|
158
|
+
call leaks; repeated calls grow memory without bound.
|
|
159
|
+
- `perform("subscribed")` — exposed only where an app defined `#subscribed`
|
|
160
|
+
public, which the ActiveRecord guide's own `after_commit` example did until
|
|
161
|
+
this release. It allocates a second `GraphActor` — a new **thread** — and
|
|
162
|
+
overwrites the reference to the first, so the original is never stopped.
|
|
163
|
+
Repeated calls exhaust the process's threads.
|
|
164
|
+
|
|
165
|
+
Fixed by subtracting in `action_methods` itself, which works on every supported
|
|
166
|
+
version:
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
HIDDEN_ACTIONS = %w[build_graph subscribed unsubscribed].freeze
|
|
170
|
+
def action_methods = super - HIDDEN_ACTIONS
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
No application change is required. Writing `#subscribed` and `#unsubscribed`
|
|
174
|
+
private is still the better habit, because it also protects the methods this
|
|
175
|
+
list does not know about — the guides now show them that way.
|
|
176
|
+
|
|
177
|
+
### Added
|
|
178
|
+
|
|
179
|
+
- `on(action, event:)` takes an **event list**, so one element can answer
|
|
180
|
+
several events: `on(:load_more, event: %i[click visible])`.
|
|
181
|
+
- **`input`** joins the delegated events, with a per-control **`debounce:`**.
|
|
182
|
+
`:input` carries 250 ms unless told otherwise (`debounce: 0` opts out); the
|
|
183
|
+
value is stamped into the markup rather than being an invisible client
|
|
184
|
+
default.
|
|
185
|
+
- **`visible`**, a pseudo-event backed by an `IntersectionObserver`, so a
|
|
186
|
+
load-more control can double as an infinite-scroll sentinel. It fires once
|
|
187
|
+
per observation and re-attaches to the replacement element after each
|
|
188
|
+
fragment swap.
|
|
189
|
+
- **`confirm:`** on `on(...)` — a `window.confirm` gate. `data-turbo-confirm`
|
|
190
|
+
does nothing on a hibiki control, since it is not a Turbo-driven form.
|
|
191
|
+
- **`reset: false`** on a submit, to keep a form's inputs. The default resets
|
|
192
|
+
them synchronously, before the server has replied, which is right for an
|
|
193
|
+
"add" form and destroys an edit form's contents on a failed commit.
|
|
194
|
+
- **Subscribe params**: `hibiki_island(channel, cid:, params: { record_id: })`
|
|
195
|
+
reach the channel as `params[:key]`, which is how a channel learns which
|
|
196
|
+
record its page is about. They are client-supplied and untrusted — use one
|
|
197
|
+
only to look up a record inside a scope the channel chooses, and never
|
|
198
|
+
interpolate one into a streamable name. They cannot override `channel` or
|
|
199
|
+
`cid`.
|
|
200
|
+
- The client has its own test suite (`spec/js`, vitest + happy-dom) and a CI
|
|
201
|
+
job, plus a pinned Rails 8.1 matrix leg.
|
|
202
|
+
|
|
203
|
+
### Changed
|
|
204
|
+
|
|
205
|
+
- **A changed checkbox now sends its checked state as a boolean**, and a
|
|
206
|
+
multi-select sends an array of its selected values. Previously both sent
|
|
207
|
+
`control.value` — the value *attribute* — so checking and unchecking a
|
|
208
|
+
checkbox produced byte-identical payloads. **This is a payload shape
|
|
209
|
+
change**: an action reading a `change` payload for a checkbox now receives
|
|
210
|
+
`true`/`false` rather than `"1"`. The form-submit path is unaffected.
|
|
211
|
+
- `transmit_value` is **equality-gated**: an effect re-runs whenever any signal
|
|
212
|
+
it read changed, so a bumped version token used to re-send every reactive
|
|
213
|
+
value's text even when byte-identical. The block still runs unconditionally,
|
|
214
|
+
so dependency collection is unchanged — only the transmit is skipped. If you
|
|
215
|
+
render a reactive placeholder *inside* a broadcast-replaced fragment, render
|
|
216
|
+
it with its current value: the swap resets the DOM text and the gate now
|
|
217
|
+
suppresses the re-send that used to heal it.
|
|
218
|
+
- Each graph job runs inside `Rails.application.executor`, so
|
|
219
|
+
`CurrentAttributes` are reset between jobs, autoloads are safe off the main
|
|
220
|
+
thread, and each job gets its own query cache.
|
|
221
|
+
- Graph-thread errors are **logged** in development and test.
|
|
222
|
+
`ActiveSupport::ErrorReporter` has no subscribers by default, so in a stock
|
|
223
|
+
app these previously vanished entirely — no line, no stack, just a fragment
|
|
224
|
+
that stopped updating. Production behaviour is unchanged.
|
|
225
|
+
|
|
226
|
+
## 0.2.0 — 2026-07-21
|
|
227
|
+
|
|
228
|
+
- Reactive values: `reactive` / `reactive_attrs` / `transmit_value`, matched
|
|
229
|
+
document-wide so a value can render outside the island that computes it.
|
|
230
|
+
- `Components::` namespacing for the Phlex generator.
|
|
231
|
+
|
|
232
|
+
## 0.1.0 — 2026-07-18
|
|
233
|
+
|
|
234
|
+
Initial release.
|
data/README.md
CHANGED
|
@@ -59,6 +59,40 @@ bin/rails g hibiki:rails:stimulus counter static_pages
|
|
|
59
59
|
|
|
60
60
|
This creates a minimal working reactive component in the given view path.
|
|
61
61
|
|
|
62
|
+
### Generating a whole CRUD resource
|
|
63
|
+
|
|
64
|
+
For a full reactive resource — a live index with search, filtering, sorting and
|
|
65
|
+
pagination, plus edit-in-place — use the scaffold generators:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
# Model, migration, route and the reactive resource, like rails g scaffold
|
|
69
|
+
bin/rails g hibiki:rails:scaffold Book title:string author:references
|
|
70
|
+
|
|
71
|
+
# Or, for a model you already have — the schema is read for you
|
|
72
|
+
bin/rails g hibiki:rails:scaffold_controller Book
|
|
73
|
+
|
|
74
|
+
# Same, but you pick the field order; everything else still comes from the model
|
|
75
|
+
bin/rails g hibiki:rails:scaffold_controller Book title:string author:references
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Your plain `rails g scaffold` is untouched. The generated markup is styled to
|
|
79
|
+
match your app (DaisyUI, Tailwind, or unstyled — detected automatically, or
|
|
80
|
+
forced with `--css=`). Run `bin/rails g hibiki:rails:scaffold --help` for the
|
|
81
|
+
rest of the options.
|
|
82
|
+
|
|
83
|
+
Listing the fields yourself only chooses their order and which ones appear — the
|
|
84
|
+
model still answers everything else, so the live validation, a number field's
|
|
85
|
+
`min:`/`max:` and a `belongs_to`'s display label all survive the choice.
|
|
86
|
+
|
|
87
|
+
Your models are edited, not just read: the one being scaffolded gains the
|
|
88
|
+
`after_commit` broadcast that makes writes from anywhere reach an open list, and
|
|
89
|
+
each model a `belongs_to` points at gains the `has_many` half plus a ping of its
|
|
90
|
+
own, so renaming a parent repaints the lists that print its name. Both are
|
|
91
|
+
idempotent, announced, and leave anything you already declared alone.
|
|
92
|
+
|
|
93
|
+
Restart the server afterwards: `app/forms/` is new, and Rails works out its
|
|
94
|
+
autoload paths at boot.
|
|
95
|
+
|
|
62
96
|
### Render the reactive component
|
|
63
97
|
|
|
64
98
|
The generated components are just Rails partials (or Phlex components, if you used the Phlex generator), so you can render one anywhere like any other partial:
|
|
@@ -73,11 +107,42 @@ Congratulations! Now you have your first reactive component!
|
|
|
73
107
|
|
|
74
108
|
Documentation site: <https://planetaska.github.io/hibiki/rails-introduction/>
|
|
75
109
|
|
|
110
|
+
Release notes and upgrade advice: [CHANGELOG.md](CHANGELOG.md). **If you run Rails 7.1 or 7.2, read the 0.3.0 entry** — it fixes channel lifecycle methods that were client-invocable on those versions.
|
|
111
|
+
|
|
76
112
|
## Development
|
|
77
113
|
|
|
78
114
|
```
|
|
79
|
-
bundle exec rake # specs + rubocop
|
|
115
|
+
bundle exec rake # Ruby specs + rubocop
|
|
116
|
+
bun install && bun run test # the client's own specs
|
|
80
117
|
```
|
|
81
118
|
|
|
82
|
-
The
|
|
119
|
+
Both are what CI runs. The Ruby suite boots a minimal inline Rails app (`spec/support/dummy_app.rb`); the JS suite (`spec/js/`) drives the real Stimulus controller in happy-dom against a stubbed Action Cable consumer.
|
|
120
|
+
|
|
121
|
+
The gem and the npm package are **released in lockstep**: `app/assets/javascripts/hibiki.js` is the single copy — the engine puts it on the asset path and `package.json` points `main`/`module`/`exports` at it — so importmap and bundler apps must never be able to resolve different client code. Bump `lib/hibiki/rails/version.rb` and `package.json` in the same commit, and publish both. The version table lives in [the JS client docs](https://planetaska.github.io/hibiki/the-js-client/).
|
|
122
|
+
|
|
123
|
+
## Contributing
|
|
124
|
+
|
|
125
|
+
Bug reports and pull requests are welcome at <https://github.com/planetaska/hibiki-rails>.
|
|
126
|
+
|
|
127
|
+
A few things that make a change easier to accept:
|
|
128
|
+
|
|
129
|
+
- **`bundle exec rake` and `bun run test` both green.** They are what CI runs, and the client half is easy to forget — most changes here touch one side, but the wire protocol is shared by both.
|
|
130
|
+
- **A regression spec first** for anything that was a bug. `spec/js/` covers the client, `spec/generators/` the generated output.
|
|
131
|
+
- **Generated code follows `rubocop-rails-omakase`**, which is what a stock Rails app lints with — not this gem's own style. The templates are written to satisfy the app's linter, not ours.
|
|
132
|
+
- **The `data-hibiki-*` attributes are a private contract** between the Ruby helpers and the vendored JS, and the two halves ship in one version — so a change to either side belongs in one commit with the other. Two of them, `data-hibiki-busy` and `data-hibiki-state`, are written by the client rather than by a helper, and apps select on them from CSS; those need a CHANGELOG entry even when no Ruby changes.
|
|
133
|
+
|
|
134
|
+
### Running against a checkout
|
|
135
|
+
|
|
136
|
+
The gem side needs nothing special: point an app's Gemfile at your clone with `gem "hibiki_rails", path: "../hibiki-rails"`.
|
|
137
|
+
|
|
138
|
+
The npm side has one trap. `bun link` from an app resolves the linked package's own imports from the **symlink's realpath**, not from the app — so `hibiki.js` looks for `@rails/actioncable` inside your checkout rather than inside the app that linked it, and if the checkout has no `node_modules` the import fails at build time with nothing pointing at the cause.
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
cd hibiki-rails && bun install # before linking, not after
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
This affects only development against a clone. Anyone installing the published package resolves normally and never sees it.
|
|
145
|
+
|
|
146
|
+
## License
|
|
83
147
|
|
|
148
|
+
[MIT](LICENSE.txt)
|