chats 0.4.1 → 0.4.2

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: '001542179fe490a6f8743099b6b31a2d9cff7cf43a28b982c0cac932b8c06bd1'
4
- data.tar.gz: 29720d94ecd7302ae2adcb17f9a81b6fbc6f0365d900e5773655250e14ea9bb9
3
+ metadata.gz: f2f76b973f012e2c86514932efaca3db25eaf8c70ed4709276d1f5f33d520de2
4
+ data.tar.gz: 2b88c68ad0d239a9ba87b5e907193924926a3926715ed39b62f844e2d899bf19
5
5
  SHA512:
6
- metadata.gz: 3d9f25316a4832204fb285ea6547fe34a5c7f938c148307bef311a3311ed770401329b60e020031d7468b200ecf1170294cb7edce6dfbebb5d8c386052aa1adf
7
- data.tar.gz: d7f28cf57770b2ad517299f56d431ea11fcc6f33b1886c36a815cce6de81dec983082cd16eb74dfd3528bb966af8feb277cb6ff286d7c89d395f934a12737d3c
6
+ metadata.gz: be31c3ce5aad04e1c9d5d0ba141403514da0f547de42a4516ede78117025a12c7f3936060787a3935e477e4f2c03aa2fcfb7b7550c0fdca77a5c6d65a0c86db3
7
+ data.tar.gz: f22af91cf0ccb50bd933659d0fdc549306ecb2bfad09ee87cfb0193499a0839c2f468cd8e109ad7266ddba35200126b28d5f8958b065420fd828a065b9f40b80
data/CHANGELOG.md CHANGED
@@ -4,6 +4,35 @@ All notable changes to this project are documented here.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.4.2] - 2026-10-17
8
+
9
+ ### Changed
10
+ **Upgrade note: your `can_message` policy now runs on every direct send,
11
+ not only when a thread is opened.** Read yours before upgrading. If it
12
+ means "who may OPEN a conversation" (a listing must be live, a ride must be
13
+ accepted), existing threads will stop taking messages the moment it turns
14
+ false for the pair — which is usually what you want (a block already
15
+ behaves this way), but is a change. If it is expensive, it now costs that
16
+ on every direct message; make it cheap or cache what it reads. It must not
17
+ raise: an exception escapes the send like any other host error. Group
18
+ conversations and system messages are unchanged.
19
+
20
+ - **`can_message` holds on every direct send.** A block placed
21
+ mid-conversation already stopped the next message; the host's policy
22
+ turning false did not, so a pair allowed when the thread was opened kept
23
+ talking after an account was banned or a membership ended. Every text
24
+ message into a direct conversation now re-runs `Chats.can_message?`
25
+ exactly like the block check: `:blocked` when either side blocked the
26
+ other, `:not_allowed` (new, in `en` and `es`) when the policy says no.
27
+ The policy is asked last, only of a send that is otherwise valid (a
28
+ stranger, a locked thread or an empty body is refused before it runs).
29
+ The composer answers with its errors stream as before.
30
+ - **A direct thread with nobody on the other side takes no messages.** When
31
+ the counterpart's account was destroyed, its seat went with it (the
32
+ gem's `dependent: :destroy` on participations); a text message into that
33
+ thread is refused with `:not_allowed` instead of slipping past both the
34
+ block and the policy checks. The thread stays readable.
35
+
7
36
  ## [0.4.1] - 2026-10-17
8
37
 
9
38
  ### Fixed
data/README.md CHANGED
@@ -171,7 +171,7 @@ That single hook makes moderate's bidirectional block table the law everywhere c
171
171
  - **can't send** into an existing one (a block placed mid-conversation stops the very next message),
172
172
  - and **stop seeing** each other's direct threads in the inbox and unread counts — *hidden, never deleted*: lift the block and the history reappears.
173
173
 
174
- Two semantics worth knowing: blocking is enforced **beneath** your `can_message` policy (a permissive or buggy policy can never let a blocked pair talk), and **group conversations are exempt from pair blocks** — the industry standard: blocking someone removes your private line, not your seat in shared spaces. If your domain should eject blocked members from groups, do it in moderate's `on_block` hook by tearing down whatever domain relationship feeds the group membership.
174
+ Two semantics worth knowing: blocking is enforced **beneath** your `can_message` policy (a permissive or buggy policy can never let a blocked pair talk), both are checked on **every direct send**, not only when the thread is opened (a block placed, or a policy turning false, mid-conversation stops the very next message with `:blocked` / `:not_allowed`), and **group conversations are exempt from pair blocks** — the industry standard: blocking someone removes your private line, not your seat in shared spaces. If your domain should eject blocked members from groups, do it in moderate's `on_block` hook by tearing down whatever domain relationship feeds the group membership.
175
175
 
