From efb990305fc8a05a95c4429cbdf86a758482bb6e Mon Sep 17 00:00:00 2001 From: Mae Beale Date: Thu, 22 Oct 2026 11:53:58 -0400 Subject: [PATCH 1/8] Start: only link people viewers can open from org list and bylines Co-Authored-By: Claude Opus 5.5 (1M context) From f61f6c3edc0e10fefe6c0ed8e20c57a3d99ca273 Mon Sep 17 00:00:00 2001 From: Mae Beale Date: Thu, 22 Oct 2026 11:58:48 -0400 Subject: [PATCH 2/8] Only list and link people a viewer can actually open Non-admins can open a profile only when it's searchable and the person is an active facilitator, so the org people list and author bylines were sending them to profiles that refused to load. Co-Authored-By: Claude Opus 5.5 (1M context) --- app/controllers/organizations_controller.rb | 2 +- app/decorators/organization_decorator.rb | 9 +++++ app/views/organizations/show.html.erb | 9 ++--- ...-22-org-pages-list-active-facilitators.yml | 10 ++++++ .../decorators/organization_decorator_spec.rb | 20 +++++++++++ .../views/organizations/show.html.erb_spec.rb | 35 +++++++++++++++++++ 6 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb index de7d68b1f3..5aac647ab7 100644 --- a/app/controllers/organizations_controller.rb +++ b/app/controllers/organizations_controller.rb @@ -210,7 +210,7 @@ def set_organization :organization_status, :windows_type, :addresses, :categorizable_items, { comments: [ :created_by, :updated_by ] }, - affiliations: { person: [ :user, *Organization::PEOPLE_TAGGINGS ] } + affiliations: { person: [ :user, :affiliations, *Organization::PEOPLE_TAGGINGS ] } ).find(params[:id]) end diff --git a/app/decorators/organization_decorator.rb b/app/decorators/organization_decorator.rb index 742b838c0a..48ba7c90b8 100644 --- a/app/decorators/organization_decorator.rb +++ b/app/decorators/organization_decorator.rb @@ -235,6 +235,15 @@ def badges badges end + # Keeps every active title per person so a facilitator's card also lists e.g. "Director". + def active_affiliations_by_facilitator + object.affiliations + .select { |affiliation| affiliation.active? && affiliation.person } + .sort_by { |affiliation| [ affiliation.person.first_name.to_s.downcase, affiliation.person.last_name.to_s.downcase ] } + .group_by(&:person) + .select { |_person, affiliations| affiliations.any?(&:facilitator?) } + end + private # The facilitator rows that record real facilitation. A zero-length (start == diff --git a/app/views/organizations/show.html.erb b/app/views/organizations/show.html.erb index d24e9e716f..c243d1df7f 100644 --- a/app/views/organizations/show.html.erb +++ b/app/views/organizations/show.html.erb @@ -131,11 +131,8 @@
-

Affiliations

- <% active_affiliations = @organization.affiliations - .select { |a| !a.inactive? && (a.end_date.nil? || a.end_date >= Date.current) && a.person.present? } - .sort_by { |a| [a.person.first_name.to_s.downcase, a.person.last_name.to_s.downcase] } %> - <% grouped = active_affiliations.group_by(&:person) %> +

Facilitators

+ <% grouped = org_decorated.active_affiliations_by_facilitator.select { |person, _| allowed_to?(:show?, person) } %> <% if grouped.any? %>
<% grouped.each do |person, person_affiliations| %> @@ -148,7 +145,7 @@
<% else %> -

No active affiliations.

+

No active facilitators.

