Status: Released as v1.0.67 (2026-07-09; interview complete same day; user approved proceeding without model handoff switch) Parent: Phase 19 README
With Champion Signup re-elevated and the Alumni Network de-elevated, this sub-phase:
/champion_signups and restructures the navbar dropdown around itNo new tables. One new column on champion_signups.
| Question | Decision |
|---|---|
| Signature scope | Champion Signup mailer emails only (welcome, profile link, admin notification). Portal Devise emails unchanged. |
| Honeypot behavior | Silent fake success — hand-rolled hidden field + minimum-time trap, no gem. Bot sees normal success; no record, no email. |
| Dashboard home | Own sidebar section at /champion_signups (new dashboard root). ChampionSignup queries stay out of champions/ (Cp::) namespace. |
| Navbar dropdown | Alumni Network removed from dropdown entirely. Dropdown section becomes “Champion Signups” (Dashboard, Insights). Alumni Network admin reachable via link in the new signup sidebar. |
| Top locations widget | Top districts (not regions), per user preference. |
welcome_email.html.erb / .text.erb: “The Alumni Team” → “The Belmont Alumni Engagement Team”profile_link_email.*: already correct — verify both formatsadmin_notification.*: internal; add/normalize signature to matchSchema: add_reference :champion_signups, :district, foreign_key: true, null: true
Model (ChampionSignup):
belongs_to :district, optional: truebefore_save :assign_district_from_zip, if: :will_save_change_to_zip_code? — mirrors Cp::Champion#assign_district_from_zip using ZipCode.lookupregion delegated through districtBackfill: migration (or rake task per launch-guide conventions) that loops signups with zip_code present and assigns district. Data check (2026-07-09): 158/236 signups have zip; all v1 street-address records also have zip_code populated — no address parsing needed. 78 records have no location data; they stay nil.
Display: wherever the index/show/dashboard shows “location” or “address”, lead with District — Region (e.g. “Nashville — Southeast”). Fallback when district is nil: City, ST if present, else raw zip, else em dash. Full street address (v1) demoted to secondary detail on the show page.
district-autocomplete Stimulus controller + /api/districts endpoint exactly as /alumni_search does (app/views/alumni/search.html.erb lines ~105–130):district, filter where(district_id: District.find_by(code: params[:district])&.id) (match alumni_search param semantics — district code param)Applies to Public::ChampionSignups#create (step 1) and #request_link (profile-link form).
website), visually hidden via CSS (not display:none inline — position off-screen), tabindex="-1", autocomplete="off"Rails.logger.info with tag for later analysis), do not create the record or send email, render the same success response the real flow would (silent fake success)layouts/signups.html.erb + champion_signups/_sidebar.html.erb, modeled on layouts/champions.html.erb / champions/_sidebar.html.erb/champion_signups root becomes the Dashboard action; current index moves to /champion_signups/all (mirrors champions/champions#index + all) — keep controller top-level (not champions/) per namespace rules| Section | Items |
|---|---|
| Overview | Dashboard |
| Signups | All Signups (count badge), Duplicates (badge when > 0) |
| Analytics | Insights |
| Links | Alumni Network Admin (→ /champions), Back to Alumni Lookup |
layouts/_navbar.html.erb)cp/champion_mailer (Alumni Portal Devise emails) — out of scope by decisiondistrict_id; invalid/unknown zip leaves it nil/champion_signups renders dashboard; /champion_signups/all renders the list; old bookmarks to index don’t 404bin/test — 0 failures (4599 runs)shared/email/_team_signature (html + text) rendered by all three signup mailer emails. Admin notification also gained a Location row (District — Region).district_id on champion_signups (migration backfills in-place), belongs_to :district, before_save :assign_district_from_zip (strips non-digits; self-heals nil districts on save; never overwrites v1 city/state), #region, #location_label. District has_many :champion_signups, dependent: :nullify.district-autocomplete + /api/districts on /champion_signups/all; controller matches Alumni.filter_by_district semantics (code or id; unknown → none). Location column/show/detail views lead with District — Region, falling back to city/state, then zip.SignupHoneypot service (off-screen website field + signed form_ts min-time trap) on step 1 and profile-link forms. Trips respond with silent fake success (no record/email) and log [honeypot] with IP/UA.layouts/signup_admin + champion_signups/_sidebar (reuses champion-admin-sidebar Stimulus controller). index = dashboard (quick stats, recent signups, top districts, completion funnel, duplicates alert); list moved to all (stat cards removed; destroy/merge redirects updated). Navbar dropdown section renamed “Champion Signups” (Dashboard + Signup Insights); Alumni Network links removed from dropdown, reachable via sidebar “Alumni Network Admin”./champion_signups/all follows the /champions/champions/all pattern: sm:hidden tappable card list (name, v2/status/BUID/duplicate badges, location, date, mobile pagination) + hidden sm:block desktop table. Filters trimmed to Status/Role/District visible; Interest/Submitted/Affinity filters collapsed into a “More filters” disclosure (auto-open when active).20260709120000_add_district_to_champion_signups — adds the column and backfills district_id from zip in-place (low risk, ~240 rows, ran in <1s in dev). No rake task needed; rows created between deploy and migrate self-heal on next save.ChampionSignup.where.not(zip_code: [nil,'']).where(district_id: nil).count should be ≈0 (the known quote-wrapped spam zip heals on next save or can be deleted)./all on mobile widths, district filter, old /champion_signups bookmarks land on the dashboard.duplicates route/action removed instead of linked: it never had a template (would 500); duplicates review is /champion_signups/all?status=duplicates, which the sidebar and dashboard link to.link_sent=true).