176
176
  ### 2. Reportable + filtered messages
177
177
 
@@ -82,6 +82,7 @@ en:
82
82
  base:
83
83
  locked: "This conversation is closed."
84
84
  blocked: "You can't message this person."
85
+ not_allowed: "You can't message this person right now."
85
86
  action:
86
87
  invalid: "needs a label of up to %{count} characters and a link to an http(s) address or to a path on this site that starts with /"
87
88
  author:
@@ -82,6 +82,7 @@ es:
82
82
  base:
83
83
  locked: "Esta conversación está cerrada."
84
84
  blocked: "No puedes enviar mensajes a esta persona."
85
+ not_allowed: "Ahora mismo no puedes enviar mensajes a esta persona."
85
86
  action:
86
87
  invalid: "necesita un texto de hasta %{count} caracteres y un enlace a una dirección http(s) o a una ruta de este sitio que empiece por /"
87
88
  author:
@@ -115,13 +115,15 @@ module Chats
115
115
  validate :body_or_files_required
116
116
  validate :body_must_fit_length_limit
117
117
  validate :sender_must_be_active_participant, on: :create
118
- validate :sender_must_not_be_blocked, on: :create
119
118
  validate :conversation_must_not_be_locked, on: :create
120
119
  validate :author_must_be_persisted
121
120
  # Checked at the write that SETS it, not on every save: an edit must
122
121
  # never fail over an action nobody touched.
123
122
  validate :action_must_be_well_formed, if: :will_save_change_to_action?
124
123
  validate :files_must_be_allowed
124
+ # Last on purpose: the host's policy is asked only of a send that is
125
+ # otherwise valid (see the method).
126
+ validate :sender_must_still_be_allowed, on: :create
125
127
 
126
128
  after_create :register_on_conversation
127
129
  after_create_commit :broadcast_created
@@ -337,15 +339,37 @@ module Chats
337
339
  errors.add(:sender, :not_a_participant) unless conversation.participant?(sender)
338
340
  end
339
341
 
340
- # Block enforcement at the WRITE, not just at conversation creation: a
341
- # block placed mid-conversation must stop the very next send. Direct
342
- # threads only — see Conversation.excluding_blocked_for for the group
343
- # rationale.
344
- def sender_must_not_be_blocked
345
- return if system? || sender.nil? || conversation.nil? || !conversation.direct?
342
+ # Who may talk to whom is decided at the WRITE, not just when the
343
+ # conversation was opened: a block placed mid-conversation stops the very
344
+ # next send, and so does the host's `can_message` policy turning false —
345
+ # an account banned, a listing closed, a membership ended. The pair was
346
+ # allowed when the thread began; what matters is whether it still is.
347
+ # Direct threads only — see Conversation.excluding_blocked_for for the
348
+ # group rationale. Two errors, because they read differently to the
349
+ # sender: `:blocked` (either side blocked the other) and `:not_allowed`
350
+ # (the policy said no).
351
+ #
352
+ # Asked last, and only of a send that is otherwise valid: a stranger to
353
+ # the conversation, a locked thread or an empty body is refused before
354
+ # the host's policy runs, so the policy is never consulted about a
355
+ # message that could not be sent anyway (it may be expensive, and it
356
+ # runs on every direct send now). A policy must not raise: an
357
+ # exception escapes the send like any other host error.
358
+ #
359
+ # A direct thread with nobody on the other side — the counterpart's
360
+ # account was destroyed and its seat went with it — takes no messages:
361
+ # there is no one the policy could allow.
362
+ def sender_must_still_be_allowed
363
+ return if system? || sender.nil? || conversation.nil? || !conversation.direct? || errors.any?
346
364
 
347
365
  other = conversation.other_participants(sender).first&.messager
348
- errors.add(:base, :blocked) if other && Chats.blocked_between?(sender, other)
366
+ if other.nil?
367
+ errors.add(:base, :not_allowed)
368
+ elsif Chats.blocked_between?(sender, other)
369
+ errors.add(:base, :blocked)
370
+ elsif !Chats.config.can_message.call(sender, other)
371
+ errors.add(:base, :not_allowed)
372
+ end
349
373
  end
350
374
 
351
375
  # Authorship is a signature, not a conversation seat. A host may keep
data/lib/chats/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chats
4
- VERSION = "0.4.1"
4
+ VERSION = "0.4.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chats
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - rameerez