hibiki_rails 0.13.0 → 0.14.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dfd1da2a9d64baab573c47345a55fbe7f985c3e6781f2e0d04879043b0889dcc
4
- data.tar.gz: f012a899f4c6b675b75102f217dc9e37feaa827e4c63097cc1a86de353dce543
3
+ metadata.gz: 6b163c0d20f8fcca4839e8cfcae98316cae9b8d01bd4f81eece18e02a9ae39be
4
+ data.tar.gz: bf61ca3db0cd1ad769a7f5ee931a06362124841e4d62ba0bd55856d892511b85
5
5
  SHA512:
6
- metadata.gz: 46110572a666ccd8f7c44114433259ebb901d50d7625ce7ff3468de7c0a34bbc6038766cf611eb977b1126d54fab8dbc018a55aa0a3aaab91553cf43fdcca048
7
- data.tar.gz: 8b71c3b742e04e93b9e58707dcf7ebd1dda9319a0894108e07a5a560008f24591a43969e3ce1c4572e4c3ad76d0cf4fc025dd9fe3f518141bdf870e58261ab73
6
+ metadata.gz: 317d9a267de7853075cc35f089099f5f5fc89060afa2db3afd298d8b955f5dd441fdd90d1d2780be561280c6a9e514f827901112ede534a6dd82f06cf763e9a3
7
+ data.tar.gz: 2623fa07782f9d5264592c4bc3e977cb229967b1810f1b05c121bd16dbbc546335c420fd855751ae0dda71e6a148b648623a010344dbec21283f764f76d9f813
data/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@ 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.14.0 — 2026-09-07
8
+
9
+ ### Changed
10
+
11
+ **The infinite-scroll Load-more control has a degraded path.** Under
12
+ `--infinite-scroll` the scaffold emitted one button that answered both a
13
+ click and the `visible` sentinel event, and gave it no `fallback:` on
14
+ purpose: a sentinel that navigates on a dead socket would make scrolling
15
+ navigate. Without JavaScript the button rendered and did nothing. The control
16
+ is now two elements. The wrapper (`books_load_more`, the same id) is the
17
+ sentinel and carries only `visible`. A link inside it is the click path, with
18
+ `fallback: true` and a real href: the query already reads `?page=N` as a
19
+ growing window, so a degraded click shows every window up to N, and the
20
+ link's fragment lands on the last row the reader had already seen. The JS
21
+ client is unchanged.
22
+
23
+ - Both view layers. The Phlex component uses a bare `a`, so no `LinkTo`
24
+ include; `page_url` is now emitted in both pagination modes.
25
+ - Regenerating an app changes only `_list.html.erb` / `list.rb`. A system
26
+ test that clicked `#books_load_more button` now clicks the `a`.
27
+ - Docs: the fallback contract in [The JS client] gains the rule — never give
28
+ a `visible` control a `fallback:`; split it as the scaffold does.
29
+
7
30
  ## 0.13.0 — 2026-09-07
8
31
 
9
32
  ### Changed
@@ -61,10 +61,11 @@ class <%= view_class_name(:list) %> < Views::Base
61
61
  pages = <%= query_class_name %>.page_numbers(page: @page, page_count: @page_count)
62
62
  <%= render_call(:pagination, ["page: @page", "page_count: @page_count", "pages: pages", %(anchor: "##{list_dom_id}"), "id: id", "url: page_url"], indent: 4, shared: true) %>
63
63
  end
64
+ <% end -%>
64
65
 
65
- # Real pagination hrefs carrying the current query state — the channel
66
- # re-renders them with url_params fresh, so the fallback URLs stay
67
- # truthful as the live state moves.
66
+ # Real hrefs for the <%= infinite? ? "Load-more link" : "page links" %>, carrying the current query
67
+ # state — the channel re-renders them with url_params fresh, so the
68
+ # fallback URLs stay truthful as the live state moves.
68
69
  def page_url
69
70
  ->(n) do
70
71
  query = @url_params.except(:page)
@@ -72,19 +73,22 @@ class <%= view_class_name(:list) %> < Views::Base
72
73
  <%= index_helper(type: :path) %>(**query)
73
74
  end
74
75
  end
75
- <% end -%>
76
76
  <% if infinite? -%>
77
77
 
78
- # `visible` is a pseudo-event backed by an IntersectionObserver, so this
79
- # button doubles as the infinite-scroll sentinel. The client stamps
80
- # data-hibiki-busy on it while the next page is in flight.
78
+ # `visible` is a pseudo-event backed by an IntersectionObserver: the wrapper
79
+ # is the scroll sentinel, the link inside it is the click path and its href
80
+ # the fallback. A sentinel must never fall back — scrolling into view on a
81
+ # dead socket would navigate.
81
82
  def load_more
