From ae149fca92cbfa393b1fdcff206912d6a4d3d2eb Mon Sep 17 00:00:00 2001 From: Edward Tippett Date: Tue, 22 Sep 2026 23:05:28 +0700 Subject: [PATCH 01/11] Hash fixture passwords at minimum bcrypt cost The fixture calls BCrypt directly, bypassing Rails' test configuration. --- test/fixtures/users.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 39f71a5..a2f8804 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -1,4 +1,4 @@ -<% password_digest = BCrypt::Password.create("secret123456") %> +<% password_digest = BCrypt::Password.create("secret123456", cost: BCrypt::Engine::MIN_COST) %> david: name: David From 26c70b325447a48f0a190850382cea18c4641fa5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 19:34:42 +0000 Subject: [PATCH 02/11] build(deps): bump propshaft from `e49a9de` to `dc979db` Bumps [propshaft](https://github.com/rails/propshaft) from `e49a9de` to `dc979db`. - [Release notes](https://github.com/rails/propshaft/releases) - [Commits](https://github.com/rails/propshaft/compare/e49a9de659ff27462015e54dd832e86e762a6ddc...dc979db89cd07c72ee4d11d415ae1cb4fd072623) --- updated-dependencies: - dependency-name: propshaft dependency-version: dc979db89cd07c72ee4d11d415ae1cb4fd072623 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index e361a18..4df52a1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -23,9 +23,9 @@ 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 @@ -142,7 +142,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 +159,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 @@ -185,7 +185,7 @@ GEM globalid (1.3.0) activesupport (>= 6.1) hashdiff (1.2.0) - i18n (1.14.7) + i18n (1.15.2) concurrent-ruby (~> 1.0) image_processing (1.14.0) mini_magick (>= 4.9.5, < 6) @@ -224,7 +224,9 @@ GEM 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) @@ -263,7 +265,7 @@ GEM pp (0.6.3) prettyprint prettyprint (0.2.0) - prism (1.4.0) + prism (1.9.0) psych (5.2.6) date stringio @@ -271,7 +273,7 @@ GEM 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) From 862e128722e32ae1a0abae10ff33dc6e359d67be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 19:34:48 +0000 Subject: [PATCH 03/11] build(deps): bump ruby/setup-ruby Bumps the github-actions group with 1 update: [ruby/setup-ruby](https://github.com/ruby/setup-ruby). Updates `ruby/setup-ruby` from 1.324.0 to 1.327.0 - [Release notes](https://github.com/ruby/setup-ruby/releases) - [Changelog](https://github.com/ruby/setup-ruby/blob/master/release.rb) - [Commits](https://github.com/ruby/setup-ruby/compare/a0102e0972be65f351c307e2d64b9314a57c8073...14594264cd68ce8a2345dd349bc3d138a4ef85c8) --- updated-dependencies: - dependency-name: ruby/setup-ruby dependency-version: 1.327.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 050be3e..4cfd977 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 @@ -87,7 +87,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 +117,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: From 957c9da9c4a151c7d635b0cec021cf6f3f777791 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 19:34:50 +0000 Subject: [PATCH 04/11] build(deps-dev): bump faker from 3.5.2 to 3.8.0 Bumps [faker](https://github.com/faker-ruby/faker) from 3.5.2 to 3.8.0. - [Release notes](https://github.com/faker-ruby/faker/releases) - [Changelog](https://github.com/faker-ruby/faker/blob/main/CHANGELOG.md) - [Commits](https://github.com/faker-ruby/faker/compare/v3.5.2...v3.8.0) --- updated-dependencies: - dependency-name: faker dependency-version: 3.8.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index e361a18..d42e8f0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -159,7 +159,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 @@ -172,7 +172,7 @@ GEM drb (2.2.3) erb (6.0.4) 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) @@ -185,7 +185,7 @@ GEM globalid (1.3.0) activesupport (>= 6.1) hashdiff (1.2.0) - i18n (1.14.7) + i18n (1.15.2) concurrent-ruby (~> 1.0) image_processing (1.14.0) mini_magick (>= 4.9.5, < 6) From 67b6df38478ea2c258799aaf0d5327fdae9c6cb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 19:36:16 +0000 Subject: [PATCH 05/11] build(deps-dev): bump selenium-webdriver from 4.35.0 to 4.49.0 Bumps [selenium-webdriver](https://github.com/SeleniumHQ/selenium) from 4.35.0 to 4.49.0. - [Release notes](https://github.com/SeleniumHQ/selenium/releases) - [Changelog](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES) - [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.35.0...selenium-4.49.0) --- updated-dependencies: - dependency-name: selenium-webdriver dependency-version: 4.49.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index e361a18..4df1a1a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -355,9 +355,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) From 955d799d1192fe247bdabaa0f8cd170cbe21122a Mon Sep 17 00:00:00 2001 From: Sam Ruby Date: Sun, 4 Oct 2026 20:06:53 -0400 Subject: [PATCH 06/11] Find the Edge install icon under images/external install-edge.svg lives in app/assets/images/external/, alongside the other install icons, but the Edge branch of pwa/_install_instructions asked for it at the top level. Propshaft raises MissingAssetError, so a browser the useragent gem reports as Edge got a 500 on the profile page and anywhere else the partial renders. Co-Authored-By: Claude Opus 5.5 --- app/views/pwa/_install_instructions.html.erb | 2 +- test/controllers/users/profiles_controller_test.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/views/pwa/_install_instructions.html.erb b/app/views/pwa/_install_instructions.html.erb index d956053..b82ddb2 100644 --- a/app/views/pwa/_install_instructions.html.erb +++ b/app/views/pwa/_install_instructions.html.erb @@ -9,7 +9,7 @@ <% case when platform.edge? %>
    -
  1. Click <%= image_tag "install-edge.svg", alt: "the app available - install Campfire chat button", size: 16 %>in the address bar.
  2. +
  3. Click <%= image_tag "external/install-edge.svg", alt: "the app available - install Campfire chat button", size: 16 %>in the address bar.
  4. Click Install.
