hibiki_rails 0.4.0 → 0.5.1
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 +126 -0
- data/README.md +15 -2
- data/lib/generators/hibiki/rails/css_variant.rb +7 -4
- data/lib/generators/hibiki/rails/generator_helpers.rb +1 -2
- data/lib/generators/hibiki/rails/install/install_generator.rb +1 -2
- data/lib/generators/hibiki/rails/install/templates/hibiki_controller.js.tt +3 -6
- data/lib/generators/hibiki/rails/island/templates/channel.rb.tt +1 -3
- data/lib/generators/hibiki/rails/island/templates/display.html.erb.tt +1 -2
- data/lib/generators/hibiki/rails/island/templates/island.html.erb.tt +2 -6
- data/lib/generators/hibiki/rails/phlex/phlex_generator.rb +3 -4
- data/lib/generators/hibiki/rails/phlex/templates/channel.rb.tt +1 -4
- data/lib/generators/hibiki/rails/phlex/templates/component.rb.tt +3 -8
- data/lib/generators/hibiki/rails/phlex/templates/island_component.rb.tt +3 -5
- data/lib/generators/hibiki/rails/scaffold/USAGE +9 -5
- data/lib/generators/hibiki/rails/scaffold/scaffold_generator.rb +2 -0
- data/lib/generators/hibiki/rails/scaffold_controller/USAGE +15 -13
- data/lib/generators/hibiki/rails/scaffold_controller/scaffold_controller_generator.rb +64 -6
- data/lib/generators/hibiki/rails/scaffold_controller/templates/daisyui/views/_pagination.html.erb.tt +9 -37
- data/lib/generators/hibiki/rails/scaffold_controller/templates/none/views/_pagination.html.erb.tt +9 -24
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/daisyui/views/pagination.rb.tt +54 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/none/views/pagination.rb.tt +56 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/controls.rb.tt +99 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/edit.rb.tt +24 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/field_error.rb.tt +15 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/form.rb.tt +63 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/index.rb.tt +45 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/list.rb.tt +51 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/new.rb.tt +22 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/row.rb.tt +70 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/row_form.rb.tt +81 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/show.rb.tt +39 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/tailwind/views/pagination.rb.tt +60 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/channel.rb.tt +33 -131
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/controller.rb.tt +14 -4
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/form.rb.tt +5 -16
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/hibiki_busy.css.tt +63 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/member_channel.rb.tt +11 -22
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/query.rb.tt +16 -76
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/row.rb.tt +3 -11
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_controls.html.erb.tt +10 -41
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_form.html.erb.tt +1 -5
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_list.html.erb.tt +7 -33
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_row.html.erb.tt +10 -33
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_row_form.html.erb.tt +11 -34
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/index.html.erb.tt +7 -26
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/show.html.erb.tt +3 -9
- data/lib/generators/hibiki/rails/scaffold_controller/templates/tailwind/views/_pagination.html.erb.tt +9 -25
- data/lib/generators/hibiki/rails/scaffold_model_injection.rb +8 -21
- data/lib/generators/hibiki/rails/scaffold_parent_injection.rb +4 -21
- data/lib/generators/hibiki/rails/scaffold_parent_notices.rb +6 -9
- data/lib/generators/hibiki/rails/scaffold_phlex_helpers.rb +274 -0
- data/lib/generators/hibiki/rails/scaffold_post_install.rb +27 -15
- data/lib/generators/hibiki/rails/scaffold_schema.rb +11 -8
- data/lib/generators/hibiki/rails/scaffold_transport_stylesheet.rb +141 -0
- data/lib/generators/hibiki/rails/scaffold_view_helpers.rb +88 -23
- data/lib/generators/hibiki/rails/stimulus/templates/channel.rb.tt +1 -3
- data/lib/generators/hibiki/rails/stimulus/templates/display.html.erb.tt +1 -2
- data/lib/generators/hibiki/rails/stimulus/templates/island.html.erb.tt +1 -3
- data/lib/hibiki/rails/channel.rb +8 -4
- data/lib/hibiki/rails/version.rb +1 -1
- metadata +21 -6
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_busy.html.erb.tt +0 -103
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cd126c85e743e5ec9c89e54a1ffaba4cb04e6b6b0ef89868d60023168a0936a7
|
|
4
|
+
data.tar.gz: 00b4dfa1606348df1039cf5b69d1129c170fd418966b3db120964b71eba1e1c6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3c1e7d75c946f79cc898c20c2c3a8457ba78c152963f8b8155dd70a26308c138b31acc4b7a732e037d6bc28e933fb542a509d2dabee2e03602727d7e5e81b6d6
|
|
7
|
+
data.tar.gz: 6edf4361a4edbda81b933167cb6a7e08030cb50aac694c601a22c2210036eab5c56b101a449d3ea174a5485347e7c9c6e1cc86f9da123cbfe02e0e1ed34f41dd
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,132 @@ The gem and the npm package are released in lockstep and share these version
|
|
|
4
4
|
numbers — `app/assets/javascripts/hibiki.js` is a single copy served both ways,
|
|
5
5
|
so importmap and bundler apps always resolve identical client code.
|
|
6
6
|
|
|
7
|
+
## 0.5.1 — 2026-08-08
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
**Generator output and messages slimmed down.** The scaffold templates used to
|
|
12
|
+
carry long design-rationale comments into every generated file; they are now
|
|
13
|
+
one-to-three-line hints, with the docs holding the prose. The safety notes a
|
|
14
|
+
user editing the file actually needs stayed: public channel methods are
|
|
15
|
+
client-invocable actions, ActionCable's exact-arity rule, the untrusted
|
|
16
|
+
subscribe param, Phlex omitting `false`-valued attributes, and the busy
|
|
17
|
+
stylesheet's do-not-wrap-in-a-layer rule.
|
|
18
|
+
|
|
19
|
+
The generators' status notices were shortened the same way — they still say
|
|
20
|
+
what to do, just not why at essay length.
|
|
21
|
+
|
|
22
|
+
No behavior change anywhere: no code inside any template moved, and neither
|
|
23
|
+
the runtime nor the packaged client changed (the npm 0.5.1 exists only to keep
|
|
24
|
+
the lockstep rule). Re-running a scaffold with `--force` rewrites the views
|
|
25
|
+
with the shorter comments; that diff is the whole upgrade.
|
|
26
|
+
|
|
27
|
+
## 0.5.0 — 2026-08-05
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
**`--phlex` on both scaffold generators.** `bin/rails g hibiki:rails:scaffold
|
|
32
|
+
Book title:string --phlex` emits Phlex components under `app/views/books/*.rb`,
|
|
33
|
+
namespaced `Views::`, instead of ERB templates. Purely additive: without the
|
|
34
|
+
flag nothing about the generated output changes, byte for byte.
|
|
35
|
+
|
|
36
|
+
Only the view layer moves. The channels, the query object, the ReactiveForm,
|
|
37
|
+
the model injections, every action and the whole `data-hibiki-*` protocol are
|
|
38
|
+
the same either way — the controller gains an explicit `render Views::…` at
|
|
39
|
+
each of its six render sites, and the two `broadcast_morph` calls swap
|
|
40
|
+
`partial:`/`locals:` for `renderable:`, and that is the entire difference
|
|
41
|
+
outside the templates.
|
|
42
|
+
|
|
43
|
+
Needs `phlex-rails` and `bin/rails g phlex:install`. The generator warns when
|
|
44
|
+
either is missing and writes the files anyway, so the scaffold can come first.
|
|
45
|
+
|
|
46
|
+
Four things worth knowing, because they are not what an ERB reader expects.
|
|
47
|
+
Phlex renders `String`, `Symbol`, `Integer` and `Float` and raises on anything
|
|
48
|
+
else, so date, time and decimal columns are emitted with an explicit `to_s`.
|
|
49
|
+
Phlex omits a `false`-valued attribute entirely, so the page control's
|
|
50
|
+
`data-turbo` is the string `"false"`. Phlex emits no whitespace between
|
|
51
|
+
siblings, so the components space their inline neighbours explicitly. And
|
|
52
|
+
`options_for_select` outputs directly and raises if its return value is passed
|
|
53
|
+
on, so both select sites take their options from a block.
|
|
54
|
+
|
|
55
|
+
The page control is still the one component with a per-`--css` fork, in both
|
|
56
|
+
trees. Under Phlex the plain-Tailwind fork's *reason* dissolves — a hoisted
|
|
57
|
+
template local becomes an ordinary constant — but it stays forked so the two
|
|
58
|
+
trees match file for file and a future `--css` decision stays a diff rather
|
|
59
|
+
than a judgement call.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
**The loading and connection recipes are an asset, not a partial.** They were a
|
|
64
|
+
103-line inline `<style>` emitted as `app/views/<resource>/_busy.html.erb` and
|
|
65
|
+
rendered once per page; they are now
|
|
66
|
+
`app/assets/stylesheets/hibiki_busy.css`, written once per app.
|
|
67
|
+
|
|
68
|
+
Per resource was always wrong: every rule keys on an attribute the client
|
|
69
|
+
stamps and none of them mentions a model, so a two-resource app carried two
|
|
70
|
+
byte-identical copies. It also put CSS somewhere a Content-Security-Policy that
|
|
71
|
+
forbids inline styles would reject.
|
|
72
|
+
|
|
73
|
+
The generator wires it for you: a cssbundling or tailwindcss-rails entry
|
|
74
|
+
stylesheet gets an `@import`, a layout already using
|
|
75
|
+
`stylesheet_link_tag :app` or `:all` needs nothing, and anything else gets a
|
|
76
|
+
`stylesheet_link_tag` injected into the layout. Only when none of those applies
|
|
77
|
+
does it print the line to add. Every branch is idempotent.
|
|
78
|
+
|
|
79
|
+
**If you re-run the generator on an app scaffolded before this**, the old
|
|
80
|
+
`_busy.html.erb` stays on disk — a generator never deletes — and nothing
|
|
81
|
+
renders it any more. The post-install output names it; delete it.
|
|
82
|
+
|
|
83
|
+
The rules are deliberately unlayered, and the file says so: two of them set
|
|
84
|
+
`display` on elements that also carry Tailwind utilities, and unlayered
|
|
85
|
+
declarations beat `@layer utilities` whatever the link order.
|
|
86
|
+
|
|
87
|
+
### Fixed
|
|
88
|
+
|
|
89
|
+
**The npm package no longer drags in a second copy of `@rails/actioncable`.**
|
|
90
|
+
It moves from `dependencies` to `peerDependencies` at `>= 7.0`, matching
|
|
91
|
+
turbo-rails' own range.
|
|
92
|
+
|
|
93
|
+
Why there were two: `@rails/actioncable`'s npm `latest` dist-tag is 7.2.302 even
|
|
94
|
+
though 8.x is published, and resolvers prefer `latest` when it satisfies the
|
|
95
|
+
range. So turbo-rails' `>=7.0` took 7.2.302 while this package's `>= 8.0` was
|
|
96
|
+
forced up to 8.1.301, and both ended up in the bundle — about 16 KB of duplicate
|
|
97
|
+
client, and two separate module instances that could never share a consumer.
|
|
98
|
+
A fresh install happened to hoist a single copy; the duplicate appeared when
|
|
99
|
+
adding hibiki-rails to an app whose lockfile already pinned 7.2.302, which is
|
|
100
|
+
every existing app.
|
|
101
|
+
|
|
102
|
+
If your bundler warns about an unmet peer, install `@rails/actioncable`
|
|
103
|
+
explicitly — but a stock Rails app already has it via turbo-rails, and both bun
|
|
104
|
+
and npm 7+ auto-install a missing peer. The client uses only `createConsumer`,
|
|
105
|
+
`subscriptions.create` and `subscription.perform`, all stable since Action
|
|
106
|
+
Cable 6, so the lower floor changes nothing at runtime.
|
|
107
|
+
|
|
108
|
+
### Changed — BREAKING
|
|
109
|
+
|
|
110
|
+
**The Rails floor is now 8.0.** `actioncable` and `railties` move from
|
|
111
|
+
`>= 7.1` to `>= 8.0`, and the 7.1 / 7.2 CI legs are gone. Ruby stays at `>= 3.4`.
|
|
112
|
+
|
|
113
|
+
This is a correction as much as a policy change: **generated controllers never
|
|
114
|
+
ran on Rails 7.** `hibiki:rails:scaffold` emits `params.expect` at two sites,
|
|
115
|
+
inherited from Rails 8's own scaffold, and `ActionController::Parameters#expect`
|
|
116
|
+
does not exist before 8.0 — so a generated controller raised `NoMethodError` on
|
|
117
|
+
the first request to `show`, `edit`, `update`, `create` or `destroy` on 7.1 and
|
|
118
|
+
7.2. The generator suite never caught it because those specs assert on emitted
|
|
119
|
+
source text and never boot the result.
|
|
120
|
+
|
|
121
|
+
The alternative — branching the template on `Rails::VERSION` — was rejected: it
|
|
122
|
+
would also need a way to *execute* generated output on the old legs, which is
|
|
123
|
+
more work than dropping two CI legs for a version combination (Rails 7.x on
|
|
124
|
+
Ruby >= 3.4) that barely exists.
|
|
125
|
+
|
|
126
|
+
**If you are on Rails 7.1 or 7.2, stay on 0.4.0.** It remains available and is
|
|
127
|
+
unaffected; nothing in 0.5.0 is a security fix for it. Note that the *runtime*
|
|
128
|
+
half of the gem — channels, the graph, the broadcast helpers, the client — has
|
|
129
|
+
no known 8.0-only dependency; it is the generators whose output does. The floor
|
|
130
|
+
applies to the whole gem anyway, because shipping a gem whose headline generator
|
|
131
|
+
cannot run on its own declared floor is what got us here.
|
|
132
|
+
|
|
7
133
|
## 0.4.0 — 2026-08-03
|
|
8
134
|
|
|
9
135
|
### Added
|
data/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Turbo Streams broadcast → Turbo morphs the DOM
|
|
|
10
10
|
|
|
11
11
|
A graph lives per cable connection (in practice: per browser tab), built when the channel subscribes and disposed when it unsubscribes. Effects subscribe to whatever signals they read; when an action writes a signal, exactly the affected effects re-render and broadcast.
|
|
12
12
|
|
|
13
|
-
Supports Rails >=
|
|
13
|
+
Supports Rails >= 8.0, Ruby >= 3.4.
|
|
14
14
|
|
|
15
15
|
## Rails quick start
|
|
16
16
|
|
|
@@ -80,6 +80,13 @@ match your app (DaisyUI, Tailwind, or unstyled — detected automatically, or
|
|
|
80
80
|
forced with `--css=`). Run `bin/rails g hibiki:rails:scaffold --help` for the
|
|
81
81
|
rest of the options.
|
|
82
82
|
|
|
83
|
+
Pass `--phlex` for Phlex components under `app/views/books/*.rb` instead of ERB
|
|
84
|
+
templates. It needs `phlex-rails` and `bin/rails g phlex:install`, and it
|
|
85
|
+
changes the view layer only — the channels, the query object, the form and the
|
|
86
|
+
whole client protocol are the same either way. Note this is a different thing
|
|
87
|
+
from the `hibiki_phlex` gem, which makes a component own reactive state; here
|
|
88
|
+
the channel owns the state and the components are ordinary stateless views.
|
|
89
|
+
|
|
83
90
|
Listing the fields yourself only chooses their order and which ones appear — the
|
|
84
91
|
model still answers everything else, so the live validation, a number field's
|
|
85
92
|
`min:`/`max:` and a `belongs_to`'s display label all survive the choice.
|
|
@@ -90,6 +97,12 @@ each model a `belongs_to` points at gains the `has_many` half plus a ping of its
|
|
|
90
97
|
own, so renaming a parent repaints the lists that print its name. Both are
|
|
91
98
|
idempotent, announced, and leave anything you already declared alone.
|
|
92
99
|
|
|
100
|
+
One file lands outside the resource: `app/assets/stylesheets/hibiki_busy.css`,
|
|
101
|
+
which styles the loading and connection state the client stamps. It is written
|
|
102
|
+
once per app, shared by every generated resource, and wired into your stylesheet
|
|
103
|
+
or layout automatically — the post-install output says which, or gives you the
|
|
104
|
+
line to add when it cannot tell.
|
|
105
|
+
|
|
93
106
|
Restart the server afterwards: `app/forms/` is new, and Rails works out its
|
|
94
107
|
autoload paths at boot.
|
|
95
108
|
|
|
@@ -107,7 +120,7 @@ Congratulations! Now you have your first reactive component!
|
|
|
107
120
|
|
|
108
121
|
Documentation site: <https://planetaska.github.io/hibiki/rails-introduction/>
|
|
109
122
|
|
|
110
|
-
Release notes and upgrade advice: [CHANGELOG.md](CHANGELOG.md). **If you
|
|
123
|
+
Release notes and upgrade advice: [CHANGELOG.md](CHANGELOG.md). **Rails 7.1 and 7.2 are supported up to 0.4.0 only** — 0.5.0 raises the floor to 8.0. If you are staying on 0.4.0 with Rails 7.x, read the 0.3.0 entry: it fixes channel lifecycle methods that were client-invocable on those versions.
|
|
111
124
|
|
|
112
125
|
## Development
|
|
113
126
|
|
|
@@ -121,10 +121,13 @@ module Hibiki
|
|
|
121
121
|
badge_warning: "inline-flex items-center rounded-full bg-amber-100 px-2 py-1 " \
|
|
122
122
|
"text-xs font-medium text-amber-800",
|
|
123
123
|
# `inline-block` is load-bearing, not decoration: a bare span is
|
|
124
|
-
# inline, where h-3/w-3 do nothing. It
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
124
|
+
# inline, where h-3/w-3 do nothing. It also collides with
|
|
125
|
+
# .hbk-control-busy's `display: none` at equal specificity — the
|
|
126
|
+
# transport stylesheet wins because it is UNLAYERED and Tailwind's
|
|
127
|
+
# utilities are in @layer utilities, which beats them whatever the
|
|
128
|
+
# link order. (Before 0.5.0 the rules were a <style> in the body and
|
|
129
|
+
# won on document order instead; that is why the stylesheet's header
|
|
130
|
+
# now says not to wrap it in a layer.)
|
|
128
131
|
spinner: "inline-block h-3 w-3 animate-spin rounded-full border-2 " \
|
|
129
132
|
"border-current border-r-transparent align-[-0.15em]",
|
|
130
133
|
warning_text: "text-amber-600",
|
|
@@ -71,8 +71,7 @@ module Hibiki
|
|
|
71
71
|
def wiring_hint
|
|
72
72
|
return if hibiki_registered? && helpers_included?
|
|
73
73
|
|
|
74
|
-
say_status :hint, "the packaged client is not fully wired " \
|
|
75
|
-
"(register line and/or Helpers include) — run: " \
|
|
74
|
+
say_status :hint, "the packaged client is not fully wired. Run: " \
|
|
76
75
|
"bin/rails g hibiki:rails:install", :yellow
|
|
77
76
|
end
|
|
78
77
|
end
|
|
@@ -103,8 +103,7 @@ module Hibiki
|
|
|
103
103
|
|
|
104
104
|
def bundler_note
|
|
105
105
|
say_status :skip, "#{IMPORTMAP} not found — with a JS bundler, " \
|
|
106
|
-
"npm/yarn add hibiki-rails instead
|
|
107
|
-
"@rails/actioncable)", :yellow
|
|
106
|
+
"npm/yarn/bun add hibiki-rails instead", :yellow
|
|
108
107
|
end
|
|
109
108
|
end
|
|
110
109
|
end
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
// Registers the packaged hibiki client
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// identifier in data-controller. File-backed on purpose: `bin/rails
|
|
5
|
-
// stimulus:manifest:update` re-derives this exact registration from the
|
|
6
|
-
// filename, so it survives manifest rewrites.
|
|
1
|
+
// Registers the packaged hibiki client as "hibiki" Stimulus controller.
|
|
2
|
+
// `bin/rails stimulus:manifest:update` re-derives this,
|
|
3
|
+
// so it survives manifest rewrites.
|
|
7
4
|
export { default } from "hibiki-rails"
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Generated by hibiki:rails:island — a working mini-example: one state,
|
|
4
|
-
# one derived, one action, one broadcasting effect.
|
|
5
|
-
# actor, the root, the per-action batch, and the [channel_name, cid]
|
|
6
|
-
# stream convention; this class is just the graph and its actions.
|
|
4
|
+
# one derived, one action, one broadcasting effect.
|
|
7
5
|
class <%= class_name %>Channel < ApplicationCable::Channel
|
|
8
6
|
include Hibiki::Rails::Channel
|
|
9
7
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
<%%# The root id is the
|
|
2
|
-
channel effect's target. %>
|
|
1
|
+
<%%# The root id is the replacement key. Keep in sync with channel effect's target. %>
|
|
3
2
|
<p id="<%= display_dom_id %>">
|
|
4
3
|
count: <strong><%%= count %></strong> · doubled: <strong><%%= doubled %></strong>
|
|
5
4
|
</p>
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
<%%#
|
|
2
|
-
write the underlying data attributes by hand, they are private to the
|
|
3
|
-
gem and version with its packaged JS. %>
|
|
1
|
+
<%%# Reactive island generated by hibiki_rails %>
|
|
4
2
|
<%% cid = local_assigns.fetch(:cid) { SecureRandom.uuid } %>
|
|
5
3
|
<%%= tag.div(**hibiki_island(<%= class_name %>Channel, cid:)) do %>
|
|
6
4
|
<%%= turbo_stream_from "<%= stream_name %>", cid %>
|
|
7
5
|
|
|
8
|
-
<%%#
|
|
9
|
-
Turbo stream above confirms, so the graph's first broadcast replaces
|
|
10
|
-
this. %>
|
|
6
|
+
<%%# Placeholder only: the graph's first broadcast replaces this. %>
|
|
11
7
|
<%%= render "<%= view_dir %>/<%= file_name %>_display", count: 0, doubled: 0 %>
|
|
12
8
|
|
|
13
9
|
<p><%%= tag.button("+1", **on(:increment)) %></p>
|
|
@@ -27,9 +27,8 @@ module Hibiki
|
|
|
27
27
|
def warn_without_hibiki_phlex
|
|
28
28
|
require "hibiki/phlex"
|
|
29
29
|
rescue LoadError
|
|
30
|
-
say_status :warn, "hibiki_phlex is not in this bundle
|
|
31
|
-
"
|
|
32
|
-
"(and `gem \"phlex-rails\"` to render components from views).", :yellow
|
|
30
|
+
say_status :warn, "hibiki_phlex is not in this bundle. " \
|
|
31
|
+
"Add `gem \"hibiki_phlex\"`.", :yellow
|
|
33
32
|
end
|
|
34
33
|
|
|
35
34
|
def create_channel
|
|
@@ -64,7 +63,7 @@ module Hibiki
|
|
|
64
63
|
def register_hint
|
|
65
64
|
return if hibiki_registered?
|
|
66
65
|
|
|
67
|
-
say_status :hint, "the packaged client is not registered
|
|
66
|
+
say_status :hint, "the packaged client is not registered. Run: " \
|
|
68
67
|
"bin/rails g hibiki:rails:install", :yellow
|
|
69
68
|
end
|
|
70
69
|
end
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Generated by hibiki:rails:phlex
|
|
4
|
-
# (Hibiki::Reactive); the channel owns the transport. One render effect,
|
|
5
|
-
# re-rendering THE SAME INSTANCE, its HTML transmitted over the channel's
|
|
6
|
-
# own subscription and swapped in by the fragment's root DOM id.
|
|
3
|
+
# Generated by hibiki:rails:phlex
|
|
7
4
|
class <%= class_name %>Channel < ApplicationCable::Channel
|
|
8
5
|
include Hibiki::Rails::Channel
|
|
9
6
|
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Generated by hibiki:rails:phlex
|
|
4
|
-
# is a plain Ruby object: Hibiki::Reactive gives it per-instance signals
|
|
5
|
-
# read as ordinary method calls (no .value anywhere), Rerenderable lets
|
|
6
|
-
# the channel's render effect re-render this same instance, and Helpers
|
|
7
|
-
# stamps the client's wire protocol.
|
|
3
|
+
# Generated by hibiki:rails:phlex
|
|
8
4
|
class Components::<%= class_name %> < Phlex::HTML
|
|
9
5
|
include Hibiki::Reactive
|
|
10
6
|
include Hibiki::Phlex::Rerenderable
|
|
@@ -14,11 +10,10 @@ class Components::<%= class_name %> < Phlex::HTML
|
|
|
14
10
|
derived(:doubled) { count * 2 }
|
|
15
11
|
|
|
16
12
|
def view_template
|
|
17
|
-
# The root id is the swap key for transmitted fragments
|
|
13
|
+
# The root id is the swap key for transmitted fragments; keep page-unique.
|
|
18
14
|
div(id: "<%= component_dom_id %>") do
|
|
19
15
|
p { "count: #{count} · doubled: #{doubled}" }
|
|
20
|
-
#
|
|
21
|
-
# delegation keeps the button working across replacements.
|
|
16
|
+
# The island's delegation keeps the button working across replacements.
|
|
22
17
|
button(**on(:increment)) { "+1" }
|
|
23
18
|
end
|
|
24
19
|
end
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
3
|
+
# Generated by hibiki:rails:phlex
|
|
4
|
+
# Render from any page:
|
|
6
5
|
# <%%= render Components::<%= class_name %>Island.new %>
|
|
7
6
|
class Components::<%= class_name %>Island < Phlex::HTML
|
|
8
7
|
include Hibiki::Rails::Helpers
|
|
9
8
|
|
|
10
9
|
def view_template
|
|
11
10
|
div(**hibiki_island(<%= class_name %>Channel, cid: SecureRandom.uuid)) do
|
|
12
|
-
#
|
|
13
|
-
# channel's long-lived instance takes over from its first transmit.
|
|
11
|
+
# Placeholder; the channel takes over from the first transmit.
|
|
14
12
|
render Components::<%= class_name %>.new
|
|
15
13
|
end
|
|
16
14
|
end
|
|
@@ -8,9 +8,9 @@ Description:
|
|
|
8
8
|
Everything is derived from the field list you pass, exactly as Rails'
|
|
9
9
|
scaffold derives its own output. Stock `rails g scaffold` is untouched.
|
|
10
10
|
|
|
11
|
-
The generated index is live: search, filter, sort and pagination are
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
The generated index is live: search, filter, sort, and pagination are all
|
|
12
|
+
reactive, edits happen in place, and changes from anywhere update every open
|
|
13
|
+
list.
|
|
14
14
|
|
|
15
15
|
Example:
|
|
16
16
|
bin/rails generate hibiki:rails:scaffold Book title:string author:references
|
|
@@ -25,8 +25,12 @@ Example:
|
|
|
25
25
|
app/forms/book_form.rb
|
|
26
26
|
app/controllers/books_controller.rb
|
|
27
27
|
app/views/books/*
|
|
28
|
+
app/assets/stylesheets/hibiki_busy.css
|
|
28
29
|
And add to config/routes.rb:
|
|
29
30
|
resources :books
|
|
30
31
|
|
|
31
|
-
Restart the server afterwards
|
|
32
|
-
|
|
32
|
+
Restart the server afterwards if it is running — app/forms is new.
|
|
33
|
+
|
|
34
|
+
Pass --phlex to emit Phlex components under app/views/books/*.rb instead of
|
|
35
|
+
ERB templates. The view layer is the only thing it changes; it needs the
|
|
36
|
+
phlex-rails gem and `bin/rails g phlex:install`.
|
|
@@ -45,6 +45,8 @@ module Hibiki
|
|
|
45
45
|
class_option :skip_search, type: :boolean, default: false,
|
|
46
46
|
desc: "Omit the search box and the LIKE terms behind it"
|
|
47
47
|
class_option :page_size, type: :numeric, default: 20, desc: "Rows per page"
|
|
48
|
+
class_option :phlex, type: :boolean, default: false,
|
|
49
|
+
desc: "Emit Phlex components under app/views instead of ERB templates"
|
|
48
50
|
|
|
49
51
|
def initialize(...)
|
|
50
52
|
super
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
Description:
|
|
2
|
-
Generates a reactive CRUD resource for a model that
|
|
3
|
-
overlay half of hibiki:rails:scaffold.
|
|
2
|
+
Generates a reactive CRUD resource for a model that already exists.
|
|
4
3
|
|
|
5
|
-
With no field list the model's
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
migrated yet.
|
|
4
|
+
With no field list, the model's schema is used to determine columns, types,
|
|
5
|
+
associations, and validators. Pass fields explicitly to override this or
|
|
6
|
+
when the table hasn't been migrated yet.
|
|
9
7
|
|
|
10
|
-
The generated index is live: search, filter, sort and pagination are
|
|
11
|
-
|
|
8
|
+
The generated index is live: search, filter, sort, and pagination are all
|
|
9
|
+
reactive, edits happen in place, and changes from anywhere update every open
|
|
12
10
|
list.
|
|
13
11
|
|
|
14
|
-
Two models are MODIFIED. This one gets a delegate
|
|
15
|
-
after_commit broadcast
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
idempotent, both are announced, and anything already declared is left alone.
|
|
12
|
+
Two models are MODIFIED. This one gets a delegate for each belongs_to and
|
|
13
|
+
an after_commit broadcast. The related models get the missing has_many
|
|
14
|
+
association and a ping so changes are reflected here. Existing declarations
|
|
15
|
+
are left alone.
|
|
19
16
|
|
|
20
17
|
Example:
|
|
21
18
|
bin/rails generate hibiki:rails:scaffold_controller Book
|
|
@@ -28,7 +25,12 @@ Example:
|
|
|
28
25
|
app/forms/book_form.rb
|
|
29
26
|
app/controllers/books_controller.rb
|
|
30
27
|
app/views/books/*
|
|
28
|
+
app/assets/stylesheets/hibiki_busy.css
|
|
31
29
|
And modify:
|
|
32
30
|
app/models/book.rb
|
|
33
31
|
app/models/author.rb # each model a belongs_to points at
|
|
34
32
|
config/routes.rb
|
|
33
|
+
|
|
34
|
+
Pass --phlex to emit Phlex components under app/views/books/*.rb instead of
|
|
35
|
+
ERB templates. The view layer is the only thing it changes; it needs the
|
|
36
|
+
phlex-rails gem and `bin/rails g phlex:install`.
|
|
@@ -8,7 +8,9 @@ require_relative "../scaffold_view_helpers"
|
|
|
8
8
|
require_relative "../scaffold_model_injection"
|
|
9
9
|
require_relative "../scaffold_parent_injection"
|
|
10
10
|
require_relative "../scaffold_parent_notices"
|
|
11
|
+
require_relative "../scaffold_phlex_helpers"
|
|
11
12
|
require_relative "../scaffold_post_install"
|
|
13
|
+
require_relative "../scaffold_transport_stylesheet"
|
|
12
14
|
require_relative "../scaffold_schema"
|
|
13
15
|
require_relative "../css_variant"
|
|
14
16
|
|
|
@@ -29,10 +31,12 @@ module Hibiki
|
|
|
29
31
|
include GeneratorHelpers
|
|
30
32
|
include ScaffoldHelpers
|
|
31
33
|
include ScaffoldViewHelpers
|
|
34
|
+
include ScaffoldPhlexHelpers
|
|
32
35
|
include ScaffoldModelInjection
|
|
33
36
|
include ScaffoldParentInjection
|
|
34
37
|
include ScaffoldParentNotices
|
|
35
38
|
include ScaffoldPostInstall
|
|
39
|
+
include ScaffoldTransportStylesheet
|
|
36
40
|
|
|
37
41
|
TEMPLATE_ROOT = File.expand_path("templates", __dir__)
|
|
38
42
|
|
|
@@ -55,6 +59,8 @@ module Hibiki
|
|
|
55
59
|
desc: "Rows per page"
|
|
56
60
|
class_option :skip_routes, type: :boolean,
|
|
57
61
|
desc: "Don't add routes to config/routes.rb"
|
|
62
|
+
class_option :phlex, type: :boolean, default: false,
|
|
63
|
+
desc: "Emit Phlex components under app/views instead of ERB templates"
|
|
58
64
|
|
|
59
65
|
# Deliberately NO check_class_collision on the controller. Rails' own
|
|
60
66
|
# scaffold_controller assumes a brand-new resource; this one's primary
|
|
@@ -62,6 +68,13 @@ module Hibiki
|
|
|
62
68
|
# controller existing is the normal case rather than an error. Thor's
|
|
63
69
|
# per-file conflict prompt is the right granularity for that.
|
|
64
70
|
|
|
71
|
+
# First, so a --phlex app missing its wiring reads that at the TOP of
|
|
72
|
+
# the output rather than under forty created-file lines. It only warns,
|
|
73
|
+
# so the files are written either way — see ScaffoldPhlexHelpers.
|
|
74
|
+
def check_phlex_wiring
|
|
75
|
+
warn_without_phlex_rails
|
|
76
|
+
end
|
|
77
|
+
|
|
65
78
|
# Resolving the schema first means a missing model or an unmigrated
|
|
66
79
|
# table aborts before anything is written, rather than half-way through.
|
|
67
80
|
def resolve_schema
|
|
@@ -72,6 +85,14 @@ module Hibiki
|
|
|
72
85
|
@new_app_dirs = %w[app/forms app/channels app/models app/views].reject { exists?(it) }
|
|
73
86
|
end
|
|
74
87
|
|
|
88
|
+
# One file per app, not per resource, and the only thing this generator
|
|
89
|
+
# writes outside app/{channels,models,forms,views,controllers}. It
|
|
90
|
+
# carries no model knowledge at all — the rules key on the client's
|
|
91
|
+
# attributes — so a second resource finds it already there.
|
|
92
|
+
def create_stylesheet
|
|
93
|
+
create_transport_stylesheet
|
|
94
|
+
end
|
|
95
|
+
|
|
75
96
|
def create_channels
|
|
76
97
|
template "channel.rb.tt", collection_channel_path
|
|
77
98
|
template "member_channel.rb.tt", member_channel_path
|
|
@@ -91,7 +112,9 @@ module Hibiki
|
|
|
91
112
|
end
|
|
92
113
|
|
|
93
114
|
def create_views
|
|
94
|
-
|
|
115
|
+
return create_phlex_views if phlex?
|
|
116
|
+
|
|
117
|
+
%w[index show new edit _form _list _controls _field_error].each do |view|
|
|
95
118
|
template "views/#{view}.html.erb.tt", view_path("#{view}.html.erb")
|
|
96
119
|
end
|
|
97
120
|
|
|
@@ -149,18 +172,48 @@ module Hibiki
|
|
|
149
172
|
|
|
150
173
|
private
|
|
151
174
|
|
|
175
|
+
# No underscore, no .html.erb, and no _row/_row_form rename: all three
|
|
176
|
+
# are Rails PARTIAL conventions, and a Phlex component is reached by
|
|
177
|
+
# constant. Dropping the rename also drops a step — the file is
|
|
178
|
+
# row.rb because Zeitwerk wants basename.camelize, and calling it
|
|
179
|
+
# book.rb would put Views::Books::Book one reformat away from shadowing
|
|
180
|
+
# the model inside its own namespace.
|
|
181
|
+
#
|
|
182
|
+
# Private, because a public method on a Thor generator is a command.
|
|
183
|
+
def create_phlex_views
|
|
184
|
+
%w[index show new edit form list row row_form controls field_error].each do |view|
|
|
185
|
+
template "views/#{view}.rb.tt", view_path("#{view}.rb")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
template "views/pagination.rb.tt", view_path("pagination.rb") unless infinite?
|
|
189
|
+
end
|
|
190
|
+
|
|
152
191
|
# Ordered so an app's own lib/templates/... override wins first, then
|
|
153
|
-
# the per-variant fork, then the one shared
|
|
154
|
-
#
|
|
155
|
-
# and a shared-local extraction under
|
|
156
|
-
# class-token map can express — so
|
|
157
|
-
#
|
|
192
|
+
# the view layer, then the per-variant fork, then the one shared
|
|
193
|
+
# template set. Only the page control has a per-variant file — a
|
|
194
|
+
# tag-name change under `none` and a shared-local extraction under
|
|
195
|
+
# `tailwind`, neither of which a class-token map can express — so
|
|
196
|
+
# everything else falls through to shared/.
|
|
197
|
+
#
|
|
198
|
+
# The two layers' templates never collide (list.rb.tt vs
|
|
199
|
+
# _list.html.erb.tt), so the phlex roots could have lived beside the
|
|
200
|
+
# ERB ones. They are a subtree instead because `ls` is where this
|
|
201
|
+
# phase's permanent cost should be visible: every view change is now
|
|
202
|
+
# two templates, forever.
|
|
158
203
|
def source_paths
|
|
159
204
|
@source_paths ||= [*self.class.source_paths_for_search,
|
|
205
|
+
*phlex_source_paths,
|
|
160
206
|
File.join(TEMPLATE_ROOT, css_variant.to_s),
|
|
161
207
|
File.join(TEMPLATE_ROOT, "shared")]
|
|
162
208
|
end
|
|
163
209
|
|
|
210
|
+
def phlex_source_paths
|
|
211
|
+
return [] unless phlex?
|
|
212
|
+
|
|
213
|
+
[File.join(TEMPLATE_ROOT, "phlex", css_variant.to_s),
|
|
214
|
+
File.join(TEMPLATE_ROOT, "phlex", "shared")]
|
|
215
|
+
end
|
|
216
|
+
|
|
164
217
|
# Explicit attributes always win, matching Rails' own precedence; the
|
|
165
218
|
# schema decides the column list only when the argument list is empty.
|
|
166
219
|
# That order also happens to be the only workable one, because
|
|
@@ -246,6 +299,11 @@ module Hibiki
|
|
|
246
299
|
|
|
247
300
|
def infinite? = options[:infinite_scroll]
|
|
248
301
|
def searchable? = schema.searchable? && !options[:skip_search]
|
|
302
|
+
|
|
303
|
+
# The view layer. It reaches the templates and nothing else: the graph,
|
|
304
|
+
# the query object, the form, the actions and the whole data-hibiki
|
|
305
|
+
# protocol are the same either way.
|
|
306
|
+
def phlex? = options[:phlex]
|
|
249
307
|
end
|
|
250
308
|
end
|
|
251
309
|
end
|