Make the profile the source of truth for author credit - #2093
Conversation
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>
| end | ||
| end | ||
|
|
||
| context 'via search_by_params (the user-facing path)' do |
There was a problem hiding this comment.
🤖 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" %> |
There was a problem hiding this comment.
🤖 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 |
There was a problem hiding this comment.
🤖 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" %> |
There was a problem hiding this comment.
🤖 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" %> |
There was a problem hiding this comment.
🤖 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.
🤖 suggested review level: 5 Inspect 🔬 changes how every author credit renders app-wide, narrows search results, and adds an admin 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).AuthorCreditabledeliberately calledperson.full_namerather thanperson.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
display_name_preference(4 values)Person#nameand author creditscontributions_anonymous(new)The one asymmetry
A stored
author_credit_preferenceno longer drives display — exceptanonymous, which is always honored. Anonymity is true if the profile says so or the item says so; neither can strip it from the other. Three reasons it has to work this way:anonymouswould start rendering its author's real name on deploy.Search now honors the preference
Anonymous → matches nothing.
first_name_only→ not findable by last name.first_name_last_initial→ matches the initial, not the whole last name. This also meant dropping theperson_first/person_lastSearchCop attributes onStory/CommunityNews, which bypassed the gate entirely.Reconciliation page (
/author_credit_divergences, linked from admin home)Content whose snapshot disagrees with its author's profile, grouped by person, filterable by
person_id/type/preference/include_reconciled. Resolvable in both directions: apply one preference to the person's profile (suggested = most restrictive across their content), or edit a single item's stored value.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 deadResourceDecoratormethods. Plus two phantom strong params (profile_show_name_preference,promoted_to_story) that referenced columns that don't exist.Verified
Full suite green (5338 examples, 0 failures), rubocop clean, brakeman 0 warnings. Drove it in the running app: each format change propagates to the index and every credit at once; both anonymity paths suppress the credit and the link; search gating confirmed against real data; both warning variants render; the reconciliation page lists, filters, and saves.
🤖 Generated with Claude Code