Skip to content

Make the profile the source of truth for author credit - #2093

Open
maebeale wants to merge 17 commits into
mainfrom
maebeale/profile-visibility-preferences
Open

Make the profile the source of truth for author credit#2093
maebeale wants to merge 17 commits into
mainfrom
maebeale/profile-visibility-preferences

Conversation

@maebeale

@maebeale maebeale commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

🤖 suggested review level: 5 Inspect 🔬 changes how every author credit renders app-wide, narrows search results, and adds an admin reconciliation page

Name display was asked in two unrelated places with two different value sets — people.display_name_preference (people index) and <item>.author_credit_preference (every credit, on 8 forms). AuthorCreditable deliberately called person.full_name rather than person.name, so the per-item answer won and the profile was ignored. One facilitator could render three different ways with no single place to fix it.

Now: the profile formats every credit. The 8 per-item selects come off. The stored column is retained as the consent record, human-editable only on a new admin reconciliation page.

Two axes on the profile

Column Governs
Name format display_name_preference (4 values) Person#name and author credits
Anonymity anonymous_contributions Author credits only — the person stays listed and formatted normally on the people index

anonymous_contributions already existed on main (added by 20260810225127) but was inert: nothing read it. This PR makes it actually govern credits, so StoryImporter flagging an anonymous import now genuinely suppresses that credit.

The one asymmetry

A stored author_credit_preference no longer drives display — except anonymous, which is always honored. Anonymity is true if the profile says so or the item says so; neither can strip it from the other. A suppressed credit renders the generic "AWBW Facilitator", not the word "Anonymous" — behind a login that invited the reader to wonder who was being hidden, where the generic credit just says the org stands behind it. The suppression itself is unchanged: the name is withheld and never links. Anonymity is inherently per-item (a person may want four stories credited and the fifth not), admins need a per-item lever, and there is no backfill — so without this, every item currently storing anonymous would start rendering its author's real name on deploy.

Search honors the preference

Anonymous → matches nothing. first_name_only → not findable by last name. first_name_last_initial → matches the initial only. This also meant dropping the person_first/person_last SearchCop attributes on Story/CommunityNews, which bypassed the gate. Tradeoff: an author name can no longer be one term of an AND full-text query — honoring the preference needs per-person branching a flat index can't express.

Reconciliation page (/author_credit_divergences, linked from admin home)

Four sections, in the order to work them. Each names the code it retires once cleared, so the legacy credit paths can actually be deleted:

  1. Preference drift — stored snapshot no longer matches the profile, grouped by person
  2. Legacy name — credited by a free-text column, reported per column (workshops.full_name, resources.legacy_author_name) so each field can be retired on its own, with a suggested person matched from the free-text name
  3. Creator fallback — no author_id, so the credit falls back to the creating user's person (idea models excluded — that's their only credit path)
  4. No author — nothing to credit, renders the generic "AWBW Facilitator" credit

Sections 2–4 all resolve by assigning an author_id, the only credit path that follows a profile, links to it, and lists the record there. Filterable by person (remote search), content type, stored preference, and reconciled state.

Only the author is credited

Entering a record isn't claiming it, so a model that can name an author credits only that author — no creator fallback in display, search, or sort. Where none is named the credit reads "AWBW Facilitator". The idea models are the exception: no author_id column at all, so their creator is the only attribution they can carry, and credits_creator? draws that line in one place. Section 3 of the reconciliation page follows — those records already show the generic credit, so the creator is offered there as a suggested author to confirm, not as the profile that formats them.

Legacy credits stay clear of whoever entered them

A legacy free-text name outranks the creator, so those rows have no governing person. They take no consent snapshot on create (they were being born diverged from a profile that never described them), they aren't grouped under their creator in section 1, and credited-name search no longer returns them under the creator's name — a workshop displaying "Lisa Cohen" was coming back under the name of whoever typed it in. They resolve only by matching a real person against the legacy text. The author pickers follow the same rule: they show the record's own author (defaulting to the creator on new records only), so saving an untouched form can't silently promote the creator over a legacy credit.

One fallback label

An unattributed or suppressed credit reads "AWBW Facilitator" everywhere. The portal is behind a login, and the old three-way split ("AWBW Staff" on community news/resources, "AWBW Facilitator" elsewhere, "Anonymous" on the idea models) made "Anonymous" read as a deliberate privacy choice when it only ever meant nobody was recorded. All five per-model overrides are gone.

Also swept

Four places where a real name escaped past an anonymized credit: the variation-ideas index link, the story form's subtitle, Workshop#author_name, and two dead ResourceDecorator methods. Plus two phantom strong params (profile_show_name_preference, promoted_to_story) referencing columns that don't exist.

Verified