<% when platform.chrome? && platform.android? %> diff --git a/test/controllers/users/profiles_controller_test.rb b/test/controllers/users/profiles_controller_test.rb index 1f14953..0331aaf 100644 --- a/test/controllers/users/profiles_controller_test.rb +++ b/test/controllers/users/profiles_controller_test.rb @@ -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" } } From edaab3e5d1ae24916717b06a557971aac9c608ec Mon Sep 17 00:00:00 2001 From: Marcello Costagliola Date: Mon, 5 Oct 2026 02:50:48 +0200 Subject: [PATCH 07/11] Read the newest search matches off the index instead of sorting them all Search showed the last 100 matches by created_at, so SQLite collected every message containing the words and sorted them before keeping a page. A common word in a large account meant sorting most of its history on every search. Ordering by the full-text index's rowid, which is the message id, lets SQLite walk the index from the newest match and stop once the page is full. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JVFo3Lt9T8M5NR7KxVvsZ2 --- app/controllers/searches_controller.rb | 2 +- app/models/message/searchable.rb | 8 ++++++++ test/models/message/searchable_test.rb | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/controllers/searches_controller.rb b/app/controllers/searches_controller.rb index 221218d..735da0c 100644 --- a/app/controllers/searches_controller.rb +++ b/app/controllers/searches_controller.rb @@ -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 diff --git a/app/models/message/searchable.rb b/app/models/message/searchable.rb index 58ea47f..749180f 100644 --- a/app/models/message/searchable.rb +++ b/app/models/message/searchable.rb @@ -9,6 +9,14 @@ module Message::Searchable scope :search, ->(query) { joins("join message_search_index idx on messages.id = idx.rowid").where("idx.body match ?", query).ordered } 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 diff --git a/test/models/message/searchable_test.rb b/test/models/message/searchable_test.rb index d55bedf..6a8f4ab 100644 --- a/test/models/message/searchable_test.rb +++ b/test/models/message/searchable_test.rb @@ -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: "My hovercraft is full of eels", client_message_id: "earth", creator: users(:david) From 6288a10633aad0e3d155db0f253d0f417e93ea4c Mon Sep 17 00:00:00 2001 From: Marcello Costagliola Date: Mon, 5 Oct 2026 02:58:11 +0200 Subject: [PATCH 08/11] Post a webhook reply as an attachment only when the bot answered 200 A bot's reply becomes a message when the status is 200 and the type is text, and an attachment otherwise, but the attachment branch never looked at the status. Whenever a bot's endpoint failed, its error page landed in the room as a file: a proxy's 502 page as attachment.html, a 404 as attachment.text. Apply the text branch's 200 check to attachments too. The error reply test answered without a content type, which skipped the attachment branch, so it now answers with an HTML error page. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JVFo3Lt9T8M5NR7KxVvsZ2 --- app/models/webhook.rb | 2 +- test/models/webhook_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/webhook.rb b/app/models/webhook.rb index 5c307e7..f386e5f 100644 --- a/app/models/webhook.rb +++ b/app/models/webhook.rb @@ -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 diff --git a/test/models/webhook_test.rb b/test/models/webhook_test.rb index 4f4e3bc..d37192a 100644 --- a/test/models/webhook_test.rb +++ b/test/models/webhook_test.rb @@ -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: "

Internal Error!

", headers: { "Content-Type" => "text/html" }) response = webhooks(:bender).deliver(messages(:first)) end end From 1cb2f69786c888b7637fd572e149e7d274ff81da Mon Sep 17 00:00:00 2001 From: Sam Ruby Date: Sun, 4 Oct 2026 20:23:44 -0400 Subject: [PATCH 09/11] Compile the PWA help and account settings through Herb Rails main compiles HTML templates through Herb under the 8.2 framework defaults, which Campfire loads. Herb rejects `case` and its first `when` in a single ERB tag, so the three pwa/ partials failed to compile, and `herb:check` rejects ERB output in attribute names, which the account settings' switch used for `checked`. Give `case` its own tag and build the switch with tag.input; both render the same under Erubi. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 244e77241bd2d9e973f5a8dcca0cf8230f81176a) --- app/views/accounts/edit.html.erb | 7 +++---- app/views/pwa/_browser_settings.html.erb | 4 ++-- app/views/pwa/_install_instructions.html.erb | 4 ++-- app/views/pwa/_system_settings.html.erb | 4 ++-- test/controllers/accounts_controller_test.rb | 10 ++++++++++ test/controllers/rooms_controller_test.rb | 15 +++++++++++++++ 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/app/views/accounts/edit.html.erb b/app/views/accounts/edit.html.erb index cfa750d..f2d1ae8 100644 --- a/app/views/accounts/edit.html.erb +++ b/app/views/accounts/edit.html.erb @@ -76,10 +76,9 @@ <% end %>