From 5796a76c7995e664f79c581f72e56fa6d7f468ae Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 15:25:51 +0000
Subject: [PATCH 1/6] Initial plan
From 8a4d4fedda5d9347947f5bddb2906a7c3ca05c94 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 15:29:45 +0000
Subject: [PATCH 2/6] Add Workshop toggle UX with New Workshop option
Co-authored-by: maebeale <7607813+maebeale@users.noreply.github.com>
---
app/controllers/story_ideas_controller.rb | 13 +++-
.../controllers/workshop_toggle_controller.js | 41 ++++++++++++
app/views/story_ideas/_form.html.erb | 65 ++++++++++++-------
3 files changed, 95 insertions(+), 24 deletions(-)
create mode 100644 app/frontend/javascript/controllers/workshop_toggle_controller.js
diff --git a/app/controllers/story_ideas_controller.rb b/app/controllers/story_ideas_controller.rb
index a99572a7c..0d87d8e06 100644
--- a/app/controllers/story_ideas_controller.rb
+++ b/app/controllers/story_ideas_controller.rb
@@ -110,7 +110,9 @@ def set_form_variables
@organizations = (@user || current_user)&.organizations&.order(:name) || Organization.none
@windows_types = WindowsType.all
- @workshops = authorized_scope(Workshop.all).includes(:windows_type).order(:title)
+ # Create a special "New Workshop" option
+ new_workshop_option = OpenStruct.new(id: "new", type_name: "New Workshop")
+ @workshops = [ new_workshop_option ] + authorized_scope(Workshop.all).includes(:windows_type).order(:title).to_a
users = authorized_scope(User.has_access.includes(:person))
users = users.or(User.where(id: @story_idea.created_by_id)) if @story_idea&.created_by_id
@@ -156,7 +158,7 @@ def set_story_idea
end
def story_idea_params
- params.require(:story_idea).permit(
+ permitted_params = params.require(:story_idea).permit(
:title, :body, :youtube_url,
:permission_given, :publish_preferences, :promoted_to_story,
:windows_type_id, :organization_id, :workshop_id, :external_workshop_title,
@@ -166,5 +168,12 @@ def story_idea_params
primary_asset_attributes: [ :id, :file, :_destroy ],
gallery_assets_attributes: [ :id, :file, :_destroy ]
)
+
+ # Clear workshop_id if "new" was selected (triggers external_workshop_title)
+ if permitted_params[:workshop_id] == "new"
+ permitted_params[:workshop_id] = nil
+ end
+
+ permitted_params
end
end
diff --git a/app/frontend/javascript/controllers/workshop_toggle_controller.js b/app/frontend/javascript/controllers/workshop_toggle_controller.js
new file mode 100644
index 000000000..7e1d48428
--- /dev/null
+++ b/app/frontend/javascript/controllers/workshop_toggle_controller.js
@@ -0,0 +1,41 @@
+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"];
+
+ connect() {
+ this.checkInitialState();
+ }
+
+ checkInitialState() {
+ const select = this.dropdownTarget.querySelector("select");
+ if (select && select.value === "new") {
+ this.showExternalField();
+ }
+ }
+
+ handleChange(event) {
+ const value = event.target.value;
+ if (value === "new") {
+ this.showExternalField();
+ }
+ }
+
+ showExternalField() {
+ this.dropdownTarget.classList.add("hidden");
+ this.externalFieldTarget.classList.remove("hidden");
+ }
+
+ 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) {
+ select.value = "";
+ }
+ }
+}
diff --git a/app/views/story_ideas/_form.html.erb b/app/views/story_ideas/_form.html.erb
index 7ad4e4a4e..f088f8163 100644
--- a/app/views/story_ideas/_form.html.erb
+++ b/app/views/story_ideas/_form.html.erb
@@ -51,28 +51,49 @@
class: ("readonly" if promoted_to_story)
} %>
-
- <%= f.input :workshop_id,
- as: :select,
- collection: @workshops,
- label_method: :type_name,
- value_method: :id,
- label: "Workshop",
- prompt: "Select a Workshop",
- required: true,
- disabled: promoted_to_story,
- selected: params[:workshop_id].presence || f.object.workshop_id,
- 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 }" },
- label_html: { class: "block text-sm font-medium text-gray-700 mb-1" } %>
- <%= f.input :external_workshop_title,
- as: :text,
- label: "External title (if no workshop above)",
- input_html: {
- rows: 1,
- class: ("readonly" if promoted_to_story)
- } %>
+
+
+ <%= f.input :workshop_id,
+ as: :select,
+ collection: @workshops,
+ label_method: :type_name,
+ value_method: :id,
+ label: "Workshop",
+ prompt: "Select a Workshop",
+ required: false,
+ disabled: promoted_to_story,
+ selected: (params[:workshop_id].presence || f.object.workshop_id) unless f.object.workshop_id.nil? && f.object.external_workshop_title.present?,
+ 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 }",
+ data: { action: "change->workshop-toggle#handleChange" } },
+ label_html: { class: "block text-sm font-medium text-gray-700 mb-1" } %>
+
+
+
+
+ <%= f.input :external_workshop_title,
+ as: :text,
+ label: "Workshop Title",
+ wrapper: false,
+ input_html: {
+ rows: 1,
+ class: ("readonly" if promoted_to_story)
+ },
+ label_html: { class: "block text-sm font-medium text-gray-700 mb-1" } %>
+
+ <% unless promoted_to_story %>
+
+ <% end %>
+
+
<% if current_user.person&.affiliations&.count != 1 %>
From a81489885c1646e71766cda331e85e0f7a7586c7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 15:33:17 +0000
Subject: [PATCH 3/6] Fix syntax errors and rubocop offenses
Co-authored-by: maebeale <7607813+maebeale@users.noreply.github.com>
---
app/controllers/story_ideas_controller.rb | 4 ++--
app/views/story_ideas/_form.html.erb | 6 +++++-
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/app/controllers/story_ideas_controller.rb b/app/controllers/story_ideas_controller.rb
index 0d87d8e06..d5be5f52a 100644
--- a/app/controllers/story_ideas_controller.rb
+++ b/app/controllers/story_ideas_controller.rb
@@ -168,12 +168,12 @@ def story_idea_params
primary_asset_attributes: [ :id, :file, :_destroy ],
gallery_assets_attributes: [ :id, :file, :_destroy ]
)
-
+
# Clear workshop_id if "new" was selected (triggers external_workshop_title)
if permitted_params[:workshop_id] == "new"
permitted_params[:workshop_id] = nil
end
-
+
permitted_params
end
end
diff --git a/app/views/story_ideas/_form.html.erb b/app/views/story_ideas/_form.html.erb
index f088f8163..a2a3df78d 100644
--- a/app/views/story_ideas/_form.html.erb
+++ b/app/views/story_ideas/_form.html.erb
@@ -62,7 +62,7 @@
prompt: "Select a Workshop",
required: false,
disabled: promoted_to_story,
- selected: (params[:workshop_id].presence || f.object.workshop_id) unless f.object.workshop_id.nil? && f.object.external_workshop_title.present?,
+ selected: (f.object.workshop_id.nil? && f.object.external_workshop_title.present? ? nil : (params[:workshop_id].presence || f.object.workshop_id)),
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 }",
@@ -94,6 +94,10 @@
<% end %>
+
+ <% end %>
+
+
<% if current_user.person&.affiliations&.count != 1 %>
From d9129ff197c82b6e1c22ac91a2a18040accab482 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/6] 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 | 4 ++--
4 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/app/frontend/javascript/controllers/workshop_toggle_controller.js b/app/frontend/javascript/controllers/workshop_toggle_controller.js
index 7e1d48428..6a53755b2 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 4e9f8c3f5..e3171fa99 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -169,4 +169,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 c0124a2be..70ea386d8 100644
--- a/app/models/story_idea.rb
+++ b/app/models/story_idea.rb
@@ -44,6 +44,15 @@ def self.search_by_params(params)
validates :body, presence: true
validates :permission_given, presence: true
validates :publish_preferences, presence: true
+ validate :workshop_or_external_title_present
+
+ 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
# Nested attributes
accepts_nested_attributes_for :primary_asset, allow_destroy: true, reject_if: :all_blank
diff --git a/app/views/story_ideas/_form.html.erb b/app/views/story_ideas/_form.html.erb
index a2a3df78d..711d23ea9 100644
--- a/app/views/story_ideas/_form.html.erb
+++ b/app/views/story_ideas/_form.html.erb
@@ -62,7 +62,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 }",
@@ -83,7 +83,7 @@
label_html: { class: "block text-sm font-medium text-gray-700 mb-1" } %>
<% unless promoted_to_story %>
-