Full suite green (6898 examples, 0 failures, 17 pre-existing pendings) via ai/test_extra. Rubocop clean on 1511 files. Driven in the running app: format changes propagate to the index and every credit at once, both anonymity paths suppress credit and link, search gating confirmed against real data, all four reconciliation sections populate and resolve.

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings August 4, 2026 14:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 4, 2026 14:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

end
end

context 'via search_by_params (the user-facing path)' do

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Behavior change worth knowing: an author name can no longer be one term of an AND full-text query (search("John technology") used to work). Honoring the credit preference needs per-person branching that a flat SearchCop index cannot express, so person-name search moved entirely to by_credited_person_name, OR-ed in by search_by_params. Single-term author search still works.

<%= link_to divergence_record_title(record), polymorphic_path(record),
target: "_blank", rel: "noopener",
title: "Opens in a new tab",
class: "text-blue-700 hover:underline" %>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Deliberate deviation from the eyebrow convention. These link to 8 different destinations, none of which has a return_to mechanism today (they use hardcoded utility-link rows), so retrofitting all eight was out of scope here — using target="_blank" instead. Happy to do the eyebrow work as a follow-up if you would rather link in-page.

end
end

context 'via search_by_params (the user-facing path)' do

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Behavior change worth knowing: an author name can no longer be one term of an AND full-text query (search("John technology") used to work). Honoring the credit preference needs per-person branching a flat SearchCop index cannot express, so person-name search moved entirely to by_credited_person_name, OR-ed in by search_by_params. Single-term author search still works.

<%= link_to divergence_record_title(record), polymorphic_path(record),
target: "_blank", rel: "noopener",
title: "Opens in a new tab",
class: "text-blue-700 hover:underline" %>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Deliberate deviation from the eyebrow convention. These rows link to 8 different destinations, none of which has a return_to mechanism today (they use hardcoded utility-link rows), so retrofitting all eight was out of scope — using target="_blank" instead. Happy to do the eyebrow work as a follow-up if you would rather link in-page.

<%= link_to divergence_record_title(record), polymorphic_path(record),
target: "_blank", rel: "noopener",
title: "Opens in a new tab",
class: "text-blue-700 hover:underline" %>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Deliberate deviation from the eyebrow convention. These rows link to 8 different destinations, none of which has a return_to mechanism today (they use hardcoded utility-link rows), so retrofitting all eight was out of scope — using target="_blank" instead. Happy to do the eyebrow work as a follow-up if you would rather link in-page.

Copilot AI review requested due to automatic review settings August 6, 2026 18:20
@maebeale
maebeale force-pushed the maebeale/profile-visibility-preferences branch from bdcfd5d to c1d2886 Compare August 6, 2026 18:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 8, 2026 12:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 8, 2026 12:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 8, 2026 18:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@maebeale
maebeale marked this pull request as ready for review August 9, 2026 16:39
Copilot AI review requested due to automatic review settings August 9, 2026 20:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 10, 2026 02:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 10, 2026 20:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@maebeale
maebeale force-pushed the maebeale/profile-visibility-preferences branch from a13d917 to 11fc630 Compare August 11, 2026 06:56
Copilot AI review requested due to automatic review settings August 11, 2026 06:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

maebeale and others added 2 commits August 15, 2026 11:29
Name display was asked twice with two value sets: once on the person
profile (people index) and once per item (every credit), so the same
facilitator could render three different ways with no single place to
fix it. Point credits at the profile instead.

Anonymity stays a per-item latch — a person may want four stories
credited and the fifth not, and nothing should be able to de-anonymize
an item that was submitted anonymously.

Co-Authored-By: Claude <noreply@anthropic.com>
Strip the per-item credit select from all 8 forms and show a warning
only where the stored snapshot disagrees with the profile. The stored
column stays as the consent record, editable in one place.

Search now honors the preference too: an anonymous credit matches
nothing, first_name_only isn't findable by last name, and
first_name_last_initial matches only the initial. Dropped the
person_first/person_last SearchCop attributes that bypassed this.

Co-Authored-By: Claude <noreply@anthropic.com>
maebeale and others added 11 commits August 15, 2026 11:29
Main introduced shared/_badge while this branch was in flight; the
reconciled pill was hand-rolled.

