diff --git a/Dockerfile b/Dockerfile
index 6341f3fc1..ea1e28f8b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -59,9 +59,6 @@ RUN useradd rails --create-home --shell /bin/bash && \
chown -R rails:rails db log storage tmp
USER rails:rails
-# Entrypoint prepares the database.
-ENTRYPOINT ["/rails/bin/docker-entrypoint"]
-
# Ruby GC tuning values pulled from Autotuner recommendations
ENV RUBY_GC_HEAP_0_INIT_SLOTS=692636 \
RUBY_GC_HEAP_1_INIT_SLOTS=175943 \
diff --git a/app/javascript/controllers/turbo_navigation_controller.js b/app/javascript/controllers/turbo_navigation_controller.js
index 4c5823620..d755342e9 100644
--- a/app/javascript/controllers/turbo_navigation_controller.js
+++ b/app/javascript/controllers/turbo_navigation_controller.js
@@ -6,6 +6,8 @@ export default class extends Controller {
}
backIfSamePath(event) {
+ if (event.ctrlKey || event.metaKey || event.shiftKey) { return }
+
const link = event.target.closest("a")
const targetUrl = new URL(link.href)
diff --git a/app/models/concerns/searchable.rb b/app/models/concerns/searchable.rb
index 39e5d95f8..ebb1d7bff 100644
--- a/app/models/concerns/searchable.rb
+++ b/app/models/concerns/searchable.rb
@@ -33,6 +33,7 @@ module Searchable
def search_record_attributes
{
account_id: account_id,
+ account_key: "account#{account_id}",
searchable_type: self.class.name,
searchable_id: id,
card_id: search_card_id,
diff --git a/app/models/search/record.rb b/app/models/search/record.rb
index 8b54a7c33..dc53d3095 100644
--- a/app/models/search/record.rb
+++ b/app/models/search/record.rb
@@ -17,7 +17,7 @@ class Search::Record < ApplicationRecord
scope :for_query, ->(query:, user:) do
if query.valid? && user.board_ids.any?
- matching(query.to_s).for_user(user)
+ matching(query.to_s, user.account_id).for_user(user)
else
none
end
diff --git a/app/views/cards/container/_title.html.erb b/app/views/cards/container/_title.html.erb
index 47a6a0924..a7176ecb2 100644
--- a/app/views/cards/container/_title.html.erb
+++ b/app/views/cards/container/_title.html.erb
@@ -23,7 +23,7 @@
<%= form.label :title, class: "flex flex-column align-center autoresize__wrapper", data: { autoresize_target: "wrapper", autoresize_clone_value: "" } do %>
<%= form.text_area :title, placeholder: "Name it…",
class: "card-field__title autoresize__textarea input input--textarea full-width borderless txt-align-start hide-focus-ring hide-scrollbar",
- autofocus: card.title.blank?, rows: 1,
+ autofocus: card.title.blank?, rows: 1, dir: "auto",
data: { autoresize_target: "textarea", action: "input->autoresize#resize auto-save#change blur->auto-save#submit keydown.enter->auto-save#submit:prevent" } %>
<% end %>
diff --git a/app/views/cards/edit.html.erb b/app/views/cards/edit.html.erb
index e243fb2b4..0f33ab9c3 100644
--- a/app/views/cards/edit.html.erb
+++ b/app/views/cards/edit.html.erb
@@ -4,7 +4,7 @@
<%= form.label :title, class: "flex flex-column align-center autoresize__wrapper", data: { autoresize_target: "wrapper", autoresize_clone_value: "" } do %>
<%= form.text_area :title, class: "card-field__title autoresize__textarea input input--textarea full-width borderless txt-align-start hide-focus-ring hide-scrollbar",
- required: true, autofocus: true, placeholder: "Name it…", rows: 1,
+ required: true, autofocus: true, placeholder: "Name it…", rows: 1, dir: "auto",
data: { autoresize_target: "textarea", action: "input->autoresize#resize keydown.enter->form#submit:prevent keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel focus->form#select" } %>
<% end %>
diff --git a/db/migrate/20251121092508_add_account_key_to_search_records.rb b/db/migrate/20251121092508_add_account_key_to_search_records.rb
new file mode 100644
index 000000000..8960f56bc
--- /dev/null
+++ b/db/migrate/20251121092508_add_account_key_to_search_records.rb
@@ -0,0 +1,19 @@
+class AddAccountKeyToSearchRecords < ActiveRecord::Migration[8.2]
+ def up
+ 16.times do |shard_id|
+ table_name = "search_records_#{shard_id}"
+
+ add_column table_name, :account_key, :string, null: false, default: ""
+ add_index table_name, [:account_key, :content, :title], type: :fulltext
+ end
+ end
+
+ def down
+ 16.times do |shard_id|
+ table_name = "search_records_#{shard_id}"
+
+ remove_index table_name, column: [:account_key, :content, :title], type: :fulltext
+ remove_column table_name, :account_key
+ end
+ end
+end
diff --git a/db/migrate/20251121112416_remove_old_fulltext_indexes_from_search_records.rb b/db/migrate/20251121112416_remove_old_fulltext_indexes_from_search_records.rb
new file mode 100644
index 000000000..f9d38dadf
--- /dev/null
+++ b/db/migrate/20251121112416_remove_old_fulltext_indexes_from_search_records.rb
@@ -0,0 +1,16 @@
+class RemoveOldFulltextIndexesFromSearchRecords < ActiveRecord::Migration[8.2]
+ def up
+ # Remove the old fulltext indexes (content, title) from all 16 search_records shards
+ # We're keeping the new indexes (account_key, content, title) for tenant-aware searching
+ (0..15).each do |shard|
+ remove_index "search_records_#{shard}", name: "index_search_records_#{shard}_on_content_and_title"
+ end
+ end
+
+ def down
+ # Re-create the old fulltext indexes in case we need to rollback
+ (0..15).each do |shard|
+ add_index "search_records_#{shard}", [ :content, :title ], type: :fulltext, name: "index_search_records_#{shard}_on_content_and_title"
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 7130719ae..54bedccb4 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
+ActiveRecord::Schema[8.2].define(version: 2025_11_21_112416) do
create_table "accesses", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "accessed_at"
t.uuid "account_id", null: false
@@ -425,6 +425,7 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
create_table "search_records_0", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -433,12 +434,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_0_on_account_id"
- t.index ["content", "title"], name: "index_search_records_0_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_0_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_0_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_1", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -447,12 +449,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_1_on_account_id"
- t.index ["content", "title"], name: "index_search_records_1_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_1_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_1_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_10", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -461,12 +464,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_10_on_account_id"
- t.index ["content", "title"], name: "index_search_records_10_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_10_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_10_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_11", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -475,12 +479,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_11_on_account_id"
- t.index ["content", "title"], name: "index_search_records_11_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_11_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_11_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_12", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -489,12 +494,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_12_on_account_id"
- t.index ["content", "title"], name: "index_search_records_12_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_12_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_12_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_13", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -503,12 +509,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_13_on_account_id"
- t.index ["content", "title"], name: "index_search_records_13_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_13_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_13_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_14", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -517,12 +524,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_14_on_account_id"
- t.index ["content", "title"], name: "index_search_records_14_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_14_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_14_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_15", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -531,12 +539,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_15_on_account_id"
- t.index ["content", "title"], name: "index_search_records_15_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_15_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_15_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_2", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -545,12 +554,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_2_on_account_id"
- t.index ["content", "title"], name: "index_search_records_2_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_2_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_2_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_3", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -559,12 +569,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_3_on_account_id"
- t.index ["content", "title"], name: "index_search_records_3_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_3_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_3_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_4", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -573,12 +584,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_4_on_account_id"
- t.index ["content", "title"], name: "index_search_records_4_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_4_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_4_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_5", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -587,12 +599,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_5_on_account_id"
- t.index ["content", "title"], name: "index_search_records_5_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_5_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_5_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_6", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -601,12 +614,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_6_on_account_id"
- t.index ["content", "title"], name: "index_search_records_6_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_6_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_6_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_7", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -615,12 +629,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_7_on_account_id"
- t.index ["content", "title"], name: "index_search_records_7_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_7_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_7_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_8", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -629,12 +644,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_8_on_account_id"
- t.index ["content", "title"], name: "index_search_records_8_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_8_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_8_on_searchable_type_and_searchable_id", unique: true
end
create_table "search_records_9", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.uuid "account_id", null: false
+ t.string "account_key", default: "", null: false
t.uuid "board_id", null: false
t.uuid "card_id", null: false
t.text "content"
@@ -643,7 +659,7 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_20_203100) do
t.string "searchable_type", null: false
t.string "title"
t.index ["account_id"], name: "index_search_records_9_on_account_id"
- t.index ["content", "title"], name: "index_search_records_9_on_content_and_title", type: :fulltext
+ t.index ["account_key", "content", "title"], name: "index_search_records_9_on_account_key_and_content_and_title", type: :fulltext
t.index ["searchable_type", "searchable_id"], name: "index_search_records_9_on_searchable_type_and_searchable_id", unique: true
end