hibiki_rails 0.5.0 → 0.6.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 +117 -0
- data/lib/generators/hibiki/rails/css_variant.rb +11 -2
- 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 +4 -6
- data/lib/generators/hibiki/rails/scaffold_controller/USAGE +9 -14
- data/lib/generators/hibiki/rails/scaffold_controller/scaffold_controller_generator.rb +18 -20
- data/lib/generators/hibiki/rails/scaffold_controller/templates/daisyui/views/_pagination.html.erb.tt +21 -43
- data/lib/generators/hibiki/rails/scaffold_controller/templates/none/views/_pagination.html.erb.tt +21 -30
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/daisyui/views/pagination.rb.tt +21 -51
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/none/views/pagination.rb.tt +22 -49
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/controls.rb.tt +9 -42
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/field_error.rb.tt +8 -8
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/form.rb.tt +4 -24
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/form_errors.rb.tt +29 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/index.rb.tt +7 -21
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/list.rb.tt +24 -35
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/row.rb.tt +11 -35
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/row_form.rb.tt +14 -35
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/shared/views/show.rb.tt +3 -8
- data/lib/generators/hibiki/rails/scaffold_controller/templates/phlex/tailwind/views/pagination.rb.tt +21 -55
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/channel.rb.tt +35 -130
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/controller.rb.tt +2 -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 +20 -63
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/member_channel.rb.tt +15 -23
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/query.rb.tt +22 -80
- 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/_field_error.html.erb.tt +6 -1
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_form.html.erb.tt +2 -16
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_form_errors.html.erb.tt +24 -0
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_list.html.erb.tt +16 -34
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_row.html.erb.tt +11 -33
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/_row_form.html.erb.tt +12 -35
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/views/index.html.erb.tt +7 -21
- 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 +21 -31
- data/lib/generators/hibiki/rails/scaffold_helpers.rb +13 -6
- data/lib/generators/hibiki/rails/scaffold_model_injection.rb +12 -50
- 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 +10 -10
- data/lib/generators/hibiki/rails/scaffold_post_install.rb +15 -16
- data/lib/generators/hibiki/rails/scaffold_schema.rb +4 -11
- data/lib/generators/hibiki/rails/scaffold_shared_views.rb +76 -0
- data/lib/generators/hibiki/rails/scaffold_transport_stylesheet.rb +23 -11
- 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/engine.rb +8 -0
- data/lib/hibiki/rails/swallowed_write_warning.rb +43 -0
- data/lib/hibiki/rails/version.rb +1 -1
- data/lib/hibiki/rails.rb +30 -0
- metadata +7 -4
- data/lib/generators/hibiki/rails/scaffold_controller/templates/shared/row.rb.tt +0 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d708875aa67bf5d9b9137ac8d9bb05cf23604db936011706d068ec0b7116429c
|
|
4
|
+
data.tar.gz: 941b16f996127706d699e8812cf712b755c0f833f7a04c9e71912844421645ba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 74175d980c70f1b4ea1bb9ba593ef9940326eccfce6c783ceefdebfb2a0659706faa1040393759549366c5880ea23459740e0f3f280e64fb47a4bc32a371f69c
|
|
7
|
+
data.tar.gz: c88a903f15b13516310beb32a7ed741942c49ae65823a6422fb36823400a9fea420ae08bd2fe047bc1d2e6ff3e38cef8f86c0665eeee6014e0f495c880ad90a4
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,123 @@ 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.6.0 — 2026-08-10
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
**`Hibiki::Rails.record_equals` — a per-signal comparator for ActiveRecord
|
|
12
|
+
records.** `ActiveRecord#==` compares class and id only, so a stale record is
|
|
13
|
+
`==` to its edited reload and a signal write carrying the fresh one is silently
|
|
14
|
+
dropped. Pass the comparator per signal and the write goes through:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
state(:items, equals: Hibiki::Rails.record_equals) { fetch }
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
It compares class + `attributes`, recurses through arrays, and falls back to
|
|
21
|
+
`==` for everything else. It needs hibiki 0.3.0's `equals:` (see the dependency
|
|
22
|
+
change below), which is honored at both of the graph's equality gates — the
|
|
23
|
+
write gate and the flush-time check — so a change that passes the comparator
|
|
24
|
+
actually reaches the page. This is opt-in sugar that makes the snapshot pattern
|
|
25
|
+
forgiving; "records stop at the boundary" stays the documented default, and a
|
|
26
|
+
comparator still cannot see an in-place mutation that never enters the write
|
|
27
|
+
path.
|
|
28
|
+
|
|
29
|
+
**A development-mode warning for the write that pattern swallows.** When a
|
|
30
|
+
`State` write is dropped by the default `==` but the old and new values'
|
|
31
|
+
`attributes` differ — the classic silent-stale-UI debugging session — the log
|
|
32
|
+
now says so and points at the docs. Development only (never test or
|
|
33
|
+
production), zero semantic change: the write is still dropped.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
**The scaffold's row projection is gone — records now cross the boundary as
|
|
38
|
+
frozen snapshots.** `app/models/book_row.rb` is no longer generated. The query
|
|
39
|
+
object's `rows` returns real records, hardened at the boundary —
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
def rows
|
|
43
|
+
@rows ||= window_scope.strict_loading.map { it.readonly!; it.freeze }
|
|
44
|
+
end
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
— and the channels' `rows`/`row` deriveds compare them with
|
|
48
|
+
`equals: Hibiki::Rails.record_equals`. What the `Data` projection's structural
|
|
49
|
+
`==` used to provide, the comparator provides; what it could never provide, the
|
|
50
|
+
freeze triple does: an attribute write raises `FrozenError`, `save` raises
|
|
51
|
+
`ActiveRecord::ReadOnlyRecord`, and an unpreloaded association walk raises
|
|
52
|
+
`ActiveRecord::StrictLoadingViolationError` instead of firing a lazy query off
|
|
53
|
+
the graph thread. Views print a `belongs_to` label as `book.author&.name`
|
|
54
|
+
directly, so **the scaffold no longer injects a `delegate` per `belongs_to`
|
|
55
|
+
into the model** — the model injection is the `after_commit` ping alone. The
|
|
56
|
+
member channel's fetch preloads its associations (`includes(...) +
|
|
57
|
+
strict_loading`) for the same reason.
|
|
58
|
+
|
|
59
|
+
Existing scaffolded apps keep working untouched: their `book_row.rb` and
|
|
60
|
+
delegates are app code, and the runtime reads none of it. Re-running a scaffold
|
|
61
|
+
with `--force` moves the resource over; the generator never deletes, so the
|
|
62
|
+
orphaned `*_row.rb` stays on disk for you to remove.
|
|
63
|
+
|
|
64
|
+
**The hibiki dependency floor is `~> 0.3`** (per-signal `equals:`, shipped in
|
|
65
|
+
hibiki 0.3.0). Everything 0.2 provided still holds; generated channels now rely
|
|
66
|
+
on the comparator being consulted at both equality gates.
|
|
67
|
+
|
|
68
|
+
**The page control and the field-error line are shared partials now.** Each
|
|
69
|
+
scaffold used to write its own copy per resource; both are presentation-only,
|
|
70
|
+
so they are emitted once per app instead — `app/views/shared/_pagination.html.erb`
|
|
71
|
+
and `_field_error.html.erb` (under `--phlex`: `Views::Shared::Pagination` and
|
|
72
|
+
`Views::Shared::FieldError` in `app/views/shared/`). Everything resource-shaped
|
|
73
|
+
reaches the page control as locals from the list partial. A second scaffold
|
|
74
|
+
finds the files present and leaves them; if they were generated under a
|
|
75
|
+
different `--css` style, a notice says so instead of silently restyling every
|
|
76
|
+
other resource's control.
|
|
77
|
+
|
|
78
|
+
**Pagination renders above the list as well as below**, so a long page starts
|
|
79
|
+
with a control in reach. Both copies live inside the re-rendered fragment and
|
|
80
|
+
carry distinct ids (`books_pagination_top` / `books_pagination`), so idiomorph
|
|
81
|
+
updates them in place.
|
|
82
|
+
|
|
83
|
+
Re-running a scaffold with `--force` moves the list over to the shared
|
|
84
|
+
partials and prints a notice naming the now-dead per-resource copies — the
|
|
85
|
+
generator never deletes them itself.
|
|
86
|
+
|
|
87
|
+
**The form's error summary is a shared partial too** —
|
|
88
|
+
`app/views/shared/_form_errors.html.erb` (under `--phlex`:
|
|
89
|
+
`Views::Shared::FormErrors`), rendered by every scaffolded full-page form. The
|
|
90
|
+
resource name comes off the record at render time, so one file serves every
|
|
91
|
+
scaffold. Under `--css=tailwind` and `--css=daisyui` the block is styled as a
|
|
92
|
+
red alert panel instead of the scaffold-stock `style="color: red"`, which
|
|
93
|
+
`--css=none` keeps.
|
|
94
|
+
|
|
95
|
+
### Fixed
|
|
96
|
+
|
|
97
|
+
**The `hibiki_busy.css` import lands directly below `@import "tailwindcss";`**
|
|
98
|
+
instead of at the end of the entry stylesheet — appending placed it after
|
|
99
|
+
`@plugin` lines, and CSS requires every `@import` before any other statement.
|
|
100
|
+
Also fixed: re-running a scaffold in a tailwindcss-rails app
|
|
101
|
+
(`app/assets/tailwind/application.css`) duplicated the import on every run,
|
|
102
|
+
because the idempotence probe checked the wrong path spelling.
|
|
103
|
+
|
|
104
|
+
## 0.5.1 — 2026-08-08
|
|
105
|
+
|
|
106
|
+
### Changed
|
|
107
|
+
|
|
108
|
+
**Generator output and messages slimmed down.** The scaffold templates used to
|
|
109
|
+
carry long design-rationale comments into every generated file; they are now
|
|
110
|
+
one-to-three-line hints, with the docs holding the prose. The safety notes a
|
|
111
|
+
user editing the file actually needs stayed: public channel methods are
|
|
112
|
+
client-invocable actions, ActionCable's exact-arity rule, the untrusted
|
|
113
|
+
subscribe param, Phlex omitting `false`-valued attributes, and the busy
|
|
114
|
+
stylesheet's do-not-wrap-in-a-layer rule.
|
|
115
|
+
|
|
116
|
+
The generators' status notices were shortened the same way — they still say
|
|
117
|
+
what to do, just not why at essay length.
|
|
118
|
+
|
|
119
|
+
No behavior change anywhere: no code inside any template moved, and neither
|
|
120
|
+
the runtime nor the packaged client changed (the npm 0.5.1 exists only to keep
|
|
121
|
+
the lockstep rule). Re-running a scaffold with `--force` rewrites the views
|
|
122
|
+
with the shorter comments; that diff is the whole upgrade.
|
|
123
|
+
|
|
7
124
|
## 0.5.0 — 2026-08-05
|
|
8
125
|
|
|
9
126
|
### Added
|
|
@@ -87,10 +87,19 @@ module Hibiki
|
|
|
87
87
|
spinner: "loading loading-spinner loading-xs",
|
|
88
88
|
warning_text: "text-warning",
|
|
89
89
|
error_text: "text-error text-sm mt-1",
|
|
90
|
+
# The full-form error summary. Plain Tailwind on purpose, so both
|
|
91
|
+
# styled variants share it via the merge — an addition (2026-08-09),
|
|
92
|
+
# not a transcription: the reference apps carried the scaffold-stock
|
|
93
|
+
# `style="color: red"` block, which --css=none still emits.
|
|
94
|
+
error_summary: "rounded-md border border-red-200 bg-red-50 p-4 mb-6",
|
|
95
|
+
error_summary_title: "text-sm font-semibold text-red-800",
|
|
96
|
+
error_summary_list: "mt-2 list-disc list-inside text-sm text-red-700",
|
|
90
97
|
muted: "opacity-60 italic",
|
|
91
98
|
muted_inline: "opacity-60",
|
|
92
99
|
counts: "ml-auto text-sm opacity-70",
|
|
93
|
-
|
|
100
|
+
# my-, not mt-: the control renders above the list as well as below,
|
|
101
|
+
# and the top copy needs the gap on its other side.
|
|
102
|
+
pagination_nav: "join my-2 mx-auto flex justify-center w-fit"
|
|
94
103
|
}.freeze
|
|
95
104
|
|
|
96
105
|
# DaisyUI is a plugin over Tailwind, and the merge says so literally:
|
|
@@ -135,7 +144,7 @@ module Hibiki
|
|
|
135
144
|
muted: "text-gray-500 italic",
|
|
136
145
|
muted_inline: "text-gray-400",
|
|
137
146
|
counts: "ml-auto text-sm text-gray-500",
|
|
138
|
-
pagination_nav: "
|
|
147
|
+
pagination_nav: "my-2 mx-auto flex justify-center w-fit -space-x-px rounded-md shadow-sm"
|
|
139
148
|
).freeze
|
|
140
149
|
|
|
141
150
|
# Every lookup misses, so every class argument is omitted entirely.
|
|
@@ -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
|
|
@@ -19,7 +19,6 @@ Example:
|
|
|
19
19
|
db/migrate/XXXXXXXXXXXX_create_books.rb
|
|
20
20
|
app/models/book.rb
|
|
21
21
|
app/models/book_query.rb
|
|
22
|
-
app/models/book_row.rb
|
|
23
22
|
app/channels/books_channel.rb
|
|
24
23
|
app/channels/book_channel.rb
|
|
25
24
|
app/forms/book_form.rb
|
|
@@ -29,8 +28,7 @@ Example:
|
|
|
29
28
|
And add to config/routes.rb:
|
|
30
29
|
resources :books
|
|
31
30
|
|
|
32
|
-
Restart the server afterwards
|
|
33
|
-
app/* glob at boot, and app/forms is new.
|
|
31
|
+
Restart the server afterwards if it is running — app/forms is new.
|
|
34
32
|
|
|
35
33
|
Pass --phlex to emit Phlex components under app/views/books/*.rb instead of
|
|
36
34
|
ERB templates. The view layer is the only thing it changes; it needs the
|
|
@@ -1,28 +1,23 @@
|
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
because a row prints the parent's label rather than its id. Both are
|
|
18
|
-
idempotent, both are announced, and anything already declared is left alone.
|
|
12
|
+
Two models are MODIFIED. This one gets an after_commit broadcast. The
|
|
13
|
+
related models get the missing has_many association and a ping so changes
|
|
14
|
+
are reflected here. Existing declarations are left alone.
|
|
19
15
|
|
|
20
16
|
Example:
|
|
21
17
|
bin/rails generate hibiki:rails:scaffold_controller Book
|
|
22
18
|
|
|
23
19
|
This will create:
|
|
24
20
|
app/models/book_query.rb
|
|
25
|
-
app/models/book_row.rb
|
|
26
21
|
app/channels/books_channel.rb
|
|
27
22
|
app/channels/book_channel.rb
|
|
28
23
|
app/forms/book_form.rb
|
|
@@ -10,6 +10,7 @@ require_relative "../scaffold_parent_injection"
|
|
|
10
10
|
require_relative "../scaffold_parent_notices"
|
|
11
11
|
require_relative "../scaffold_phlex_helpers"
|
|
12
12
|
require_relative "../scaffold_post_install"
|
|
13
|
+
require_relative "../scaffold_shared_views"
|
|
13
14
|
require_relative "../scaffold_transport_stylesheet"
|
|
14
15
|
require_relative "../scaffold_schema"
|
|
15
16
|
require_relative "../css_variant"
|
|
@@ -36,6 +37,7 @@ module Hibiki
|
|
|
36
37
|
include ScaffoldParentInjection
|
|
37
38
|
include ScaffoldParentNotices
|
|
38
39
|
include ScaffoldPostInstall
|
|
40
|
+
include ScaffoldSharedViews
|
|
39
41
|
include ScaffoldTransportStylesheet
|
|
40
42
|
|
|
41
43
|
TEMPLATE_ROOT = File.expand_path("templates", __dir__)
|
|
@@ -100,7 +102,6 @@ module Hibiki
|
|
|
100
102
|
|
|
101
103
|
def create_models
|
|
102
104
|
template "query.rb.tt", query_path
|
|
103
|
-
template "row.rb.tt", row_path
|
|
104
105
|
end
|
|
105
106
|
|
|
106
107
|
def create_form
|
|
@@ -112,27 +113,26 @@ module Hibiki
|
|
|
112
113
|
end
|
|
113
114
|
|
|
114
115
|
def create_views
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
if phlex?
|
|
117
|
+
create_phlex_views
|
|
118
|
+
else
|
|
119
|
+
%w[index show new edit _form _list _controls].each do |view|
|
|
120
|
+
template "views/#{view}.html.erb.tt", view_path("#{view}.html.erb")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
template "views/_row.html.erb.tt", view_path("_#{row_partial}.html.erb")
|
|
124
|
+
template "views/_row_form.html.erb.tt", view_path("_#{row_form_partial}.html.erb")
|
|
119
125
|
end
|
|
120
126
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
# Under --infinite-scroll the sentinel is inlined in _list and there
|
|
125
|
-
# is no page control at all — not an empty one.
|
|
126
|
-
template "views/_pagination.html.erb.tt", view_path("_pagination.html.erb") unless infinite?
|
|
127
|
+
# The page control and the field-error line, once per app — see
|
|
128
|
+
# ScaffoldSharedViews.
|
|
129
|
+
create_shared_views
|
|
127
130
|
end
|
|
128
131
|
|
|
129
132
|
# The model being scaffolded — one of two files this generator modifies
|
|
130
|
-
# that the app already owned.
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
# prints an association's LABEL, and the show page hands that partial a
|
|
134
|
-
# live record — so without the delegate the show page raises on arrival.
|
|
135
|
-
# Once we are editing the model anyway, the ping belongs here too.
|
|
133
|
+
# that the app already owned. It gets the after_commit ping, which is
|
|
134
|
+
# what makes another tab, another user, the plain controller and a
|
|
135
|
+
# console write all reach an open list.
|
|
136
136
|
#
|
|
137
137
|
# The alternative — printing it and hoping — fails SILENTLY: everything
|
|
138
138
|
# works in one tab, and cross-tab plus out-of-band writes simply never
|
|
@@ -181,11 +181,9 @@ module Hibiki
|
|
|
181
181
|
#
|
|
182
182
|
# Private, because a public method on a Thor generator is a command.
|
|
183
183
|
def create_phlex_views
|
|
184
|
-
%w[index show new edit form list row row_form controls
|
|
184
|
+
%w[index show new edit form list row row_form controls].each do |view|
|
|
185
185
|
template "views/#{view}.rb.tt", view_path("#{view}.rb")
|
|
186
186
|
end
|
|
187
|
-
|
|
188
|
-
template "views/pagination.rb.tt", view_path("pagination.rb") unless infinite?
|
|
189
187
|
end
|
|
190
188
|
|
|
191
189
|
# Ordered so an app's own lib/templates/... override wins first, then
|
data/lib/generators/hibiki/rails/scaffold_controller/templates/daisyui/views/_pagination.html.erb.tt
CHANGED
|
@@ -1,62 +1,40 @@
|
|
|
1
1
|
<%#- The ONE component with a per-variant template. DaisyUI expresses a page
|
|
2
2
|
item as two tokens (`join-item btn`), so each of the six variants below can
|
|
3
|
-
repeat them inline — which is exactly what plain Tailwind cannot do.
|
|
4
|
-
<%%# locals: (page: 1, page_count: 1) -%>
|
|
5
|
-
<%%# The numbered page control. It lives INSIDE the replaced fragment, like
|
|
6
|
-
every other control in there: page_count is a server number, so a
|
|
7
|
-
single-page list simply stops rendering this element. Placed outside the
|
|
8
|
-
fragment it would never be re-rendered and could only ever change its TEXT
|
|
9
|
-
(reactive values are textContent-only), leaving a control whose links point
|
|
10
|
-
at pages that no longer exist.
|
|
3
|
+
repeat them inline — which is exactly what plain Tailwind cannot do.
|
|
11
4
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
has to MERGE rather than splat: a bare ** after `data: { turbo: false }`
|
|
24
|
-
would silently replace it and the anchor would stop scrolling. %>
|
|
25
|
-
<%%# PENDING. Nothing here asks for it: the client stamps data-hibiki-busy on
|
|
26
|
-
the control that fired, so the clicked page link dims itself while the trip
|
|
27
|
-
is out, and the swap that answers it destroys the attribute along with the
|
|
28
|
-
link. The bar above the list is the other half — the anchor jump has
|
|
29
|
-
already put it in view by then. %>
|
|
5
|
+
Emitted ONCE per app into app/views/shared/, so nothing here may name the
|
|
6
|
+
resource: everything resource-shaped (the wrapper id, the anchor target,
|
|
7
|
+
the page-number window) arrives as a local from the list partial. The
|
|
8
|
+
`--css=` marker below is how a later scaffold run detects a variant
|
|
9
|
+
mismatch — keep it. -%>
|
|
10
|
+
<%%# locals: (page: 1, page_count: 1, pages: [], anchor:, id:) -%>
|
|
11
|
+
<%%# Shared page control (--css=<%= css_variant %>), rendered above and below
|
|
12
|
+
every scaffolded list. The links are anchors to the list, so a click also
|
|
13
|
+
does the browser's own fragment jump; data-turbo="false" keeps Turbo Drive
|
|
14
|
+
out. `on` returns a { data: } hash — merge, don't splat, or the anchor
|
|
15
|
+
stops scrolling. %>
|
|
30
16
|
<%% page_data = ->(n) { { turbo: false }.merge(on(:go_to_page, with: { page: n })[:data]) } %>
|
|
31
|
-
<%%# The
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
dead vertical space on a list that has none. %>
|
|
35
|
-
<div id="<%= pagination_dom_id %>">
|
|
17
|
+
<%%# The wrapper always renders (so idiomorph has a node to pair with) and
|
|
18
|
+
stays bare — layout classes here would leave dead space when empty. %>
|
|
19
|
+
<div id="<%%= id %>">
|
|
36
20
|
<%% if page_count > 1 %>
|
|
37
21
|
<nav<%= css_attr(:pagination_nav) %> aria-label="Pagination">
|
|
38
|
-
<%%# Prev/next
|
|
39
|
-
|
|
40
|
-
one that reflows under the pointer is how you mis-click a list that
|
|
41
|
-
just repainted. A span, not a disabled link: there is no page to
|
|
42
|
-
name, so there is no action to stamp. %>
|
|
22
|
+
<%%# Prev/next go inert at the ends rather than disappearing, so the
|
|
23
|
+
control keeps its width across a page change. %>
|
|
43
24
|
<%% if page == 1 %>
|
|
44
25
|
<span class="join-item btn btn-disabled" aria-disabled="true">«</span>
|
|
45
26
|
<%% else %>
|
|
46
|
-
<%%= link_to "«",
|
|
27
|
+
<%%= link_to "«", anchor, class: "join-item btn", rel: "prev",
|
|
47
28
|
"aria-label": "Previous page", data: page_data.(page - 1) %>
|
|
48
29
|
<%% end %>
|
|
49
30
|
|
|
50
|
-
<%%
|
|
31
|
+
<%% pages.each do |n| %>
|
|
51
32
|
<%% if n.nil? %>
|
|
52
33
|
<span class="join-item btn btn-disabled" aria-hidden="true">…</span>
|
|
53
34
|
<%% elsif n == page %>
|
|
54
|
-
<%%# aria-current is the only thing that tells a screen reader which
|
|
55
|
-
page it is on: the visual cue is a class, and the link text is
|
|
56
|
-
just a number either way. %>
|
|
57
35
|
<span class="join-item btn btn-active" aria-current="page"><%%= n %></span>
|
|
58
36
|
<%% else %>
|
|
59
|
-
<%%= link_to n,
|
|
37
|
+
<%%= link_to n, anchor, class: "join-item btn",
|
|
60
38
|
"aria-label": "Page #{n}", data: page_data.(n) %>
|
|
61
39
|
<%% end %>
|
|
62
40
|
<%% end %>
|
|
@@ -64,7 +42,7 @@
|
|
|
64
42
|
<%% if page == page_count %>
|
|
65
43
|
<span class="join-item btn btn-disabled" aria-disabled="true">»</span>
|
|
66
44
|
<%% else %>
|
|
67
|
-
<%%= link_to "»",
|
|
45
|
+
<%%= link_to "»", anchor, class: "join-item btn", rel: "next",
|
|
68
46
|
"aria-label": "Next page", data: page_data.(page + 1) %>
|
|
69
47
|
<%% end %>
|
|
70
48
|
</nav>
|