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 %> -