From 1a2cf29176118e602efdad354c70f5aad77dd0d3 Mon Sep 17 00:00:00 2001 From: maebeale Date: Tue, 3 Mar 2026 10:38:05 -0500 Subject: [PATCH 01/14] Improve variation and variation idea forms and edit pages - Add windows_type_id column to workshop_variations table - Remove delegate :windows_type from WorkshopVariation (uses own column now) - Add windows type dropdown to variation form next to name field - Permit windows_type_id in workshop_variations controller - Move windows type to same row as name in variation idea form - Hide workshop dropdown unless params[:admin]=true on both forms - Show variation/idea name and parent workshop on edit page headings - Copy windows_type_id and organization_id when promoting idea to variation Co-Authored-By: Claude Opus 4.6 --- .../workshop_variations_controller.rb | 3 +- app/models/workshop_variation.rb | 2 - .../workshop_variation_from_idea_service.rb | 2 +- .../workshop_variation_ideas/_form.html.erb | 49 ++++++++++--------- .../workshop_variation_ideas/edit.html.erb | 5 +- app/views/workshop_variations/_form.html.erb | 8 ++- app/views/workshop_variations/edit.html.erb | 5 +- ..._windows_type_id_to_workshop_variations.rb | 5 ++ db/schema.rb | 5 +- 9 files changed, 50 insertions(+), 34 deletions(-) create mode 100644 db/migrate/20260303180000_add_windows_type_id_to_workshop_variations.rb diff --git a/app/controllers/workshop_variations_controller.rb b/app/controllers/workshop_variations_controller.rb index 3c55fa029..3ee5379da 100644 --- a/app/controllers/workshop_variations_controller.rb +++ b/app/controllers/workshop_variations_controller.rb @@ -111,7 +111,8 @@ def set_form_variables def workshop_variation_params params.require(:workshop_variation).permit( [ :name, :rhino_body, :published, :publicly_visible, :position, :youtube_url, :created_by_id, - :organization_id, :workshop_id, :workshop_variation_idea_id, :author_credit_preference + :organization_id, :workshop_id, :workshop_variation_idea_id, :author_credit_preference, + :windows_type_id ] ) end diff --git a/app/models/workshop_variation.rb b/app/models/workshop_variation.rb index 5cc80e187..c3e83d2e4 100644 --- a/app/models/workshop_variation.rb +++ b/app/models/workshop_variation.rb @@ -42,8 +42,6 @@ def self.search_by_params(params) accepts_nested_attributes_for :primary_asset, allow_destroy: true, reject_if: :all_blank accepts_nested_attributes_for :gallery_assets, allow_destroy: true, reject_if: :all_blank - delegate :windows_type, to: :workshop - # Scopes # See Publishable, Trendable diff --git a/app/services/workshop_variation_from_idea_service.rb b/app/services/workshop_variation_from_idea_service.rb index b84720353..52abdf576 100644 --- a/app/services/workshop_variation_from_idea_service.rb +++ b/app/services/workshop_variation_from_idea_service.rb @@ -18,7 +18,7 @@ def call def attributes_from_idea workshop_variation_idea.attributes.slice( - "name", "youtube_url", "position", "workshop_id", "author_credit_preference" + "name", "youtube_url", "position", "workshop_id", "windows_type_id", "organization_id", "author_credit_preference" ).merge( created_by_id: user.id, workshop_variation_idea_id: workshop_variation_idea.id, diff --git a/app/views/workshop_variation_ideas/_form.html.erb b/app/views/workshop_variation_ideas/_form.html.erb index c9ca35437..d252e548d 100644 --- a/app/views/workshop_variation_ideas/_form.html.erb +++ b/app/views/workshop_variation_ideas/_form.html.erb @@ -22,33 +22,16 @@ <% end %> <%= render 'shared/errors', resource: workshop_variation_idea if workshop_variation_idea.errors.any? %> - -
-
+ +
+
<%= f.input :name, label: "Variation name", required: true, disabled: promoted_to_variation, input_html: { class: "block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 #{('readonly' if promoted_to_variation)}" } %>
-
-
-
- <%= f.input :workshop_id, - collection: f.object.workshop.present? ? [[ f.object.workshop.remote_search_label[:label], f.object.workshop.id]] : [], - include_blank: true, - required: true, - input_html: { - data: { - controller: "remote-select", - remote_select_model_value: "workshop" - } - }, - prompt: "Search by title", - label: "Workshop", - label_html: { class: "block text-sm font-medium text-gray-700 mb-1 #{ 'readonly' if promoted_to_variation}" } %> -
-
+
<%= f.input :windows_type_id, as: :select, required: true, @@ -60,6 +43,28 @@ class: "#{'readonly' if promoted_to_variation} block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm" } %>
+
+ +
+
+ <% if params[:admin] == "true" && !promoted_to_variation %> + <%= f.input :workshop_id, + collection: f.object.workshop.present? ? [[ f.object.workshop.remote_search_label[:label], f.object.workshop.id]] : [], + include_blank: true, + required: true, + input_html: { + data: { + controller: "remote-select", + remote_select_model_value: "workshop" + } + }, + prompt: "Search by title", + label: "Workshop", + label_html: { class: "block text-sm font-medium text-gray-700 mb-1" } %> + <% elsif f.object.workshop_id.present? %> + <%= f.hidden_field :workshop_id, value: f.object.workshop_id %> + <% end %> +
<% if current_user.person&.affiliations&.count != 1 %> <%= f.input :organization_id, @@ -107,7 +112,7 @@ <%= f.input :youtube_url, as: :text, label: "YouTube link (optional)".html_safe, - hint: "If you already have a video on YouTube you’d like to share", + hint: "If you already have a video on YouTube you'd like to share", disabled: promoted_to_variation, input_html: { rows: 1, class: ("readonly" if promoted_to_variation) diff --git a/app/views/workshop_variation_ideas/edit.html.erb b/app/views/workshop_variation_ideas/edit.html.erb index f76f0bc78..b4e976645 100644 --- a/app/views/workshop_variation_ideas/edit.html.erb +++ b/app/views/workshop_variation_ideas/edit.html.erb @@ -17,7 +17,10 @@ <%= link_to "View", workshop_variation_idea_path(@workshop_variation_idea), class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %>
-

Edit workshop variation idea

+

Edit workshop variation idea: <%= @workshop_variation_idea.name %>

+ <% if @workshop_variation_idea.workshop.present? %> +

For workshop: <%= @workshop_variation_idea.workshop.remote_search_label[:label] %>

+ <% end %>
diff --git a/app/views/workshop_variations/_form.html.erb b/app/views/workshop_variations/_form.html.erb index 601ab16bc..20f6698f6 100644 --- a/app/views/workshop_variations/_form.html.erb +++ b/app/views/workshop_variations/_form.html.erb @@ -8,11 +8,7 @@
<% if @workshop %> <%= f.hidden_field :workshop_id, value: @workshop.id %> -
- Variant of workshop: - <%= link_to(@workshop.name, workshop_path(@workshop), class: "hover:underline") %> -
- <% else %> + <% elsif params[:admin] == "true" %> <%= f.input :workshop_id, collection: f.object.workshop.present? ? [[ f.object.workshop.remote_search_label[:label], f.object.workshop.id]] : [], include_blank: true, @@ -26,6 +22,8 @@ prompt: "Search by title", label: "Workshop", label_html: { class: "block text-sm font-medium text-gray-700 mb-1 " } %> + <% elsif f.object.workshop_id.present? %> + <%= f.hidden_field :workshop_id, value: f.object.workshop_id %> <% end %>
diff --git a/app/views/workshop_variations/edit.html.erb b/app/views/workshop_variations/edit.html.erb index b036c4507..c95ad1d17 100644 --- a/app/views/workshop_variations/edit.html.erb +++ b/app/views/workshop_variations/edit.html.erb @@ -4,7 +4,10 @@ <%= link_to "Home", root_path, class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %> <%= link_to "View", workshop_variation_path(@workshop_variation), class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %>
-

Edit workshop variation

+

Edit workshop variation: <%= @workshop_variation.name %>

+ <% if @workshop_variation.workshop.present? %> +

Variant of workshop: <%= @workshop_variation.workshop.remote_search_label[:label] %>

+ <% end %>
diff --git a/db/migrate/20260303180000_add_windows_type_id_to_workshop_variations.rb b/db/migrate/20260303180000_add_windows_type_id_to_workshop_variations.rb new file mode 100644 index 000000000..b47e30cf7 --- /dev/null +++ b/db/migrate/20260303180000_add_windows_type_id_to_workshop_variations.rb @@ -0,0 +1,5 @@ +class AddWindowsTypeIdToWorkshopVariations < ActiveRecord::Migration[8.0] + def change + add_reference :workshop_variations, :windows_type, type: :integer, foreign_key: true, null: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 1baa25275..77439b4b6 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.1].define(version: 2026_03_02_150000) do +ActiveRecord::Schema[8.1].define(version: 2026_03_03_180000) do create_table "action_text_mentions", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.bigint "action_text_rich_text_id", null: false t.datetime "created_at", null: false @@ -1217,12 +1217,14 @@ t.boolean "published", default: false, null: false t.datetime "updated_at", precision: nil, null: false t.integer "variation_id" + t.integer "windows_type_id" t.integer "workshop_id", null: false t.bigint "workshop_variation_idea_id" t.string "youtube_url" t.index ["created_by_id"], name: "index_workshop_variations_on_created_by_id" t.index ["organization_id"], name: "index_workshop_variations_on_organization_id" t.index ["published"], name: "index_workshop_variations_on_published" + t.index ["windows_type_id"], name: "index_workshop_variations_on_windows_type_id" t.index ["workshop_id", "name"], name: "index_workshop_variations_on_workshop_id_and_name", unique: true t.index ["workshop_id"], name: "index_workshop_variations_on_workshop_id" t.index ["workshop_variation_idea_id"], name: "index_workshop_variations_on_workshop_variation_idea_id" @@ -1433,6 +1435,7 @@ add_foreign_key "workshop_variation_ideas", "workshops" add_foreign_key "workshop_variations", "organizations" add_foreign_key "workshop_variations", "users", column: "created_by_id" + add_foreign_key "workshop_variations", "windows_types" add_foreign_key "workshop_variations", "workshop_variation_ideas" add_foreign_key "workshop_variations", "workshops" add_foreign_key "workshops", "users", column: "created_by_id" From ffa07c53418c50fb447d0f64038eb64b50dab2d5 Mon Sep 17 00:00:00 2001 From: maebeale Date: Tue, 3 Mar 2026 10:57:56 -0500 Subject: [PATCH 02/14] Check admin param before @workshop in variation form workshop conditional Ensures admin users with ?admin=true can change the workshop even when @workshop is set, rather than always hiding the dropdown. Co-Authored-By: Claude Opus 4.6 --- app/views/workshop_variations/_form.html.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/workshop_variations/_form.html.erb b/app/views/workshop_variations/_form.html.erb index 20f6698f6..00d161246 100644 --- a/app/views/workshop_variations/_form.html.erb +++ b/app/views/workshop_variations/_form.html.erb @@ -6,9 +6,7 @@
- <% if @workshop %> - <%= f.hidden_field :workshop_id, value: @workshop.id %> - <% elsif params[:admin] == "true" %> + <% if params[:admin] == "true" %> <%= f.input :workshop_id, collection: f.object.workshop.present? ? [[ f.object.workshop.remote_search_label[:label], f.object.workshop.id]] : [], include_blank: true, @@ -22,6 +20,8 @@ prompt: "Search by title", label: "Workshop", label_html: { class: "block text-sm font-medium text-gray-700 mb-1 " } %> + <% elsif @workshop %> + <%= f.hidden_field :workshop_id, value: @workshop.id %> <% elsif f.object.workshop_id.present? %> <%= f.hidden_field :workshop_id, value: f.object.workshop_id %> <% end %> From 496e0d83cf7bdc33c57294bcc4f34b114eb4ffc7 Mon Sep 17 00:00:00 2001 From: maebeale Date: Wed, 4 Mar 2026 05:58:13 -0500 Subject: [PATCH 03/14] Show workshop dropdown on new variation and variation idea forms The workshop_id dropdown now shows on new records so users can select a workshop. On edit, it remains hidden unless ?admin=true. Co-Authored-By: Claude Opus 4.6 --- app/views/workshop_variation_ideas/_form.html.erb | 2 +- app/views/workshop_variations/_form.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/workshop_variation_ideas/_form.html.erb b/app/views/workshop_variation_ideas/_form.html.erb index d252e548d..24f84cf0a 100644 --- a/app/views/workshop_variation_ideas/_form.html.erb +++ b/app/views/workshop_variation_ideas/_form.html.erb @@ -47,7 +47,7 @@
- <% if params[:admin] == "true" && !promoted_to_variation %> + <% if !promoted_to_variation && (params[:admin] == "true" || f.object.new_record?) %> <%= f.input :workshop_id, collection: f.object.workshop.present? ? [[ f.object.workshop.remote_search_label[:label], f.object.workshop.id]] : [], include_blank: true, diff --git a/app/views/workshop_variations/_form.html.erb b/app/views/workshop_variations/_form.html.erb index 00d161246..efab6ef71 100644 --- a/app/views/workshop_variations/_form.html.erb +++ b/app/views/workshop_variations/_form.html.erb @@ -6,7 +6,7 @@
- <% if params[:admin] == "true" %> + <% if params[:admin] == "true" || (f.object.new_record? && !@workshop) %> <%= f.input :workshop_id, collection: f.object.workshop.present? ? [[ f.object.workshop.remote_search_label[:label], f.object.workshop.id]] : [], include_blank: true, From 7286140cd41f71399d4e8f611b6d9a1c17c11dc4 Mon Sep 17 00:00:00 2001 From: maebeale Date: Wed, 4 Mar 2026 05:59:27 -0500 Subject: [PATCH 04/14] Move workshop dropdown above variation name in variation idea form Reorders the form so workshop and organization appear first, followed by name and windows type below. Co-Authored-By: Claude Opus 4.6 --- .../workshop_variation_ideas/_form.html.erb | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/app/views/workshop_variation_ideas/_form.html.erb b/app/views/workshop_variation_ideas/_form.html.erb index 24f84cf0a..0f1962507 100644 --- a/app/views/workshop_variation_ideas/_form.html.erb +++ b/app/views/workshop_variation_ideas/_form.html.erb @@ -22,29 +22,7 @@
<% end %> <%= render 'shared/errors', resource: workshop_variation_idea if workshop_variation_idea.errors.any? %> - -
-
- <%= f.input :name, - label: "Variation name", - required: true, - disabled: promoted_to_variation, - input_html: { class: "block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 #{('readonly' if promoted_to_variation)}" } %> -
-
- <%= f.input :windows_type_id, - as: :select, - required: true, - label: "Windows audience", - prompt: "Select audience", - collection: @windows_types, label_method: :short_name, value_method: :id, - disabled: promoted_to_variation, - input_html: { - class: "#{'readonly' if promoted_to_variation} block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm" - } %> -
-
- +
<% if !promoted_to_variation && (params[:admin] == "true" || f.object.new_record?) %> @@ -88,6 +66,29 @@
+ +
+
+ <%= f.input :name, + label: "Variation name", + required: true, + disabled: promoted_to_variation, + input_html: { class: "block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 #{('readonly' if promoted_to_variation)}" } %> +
+
+ <%= f.input :windows_type_id, + as: :select, + required: true, + label: "Windows audience", + prompt: "Select audience", + collection: @windows_types, label_method: :short_name, value_method: :id, + disabled: promoted_to_variation, + input_html: { + class: "#{'readonly' if promoted_to_variation} block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + } %> +
+
+ <% if promoted_to_variation %>
<%= workshop_variation_idea.rhino_body %>
<% else %> From 6ec1b2f0a26bad5def44cacdc75a7a5e512bf2e2 Mon Sep 17 00:00:00 2001 From: maebeale Date: Wed, 4 Mar 2026 06:06:37 -0500 Subject: [PATCH 05/14] Remove custom label_html from workshop dropdown for consistent sizing Lets both workshop and windows type labels use the same simple_form default styling. Co-Authored-By: Claude Opus 4.6 --- app/views/workshop_variation_ideas/_form.html.erb | 3 +-- app/views/workshop_variations/_form.html.erb | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/views/workshop_variation_ideas/_form.html.erb b/app/views/workshop_variation_ideas/_form.html.erb index 0f1962507..02a2eb219 100644 --- a/app/views/workshop_variation_ideas/_form.html.erb +++ b/app/views/workshop_variation_ideas/_form.html.erb @@ -37,8 +37,7 @@ } }, prompt: "Search by title", - label: "Workshop", - label_html: { class: "block text-sm font-medium text-gray-700 mb-1" } %> + label: "Workshop" %> <% elsif f.object.workshop_id.present? %> <%= f.hidden_field :workshop_id, value: f.object.workshop_id %> <% end %> diff --git a/app/views/workshop_variations/_form.html.erb b/app/views/workshop_variations/_form.html.erb index efab6ef71..290d68623 100644 --- a/app/views/workshop_variations/_form.html.erb +++ b/app/views/workshop_variations/_form.html.erb @@ -18,8 +18,7 @@ } }, prompt: "Search by title", - label: "Workshop", - label_html: { class: "block text-sm font-medium text-gray-700 mb-1 " } %> + label: "Workshop" %> <% elsif @workshop %> <%= f.hidden_field :workshop_id, value: @workshop.id %> <% elsif f.object.workshop_id.present? %> From 1f71295efaad3c5b360baddb3e7cc07bf3d5fe44 Mon Sep 17 00:00:00 2001 From: maebeale Date: Wed, 4 Mar 2026 06:07:09 -0500 Subject: [PATCH 06/14] Add windows type dropdown to variation form and reorder fields Workshop dropdown is already above the name row. Adds the windows type dropdown next to the name field, matching the variation idea form layout. Co-Authored-By: Claude Opus 4.6 --- app/views/workshop_variations/_form.html.erb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app/views/workshop_variations/_form.html.erb b/app/views/workshop_variations/_form.html.erb index 290d68623..af8129f9d 100644 --- a/app/views/workshop_variations/_form.html.erb +++ b/app/views/workshop_variations/_form.html.erb @@ -43,11 +43,25 @@
<% end %> + +
+
+ <%= f.input :name, label: "Variation name", input_html: { class: "w-full" } %> +
+
+ <%= f.input :windows_type_id, + as: :select, + collection: WindowsType.all, + label_method: :short_name, + value_method: :id, + include_blank: "Select type", + label: "Windows type" %> +
+
+
- <%= f.input :name, label: "Variation name", input_html: { class: "w-full" } %> -
<%= rhino_editor(f, :body) %> From b224992d6dae5bde52afb4f4d7459695b92e6a9d Mon Sep 17 00:00:00 2001 From: maebeale Date: Wed, 4 Mar 2026 06:07:54 -0500 Subject: [PATCH 07/14] Make select inputs taller to match text input height Adds py-2 to windows type select inputs on both variation and variation idea forms. Co-Authored-By: Claude Opus 4.6 --- app/views/workshop_variation_ideas/_form.html.erb | 2 +- app/views/workshop_variations/_form.html.erb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/workshop_variation_ideas/_form.html.erb b/app/views/workshop_variation_ideas/_form.html.erb index 02a2eb219..eaa52de68 100644 --- a/app/views/workshop_variation_ideas/_form.html.erb +++ b/app/views/workshop_variation_ideas/_form.html.erb @@ -83,7 +83,7 @@ collection: @windows_types, label_method: :short_name, value_method: :id, disabled: promoted_to_variation, input_html: { - class: "#{'readonly' if promoted_to_variation} block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + class: "#{'readonly' if promoted_to_variation} py-2 block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm" } %>
diff --git a/app/views/workshop_variations/_form.html.erb b/app/views/workshop_variations/_form.html.erb index af8129f9d..c226c1bf2 100644 --- a/app/views/workshop_variations/_form.html.erb +++ b/app/views/workshop_variations/_form.html.erb @@ -55,7 +55,8 @@ label_method: :short_name, value_method: :id, include_blank: "Select type", - label: "Windows type" %> + label: "Windows type", + input_html: { class: "py-2" } %>
From 66b02495d7035523d893479113c138c8e0c89225 Mon Sep 17 00:00:00 2001 From: maebeale Date: Wed, 4 Mar 2026 06:09:12 -0500 Subject: [PATCH 08/14] Match select input styling to text input for consistent height Uses the same tailwind classes as the simple_form text input wrapper (rounded-lg, px-3 py-2, shadow-sm) on both windows type selects. Co-Authored-By: Claude Opus 4.6 --- app/views/workshop_variation_ideas/_form.html.erb | 2 +- app/views/workshop_variations/_form.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/workshop_variation_ideas/_form.html.erb b/app/views/workshop_variation_ideas/_form.html.erb index eaa52de68..a6679aaf7 100644 --- a/app/views/workshop_variation_ideas/_form.html.erb +++ b/app/views/workshop_variation_ideas/_form.html.erb @@ -83,7 +83,7 @@ collection: @windows_types, label_method: :short_name, value_method: :id, disabled: promoted_to_variation, input_html: { - class: "#{'readonly' if promoted_to_variation} py-2 block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + class: "#{'readonly' if promoted_to_variation} w-full rounded-lg border border-gray-300 px-3 py-2 text-gray-800 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:outline-none" } %>
diff --git a/app/views/workshop_variations/_form.html.erb b/app/views/workshop_variations/_form.html.erb index c226c1bf2..1904ffe43 100644 --- a/app/views/workshop_variations/_form.html.erb +++ b/app/views/workshop_variations/_form.html.erb @@ -56,7 +56,7 @@ value_method: :id, include_blank: "Select type", label: "Windows type", - input_html: { class: "py-2" } %> + input_html: { class: "w-full rounded-lg border border-gray-300 px-3 py-2 text-gray-800 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:outline-none" } %>
From a467ba57e1b3089cc804051c3c16ca2a05550111 Mon Sep 17 00:00:00 2001 From: maebeale Date: Wed, 4 Mar 2026 07:47:13 -0500 Subject: [PATCH 09/14] Refine variation/idea UI: promote banners, defaults, index improvements - Fix controller bug where `&&` short-circuit set instance vars to `false` instead of `nil` - Add purple "Promoted from/to" banners on variation and idea show/edit/form pages - Add admin-only variation idea dropdown with searchable select on variation form - Default windows_type to Combined and author_credit_preference to full_name on form - Show windows type in variation title on show/index pages with Combined fallback - Consistent "Workshop:" button styling across all variation/idea pages - Variation index: name column first with linked title, workshop as grey text link - Show [UNPUBLISHED] badge and 80-char description with 500-char hover on index - Gate promote banners by policy: "Promoted to" visible if published, "Not promoted" admin-only - Seed most variations as published and link some to variation ideas Co-Authored-By: Claude Opus 4.6 --- .../workshop_variations_controller.rb | 6 +- app/models/workshop_variation.rb | 2 + .../workshop_variation_ideas/_form.html.erb | 38 +----- .../workshop_variation_ideas/edit.html.erb | 36 +++-- .../workshop_variation_ideas/index.html.erb | 12 +- .../workshop_variation_ideas/show.html.erb | 43 ++++-- app/views/workshop_variations/_form.html.erb | 127 +++++++++++------- app/views/workshop_variations/edit.html.erb | 5 +- app/views/workshop_variations/index.html.erb | 10 +- app/views/workshop_variations/show.html.erb | 38 +++--- config/locales/en.yml | 5 + db/seeds/dummy_dev_seeds.rb | 13 +- 12 files changed, 198 insertions(+), 137 deletions(-) diff --git a/app/controllers/workshop_variations_controller.rb b/app/controllers/workshop_variations_controller.rb index 3ee5379da..9cf864d48 100644 --- a/app/controllers/workshop_variations_controller.rb +++ b/app/controllers/workshop_variations_controller.rb @@ -102,10 +102,8 @@ def update # end def set_form_variables - @workshop = @workshop_variation.workshop || params[:workshop_id].present? && - Workshop.where(id: params[:workshop_id]).last - @workshop_variation_idea = params[:workshop_variation_idea_id].present? && - WorkshopVariationIdea.find_by(id: params[:workshop_variation_idea_id]) + @workshop = @workshop_variation.workshop || (Workshop.find_by(id: params[:workshop_id]) if params[:workshop_id].present?) + @workshop_variation_idea = WorkshopVariationIdea.find_by(id: params[:workshop_variation_idea_id]) if params[:workshop_variation_idea_id].present? end def workshop_variation_params diff --git a/app/models/workshop_variation.rb b/app/models/workshop_variation.rb index c3e83d2e4..15a253a67 100644 --- a/app/models/workshop_variation.rb +++ b/app/models/workshop_variation.rb @@ -37,6 +37,8 @@ def self.search_by_params(params) has_many :assets, as: :owner, dependent: :destroy validates :name, presence: true, uniqueness: { scope: :workshop_id, case_sensitive: false } + validates :windows_type_id, presence: true + validates :author_credit_preference, presence: true validates :rhino_body, presence: true accepts_nested_attributes_for :primary_asset, allow_destroy: true, reject_if: :all_blank diff --git a/app/views/workshop_variation_ideas/_form.html.erb b/app/views/workshop_variation_ideas/_form.html.erb index a6679aaf7..49b7bfc5d 100644 --- a/app/views/workshop_variation_ideas/_form.html.erb +++ b/app/views/workshop_variation_ideas/_form.html.erb @@ -31,6 +31,7 @@ include_blank: true, required: true, input_html: { + class: "h-10 rounded-md", data: { controller: "remote-select", remote_select_model_value: "workshop" @@ -72,7 +73,7 @@ label: "Variation name", required: true, disabled: promoted_to_variation, - input_html: { class: "block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 #{('readonly' if promoted_to_variation)}" } %> + input_html: { class: "block w-full h-10 rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 #{('readonly' if promoted_to_variation)}" } %>
<%= f.input :windows_type_id, @@ -83,7 +84,7 @@ collection: @windows_types, label_method: :short_name, value_method: :id, disabled: promoted_to_variation, input_html: { - class: "#{'readonly' if promoted_to_variation} w-full rounded-lg border border-gray-300 px-3 py-2 text-gray-800 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:outline-none" + class: "#{'readonly' if promoted_to_variation} w-full h-10 rounded-md border border-gray-300 px-3 py-2 text-gray-800 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:outline-none" } %>
@@ -108,7 +109,7 @@ collection: AuthorCreditable::IDEA_FORM_OPTIONS, include_blank: "Select a preference", selected: f.object.author_credit_preference, - input_html: { class: "block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm" } %> + input_html: { class: "block w-full h-10 rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm" } %> <%= f.input :youtube_url, as: :text, label: "YouTube link (optional)".html_safe, @@ -132,36 +133,11 @@ class: ("readonly" if promoted_to_variation) } %> - <% if allowed_to?(:manage?, WorkshopVariationIdea) || @user %> -
-
- <% if promoted_to_variation %> -
- - <% f.object. workshop_variations.each do |workshop_variation| %> - <%= link_to workshop_variation.title, workshop_variation_path(workshop_variation), class: "btn btn-secondary-outline" %> - <% end %> -
- <% elsif allowed_to?(:manage?, WorkshopVariationIdea) && f.object.persisted? %> -
- <%= link_to "Promote to Workshop Variation", - new_workshop_variation_path(workshop_variation_idea_id: @workshop_variation_idea.id), - class: "btn btn-secondary-outline ms-2" %> -
- <% end %> -
-
- <% end %> <%= render "shared/form_image_fields", f: f, include_primary_asset: false unless promoted_to_variation %>
- <% if allowed_to?(:destroy?, f.object) && f.object. workshop_variations.none? && !promoted_to_variation %> - - <%= link_to "Promote to Workshop Variation", - new_workshop_variation_path(workshop_variation_idea_id: f.object.id), - class: "btn btn-secondary-outline" %> - <%= link_to "Delete", @workshop_variation_idea, class: "btn btn-danger-outline", - data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete?" } %> - + <% if allowed_to?(:destroy?, f.object) && f.object.workshop_variations.none? && !promoted_to_variation %> + <%= link_to "Delete", @workshop_variation_idea, class: "admin-only bg-blue-100 btn btn-danger-outline", + data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete?" } %> <% end %> <% cancel_path = if params[:workshop_id].present? && @workshop diff --git a/app/views/workshop_variation_ideas/edit.html.erb b/app/views/workshop_variation_ideas/edit.html.erb index b4e976645..104b78ac0 100644 --- a/app/views/workshop_variation_ideas/edit.html.erb +++ b/app/views/workshop_variation_ideas/edit.html.erb @@ -1,25 +1,45 @@ <% content_for(:page_bg_class, "admin-only bg-blue-100") %>
- <%= link_to "Home", root_path, class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %> <% if @workshop_variation_idea.workshop_variations.any? %> <% @workshop_variation_idea.workshop_variations.each do |workshop_variation| %> <%= link_to "View Variation", workshop_variation_path(workshop_variation), class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %> <% end %> - <% else %> - <% if allowed_to?(:manage?, @workshop_variation_idea) && @workshop_variation_idea.persisted? %> - <%= link_to "Promote to Workshop Variation", - new_workshop_variation_path(workshop_variation_idea_id: @workshop_variation_idea.id), - class: "admin-only bg-blue-100 btn btn-secondary-outline" %> - <% end %> <% end %> + <%= link_to "Home", root_path, class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %> <%= link_to "View", workshop_variation_idea_path(@workshop_variation_idea), class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %>