<% end %>
diff --git a/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml b/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml new file mode 100644 index 0000000000..dc6cdd98bd --- /dev/null +++ b/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml @@ -0,0 +1,10 @@ +name: "Organization pages list active facilitators" +area: people +display_status: user_facing +released_on: 2026-10-22 +pr_number: 2785 +summary: >- + An organization's people list now shows only its active facilitators whose profiles you can open, + and author names link to a profile only when you can open it. +pro_tips: + - "An author whose profile you can't open shows as plain text; on organization pages they're left off the list." diff --git a/spec/decorators/organization_decorator_spec.rb b/spec/decorators/organization_decorator_spec.rb index 47911c9074..70e72ffe18 100644 --- a/spec/decorators/organization_decorator_spec.rb +++ b/spec/decorators/organization_decorator_spec.rb @@ -1,6 +1,26 @@ require "rails_helper" RSpec.describe OrganizationDecorator do + describe "#active_affiliations_by_facilitator" do + let(:organization) { create(:organization) } + let(:facilitator) { create(:person, first_name: "Zoe") } + let(:earlier) { create(:person, first_name: "Abe") } + + it "groups each active facilitator's active titles, sorted by name, leaving out everyone else" do + facilitation = create(:affiliation, organization: organization, person: facilitator) + director = create(:affiliation, organization: organization, person: facilitator, title: "Director") + create(:affiliation, organization: organization, person: facilitator, title: "Counselor", end_date: 1.month.ago) + create(:affiliation, organization: organization, person: earlier) + create(:affiliation, organization: organization, title: "Board Member") + create(:affiliation, organization: organization, end_date: 1.month.ago) + + grouped = organization.reload.decorate.active_affiliations_by_facilitator + + expect(grouped.keys).to eq([ earlier, facilitator ]) + expect(grouped[facilitator]).to contain_exactly(facilitation, director) + end + end + describe "#affiliated_since_display" do let(:organization) { create(:organization) } diff --git a/spec/views/organizations/show.html.erb_spec.rb b/spec/views/organizations/show.html.erb_spec.rb index 28838201c9..5954aca3d1 100644 --- a/spec/views/organizations/show.html.erb_spec.rb +++ b/spec/views/organizations/show.html.erb_spec.rb @@ -8,6 +8,7 @@ before do assign(:organization, organization) + sign_in user allow(view).to receive(:current_user).and_return(user) render end @@ -40,4 +41,38 @@ expect(rendered).to match(/Facilitator.*Director/) end end + + context "with people a viewer can't open" do + let(:facilitator) { create(:person, first_name: "Fay", last_name: "Active") } + let(:director) { create(:person, first_name: "Dee", last_name: "Director") } + let(:lapsed) { create(:person, first_name: "Lee", last_name: "Lapsed") } + let(:hidden) { create(:person, first_name: "Hal", last_name: "Hidden", profile_is_searchable: false) } + + before do + create(:affiliation, person: facilitator, organization: organization) + create(:affiliation, person: director, organization: organization, title: "Director") + create(:affiliation, person: lapsed, organization: organization, end_date: 1.month.ago) + create(:affiliation, person: hidden, organization: organization) + assign(:organization, organization.reload) + render + end + + it "lists only active facilitators whose profile the viewer can open" do + expect(rendered).to include(person_path(facilitator)) + expect(rendered).not_to include(person_path(director)) + expect(rendered).not_to include(person_path(lapsed)) + expect(rendered).not_to include(person_path(hidden)) + end + + context "when the viewer is an admin" do + let(:user) { admin } + + it "lists every active facilitator, but not other titles" do + expect(rendered).to include(person_path(facilitator)) + expect(rendered).to include(person_path(hidden)) + expect(rendered).not_to include(person_path(director)) + expect(rendered).not_to include(person_path(lapsed)) + end + end + end end From 4f90d22ca5803e790cd3e81b46c4cfab6362cb5c Mon Sep 17 00:00:00 2001 From: Mae Beale Date: Fri, 23 Oct 2026 08:08:34 -0400 Subject: [PATCH 3/8] Scope the feature note to the org list now that main links bylines by policy Co-Authored-By: Claude Opus 5.5 (1M context) --- .../2026-10-22-org-pages-list-active-facilitators.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml b/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml index dc6cdd98bd..8769fe2af3 100644 --- a/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml +++ b/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml @@ -5,6 +5,6 @@ released_on: 2026-10-22 pr_number: 2785 summary: >- An organization's people list now shows only its active facilitators whose profiles you can open, - and author names link to a profile only when you can open it. + so every name on it leads to a profile. pro_tips: - - "An author whose profile you can't open shows as plain text; on organization pages they're left off the list." + - "A facilitator's card still shows their other titles at the organization, like Director." From 3235d92289df85d875f24d3c0f37b4a87d314148 Mon Sep 17 00:00:00 2001 From: Mae Beale Date: Fri, 23 Oct 2026 10:47:53 -0400 Subject: [PATCH 4/8] Let admins keep seeing everyone affiliated with an org Only non-admins hit dead-end profile links, so the facilitator-only filter applies to them; admins still need the full roster. Co-Authored-By: Claude Opus 5.5 (1M context) --- app/decorators/organization_decorator.rb | 16 ++++++++------ app/views/organizations/show.html.erb | 8 ++++--- .../decorators/organization_decorator_spec.rb | 22 ++++++++++++++----- .../views/organizations/show.html.erb_spec.rb | 4 ++-- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/app/decorators/organization_decorator.rb b/app/decorators/organization_decorator.rb index 48ba7c90b8..a8d0c0920b 100644 --- a/app/decorators/organization_decorator.rb +++ b/app/decorators/organization_decorator.rb @@ -235,13 +235,15 @@ def badges badges end - # Keeps every active title per person so a facilitator's card also lists e.g. "Director". - def active_affiliations_by_facilitator - object.affiliations - .select { |affiliation| affiliation.active? && affiliation.person } - .sort_by { |affiliation| [ affiliation.person.first_name.to_s.downcase, affiliation.person.last_name.to_s.downcase ] } - .group_by(&:person) - .select { |_person, affiliations| affiliations.any?(&:facilitator?) } + # facilitators_only keeps every active title per person, so a facilitator's card still lists e.g. "Director". + def active_affiliations_by_person(facilitators_only: false) + grouped = object.affiliations + .select { |affiliation| affiliation.active? && affiliation.person } + .sort_by { |affiliation| [ affiliation.person.first_name.to_s.downcase, affiliation.person.last_name.to_s.downcase ] } + .group_by(&:person) + return grouped unless facilitators_only + + grouped.select { |_person, affiliations| affiliations.any?(&:facilitator?) } end private diff --git a/app/views/organizations/show.html.erb b/app/views/organizations/show.html.erb index c243d1df7f..ec033c0818 100644 --- a/app/views/organizations/show.html.erb +++ b/app/views/organizations/show.html.erb @@ -131,8 +131,10 @@
-

