diff --git a/app/views/comments_and_communications/_communication_row_readonly.html.erb b/app/views/comments_and_communications/_communication_row_readonly.html.erb
index b14046b6d7..34784b7bc5 100644
--- a/app/views/comments_and_communications/_communication_row_readonly.html.erb
+++ b/app/views/comments_and_communications/_communication_row_readonly.html.erb
@@ -6,9 +6,11 @@
Notification where entry.manual_log? is false). Optional: person (scopes the
date link's feed). %>
<% source_chip = feed_source_chip(entry.noticeable, icon: entry.decorate.channel_icon) %>
+<%# Only the everyone feed names who each row is about. %>
+<% leading = local_assigns[:person] ? source_chip : safe_join([ feed_person_chip(entry), source_chip || entry.decorate.channel_chip ].compact, " ") %>
<% trailing = content_tag(:span, render("notifications/responded_checkbox", notification: entry), class: "ml-auto shrink-0") %>
<%= tag.div id: dom_id(entry), data: expandable_feed_row_data(clamp: false, key: dom_id(entry)) do %>
<%= render "notifications/communication_line", notification: entry, admin: true, truncate: false, expandable: true,
- date_path: feed_entry_path(entry, person: local_assigns[:person]),
- leading: source_chip, trailing: trailing, flag: render("notifications/flag_toggle", notification: entry) %>
+ date_path: feed_entry_path(entry, person: local_assigns[:person]), feed_person: local_assigns[:person],
+ leading: leading, trailing: trailing, flag: render("notifications/flag_toggle", notification: entry) %>
<% end %>
diff --git a/app/views/comments_and_communications/index.html.erb b/app/views/comments_and_communications/index.html.erb
index 99f949ad9b..ffa1dc2d81 100644
--- a/app/views/comments_and_communications/index.html.erb
+++ b/app/views/comments_and_communications/index.html.erb
@@ -27,13 +27,13 @@
<% subtitle = capture do %>
<%= pluralize(@total_count, "entry", plural: "entries") %><% if @flagged_count.positive? %> ·
<% end %>
<% end %>
-<%# Title: the person's name (or "Everyone"). On a person's feed their activity,
- profile, and the composer triggers sit beside it; the composer panels open in
+<%# Title: the person's profile chip (or "Everyone"). On a person's feed their
+ activity and the composer triggers sit beside it; the composer panels open in
the body, sharing the card-level panel-toggle scope (content_data). The person
picker that scopes the feed lives in the filter row below. %>
<% title = capture do %>
- <%= @person ? tag.span(@person.full_name, title: @person.preferred_email) : "Everyone" %>
+ <%= @person ? render("people/person_chip", person: @person) : "Everyone" %>
<% if @person %>
<% if allowed_to?(:index?, with: Admin::AhoyActivityPolicy) %>
@@ -42,10 +42,6 @@
<%= @person.first_name %>'s activity
<% end %>
<% end %>
- <%= link_to person_path(@person), class: button_classes(:primary_outline) do %>
-
- <%= @person.first_name %>'s profile
- <% end %>
<%= render "comments_and_communications/add_buttons" %>
<% end %>
diff --git a/app/views/notifications/_communication_line.html.erb b/app/views/notifications/_communication_line.html.erb
index b79d53fc0d..aa8bbf1f3f 100644
--- a/app/views/notifications/_communication_line.html.erb
+++ b/app/views/notifications/_communication_line.html.erb
@@ -8,7 +8,8 @@
neutral source chip, pointing at that record's edit page, with the channel icon
inside it). Optional expandable adds a chevron that unclamps a truncated row
(outside the card link so it doesn't navigate) or hides a full row's body;
- date_path (+ date_new_tab) links the timestamp to the feed narrowed to this entry. %>
+ date_path (+ date_new_tab) links the timestamp to the feed narrowed to this entry;
+ feed_person is the person whose feed the row sits on, for the sender chip's back link. %>
<% admin = local_assigns.fetch(:admin, true) %>
<% mark_admin_only = local_assigns.fetch(:mark_admin_only, false) %>
<% truncate_body = local_assigns.fetch(:truncate, true) %>
@@ -39,9 +40,20 @@
path: local_assigns[:date_path], new_tab: local_assigns[:date_new_tab] %>
<% end %>
<% main = capture do %>
-
- <%= truncate(from_first.to_s, length: 10, omission: "..") %>
-
+ <% from_chip_class = "#{chip[0]} #{chip[1]} max-w-full truncate rounded px-1.5 py-0.5 text-left text-xs" %>
+ <% from_label = truncate(from_first.to_s, length: 10, omission: "..") %>
+ <% if !truncate_body && decorated.from_person %>
+ <%# Only the untruncated row can link the person: a card-wide link can't nest one. %>
+
+ <%= link_to from_label, feed_person_edit_path(decorated.from_person, feed_person: local_assigns[:feed_person]), target: "_blank", rel: "noopener",
+ class: "#{from_chip_class} hover:underline" %>
+ <%= facilitator_hover_card(decorated.from_person, header: true) %>
+
+ <% else %>
+
+ <%= from_label %>
+
+ <% end %>
<%# Subject and body share one column so the body starts flush with the subject. %>
<% if truncate_body %>
diff --git a/app/views/people/_facilitator_hover_card.html.erb b/app/views/people/_facilitator_hover_card.html.erb
new file mode 100644
index 0000000000..3d85812bfa
--- /dev/null
+++ b/app/views/people/_facilitator_hover_card.html.erb
@@ -0,0 +1,60 @@
+<%# Admin hover card for a person chip: when they first affiliated, each active
+ facilitator affiliation's org, org status, and dates, their latest
+ facilitator trainings with attendance, then their membership status. One
+ grid, so every status pill shares the left column's width. Shown by a
+ `group/person` ancestor. Locals: person. Optional: header (name + email on
+ top, for chips too small to show them). %>
+<% person = person.decorate %>
+<% affiliated_since = person.affiliated_since_date %>
+<% facilitations = person.active_facilitator_affiliations %>
+<% trainings = person.recent_facilitator_training_registrations %>
+<% pill_cell = "flex self-start *:w-full *:justify-center" %>
+<% heading = "col-span-2 mt-3 text-xs font-semibold tracking-wide text-gray-500 uppercase" %>
+<% empty = "col-span-2 mt-1 text-gray-500" %>
+
+ <% if local_assigns[:header] %>
+ <%= person.full_name %>
+ <%= person.preferred_email %>
+ <% end %>
+
+ <%= affiliated_since ? "Affiliated since #{affiliated_since.strftime('%B %Y')}" : "No affiliation dates recorded" %>
+
+ <% if facilitations.any? %>
+ <% facilitations.each do |affiliation| %>
+ <% affiliation = affiliation.decorate %>
+ <%= affiliation.organization.decorate.organization_status_chip(admin: true) %>
+
+ <%= affiliation.organization.name %>
+ <%= affiliation.period_note %>
+
+ <% end %>
+ <% else %>
+ No active facilitator affiliation
+ <% end %>
+ Facilitator trainings
+ <% if trainings.any? %>
+ <% trainings.each do |registration| %>
+ <% registration = registration.decorate %>
+ <%= render "shared/badge", **registration.attendance_status_badge %>
+ <%# An on-demand training spans its year, so the year stands in for a date range. %>
+
+ <%= registration.event.on_demand? ? "On-demand #{registration.event.start_date&.in_time_zone&.year}" : registration.event.decorate.short_date_range %>
+
+ <% end %>
+ <% else %>
+ None
+ <% end %>
+ <% if Membership.enabled? %>
+ <% membership = person.current_membership&.decorate %>
+ Membership
+ <% if membership %>
+ <% if membership.payment_badge? %><%= render "shared/badge", label: membership.payment_label, classes: membership.payment_classes %><% end %>
+
+ <%= membership.status_label %>
+ <%= membership.period_label %>
+
+ <% else %>
+ None
+ <% end %>
+ <% end %>
+
diff --git a/app/views/people/_form.html.erb b/app/views/people/_form.html.erb
index d5eac63911..55cf30bf77 100644
--- a/app/views/people/_form.html.erb
+++ b/app/views/people/_form.html.erb
@@ -16,6 +16,9 @@
<%= hidden_field_tag :check, params[:check] if params[:check].present? %>
<% elsif params[:return_to] == "dedupe" %>
<%= hidden_field_tag :return_to, "dedupe" %>
+ <% elsif params[:return_to] == CommentsHelper::FEED_RETURN_TO %>
+ <%= hidden_field_tag :return_to, CommentsHelper::FEED_RETURN_TO %>
+ <%= hidden_field_tag :feed_person_id, params[:feed_person_id] if params[:feed_person_id].present? %>
<% elsif params[:return_to] == "registration_link" && params[:event_registration_id].present? %>
<%= hidden_field_tag :return_to, "registration_link" %>
<%= hidden_field_tag :event_registration_id, params[:event_registration_id] %>
diff --git a/app/views/people/_person_chip.html.erb b/app/views/people/_person_chip.html.erb
new file mode 100644
index 0000000000..d838c7a343
--- /dev/null
+++ b/app/views/people/_person_chip.html.erb
@@ -0,0 +1,7 @@
+<%# The person's edit chip with their facilitator hover card, for the title of
+ their comments & communications feed. Locals: person. %>
+
+ <%= person_edit_button(person, subtitle: person.preferred_email, width_class: "w-auto max-w-xs",
+ show_title: false, path_params: { return_to: CommentsHelper::FEED_RETURN_TO, feed_person_id: person.id }) %>
+ <%= facilitator_hover_card(person) %>
+
diff --git a/app/views/people/edit.html.erb b/app/views/people/edit.html.erb
index 8d69e6b3e1..87ab3a2de6 100644
--- a/app/views/people/edit.html.erb
+++ b/app/views/people/edit.html.erb
@@ -24,6 +24,12 @@
Dedupe people
<% end %>
+ <% elsif params[:return_to] == CommentsHelper::FEED_RETURN_TO %>
+
+ <%= link_to feed_return_path, class: "text-sm #{eyebrow_link_class} px-2 py-1" do %>
+ Comments & communications
+ <% end %>
+
<% elsif params[:return_to] == "registration_link" && params[:event_registration_id].present? %>
<%= link_to link_organization_event_registration_path(params[:event_registration_id], return_to: params[:link_org_return_to].presence), class: "text-sm #{eyebrow_link_class} px-2 py-1" do %>
diff --git a/db/seeds/features/2026-11-04-comms-person-chip-facilitator-hover.yml b/db/seeds/features/2026-11-04-comms-person-chip-facilitator-hover.yml
new file mode 100644
index 0000000000..5cce279928
--- /dev/null
+++ b/db/seeds/features/2026-11-04-comms-person-chip-facilitator-hover.yml
@@ -0,0 +1,13 @@
+name: "See who each comment or communication is about, and their facilitator standing"
+area: communications
+display_status: admin_facing
+released_on: 2026-11-04
+pr_number: 2766
+summary: >-
+ On the everyone view of comments & communications, each row now names the
+ person it's about. Hover a name to see when they first affiliated, their
+ active facilitator affiliations, latest trainings with attendance, and
+ membership status.
+pro_tips:
+ - "Click a name to edit that person; the edit page links you back to the comments & communications you came from."
+ - "Dates close to today read in plain words, like \"ending next month\", so a lapsing term stands out."
diff --git a/spec/decorators/affiliation_decorator_spec.rb b/spec/decorators/affiliation_decorator_spec.rb
index d19b309891..d9a9dabc3e 100644
--- a/spec/decorators/affiliation_decorator_spec.rb
+++ b/spec/decorators/affiliation_decorator_spec.rb
@@ -51,6 +51,34 @@
# An inactive row sits on the Inactive tab, so a back link must land on the
# section rather than a row the page isn't showing.
+ describe "#period_note" do
+ around { |example| travel_to(Date.new(2026, 10, 20)) { example.run } }
+
+ it "reads an imminent end in relative terms" do
+ affiliation = build(:affiliation, start_date: Date.new(2023, 3, 1), end_date: Date.new(2026, 11, 5)).decorate
+ expect(affiliation.period_note).to eq("Started March 2023, ending next month")
+ end
+
+ it "names the month of a farther end" do
+ affiliation = build(:affiliation, start_date: Date.new(2023, 3, 1), end_date: Date.new(2027, 3, 1)).decorate
+ expect(affiliation.period_note).to eq("Started March 2023, ending March 2027")
+ end
+
+ it "reads an open-ended term by its start alone" do
+ affiliation = build(:affiliation, start_date: Date.new(2026, 10, 1), end_date: nil).decorate
+ expect(affiliation.period_note).to eq("Started this month")
+ end
+
+ it "reads a scheduled term as starting" do
+ affiliation = build(:affiliation, start_date: Date.new(2026, 11, 5), end_date: nil).decorate
+ expect(affiliation.period_note).to eq("Starting next month")
+ end
+
+ it "notes when no dates are recorded" do
+ expect(build(:affiliation, start_date: nil, end_date: nil).decorate.period_note).to eq("Dates not recorded")
+ end
+ end
+
describe "#return_anchor" do
it "points at the row itself when the affiliation is active" do
affiliation = create(:affiliation, start_date: 1.year.ago.to_date, end_date: nil)
diff --git a/spec/decorators/comment_decorator_spec.rb b/spec/decorators/comment_decorator_spec.rb
index a9eb03ed9b..b6a680b2c8 100644
--- a/spec/decorators/comment_decorator_spec.rb
+++ b/spec/decorators/comment_decorator_spec.rb
@@ -102,6 +102,32 @@
end
end
+ describe "#about_person" do
+ it "is the person a profile comment is on" do
+ person = create(:person)
+ expect(create(:comment, commentable: person).decorate.about_person).to eq(person)
+ end
+
+ it "is the registrant behind a registration or CE registration comment" do
+ ce = create(:continuing_education_registration)
+ registrant = ce.event_registration.registrant
+
+ expect(create(:comment, commentable: ce.event_registration).decorate.about_person).to eq(registrant)
+ expect(create(:comment, commentable: ce).decorate.about_person).to eq(registrant)
+ end
+
+ it "is the credited author of an authored record" do
+ author = create(:person)
+ story = create(:story, author: author)
+
+ expect(create(:comment, commentable: story).decorate.about_person).to eq(author)
+ end
+
+ it "is nil for a record that belongs to no one person" do
+ expect(create(:comment, commentable: create(:organization)).decorate.about_person).to be_nil
+ end
+ end
+
describe "#event / #event_registration" do
it "resolves the event and registration for a registration comment" do
registration = create(:event_registration)
diff --git a/spec/decorators/person_decorator_spec.rb b/spec/decorators/person_decorator_spec.rb
index 3e12552572..9c5374c9f7 100644
--- a/spec/decorators/person_decorator_spec.rb
+++ b/spec/decorators/person_decorator_spec.rb
@@ -294,6 +294,49 @@
end
end
+ describe "#active_facilitator_affiliations" do
+ let(:person) { create(:person) }
+
+ it "lists running facilitator terms, earliest first" do
+ later = create(:affiliation, person: person, start_date: 1.year.ago, end_date: nil)
+ earlier = create(:affiliation, person: person, start_date: 2.years.ago, end_date: 1.month.from_now)
+
+ expect(person.decorate.active_facilitator_affiliations).to eq([ earlier, later ])
+ end
+
+ it "skips upcoming and ended terms and non-facilitator titles" do
+ create(:affiliation, person: person, start_date: 1.month.from_now, end_date: nil)
+ create(:affiliation, person: person, start_date: 3.years.ago, end_date: 1.year.ago)
+ create(:affiliation, person: person, title: "Board member", start_date: 1.year.ago)
+
+ expect(person.decorate.active_facilitator_affiliations).to be_empty
+ end
+ end
+
+ describe "#recent_facilitator_training_registrations" do
+ let(:person) { create(:person) }
+
+ it "returns their latest facilitator trainings, newest first, ignoring other events" do
+ old_training = create(:event, facilitator_training: true, start_date: 3.years.ago, end_date: 3.years.ago + 1.day)
+ recent_training = create(:event, facilitator_training: true, start_date: 1.month.ago, end_date: 1.month.ago + 1.day)
+ other_event = create(:event, facilitator_training: false, start_date: 1.week.ago, end_date: 1.week.ago + 1.day)
+ old_registration = create(:event_registration, registrant: person, event: old_training, status: "attended")
+ recent_registration = create(:event_registration, registrant: person, event: recent_training, status: "no_show")
+ create(:event_registration, registrant: person, event: other_event)
+
+ expect(person.decorate.recent_facilitator_training_registrations).to eq([ recent_registration, old_registration ])
+ end
+
+ it "caps the list" do
+ 4.times do |i|
+ event = create(:event, facilitator_training: true, start_date: (i + 1).months.ago, end_date: (i + 1).months.ago + 1.day)
+ create(:event_registration, registrant: person, event: event)
+ end
+
+ expect(person.decorate.recent_facilitator_training_registrations.size).to eq(3)
+ end
+ end
+
describe "#affiliated_since_note" do
let(:person) { create(:person) }
diff --git a/spec/requests/comments_and_communications_return_spec.rb b/spec/requests/comments_and_communications_return_spec.rb
new file mode 100644
index 0000000000..aaaae030d4
--- /dev/null
+++ b/spec/requests/comments_and_communications_return_spec.rb
@@ -0,0 +1,34 @@
+require "rails_helper"
+
+RSpec.describe "Returning to comments & communications from a person chip", type: :request do
+ let(:admin) { create(:user, :admin) }
+ let(:person) { create(:person) }
+
+ before { sign_in admin }
+
+ it "gives the person edit page an eyebrow back to that person's feed" do
+ get edit_person_path(person, return_to: "comments_and_communications", feed_person_id: person.id)
+
+ back = Nokogiri::HTML(response.body).css("a").find { |a| a.text.strip == "Comments & communications" }
+ expect(back["href"]).to eq(comments_and_communications_path(person_id: person.id))
+ end
+
+ it "gives the person edit page an eyebrow back to the everyone feed" do
+ get edit_person_path(person, return_to: "comments_and_communications")
+
+ back = Nokogiri::HTML(response.body).css("a").find { |a| a.text.strip == "Comments & communications" }
+ expect(back["href"]).to eq(comments_and_communications_path)
+ end
+
+ it "carries the origin through the form and returns to the feed after saving" do
+ get edit_person_path(person, return_to: "comments_and_communications", feed_person_id: person.id)
+
+ form = Nokogiri::HTML(response.body).at_css("form[action='#{person_path(person)}']")
+ expect(form.at_css("input[name='return_to'][value='comments_and_communications']")).to be_present
+ expect(form.at_css("input[name='feed_person_id'][value='#{person.id}']")).to be_present
+
+ patch person_path(person), params: { person: { first_name: "Updated" }, return_to: "comments_and_communications", feed_person_id: person.id }
+
+ expect(response).to redirect_to(comments_and_communications_path(person_id: person.id))
+ end
+end
diff --git a/spec/requests/comments_and_communications_spec.rb b/spec/requests/comments_and_communications_spec.rb
index d319fef255..7a9692ffb1 100644
--- a/spec/requests/comments_and_communications_spec.rb
+++ b/spec/requests/comments_and_communications_spec.rb
@@ -36,12 +36,74 @@
expect(form.at_css("input[type='hidden'][name='person_id']")).to be_nil
end
- it "shows the person's email on hover over their name in the title" do
+ it "titles the page with the person's edit chip, showing their name and email" do
get comments_and_communications_path(person_id: person.id)
- name = Nokogiri::HTML(response.body).at_css("h1 [title='primary@example.com']")
- expect(name.text).to eq(person.full_name)
- expect(name.name).to eq("span")
+ edit_path = edit_person_path(person, return_to: "comments_and_communications", feed_person_id: person.id)
+ chip = Nokogiri::HTML(response.body).at_css("h1 a[href='#{edit_path}']")
+ expect(chip.text).to include(person.full_name)
+ expect(chip.text).to include("primary@example.com")
+ end
+
+ it "shows affiliated-since and current facilitator terms on hover over the person chip" do
+ organization = create(:organization, name: "Riverside Shelter")
+ create(:affiliation, person: person, title: "Board member", start_date: Date.new(2021, 5, 1))
+ create(:affiliation, person: person, organization: organization, start_date: Date.new(2023, 3, 1), end_date: 2.years.from_now)
+
+ get comments_and_communications_path(person_id: person.id)
+
+ card = Nokogiri::HTML(response.body).at_css("h1 .group\\/person")
+ expect(card.text).to include("Affiliated since May 2021")
+ expect(card.text).to include("Riverside Shelter")
+ expect(card.text).to include("Active")
+ expect(card.text).to include("Started March 2023")
+ end
+
+ it "lists the person's latest facilitator trainings with attendance in the hover card" do
+ training = create(:event, title: "Art Facilitator Training: March 2026", facilitator_training: true,
+ start_date: Time.zone.local(2026, 3, 5, 9), end_date: Time.zone.local(2026, 3, 6, 17))
+ create(:event_registration, registrant: person, event: training, status: "attended")
+
+ get comments_and_communications_path(person_id: person.id)
+
+ card = Nokogiri::HTML(response.body).at_css("h1 .group\\/person")
+ expect(card.text).to include("Facilitator trainings")
+ expect(card.text).to include("Mar 5-6, 2026")
+ expect(card.text).not_to include("Art Facilitator Training")
+ expect(card.text).to include("Attended")
+ end
+
+ it "labels an on-demand training by its year" do
+ training = create(:event, title: "On-Demand Facilitator Training 2026", facilitator_training: true, on_demand: true,
+ start_date: Time.zone.local(2026, 1, 1, 9), end_date: Time.zone.local(2026, 12, 31, 17))
+ create(:event_registration, registrant: person, event: training, status: "registered")
+
+ get comments_and_communications_path(person_id: person.id)
+
+ card = Nokogiri::HTML(response.body).at_css("h1 .group\\/person")
+ expect(card.text).to include("On-demand 2026")
+ expect(card.text).not_to include("On-Demand Facilitator Training")
+ expect(card.text).not_to include("Jan 1")
+ end
+
+ it "shows the person's membership status, payment, and term in the hover card" do
+ membership = create(:membership, person: person, start_date: Date.new(2026, 1, 1), end_date: Date.new(2026, 12, 31))
+ create(:membership_invoice, membership: membership, start_date: Date.new(2026, 1, 1), end_date: Date.new(2026, 12, 31), cost_cents: 0)
+
+ travel_to(Date.new(2026, 10, 21)) { get comments_and_communications_path(person_id: person.id) }
+
+ card = Nokogiri::HTML(response.body).at_css("h1 .group\\/person")
+ expect(card.text).to include("Membership")
+ expect(card.text).to include("Active")
+ expect(card.text).to include("Jan 1, 2026 – Dec 31, 2026")
+ expect(card.text).to include("Paid")
+ end
+
+ it "says a person without a membership has none in the hover card" do
+ get comments_and_communications_path(person_id: person.id)
+
+ card = Nokogiri::HTML(response.body).at_css("h1 .group\\/person")
+ expect(card.text.squish).to include("Membership None")
end
it "links to the person's activity history from the header" do
@@ -52,8 +114,6 @@
link = doc.css("a").find { |a| a.text.strip == "Aisha's activity" }
expect(link).to be_present
expect(link["href"]).to eq(admin_activities_events_path(person_id: person.id, time_period: "all_time", audience: %w[visitors users staff], hide_account: "1", hide_interactions: "1"))
- profile = doc.css("a").find { |a| a.text.strip == "Aisha's profile" }
- expect(profile["href"]).to eq(person_path(person))
everyone = doc.css("a").find { |a| a.text.strip == "View everyone" }
expect(everyone["href"]).to eq(comments_and_communications_path)
end
@@ -380,6 +440,34 @@
end
end
+ it "names who each row is about, linking to edit that person" do
+ registration = create(:event_registration, registrant: person)
+ comment = create(:comment, commentable: registration, body: "Registration note", created_by: admin)
+ known = create(:notification, recipient_email: "primary@example.com", email_subject: "Known contact",
+ kind: "manual_log", channel: "email", recipient_role: "person", notification_type: 0)
+ unknown = create(:notification, recipient_email: "stranger@example.com", email_subject: "Unknown contact",
+ kind: "manual_log", channel: "email", recipient_role: "person", notification_type: 0)
+
+ get comments_and_communications_path, headers: { "Turbo-Frame" => "comments_and_communications_results" }
+
+ doc = Nokogiri::HTML(response.body)
+ feed_link = "a[href='#{edit_person_path(person, return_to: 'comments_and_communications')}']"
+ [ comment, known ].each do |entry|
+ chip = doc.at_css("##{ActionView::RecordIdentifier.dom_id(entry)} #{feed_link}")
+ expect(chip.text).to include(person.full_name)
+ end
+ expect(doc.at_css("##{ActionView::RecordIdentifier.dom_id(unknown)}").text).to include("stranger@example.com")
+ end
+
+ it "leaves the who-it's-about chip off a person's own feed" do
+ create(:comment, commentable: person, body: "Profile note", created_by: admin)
+
+ get comments_and_communications_path(person_id: person.id), headers: { "Turbo-Frame" => "comments_and_communications_results" }
+
+ feed_link = "a[href='#{edit_person_path(person, return_to: 'comments_and_communications')}']"
+ expect(Nokogiri::HTML(response.body).at_css(feed_link)).to be_nil
+ end
+
it "loads a picked person's composers into the frame with their filing targets" do
registration = create(:event_registration, registrant: person)
@@ -507,6 +595,22 @@
expect(row.at_css("button[data-action='edit-toggle#toggle']")).to be_nil
end
+ it "links an incoming row's sender chip to edit them, with the facilitator hover card" do
+ organization = create(:organization, name: "Riverside Shelter")
+ create(:affiliation, person: person, organization: organization, start_date: Date.new(2023, 3, 1))
+ notification = create(:notification, :incoming, noticeable: person, recipient_email: "primary@example.com",
+ email_subject: "Called in", kind: "manual_log",
+ channel: "phone", recipient_role: "person", notification_type: 0)
+
+ get comments_and_communications_path(person_id: person.id), headers: { "Turbo-Frame" => "comments_and_communications_results" }
+
+ row = Nokogiri::HTML(response.body).at_css("##{ActionView::RecordIdentifier.dom_id(notification)}")
+ edit_path = edit_person_path(person, return_to: "comments_and_communications", feed_person_id: person.id)
+ expect(row.at_css("a[href='#{edit_path}']")).to be_present
+ expect(row.text).to include("Riverside Shelter")
+ expect(row.text).to include("Started March 2023")
+ end
+
it "shows a Responded checkbox for an incoming manual communication" do
notification = create(:notification, :incoming, noticeable: person, recipient_email: "primary@example.com",
email_subject: "Called in", kind: "manual_log",