82
- div<%= arg_list(%(id: "#{load_more_dom_id}"), *css_args(:load_more_wrap)) %> do
83
- button<%= arg_list(%(type: "button"), *css_args(:btn_wide), "**on(:go_to_page, event: %i[click visible], with: { page: @page + 1 })", indent: 13) %> do
83
+ div<%= arg_list(%(id: "#{load_more_dom_id}"), *css_args(:load_more_wrap), "**on(:go_to_page, event: :visible, with: { page: @page + 1 })", indent: 8) %> do
84
+ # The STRING "false", not the boolean — Phlex omits false-valued attributes.
85
+ a<%= arg_list("href: load_more_href", *css_args(:btn_wide), "data: { turbo: \"false\" }.merge(on(:go_to_page, with: { page: @page + 1 },\n#{' ' * 42}fallback: true)[:data])", indent: 8) %> do
84
86
  plain "Load more"
85
87
  span<%= arg_list(*css_args(:muted_inline)) %> { " · #{@remaining} remaining" }
86
88
  end
87
89
  end
88
90
  end
91
+
92
+ def load_more_href = "#{page_url.(@page + 1)}#<%= row_dom_id_prefix %>_#{@<%= controller_file_name %>.last.id}"
89
93
  <% end -%>
90
94
  end
@@ -3,16 +3,14 @@
3
3
  <%%# locals: (<%= list_locals_signature %>) -%>
4
4
  <%%# The island's re-rendered fragment. Its root id is the broadcast target —
5
5
  deliberately not the island root, which carries the stream source. %>
6
- <% unless infinite? -%>
7
- <%%# Real pagination hrefs carrying the current query state — the channel
8
- re-renders them with url_params fresh, so the fallback URLs stay
9
- truthful as the live state moves. %>
6
+ <%%# Real hrefs for the <%= infinite? ? "Load-more link" : "page links" %>, carrying the current query
7
+ state — the channel re-renders them with url_params fresh, so the
8
+ fallback URLs stay truthful as the live state moves. %>
10
9
  <%% page_url = ->(n) do
11
10
  query = url_params.except(:page)
12
11
  query[:page] = n unless n == 1
13
12
  <%= index_helper(type: :path) %>(**query)
14
13
  end %>
15
- <% end -%>
16
14
  <div id="<%= list_dom_id %>"<%= css_attr(:list) %>>
17
15
  <% if inline_create? -%>
18
16
  <%%# The inline create form — only the channel ever renders it (the New
@@ -44,20 +42,25 @@
44
42
  <%% end %>
45
43
 
46
44
  <% if infinite? -%>
47
- <%%# `visible` is a pseudo-event backed by an IntersectionObserver, so this
48
- button doubles as the infinite-scroll sentinel. The client stamps
49
- data-hibiki-busy on it while the next page is in flight. %>
45
+ <%%# `visible` is a pseudo-event backed by an IntersectionObserver: the wrapper
46
+ is the scroll sentinel, the link inside it is the click path and its href
47
+ the fallback. A sentinel must never fall back — scrolling into view on a
48
+ dead socket would navigate. %>
50
49
  <%% if remaining.positive? %>
51
- <div id="<%= load_more_dom_id %>"<%= css_attr(:load_more_wrap) %>>
52
- <%%= tag.button(type: "button",
50
+ <%%= tag.div(id: "<%= load_more_dom_id %>",
53
51
  <% if css? -%>
54
- class: "<%= css(:btn_wide) %>",
52
+ class: "<%= css(:load_more_wrap) %>",
55
53
  <% end -%>
56
- **on(:go_to_page, event: %i[click visible],
57
- with: { page: page + 1 })) do %>
54
+ **on(:go_to_page, event: :visible, with: { page: page + 1 })) do %>
55
+ <%%= link_to "#{page_url.(page + 1)}#<%= row_dom_id_prefix %>_#{<%= controller_file_name %>.last.id}",
56
+ <% if css? -%>
57
+ class: "<%= css(:btn_wide) %>",
58
+ <% end -%>
59
+ data: { turbo: false }.merge(on(:go_to_page, with: { page: page + 1 },
60
+ fallback: true)[:data]) do %>
58
61
  Load more<span<%= css_attr(:muted_inline) %>>&nbsp;·&nbsp;<%%= remaining %> remaining</span>
59
62
  <%% end %>
60
- </div>
63
+ <%% end %>
61
64
  <%% end %>
62
65
  <% else -%>
63
66
  <%%= render "shared/pagination", page: page, page_count: page_count, pages: pages,
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Hibiki
4
4
  module Rails
5
- VERSION = "0.13.0"
5
+ VERSION = "0.14.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hibiki_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - planetaska