<% default_org = default_organization_for_form(f.object) %>
From 337978eb3e260500c84c0a74b1d3527f4d49c1aa Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 15:38:02 +0000
Subject: [PATCH 4/7] Address code review feedback - improve validation and
readability
Co-authored-by: maebeale <7607813+maebeale@users.noreply.github.com>
---
.../javascript/controllers/workshop_toggle_controller.js | 4 ++--
app/helpers/application_helper.rb | 9 +++++++++
app/models/story_idea.rb | 9 +++++++++
app/views/story_ideas/_form.html.erb | 2 +-
4 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/app/frontend/javascript/controllers/workshop_toggle_controller.js b/app/frontend/javascript/controllers/workshop_toggle_controller.js
index 7e1d48428d..6a53755b2e 100644
--- a/app/frontend/javascript/controllers/workshop_toggle_controller.js
+++ b/app/frontend/javascript/controllers/workshop_toggle_controller.js
@@ -3,7 +3,7 @@ import { Controller } from "@hotwired/stimulus";
// Connects to data-controller="workshop-toggle"
// Handles toggling between workshop dropdown and external title field
export default class extends Controller {
- static targets = ["dropdown", "externalField", "closeButton"];
+ static targets = ["dropdown", "externalField"];
connect() {
this.checkInitialState();
@@ -31,7 +31,7 @@ export default class extends Controller {
showDropdown() {
this.externalFieldTarget.classList.add("hidden");
this.dropdownTarget.classList.remove("hidden");
-
+
// Clear the selection back to prompt
const select = this.dropdownTarget.querySelector("select");
if (select) {
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 5d84242875..87ade914ea 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -577,4 +577,13 @@ def us_time_zone_fundamentals
]
ActiveSupport::TimeZone.us_zones.select { |z| zone_names.include?(z.name) }.sort_by { |z| zone_names.index(z.name) }.map { |z| [ z.to_s, z.name ] }
end
+
+ def workshop_selected_value(story_idea, params)
+ # If editing and has external title but no workshop, don't select anything
+ return nil if story_idea.workshop_id.nil? && story_idea.external_workshop_title.present?
+
+ # Otherwise use param or object value
+ params[:workshop_id].presence || story_idea.workshop_id
+ end
end
+
diff --git a/app/models/story_idea.rb b/app/models/story_idea.rb
index 65155be0bb..d245f5bb3e 100644
--- a/app/models/story_idea.rb
+++ b/app/models/story_idea.rb
@@ -43,6 +43,7 @@ def self.search_by_params(params)
validates :permission_given, presence: true
validates :author_credit_preference, presence: true
validates :rhino_body, presence: true
+ validate :workshop_or_external_title_present
# Nested attributes
accepts_nested_attributes_for :primary_asset, allow_destroy: true, reject_if: :all_blank
@@ -73,4 +74,12 @@ def organization_locality
def organization_description
organization&.organization_description
end
+
+ private
+
+ def workshop_or_external_title_present
+ if workshop_id.blank? && external_workshop_title.blank?
+ errors.add(:base, "Please select a workshop or enter an external workshop title")
+ end
+ end
end
diff --git a/app/views/story_ideas/_form.html.erb b/app/views/story_ideas/_form.html.erb
index a63d438c14..71535d8895 100644
--- a/app/views/story_ideas/_form.html.erb
+++ b/app/views/story_ideas/_form.html.erb
@@ -67,7 +67,7 @@
prompt: "Select a Workshop",
required: false,
disabled: promoted_to_story,
- selected: (f.object.workshop_id.nil? && f.object.external_workshop_title.present? ? nil : (params[:workshop_id].presence || f.object.workshop_id)),
+ selected: workshop_selected_value(f.object, params),
input_html: { value: params[:workshop_id].presence || f.object.workshop_id,
class: "block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500
focus:border-blue-500 #{ 'readonly' if promoted_to_story }",
From 4b22be48c84ae3ad52f3531b4737d1c29dfd8e97 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 15:40:54 +0000
Subject: [PATCH 5/7] Add tests for workshop toggle functionality
Co-authored-by: maebeale <7607813+maebeale@users.noreply.github.com>
---
app/models/story_idea.rb | 1 +
spec/models/story_idea_spec.rb | 50 ++++++++++++++++++++++++++-----
spec/requests/story_ideas_spec.rb | 13 ++++++++
3 files changed, 56 insertions(+), 8 deletions(-)
diff --git a/app/models/story_idea.rb b/app/models/story_idea.rb
index d245f5bb3e..6befe6ac29 100644
--- a/app/models/story_idea.rb
+++ b/app/models/story_idea.rb
@@ -83,3 +83,4 @@ def workshop_or_external_title_present
end
end
end
+
diff --git a/spec/models/story_idea_spec.rb b/spec/models/story_idea_spec.rb
index 663e12a8a5..fb6d7982a3 100644
--- a/spec/models/story_idea_spec.rb
+++ b/spec/models/story_idea_spec.rb
@@ -3,6 +3,40 @@
RSpec.describe StoryIdea, type: :model do
it_behaves_like "author_creditable", factory: :story_idea
+ describe "validations" do
+ context "workshop selection" do
+ it "is valid with a workshop_id" do
+ story_idea = create(:story_idea, external_workshop_title: nil)
+ expect(story_idea).to be_persisted
+ expect(story_idea.workshop).to be_present
+ end
+
+ it "is valid with external_workshop_title and no workshop" do
+ story_idea = create(:story_idea,
+ workshop: nil,
+ external_workshop_title: "My External Workshop")
+ expect(story_idea).to be_persisted
+ expect(story_idea.external_workshop_title).to eq("My External Workshop")
+ end
+
+ it "is invalid without workshop_id or external_workshop_title" do
+ story_idea = build(:story_idea,
+ workshop: nil,
+ external_workshop_title: nil)
+ expect(story_idea).not_to be_valid
+ expect(story_idea.errors[:base]).to include("Please select a workshop or enter an external workshop title")
+ end
+
+ it "is valid with both workshop_id and external_workshop_title" do
+ story_idea = create(:story_idea,
+ external_workshop_title: "My External Workshop")
+ expect(story_idea).to be_persisted
+ expect(story_idea.workshop).to be_present
+ expect(story_idea.external_workshop_title).to eq("My External Workshop")
+ end
+ end
+ end
+
describe "#workshop_title" do
it "returns workshop title when only workshop is present" do
workshop = create(:workshop, title: "Healing Art")
@@ -46,23 +80,23 @@
end
end
- describe '.search_by_params' do
- let!(:idea_alpha) { create(:story_idea, title: 'Art Healing Journey') }
- let!(:idea_beta) { create(:story_idea, title: 'Community Impact Report') }
+ describe ".search_by_params" do
+ let!(:idea_alpha) { create(:story_idea, title: "Art Healing Journey") }
+ let!(:idea_beta) { create(:story_idea, title: "Community Impact Report") }
- it 'returns all when no params' do
+ it "returns all when no params" do
results = StoryIdea.search_by_params({})
expect(results).to include(idea_alpha, idea_beta)
end
- it 'filters by query matching title' do
- results = StoryIdea.search_by_params(query: 'Art Healing')
+ it "filters by query matching title" do
+ results = StoryIdea.search_by_params(query: "Art Healing")
expect(results).to include(idea_alpha)
expect(results).not_to include(idea_beta)
end
- it 'returns empty for non-matching query' do
- results = StoryIdea.search_by_params(query: 'nonexistent')
+ it "returns empty for non-matching query" do
+ results = StoryIdea.search_by_params(query: "nonexistent")
expect(results).not_to include(idea_alpha, idea_beta)
end
diff --git a/spec/requests/story_ideas_spec.rb b/spec/requests/story_ideas_spec.rb
index 9106dfaa7c..66eb9a09a7 100644
--- a/spec/requests/story_ideas_spec.rb
+++ b/spec/requests/story_ideas_spec.rb
@@ -139,6 +139,19 @@
}.to change(StoryIdea, :count).by(1)
end
+ it "creates a StoryIdea with external_workshop_title when workshop_id is 'new'" do
+ attrs = valid_attributes.merge(
+ workshop_id: "new",
+ external_workshop_title: "My Custom Workshop"
+ )
+
+ post story_ideas_url, params: { story_idea: attrs }
+
+ story_idea = StoryIdea.last
+ expect(story_idea.workshop_id).to be_nil
+ expect(story_idea.external_workshop_title).to eq("My Custom Workshop")
+ end
+
it "redirects to show after create" do
post story_ideas_url, params: { story_idea: valid_attributes }
expect(response).to redirect_to(story_idea_url(StoryIdea.last))
From 28892d00a8695ddbcbaa61c50d0f3f98de7008cf Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 15:43:30 +0000
Subject: [PATCH 6/7] Address final code review feedback
Co-authored-by: maebeale <7607813+maebeale@users.noreply.github.com>
---
.../javascript/controllers/workshop_toggle_controller.js | 6 ++++++
app/helpers/application_helper.rb | 4 ++++
app/views/story_ideas/_form.html.erb | 2 +-
3 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/app/frontend/javascript/controllers/workshop_toggle_controller.js b/app/frontend/javascript/controllers/workshop_toggle_controller.js
index 6a53755b2e..a20f274731 100644
--- a/app/frontend/javascript/controllers/workshop_toggle_controller.js
+++ b/app/frontend/javascript/controllers/workshop_toggle_controller.js
@@ -37,5 +37,11 @@ export default class extends Controller {
if (select) {
select.value = "";
}
+
+ // Clear the external workshop title field
+ const externalField = this.externalFieldTarget.querySelector("input, textarea");
+ if (externalField) {
+ externalField.value = "";
+ }
}
}
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 87ade914ea..1fc826e0f1 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -585,5 +585,9 @@ def workshop_selected_value(story_idea, params)
# Otherwise use param or object value
params[:workshop_id].presence || story_idea.workshop_id
end
+
+ def show_external_workshop_field?(story_idea)
+ story_idea.workshop_id.nil? && story_idea.external_workshop_title.present?
+ end
end
diff --git a/app/views/story_ideas/_form.html.erb b/app/views/story_ideas/_form.html.erb
index 71535d8895..0e0ff4b513 100644
--- a/app/views/story_ideas/_form.html.erb
+++ b/app/views/story_ideas/_form.html.erb
@@ -74,7 +74,7 @@
data: { action: "change->workshop-toggle#handleChange" } },
label_html: { class: "block text-sm font-medium text-gray-700 mb-1" } %>
-
+
<%= f.input :external_workshop_title,
From e5f1c77d135f5741cfff2f703387305d6d482529 Mon Sep 17 00:00:00 2001
From: maebeale
Date: Sun, 8 Mar 2026 06:40:59 -0400
Subject: [PATCH 7/7] Fix rebase conflicts and test failures for workshop
toggle UX
Resolve trailing whitespace from merge, remove duplicate closing tags
in form view, and update tests to use build instead of create where
workshop validation now prevents nil workshop + nil external title.
Co-Authored-By: Claude Opus 4.6
---
app/helpers/application_helper.rb | 1 -
app/models/story_idea.rb | 1 -
app/views/story_ideas/_form.html.erb | 4 ----
spec/models/story_idea_spec.rb | 8 +++++---
4 files changed, 5 insertions(+), 9 deletions(-)
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 1fc826e0f1..b49a8f8932 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -590,4 +590,3 @@ def show_external_workshop_field?(story_idea)
story_idea.workshop_id.nil? && story_idea.external_workshop_title.present?
end
end
-
diff --git a/app/models/story_idea.rb b/app/models/story_idea.rb
index 6befe6ac29..d245f5bb3e 100644
--- a/app/models/story_idea.rb
+++ b/app/models/story_idea.rb
@@ -83,4 +83,3 @@ def workshop_or_external_title_present
end
end
end
-
diff --git a/app/views/story_ideas/_form.html.erb b/app/views/story_ideas/_form.html.erb
index 0e0ff4b513..98122e9671 100644
--- a/app/views/story_ideas/_form.html.erb
+++ b/app/views/story_ideas/_form.html.erb
@@ -99,10 +99,6 @@
<% end %>
-
- <% end %>
-
-
<% default_org = default_organization_for_form(f.object) %>
diff --git a/spec/models/story_idea_spec.rb b/spec/models/story_idea_spec.rb
index fb6d7982a3..f0b08737ac 100644
--- a/spec/models/story_idea_spec.rb
+++ b/spec/models/story_idea_spec.rb
@@ -56,12 +56,12 @@
end
it "returns nil when both workshop and external_workshop_title are absent" do
- idea = create(:story_idea, workshop: nil, external_workshop_title: nil)
+ idea = build(:story_idea, workshop: nil, external_workshop_title: nil)
expect(idea.workshop_title).to be_nil
end
it "returns nil when external_workshop_title is blank" do
- idea = create(:story_idea, workshop: nil, external_workshop_title: "")
+ idea = build(:story_idea, workshop: nil, external_workshop_title: "")
expect(idea.workshop_title).to be_nil
end
end
@@ -74,7 +74,9 @@
end
it "omits workshop section when no workshop or external title" do
- idea = create(:story_idea, workshop: nil, external_workshop_title: nil)
+ idea = create(:story_idea, workshop: nil, external_workshop_title: "Temp")
+ idea.update_column(:external_workshop_title, nil)
+ idea.reload
expect(idea.full_name).not_to include(":")
expect(idea.full_name).to include(idea.author_credit)
end