Merge upstream main into reuse-pinned-push-connections

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bj8KnxpTf9sj2Ysa8aLAVa
This commit is contained in:
Marcello Costagliola
2026-10-06 13:33:38 +02:00
21 changed files with 165 additions and 60 deletions
+27 -4
View File
@@ -21,7 +21,7 @@ jobs:
persist-credentials: false
- name: Set up Ruby
uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1.324.0
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
with:
ruby-version: .ruby-version
bundler-cache: true
@@ -40,7 +40,7 @@ jobs:
persist-credentials: false
- name: Set up Ruby
uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1.324.0
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
with:
ruby-version: .ruby-version
bundler-cache: true
@@ -48,6 +48,29 @@ jobs:
- name: Lint code for consistent style
run: bin/rubocop
herb:
name: Herb check
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Install packages
run: sudo apt-get update && sudo apt-get install --no-install-recommends -y libvips
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Ruby
uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1.324.0
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Check that every HTML template compiles through Herb
run: bin/rails herb:check
lint-actions:
name: GitHub Actions audit
runs-on: ubuntu-latest
@@ -87,7 +110,7 @@ jobs:
persist-credentials: false
- name: Set up Ruby
uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1.324.0
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
env:
REDIS_URL: redis://localhost:6379/0
with:
@@ -117,7 +140,7 @@ jobs:
persist-credentials: false
- name: Set up Ruby
uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1.324.0
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
env:
REDIS_URL: redis://localhost:6379/0
with:
+52 -36
View File
@@ -23,16 +23,16 @@ GIT
GIT
remote: https://github.com/rails/propshaft.git
revision: e49a9de659ff27462015e54dd832e86e762a6ddc
revision: dc979db89cd07c72ee4d11d415ae1cb4fd072623
specs:
propshaft (1.2.1)
propshaft (1.3.2)
actionpack (>= 7.0.0)
activesupport (>= 7.0.0)
rack
GIT
remote: https://github.com/rails/rails.git
revision: 1a02651ac37fb64b4de2a2b73461d86acf9c98fb
revision: e3d5c569d217c56c022b04acb127a8c24dbe9685
branch: main
specs:
actioncable (8.2.0.alpha)
@@ -63,10 +63,10 @@ GIT
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
rails-html-sanitizer (~> 1.7)
useragent (~> 0.16)
actiontext (8.2.0.alpha)
action_text-trix (~> 2.1.15)
action_text-trix (~> 2.1.16)
actionpack (= 8.2.0.alpha)
activerecord (= 8.2.0.alpha)
activestorage (= 8.2.0.alpha)
@@ -77,11 +77,12 @@ GIT
activesupport (= 8.2.0.alpha)
builder (~> 3.1)
erubi (~> 1.11)
herb (>= 0.10)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
rails-html-sanitizer (~> 1.7)
activejob (8.2.0.alpha)
activesupport (= 8.2.0.alpha)
globalid (>= 0.3.6)
globalid (>= 1.4.0)
activemodel (8.2.0.alpha)
activesupport (= 8.2.0.alpha)
activerecord (8.2.0.alpha)
@@ -93,7 +94,7 @@ GIT
activejob (= 8.2.0.alpha)
activerecord (= 8.2.0.alpha)
activesupport (= 8.2.0.alpha)
marcel (~> 1.0)
marcel (~> 2.0)
activesupport (8.2.0.alpha)
base64
bigdecimal
@@ -104,6 +105,8 @@ GIT
json
logger (>= 1.4.2)
minitest (>= 5.1)
psych (>= 4)
ractor-dispatch (>= 0.3.0)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
@@ -142,7 +145,7 @@ GEM
base64 (0.3.0)
bcrypt (3.1.22)
benchmark (0.5.0)
bigdecimal (3.3.1)
bigdecimal (4.1.3)
brakeman (8.0.6)
racc
builder (3.3.0)
@@ -159,7 +162,7 @@ GEM
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
chunky_png (1.4.0)
concurrent-ruby (1.3.7)
concurrent-ruby (1.3.8)
connection_pool (2.5.5)
crack (1.0.0)
bigdecimal
@@ -170,9 +173,9 @@ GEM
irb (~> 1.10)
reline (>= 0.3.8)
drb (2.2.3)
erb (6.0.4)
erb (6.0.7)
erubi (1.13.1)
faker (3.5.2)
faker (3.8.0)
i18n (>= 1.8.11, < 2)
ffi (1.17.2)
ffi (1.17.2-aarch64-linux-gnu)
@@ -182,17 +185,22 @@ GEM
geared_pagination (1.2.0)
activesupport (>= 5.0)
addressable (>= 2.5.0)
globalid (1.3.0)
globalid (1.4.0)
activesupport (>= 6.1)
hashdiff (1.2.0)
i18n (1.14.7)
herb (0.11.0-aarch64-linux-gnu)
herb (0.11.0-arm64-darwin)
herb (0.11.0-x86_64-darwin)
herb (0.11.0-x86_64-linux-gnu)
i18n (1.15.2)
concurrent-ruby (~> 1.0)
image_processing (1.14.0)
mini_magick (>= 4.9.5, < 6)
ruby-vips (>= 2.0.17, < 3)
io-console (0.8.1)
irb (1.15.3)
io-console (0.9.4)
irb (1.18.0)
pp (>= 0.6.0)
prism (>= 1.3.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jbuilder (2.14.1)
@@ -219,26 +227,28 @@ GEM
net-imap
net-pop
net-smtp
marcel (1.1.0)
marcel (2.1.0)
matrix (0.4.3)
mini_magick (5.3.1)
logger
mini_mime (1.1.5)
minitest (5.26.2)
minitest (6.0.6)
drb (~> 2.0)
prism (~> 1.5)
mocha (3.1.0)
ruby2_keywords (>= 0.0.5)
mono_logger (1.1.2)
multi_json (1.17.0)
mustermann (3.0.4)
ruby2_keywords (~> 0.0.1)
net-imap (0.6.4.1)
net-imap (0.6.7)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
net-protocol (0.4.0)
timeout
net-smtp (0.5.1)
net-smtp (0.5.2)
net-protocol
nio4r (2.7.5)
nokogiri (1.19.4-aarch64-linux-gnu)
@@ -258,18 +268,18 @@ GEM
platform_agent (1.0.1)
activesupport (>= 5.2.0)
useragent (~> 0.16.3)
pp (0.6.3)
pp (0.6.4)
prettyprint
prettyprint (0.2.0)
prism (1.4.0)
psych (5.2.6)
prism (1.9.0)
psych (5.5.0)
date
stringio
public_suffix (6.0.2)
puma (7.2.1)
nio4r (~> 2.0)
racc (1.8.1)
rack (3.2.6)
rack (3.2.7)
rack-protection (4.2.1)
base64 (>= 0.1.0)
logger (>= 1.6.0)
@@ -279,8 +289,9 @@ GEM
rack (>= 3.0.0)
rack-test (2.2.0)
rack (>= 1.3)
rackup (2.2.1)
rackup (2.3.1)
rack (>= 3)
ractor-dispatch (0.3.0)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
@@ -293,19 +304,24 @@ GEM
activesupport (> 3.1)
railties (> 3.1)
rainbow (3.1.1)
rake (13.3.1)
rdoc (6.16.1)
rake (13.4.2)
rbs (4.2.0)
logger
prism (>= 1.6.0)
tsort
rdoc (8.1.0)
erb
psych (>= 4.0.0)
prism (>= 1.6.0)
rbs (>= 4.0.0)
tsort
redis (5.4.1)
redis-client (>= 0.22.0)
redis-client (0.25.2)
redis-client (0.30.1)
connection_pool
redis-namespace (1.11.0)
redis (>= 4)
regexp_parser (2.11.2)
reline (0.6.3)
reline (0.7.0)
io-console (~> 0.5)
resque (2.7.0)
mono_logger (~> 1)
@@ -353,9 +369,9 @@ GEM
ffi (~> 1.12)
logger
ruby2_keywords (0.0.5)
rubyzip (3.4.0)
rubyzip (3.7.0)
securerandom (0.4.1)
selenium-webdriver (4.35.0)
selenium-webdriver (4.49.0)
base64 (~> 0.2)
logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5)
@@ -380,8 +396,8 @@ GEM
sqlite3 (2.9.6-x86_64-linux-gnu)
stimulus-rails (1.3.4)
railties (>= 6.0.0)
stringio (3.1.8)
thor (1.4.0)
stringio (3.2.0)
thor (1.5.0)
thruster (0.1.23-aarch64-linux)
thruster (0.1.23-arm64-darwin)
thruster (0.1.23-x86_64-darwin)
@@ -410,7 +426,7 @@ GEM
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.7.3)
zeitwerk (2.8.3)
PLATFORMS
aarch64-linux
+1 -1
View File
@@ -20,7 +20,7 @@ class SearchesController < ApplicationController
private
def set_messages
if query.present?
@messages = Current.user.reachable_messages.search(query).with_presentation.last_page_of(100)
@messages = Current.user.reachable_messages.search(query).with_presentation.last_page_of_matches(100)
else
@messages = Message.none
end
+8
View File
@@ -17,6 +17,14 @@ module Message::Searchable
end
end
class_methods do
# Orders by the index's rowid, which is the message id, so SQLite walks the full-text index
# newest first and stops at the page. Ordering by created_at sorted every match before paging.
def last_page_of_matches(size)
Message::Pagination::Page.load(reorder("idx.rowid"), :last, size)
end
end
private
def create_in_index
execute_sql_with_binds "insert into message_search_index(rowid, body) values (?, ?)", id, plain_text_body
+5
View File
@@ -11,6 +11,11 @@ module User::Mentionable
"users/mention"
end
# How a mention appears inside the editor, matching the prompt's editor template.
def to_editor_content_attachment_partial_path
"users/mention"
end
def attachable_plain_text_representation(caption)
"@#{name}"
end
+1 -1
View File
@@ -63,7 +63,7 @@ class Webhook < ApplicationRecord
end
def extract_attachment_from(response)
if response.content_type && mime_type = Mime::Type.lookup(response.content_type)
if response.code == "200" && response.content_type && mime_type = Mime::Type.lookup(response.content_type)
ActiveStorage::Blob.create_and_upload! \
io: StringIO.new(response.body), filename: "attachment.#{mime_type.symbol}", content_type: mime_type.to_s
end
+3 -4
View File
@@ -76,10 +76,9 @@
<% end %>
<label class="switch">
<input type="checkbox"
class="switch__input"
<%= "checked" if Current.account.settings.restrict_room_creation_to_administrators? %>
data-action="change->form#submit">
<%= tag.input type: "checkbox", class: "switch__input",
checked: Current.account.settings.restrict_room_creation_to_administrators?,
data: { action: "change->form#submit" } %>
<span class="switch__btn round"></span>
<span class="for-screen-reader">
Must be admin to create new rooms
+2 -2
View File
@@ -6,8 +6,8 @@
<%= image_tag "disclosure.svg", aria: { hidden: "true" }, size: 10, class: "disclosure" %>
</summary>
<% case
when platform.firefox? && platform.android? %>
<% case %>
<% when platform.firefox? && platform.android? %>
<ol>
<li>Tap <em><%= image_tag "lock.svg", alt: "the View site information button", size: 20 %></em> in the address bar.</li>
<li>Tap <em>Notification</em> to change to <em>Allowed</em>.</li>
+3 -3
View File
@@ -6,10 +6,10 @@
<%= image_tag "disclosure.svg", aria: { hidden: "true" }, size: 10, class: "disclosure" %>
</summary>
<% case
when platform.edge? %>
<% case %>
<% when platform.edge? %>
<ol>
<li>Click <em><%= image_tag "install-edge.svg", alt: "the app available - install Campfire chat button", size: 16 %></em>in the address bar.</li>
<li>Click <em><%= image_tag "external/install-edge.svg", alt: "the app available - install Campfire chat button", size: 16 %></em>in the address bar.</li>
<li>Click <em>Install</em>.</li>
</ol>
<% when platform.chrome? && platform.android? %>
+2 -2
View File
@@ -5,8 +5,8 @@
<%= image_tag "disclosure.svg", aria: { hidden: "true" }, size: 10, class: "disclosure" %>
</summary>
<% case
when platform.firefox? && platform.android? %>
<% case %>
<% when platform.firefox? && platform.android? %>
<ol>
<li>Tap the <em><%= image_tag "menu-dots-vertical.svg", alt: "More options", size: 16 %></em> menu button.</li>
<li>Tap <em>Settings</em>.</li>
+1 -1
View File
@@ -80,5 +80,5 @@ Rails.application.configure do
config.action_controller.raise_on_missing_callback_actions = true
# Visit /rails/locks to see the locks
config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks
config.middleware.insert_before ActionDispatch::Executor, ActionDispatch::DebugLocks
end
+8
View File
@@ -6,3 +6,11 @@ if Rails.env.production? && ENV["SKIP_TELEMETRY"].blank?
config.release = ENV["GIT_REVISION"]
end
end
# sentry-rails declares its Action Cable handle_open and handle_close wrappers private,
# but Rails 8.2 calls them from outside the connection, so every /cable connection fails.
# Remove once sentry-rails ships getsentry/sentry-ruby#2972 (issue #2975).
ActiveSupport.on_load(:action_cable_connection) do
require "sentry/rails/action_cable"
Sentry::Rails::ActionCableExtensions::Connection.send(:public, :handle_open, :handle_close)
end
+1 -1
View File
@@ -1,2 +1,2 @@
# Used to match JavaScripts (new Date).getTime() for sorting
Time::DATE_FORMATS[:epoch] = ->(time) { (time.to_f * 1000).to_i }
ActiveSupport::TimeFormats.register(:epoch, ->(time) { (time.to_f * 1000).to_i })
+2 -2
View File
@@ -8,7 +8,7 @@ class UnreadRoomsChannelTest < ActionCable::Channel::TestCase
assert subscription.confirmed?
assert_has_stream "user_#{users(:jz).id}_unreads"
assert_not_includes subscription.streams, "unread_rooms"
assert_not_includes subscription.stream_names, "unread_rooms"
end
test "an outsider is not told about activity in a room they can't see" do
@@ -37,7 +37,7 @@ class UnreadRoomsChannelTest < ActionCable::Channel::TestCase
stub_connection(current_user: user)
subscribe
stream = subscription.streams.sole
stream = subscription.stream_names.sole
before = ActionCable.server.pubsub.broadcasts(stream).size
yield
@@ -10,6 +10,16 @@ class AccountsControllerTest < ActionDispatch::IntegrationTest
assert_response :ok
end
test "edit shows whether room creation is restricted to administrators" do
get edit_account_url
assert_select "label.switch input.switch__input[type=checkbox]:not([checked])"
accounts(:signal).update!(settings: { restrict_room_creation_to_administrators: true })
get edit_account_url
assert_select "label.switch input.switch__input[type=checkbox][checked][data-action='change->form#submit']"
end
test "edit groups administrators separately from members with a divider" do
get edit_account_url
+15
View File
@@ -15,6 +15,21 @@ class RoomsControllerTest < ActionDispatch::IntegrationTest
assert_response :success
end
test "show renders notification help for each platform" do
{
"Firefox on Android" => "Mozilla/5.0 (Android 14; Mobile; rv:131.0) Gecko/131.0 Firefox/131.0",
"Chrome on Android" => "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36",
"Firefox on desktop" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0",
"Chrome on desktop" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
"Safari on macOS" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15"
}.each do |platform, user_agent|
get room_url(users(:david).rooms.last), headers: { "User-Agent" => user_agent }
assert_response :success, platform
assert_select ".notifications-help ol", { minimum: 1 }, platform
end
end
test "shows records the last room visited in a cookie" do
get room_url(users(:david).rooms.last)
assert response.cookies[:last_room] = users(:david).rooms.last.id
@@ -11,6 +11,13 @@ class Users::ProfilesControllerTest < ActionDispatch::IntegrationTest
assert_response :success
end
test "show includes install instructions for Edge" do
get user_profile_url, headers: { "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18363" }
assert_response :success
assert_select ".pwa__instructions img[src*='install-edge']"
end
test "update" do
put user_profile_url, params: { user: { name: "John Doe", bio: "Acrobat" } }
+1 -1
View File
@@ -1,4 +1,4 @@
<% password_digest = BCrypt::Password.create("secret123456") %>
<% password_digest = BCrypt::Password.create("secret123456", cost: BCrypt::Engine::MIN_COST) %>
david:
name: David
+15
View File
@@ -20,6 +20,21 @@ class Message::SearchableTest < ActiveSupport::TestCase
assert_equal messages, rooms(:designers).messages.search("cat")
end
test "the last page of matches holds the newest ones, read off the index without sorting every match" do
messages = [ "first cat", "second cat", "third cat" ].map do |body|
rooms(:designers).messages.create! body: body, client_message_id: body, creator: users(:david)
end
queries = []
collect = ->(*, payload) { queries << payload[:sql] if payload[:sql].include?("message_search_index") }
page = ActiveSupport::Notifications.subscribed(collect, "sql.active_record") do
rooms(:designers).messages.search("cat").last_page_of_matches(2)
end
assert_equal messages.last(2), page
assert_no_match(/TEMP B-TREE/, Message.connection.select_rows("EXPLAIN QUERY PLAN #{queries.sole}").map(&:last).join(" | "))
end
test "rich text body is converted to plain text for indexing" do
message = rooms(:designers).messages.create! body: "<span>My hovercraft is full of eels</span>", client_message_id: "earth", creator: users(:david)
+1 -1
View File
@@ -41,7 +41,7 @@ class WebhookTest < ActiveSupport::TestCase
test "delivery with error reply" do
assert_no_difference -> { Message.count } do
WebMock.stub_request(:post, webhooks(:bender).url).to_return(status: 500, body: "Internal Error!", headers: {})
WebMock.stub_request(:post, webhooks(:bender).url).to_return(status: 500, body: "<h1>Internal Error!</h1>", headers: { "Content-Type" => "text/html" })
response = webhooks(:bender).deliver(messages(:first))
end
end
-1
View File
@@ -2,7 +2,6 @@ ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment"
require "rails/test_help"
require "minitest/unit"
require "mocha/minitest"
require "webmock/minitest"
require "turbo/broadcastable/test_helper"