Co-Authored-By: Claude <noreply@anthropic.com>
Sort each person's suggested preference into the worklist and name it
inline (e.g. "Suggested: First name only — most restrictive across
Maria Johnson's content") so the hint is specific to the person rather
than a generic line. Add a person name-search picker to the filters and
order groups by first then last name.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keeps the admin worklist open while checking a profile, matching the
content links that already open in a new tab.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clearing a single column is what makes that column safe to drop, so each
one gets its own subsection and its own all-clear note naming the field.

Guess who each free-text name refers to so an admin confirms rather than
looks every one up, resolved in one query for the page.

Co-Authored-By: Claude <noreply@anthropic.com>
Every fix on the author credit divergences page (Save stored consent,
Apply to profile, Credit to a person) did a full-page redirect. Respond
with a Turbo Stream that re-renders just the results frame and flash, so
the worklist updates in place with no page flip. Non-Turbo requests still
redirect.

Also widen the remote-select left padding so the search icon never
overlaps the placeholder or selected value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- author_credit returns "Anonymous" for a stored-anonymous item even when it
  carries a legacy free-text name — that name belongs to no profile, so nothing
  else would suppress it.
- Add a credited_openly scope so a NULL snapshot (which means "follow the
  profile") stays visible; a bare where.not would drop it, since NULL never
  compares unequal. Use it for a person's public authored content.
- Build the divergences result only on the frame request, so the full page load
  stays cheap.
- Under an active filter, an empty section is the filter's doing, not a
  milestone, so section-clear withholds the congratulations and cleanup note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The divergences page couldn't finish its own job. An item whose stored consent was
"anonymous" offered no way back to "follow the profile", so the only exit was picking
a name format — re-recording a consent nobody gave. Per-item anonymity is the legacy
state this page exists to drain, and the profile is the source of truth, so clearing
it is now allowed on every row.

The author picker fell back to the creator, so a workshop credited to the legacy name
"Lisa Cohen" showed "Umberto User" in its own edit form — and the next save would have
written that creator into author_id, destroying the legacy credit. The picker now
reflects only the record's own author (still defaulting to the creator on new records)
and names the legacy credit standing in when there isn't one.

Sorting COALESCEd author, creator, legacy while display used author, legacy, creator,
so legacy-credited rows sorted under a name they never show.

A note under each author picker flags when the credited person's profile suppresses
credits, since nothing on the record itself reveals that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…the creator

The column reads better as the state it is than as a predicate on a collection, and
it now matches the "Anonymous contributions" label already used in the profile form
and the factory trait. The migration is unmerged, so it changes in place rather than
stacking a rename on top of itself.

A legacy free-text name follows nobody's profile, so a legacy-credited row now
reports no governing person. It was being grouped on the divergences page under
whoever happened to enter it, asserting that person's profile had drifted from a
consent snapshot that never described them. Those rows have to be matched to a real
person by hand against the legacy text — the creator is not a fallback for them.

Community news author is optional on the model, so the form offers a blank option
rather than forcing whoever edits one of the legacy authorless rows to credit
somebody before they can save anything else.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The table had drifted well past this branch's additions.

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 15, 2026 15:45
@maebeale
maebeale force-pushed the maebeale/profile-visibility-preferences branch from 11fc630 to e4c6167 Compare August 15, 2026 15:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 15, 2026 22:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

The workshop show page rendered a Resource's author association directly,
which has no to_s — so a public page printed nothing where a credit
belonged, honoring neither the name preference nor anonymity.

The workshop idea page used User#full_name, the real name, where its
sibling idea pages use User#name.

Also drops the obsolete brakeman ignore entry for callouts_controller;
the warning it silenced no longer fires.

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 15, 2026 23:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

… fallback

Two paths still routed a legacy free-text credit through its creator. The consent
snapshot was taken from the creator's profile on create, so a legacy row was born
diverged from a profile that never described it — the exact rows that shouldn't be
resolved that way. And credited-name search matched the creator, so a workshop
displaying "Lisa Cohen" came back under the name of whoever typed it in, which is
the mismatch between search and display the rest of this work removes.

The portal sits behind a login, so an unattributed credit says "AWBW Facilitator"
rather than "AWBW Staff" or "Anonymous". "Anonymous" in particular read as a
deliberate privacy choice when it only ever meant nobody was recorded. That makes
every per-model override redundant, so they come out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 15, 2026 23:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Entering a record isn't claiming it, so a model that can name an author now credits
only that author. Where none is named the credit reads "AWBW Facilitator" rather
than the name of whoever typed it in — and it's no longer findable or sortable under
that person either, which was the last way the creator leaked into a credit they had
nothing to do with. The idea models keep their creator credit: they have no author_id
at all, so it's the only attribution they can carry.

A suppressed credit reads "AWBW Facilitator" too, from either anonymity path. Behind
a login, "Anonymous" invited the reader to wonder who was being hidden; the generic
credit just says the org stands behind it. The suppression is unchanged — the name is
still withheld and still never links.

Section 3 of the divergences page follows: those records show the generic credit now,
so the creator appears there as a suggested author to confirm, not as the profile
that formats them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 16, 2026 02:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants