livechat 0.9.0 → 0.10.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: 93a0ce2c43a9f0ba3f3ce569aac6758962a949919d924ada0283ebd043c34216
4
- data.tar.gz: 961e891c713d52e780a52149b87230c8661da575a53b7a9356865db39241803b
3
+ metadata.gz: 63ba0ec549bbfac094a6a507ac4e3525c01b9b8926dbbf4e7822e7db05c2472d
4
+ data.tar.gz: 7afeeacda8689b6335c87a06a9fe0fecb50388379fc32d7255ab0fec16afe04e
5
5
  SHA512:
6
- metadata.gz: 20925768376d8fd25aef9323616e9e506c3e39081350ff25166a10eeaeceafbdd2a728a6015fe15407de8d7ed981381ccb1747bc95d10690bd4f9d8e7100b3e6
7
- data.tar.gz: 78e714d488cc3ef8a29aafe63dc80f08069825c1fc78ff5294ce9c2ccce00cd84ffc2d2a44ad0c5b126d42ea7871ff99147621f3e5384fb844761bed607a9856
6
+ metadata.gz: 26d8a59095979df9546eaa12aee10d828b50677a2b32e199c2b25a9820cfa7d3e8a4b24f9ec53e4f19c286f49948ab8bb4ea1756bfea3818d849240b8221f080
7
+ data.tar.gz: e287d3e63e218974245e0c5e495603aee5ae4ddd3b3b9c91af2ebc6ca103aa2d0ffdea731f39003ad79e10c52101bf9f187afba64cb4ded94e07c17ab6bb8cd0
data/AGENTS.md CHANGED
@@ -176,6 +176,6 @@ Conventions this codebase holds to — follow them rather than the first thing t
176
176
  - **The widget is plain ES5-style JS served by the engine**, no build step, no framework, config read from a JSON block so a Turbo visit re-reads the current page's settings.
177
177
  - **Visitor scoping is never by conversation id.** The widget's endpoints resolve the thread from the signed-in id or the guest cookie, so no id in a request can address someone else's conversation. Keep it that way.
178
178
  - **The dummy app pins `config.active_job.queue_adapter = :test`.** Do not remove it or let it drift back to the `:async` default. Attaching a file enqueues Active Storage's analysis job, and `:async` runs it on a background thread that checks out its own connection — writes no test transaction covers, landing in the middle of whatever runs next. That is a suite that fails order-dependently in a test which never created a row, and it is miserable to trace back.
179
- - **`lib/livechat/dashboard.css` is half shared.** Everything above the `GEM-SPECIFIC` banner is the design system all five gems in the family ship — the same tokens, the same `.page-head`/`.tabs`/`.filters`/`.card`/`.badge`/`button`, the same `.dashboard-shell` + `.record-row` + `.detail-panel` two-pane dashboard — identical in every repo apart from the `lvc` prefix. Diff it against a sibling before changing it, and carry the change to the other four. Anything only this gem has goes below the banner. New dashboard markup reuses the shared class names rather than inventing a domain-specific one.
179
+ - **`lib/livechat/dashboard.css` is half shared.** Everything above the `GEM-SPECIFIC` banner is the design system all five gems in the family ship — the same tokens, the same `.page-head`/`.tabs`/`.filters`/`.card`/`.badge`/`button`/`.status-switch`, the same `.dashboard-shell` + `.record-row` + `.detail-panel` two-pane dashboard — identical in every repo apart from the `lvc` prefix. Diff it against a sibling before changing it, and carry the change to the other four. Anything only this gem has goes below the banner. New dashboard markup reuses the shared class names rather than inventing a domain-specific one.
180
180
  - Every user-facing change bumps `lib/livechat/version.rb` and adds a `CHANGELOG.md` entry that says what it costs, not only what it adds.
181
181
  - Commit messages are prose that explains the tradeoff — read `git log` before writing one.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0
4
+
5
+ - **The open/resolved switch is now the family's shared component.** It looks the
6
+ same and sits where it sat, but it is `.status-switch` in the shared core of
7
+ `dashboard.css` rather than this gem's own `.status-toggle`, so ideasbugs could
8
+ adopt it for its three statuses instead of growing a second near-identical
9
+ control. `.status-toggle`, `.status-toggle-form`, `.is-open` and `.is-resolved`
10
+ are gone — that is the breaking change if you styled them.
11
+ - Each half is now its own target: `Open` reopens, `Resolved` resolves, via
12
+ `formaction` on two submit buttons in one form. Before, the whole pill was one
13
+ button that flipped whichever way it was not. The half that is already current
14
+ is inert, so clicking it can no longer log a reopen of an open chat. The
15
+ sliding highlight went with the rewrite — it never animated anyway, since
16
+ resolving reloads the page and the pill arrives already in its new position.
17
+ `POST /:id/resolve` and `POST /:id/reopen` are unchanged.
18
+
19
+ ## 0.9.1
20
+
21
+ - **The conversation heading reads as one block.** The visitor's email and locale
22
+ moved out of their own row and sit directly under the name, inside a
23
+ `.panel-titles` wrapper, and the open/resolved switch is pinned to the right
24
+ edge of the heading row with `margin-left: auto` rather than relying on the
25
+ row's `space-between` — a long name no longer drags the switch inward. If you
26
+ targeted `.context-line` as a child of `.detail-panel`, it is now a child of
27
+ `.panel-head`.
28
+
3
29
  ## 0.9.0
4
30
 
5
31
  - **One design system across the family.** The stylesheet now opens with a
@@ -1,33 +1,39 @@
1
1
  <div class="detail-panel">
2
2
  <div class="panel-head">
3
- <h1><%= conversation.display_name %> <span class="case-id">#<%= conversation.id %></span></h1>
4
- <% if conversation.open? %>
5
- <%= button_to resolve_conversation_path(conversation), method: :post,
6
- form_class: 'status-toggle-form',
7
- class: 'status-toggle is-open',
8
- aria: { label: t('livechat.dashboard.resolve', default: 'Resolve') } do %>
9
- <span><%= t('livechat.statuses.open', default: 'Open') %></span>
10
- <span><%= t('livechat.statuses.resolved', default: 'Resolved') %></span>
11
- <% end %>
12
- <% else %>
13
- <%= button_to reopen_conversation_path(conversation), method: :post,
14
- form_class: 'status-toggle-form',
15
- class: 'status-toggle is-resolved',
16
- aria: { label: t('livechat.dashboard.reopen', default: 'Reopen') } do %>
17
- <span><%= t('livechat.statuses.open', default: 'Open') %></span>
18
- <span><%= t('livechat.statuses.resolved', default: 'Resolved') %></span>
3
+ <%# The name with the visitor's context directly under it, so the status
4
+ toggle on the right of the row clears both. %>
5
+ <div class="panel-titles">
6
+ <h1><%= conversation.display_name %> <span class="case-id">#<%= conversation.id %></span></h1>
7
+ <%# One quiet line of live context: who the visitor is and the language to answer in. %>
8
+ <p class="muted context-line">
9
+ <% context = [] %>
10
+ <% context << conversation.visitor_email if conversation.visitor_email.present? %>
11
+ <% context << conversation.locale if conversation.locale.present? %>
12
+ <%= safe_join(context, ' · ') %>
13
+ </p>
14
+ </div>
15
+ <%# The shared status switch: the state it is in is lit and inert, the other
16
+ one submits to its own route via `formaction`. One form, no JS, and
17
+ clicking the lit half cannot log a reopen of an already-open chat. %>
18
+ <%= form_with url: resolve_conversation_path(conversation), method: :post, class: 'status-switch' do %>
19
+ <% if conversation.open? %>
20
+ <button type="button" class="status-open current" aria-current="true">
21
+ <%= t('livechat.statuses.open', default: 'Open') %>
22
+ </button>
23
+ <button formaction="<%= resolve_conversation_path(conversation) %>" class="status-resolved">
24
+ <%= t('livechat.statuses.resolved', default: 'Resolved') %>
25
+ </button>
26
+ <% else %>
27
+ <button formaction="<%= reopen_conversation_path(conversation) %>" class="status-open">
28
+ <%= t('livechat.statuses.open', default: 'Open') %>
29
+ </button>
30
+ <button type="button" class="status-resolved current" aria-current="true">
31
+ <%= t('livechat.statuses.resolved', default: 'Resolved') %>
32
+ </button>
19
33
  <% end %>