Edit workshop variation idea: <%= @workshop_variation_idea.name %>

<% if @workshop_variation_idea.workshop.present? %> -

For workshop: <%= @workshop_variation_idea.workshop.remote_search_label[:label] %>

+

Workshop: <%= link_to("#{@workshop_variation_idea.workshop.title} (#{@workshop_variation_idea.workshop.windows_type&.short_name || 'Combined'})", + workshop_path(@workshop_variation_idea.workshop), class: "btn btn-secondary-outline") %>

+ <% end %> + + <% visible_variations = allowed_to?(:manage?, WorkshopVariation) ? @workshop_variation_idea.workshop_variations : @workshop_variation_idea.workshop_variations.where(published: true) %> + <% if visible_variations.any? %> +
+
+ + + Promoted to: + <% visible_variations.each do |variation| %> + <%= link_to variation.title, workshop_variation_path(variation), class: "underline hover:text-purple-600" %> + <% end %> + +
+
+ <% elsif @workshop_variation_idea.persisted? && allowed_to?(:manage?, WorkshopVariation) %> +
+
+ + This idea has not been promoted yet. + <%= link_to "Promote to Workshop Variation", + new_workshop_variation_path(workshop_variation_idea_id: @workshop_variation_idea.id), + class: "btn btn-secondary-outline text-sm" %> +
+
<% end %>
diff --git a/app/views/workshop_variation_ideas/index.html.erb b/app/views/workshop_variation_ideas/index.html.erb index e41042bdb..4196839fe 100644 --- a/app/views/workshop_variation_ideas/index.html.erb +++ b/app/views/workshop_variation_ideas/index.html.erb @@ -21,8 +21,8 @@ Main Image - Name - Workshop + Name + Workshop Author Promoted? Actions @@ -33,7 +33,7 @@ <% @workshop_variation_ideas.each do |workshop_variation_idea| %> -
+
<%= render "assets/display_image", resource: workshop_variation_idea, width: 18, height: 14, @@ -42,7 +42,9 @@ file: workshop_variation_idea.display_image %>
- <%= workshop_variation_idea.name %> + + <%= link_to workshop_variation_idea.name, workshop_variation_idea_path(workshop_variation_idea), class: "hover:underline" %> + <%= workshop_variation_idea.workshop.title %> <%= workshop_variation_idea.created_by.name %> @@ -55,7 +57,7 @@ - <%= link_to 'Edit', edit_workshop_variation_idea_path(workshop_variation_idea), class: "btn btn-secondary-outline" %> + <%= link_to 'View', workshop_variation_idea_path(workshop_variation_idea), class: "btn btn-secondary-outline" %> <% end %> diff --git a/app/views/workshop_variation_ideas/show.html.erb b/app/views/workshop_variation_ideas/show.html.erb index 1bc1797c4..e0c616894 100644 --- a/app/views/workshop_variation_ideas/show.html.erb +++ b/app/views/workshop_variation_ideas/show.html.erb @@ -1,6 +1,12 @@ <% content_for(:page_bg_class, "admin-or-owner") %>
+ <% if @workshop_variation_idea.workshop_variations.any? %> + <% @workshop_variation_idea.workshop_variations.each do |workshop_variation| %> + <%= link_to "View Variation", workshop_variation_path(workshop_variation), + class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %> + <% end %> + <% end %> <%= link_to "Home", root_path, class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %> <% if allowed_to?(:index?, WorkshopVariationIdea) %> <%= link_to "Workshop Variation Ideas", workshop_variation_ideas_path, class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %> @@ -16,15 +22,28 @@

