Conversation
51c5056 to
88e0a12
Compare
88e0a12 to
901891a
Compare
Non-admin owners can't edit their primary email, organization name, or affiliation details, but had no way to ask for a correction. Add a reusable "Contact us to request a change" affordance (prefilled through the existing ContactUs → admin notification channel) next to those read-only fields, and stage owner self-service profile editing behind Person.owner_editing_enabled? (OWNER_PROFILE_EDIT) so it can be trialed in staging before the profile-launch policy flip. The controller strips the locked fields from an owner's submission as a server-side backstop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the email-only "request a change" links with a ProfileChangeRequest record so admins can act on requests in-app instead of only over email. Owners submit a per-field request from their profile; submitting still creates the admin + submitter notification email thread, now pointing at the structured record. Admins work a "Changes requested" queue (linked from the admin home and surfaced on each person's edit page): Approve auto-applies where it can (primary email via the existing confirmation flow, organization rename), otherwise Update manually + Mark as resolved, or Decline. Status is pending/resolved/declined with a resolution_method (approved/manual). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round of improvements to the change-request feature: - Notify the requester when a request is resolved or declined, with an optional reviewer note (required-in-spirit on decline) shown to them. - One open request per (person, field): the "Request a change" link re-opens the pending one to edit, and owners can edit their own pending request. - Snapshot the target organization/affiliation so a later profile change can't move the target; org rename now shows a blast-radius confirm. - Apply guards: reject an email already used by another account, and no-op (resolve without re-sending) when the value already matches. - Admin queue is now a lazy turbo-frame with status filtering; Approve / Decline / Mark-as-resolved answer with turbo-streams so the row updates in place instead of a full reload. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Aligns with the See Other convention from #2536, picked up in the rebase onto main, so a Turbo form submission advances instead of re-rendering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Affiliation requests were a single free-text box. Now the requester selects which affiliation (or "a new one"), picks what should change (title/role, dates, organization, remove, add, other), and gives details — so staff get a specific, structured request instead of prose to parse. - affiliation_id target (validated to belong to the person); admin "Update manually" deep-links to that affiliation, and the card shows which one. - One open request per affiliation (not just per field), so two affiliations can be flagged at once; the pending-state link still applies to the single-target email/org fields. - requested_value is now required for every field (the new value / the category). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Affiliation requests now capture the concrete new value per category, so Approve applies them like the email/org requests instead of always needing a manual edit: - Category-driven form (reuses the conditional-fields Stimulus controller): title → text; dates → two date fields; organization → new name; add a new affiliation → org search (remote-select) + title + dates; remove → none; other → details. - Approve applies title/dates/org-rename/remove(end-date+inactivate)/add-new; "Other" stays manual. Org rename keeps the blast-radius confirm. - proposed_* columns store the new value; irrelevant sibling values are cleared before save; per-category presence validations. - Admin card shows the proposed "New value"; conditional-fields now accepts a comma-separated show-when so one field can serve several categories. Also: status chips moved to the left of affiliation rows; "Remove this affiliation" wording; line break in the request intro. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
882d077 to
f446e5b
Compare
| # and affiliations are rendered read-only for owners, who can only *request* | ||
| # changes to them. Drop those from a non-admin owner's submission so a crafted | ||
| # request can't slip past. No-op for admins, who edit them directly. | ||
| def reject_owner_locked_changes!(attrs) |
There was a problem hiding this comment.
Can we use action policy scoped params?
| end | ||
|
|
||
| def update | ||
| authorize! @request, to: :update? |
There was a problem hiding this comment.
| authorize! @request, to: :update? | |
| authorize! @request |
| end | ||
|
|
||
| def edit | ||
| authorize! @request, to: :update? |
There was a problem hiding this comment.
| authorize! @request, to: :update? | |
| authorize! @request |
I'd either add a policy action for edit or use alias in the policy.
There was a problem hiding this comment.
Same comment for all the controller actions in this file.
| locals: { request: @request } | ||
| ) | ||
| end | ||
| format.html { redirect_back fallback_location: profile_change_requests_path, notice: notice } |
There was a problem hiding this comment.
| format.html { redirect_back fallback_location: profile_change_requests_path, notice: notice } | |
| format.html { redirect_back_or_to profile_change_requests_path, notice: notice } |
redirect_back is soft deprecated.
| format.turbo_stream do | ||
| render turbo_stream: turbo_stream.replace( | ||
| ActionView::RecordIdentifier.dom_id(@request), | ||
| partial: "profile_change_requests/request", | ||
| locals: { request: @request } | ||
| ) | ||
| end |
There was a problem hiding this comment.
| format.turbo_stream do | |
| render turbo_stream: turbo_stream.replace( | |
| ActionView::RecordIdentifier.dom_id(@request), | |
| partial: "profile_change_requests/request", | |
| locals: { request: @request } | |
| ) | |
| end | |
| format.turbo_stream |
If you create a turbo_stream.erb dom_id is available without calling ActionView....` and its a bit more idiomatic
There was a problem hiding this comment.
Also you will want to add flash.now if you want the same notice that you are using for html response
| # Owner self-service profile editing is staged behind this flag so it can be | ||
| # trialed in staging before PersonPolicy#edit? flips from admin-only to | ||
| # admin-or-owner at profile launch. Default off; set OWNER_PROFILE_EDIT=true. | ||
| def self.owner_editing_enabled? |
There was a problem hiding this comment.
I'm not apposed to using an env but just a thought.
We have !Rails.env.production? for enable? on membership. We could do the same for this. On less thing to do on DO


🤖 suggested review level: 5 Inspect 🔬 new model + migrations, admin apply-on-approve logic, notification wiring, lazy queue + turbo-stream actions
Facilitators can't edit their primary email, organization name, or affiliation details — this gives them a way to request those corrections and gives admins an in-app queue to act on, instead of the changes living only in an email.
Owner side
ProfileChangeRequestand the admin + submitter notification email thread.Admin side
/profile_change_requests) — lazy turbo-frame with status filter, linked from the admin home (pending count) and surfaced on each person's edit page. Cards show the target affiliation.ProfileChangeRequests::Apply), Update manually (deep-links to the exact affiliation), Mark as resolved, Decline. Resolve/Decline carry a reviewer note.profile_change_reviewed).Safeguards
requested_valuerequired on every request.Notes for reviewers
OWNER_PROFILE_EDITflag; decoupling that entry point is deferred to the profile-launch work.🤖 Generated with Claude Code