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