20
34
  <% end %>
21
35
  </div>
22
36
 
23
- <%# One quiet line of live context: who the visitor is and the language to answer in. %>
24
- <p class="muted context-line">
25
- <% context = [] %>
26
- <% context << conversation.visitor_email if conversation.visitor_email.present? %>
27
- <% context << conversation.locale if conversation.locale.present? %>
28
- <%= safe_join(context, ' · ') %>
29
- </p>
30
-
31
37
  <div class="card">
32
38
  <% last = messages.last %>
33
39
  <% cable = livechat_cable_data("livechat:conversation:#{conversation.id}") %>
@@ -144,6 +144,19 @@
144
144
  & .actions form { display: inline; }
145
145
  & .form-actions { padding-top: 12px; }
146
146
 
147
+ /* Status switch: one pill holding every state a record can be in, the state it
148
+ is in now lit. Each other segment is a submit button, so moving a record
149
+ takes one click and no JavaScript; the current segment is a `type="button"`
150
+ so clicking it cannot re-file the record it is already filed under. Two
151
+ states or five, the pill sizes itself. A gem tints the lit segment with its
152
+ own status colour — see the `GEM-SPECIFIC` section. */
153
+ & .status-switch { display: inline-flex; flex: 0 0 auto; margin: 0; padding: 3px; border: 1px solid var(--lvc-border); border-radius: 999px; background: var(--lvc-bg); }
154
+ & .status-switch button { min-height: 30px; padding: 0 12px; border: 0; border-radius: 999px; background: none; color: var(--lvc-muted); font-size: 13px; font-weight: 700; white-space: nowrap; transition: background .16s ease, color .16s ease; }
155
+ & .status-switch button:hover { border: 0; background: color-mix(in srgb, var(--lvc-muted) 12%, transparent); color: var(--lvc-text); }
156
+ & .status-switch button.current { background: var(--lvc-surface); color: var(--lvc-text); box-shadow: 0 1px 2px rgba(15, 23, 42, .12); cursor: default; }
157
+ & .status-switch button.current:hover { background: var(--lvc-surface); }
158
+ & .panel-head .status-switch { margin-left: auto; }
159
+
147
160
  & .card { overflow: hidden; border: 1px solid var(--lvc-border); border-radius: 12px; background: var(--lvc-surface); }
148
161
  & .card.pad { padding: 16px; overflow: visible; }
149
162
  & table { width: 100%; border-collapse: collapse; }
@@ -266,6 +279,7 @@
266
279
  & .dashboard-detail .detail-panel { min-height: calc(100vh - 64px); }
267
280
  & .mobile-back { display: block; flex: 0 0 auto; margin: 4px 0 10px; font-size: 13px; }
268
281
  & .panel-head h1 { font-size: 18px; }
282
+ & .status-switch button { padding: 0 9px; font-size: 12px; }
269
283
  & dl { grid-template-columns: 1fr; gap: 3px; }
270
284
  & dd { margin-bottom: 8px; }
271
285
  }
@@ -290,16 +304,13 @@
290
304
  & .record-row.unread .record-copy { font-weight: 700; }
291
305
  & .record-side .avatars { min-height: 26px; }
292
306
 
293
- /* One switch reading open|resolved, sliding to the side it is on. */
294
- & .status-toggle-form { flex: 0 0 auto; }
295
- & .status-toggle { position: relative; display: grid; grid-template-columns: 1fr 1fr; gap: 0; width: 176px; min-height: 36px; padding: 3px; border-radius: 999px; border: 1px solid var(--lvc-border); background: var(--lvc-bg); color: var(--lvc-muted); overflow: hidden; }
296
- & .status-toggle::before { content: ""; position: absolute; z-index: 0; top: 3px; bottom: 3px; width: calc(50% - 3px); border-radius: 999px; background: var(--lvc-surface); box-shadow: 0 1px 2px rgba(15, 23, 42, .12); transition: left .16s ease; }
297
- & .status-toggle.is-open::before { left: 3px; }
298
- & .status-toggle.is-resolved::before { left: 50%; }
299
- & .status-toggle span { position: relative; z-index: 1; display: flex; align-items: center; justify-content: center; min-width: 0; padding: 0 10px; font-size: 13px; font-weight: 700; white-space: nowrap; }
300
- & .status-toggle.is-open span:first-child { color: var(--lvc-open); }
301
- & .status-toggle.is-resolved span:last-child { color: var(--lvc-text); }
302
- & .status-toggle:hover { text-decoration: none; color: var(--lvc-text); }
307
+ /* The panel heading: the name with the visitor's context directly under it,
308
+ and the shared status switch pushed to the far right of the row whatever the
309
+ name's length. */
310
+ & .panel-titles { display: flex; min-width: 0; flex-direction: column; gap: 2px; }
311
+ & .context-line { margin: 0; overflow-wrap: anywhere; }
312
+ /* An open conversation is the one that wants an answer, so say so in green. */
313
+ & .status-switch button.current.status-open { color: var(--lvc-open); }
303
314
 
304
315
  & .avatars { display: inline-flex; }
305
316
  & .avatars .avatar { width: 26px; height: 26px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; color: #fff; border: 2px solid var(--lvc-surface); cursor: default; }
@@ -312,7 +323,6 @@
312
323
  & .avatar.color-5 { background: #0891b2; }
313
324
 
314
325
  /* The thread: the visitor on the left, agents on the right. */
315
- & .context-line { margin: -6px 0 16px; overflow-wrap: anywhere; }
316
326
  & .thread { display: flex; flex-direction: column; gap: 4px; padding: 16px; }
317
327
  & .thread .who { font-size: 12px; color: var(--lvc-muted); margin: 10px 4px 2px; }
318
328
  & .thread .who.visitor { text-align: left; }
@@ -362,8 +372,7 @@
362
372
 
363
373
  /* Inside the thread pane only the messages scroll, so a reply never jumps the
364
374
  page and the composer stays put. */
365
- & .detail-panel .panel-head,
366
- & .detail-panel .context-line { flex: 0 0 auto; }
375
+ & .detail-panel .panel-head { flex: 0 0 auto; }
367
376
  & .detail-panel .card { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; border-radius: 8px; }
368
377
  & .detail-panel .thread { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
369
378
  & .detail-panel .reply { flex: 0 0 auto; }
@@ -376,15 +385,12 @@
376
385
  .lvc-convo .container { display: flex; flex-direction: column; padding-bottom: 16px; }
377
386
  .lvc-convo .breadcrumb,
378
387
  .lvc-convo .panel-head,
379
- .lvc-convo .context-line,
380
388
  .lvc-convo .flash { flex: 0 0 auto; }
381
389
  .lvc-convo .detail-panel { flex: 1 1 auto; min-height: 0; }
382
390
 
383
391
  @media (max-width: 760px) {
384
392
  .lvc-dashboard {
385
393
  & .detail-panel .panel-head h1 { font-size: 18px; }
386
- & .status-toggle { width: 152px; min-height: 34px; }
387
- & .status-toggle span { padding: 0 7px; font-size: 12px; }
388
394
  & .thread .msg { max-width: 88%; }
389
395
  }
390
396
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Livechat
4
- VERSION = '0.9.0'
4
+ VERSION = '0.10.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: livechat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaroslav Shmarov