From fbeb1ae993451fecc2bcbcb6cb1ee6ed65ae106d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 5 Oct 2026 12:35:35 +0000 Subject: [PATCH 1/3] Query sidebar directs and shared rooms separately Load visible direct memberships ordered by room recency and other rooms ordered by name, instead of hydrating every membership and splitting them in Ruby. Fixes #307. Co-authored-by: Thomas Klemm --- app/controllers/users/sidebars_controller.rb | 5 +++-- app/models/membership.rb | 1 + .../users/sidebars_controller_test.rb | 21 +++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/app/controllers/users/sidebars_controller.rb b/app/controllers/users/sidebars_controller.rb index ce998e8..4943a34 100644 --- a/app/controllers/users/sidebars_controller.rb +++ b/app/controllers/users/sidebars_controller.rb @@ -2,8 +2,9 @@ class Users::SidebarsController < ApplicationController DIRECT_PLACEHOLDERS = 20 def show - @direct_memberships, @other_memberships = Current.user.memberships.visible.with_ordered_room.partition { |membership| membership.room.direct? } - @direct_memberships = @direct_memberships.sort_by { |membership| membership.room.updated_at }.reverse + visible_memberships = Current.user.memberships.visible + @direct_memberships = visible_memberships.with_direct_rooms + @other_memberships = visible_memberships.without_direct_rooms.with_ordered_room @direct_placeholder_users = find_direct_placeholder_users end diff --git a/app/models/membership.rb b/app/models/membership.rb index 74c225b..da7a0a6 100644 --- a/app/models/membership.rb +++ b/app/models/membership.rb @@ -9,6 +9,7 @@ class Membership < ApplicationRecord enum :involvement, %w[ invisible nothing mentions everything ].index_by(&:itself), prefix: :involved_in scope :with_ordered_room, -> { includes(:room).joins(:room).order("LOWER(rooms.name)") } + scope :with_direct_rooms, -> { includes(:room).joins(:room).where(rooms: { type: "Rooms::Direct" }).order(rooms: { updated_at: :desc }) } scope :without_direct_rooms, -> { joins(:room).where.not(room: { type: "Rooms::Direct" }) } scope :visible, -> { where.not(involvement: :invisible) } diff --git a/test/controllers/users/sidebars_controller_test.rb b/test/controllers/users/sidebars_controller_test.rb index 50caae7..5ed957a 100644 --- a/test/controllers/users/sidebars_controller_test.rb +++ b/test/controllers/users/sidebars_controller_test.rb @@ -27,4 +27,25 @@ class Users::SidebarsControllerTest < ActionDispatch::IntegrationTest get user_sidebar_url assert_select ".unread", count: users(:david).memberships.reject { |m| m.room.direct? || !m.unread? }.count end + + test "directs are queried separately and ordered by room recency, not name" do + older = rooms(:david_and_jason) + newer = rooms(:david_and_kevin) + older.update_column :updated_at, 2.days.ago + newer.update_column :updated_at, 1.minute.ago + + get user_sidebar_url + + assert_operator @response.body.index(dom_id(newer, :list)), :<, @response.body.index(dom_id(older, :list)) + end + + test "shared rooms stay ordered by name" do + get user_sidebar_url + + shared_ids = users(:david).rooms.without_directs.sort_by { |room| room.name.downcase }.map { |room| dom_id(room, :list) } + positions = shared_ids.map { |id| @response.body.index(id) } + + assert positions.all? + assert_equal positions.sort, positions + end end From 88fbeedc759ea64274076f79d427aeae19abca1a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 5 Oct 2026 12:36:42 +0000 Subject: [PATCH 2/3] Avoid a double join alias when loading shared sidebar rooms Chaining without_direct_rooms with with_ordered_room joined rooms as `room` while still ordering on `rooms.name`. Load shared rooms in one scope instead. Co-authored-by: Thomas Klemm --- app/controllers/users/sidebars_controller.rb | 2 +- app/models/membership.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/users/sidebars_controller.rb b/app/controllers/users/sidebars_controller.rb index 4943a34..26eedf7 100644 --- a/app/controllers/users/sidebars_controller.rb +++ b/app/controllers/users/sidebars_controller.rb @@ -4,7 +4,7 @@ class Users::SidebarsController < ApplicationController def show visible_memberships = Current.user.memberships.visible @direct_memberships = visible_memberships.with_direct_rooms - @other_memberships = visible_memberships.without_direct_rooms.with_ordered_room + @other_memberships = visible_memberships.with_ordered_shared_rooms @direct_placeholder_users = find_direct_placeholder_users end diff --git a/app/models/membership.rb b/app/models/membership.rb index da7a0a6..f029193 100644 --- a/app/models/membership.rb +++ b/app/models/membership.rb @@ -10,6 +10,7 @@ class Membership < ApplicationRecord scope :with_ordered_room, -> { includes(:room).joins(:room).order("LOWER(rooms.name)") } scope :with_direct_rooms, -> { includes(:room).joins(:room).where(rooms: { type: "Rooms::Direct" }).order(rooms: { updated_at: :desc }) } + scope :with_ordered_shared_rooms, -> { includes(:room).joins(:room).where.not(rooms: { type: "Rooms::Direct" }).order("LOWER(rooms.name)") } scope :without_direct_rooms, -> { joins(:room).where.not(room: { type: "Rooms::Direct" }) } scope :visible, -> { where.not(involvement: :invisible) } From d6b3dfd64d0752243e341c62f4538e28e34d2e50 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 5 Oct 2026 12:48:29 +0000 Subject: [PATCH 3/3] Compose sidebar lists from existing room scopes Directs merge Room.directs and order by recency. Shared rooms reuse with_ordered_room and without_direct_rooms, with the STI filter coming from Room.without_directs so the join alias stays rooms. Co-authored-by: Thomas Klemm --- app/controllers/users/sidebars_controller.rb | 2 +- app/models/membership.rb | 5 ++--- test/controllers/users/sidebars_controller_test.rb | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/controllers/users/sidebars_controller.rb b/app/controllers/users/sidebars_controller.rb index 26eedf7..c14f121 100644 --- a/app/controllers/users/sidebars_controller.rb +++ b/app/controllers/users/sidebars_controller.rb @@ -4,7 +4,7 @@ class Users::SidebarsController < ApplicationController def show visible_memberships = Current.user.memberships.visible @direct_memberships = visible_memberships.with_direct_rooms - @other_memberships = visible_memberships.with_ordered_shared_rooms + @other_memberships = visible_memberships.with_ordered_room.without_direct_rooms @direct_placeholder_users = find_direct_placeholder_users end diff --git a/app/models/membership.rb b/app/models/membership.rb index f029193..9bf7763 100644 --- a/app/models/membership.rb +++ b/app/models/membership.rb @@ -9,9 +9,8 @@ class Membership < ApplicationRecord enum :involvement, %w[ invisible nothing mentions everything ].index_by(&:itself), prefix: :involved_in scope :with_ordered_room, -> { includes(:room).joins(:room).order("LOWER(rooms.name)") } - scope :with_direct_rooms, -> { includes(:room).joins(:room).where(rooms: { type: "Rooms::Direct" }).order(rooms: { updated_at: :desc }) } - scope :with_ordered_shared_rooms, -> { includes(:room).joins(:room).where.not(rooms: { type: "Rooms::Direct" }).order("LOWER(rooms.name)") } - scope :without_direct_rooms, -> { joins(:room).where.not(room: { type: "Rooms::Direct" }) } + scope :with_direct_rooms, -> { includes(:room).joins(:room).merge(Room.directs).order(rooms: { updated_at: :desc }) } + scope :without_direct_rooms, -> { joins(:room).merge(Room.without_directs) } scope :visible, -> { where.not(involvement: :invisible) } scope :unread, -> { where.not(unread_at: nil) } diff --git a/test/controllers/users/sidebars_controller_test.rb b/test/controllers/users/sidebars_controller_test.rb index 5ed957a..c563541 100644 --- a/test/controllers/users/sidebars_controller_test.rb +++ b/test/controllers/users/sidebars_controller_test.rb @@ -28,7 +28,7 @@ class Users::SidebarsControllerTest < ActionDispatch::IntegrationTest assert_select ".unread", count: users(:david).memberships.reject { |m| m.room.direct? || !m.unread? }.count end - test "directs are queried separately and ordered by room recency, not name" do + test "directs are ordered by room recency, not name" do older = rooms(:david_and_jason) newer = rooms(:david_and_kevin) older.update_column :updated_at, 2.days.ago