bh 6.3.0 → 6.5.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: 8e8820e04b1b71fbfacf44696b6fec712f77987c217dbedf5c2314867600bf42
4
- data.tar.gz: 761251633d36f3e62dd92d9784c71d44a2306bc6c3358d24b3420f3347cebff6
3
+ metadata.gz: bcf981e1ac2ccf3453876292551f8c226f128d580df804e5fe639bfa93cd891e
4
+ data.tar.gz: 493f3eed784399be33e0d1a96ac3152dbb44ab6da23539e4f596ec6e26419bd0
5
5
  SHA512:
6
- metadata.gz: 84be37f63354fe89c52d6532cb55a5c2db53127a568c228d58acc53d7d3a0ff3cbb98cad224b7bdd76138a3a02ffcf2b3a338cacda2ec0f12427e26fd10db361
7
- data.tar.gz: bd2d39d1d0377f5e5730f90ad6400edf736b1cc281b63e2a491c8c6cf7bf922e3822a6f1525e5f7ae775f3ff48de3f089f2c526d9a09b992f6c53a875b9d9eb7
6
+ metadata.gz: dee9ed53e3555c3643795bbbd2d6edf0079fb018fb72eb070bfa94f6eefe1bccb177db45dcb94bbbfa3a9702b04217222203a5757aa7877b443a4efc229f84c5
7
+ data.tar.gz: 4d12abc38791cff92dc944a0626333fa269bfc6e2b012b7787329ee31445b445c7fb7b6a79f658e119cc28037ea82de71914155cbba0f6c085a35edfd0ebf9da
data/CHANGELOG.md CHANGED
@@ -8,6 +8,33 @@ For more information about changelogs, check
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## 6.5.0 - 2026-09-18
12
+
13
+ * [FIX] The shell fills the screen in whole pixels
14
+
15
+ `100dvh` computes to a fraction on a scaled display -- 415.455px against a viewport
16
+ reporting 415 -- and the tenths over are enough for a browser to draw a scrollbar the
17
+ page cannot use, beside the one the content it holds already has. The shell takes a
18
+ percentage of a document given a height instead, which is the same whole number the
19
+ viewport reports.
20
+
21
+ ## 6.4.0 - 2026-09-18
22
+
23
+ * [FEATURE] A message carries its `photos`, drawn in the bubble under its words
24
+
25
+ `Bh::Message` takes a list of addresses beside its text. Each is drawn under the words in
26
+ the same bubble, linking to itself in a tab of its own: what fits beside words is rarely
27
+ what somebody wants to look at. A message carrying pictures and no words is a message all
28
+ the same, rather than one still being typed.
29
+
30
+ * [FEATURE] `chat_with` takes a `note:`, said over the thread rather than in it
31
+
32
+ What the conversation is about, or the other way to reach whoever is on the far end of it.
33
+ Over rather than in, because nobody in the conversation said it and a bubble would claim
34
+ somebody had. It holds the top while the thread scrolls under it, and carries the page's
35
+ own background for that reason: a tint would read as somebody's turn, and without one the
36
+ bubbles would show through.
37
+
11
38
  ## 6.3.0 - 2026-09-18
12
39
 
13
40
  * [FEATURE] The nine palettes, served at `/bh/theme/<name>.css`
@@ -7,6 +7,33 @@
7
7
  overflows pushes its first message clean out of the top with no way to scroll back. */
8
8
  .chat > :first-child { margin-block-start: auto }
9
9
 
10
+ /* What the whole thread is about, or the other way to reach whoever is on the far end of
11
+ it: said over the conversation rather than in it, quiet and in the page's own words,
12
+ because nobody in the conversation said it. It holds the top while the thread scrolls
13
+ under it, which is why it carries the page's background and not a band of its own: a
14
+ tint here would read as somebody's turn, and bubbles would show through without one. */
15
+ .chat-note {
16
+ position: sticky;
17
+ inset-block-start: 0;
18
+ z-index: 1;
19
+ background-color: var(--bs-body-bg);
20
+ font-size: var(--bs-font-size-sm);
21
+ color: var(--bs-fg-2);
22
+ margin: 0;
23
+ padding-block: 0.25rem 0.75rem;
24
+ }
25
+
26
+ /* A picture a message carries, under its words and no wider than the bubble holding it. */
27
+ .chat-photo {
28
+ display: block;
29
+ max-width: 100%;
30
+ height: auto;
31
+ border-radius: 0.5rem;
32
+ margin-block-start: 0.5rem;
33
+ }
34
+ /* The first thing in a bubble has the bubble's own padding over it already. */
35
+ .chat-bubble > a:first-child .chat-photo { margin-block-start: 0 }
36
+
10
37
  /* Who said it and when, small, over the bubble it belongs to and on its side. */
11
38
  .chat-aside { font-size: var(--bs-font-size-sm); color: var(--bs-fg-2); margin: 0 0.75rem }
12
39
  .chat-aside-out { align-self: flex-end; margin-inline-end: 1.25rem }
@@ -21,6 +21,9 @@ span.breadcrumb-link.active {
21
21
  page whose table had no rows to be taller than the menu. `clip` leaves the
22
22
  other axis alone. */
23
23
  html { overflow-x: hidden; }
24
+ /* And a height for the shell below to take a percentage of, so the one screen it
25
+ fills is measured in whole pixels rather than in a unit that carries a fraction. */
26
+ html, body { height: 100%; }
24
27
  .recourse-shell { overflow-x: clip; }
25
28
 
26
29
  /* A finger does not hover. The tint a pointer leaves on the row under it, or on
@@ -65,7 +68,10 @@ html { overflow-x: hidden; }
65
68
  grid-template-columns: auto minmax(0, 1fr);
66
69
  grid-template-rows: auto minmax(0, 1fr);
67
70
  grid-template-areas: 'nav nav' 'aside main';
68
- height: 100dvh;
71
+ /* A percentage of the block a viewport unit would have measured: `100dvh` carries
72
+ the fraction a scaled display computes it to -- 415.455px against a viewport
73
+ reporting 415 -- and the tenths over are a scrollbar the page cannot use. */
74
+ height: 100%;
69
75
  }
70
76
  .recourse-shell > nav { grid-area: nav; }
71
77
  .recourse-sidebar { grid-area: aside; }
@@ -8,6 +8,7 @@ en:
8
8
  more: '%{first} + %{count} more'
9
9
  no_results: Nothing matches that
10
10
  okay: Okay
11
+ photo: Photo
11
12
  search: Search
12
13
  send: Send
13
14
  typing: Typing
@@ -8,12 +8,16 @@ module Bh
8
8
  include Asides, Asks
9
9
 
10
10
  # `messages` as a thread that opens at its newest and, where `url` is given, a field
11
- # under it that posts the next message there, suggesting `hints` one at a time.
12
- def chat_with(messages:, url: nil, hints: [])
13
- parts = [bh_chat_messages(messages)]
11
+ # under it that posts the next message there, suggesting `hints` one at a time. A
12
+ # `note` stands over the whole thread: what this conversation is about, or the other
13
+ # way to reach whoever is on the far end of it.
14
+ def chat_with(messages:, url: nil, hints: [], note: nil)
15
+ parts = [bh_chat_note(note), bh_chat_messages(messages)]
14
16
  parts.push bh_chat_ask(url, hints) if url
15
17
 
16
- tag.div safe_join(parts, "\n"), class: 'chat-thread', data: { controller: 'thread' }
18
+ said = safe_join parts.compact, "\n"
19
+
20
+ tag.div said, class: 'chat-thread', data: { controller: 'thread' }
17
21
  end
18
22
 
19
23
  private
@@ -30,13 +34,36 @@ module Bh
30
34
  safe_join parts.compact, "\n"
31
35
  end
32
36
 
37
+ # Said over the thread rather than in it, so it is not one of the turns: nobody
38
+ # said this, and a bubble would claim somebody had.
39
+ def bh_chat_note(note)
40
+ tag.p note, class: 'chat-note' if note.present?
41
+ end
42
+
33
43
  def bh_chat_bubble(message)
34
44
  side = Asides::SIDES.fetch message.side
35
- return tag.p message.text, class: "chat-bubble chat-#{side}" if message.text
45
+ said = bh_chat_said message
46
+ return tag.p said, class: "chat-bubble chat-#{side}" if said
36
47
 
37
48
  tag.p safe_join(Array.new(3) { tag.span }), class: "chat-bubble chat-#{side} chat-typing",
38
49
  aria: { label: t('bh.typing') }
39
50
  end
51
+
52
+ # The words and then the pictures, which is the order a phone lays a picture
53
+ # message out in -- and nothing at all where a message has neither yet.
54
+ def bh_chat_said(message)
55
+ pictures = Array(message.photos).map { |photo| bh_chat_photo photo }
56
+
57
+ safe_join([message.text, *pictures].compact_blank, "\n").presence
58
+ end
59
+
60
+ # A picture in the bubble, opening full size in a tab of its own: what fits beside
61
+ # words is rarely what somebody wants to look at.
62
+ def bh_chat_photo(url)
63
+ tag.a href: url, target: :_blank, rel: 'noopener' do
64
+ tag.img src: url, alt: t('bh.photo'), class: 'chat-photo', loading: :lazy
65
+ end
66
+ end
40
67
  end
41
68
  end
42
69
  end
data/lib/bh/message.rb CHANGED
@@ -1,11 +1,14 @@
1
1
  module Bh
2
2
  # One message of a chat, as `chat_with` draws it: which `side` its bubble is on (`:left`
3
- # for the other party, `:right` for the reader), its `text`, who the `sender` was, when it
4
- # was said (`at`), how its `delivery` went (`:delivered`, `:sent`, `:failed`,
5
- # `:undelivered` or nil) and what it `cost` in dollars, where anything did. Any object
6
- # answering the same six is a message too; this is for a host that has the facts and no
7
- # model to hang them on. A message with no text yet is drawn as being typed.
8
- Message = Data.define :side, :text, :sender, :at, :delivery, :cost do
9
- def initialize(side:, text: nil, sender: nil, at: nil, delivery: nil, cost: nil) = super
3
+ # for the other party, `:right` for the reader), its `text`, the `photos` it carries as
4
+ # a list of addresses, who the `sender` was, when it was said (`at`), how its `delivery`
5
+ # went (`:delivered`, `:sent`, `:failed`, `:undelivered` or nil) and what it `cost` in
6
+ # dollars, where anything did. Any object answering the same seven is a message too;
7
+ # this is for a host that has the facts and no model to hang them on. A message with
8
+ # neither words nor pictures yet is drawn as being typed.
9
+ Message = Data.define :side, :text, :photos, :sender, :at, :delivery, :cost do
10
+ def initialize(side:, text: nil, photos: [], sender: nil, at: nil, delivery: nil, cost: nil)
11
+ super
12
+ end
10
13
  end
11
14
  end
data/lib/bh/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Bh
2
2
  # Version of the gem, read by the gemspec and by hosts checking compatibility.
3
- VERSION = '6.3.0'
3
+ VERSION = '6.5.0'
4
4
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bh6",
3
- "version": "6.3.0",
3
+ "version": "6.5.0",
4
4
  "private": true,
5
5
  "description": "Bootstrap 6 in a Rails app: the form builder that dresses every field, the components Bootstrap ships behavior for and no markup, and the Stimulus controllers behind them.",
6
6
  "license": "MIT",