Facilitators

- <% grouped = org_decorated.active_affiliations_by_facilitator.select { |person, _| allowed_to?(:show?, person) } %> + <% admin_view = current_user&.super_user? %> +

<%= admin_view ? "Affiliations" : "Facilitators" %>

+ <% grouped = org_decorated.active_affiliations_by_person(facilitators_only: !admin_view) + .select { |person, _| allowed_to?(:show?, person) } %> <% if grouped.any? %>
<% grouped.each do |person, person_affiliations| %> @@ -145,7 +147,7 @@
<% else %> -

No active facilitators.

+

<%= admin_view ? "No active affiliations." : "No active facilitators." %>

<% end %>
diff --git a/spec/decorators/organization_decorator_spec.rb b/spec/decorators/organization_decorator_spec.rb index 70e72ffe18..73ac0a408f 100644 --- a/spec/decorators/organization_decorator_spec.rb +++ b/spec/decorators/organization_decorator_spec.rb @@ -1,20 +1,30 @@ require "rails_helper" RSpec.describe OrganizationDecorator do - describe "#active_affiliations_by_facilitator" do + describe "#active_affiliations_by_person" do let(:organization) { create(:organization) } let(:facilitator) { create(:person, first_name: "Zoe") } let(:earlier) { create(:person, first_name: "Abe") } + let(:board_member) { create(:person, first_name: "Mia") } + let!(:facilitation) { create(:affiliation, organization: organization, person: facilitator) } + let!(:director) { create(:affiliation, organization: organization, person: facilitator, title: "Director") } - it "groups each active facilitator's active titles, sorted by name, leaving out everyone else" do - facilitation = create(:affiliation, organization: organization, person: facilitator) - director = create(:affiliation, organization: organization, person: facilitator, title: "Director") + before do create(:affiliation, organization: organization, person: facilitator, title: "Counselor", end_date: 1.month.ago) create(:affiliation, organization: organization, person: earlier) - create(:affiliation, organization: organization, title: "Board Member") + create(:affiliation, organization: organization, person: board_member, title: "Board Member") create(:affiliation, organization: organization, end_date: 1.month.ago) + end + + it "groups each actively affiliated person's active titles, sorted by name" do + grouped = organization.reload.decorate.active_affiliations_by_person + + expect(grouped.keys).to eq([ earlier, board_member, facilitator ]) + expect(grouped[facilitator]).to contain_exactly(facilitation, director) + end - grouped = organization.reload.decorate.active_affiliations_by_facilitator + it "keeps only active facilitators, with all their active titles, when facilitators_only" do + grouped = organization.reload.decorate.active_affiliations_by_person(facilitators_only: true) expect(grouped.keys).to eq([ earlier, facilitator ]) expect(grouped[facilitator]).to contain_exactly(facilitation, director) diff --git a/spec/views/organizations/show.html.erb_spec.rb b/spec/views/organizations/show.html.erb_spec.rb index 5954aca3d1..8e525a3c0d 100644 --- a/spec/views/organizations/show.html.erb_spec.rb +++ b/spec/views/organizations/show.html.erb_spec.rb @@ -67,10 +67,10 @@ context "when the viewer is an admin" do let(:user) { admin } - it "lists every active facilitator, but not other titles" do + it "lists every actively affiliated person, whatever their title" do expect(rendered).to include(person_path(facilitator)) expect(rendered).to include(person_path(hidden)) - expect(rendered).not_to include(person_path(director)) + expect(rendered).to include(person_path(director)) expect(rendered).not_to include(person_path(lapsed)) end end From e1ecfaf88a6cb0524ec67182a85aacf79c3a0646 Mon Sep 17 00:00:00 2001 From: Mae Beale Date: Fri, 23 Oct 2026 10:49:43 -0400 Subject: [PATCH 5/8] Mark people facilitators can't open, and stop linking hidden bookmark creators Admins see the full roster, so a lock tells them which profiles a facilitator would hit a dead end on. The personal bookmarks creator button had the same dead-end link the org list did. Co-Authored-By: Claude Opus 5.5 (1M context) --- app/views/bookmarks/_personal_row.html.erb | 4 +++- app/views/organizations/show.html.erb | 5 ++++- ...-22-org-pages-list-active-facilitators.yml | 6 ++++-- spec/requests/bookmarks_spec.rb | 21 +++++++++++++++++++ .../views/organizations/show.html.erb_spec.rb | 5 +++++ 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/app/views/bookmarks/_personal_row.html.erb b/app/views/bookmarks/_personal_row.html.erb index 16131eb60c..5d8c143474 100644 --- a/app/views/bookmarks/_personal_row.html.erb +++ b/app/views/bookmarks/_personal_row.html.erb @@ -57,8 +57,10 @@
<% author = bookmarkable.created_by if bookmarkable.respond_to?(:created_by) %> Created by: - <% if author&.person %> + <% if author&.person && allowed_to?(:show?, author.person) %> <%= person_profile_button(author.person, compact: true, width_class: "inline-flex w-fit", data: { turbo: false }) %> + <% elsif author&.person %> + <%= person_name(author.person) %> <% elsif author %> <%= user_button(author, data: { turbo: false }) %> <% elsif bookmark.bookmarkable_type == "Workshop" %> diff --git a/app/views/organizations/show.html.erb b/app/views/organizations/show.html.erb index ec033c0818..3a9ddbb855 100644 --- a/app/views/organizations/show.html.erb +++ b/app/views/organizations/show.html.erb @@ -138,10 +138,13 @@ <% if grouped.any? %>
<% grouped.each do |person, person_affiliations| %> -
+
<% titles = person_affiliations.filter_map { |a| a.title.presence }.uniq %> <% titles = titles.sort_by { |t| [t.downcase.include?("facilitator") ? 0 : 1, t.downcase] } %> <%= person_profile_button(person, subtitle: titles.any? ? titles.join(", ") : nil) %> + <% if admin_view && !person.published? %> + + <% end %>
<% end %>
diff --git a/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml b/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml index 8769fe2af3..3a47fc2809 100644 --- a/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml +++ b/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml @@ -4,7 +4,9 @@ display_status: user_facing released_on: 2026-10-22 pr_number: 2785 summary: >- - An organization's people list now shows only its active facilitators whose profiles you can open, - so every name on it leads to a profile. + An organization's people list now shows facilitators only the active facilitators whose profiles + they can open, so every name on it leads to a profile. pro_tips: - "A facilitator's card still shows their other titles at the organization, like Director." + - "Admins still see everyone affiliated with the organization; a lock marks people facilitators can't open." + - "On My bookmarks, a creator whose profile you can't open shows as a plain name." diff --git a/spec/requests/bookmarks_spec.rb b/spec/requests/bookmarks_spec.rb index 8630d2fdfc..b740debfe8 100644 --- a/spec/requests/bookmarks_spec.rb +++ b/spec/requests/bookmarks_spec.rb @@ -129,6 +129,27 @@ expect(response).to have_http_status(:ok) end + context "with the bookmarked item's creator" do + let(:creator) { create(:user, person: create(:person, first_name: "Cara", last_name: "Creator")) } + + before { workshop.update_columns(created_by_id: creator.id) } + + it "links the creator's profile when the viewer can open it" do + create(:affiliation, person: creator.person) + + get personal_bookmarks_path, headers: { "Turbo-Frame" => "personal_bookmarks_results" } + + expect(response.body).to include(person_path(creator.person)) + end + + it "shows the creator's name without a link when the viewer can't open the profile" do + get personal_bookmarks_path, headers: { "Turbo-Frame" => "personal_bookmarks_results" } + + expect(response.body).not_to include(person_path(creator.person)) + expect(response.body).to include("Cara") + end + end + it "can filter personal bookmarks by keyword via turbo frame" do get personal_bookmarks_path, params: { keyword: workshop.title }, headers: { "Turbo-Frame" => "personal_bookmarks_results" } diff --git a/spec/views/organizations/show.html.erb_spec.rb b/spec/views/organizations/show.html.erb_spec.rb index 8e525a3c0d..177e7d1a8e 100644 --- a/spec/views/organizations/show.html.erb_spec.rb +++ b/spec/views/organizations/show.html.erb_spec.rb @@ -58,6 +58,7 @@ end it "lists only active facilitators whose profile the viewer can open" do + expect(rendered).not_to include("profile-hidden-lock") expect(rendered).to include(person_path(facilitator)) expect(rendered).not_to include(person_path(director)) expect(rendered).not_to include(person_path(lapsed)) @@ -73,6 +74,10 @@ expect(rendered).to include(person_path(director)) expect(rendered).not_to include(person_path(lapsed)) end + + it "locks only the people facilitators can't open" do + expect(rendered.scan('data-testid="profile-hidden-lock"').count).to eq(2) + end end end end From c42fd55e96a6fb57c288d150c84be3bd652e01dc Mon Sep 17 00:00:00 2001 From: Mae Beale Date: Fri, 23 Oct 2026 11:06:39 -0400 Subject: [PATCH 6/8] Make the hidden-profile lock big enough to notice The 10px corner icon blended into the card border. Co-Authored-By: Claude Opus 5.5 (1M context) --- app/views/organizations/show.html.erb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/views/organizations/show.html.erb b/app/views/organizations/show.html.erb index 3a9ddbb855..0a68e4739e 100644 --- a/app/views/organizations/show.html.erb +++ b/app/views/organizations/show.html.erb @@ -143,7 +143,10 @@ <% titles = titles.sort_by { |t| [t.downcase.include?("facilitator") ? 0 : 1, t.downcase] } %> <%= person_profile_button(person, subtitle: titles.any? ? titles.join(", ") : nil) %> <% if admin_view && !person.published? %> - + + + <% end %>
<% end %> From 760d8823af9f55ddee066786feb2bca1013a3593 Mon Sep 17 00:00:00 2001 From: Mae Beale Date: Fri, 23 Oct 2026 11:17:55 -0400 Subject: [PATCH 7/8] List only facilitators on org show for admins too, lock inside the card The section is the org's facilitator roster for every viewer; the lock sits in the card corner so it can't spill outside the grid. Co-Authored-By: Claude Opus 5.5 (1M context) --- app/decorators/organization_decorator.rb | 16 ++++++-------- app/views/organizations/show.html.erb | 14 +++++------- ...-22-org-pages-list-active-facilitators.yml | 6 ++--- .../decorators/organization_decorator_spec.rb | 22 +++++-------------- .../views/organizations/show.html.erb_spec.rb | 8 +++---- 5 files changed, 26 insertions(+), 40 deletions(-) diff --git a/app/decorators/organization_decorator.rb b/app/decorators/organization_decorator.rb index a8d0c0920b..48ba7c90b8 100644 --- a/app/decorators/organization_decorator.rb +++ b/app/decorators/organization_decorator.rb @@ -235,15 +235,13 @@ def badges badges end - # facilitators_only keeps every active title per person, so a facilitator's card still lists e.g. "Director". - def active_affiliations_by_person(facilitators_only: false) - grouped = object.affiliations - .select { |affiliation| affiliation.active? && affiliation.person } - .sort_by { |affiliation| [ affiliation.person.first_name.to_s.downcase, affiliation.person.last_name.to_s.downcase ] } - .group_by(&:person) - return grouped unless facilitators_only - - grouped.select { |_person, affiliations| affiliations.any?(&:facilitator?) } + # Keeps every active title per person so a facilitator's card also lists e.g. "Director". + def active_affiliations_by_facilitator + object.affiliations + .select { |affiliation| affiliation.active? && affiliation.person } + .sort_by { |affiliation| [ affiliation.person.first_name.to_s.downcase, affiliation.person.last_name.to_s.downcase ] } + .group_by(&:person) + .select { |_person, affiliations| affiliations.any?(&:facilitator?) } end private diff --git a/app/views/organizations/show.html.erb b/app/views/organizations/show.html.erb index 0a68e4739e..e24f0c8f59 100644 --- a/app/views/organizations/show.html.erb +++ b/app/views/organizations/show.html.erb @@ -131,10 +131,8 @@
- <% admin_view = current_user&.super_user? %> -