<%= @workshop_variation_idea.name %>

- <% if @workshop_variation_idea.workshop_variations.any? %> -
- - - Promoted to: - <% @workshop_variation_idea.workshop_variations.each do |variation| %> - <%= link_to variation.title, workshop_variation_path(variation), class: "underline hover:text-blue-600" %> - <% end %> - + <% visible_variations = allowed_to?(:manage?, WorkshopVariation) ? @workshop_variation_idea.workshop_variations : @workshop_variation_idea.workshop_variations.where(published: true) %> + <% if visible_variations.any? %> +
+
+ + + Promoted to: + <% visible_variations.each do |variation| %> + <%= link_to variation.title, workshop_variation_path(variation), class: "underline hover:text-purple-600" %> + <% end %> + +
+
+ <% elsif @workshop_variation_idea.persisted? && allowed_to?(:manage?, WorkshopVariation) %> +
+
+ + This idea has not been promoted yet. + <%= link_to "Promote to Workshop Variation", + new_workshop_variation_path(workshop_variation_idea_id: @workshop_variation_idea.id), + class: "btn btn-secondary-outline text-sm" %> +
<% end %> @@ -56,9 +75,9 @@
Workshop: <% if @workshop_variation_idea.workshop && allowed_to?(:show?, @workshop_variation_idea.workshop) %> - <%= link_to @workshop_variation_idea.workshop.title, workshop_path(@workshop_variation_idea.workshop), - data: { turbo_frame: "_top" }, - class: "inline-block px-3 py-1 rounded-md text-sm font-medium #{DomainTheme.bg_class_for(:workshops, intensity: 100)} #{DomainTheme.text_class_for(:workshops)} #{DomainTheme.bg_class_for(:workshops, intensity: 100, hover: true)}" %> + <%= link_to "#{@workshop_variation_idea.workshop.title} (#{@workshop_variation_idea.workshop.windows_type&.short_name || 'Combined'})", + workshop_path(@workshop_variation_idea.workshop), + class: "btn btn-secondary-outline text-sm" %> <% else %> <%= @workshop_variation_idea.workshop&.title || "—" %> <% end %> diff --git a/app/views/workshop_variations/_form.html.erb b/app/views/workshop_variations/_form.html.erb index 1904ffe43..aafa9c92a 100644 --- a/app/views/workshop_variations/_form.html.erb +++ b/app/views/workshop_variations/_form.html.erb @@ -12,6 +12,7 @@ include_blank: true, required: true, input_html: { + class: "h-10 rounded-md", data: { controller: "remote-select", remote_select_model_value: "workshop" @@ -26,38 +27,45 @@ <% end %>
- <% if @workshop_variation_idea %> -
-
- Promoting from Variation Idea: + <% linked_idea = @workshop_variation_idea || f.object.workshop_variation_idea %> + <% if linked_idea %> +
+
+ + Promoted from: + <%= link_to linked_idea.name, + workshop_variation_idea_path(linked_idea), + class: "btn btn-secondary-outline text-sm" %>
- -
- <%= link_to @workshop_variation_idea.name, - workshop_variation_idea_path(@workshop_variation_idea), - class: "hover:underline" %> -
- - <%= f.hidden_field :workshop_variation_idea_id, - value: @workshop_variation_idea.id %>
<% end %> - -
-
- <%= f.input :name, label: "Variation name", input_html: { class: "w-full" } %> -
-
- <%= f.input :windows_type_id, + +
+
+
+ <%= f.input :name, label: "Variation name", input_html: { class: "w-full h-10" } %> +
+
+ <%= f.input :windows_type_id, as: :select, + required: true, collection: WindowsType.all, label_method: :short_name, value_method: :id, + selected: f.object.windows_type_id || WindowsType.find_by(name: "Combined")&.id, include_blank: "Select type", label: "Windows type", - input_html: { class: "w-full rounded-lg border border-gray-300 px-3 py-2 text-gray-800 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:outline-none" } %> + input_html: { class: "w-full h-10 rounded-md border border-gray-300 px-3 py-2 text-gray-800 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:outline-none" } %> +
+ + <% if allowed_to?(:manage?, WorkshopVariation) %> +
+ <%= f.input :published, as: :boolean, input_html: { checked: f.object.id ? f.object.published? : false } %> + <%= f.input :publicly_visible, as: :boolean, input_html: { checked: f.object.id ? f.object.publicly_visible? : false } %> +
+ <% end %>
@@ -71,39 +79,66 @@ <% end %>
- <%= f.input :youtube_url, - label: "YouTube link (optional)".html_safe, - hint: "If you already have a video on YouTube you’d like to share", - input_html: { class: "w-full" } %>
- <% if allowed_to?(:manage?, WorkshopVariation) %> -
- <%= f.input :published, as: :boolean, input_html: { checked: f.object.id ? f.object.published? : false } %> - <%= f.input :publicly_visible, as: :boolean, input_html: { checked: f.object.id ? f.object.publicly_visible? : false } %> - <%= f.input :position, as: :integer, label: "Ordering", input_html: { class: "w-24" } %> - -
- <%= f.input :created_by_id, - as: :select, - collection: User.includes(:person).references(:person).order(Arel.sql("LOWER(people.first_name), LOWER(people.last_name), LOWER(users.email), LOWER(people.email_2), LOWER(people.email)")), - label_method: :full_name_with_email, - value_method: :id, - selected: (f.object.created_by_id || current_user.id), - input_html: { class: "w-full rounded-lg px-3 py-2", - data: { searchable_select_target: "select" } } %> -
+
+
<%= f.input :author_credit_preference, as: :select, + required: true, collection: AuthorCreditable::ADMIN_FORM_OPTIONS, include_blank: "Select a preference", - selected: f.object.author_credit_preference, + selected: f.object.author_credit_preference || "full_name", label: "Author credit preference", - hint: "Controls how the author's name is displayed" %> + hint: "Controls how the author’s name is displayed", + input_html: { class: "h-10 rounded-md" } %> + + <% if allowed_to?(:manage?, WorkshopVariation) %> +
+ <% if params[:admin] == "true" %> + <%= f.input :position, as: :integer, label: "Ordering", input_html: { class: "w-24" } %> + <% end %> +
+ <%= f.input :created_by_id, + as: :select, + label: "Variation author", + collection: User.includes(:person).references(:person).order(Arel.sql("LOWER(people.first_name), LOWER(people.last_name), LOWER(users.email), LOWER(people.email_2), LOWER(people.email)")), + label_method: :full_name_with_email, + value_method: :id, + selected: (f.object.created_by_id || current_user.id), + input_html: { class: "w-full rounded-lg px-3 py-2", + data: { searchable_select_target: "select" } } %> +
+
+ <% else %> + <%= f.hidden_field :created_by_id, value: current_user.id %> + <% end %>
- <% else %> - <%= f.hidden_field :created_by_id, value: current_user.id %> - <% end %> + +
+ <%= f.input :youtube_url, + label: "YouTube link (optional)".html_safe, + hint: "If you already have a video on YouTube you’d like to share", + input_html: { class: "w-full" } %> + + <% if allowed_to?(:manage?, WorkshopVariation) %> +
+
+ <%= f.input :workshop_variation_idea_id, + as: :select, + label: "Variation idea", + collection: WorkshopVariationIdea.includes(:workshop, :windows_type, :created_by).order(:name), + label_method: ->(idea) { "#{idea.name} (#{idea.windows_type&.short_name&.first || 'C'}) by #{idea.created_by&.name || '?'} — #{idea.workshop&.title || 'No workshop'} (#{idea.workshop&.windows_type&.short_name&.first || 'C'})" }, + value_method: :id, + include_blank: "Select a variation idea", + selected: (f.object.workshop_variation_idea_id || @workshop_variation_idea&.id), + input_html: { class: "w-full rounded-lg px-3 py-2", + data: { searchable_select_target: "select" } } %> +
+
+ <% end %> +
+
diff --git a/app/views/workshop_variations/edit.html.erb b/app/views/workshop_variations/edit.html.erb index c95ad1d17..976600000 100644 --- a/app/views/workshop_variations/edit.html.erb +++ b/app/views/workshop_variations/edit.html.erb @@ -6,11 +6,10 @@

Edit workshop variation: <%= @workshop_variation.name %>

<% if @workshop_variation.workshop.present? %> -

Variant of workshop: <%= @workshop_variation.workshop.remote_search_label[:label] %>

+

Workshop: <%= link_to("#{@workshop_variation.workshop.name} (#{@workshop_variation.workshop.windows_type&.short_name || 'Combined'})", + workshop_path(@workshop_variation.workshop), class: "btn btn-secondary-outline") %>

<% end %> -
-
<%= render "form", workshop_variation: @workshop_variation %> <%= render "shared/audit_info", resource: @workshop_variation %> diff --git a/app/views/workshop_variations/index.html.erb b/app/views/workshop_variations/index.html.erb index 4f17fbd8c..75bfc5366 100644 --- a/app/views/workshop_variations/index.html.erb +++ b/app/views/workshop_variations/index.html.erb @@ -20,8 +20,8 @@ - + @@ -32,18 +32,18 @@ <% @workshop_variations.each do |workshop_variation| %> + -
Workshop Variation nameWorkshop Description Author Updated
<%= link_to "#{truncate(workshop_variation.name, length: 30)} (#{workshop_variation.windows_type&.short_name || 'Combined'})#{' [UNPUBLISHED]' unless workshop_variation.published?}", workshop_variation_path(workshop_variation), class: "hover:text-blue-600 hover:underline" %> <% if workshop_variation.workshop %> <%= link_to truncate(strip_tags(workshop_variation.workshop.name), length: 30), workshop_path(workshop_variation.workshop), - class: "btn btn-secondary-outline" %> + class: "text-gray-500 hover:text-gray-700" %> <% end %> <%= truncate(workshop_variation.name, length: 30) %> <% plain = workshop_variation.rhino_body.to_plain_text %> - - <%= truncate(plain, length: 30) %> + + <%= truncate(plain, length: 80) %> diff --git a/app/views/workshop_variations/show.html.erb b/app/views/workshop_variations/show.html.erb index b4568decf..17b4d254c 100644 --- a/app/views/workshop_variations/show.html.erb +++ b/app/views/workshop_variations/show.html.erb @@ -15,35 +15,31 @@
- Workshop: <%= link_to(@workshop_variation.workshop.name, - workshop_path(@workshop_variation.workshop), class: "btn btn-secondary-outline") %> + Workshop: <%= link_to("#{@workshop_variation.workshop.name} (#{@workshop_variation.workshop.windows_type&.short_name || 'Combined'})", + workshop_path(@workshop_variation.workshop), class: "btn btn-secondary-outline") %>
+ <% if @workshop_variation.workshop_variation_idea.present? && allowed_to?(:edit?, @workshop_variation) %> +
+
+ + Promoted from: + <%= link_to @workshop_variation.workshop_variation_idea.name, + workshop_variation_idea_path(@workshop_variation.workshop_variation_idea), + class: "btn btn-secondary-outline text-sm" %> +
+
+ <% end %> + -

- <%= @workshop_variation.name %> - <% unless @workshop_variation.published? %> - - [UNPUBLISHED] - - <% end %> -

+ <%= title_with_badges(@workshop_variation, font_size: "text-3xl", + record_title: "#{@workshop_variation.name} (#{@workshop_variation.windows_type&.short_name || 'Combined'})") %>
- <% if @workshop_variation.author_credit_preference.present? %> - <%= @workshop_variation.author_credit %> - <% else %> - <% creator = @workshop_variation.created_by %> - <% display_name = @workshop_variation.workshop_variation_idea&.author_credit || creator&.name %> - <% if creator&.person && allowed_to?(:show?, creator.person, with: PersonPolicy) %> - <%= link_to display_name, person_path(creator.person), class: "text-blue-600 hover:underline" %> - <% else %> - <%= display_name %> - <% end %> - <% end %> + <%= @workshop_variation.author_credit %> "> <%= @workshop_variation.created_at.strftime('%B %e, %Y') %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 9d3c80238..4c5b68b82 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -32,6 +32,11 @@ en: hello: "Hello world" activerecord: + attributes: + workshop_variation: + rhino_body: "Details" + workshop_variation_idea: + rhino_body: "Details" models: tutorial: one: "Video" diff --git a/db/seeds/dummy_dev_seeds.rb b/db/seeds/dummy_dev_seeds.rb index 16e07bd37..0fb7a2690 100644 --- a/db/seeds/dummy_dev_seeds.rb +++ b/db/seeds/dummy_dev_seeds.rb @@ -555,14 +555,15 @@ ] # rubocop:enable Style/PercentLiteralDelimiters -variations.each do |var_data| +variations.each_with_index do |var_data, i| workshop = Workshop.find_by(title: var_data[:workshop_title]) next unless workshop workshop.workshop_variations.where(name: var_data[:name]).first_or_create!( body: var_data[:rhino_body], rhino_body: var_data[:rhino_body], - position: var_data[:position] + position: var_data[:position], + published: i != variations.length - 1 ) end @@ -832,6 +833,14 @@ ) end +puts "Linking some WorkshopVariations to WorkshopVariationIdeas…" +WorkshopVariationIdea.all.sample(2).each do |idea| + variation = WorkshopVariation.where(workshop_variation_idea_id: nil).sample + next unless variation + + variation.update!(workshop_variation_idea_id: idea.id) +end + puts "Creating WorkshopLogs…" 5.times do workshop = Workshop.all.sample From 27ab846277d8b805c074dd5b0184e948817f3446 Mon Sep 17 00:00:00 2001 From: maebeale Date: Wed, 4 Mar 2026 07:49:39 -0500 Subject: [PATCH 10/14] Add tests for workshop variation validations and promoted-from banner - Update factory to include required windows_type and author_credit_preference - Add model spec validation tests for all required fields - Add association tests for windows_type, created_by, workshop_variation_idea - Add request spec tests for show page promoted-from banner visibility - Add request spec test for edit page rendering without false instance vars - Fix promotion test params to include new required fields Co-Authored-By: Claude Opus 4.6 --- spec/factories/workshop_variations.rb | 2 + spec/models/workshop_variation_spec.rb | 42 ++++---- spec/requests/workshop_variations_spec.rb | 114 +++++++++++++++------- 3 files changed, 100 insertions(+), 58 deletions(-) diff --git a/spec/factories/workshop_variations.rb b/spec/factories/workshop_variations.rb index 0fd3c3545..e75cd1be4 100644 --- a/spec/factories/workshop_variations.rb +++ b/spec/factories/workshop_variations.rb @@ -1,8 +1,10 @@ FactoryBot.define do factory :workshop_variation do association :workshop + association :windows_type sequence(:name) { |n| "Variation #{n}" } rhino_body { "

Variation details using CKEditor

" } + author_credit_preference { "full_name" } sequence(:position) { |n| n } published { false } diff --git a/spec/models/workshop_variation_spec.rb b/spec/models/workshop_variation_spec.rb index e45144b68..9e0f8fd5a 100644 --- a/spec/models/workshop_variation_spec.rb +++ b/spec/models/workshop_variation_spec.rb @@ -1,42 +1,42 @@ -require 'rails_helper' +require "rails_helper" RSpec.describe WorkshopVariation do it_behaves_like "author_creditable", factory: :workshop_variation - describe 'associations' do + describe "associations" do it { should belong_to(:workshop).optional } + it { should belong_to(:windows_type).optional } + it { should belong_to(:created_by).class_name("User").optional } + it { should belong_to(:workshop_variation_idea).optional } end - describe 'validations' do - # Add validation tests if any (e.g., presence of name, code?) - # subject { build(:workshop_variation) } # Requires workshop - # it { should validate_presence_of(:name) } - # it { should validate_presence_of(:code) } - end + describe "validations" do + subject { build(:workshop_variation) } - # it 'is valid with valid attributes' do - # # Note: Factory needs association uncommented for create - # # expect(build(:workshop_variation)).to be_valid - # pending("Requires functional workshop factory and association uncommented") - # end + it { should validate_presence_of(:name) } + it { should validate_presence_of(:rhino_body) } + it { should validate_presence_of(:windows_type_id) } + it { should validate_presence_of(:author_credit_preference) } + it { should validate_uniqueness_of(:name).scoped_to(:workshop_id).case_insensitive } + end - describe '.search_by_params' do - let!(:variation_a) { create(:workshop_variation, name: 'Watercolor Technique') } - let!(:variation_b) { create(:workshop_variation, name: 'Clay Sculpting') } + describe ".search_by_params" do + let!(:variation_a) { create(:workshop_variation, name: "Watercolor Technique") } + let!(:variation_b) { create(:workshop_variation, name: "Clay Sculpting") } - it 'returns all when no params' do + it "returns all when no params" do results = WorkshopVariation.search_by_params({}) expect(results).to include(variation_a, variation_b) end - it 'filters by query matching name' do - results = WorkshopVariation.search_by_params(query: 'Watercolor') + it "filters by query matching name" do + results = WorkshopVariation.search_by_params(query: "Watercolor") expect(results).to include(variation_a) expect(results).not_to include(variation_b) end - it 'returns empty for non-matching query' do - results = WorkshopVariation.search_by_params(query: 'nonexistent') + it "returns empty for non-matching query" do + results = WorkshopVariation.search_by_params(query: "nonexistent") expect(results).not_to include(variation_a, variation_b) end end diff --git a/spec/requests/workshop_variations_spec.rb b/spec/requests/workshop_variations_spec.rb index 1592761a1..dcc4ac1cd 100644 --- a/spec/requests/workshop_variations_spec.rb +++ b/spec/requests/workshop_variations_spec.rb @@ -4,6 +4,7 @@ let(:regular_user) { create(:user) } let(:admin) { create(:user, super_user: true) } let(:workshop) { create(:workshop, published: true) } + let(:windows_type) { create(:windows_type) } let(:valid_attributes) do { @@ -11,6 +12,8 @@ rhino_body: "

A therapeutic approach to art-making.

", youtube_url: "https://www.youtube.com/watch?v=example", workshop_id: workshop.id, + windows_type_id: windows_type.id, + author_credit_preference: "full_name", created_by_id: admin.id } end @@ -18,7 +21,9 @@ let(:invalid_attributes) do { name: "", - rhino_body: "" + rhino_body: "", + windows_type_id: nil, + author_credit_preference: "" } end @@ -51,19 +56,22 @@ end describe "POST /create from workshop_variation_idea" do + let(:promotion_params) do + { + name: workshop_variation_idea.name, + rhino_body: workshop_variation_idea.rhino_body.body, + youtube_url: workshop_variation_idea.youtube_url, + workshop_id: workshop_variation_idea.workshop_id, + windows_type_id: workshop_variation_idea.windows_type_id, + author_credit_preference: workshop_variation_idea.author_credit_preference, + workshop_variation_idea_id: workshop_variation_idea.id, + created_by_id: admin.id + } + end + it "creates a WorkshopVariation linked to the idea" do expect { - post workshop_variations_path, - params: { - workshop_variation: { - name: workshop_variation_idea.name, - rhino_body: workshop_variation_idea.rhino_body.body, - youtube_url: workshop_variation_idea.youtube_url, - workshop_id: workshop_variation_idea.workshop_id, - workshop_variation_idea_id: workshop_variation_idea.id, - created_by_id: admin.id - } - } + post workshop_variations_path, params: { workshop_variation: promotion_params } }.to change(WorkshopVariation, :count).by(1) new_variation = WorkshopVariation.last @@ -73,25 +81,25 @@ end it "attaches assets from idea when promote_idea_assets is true" do - # Create an idea with a primary asset idea_with_asset = create(:workshop_variation_idea, workshop: workshop) - primary_asset = PrimaryAsset.create!( + PrimaryAsset.create!( owner: idea_with_asset, file: fixture_file_upload("spec/fixtures/files/sample.png", "image/png") ) + idea_params = { + name: idea_with_asset.name, + rhino_body: idea_with_asset.rhino_body.body, + workshop_id: idea_with_asset.workshop_id, + windows_type_id: idea_with_asset.windows_type_id, + author_credit_preference: idea_with_asset.author_credit_preference, + workshop_variation_idea_id: idea_with_asset.id, + created_by_id: admin.id + } + expect { post workshop_variations_path, - params: { - workshop_variation: { - name: idea_with_asset.name, - rhino_body: idea_with_asset.rhino_body.body, - workshop_id: idea_with_asset.workshop_id, - workshop_variation_idea_id: idea_with_asset.id, - created_by_id: admin.id - }, - promote_idea_assets: "true" - } + params: { workshop_variation: idea_params, promote_idea_assets: "true" } }.to change(WorkshopVariation, :count).by(1) new_variation = WorkshopVariation.last @@ -100,21 +108,22 @@ it "does not duplicate assets if promote_idea_assets is false" do idea_with_asset = create(:workshop_variation_idea, workshop: workshop) - primary_asset = PrimaryAsset.create!( + PrimaryAsset.create!( owner: idea_with_asset, file: fixture_file_upload("spec/fixtures/files/sample.png", "image/png") ) - post workshop_variations_path, - params: { - workshop_variation: { - name: idea_with_asset.name, - rhino_body: idea_with_asset.rhino_body.body, - workshop_id: idea_with_asset.workshop_id, - workshop_variation_idea_id: idea_with_asset.id, - created_by_id: admin.id - } - } + idea_params = { + name: idea_with_asset.name, + rhino_body: idea_with_asset.rhino_body.body, + workshop_id: idea_with_asset.workshop_id, + windows_type_id: idea_with_asset.windows_type_id, + author_credit_preference: idea_with_asset.author_credit_preference, + workshop_variation_idea_id: idea_with_asset.id, + created_by_id: admin.id + } + + post workshop_variations_path, params: { workshop_variation: idea_params } new_variation = WorkshopVariation.last expect(new_variation.assets.count).to eq(0) @@ -126,9 +135,11 @@ post workshop_variations_path, params: { workshop_variation: { - name: workshop_variation_idea.name, - rhino_body: workshop_variation_idea.rhino_body.body, + name: workshop_variation_idea.name, + rhino_body: workshop_variation_idea.rhino_body.body, workshop_id: workshop_variation_idea.workshop_id, + windows_type_id: workshop_variation_idea.windows_type_id, + author_credit_preference: workshop_variation_idea.author_credit_preference, workshop_variation_idea_id: workshop_variation_idea.id, created_by_id: admin.id } @@ -186,12 +197,41 @@ end end + describe "GET /show" do + it "renders successfully" do + variation = create(:workshop_variation, valid_attributes) + get workshop_variation_path(variation) + expect(response).to have_http_status(:ok) + end + + it "shows promoted from banner when linked to an idea" do + idea = create(:workshop_variation_idea, workshop: workshop) + variation = create(:workshop_variation, valid_attributes.merge(workshop_variation_idea_id: idea.id)) + get workshop_variation_path(variation) + expect(response.body).to include("Promoted from:") + expect(response.body).to include(idea.name) + end + + it "does not show promoted from banner when not linked" do + variation = create(:workshop_variation, valid_attributes) + get workshop_variation_path(variation) + expect(response.body).not_to include("Promoted from:") + end + end + describe "GET /edit" do it "renders successfully" do variation = create(:workshop_variation, valid_attributes) get edit_workshop_variation_path(variation) expect(response).to have_http_status(:ok) end + + it "renders without error when no workshop_variation_idea_id param" do + variation = create(:workshop_variation, valid_attributes) + get edit_workshop_variation_path(variation) + expect(response).to have_http_status(:ok) + expect(response.body).not_to include("Promoted from:") + end end describe "PATCH /update" do From eb612fa9b9232e28ae51169720cc367874dc1ddd Mon Sep 17 00:00:00 2001 From: maebeale Date: Wed, 4 Mar 2026 08:41:02 -0500 Subject: [PATCH 11/14] Redesign variation and idea index tables with consistent layout Unify both indexes with matching columns: image thumbnail, name (with workshop subtitle), description icon, author link, lineage cross-links (From idea / Promoted to), updated date. Add unpublished badge chip with eye-slash icon matching title_with_badges style. Co-Authored-By: Claude Opus 4.6 --- .../workshop_variation_ideas/index.html.erb | 79 +++++++++++++------ app/views/workshop_variations/index.html.erb | 55 ++++++++++--- 2 files changed, 99 insertions(+), 35 deletions(-) diff --git a/app/views/workshop_variation_ideas/index.html.erb b/app/views/workshop_variation_ideas/index.html.erb index 4196839fe..78f4a76dc 100644 --- a/app/views/workshop_variation_ideas/index.html.erb +++ b/app/views/workshop_variation_ideas/index.html.erb @@ -20,44 +20,75 @@ - - - - - - + + + + + + + <% @workshop_variation_ideas.each do |workshop_variation_idea| %> - - + - - - + - - - + <% end %> diff --git a/app/views/workshop_variations/index.html.erb b/app/views/workshop_variations/index.html.erb index 75bfc5366..1c844e0ad 100644 --- a/app/views/workshop_variations/index.html.erb +++ b/app/views/workshop_variations/index.html.erb @@ -20,10 +20,11 @@
Main ImageNameWorkshopAuthorPromoted?ActionsNameDescriptionAuthorPromoted toUpdatedActions
-
+
+
<%= render "assets/display_image", resource: workshop_variation_idea, - width: 18, height: 14, - variant: :index, - link_to_object: true, - file: workshop_variation_idea.display_image %> + width: 12, height: 9, + link: false, + link_to_object: false, + file: workshop_variation_idea.display_image, + variant: :gallery %>
- <%= link_to workshop_variation_idea.name, workshop_variation_idea_path(workshop_variation_idea), class: "hover:underline" %> + +
+ <%= link_to truncate(workshop_variation_idea.name, length: 30), + workshop_variation_idea_path(workshop_variation_idea), + class: "hover:text-blue-600 hover:underline" %> +
+ <% if workshop_variation_idea.workshop %> +
+ <%= link_to truncate(strip_tags(workshop_variation_idea.workshop.name), length: 40), + workshop_path(workshop_variation_idea.workshop), + class: "hover:text-gray-600" %> +
+ <% end %> +
+ <% plain = workshop_variation_idea.rhino_body.to_plain_text %> + <% if plain.present? %> + + + + <% end %> <%= workshop_variation_idea.workshop.title %><%= workshop_variation_idea.created_by.name %> - <% if workshop_variation_idea.workshop_variations.any? %> - + + <% display_name = workshop_variation_idea.author_credit.presence || workshop_variation_idea.created_by&.name %> + <% if workshop_variation_idea.created_by&.person %> + <%= link_to display_name, + person_path(workshop_variation_idea.created_by.person), + class: "text-gray-500 hover:text-gray-700" %> + <% elsif display_name %> + <%= display_name %> + <% end %> + + <% promoted_variation = workshop_variation_idea.workshop_variations.first %> + <% if promoted_variation %> + <%= link_to "Variation ##{promoted_variation.id}", + workshop_variation_path(promoted_variation), + class: "text-purple-600 hover:text-purple-800 hover:underline" %> <% else %> - -- + -- <% end %> - <%= link_to 'View', workshop_variation_idea_path(workshop_variation_idea), class: "btn btn-secondary-outline" %> + <%= workshop_variation_idea.updated_at&.strftime("%m/%d/%y") %> + <%= link_to "View", workshop_variation_idea_path(workshop_variation_idea), class: "btn btn-secondary-outline" %>
- - + + + @@ -32,30 +33,62 @@ <% @workshop_variations.each do |workshop_variation| %> - - + +
Variation nameWorkshopName Description AuthorFrom idea Updated Edit
<%= link_to "#{truncate(workshop_variation.name, length: 30)} (#{workshop_variation.windows_type&.short_name || 'Combined'})#{' [UNPUBLISHED]' unless workshop_variation.published?}", workshop_variation_path(workshop_variation), class: "hover:text-blue-600 hover:underline" %> + +
+ <%= render "assets/display_image", + resource: workshop_variation, + width: 12, height: 9, + link: false, + link_to_object: false, + file: workshop_variation.display_image, + variant: :gallery %> +
+
+
+ <%= link_to "#{truncate(workshop_variation.name, length: 30)} (#{workshop_variation.windows_type&.short_name || 'Combined'})", workshop_variation_path(workshop_variation), class: "hover:text-blue-600 hover:underline" %> + <% unless workshop_variation.published? %> + + + Unpublished + + <% end %> +
<% if workshop_variation.workshop %> - <%= link_to truncate(strip_tags(workshop_variation.workshop.name), length: 30), - workshop_path(workshop_variation.workshop), - class: "text-gray-500 hover:text-gray-700" %> +
+ <%= link_to truncate(strip_tags(workshop_variation.workshop.name), length: 40), + workshop_path(workshop_variation.workshop), + class: "hover:text-gray-600" %> +
<% end %>
<% plain = workshop_variation.rhino_body.to_plain_text %> - - <%= truncate(plain, length: 80) %> - + <% if plain.present? %> + + + + <% end %> <% display_name = workshop_variation.workshop_variation_idea&.author_credit || workshop_variation.created_by&.name %> <% if workshop_variation.created_by&.person %> <%= link_to display_name, person_path(workshop_variation.created_by.person), - class: "btn btn-secondary-outline" %> + class: "text-gray-500 hover:text-gray-700" %> <% elsif display_name %> <%= display_name %> <% end %> + <% if workshop_variation.workshop_variation_idea %> + <%= link_to "Idea ##{workshop_variation.workshop_variation_idea.id}", + workshop_variation_idea_path(workshop_variation.workshop_variation_idea), + class: "text-purple-600 hover:text-purple-800 hover:underline" %> + <% else %> + -- + <% end %> + <%= workshop_variation.updated_at&.strftime('%m/%d/%y') %> <%= link_to "Edit", From 100805ba967660ec7cecbc4adc6b3da94275c6f9 Mon Sep 17 00:00:00 2001 From: maebeale Date: Wed, 4 Mar 2026 08:56:42 -0500 Subject: [PATCH 12/14] Match ideas index page background to ideas form styling Co-Authored-By: Claude Opus 4.6 --- app/views/workshop_variation_ideas/index.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/workshop_variation_ideas/index.html.erb b/app/views/workshop_variation_ideas/index.html.erb index 78f4a76dc..4228083a1 100644 --- a/app/views/workshop_variation_ideas/index.html.erb +++ b/app/views/workshop_variation_ideas/index.html.erb @@ -1,5 +1,5 @@ -<% content_for(:page_bg_class, "admin-only bg-blue-100") %> -
+<% content_for(:page_bg_class, "admin-or-auth") %> +
From 467e57253bc83faddd8cb4b09edb66a5b59cca50 Mon Sep 17 00:00:00 2001 From: maebeale Date: Wed, 4 Mar 2026 09:48:36 -0500 Subject: [PATCH 13/14] Fix CI test failures for ideas index bg class and variation show spec - Update page_bg_class_alignment_spec to expect "admin-or-auth" for ideas index after changing from "admin-only bg-blue-100" - Fix show view spec to use author_credit instead of user.name - Fix unpublished indicator check to match title_with_badges output Co-Authored-By: Claude Opus 4.6 --- spec/views/page_bg_class_alignment_spec.rb | 2 +- spec/views/workshop_variations/show.html.erb_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/views/page_bg_class_alignment_spec.rb b/spec/views/page_bg_class_alignment_spec.rb index da368f997..039767110 100644 --- a/spec/views/page_bg_class_alignment_spec.rb +++ b/spec/views/page_bg_class_alignment_spec.rb @@ -110,7 +110,7 @@ "app/views/users/index.html.erb" => "admin-only bg-blue-100", "app/views/windows_types/index.html.erb" => "admin-only bg-blue-100", "app/views/workshop_ideas/index.html.erb" => "admin-only bg-blue-100", - "app/views/workshop_variation_ideas/index.html.erb" => "admin-only bg-blue-100", + "app/views/workshop_variation_ideas/index.html.erb" => "admin-or-auth", # show "app/views/banners/show.html.erb" => "admin-only bg-blue-100", "app/views/categories/show.html.erb" => "admin-only bg-blue-100", diff --git a/spec/views/workshop_variations/show.html.erb_spec.rb b/spec/views/workshop_variations/show.html.erb_spec.rb index 5e7206d82..49e501eef 100644 --- a/spec/views/workshop_variations/show.html.erb_spec.rb +++ b/spec/views/workshop_variations/show.html.erb_spec.rb @@ -22,7 +22,7 @@ expect(rendered).to include(workshop_variation.name) expect(rendered).to include(workshop_variation.rhino_body.body.to_s) expect(rendered).to include(workshop.title) - expect(rendered).to include(user.name) + expect(rendered).to include(workshop_variation.author_credit) end context "when the workshop_variation has a youtube_url" do @@ -39,7 +39,7 @@ it "displays an unpublished indicator" do render - expect(rendered).to include("[UNPUBLISHED]") + expect(rendered).to include("Unpublished") end end end From 0013bf0939f911da47a03308a1a66f9ff74ba2ee Mon Sep 17 00:00:00 2001 From: maebeale Date: Wed, 4 Mar 2026 09:50:07 -0500 Subject: [PATCH 14/14] Revert ideas index page_bg_class to admin-only, keep DomainTheme intensity Co-Authored-By: Claude Opus 4.6 --- app/views/workshop_variation_ideas/index.html.erb | 2 +- spec/views/page_bg_class_alignment_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/workshop_variation_ideas/index.html.erb b/app/views/workshop_variation_ideas/index.html.erb index 4228083a1..caed96eae 100644 --- a/app/views/workshop_variation_ideas/index.html.erb +++ b/app/views/workshop_variation_ideas/index.html.erb @@ -1,4 +1,4 @@ -<% content_for(:page_bg_class, "admin-or-auth") %> +<% content_for(:page_bg_class, "admin-only bg-blue-100") %>
diff --git a/spec/views/page_bg_class_alignment_spec.rb b/spec/views/page_bg_class_alignment_spec.rb index 039767110..da368f997 100644 --- a/spec/views/page_bg_class_alignment_spec.rb +++ b/spec/views/page_bg_class_alignment_spec.rb @@ -110,7 +110,7 @@ "app/views/users/index.html.erb" => "admin-only bg-blue-100", "app/views/windows_types/index.html.erb" => "admin-only bg-blue-100", "app/views/workshop_ideas/index.html.erb" => "admin-only bg-blue-100", - "app/views/workshop_variation_ideas/index.html.erb" => "admin-or-auth", + "app/views/workshop_variation_ideas/index.html.erb" => "admin-only bg-blue-100", # show "app/views/banners/show.html.erb" => "admin-only bg-blue-100", "app/views/categories/show.html.erb" => "admin-only bg-blue-100",