diff --git a/app/javascript/controllers/clicker_controller.js b/app/javascript/controllers/clicker_controller.js
index 6d35719425..70a7575a88 100644
--- a/app/javascript/controllers/clicker_controller.js
+++ b/app/javascript/controllers/clicker_controller.js
@@ -3,8 +3,12 @@ import { nextFrame } from "helpers/timing_helpers";
export default class extends Controller {
static targets = [ "clickable" ]
+ static outlets = [ "auto-save" ]
async click() {
+ if (this.hasAutoSaveOutlet) {
+ await this.autoSaveOutlet.submit()
+ }
await nextFrame()
this.#clickable.click()
}
diff --git a/app/views/cards/container/footer/_create.html.erb b/app/views/cards/container/footer/_create.html.erb
index 4e5a83e205..fae4e47ce4 100644
--- a/app/views/cards/container/footer/_create.html.erb
+++ b/app/views/cards/container/footer/_create.html.erb
@@ -3,13 +3,13 @@
<%= button_to card_publish_path(card), name: "creation_type", value: "add", class: "btn",
title: "Create card (#{ hotkey_label(["ctrl", "enter"]) })",
form: { data: { controller: "form bridge--form" } },
- data: { form_target: "submit", bridge__form_target: "submit", controller: "clicker", action: "keydown.ctrl+enter@document->clicker#click keydown.meta+enter@document->clicker#click" } do %>
+ data: { form_target: "submit", bridge__form_target: "submit", controller: "clicker", clicker_auto_save_outlet: "#card_form", action: "keydown.ctrl+enter@document->clicker#click keydown.meta+enter@document->clicker#click" } do %>
Create card
<% end %>
<%= button_to card_publish_path(card), method: :post, class: "btn btn--reversed", name: "creation_type", value: "add_another",
title: "Create and add another (#{ hotkey_label(["ctrl", "shift", "enter"]) })", form: { data: { controller: "form" } },
- data: { form_target: "submit", controller: "clicker", action: "keydown.ctrl+shift+enter@document->clicker#click keydown.meta+shift+enter@document->clicker#click" } do %>
+ data: { form_target: "submit", controller: "clicker", clicker_auto_save_outlet: "#card_form", action: "keydown.ctrl+shift+enter@document->clicker#click keydown.meta+shift+enter@document->clicker#click" } do %>
Create and add another
<% end %>
diff --git a/test/system/card_creation_race_test.rb b/test/system/card_creation_race_test.rb
new file mode 100644
index 0000000000..5dc5784955
--- /dev/null
+++ b/test/system/card_creation_race_test.rb
@@ -0,0 +1,47 @@
+require "application_system_test_case"
+
+class CardCreationRaceTest < ApplicationSystemTestCase
+ # Reproduces the race documented in #2778: when the user types a title in a
+ # new draft and immediately presses Cmd/Ctrl+Enter, the publish request can
+ # reach the server before the auto-save PATCH commits the title. The publish
+ # callback then writes "Untitled", which the late PATCH rewrites — generating
+ # a phantom `card_title_changed` event and a system comment as a side effect.
+ setup do
+ CardsController.class_eval do
+ alias_method :_update_without_test_delay, :update
+ def update
+ sleep 0.5
+ _update_without_test_delay
+ end
+ end
+ end
+
+ teardown do
+ CardsController.class_eval do
+ alias_method :update, :_update_without_test_delay
+ remove_method :_update_without_test_delay
+ end
+ end
+
+ test "Cmd+Enter on a new draft preserves the typed title without phantom events or system comments" do
+ sign_in_as(users(:david))
+
+ visit board_url(boards(:writebook))
+ click_on "Add a card"
+
+ title_field = find("textarea[name='card[title]']")
+ title_field.send_keys "Race fix verified"
+ title_field.send_keys [ :control, :enter ]
+
+ assert_current_path board_path(boards(:writebook))
+ assert_text "Race fix verified" # wait for late PATCH to commit before assertions
+
+ card = Card.where(creator: users(:david)).order(:created_at).last
+ assert_equal "Race fix verified", card.reload.title
+ assert card.published?, "card should be published"
+ refute Event.exists?(action: "card_title_changed", eventable: card),
+ "publish should not generate a phantom card_title_changed event"
+ refute card.comments.any? { |c| c.body.to_plain_text.include?("changed the title") },
+ "publish should not generate a phantom 'changed the title' system comment"
+ end
+end