<%= admin_view ? "Affiliations" : "Facilitators" %>

- <% grouped = org_decorated.active_affiliations_by_person(facilitators_only: !admin_view) - .select { |person, _| allowed_to?(:show?, person) } %> +

Facilitators

+ <% grouped = org_decorated.active_affiliations_by_facilitator.select { |person, _| allowed_to?(:show?, person) } %> <% if grouped.any? %>
<% grouped.each do |person, person_affiliations| %> @@ -142,10 +140,10 @@ <% titles = person_affiliations.filter_map { |a| a.title.presence }.uniq %> <% titles = titles.sort_by { |t| [t.downcase.include?("facilitator") ? 0 : 1, t.downcase] } %> <%= person_profile_button(person, subtitle: titles.any? ? titles.join(", ") : nil) %> - <% if admin_view && !person.published? %> - + - + <% end %>
@@ -153,7 +151,7 @@
<% else %> -

<%= admin_view ? "No active affiliations." : "No active facilitators." %>

+

No active facilitators.

<% end %>
diff --git a/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml b/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml index 3a47fc2809..4f5e03fef7 100644 --- a/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml +++ b/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml @@ -4,9 +4,9 @@ display_status: user_facing released_on: 2026-10-22 pr_number: 2785 summary: >- - An organization's people list now shows facilitators only the active facilitators whose profiles - they can open, so every name on it leads to a profile. + An organization page now lists its active facilitators, and facilitators see only the ones whose + profiles they can open, so every name leads to a profile. pro_tips: - "A facilitator's card still shows their other titles at the organization, like Director." - - "Admins still see everyone affiliated with the organization; a lock marks people facilitators can't open." + - "Admins see every active facilitator; a lock marks the ones other facilitators can't open." - "On My bookmarks, a creator whose profile you can't open shows as a plain name." diff --git a/spec/decorators/organization_decorator_spec.rb b/spec/decorators/organization_decorator_spec.rb index 73ac0a408f..70e72ffe18 100644 --- a/spec/decorators/organization_decorator_spec.rb +++ b/spec/decorators/organization_decorator_spec.rb @@ -1,30 +1,20 @@ require "rails_helper" RSpec.describe OrganizationDecorator do - describe "#active_affiliations_by_person" do + describe "#active_affiliations_by_facilitator" do let(:organization) { create(:organization) } let(:facilitator) { create(:person, first_name: "Zoe") } let(:earlier) { create(:person, first_name: "Abe") } - let(:board_member) { create(:person, first_name: "Mia") } - let!(:facilitation) { create(:affiliation, organization: organization, person: facilitator) } - let!(:director) { create(:affiliation, organization: organization, person: facilitator, title: "Director") } - before do + it "groups each active facilitator's active titles, sorted by name, leaving out everyone else" do + facilitation = create(:affiliation, organization: organization, person: facilitator) + director = create(:affiliation, organization: organization, person: facilitator, title: "Director") create(:affiliation, organization: organization, person: facilitator, title: "Counselor", end_date: 1.month.ago) create(:affiliation, organization: organization, person: earlier) - create(:affiliation, organization: organization, person: board_member, title: "Board Member") + create(:affiliation, organization: organization, title: "Board Member") create(:affiliation, organization: organization, end_date: 1.month.ago) - end - - it "groups each actively affiliated person's active titles, sorted by name" do - grouped = organization.reload.decorate.active_affiliations_by_person - - expect(grouped.keys).to eq([ earlier, board_member, facilitator ]) - expect(grouped[facilitator]).to contain_exactly(facilitation, director) - end - it "keeps only active facilitators, with all their active titles, when facilitators_only" do - grouped = organization.reload.decorate.active_affiliations_by_person(facilitators_only: true) + grouped = organization.reload.decorate.active_affiliations_by_facilitator expect(grouped.keys).to eq([ earlier, facilitator ]) expect(grouped[facilitator]).to contain_exactly(facilitation, director) diff --git a/spec/views/organizations/show.html.erb_spec.rb b/spec/views/organizations/show.html.erb_spec.rb index 177e7d1a8e..23cc302aee 100644 --- a/spec/views/organizations/show.html.erb_spec.rb +++ b/spec/views/organizations/show.html.erb_spec.rb @@ -68,15 +68,15 @@ context "when the viewer is an admin" do let(:user) { admin } - it "lists every actively affiliated person, whatever their title" do + it "lists every active facilitator, but not other titles" do expect(rendered).to include(person_path(facilitator)) expect(rendered).to include(person_path(hidden)) - expect(rendered).to include(person_path(director)) + expect(rendered).not_to include(person_path(director)) expect(rendered).not_to include(person_path(lapsed)) end - it "locks only the people facilitators can't open" do - expect(rendered.scan('data-testid="profile-hidden-lock"').count).to eq(2) + it "locks only the facilitators others can't open" do + expect(rendered.scan('data-testid="profile-hidden-lock"').count).to eq(1) end end end From f638925d52c856436afc02516f7bec72fd8ca297 Mon Sep 17 00:00:00 2001 From: Mae Beale Date: Fri, 23 Oct 2026 11:24:48 -0400 Subject: [PATCH 8/8] Say the lock means facilitators don't see the card at all Co-Authored-By: Claude Opus 5.5 (1M context) --- app/views/organizations/show.html.erb | 2 +- .../features/2026-10-22-org-pages-list-active-facilitators.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/organizations/show.html.erb b/app/views/organizations/show.html.erb index e24f0c8f59..ef74d0904c 100644 --- a/app/views/organizations/show.html.erb +++ b/app/views/organizations/show.html.erb @@ -142,7 +142,7 @@ <%= person_profile_button(person, subtitle: titles.any? ? titles.join(", ") : nil) %> <% if current_user&.super_user? && !person.published? %> + title="Hidden from facilitators — only admins see this card" data-testid="profile-hidden-lock"> <% end %> diff --git a/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml b/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml index 4f5e03fef7..892b94d15c 100644 --- a/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml +++ b/db/seeds/features/2026-10-22-org-pages-list-active-facilitators.yml @@ -8,5 +8,5 @@ summary: >- profiles they can open, so every name leads to a profile. pro_tips: - "A facilitator's card still shows their other titles at the organization, like Director." - - "Admins see every active facilitator; a lock marks the ones other facilitators can't open." + - "Admins see every active facilitator; a lock marks the ones hidden from facilitators, who don't see those cards." - "On My bookmarks, a creator whose profile you can't open shows as a plain name."