Changelog
All notable changes to the Alumni Lookup application are documented here.
The format follows Keep a Changelog.
[Unreleased]
Added
- “Has Engagement Activity” filter — Alumni search now includes a checkbox to filter alumni who have engagement activity records. Added to controller params,
AlumniFilterService, and search view.
Changed
- Documentation audit & cleanup — Comprehensive review of all planning docs to reflect current project state (all 17 phases complete):
- AI Context Bundle (AI_00–AI_08): Updated dates to April 2026; Phase 6 → Complete, Phase 17 → Complete with full sub-phase details; phase count references updated from 15 to 17.
- STAKEHOLDER-OVERVIEW: Version 5.0; added Phases 16 & 17 to roadmap table; Phase 6 status corrected from “Deferred” to “Complete”; dates and footer updated.
- JOBS-TO-BE-DONE: Version 5.0; Phase 13 references updated from planned (📋) to delivered (✅) for jobs C5, C10, C11, C12; Phase 6 references updated; remaining opportunity table trimmed to true backlog items only; Phase 13 section rewritten from future to past tense.
- Phases README: Added Phases 16 & 17; Phase 6 status corrected; phase count updated to 17.
- Features README & 08-REPORTING: Updated Phase 6 status from “Deferred” to “Partially Complete” (6.1 delivered, 6.2/6.3 in Backlog).
- BACKLOG: Staff reporting entry updated to reflect what’s already delivered vs. remaining.
- Champion Portal README: Status line updated to “All 17 phases complete”.
- Phase 6 README: Status changed from “Deferred” to “Complete” with details on what was delivered vs. moved to Backlog.
- Roadmap controller: Phase 6 status →
:complete; sub-phases 6.2/6.3 → :backlog.
Removed
- Event check-in integration planning docs — Removed
docs/planning/event-checkin-integration/ (7 files). These planning docs for the standalone alumni_events app are no longer needed in this repository.
[1.0.61] - 2026-04-03
Added
- PWA back navigation system — Contextual “← Back to X” links now auto-render in all three layouts (Alumni Network, Staff Portal, Champion Admin) via
BackNavigationHelper. Maps ~50 controller/action combos to logical parent pages (iOS-style predictable navigation). Replaces 31 hand-coded inline render 'shared/back_link' calls with a single layout-level helper. Public/unauthenticated views suppress the back link via content_for(:hide_back_link). Views can override via content_for(:back_link_path) + content_for(:back_link_text).
Fixed
- CRM changes page excluded import-sourced records — The
/settings/affinaquest/crm_changes page was showing records from Affinaquest imports (recency conflicts) alongside app-originated changes. These import records already came from the CRM and shouldn’t be flagged for export back. Added app_originated scope to CrmDataChange; applied to listing, export CSV, mark-exported, action item badge count, and CrmDataExportBatch.export_all_pending!. Import records are still created for field protection tracking — they just no longer appear on the export page.
Changed
- IP ownership resolved — Updated LEGAL_REVIEW.md and Terms of Service draft to reflect agreement reached with Belmont legal team (April 2026): Chip retains IP rights; Belmont has indefinite royalty-free use; future monetization to be discussed with ~50/50 development-time split as starting point. Outstanding Gaps item #7 narrowed to domain ownership only.
[1.0.60] - 2026-03-28
Added
- Phase 17.4: Staff Management & Data Catalog — Verified
to_champion_attributes field mapping (9 fields), added legacy data notice to staff signups index, updated CHAMPION_SIGNUP_SYSTEM.md with retirement notice and field mapping table.
- Phase 17.3: Legacy Signup Flow Retirement — Removed public
/signups/* multi-step wizard (controller, views, mailer, initializer, rake task, tests). Replaced with 301 redirects to /champions. Preserved: ChampionSignup model, staff management interface, data conversion pipeline in ConfirmationsController, SVG partials used by staff views. 2 redirect tests added.
- Phase 17.2: Generic Landing Page Refresh — Updated
/ landing page with two-tier messaging. Replaced abstract pillars (Belong/Gather/Inspire) with concrete feature cards (Alumni Directory, Communities, Events, Conversations). Added “For Alumni Who Want to Do More” Champions teaser section linking to /champions. Removed inline footer (layout footer handles it). 10 tests, 22 assertions.
- Phase 17.1: Public Champions Landing Page — New public page at
/champions serving as the redirect target for alumnichampions.com. Educates anonymous visitors about the Alumni Champions program with adapted content from the authenticated /champion-info page. Includes hero, three engagement tiers (Member/Champion/Community Leader), four role cards (informational only), benefits, social proof stats, How It Works, and signup/signin CTAs. Social proof counts true Champions (role selected) not just verified portal members. OG meta tags for social sharing. 10 tests, 26 assertions.
Cp::ChampionsLandingController — extends ApplicationController (public, no auth), redirects signed-in users to /champion-info
GET /champions route in public scope (between policy routes and authenticated routes)
- Documented Cp::Champion model naming caveat: model represents all portal members, true Champions have
primary_role present
[1.0.59] - 2026-03-27
Added
- Import cancel button: Cancel buttons on all scanning and processing pages for both Affinaquest and Current Student imports. Marks batch as “Cancelled by user” and background jobs check at chunk boundaries (every 500 records) and stop cleanly. Guards on
scan_complete! and complete! prevent a finishing job from overwriting a cancelled status.
Changed
- Manifest storage moved from Redis to PostgreSQL: Import manifests (Affinaquest and Current Student) are now stored as Zlib-compressed binary in a
manifest_data column on the batch tables instead of Redis. Eliminates Redis OOM on large CSV imports — the 50k-row Affinaquest import no longer needs Redis capacity for manifest storage. Redis is now reserved for Sidekiq and Action Cable only.
- CSV content stored in database instead of Sidekiq args: Upload controllers now store the CSV file content (Zlib-compressed) in a
csv_content column on the batch record, passing only the batch ID to scan jobs. Previously the full CSV string (~8MB for 49k rows) was passed as a Sidekiq job argument through Redis, causing OOM before the scan even started.
- Chunked apply phase:
apply_manifest_to_batch now processes changes in 500-record chunks instead of loading all records and alumni into memory at once. Per-chunk alumni preloads, key transforms, and GC runs keep worker memory under 512MB for 49k-row imports.
- Commit actions no longer load manifest: Both Affinaquest and Current Student commit controller actions now use batch stats from the scan phase instead of loading the full manifest synchronously — eliminates H12 (30-second timeout) errors on large imports.
Fixed
- Current Student processing page double-count: Counters on the processing page showed double values because
start! didn’t reset stats from scan preview. Both start! methods now reset all counters to 0 before the apply phase begins.
- Worker memory not released after import: Ruby’s memory allocator holds freed pages after large imports, keeping worker at peak memory (~500MB) indefinitely. Added
GC.start(full_mark: true) + GC.compact after all scan/apply jobs complete, and csv_content cleanup in apply jobs. Set MALLOC_ARENA_MAX=2 on both staging and production to reduce glibc arena fragmentation.
- Affinaquest false-positive “To Update” on re-import: Re-importing the same CSV showed thousands of phantom updates with “No field changes.” Rows with only recency conflicts (no actual field changes) were being marked
:update instead of :skip. Fixed by checking fields_to_update.empty? only, ignoring recency conflicts for the action decision.
- Current Student false-positive “To Update” on re-import: Same category of bug — rows where all 3 enrollment fields matched were still marked
:update with an empty changes hash. Now builds changes hash first, checks if empty, and returns :skip before marking :update.
[1.0.58] - 2026-03-24
Fixed
- Redis OOM on large CSV imports: Zlib-compress manifest data before storing in Redis. A 7.8MB CSV was triggering OOM on Heroku Redis Mini (25MB). JSON manifests compress 5-10x, bringing ~20MB manifests down to ~2-3MB. Backward-compatible fetch handles both compressed and legacy data.
[1.0.57] - 2026-03-24
Added
- Current Students: Create-if-not-found: Current Student importer now creates new Alumni records for students not found in the database, instead of skipping them
- New
created_count column on current_student_import_batches (migration)
create_alumni_from_row builds records with BUID, names, enrollment fields, birthdate, and contact info (zip, phone, emails, maiden name)
- Preview page shows “New Students to Create” table with green card; results page shows “Created” count
- Duplicate-safe: existence check at apply time prevents double-creates between scan and apply
increment_created!, updated complete!, scan_complete!, total_processed, summary on batch model
- Affinaquest: Birthdate import: Affinaquest Contact importer now maps
birthdate, date_of_birth, and dob CSV columns with recency-aware updates and US-format date parsing
- Current Students: Contact field import: Current Student importer now imports maiden name, zip, phone/mobile, email fields, and last modified date — fills in blanks without overwriting existing data on updates; populates fully on creates
- Stats: Population segmentation: Engagement stats dashboard differentiates degreed alumni, non-degreed alumni, and current students with population breakdown card and generation table columns
- Sidebar: Archived imports section: Settings sidebar now separates primary imports (Alumni Contacts, Current Students, Engagement Activities) from archived imports (Alumni Banner) for clearer workflow guidance
Fixed
- Stats: Blank college names:
build_alumni_per_college now uses preloaded College.pluck hash instead of .group().count which produced flat hash keys
- Stats: Import count discrepancy:
current_students scope updated to include NULL expected_graduation_year records (5,442 → 7,646)
- Birthdate import parsing:
Date.parse misinterpreted MM/DD/YYYY as DD/MM/YYYY — now uses Date.strptime("%m/%d/%Y") first with fallback
[1.0.56] - 2026-03-17
Added
- Engagement Activity Group Filtering & Bulk Delete: Filter engagement activities by group (Giving, Events, Digital, Leadership, Campus) and description text on the settings page, with bulk delete for filtered results
- Code-defined
ACTIVITY_GROUPS constant on EngagementType — no migration needed
- Filter bar with group dropdown and description search on Settings > Engagement Activities
- Bulk delete action with confirmation dialog showing filtered record count
- New
delete_filtered route and controller action
- Population Filter for Engagement Stats: Stats now default to degreed alumni only, with optional inclusion of non-degreed alumni and current students
- Three-checkbox filter (Degreed Alumni, Non-Degreed Alumni, Current Students) on engagement stats dashboard
apply_population_filter in AlumniFilterService using SQL UNION for structurally incompatible scopes
- Population selection persisted in session across tab navigation
- Threaded through all 7 stats services (Overview, Analytics, Breakdown, Demographics, Matrix, ActivityPairs, DiscussionBoards) and cache keys
- Batch Engagement API (
POST /api/v1/engagements/batch): External systems (e.g., alumni_events) can batch-record engagement activities via API. Idempotent via event_identifier — safe to re-submit.
- EngagementBatchLog model: Tracks processed batch syncs for idempotency, with API key tracking and error detail logging.
[1.0.55] - 2026-03-07
Fixed
- Welcome Home Bruin email spacing: Converted feature grid from flex/gap layout to table-based layout for email client compatibility (Gmail, Outlook)
- Auto-verified admin notification CTA: Admin email now links to champion profile instead of verification queue for auto-verified users
- NotifyAdminsJob user query: Fixed query to match
can_portal_admin users, not just role: portal_admin
Added
- Auto-verified Welcome Home email: Champions auto-verified via legacy signup or alumni email match now receive the “Welcome Home, Bruin” notification email
- Auto-verified admin notifications: In-app and web push notifications for admins now reflect auto-verified status with distinct title, body, and profile URL
[1.0.54] - 2026-03-06
Added
- Phase 16.5 — Security Headers: Comprehensive browser security headers to harden against XSS, clickjacking, and information leakage
- Content Security Policy in report-only mode:
default-src 'self', Google Fonts whitelisted, unsafe_inline for Tailwind CSS, Google OAuth and Apple Sign-In in form_action, nonce generation enabled
- Permissions-Policy via
apply_permissions_policy before_action: camera, microphone, geolocation, payment, USB, gyroscope, magnetometer, accelerometer, MIDI, autoplay, picture-in-picture set to :none; fullscreen to :self
- Referrer-Policy header (
strict-origin-when-cross-origin) added to production environment
- HSTS already provided by existing
config.force_ssl = true
- 11 new integration tests in
test/integration/security_headers_test.rb
- Phase 16.2 — Education Privacy Controls: Three-level education privacy enum (
education_show_all, education_hide_year, education_hidden) with privacy enforcement across directory, profile, career connect, and champion card views
- Directory search excludes education-hidden alumni from college/degree filters and education-hide-year from graduation year filters
- AlumniLikeMeService skips education scoring for privacy-restricted alumni
- CareerConnectService excludes deleted and education-hidden alumni from career matches
- Profile view shows privacy indicator badges for self-view (“Hidden from others” / “Year hidden from others”)
- Privacy settings radio buttons in Settings > Privacy section
- Phase 16.3 — Account Deletion & Data Export: True account deletion with PII clearing and data export capability
AccountDeletionService: Soft-delete workflow — clears 30+ PII fields, anonymizes discussion posts to “Former Member”, nullifies associations, sends confirmation email
DataExportService: Generates JSON export of all personal data (profile, education, communities, connections, activity history, privacy settings)
- Confirmation page requiring user to type “CONFIRM” before deletion
- Download My Data button with 24-hour rate limiting via activity events
- Post-deletion confirmation email sent to original address
active and deleted scopes on Cp::Champion model
- Phase 16.4 — CAN-SPAM Email Compliance: Full CAN-SPAM compliance across all 27+ email templates
Cp::CanSpamHeaders concern adds List-Unsubscribe and List-Unsubscribe-Post headers to 7 non-transactional mailers
- Shared text footer partial (
shared/email/_text_footer.text.erb) with physical address, unsubscribe link, and reason text
- HTML branded footer updated with “or unsubscribe” link
- All 27 text email templates updated to use shared footer (transactional emails marked appropriately)
- Phase 16.6 — Age Attestation: Age confirmation checkbox at registration
- Virtual
age_confirmed attribute with acceptance validation on create
age_confirmed_at timestamp recorded on successful registration
- OAuth registrations exempt via existing
skip_consent_validation?
- New activity event types:
education_privacy_updated, account_deleted, data_exported
Removed
- Deprecated
/dashboard in favor of /home: Removed the legacy DashboardController and all its views; /dashboard now redirects to /home
- Moved 4 shared partials (
_community_notifications, _invited_community, _role_card, _role_card_ideas) from cp/dashboard/ to cp/home/
- Moved
refresh_role_ideas action and turbo stream template to HomeController (route: POST /home/refresh_role_ideas, same helper cp_refresh_role_ideas_path)
- Updated 14 render calls across 5 stage partials to reference new partial locations
GET /dashboard now returns 301 redirect to /home (preserves bookmarks)
- Deleted
DashboardController (337 lines) and show.html.erb (878 lines)
- Removed inert
progressive_dashboard feature flag from all 3 environment configs
- Updated tests: renamed
dashboard_role_card_test → home_role_card_test, updated backward-compat tests to assert redirect
Added
- Role Idea CTA Click Tracking: End-to-end click tracking for role idea and contextual prompt CTA buttons on the Champion dashboard
track_click_controller.js Stimulus controller fires non-blocking POST via fetch with keepalive: true
Cp::RoleIdeaClicksController endpoint records clicked_role_idea_cta and clicked_contextual_cta activity events
- New
clicked_contextual_cta event type in Cp::ActivityEvent
- Analytics page (
champions/insights/role_ideas) updated with tabbed layout (Role Ideas / Contextual Prompts), hide-zeros toggle, role attribution for contextual prompts
insights_tabs_controller.js Stimulus controller for tab switching and zero-row filtering
Fixed
- Turbo Drive navigation error on contextual prompts: Changed
community.id to community.slug in ContextualPromptGenerator CTA routes to avoid 301 redirects that Turbo couldn’t follow
- OAuth registration bypassing consent checkboxes: Google/Apple/Facebook OAuth registrations now require age confirmation via the re-consent flow. Added
needs_age_confirmation? method to Cp::Champion that detects OAuth users without age_confirmed_at, and updated check_policy_consent to redirect these users to the consent page with an age confirmation checkbox.
- Data export crash on communities with no role column: Fixed
NoMethodError: undefined method 'role' in DataExportService#community_data by replacing role with membership_type (based on primary flag).
- OAuth sign-in “Invalid wizard step” error: Fixed
next_incomplete_wizard_step returning non-existent "privacy" step. Now returns nil when all wizard steps are complete (even if wizard_completed_at not formally set). Updated after_sign_in_path_for in session/OAuth controllers to auto-complete wizard when no steps remain.
Added
- Phase 16.1 — Legal Policy Pages & Consent Flow: Full implementation of in-app legal policy infrastructure covering all four policy pages, versioned consent tracking at registration, and re-consent mechanism for policy updates.
- Policy pages served publicly at
/terms, /privacy, /community-guidelines, /cookies (converted from docs/compliance/drafts/ with internal cross-links resolved to in-app routes)
cp_policy_acceptances table: Audit trail recording champion ID, policy type, version, acceptance timestamp, and IP address
Cp::PolicyVersion model: Central config with CURRENT_TERMS / CURRENT_PRIVACY version constants; consent_current? and stale_policies class methods drive the re-consent check
Cp::PolicyAcceptance model: Immutable audit record with validations; record_for_champion! creates acceptance entries
- Registration consent checkbox: ToS + Privacy Policy required at registration (virtual
terms_accepted attribute validated on create; OAuth registrations exempt via skip_consent_validation?)
- Consent columns on
cp_champions: terms_accepted_at, terms_version, privacy_accepted_at, privacy_version capture last-accepted version and timestamp
- Re-consent flow (
Cp::ConsentController): check_policy_consent before-action in Cp::BaseController redirects stale champions to /consent; acceptance records both policies and resumes intended destination
- Activity event tracking:
policy_consent_updated event type added to Cp::ActivityEvent
- Footer links: All four policy pages linked in champion portal footer (Terms, Privacy, Community Guidelines, Cookies)
- Tests: 37 new targeted tests — 8 policies controller, 12 consent controller, 9 policy acceptance model, 8 policy version model — plus 16 consent-related assertions added to existing champion model tests, and updated registration/dashboard/home/boards controller tests to satisfy consent requirements in fixtures
[1.0.53] - 2026-03-05
Added
- Legal Review Document (
docs/compliance/LEGAL_REVIEW.md): Comprehensive 26-section pre-launch legal assessment covering data collection, user consent, privacy controls, FERPA/COPPA, CAN-SPAM, third-party services, and 18 outstanding gaps. Updated to reflect Phase 16 implementation status — 6 gaps marked complete.
- IT Security Review Document (
docs/compliance/IT_SECURITY_REVIEW.md): Full security assessment covering authentication, encryption, rate limiting, session security, headers, and deployment. CSP (#1) and security headers (#4) recommendations marked implemented via Phase 16.5.
- Terms of Service Draft (
docs/compliance/drafts/TERMS_OF_SERVICE.md): Full ToS draft ready for legal review. 18+ age requirement, connection-gated messaging, UGC licensing, governing law (Tennessee). 2 items flagged for legal input.
- Privacy Policy Draft (
docs/compliance/drafts/PRIVACY_POLICY.md): Full Privacy Policy draft ready for legal review. Covers data collection, retention, deletion (30-day commitment), user rights, 18+ minimum age. 1 item flagged for legal input.
- Community Guidelines Draft (
docs/compliance/drafts/COMMUNITY_GUIDELINES.md): Full Community Guidelines draft with expected behavior, prohibited content, connection-gated messaging rules, moderation process, and appeals.
- Cookie Policy Draft (
docs/compliance/drafts/COOKIE_POLICY.md): Cookie Policy documenting single essential session cookie; confirms no analytics/tracking cookies in use.
- Phase 16 Spec (
docs/planning/champion-portal/phases/phase-16/README.md): Full implementation spec for 6 compliance sub-phases (16.1 policy pages + consent, 16.2 education privacy, 16.3 account deletion + export, 16.4 CAN-SPAM, 16.5 security headers, 16.6 age attestation). Includes cross-document link table and traceability matrix.
Changed
- Roadmap: Phase 16 Added (
app/controllers/champions/roadmap_controller.rb): Phase 16 — Legal & IT Compliance added with all 6 sub-phases; status: planned.
- Messaging System Docs: Corrected all references to reflect connection-gated messaging (Phase 10.3). Removed “Connections only” as a separate enum value (redundant), corrected “Disabled” → “Nobody” in MESSAGING_SYSTEM.md and compliance docs. Privacy levels now correctly documented as
anyone / limited / nobody.
- Age Requirement: Updated to 18+ throughout all compliance docs and Phase 16 spec. Eliminates all COPPA concerns by design.
alumnilookup.com removed from public-facing docs: Internal staff portal domain withheld from ToS and Privacy Policy. Retained in internal IT/Legal review documents.
- Insights Navigation & Naming: Champion Program sidebar now routes to the new Insights overview at
/champions/insights; legacy Role Ideas page now redirects into Insights.
- Role Ideas Analytics: Added dedicated Insights role ideas view and associated metrics plumbing for seeded-question and role-idea activity reporting.
- Registration Bot Protection: Alumni registration now records and validates a hidden honeypot field to reduce automated spam signups.
Changed
- Commit Workflow: Documentation Review: Added mandatory pre-commit documentation review checklist to
/deploy skill, copilot-instructions.md, and CLAUDE.md. Ensures docs stay current during ad-hoc feature/bugfix work (not just phase wraps).
Fixed
- News Card Routing: Compact news cards now always route through the canonical news post page rather than a stale direct path.
- Seeded Question Preview/Show Stability: Fixed seeded question preview and show flow issues that could surface broken or inconsistent preview behavior.
- Onboarding Metrics Naming: Insights metrics naming now consistently uses “alumni” terminology in place of legacy “champion” labels where appropriate.
[1.0.52] - 2026-03-04
Added
- Profession Step: Employment Status: Profile wizard profession step now collects employment status with a new field; progress bar spacing tightened.
Fixed
- District Sidebar: State in Name: District now displays as “City, ST” format (using
display_name) instead of bare city name.
- District Sidebar: “Find Fellow Alumni” Link: Fixed broken directory filter link — was passing
district: (slug param) with an ID value; corrected to district_id: so the search actually filters by district.
- Belmont Stories Import Hardened: Import task made more resilient against edge-case failures.
- Safari Add to Home Screen: Fixed Safari PWA shortcut opening
/dashboard instead of root; now correctly routes to /home.
Changed
- Docs: Updated STAKEHOLDER-OVERVIEW for Higher Ed context; reconciled phase completion status across all spec docs; marked Phase 15 (Design Consistency Pass) complete.
[1.0.51] - 2026-03-02
Added
- Email Columns in Alumni CSV Export: Added five email columns (
email, email_school, email_personal, email_business, email_other) to the alumni search CSV export. The email column uses a best-email fallback: stored email first, then school email (current students only), then personal, business, and other.
- Current Student Filter Checkbox: New standalone checkbox filter on the alumni search page to limit results to current students (previously buried in the champion filter dropdown).
- Has Alumni Network Account Filter Checkbox: New checkbox filter to limit alumni search results to those who have created an Alumni Network (Champion Portal) account.
Changed
- Alumni Champion Filter Dropdown: Removed “Show Current Students” option from the dropdown; it is now a dedicated checkbox above the submit button.
[1.0.50] - 2026-03-02
Added
- Legacy Verification Tool: New staff tool at
/tools/legacy_verification for processing Admissions legacy CSV files. Extracts relative names and relationships from free-text “Legacy Details” column, matches against alumni database using multi-strategy search (direct, middle-name maiden fallback, applicant-last-name fallback, geographic, fuzzy), and exports results CSV with confirmation status and matched BUIDs.
- LegacyDetailParser: Service that parses 18+ free-text formats including “Name - Relationship”, reversed “Relationship - Name”, slash/comma/colon/”and” delimiters, parenthetical relationships, multi-dash noisy entries, and free-form sentences.
- Middle-Name Maiden-Name Matching: 3-part names (e.g., “Dawn Nolan Klein”) now try middle components as alternate last names, correctly resolving married-name cases to the alumni’s maiden-name record.
- Connections Education: Empty-state connections card explains request/accept/message flow; dismissable “How Connections Work” banner on the Directory index page for first-time visitors.
- Start Exploring Escape Hatch: Adds
ready_to_explore_at milestone and /home/start_exploring action so alumni in profile_building stage can advance when only optional profile items remain.
Changed
- Champion Role Education: Dashboard role discovery card (State A) reworded with clearer educational/invitational copy; role-specific concise descriptions added for all four role paths; CTA language updated.
- Champions Header Spacing: Tightened desktop nav container alignment and reduced horizontal padding on nav links and user menu for cleaner layout consistency.
Fixed
- Email Templates Unified: Overhauled champion and admin mailers with shared branding, dark-mode-safe styles, and correct staff-vs-alumni URL routing (lookup-domain links for admin actions).
- Verification Resend Tools: Portal-admin verification UI adds actions to resend confirmation emails and update email-address-then-resend before champion confirms account.
[1.0.49] - 2026-02-28
Changed
- Role Icons Replace Emoji: Replaced all role emoji across
/champion-info, /roles, /roles/:role, quiz results, profile wizard role step, and microsite nav with inline SVG role icons (hexagonal badges) and seals (circular badges with role name). Icons render directly without circle wrappers for proper sizing. Seals used in standalone/hero contexts. Added role_seal_svg helper to ApplicationHelper. Documented icon vs seal usage rules in DESIGN-GUIDELINES.md.
- Connections Preferences Simplified: Replaced the previous multi-option scope matrix with a simpler
All Alumni & Almost Alumni vs Limit Who Can Contact Me mode in both profile wizard and settings. Limited mode now uses 3 explicit restriction checkboxes (communities, city, almost alumni).
- Connections UI Interactivity: Added Stimulus
contact_mode_controller so mode switching updates card highlighting in real time and disables limited-mode checkboxes when All Alumni is selected.
- Wizard Completion Destination: Profile wizard completion now redirects to home (
/home) instead of profile, with tests updated to match.
Fixed
- Canonical Slug Redirects: Fixed canonical slug redirect behavior for slugged routes to avoid incorrect path handling.
- Verification Show Redirect Loop: Prevented no-op
301 redirects in Champions::VerificationsController#show when a champion record still uses numeric to_param, restoring 200 responses for verification detail views while preserving canonical slug redirects for slugged records.
[1.0.48] - 2026-02-28
Added
- Import Review Workflow: Imported Belmont Stories appear in Content Submissions “Imported” tab with count badge, bulk Publish Selected / Publish All actions, and per-item Review page with Publish, Edit & Publish (promote to draft), and Dismiss (soft-decline) actions; 6 new routes, 7 controller actions, 17 new tests
- Belmont Stories Rake Verbose Output:
belmont_stories:import task now prints structured per-item results (imported/skipped/backfilled/failed) with quiet mode (QUIET=1) for scheduled runs; 3 rake task tests
Fixed
- Belmont Stories Date Parsing: NBSP and other Unicode whitespace characters in scraped dates no longer break
Date.strptime; added normalize_date_text with multi-format fallback and backfill for existing posts missing published_at; 3 new scraper tests
Changed
- Activity Feed Section Limits:
items_for_sections method replaces items(limit:) — applies independent per-section max/min (news: 4, discussion: 3, photo_album: 4) for balanced home page content; 2 new service tests
- Home Feed Defaults: News default bumped from 3→4, photo_album from 3→4
[1.0.47] - 2026-02-27
Changed — Phase 13 Completion: Root Route & Dashboard Polish
- Root route switched to
/home: Authenticated root now always points to Cp::HomeController#show; removed progressive_dashboard feature flag conditional from config/routes.rb; all cp_dashboard_path references (70+) updated to cp_home_path across 20 controllers, 10 views, 3 layouts, 12 test files; /dashboard route preserved for backward compatibility
- Header/footer/mobile nav: Logo links updated from
/dashboard to /home
- Back links: All “Back to Dashboard” → “Back to Home” across help, invites, events, news, submissions, feedback, discussions, photo albums
- Communities card: “N suggested” integrated as body row instead of header badge; plus icon + chevron linking to communities index
- Champion Role card colors: Dynamic
role_color (fountainblue, skyblue, belmontblue, belmontred) replaces hardcoded amber for contextual prompt borders, backgrounds, icons, badges, and CTA buttons
- Activity feed thumbnails: Discussion items now show thumbnails when image available (matching news card layout); photo album resolution increased to 320×320
- Discover Communities: Join/X button layout for suggested communities in exploring stage and communities index (Turbo Stream dismiss)
- Find Your Champion Role: State A converted to
_primary card partial
- District card: Icon changed from circle to MapPin SVG
- Your Impact card: Changed from
_compact to _standard card with bar chart icon
Fixed
- Turbo frame content missing: Added
target="_top" to lazy-recommendations turbo-frame elements in engaging, champion, and community_leader stages; added data-turbo-frame: "_top" to career resources link — fixes Career Resources and Alumni You Might Know sections disappearing on click
- News index route error: Fixed
NoMethodError for undefined cp_community_news_index_path → cp_community_path(community, anchor: "news")
Added — Phase 13.8: Freshness, Signals & Milestones
- “What’s New” Summary Bar (
app/views/cp/home/_whats_new_bar.html.erb): Compact bar below hero showing counts of new content (discussions, articles, events, photo albums) since last dashboard visit; click-through links to each content index; human-friendly “since” labels (earlier today, yesterday, day name, date); hidden on first visit or when nothing new
- Milestone Celebrations (
app/services/cp/milestone_service.rb, app/models/cp/milestone.rb): 8 milestone types (first_community, profile_complete, first_connection, first_post, champion_opt_in, one_year_anniversary, ten_connections, community_leader); auto-detection on dashboard load; unique per champion; celebration banner with warm copy; max 1 per visit; auto-dismiss after display; milestone_achieved activity event tracking
- Migration
create_cp_milestones: New table with champion_id, milestone_type (integer enum), achieved_at, displayed_at; unique index on (champion_id, milestone_type)
- Connections Sidebar Redesign (
app/views/cp/home/sidebar/_connections.html.erb): Shows connected people (photo, name, “View” link to profile) instead of message threads; progress indicator for <3 connections; empty state “Meet a fellow alum” with directory link
- Community Activity Badge Labels (
app/views/cp/home/sidebar/_communities.html.erb): Per-community colored badges showing new post counts (blue) and upcoming event counts (amber); tooltips with counts; icons on desktop, hidden labels on mobile; batch-loaded via controller to avoid N+1
- Role Card Visibility Expansion: Champion role card now visible from
exploring stage onward (was champion+); added to _exploring.html.erb and _engaging.html.erb stage partials
- 1 new activity event type:
milestone_achieved
- 29+ new tests: 5 milestone model tests + 16 milestone service tests + 2 HomeController what’s new tests + 2 connections sidebar view tests + 4 dashboard helper label tests
Added — Phase 13.7: Champion Nudge Engine & Role Microsite
- Champion Discovery Page (
/champion-info): Educational “What & Why” page with warm, zero-pressure tone; 3 sections (What is a Champion, Three Engagement Tiers, What Changes When You Opt In); conditional CTA (has role: summary card → role detail; no role: Explore Roles + quiz link); canonical source language from identity statement
- Champion Role Microsite — 3-page cohesive experience with shared navigation:
/champion-info — Educational overview (What & Why), no inline role management
/roles — Explore & Choose page with rich role cards, direct “Choose this role” selection, quiz CTA
/roles/:role — Role detail + management (retake quiz, change role, remove role with confirmation)
- Shared tab navigation partial (
_champion_microsite_nav.html.erb) across all 3 pages
- Consistent
max-w-3xl container with standardized padding across all pages
- Route consolidation: Removed duplicate
champion-info/select-role and champion-info/remove-role endpoints; added DELETE /roles/remove to RolesController; all entry points updated (dashboard role card → /roles/:role, profile show → /roles/:role, wizard referrers → role detail, nudge “Learn More” → /roles)
- ChampionNudgeEngine service (
app/services/cp/champion_nudge_engine.rb): 5 trigger definitions (content_submitted ≥1, active_discussant ≥3 comments, active_connector ≥5 connections, profile_complete ≥100%, community_active ≥2 posts); JSONB-based impression tracking via seen_tooltips; max 3 impressions before auto-suppression; permanent opt-out after 3 dismissals
- Dashboard nudge card (
app/views/cp/home/_champion_nudge.html.erb): Amber-accented inline card on engaged_member stage with dual CTAs (Find Your Role → quiz, Learn More → roles page) and session dismiss; turbo-frame wrapped for clean removal
- Post-action inline nudges: Flash-style amber bar in champions layout after news submissions, event submissions, and 3+ discussion comments;
set_champion_nudge_flash helper in base controller; only shown to Tier 1 Members (not Champions or CLs)
- Nudge dismiss controller (
app/controllers/cp/nudges_controller.rb): Session dismiss and permanent suppress actions with activity tracking
- 7 new activity event types:
viewed_champion_info, nudge_shown, nudge_dismissed, nudge_clicked_quiz, nudge_clicked_learn_more, role_changed, role_removed
- 34+ new tests: 14 service tests (nudge engine) + 12 champion info controller tests + 9 roles controller tests (remove_role, management, nav)
Added — Phase 13.5: Tier 2 — Champion Experience
- ContextualPromptGenerator service (
app/services/cp/contextual_prompt_generator.rb): Role-aware, data-driven prompt generation from live community data; 8 generator methods (new members, unconnected alumni, quiet communities, albums needing photos, unanswered discussions, quiet led communities, upcoming events, low-visibility events); dismissal tracking via seen_tooltips JSONB
- Contextual prompt rendering: Role card ideas partial accepts contextual prompts, renders with amber “Now” pulse badge; fills remaining slots with static ideas (total 3)
resolve_contextual_cta_route helper: Resolves string-format route references (e.g., "cp_community_path:123") to actual URLs
- Alumni Champion SVG icon badge: Inline hexagonal champion icon rendered next to names in directory cards, discussion posts, discussion comments, and profile pages; tooltip shows specific role title; sizes: sm (16px) and md (20px)
- Role ideas in digest emails: Daily and weekly digest templates include “Your Idea for Today” / “Your Champion Idea” section with emoji, title, body, and CTA button; loaded via
RoleIdeaPackService
- New Members to Welcome (
app/views/cp/home/stages/_new_members_to_welcome.html.erb): CL-specific partial showing recent members (14 days) who haven’t been welcomed yet; avatar, name, community, “Say Hello” CTA; welcome tracking via cl_welcome_sent activity events
- Community Leader stage integration: New members section rendered in
_community_leader.html.erb after community health card
Fixed
- Discussion badges: Added champion icon badge to community board post views (
boards/show.html.erb, boards/_comment.html.erb) — previously only added to global discussion views
- Tailwind JIT purging: Replaced dynamic Tailwind class interpolation (
bg-#{color}/10) with explicit ROLE_BADGE_CLASSES class map to prevent build-time purging
- Weekly digest test: Fixed timing-sensitive fixture collision where
30.days.ago event fixture coincided with travel_to date, causing false positive email sends
- Digest email padding: Added horizontal margin to role idea section for proper alignment within email container
Added — Phase 13.4: Tier 1 — Member Experience
- Onboarding stage (
_stages/onboarding.html.erb): “What to Do Next” checklist with per-item routing to specific wizard steps/profile sections; smart hero CTA path (@next_wizard_step → @next_profile_section → fallback)
- Exploring stage (
_stages/exploring.html.erb): Community type grid (4 types explained), “Recommended for You” suggestions with type labels + member counts, Browse Communities CTA
- Engaging stage (
_stages/engaging.html.erb): Activity feed, suggested communities with type labels + member counts + descriptions, dismissable career resources card (Turbo Frame, JSONB tracked)
- Language updates: “Champions You Might Know” → “Alumni You Might Know”, hope-oriented empty states, stage-specific headings, “Fellow Alumni” district language
- Connections UX: Wizard step reworded to emphasize restriction (“Only people in my communities/city”), matching language in Settings
- Sidebar profile completion: Extended visibility through
engaged_member stage; shows “X% complete · Edit →” or “Profile complete ✓”
- Edit Profile cross-link: “Connection settings →” link in profile sidebar (mobile + desktop)
- Wizard reorder: Connections moved to final wizard step
- Dev debug bar: Stage override selector for testing all 8 journey stages
- Industry field: Added to profession wizard step and completion logic
Added — Phase 13.1: Journey Engine & Tier Detection
Cp::Tierable concern (app/models/concerns/cp/tierable.rb): engagement_tier (:member/:champion/:community_leader), compute_journey_stage (8 stages: new_arrival → community_leader), recompute_journey_stage! (persists to DB), has_contributions? (board posts, news, events, comments, 5+ reactions), tier predicates
- Migration
add_journey_stage_to_cp_champions: integer column with index on cp_champions.journey_stage, default 0, not null
Cp::DashboardVisibility service: maps journey stage → visible main/sidebar sections; tier-aware sidebar ordering; feature-flagged selective data loading
- Rake task
journey_stage:recompute_all: backfill all existing champions’ cached journey_stage
Added — Phase 13.2: Progressive Dashboard Layout
Cp::HomeController: parallel to existing DashboardController; feature-flagged root; selective data loading (13 categories, only queries what the stage needs); activity recording + journey recomputation on load
- Route
GET /home → cp_home_path; feature flag config.x.features.progressive_dashboard (all envs set to false — gradual rollout)
- 17 view files under
app/views/cp/home/: show.html.erb orchestrator, _hero.html.erb (8 stage variants), 3 card tier partials (Primary/Standard/Compact), 7 sidebar partials, 5 stage partials (onboarding, exploring, engaging, champion, community_leader)
Cp::HomeHelper (app/helpers/cp/home_helper.rb): thread_other_champion(thread, champion) with multi-API fallback to shield views from model API changes
- 58 new tests: 25 Tierable, 24 DashboardVisibility, 9 HomeController, 2 HomeHelper, 1 connections partial render test
[1.0.46] - 2026-02-19
Added — Career Page Overhaul & Career Connect
- Career page redesign: New hero, Handshake as primary tool, three resource boxes (Career Resources, Career Fair Events, Career Guidance Events), simplified OCPD section
- Career Connect section: Personalized alumni matching based on career clusters — shows career-open alumni who share your field of study
- Major-level cluster mapping: 5 career clusters with ~120 major-specific overrides from OCPD data, handling cross-college majors correctly
- Cluster selector: Active cluster pill with “Looking for another career cluster?” reveal toggle
- Load more pagination: Turbo Stream-powered “Show More Alumni” button
- Blurred teaser for unauthenticated visitors with sign-in CTA
Improved — Career Connect Cards
- City/state location shown under name (respects address privacy settings)
- Connect button as primary CTA (60/40 split with View Profile)
- All degrees displayed with graduation years (not just cluster-matching degree)
- Connection request modal available directly from career page
Fixed
- View Profile link inside turbo frame caused “Content missing” — added
turbo_frame: "_top"
- Alumni-only filter excludes students and faculty/staff from Career Connect
.preload instead of .includes prevents SQL WHERE from filtering eager-loaded degrees
[1.0.45] - 2026-02-18
Fixed — Duplicate Daily Digest Emails
- Root cause:
deliver_later created EmailLog dedup records asynchronously via Sidekiq; if digest job ran multiple times before delivery, dedup check found nothing and re-sent
- Changed
deliver_later → deliver_now in Cp::NotificationDigestJob for both daily and weekly digests
- Dedup via
Cp::EmailLog now works reliably since interceptor writes log synchronously
- Backlog item added for async delivery at scale (>300 recipients)
Added — Phase 14: Alumni Content Submissions
14.1 — Data Model & Backend Foundation
submitted: -1 and declined: -2 status enums on Cp::NewsPost and Cp::Event
- Submission fields:
submitted_by_champion_id, decline_reason, submitted_at, declined_at
Cp::ContentSubmissionThread model (polymorphic content, open/resolved status)
Cp::ContentSubmissionMessage model (polymorphic sender, body, read_at tracking)
- Public-facing scopes exclude submitted/declined content
14.2 — Champion Submission Forms & UX
- News submission form at
/news/submit (Cp::NewsSubmissionsController)
- Event submission form at
/events/submit (Cp::EventSubmissionsController)
- CTAs on news index, events index, and community content sections
- Community pre-selection via
?community_id= parameter
- Submission history at
/my-submissions (Cp::SubmissionsController)
- Thread detail view with conversation history
14.3 — Admin Submissions Queue & Review
- Admin queue at
/champions/content_submissions with tab-filtered index
- Tabs: Pending / In Conversation / Promoted / Declined with count badges
- Promote to Draft, Publish Directly, and Decline with reason actions
- Reply functionality on conversation threads
- Admin sidebar link with pending badge
14.4 — Notifications & Attribution
- 4 notification types:
submission_reply, submission_promoted, submission_published, submission_declined
Cp::ContentSubmissionMailer with 3 email flows (new submission, champion reply, published)
- Attribution display on published news (card + detail) and events (card + list + detail)
Changed — iMessage-Style Chat Bubbles
- Unified chat bubble styling across all 5 thread views (support × 2, submission × 2, connections)
bg-skyblue/30 for sender’s bubbles, bg-white border for recipient’s
- Contrast-safe text colors (
text-gray-700/text-gray-800)
- “Engagement Team” as sender name on champion-facing thread views
Tests: 3,582 runs, 9,178 assertions, 0 failures
[1.0.44] - 2026-02-17
Added — Email Notification Logging
cp_email_logs table with automatic logging via EmailLoggingInterceptor
- Admin interface at
/champions/email_logs (index, show, stats, CSV export)
- Email type, recipient, subject, metadata (JSONB), and status tracking
Cp::EmailLog model with scopes and analytics methods
Added — Notification Frequency Guardrails
- Digest cooling-off:
digest_mention_count on thread participants; threads become “stale” after 2 mentions, shown only when fresh content exists
- Digest deduplication: Daily/weekly digests check
cp_email_logs to prevent duplicate sends (scheduler double-fire protection)
- Weekly stats accuracy: Exclude already-emailed notifications from weekly digest stats
- Stale thread section: Gentle “Still waiting for you” section in digest emails with softer styling
mark_read! and new message callbacks reset digest_mention_count to 0
- Connection-based stats (Phase 10.5) replacing deprecated contacts
Fixed
- CI database setup:
db:prepare replaces db:create + db:schema:load
- Seeds data: Use correct engagement level values (0-4)
- Digest job: Use
.deliver_later directly (Rails 7.1 mail header restriction)
Documentation
- Email logging quick start guide and feature docs
- Notification frequency guardrails implementation docs
Tests: 3436 runs, 0 failures
[1.0.43] - 2026-02-17
Added — Fuzzy Search Improvements
- Lookup Portal fuzzy fallback: When exact ILIKE search returns 0 results, automatically falls back to trigram + soundex + levenshtein matching with amber “closest spelling matches” indicator
- API offset pagination:
offset parameter, total_count, and has_more fields in API response envelope
- fuzzystrmatch extension: soundex() and levenshtein() for phonetic/edit-distance matching on short names (e.g., “taegan die” → “teagan dye”)
- 5 new fuzzy fallback tests (service + controller), 8 new pagination tests
Fixed
- Banner import Tempfile GC race condition: replaced Ruby Tempfile with regular file in
tmp/banner_uploads/ to prevent garbage collection between preview and commit requests
Tests: 3389 runs, 0 failures
[1.0.42] - 2026-02-17
Added — Alumni Search REST API
GET /api/v1/alumni/search endpoint with Bearer token API key authentication
ApiKey model with key generation, authentication, per-minute rate limiting (100/min default), and revocation
- pg_trgm extension + GIN trigram indexes on alumni name columns for fuzzy matching
filter_by_fuzzy_name and filter_by_fuzzy_full_name scopes on Alumni model
- Fuzzy fallback in
AlumniMatcher (exact search first, pg_trgm fallback on no results)
- Search by full name, first/last name, email, or BUID; response matches Copy blob format
- Rake tasks:
api_keys:create, api_keys:list, api_keys:revoke, api_keys:stats
- API documentation:
docs/features/ALUMNI_SEARCH_API.md
- Tests: ApiKey model (14), AlumniSearchController (26) — 3377 total, 0 failures
Added — CL Welcome via Connections
- New
cl_welcome connection type for Community Leader welcome messages
- Migration:
community_id column on cp_connection_requests for CL context
- CL welcome flow on Leadership page sends connection request (not direct message)
- Batch eligibility checks: connected/pending/privacy status shown on member lists
- Gold-styled CL welcome request cards with community context and star icon
- CL pill on connections listing:
⭐ CL · CommunityName +N (desktop), ⭐ CL (mobile)
- “Show only CLs” filter toggle on connections page
- “Your Community Leaders” sidebar card on community show page
- Bidirectional CL↔Member privacy bypass in
allows_contact_from?
cl_welcome_sent activity event type
USER_CONNECTION_TYPES constant (excludes system-only types from user settings)
- Expandable message preview on request cards (click to expand, gradient fade)
- Accepted sent requests hidden by default with “Show N accepted” toggle
expandable_text_controller.js Stimulus controller for click-to-expand text
- Tests: ConnectionRequest model (6), Champion model (3), Service (3), Leadership controller (4), Connections controller (1)
[1.0.41] - 2026-02-17
Changed — Terminology Consistency
- Replaced “Champion Portal” with “Alumni Portal” across 30+ documentation files
- User-facing copy updated: “Champions” → “alumni” per Language Style Guide
- Fixed importmap pin for push notification helpers
- Updated Mailgun DNS documentation to match current domain
Tests: 3319 runs, 0 failures
[1.0.40] - 2026-02-17
Changed — “Belmont Alum” Rebrand
- FROM address: All mailers now default to
Belmont Alumni <alumni@mail.belmontalum.com> (was Belmont Alumni Champions <champions@mail.alumnichampions.com>)
- Email subjects: “Champion Portal Weekly Summary” → “Belmont Alumni Weekly Summary”, invite → “invites you to join Belmont Alumni”, admin signup → “New Alumni Signup”
- Email headers: All
<h1> text updated from “Belmont Alumni Champions” / “Alumni Champions” to “Belmont Alumni”
- Email alt text: All
alt="Alumni Champions" → alt="Belmont Alumni" on header images
- Email body copy: ~30 replacements removing “Champion Portal” and “Alumni Champions” as portal name (e.g., “your Champion Portal account” → “your account”, “signed up for Alumni Champions” → “signed up”)
- Email footers: “Belmont Alumni Champions • alumnichampions.com” → “Belmont Alumni • alumnichampions.com”
- Digest emails: “Your Daily Champions Update” → “Your Daily Alumni Update”, “Your Weekly Belmont Champions Update” → “Your Weekly Belmont Alumni Update”
- PWA manifest: name/short_name → “Belmont Alum”, description → “Belmont University Alumni Community”
- Meta tags: Page title suffix → “Belmont Alum”, descriptions use natural phrasing instead of portal name
- Page titles: 20+ Champion Portal views updated from “Champion Portal” / “Alumni Champions” suffix to “Belmont Alum”
- Body copy: Landing page, directory, communities, help, feedback, settings, profile wizard, public CTA — all use natural phrasing (“your alumni community”, “your experience”) instead of portal proper name
- Staff admin views: “for the Champion Portal” → “for the alumni portal” in photo albums, events, news posts, stats, career resources, roadmap
- Welcome email: “Welcome to Alumni Champions!” → “Welcome to Belmont Alumni!”
- Admin notifications: “Champions quiz” → “alumni quiz”, kicker labels → “Belmont Alumni”
Preserved (identity language)
- “Alumni Champion” as identity/role recognition kept in signup flow, welcome email, invite, FAQ
- “You’re officially an Alumni Champion!” subject line kept
- Staff-facing “Alumni Champions” program references kept
Tests: 3318 runs, 0 failures, 0 errors
[1.0.39] - 2026-02-16
Added
*.belmontalum.com as alternate Champion Portal domain (routing, host allowlists, verification)
- Supported subdomains:
belmontalum.com, www.belmontalum.com, beta.belmontalum.com, staging.belmontalum.com, dev.belmontalum.com
Changed
- Updated route constraint to recognize
belmontalum in hostname
- Updated production, staging, development, and test host allowlists
- Updated post-cutover verification rake task
Tests: 3318 runs, 0 errors
[1.0.38] - 2026-02-16
Changed — Phase 12: Source-Alignment Review
Full audit of Champion Portal user-facing text against 20 canonical source documents. 20 of 25 findings implemented across 7 waves; 5 explicitly deferred.
Inclusivity Framing (Project-Wide)
- Identity principle established: External-facing surfaces lead with “alumni” and “Bruins” (inclusive, welcoming). Internal/post-login surfaces celebrate members as “Champions” (recognition of showing up, not a gated title).
- Platform naming dissolved: No proper noun for the platform. “Alumni Champions” = the people, not the product. UI says “your community” / “your alumni community.”
- Updated Language Style Guide, AGENTS.md, and copilot-instructions.md with inclusivity framing.
Tier 1 — Quick Wins (9 findings, 15 files)
- “Sign Up — It’s Free” → “You’re Invited — Join Your Fellow Bruins” (landing, careers)
- “Become a Champion” → “Welcome Home, Bruin” (registration page)
- “recruitment district” → “local Bruin community” (profile)
- “You don’t currently qualify” → warm, explanatory alternative (communities)
- “the … system” → “your … community” (4 support email templates)
- “User” → “Champion” / “person” / “member” (feedback, boards, moderation)
- “New to Champion Portal?” → “First time here?” (sign-in page)
- “Welcome to the Champion Portal!” → “Welcome Home, Bruin! 💙” (notification email)
- “alumni network” → “alumni community” (welcome packs config)
Tier 2 — Email Overhaul (3 templates)
- Confirmation email (O-02): Rewritten from task-list to belonging-first tone
- Verification approved email (O-03): Rewritten to affirm identity at peak emotional moment
- Invite email (O-04): Rewritten with community-first, relationship language
Tier 3 — Copy & UX Improvements
- Wave A: FAQ “portal” → “community” (8 replacements), “manage” links → “your” links, gatekeeping flash messages softened (4 messages)
- Wave B: 15 empty states rewritten with warmth and CTAs across dashboard, boards, news, events, messages, photo albums, and leadership
- Wave C: 8 flash messages warmed (sign-in/out, boards, communities)
- Wave D: Landing page pillars renamed: Connect/Host Events/Share Stories → Belong / Gather / Inspire. Hero subtitle: “Every Bruin who shows up is already a Champion.”
- Wave E: Wizard header subtitle and connections step copy refined
Deferred
- F-01: Roles woven into experience (Phase 11 scope)
- F-03: Generosity-as-formation language (future content work)
- F-04: Source guardrail phrases (partially addressed via other findings)
- F-06: i18n infrastructure (explicitly deferred by user)
- L-08: Careers networking vocabulary (kept as-is per user decision)
[1.0.37] - 2026-02-15
Added — Phase 11: Champion Role Dashboard Card
- Role Dashboard Card — Dashboard card with two states: State A (no role → “Discover Your Role” quiz CTA) and State B (role selected → daily idea pack with refresh). Card appears for email_verified and champion_verified Champions only.
- Daily Role Idea Packs —
Cp::RoleIdeaPackService generates 3 role-aligned action suggestions daily per Champion. Features: weighted random selection by priority, 30-day anti-repeat window, timezone-aware date rollover, Community Leader variant (mixed pool of role + CL-specific ideas).
- Roles Landing Page —
/roles page showing all 4 Champion roles (Community Builder, Digital Ambassador, Connection Advisor, Giving Advocate) with descriptions from canonical source documents. Includes “Discover Your Role” quiz CTA.
- Admin Role Ideas CRUD — Staff management at Champions > Role Ideas. Create/edit/activate/deactivate/archive ideas with role/target filtering, search, status toggle, priority control. Sidebar link with active count badge.
- 115 Seed Ideas — YAML seed data with ideas across all roles (25 each for CB/DA/CA/GA, 15 CL), authored from canonical source documents. Rake tasks:
role_ideas:seed, role_ideas:stats, role_ideas:cleanup_old_packs.
- Turbo Stream Refresh — “Try different ideas” button replaces idea pack via Turbo Stream without full page reload. Records
refreshed_role_ideas activity event.
Models Added
Cp::RoleIdea — Content model with role, target, status enum (draft/active/paused/archived), priority weighting, template variable interpolation (, , )
Cp::RoleIdeaPack — Daily pack per champion per date with refresh_count tracking
Cp::RoleIdeaPackItem — Join model with position ordering (primary at 0, secondary at 1+)
Activity Events Added
viewed_role_card, clicked_take_quiz, clicked_learn_more_roles, refreshed_role_ideas, clicked_role_idea_cta, role_selected
[1.0.36] - 2026-02-15
Added — Phase 10: Connections
- Connection Request System — Champions request connections by choosing a type (Say Hi / Career Advice / Networking) and writing an initial message. Requestees can accept, ignore, or let requests expire. Rate limiting: 10 requests/day for requestors, configurable cap for requestees.
- Connection Settings — Per-champion JSONB preferences: open-to types, daily cap, pause mode, almost-alumni toggle, community/district restrictions. Managed via Settings > Connection Preferences.
- Connections Hub — Two-tab layout replacing the old Messages nav: “Connections” tab (active conversation threads sorted by last message) + “Requests” tab (incoming/sent/past sub-tabs). Compose modal lists connected champions only.
- Connection-Gated Messaging — Must have an active Connection to message (except support threads). Disconnect archives the thread; reconnection requires a new request.
- Directory & Profile Integration — “Open to” type pills on directory cards and profiles. Connect button with modal on profiles. Filter directory by connection type. Button states reflect connection/request status.
- Contacts → Connections Migration — Mutual contacts auto-converted to Connections with linked message threads. One-way contacts removed.
ChampionContact model deleted. FAQ and UI updated.
- Admin Connection Metrics — Staff dashboard with connection stats, request rates, super-connector identification, acceptance metrics. Available at Champions > Stats > Connections.
- Engagement Score Hardening — Guard for blank
event_type in EngagementScoreService#point_value to prevent NoMethodError on nil.
Models Added
Cp::Connection — Canonical pair ordering (champion_a_id < champion_b_id), required message_thread, connected_at/disconnected_at timestamps, disconnect! method
Cp::ConnectionRequest — Statuses: pending/accepted/ignored/cancelled. Types: say_hi/career_advice/networking. Rate limiting via sent_today_by/received_today_by scopes
Models Removed
Cp::ChampionContact — Replaced entirely by Cp::Connection
Changed
- Nav: Messages → Connections — Main navigation renamed; badge shows unread connection messages
- MessageThread — Added
thread_type column (connection/support) with corresponding scopes and methods
- Champion — Replaced
contact_preference enum with connection_preferences JSONB. Added connection associations, preference methods, and connected_champion_ids cache
Post-Deployment Tasks
# Run AFTER deployment (idempotent — safe to re-run)
heroku run bin/rake connections:migrate_contacts --app alumni-lookup
[1.0.35] - 2026-02-10
Fixed
- Community Deactivation — Pending community suggestions are now removed when a community is deactivated, preventing champions from seeing recommendations for inactive communities
- Event Timezone Select — Timezone dropdown now correctly defaults to Central Time; option values changed from Rails friendly names (
tz.name) to IANA identifiers (tz.tzinfo.identifier) to match stored DB values
- Sidebar & Community Index Pills — Added
whitespace-nowrap flex-shrink-0 to count badge pills to prevent awkward line wraps
- Welcome Dismiss Idempotency —
dismiss_welcome! now preserves original timestamp on repeated calls (fixes flaky CI test)
Changed
- Message Notification Default — Default email frequency for new messages changed from
"daily" to "immediate" so new champions receive prompt email notifications for unread messages
Added
- Belmont Campus Location Shortcut — “On Belmont Campus” button on event form auto-fills Belmont University address (1900 Belmont Blvd, Nashville, TN 37212) and changes venue name field to prompt for building/room
[1.0.34] - 2026-02-10
Added - Phase 7: Career Center (Complete)
- Career Center Hub (
/careers) — Public landing page with self-serve-first career resources
- Hero section: “Your Career Partner for Life” with CTA
- Quick Links grid (Handshake, LinkedIn Learning, Indeed, Glassdoor) from YAML config
- Career Events section showing events tagged
career_event: true
- Networking Resources grouped by 6 categories with download buttons
- OCPD Partnership section with consultation booking, contact info
- Sign-up CTA for unauthenticated visitors
- Career Center link in top-level navigation (desktop + mobile)
- Resource Downloads — Authenticated PDF downloads with counter tracking
resource_downloaded activity event with resource_id and category metadata
- Login redirect for unauthenticated users
- Staff Resource Management — Full CRUD in Champion Admin (
/champions/career_resources)
- PDF drag/drop upload via
pdf_dropzone_controller.js Stimulus controller
- Search, category filter, status filter
- Toggle active/inactive (soft remove)
- Stats: total resources, active count, total downloads
- Career Events Integration —
career_event boolean on cp_events table
- Admin checkbox on event form
Cp::Event.career_events scope
- Career events displayed on Career Center and standard events pages
- Career Center Analytics — Staff dashboard at
/champions/stats/career_center
- Views, downloads, unique champions, daily trends
- Period filtering (7d/30d/90d/All Time)
- Activity Events —
career_center_view and resource_downloaded tracked
- Data Model —
cp_career_resources table with Active Storage PDF attachment
- 6 resource categories: Getting Started, Resume Building, Interview Preparation, Career Advice Conversations, Networking & Job Seeking, Follow-Up & Relationship Building
- Tests — Model tests + 3 controller test files (careers, downloads, admin CRUD)
Fixed
- Career resource CATEGORIES test assertion updated from 4 to 6 to match expanded model categories
[1.0.33] - 2026-02-07
Added - Seeded Discussions Auto-Creation
- Community
after_create Callback — New communities automatically receive 2 seeded discussion posts:
- Selects from global seeded questions using
SeededQuestionSelector with deduplication
- Publishes via
SeededQuestionPublisher with community-aware interpolation
- Skips custom communities and communities with discussion boards disabled
- Logs creation count via
Rails.logger.info
- Tests — 14 tests in
community_seeded_discussions_test.rb covering callback conditions, method behavior, and integration
Fixed
- BoardPost
dependent: :destroy — Changed has_one :seeded_question_exposure from dependent: :nullify to dependent: :destroy to fix NotNullViolation crash when deleting board posts (the board_post_id column has a NOT NULL constraint)
- WelcomeContentGenerator Service — Auto-generates welcome content for community boards:
- Template-based content from
config/welcome_packs.yml with 15 community types
- Markdown-to-HTML conversion using Redcarpet gem with extensions (hard_wrap, autolink, tables, fenced_code_blocks, strikethrough)
- Variable interpolation (
,) for personalization
- Deterministic template rotation based on community ID
- Welcome Panel UI (
_welcome_panel.html.erb):
- Gradient-accented dismissible panel shown on community pages
- Renders ActionText rich content with proper prose styling
- Stimulus-powered smooth AJAX dismissal via
dismiss_welcome_controller.js
- Auto-dismisses after configurable number of views (default 5)
- Member Tracking — Added to
cp_champion_communities join table:
welcome_dismissed_at (datetime) — When member dismissed the panel
community_view_count (integer) — Tracks visits for auto-dismissal
dismiss_welcome controller action with activity event tracking
- Rake Tasks — 7 management tasks in
lib/tasks/welcome_content.rake:
welcome_content:generate_all — Generate for all communities
welcome_content:generate[community_id] — Generate for specific community
welcome_content:preview[community_type] — Preview templates (supports HTML=1 flag)
welcome_content:status — Show generation status across all communities
welcome_content:clear[community_id] — Clear welcome content
welcome_content:reset_dismissals[community_id] — Reset member dismissals
welcome_content:generate_type[type] — Generate for all of a community type
- Tests — Comprehensive coverage (26 tests):
welcome_content_generator_test.rb — Service generation and template selection
welcome_content_rake_test.rb — All rake task functionality
- Controller and helper tests for dismissal and display logic
- Unverified Champion Community Access — Champions can view and join communities during verification:
- View all public community pages, boards, discussions without verification
- Join communities immediately after signup (stored in
community_memberships)
- Browse discussion boards and read all posts and comments
- Only contribution features (reactions, comments, posting) gated by verification
- Verification-Gated UI with Contextual Messaging:
- boards/show.html.erb: Reactions and comment forms gated with verification teaser
- discussions/show.html.erb: Comment forms, reactions, editing gated with contextual messages
- discussions/index.html.erb: “New Post” button gated for unverified members
- communities/index.html.erb: “Join Now” button shows verification message when appropriate
- Consistent teaser pattern: Gray-50 box with lock icon explaining what verification unlocks
- Trust-First Philosophy Implementation:
- Verification unlocks CONTRIBUTION, not BELONGING
- Unverified champions feel welcomed, not excluded
- Clear messaging about what becomes available after verification
- No “wall” blocking community exploration
Added - Onboarding Metrics & Analytics (Phase 9.4 Complete)
- Cp::OnboardingEvent Model — Tracks all onboarding journey events:
- Event types: signup_email_submitted, signup_email_verified, signup_password_created, signup_oauth_started, signup_oauth_completed, wizard_started, step_viewed, step_completed, step_skipped, wizard_completed, community_accepted, community_declined
- Class methods for recording each event type with appropriate metadata
- Scopes for period filtering: last_7_days, last_30_days, last_90_days
- OnboardingInsights::OnboardingMetricsService — Signup funnel and wizard metrics:
- Signup funnel: email_submitted → email_verified → password_created
- OAuth funnel: oauth_started → oauth_completed
- Wizard funnel: started → completed with step-by-step breakdown
- Period filtering (7d, 30d, 90d, all time)
- OnboardingInsights::SeededQuestionMetricsService — Community suggestion metrics:
- Accept/decline counts and rates by community type
- Sorted tables with champions-joined ranking
- Period filtering
- InsightsController — Admin dashboard for metrics:
/champions/insights/onboarding — Signup funnel and wizard completion
/champions/insights/seeded_questions — Community suggestion performance
- Requires portal_admin+ role
- Event Instrumentation — Added to 5 controllers:
- RegistrationsController: signup_email_submitted
- ConfirmationsController: signup_email_verified, signup_password_created
- OmniauthCallbacksController: signup_oauth_started, signup_oauth_completed
- ProfileWizardController: wizard_started, step_viewed, step_completed, step_skipped, wizard_completed
- CommunitySuggestionsController: community_accepted, community_declined
- Tests — Comprehensive coverage:
- onboarding_metrics_service_test.rb: 432 lines, covers period filtering, funnel metrics, edge cases
- seeded_question_metrics_service_test.rb: 341 lines, covers accept/decline counts, community type breakdown
- insights_controller_test.rb: 158 lines, covers auth, authorization, period params
- All tests passing: 45 runs, 136 assertions, 0 failures
Added - Seeded Questions Scheduler & Bug Fixes (Phase 9.3)
- Scheduler Documentation — Added
seeded_questions:create to SCHEDULERS.md:
- Daily schedule (runs at 6am CT for evening question posting)
- Test commands and validation instructions
- Integration with Heroku Scheduler
- Rake Task Tests (
test/tasks/seeded_questions_rake_test.rb):
- 4 tests covering
seeded_questions:create and seeded_questions:stats
- Validates task execution and output formatting
Fixed - Seeded Questions Rake Task Bugs
seeded_questions:stats task — Fixed NoMethodError: undefined method 'active':
- Changed
Cp::SeededQuestion.active to Cp::SeededQuestion.status_active
- The
status enum uses prefix: true, so scopes are status_active, not active
seeded_questions:preview task — Fixed wrong column name:
- Changed
cp_community_id to community_id (BoardPost schema uses community_id)
seeded_questions:create task — Fixed wrong column name (previous session):
- Changed
cp_community_id to community_id at line 26
Added - Seeded Discussion Questions (Phase 9.2)
- Cp::SeededQuestion Model — Staff-authored discussion prompts with:
- Template variables for interpolation ({community_name}, {community_type}, {member_count}, etc.)
- Targeting: global (all communities), community_type (district/college/etc.), affinity_category
- Status workflow: draft → active → paused → archived
- Priority weighting (1-10) for selection algorithm
- Cp::SeededQuestionExposure Model — Tracks when questions are posted:
- Links seeded_question, community, and resulting board_post
- Supports 30-day rotation cooldown per question/community pair
- Scopes: recent, for_question, for_community, within_cooldown
- Cp::SeededQuestionSelector Service — Selection algorithm with:
- Community eligibility checks (organic activity, member count, max active questions)
- Weighted random selection favoring priority and freshness
- Constants: ROTATION_DAYS=30, MAX_ACTIVE_PER_COMMUNITY=2, ORGANIC_ACTIVITY_DAYS=7
- Cp::SeededQuestionPublisher Service — Publishing logic with:
- Transaction-wrapped BoardPost creation with author_type: ‘Staff’
- Template interpolation via Cp::QuestionInterpolator
- Exposure record creation with posted_at timestamp
- Cp::QuestionInterpolator Service — Template variable interpolation:
- Variables: {community_name}, {community_type}, {member_count}, {current_month}, {current_season}, {current_year}
- Season mapping (Winter: Dec-Feb, Spring: Mar-May, Summer: Jun-Aug, Fall: Sep-Nov)
Added - Admin Seeded Question Interface (Phase 9.3)
- Champions::SeededQuestionsController — Full CRUD with:
- Index with filtering (status, target_type) and search (title, body_template)
- Sorting by title, priority, exposures_count, created_at
- Preview action with live template interpolation
- Activate/deactivate actions for status management
- Admin Views (7 files in
app/views/champions/seeded_questions/):
- Index with filter tabs, search, sortable columns
- Show page with exposure history, performance metrics placeholder
- New/Edit forms with template variable reference
- Preview panel showing interpolated output
- Status badge partial with color-coded states
- Routes — Resourceful routes with member actions:
resources :seeded_questions with preview, activate, deactivate
- Controller Tests — 36 tests, 89 assertions covering:
- Index filtering and sorting
- CRUD operations with authorization
- Preview interpolation
- Activate/deactivate state transitions
Added - Enhanced Onboarding Flow (Phase 9.1)
- join_communities Wizard Step — New step in profile wizard suggesting communities based on:
- Alumni degrees (college and major communities)
- Location (district communities)
- Affinities (organization/club communities)
- Community Suggestion Cards — Display suggested communities with:
- Community icon by type (district, college, major, affinity, industry)
- Name, member count, and personalized reason
- “Join” and “Not for me” action buttons
- AJAX Integration — Turbo Stream removes cards smoothly on accept/decline
- Step Logic — Step only shows if champion has pending suggestions (skipped otherwise)
- Bug Fixes:
- Fixed
college_desc → college_name attribute name in CommunityDetectionService
- Fixed
for_wizard scope to use integer enum values (0, 1, 2) not string names
Added - Mobile Optimization (Champion Admin)
- DESIGN-GUIDELINES.md v2.3 — Major documentation update:
- Section 8.11: Admin Portal Mobile Patterns (table→card conversion, status badges, action buttons)
- Section 8.12: Admin Card & Container Standards (responsive rounding, inner padding)
- Section 8.13: Mobile Optimization Checklist (comprehensive 10-category audit framework)
- New Admin Show Pages:
champions/events/show.html.erb — Full event detail view with breadcrumbs, status, cover image, date/location cards
champions/news_posts/show.html.erb — News post detail with hero, content cards, featured champions, engagement stats
- Mobile Card Views — Added
sm:hidden mobile cards + hidden sm:block desktop tables:
- News Posts index
- Photo Albums index
- Verification Queue
- Filter Pills Pattern — Replaced underline tabs with touch-friendly pills:
- Support Threads (status filter)
- Feedbacks (status/type filters)
Changed - Mobile Optimization (Champion Admin)
- Card Rounding Standardized — All 50+ admin cards now use
rounded-md sm:rounded-lg (8px mobile → 12px desktop)
- Container Wrappers Removed — Eliminated redundant
px-4 sm:px-6 lg:px-8 wrappers from ~14 view files (layout provides these)
- Button Stacking — Action buttons now use
flex-col sm:flex-row for mobile-friendly vertical stacking
- Header Layout — Admin header title now links to Champion Admin root
- Text Truncation — Standardized
line-clamp-2 with proper flex container constraints
Fixed
- Routes updated to include
show action for news_posts and events (was except: [:show])
- Test assertions updated for new UI patterns (button text, filter selectors)
[1.0.32] - 2026-02-01
Added - Current Student Data Integration
- Current Student Import System (
Settings → Data Imports → Current Students):
- Two-pass CSV import: Preview first, then commit
- Redis-based manifest storage for import state
- Duplicate BUID detection and warnings
- Detailed import summary with statistics
- Student Status Display:
- “Current Student” badge on alumni profiles
- Student info in search results and profile views
- Major/classification/enrollment year display
- Current Student Model (
current_students table):
- Links to alumni via BUID
- Stores major, classification, enrollment year, expected graduation
active flag for current enrollment status
Fixed - Bug Fixes
- Redis SSL handling: Fixed SSL certificate verification error for Heroku Redis in
CurrentStudentManifestStore (matches pattern in ManifestStore and Sidekiq)
[1.0.31] - 2026-01-31
Added - Security & Rate Limiting
- Rack::Attack rate limiting (
config/initializers/rack_attack.rb):
- General rate limit: 300 requests per 5 minutes per IP
- Asset throttle: 500 requests per minute per IP
- Login protection: 5 attempts per 20 seconds per IP/email
- API rate limit: 60 requests per minute per IP
- Password reset throttle: 3 per 30 minutes per IP
- Blocklists:
- WordPress vulnerability scanner paths (
/wp-admin, /xmlrpc.php, etc.)
- Screenshot services with
?screenshotCacheBust= parameter
- Fail2ban: 10 throttle violations in 10 minutes → 1 hour IP block
- Request logging middleware (
lib/middleware/request_logger.rb):
- Logs slow requests (>5 seconds) with
[SLOW REQUEST] tag
- Detects screenshot service requests with
[SCREENSHOT SERVICE] tag
- Detects vulnerability scanners with
[SCANNER DETECTED] tag
- Security rake tasks (
lib/tasks/security.rake):
security:status — Show Rack::Attack configuration status
security:rules — List all security rules
security:analyze_tips — Heroku log analysis commands
- Recommendation caching: Directory recommendations now cached per champion (4 hour expiry)
- Counter caches:
unread_messages_count and unread_notifications_count on champions
- Redis caching: Production uses Redis for caching when available
Fixed - Bug Fixes
- Memory leak: Fixed event converter memory leak in background jobs
- Screenshot feature: Various screenshot-related fixes
[1.0.30] - 2026-01-31
Added - Feedback Screenshot Feature
- Screenshot attachment: Feedback form now supports optional screenshot upload
- Mobile-friendly capture: File input includes camera capture for mobile devices
- Preview functionality: Selected screenshots display with preview before submission
- Admin display: Screenshots visible in Champion Admin feedback view
Fixed - Bug Fixes
- Mutual contact notification: When adding a contact who already has you as a contact, notification now correctly shows “You’re now mutual contacts with [Name]!” instead of the generic add message
- Community email template: Fixed
undefined method 'build_full_url' error in join_request_approved.html.erb by adding the helper method to CommunityMailer
- Mobile contact button overlap: Profile page contact button no longer overlaps with photo on mobile — moved to inline position below photo on small screens
- Bell notification filtering:
new_message notifications now excluded from bell dropdown entirely (they have their own indicator in the Messages nav item)
[1.0.29] - 2026-01-29
Changed - Custom Communities UX Improvements
- Removed “custom” pill: Custom communities no longer display a blue “custom” pill on community cards and headers, creating a cleaner look
- Public join requests: Custom communities now allow join requests from any verified champion (previously only matched by slug)
- Membership prompt: Non-members see an inviting “Request to Join” card instead of a restricted access message
- CommunityNotification web push: Champions now receive web push notifications when a community they’re interested in reaches its member threshold
- Complete notification coverage: All three notification systems (StaffNotification, Cp::Notification, CommunityNotification) now support web push
[1.0.28] - 2026-01-29
Changed - Champion Portal Subdomain Restriction
- Portal availability: Champion Portal now only enabled on specific subdomains:
beta.alumnichampions.com — Production beta (live data)
staging.alumnichampions.com — Staging environment
dev.* and localhost — Development
- Marketing site redirect:
alumnichampions.com and www.alumnichampions.com now redirect to the legacy signup flow (Squarespace marketing site)
- CI updates: GitHub Actions smoke tests updated to use staging URLs
[1.0.27] - 2026-01-29
Changed - Beta vs Staging Environment Distinction
- Environment detection: Split Champion Portal environment detection into three tiers:
- Beta (
beta.alumnichampions.com) — Blue banner, production app with live data
- Staging (
staging.alumnichampions.com) — Amber banner, staging app with test data
- Development (
dev.*, localhost) — Development indicator
- New helper method: Added
beta_environment? alongside existing staging_environment?
- Color-coded banners: Environment banners now visually distinguish Beta (blue) from Staging (amber)
- Impersonation: Both Beta and Staging environments allow champion impersonation for testing
[1.0.26] - 2026-01-29
Fixed - Rails 7.1+ Compatibility
- db:snapshot task: Fixed compatibility with Rails 7.1+ by using
schema_migration.versions instead of querying ActiveRecord::SchemaMigration directly
- lib/tasks/migrate_cp_data.rake: New rake tasks for staging-to-production CP data migration
migrate:export_cp — Exports all Champion Portal tables as SQL INSERT statements
migrate:export_cp_summary — Preview record counts before migration
- Handles PostgreSQL reserved words (e.g.,
primary column) with proper quoting
- Silenced Rails logging for clean SQL output
[1.0.25] - 2026-01-29
Added - Environment & Domain Migration Tooling
- lib/tasks/verify.rake: Environment verification tasks
verify:environment_comparison — Compare hosts, mailer config, env vars, table stats
verify:db_schema_comparison — Compare migration versions between environments
verify:post_cutover — Automated health checks after domain cutover
- lib/tasks/db_snapshot.rake: Database comparison tool
db:snapshot — Outputs table counts, max IDs, latest timestamps by category
- Categories: Champion Portal (cp_), Engagement (engagement_), Core (alumni, degrees, users)
- docs/operations/DOMAIN_ENV_CUTOVER_RUNBOOK.md: Comprehensive cutover runbook
- Squarespace-specific DNS instructions
- Database sync strategies (full copy vs cp_* tables only)
- Before/During/After checklists
- Rollback procedures
- docs/operations/ENVIRONMENTS_CURRENT.md: Updated with staging environment details
- Domain mapping summary table
- App name references
Changed - Production Host Configuration
- config/environments/production.rb: Updated allowed hosts
- Added
staging.alumnilookup.com and staging.alumnichampions.com
- Removed
alumnichampions.com and www.alumnichampions.com (Squarespace marketing site)
- Added documentation comments explaining domain mapping
[1.0.24] - Previous Release
Changed - Event Photo Gallery Enhancement
- Per-album grouping: Event show page now displays photos grouped by album (each album as separate section) instead of flattening all photos into one grid
- Album sections: Each album shows: title with photo count → photo grid → “View all from [Album]” link
- Clearer navigation: Users can now easily see which photos belong to which album
Added - Test Coverage (Session 5.5 Audit)
- Push subscriptions controller tests: 5 tests covering Web Push subscription CRUD
- Support messages controller tests: 3 tests for CL support message creation
- Support threads fixtures: Test fixtures for support message testing
Fixed - Photo Albums Bug Fixes
- Cover photo ordering: Fixed
featured_photo method to respect position order — dragging a featured photo before another featured photo now correctly makes it the new cover
- JS Cover badge update: Sortable controller now updates Cover badge immediately after drag-drop reorder (no page refresh needed)
- Help text accuracy: Updated admin UI text from misleading “First photo will be used as cover if no cover is set” to accurate “The first featured photo becomes the cover, or the first photo if none are featured”
Added - Phase 5.5: Photo Albums (Complete)
Database & Models
- cp_photo_albums table: Photo album records with slug, title, description, status (draft/published), event linkage
- cp_photo_album_communities table: Join table for community targeting
- cp_photos table: Individual photos with position, featured flag, caption, photographer_credit, metadata (JSONB)
- Cp::PhotoAlbum model: Full associations with communities, photos, event, and has_many_attached :cover_image
- Cp::Photo model: Active Storage attachment with position/featured/caption management
Champion Admin (Lookup Portal)
- Photo Albums CRUD: Full index/new/edit/show/destroy actions in
Champions::PhotoAlbumsController
- Drag-and-drop uploads: Multi-file photo upload with Stimulus controller and progress bars
- Photo management: Reordering via drag handles, caption/credit editing, featured toggle
- Photo deletion: Individual photo removal with confirmation
- Event integration: “Create Photo Album” button on Events index for past events
- Pre-population: Albums created from events inherit title, description, and communities
Champion Portal
/photo-albums index: Gallery listing all published albums with cover images
/photo-albums/:slug detail: Album detail page with responsive photo grid
- Lightbox component: Full-screen photo viewer with swipe/keyboard navigation (←/→/Esc)
- Photo preloading: Next/previous photos preloaded for smooth navigation
- Direct sharing: Album detail pages shareable via URL
Dashboard Integration
- “From the Community” carousel: Featured photos from published albums
- Touch-friendly scrolling: CSS scroll snap for mobile carousel experience
- Empty state handling: Section hidden when no featured photos exist
Activity Tracking
- photo_album_viewed: Tracked when champions view album detail pages
Tests
- Dashboard controller tests: 8 tests covering carousel integration scenarios
- Photo Albums controller tests: 25 tests covering CRUD, nested routes, and photo management
Added - Phase 8: Notification System Enhancements (8.1-8.4 Complete)
Phase 8.1: Settings UI Updates
- Discussions notification group: Added new “Discussions” section to Settings with post_reply, comment_reply, post_reaction, and comment_reaction types
- CL-only visibility: Join Requests and Community Leadership notification rows now only visible to Community Leaders
- Explanatory note: Added clarification that account notifications (verification, support) are always immediate
Phase 8.2: Discussion Notifications
- Discussion notifications in digests: Post replies, comment replies, and reactions now included in daily/weekly digest emails
- Aggregated activity: Discussion activity grouped by board/post for better readability
Phase 8.3: Scheduler Activation
- Monday guard fix: Added
return unless Time.zone.today.monday? guard to Cp::WeeklyDigestJob — Heroku Scheduler only supports 10-min/hourly/daily
- SCHEDULERS.md updated: Documented scheduler limitation and workaround pattern
- Activation commands: Added Heroku Scheduler commands for champion daily/weekly digests
- 8 new branded email templates (HTML + text versions) for account notifications:
verification_pending_notification — When champion signs up, awaiting staff verification
verification_approved_notification — When champion is verified by staff
support_reply_notification — When staff replies to support thread
community_leader_assigned_notification — When champion is assigned as CL
- Branded confirmation email: Updated Devise confirmation_instructions to match Champion Portal branding
- ChampionMailer helper: Added
build_full_url method for consistent asset URLs
Bug Fixes
- BUID unique constraint fix: Added
normalize_buid callback to convert empty strings to nil before save, preventing PG::UniqueViolation on faculty/staff signup without BUID
Added
- Notification Digest Rake Tasks: Created
lib/tasks/notification_digests.rake with convenient tasks for QA testing:
bin/rake notifications:digest_recipients — List champions with digest notifications configured
bin/rake notifications:digests:preview_daily[email] — Preview daily digest without sending
bin/rake notifications:digests:preview_weekly[email] — Preview weekly digest without sending
bin/rake notifications:digests:send_daily_to[email] — Send daily digest to specific champion
bin/rake notifications:digests:send_weekly_to[email] — Send weekly digest to specific champion
bin/rake notifications:digests:send_daily — Send daily digests to all eligible champions
bin/rake notifications:digests:send_weekly — Send weekly digests to all eligible (bypasses Monday check)
- QA Email Testing Guide: Created
docs/qa/EMAIL_TESTING_GUIDE.md with comprehensive documentation for testing all 10 mailer classes and 23+ email types in development
Fixed
- Admin Weekly Digest sends on Mondays only: Fixed bug where admin weekly digest was sending daily instead of weekly. Added Monday guard to
Cp::WeeklyDigestJob since Heroku Scheduler doesn’t support weekly scheduling (only 10-min, hourly, daily). Job now runs daily but only sends emails on Mondays.
Added - Phase 5.4: Alumni Like Me (Recommendations) (Complete)
Recommendation Service
- Cp::AlumniLikeMeService: Multi-factor scoring algorithm for Champion recommendations
- Scoring factors: College (+20), Major (+25), Grad Year (+15/10/5), Industry (+15), Affinities (+10 each, max 3), District (+10)
- Minimum threshold: 15 points required to appear in recommendations
- Exclusion logic: Excludes self, unverified Champions, blocked users, existing contacts
Directory Integration
- “Champions Like You” section: Appears on Directory when no search/filters active (first page only)
- Recommendation cards: Compact cards with match reason badges showing why each Champion is recommended
- Visual polish: Gradient accent bars, sparkle icons, “Because you share…” header
Dashboard Integration
- “Champions You Might Know” carousel: Touch-compatible horizontal scroll section on dashboard
- CSS scroll snap: Native mobile-friendly scrolling with 2 cards visible at a time
- Profile threshold: Shows “Complete your profile” prompt if profile <50% complete
- View All link: Links to Directory for full recommendations
Activity Tracking
- recommendation_viewed: Tracked when recommendations are displayed on Directory
- recommendation_clicked: Event type added for future click tracking integration
Model Enhancement
- Champion#affinity_codes: Helper method for efficient affinity matching with automatic cache clearing
- cp_champion_contacts table: Champion-to-Champion contact relationships
- Mutual tracking: Automatically tracks when both Champions add each other
- Privacy integration: Location and messaging privacy with “Contacts Only” option
- Contact button: Icon-only button on profiles and directory cards
- Visual states: Filled user icon = contact, outline = non-contact, amber = mutual
- Hover transitions: Shows “Add” or “Remove” action on hover
- Directory contacts view:
/directory/contacts route for contacts-only listing
- Filter toggle: “My Contacts Only” filter in main directory
New Message Composer
- Modal interface: Instagram-style “New Message” composer
- Contact search: Search contacts and all Champions in single interface
- Direct initiation: Start conversations from directory, profile, or modal
Notifications
- In-app notification: “X added you as a contact” with link to profile
- Email notification: Respects notification preferences (
new_contact setting)
- Notification preferences: Toggle for contact-related notifications
Privacy Settings Enhancements
- Location privacy: “Contacts Only” option for city/state visibility
- Messaging privacy: “Contacts Only” option for who can message you
- One-way privacy: Your contacts see your info; adding someone doesn’t grant you access to theirs
Activity Tracking
- New event types:
contact_added, contact_removed tracked in activity events
- Counter cache columns:
unread_messages_count, unread_notifications_count on cp_champions
- Header badge optimization: Badges use cached counts instead of live queries
- Login drift correction: Counters recalculated on login to prevent drift
UI Polish (included with 5.3)
- Membership badges: “Student” and “Faculty/Staff” badges moved under profile photos consistently
- Messages privacy UI: Simplified one-way conversation warning (precomputed in controller)
- Contact button positioning: Consistent top-right corner placement on cards and profiles
Added - Phase 5.2: Almost Alumni + Faculty/Staff Support (Complete)
New Membership Types
- Membership type enum:
alumni, almost_alumni, faculty_staff on cp_champions
- Almost Alumni fields:
anticipated_program, anticipated_college_code, anticipated_graduation_date
- Faculty/Staff fields:
work_email, profession, affiliated_college_code, affiliated_program
Signup & Verification
- Signup wizard education step: Displays appropriate fields based on membership type selection
- Verification queue: Supports verification of Almost Alumni and Faculty/Staff with membership-type-specific display
Directory & Profile Display
- Directory filters: Alumni (default), Almost Alumni, and Faculty/Staff membership type options
- Directory cards: Display anticipated education for Almost Alumni, profession/affiliation for Faculty/Staff
- Profile badges: “Almost Alumni” and “Faculty/Staff” badges on profiles
- Profile hero: Shows anticipated program and graduation year for Almost Alumni
- Profile sidebar: Displays anticipated education in Education section for Almost Alumni
Wizard & Admin Enhancements
- Bio step in wizard: New step for champions to add their bio during onboarding
- Major autocomplete: Anticipated program field uses autocomplete searching Major model
- Graduation date picker: Month/year selects instead of full date field for anticipated graduation
- Admin membership type change: Dropdown in champion admin show page to change membership type
Deferred to Future Phase
- Degree import hook for automatic Almost Alumni → Alumni upgrade
- Admin “Recheck Education” action
- Almost Alumni upgrade email notification
Fixed
- Champion Admin discussions: editing a post now clears
community_id when switched to Global visibility and clears author fields when set to Engagement Team, preventing stale data from lingering after edits.
- Champion Portal dashboard now includes global (non-community) discussions so announcements surface everywhere.
- Hidden discussions no longer appear on the discussions index; hiding in admin fully removes them from listing.
Added
- Champion Admin discussions: edit support (including Engagement Team-authored posts) and image upload in the admin CRUD flow.
[1.0.24] - 2026-01-21
Added - Phase 5.6: Beta Feedback System (Complete)
Database-Backed Feedback Management
- Cp::Feedback model: Full CRUD with status tracking (new, read, in_progress, resolved, closed)
- Admin interface: Lookup Portal > Champion Admin > Feedback with filtering and pagination
- Sidebar integration: “Feedback” link with unread count badge (below CL Support)
- Staff notifications: Email sent to staff with
can_support_respond permission when feedback submitted
- Permission gating: Mirrors CL Support pattern — requires
portal_admin role AND can_support_respond permission
Files Created
app/models/cp/feedback.rb — Model with validations, scopes, helpers
app/controllers/champions/feedbacks_controller.rb — Admin CRUD controller
app/views/champions/feedbacks/index.html.erb — Filterable list view
app/views/champions/feedbacks/show.html.erb — Detail view with status management
db/migrate/*_create_cp_feedbacks.rb — Database schema
test/models/cp/feedback_test.rb — 24 model tests
test/controllers/champions/feedbacks_controller_test.rb — 14 controller tests
Added - Phase 5.1: User State Experience Alignment (Complete)
5.1a: Anonymous Landing Page
- Landing controller:
Cp::LandingController with champion/city stats
- Landing page view: Full-featured public page with:
- Hero section with Bell Tower background and Belmont logo
- “Your people are waiting.” headline with Sign In / Sign Up CTAs
- “What is an Alumni Champion?” section with 3 pillars (Community Builders, Connectors, Ambassadors)
- Social proof section with live champion count, city count, top cities
- Final CTA section
- Routing:
unauthenticated :cp_champion root now shows landing page
5.1b: Bug Fixes
- Community suggestions: Protected from email-verified (unverified) users
- Navigation access controls: Updated for “browse vs act” strategy
- Controller guards: All controllers verified for proper
require_champion_verified! guards
- Eligibility messaging: Overhauled with 5 distinct states (eligible, member, pending, ineligible, error)
- “What you’ll find here” section: Replaces “Why join?” with value-focused copy
- Helper methods:
community_connection_copy, community_private_copy for standardized messaging
- Language guide: Standardized copy patterns added to LANGUAGE_STYLE_GUIDE.md
Added - Phase 4: Mobile-First Interface Cleanup (Complete)
Mobile Consistency (4.1)
- Bottom nav overlap: Fixed globally with
pb-20 sm:pb-0 padding pattern
- Profile wizard buttons: Fixed visibility on steps 3-6 that were hidden behind bottom nav
- Quick Actions: Moved from Dashboard to hamburger menu
- Header logo: Stacked layout until 1024px breakpoint for better mobile display
Design Alignment (4.2)
- Card margins: Standardized horizontal margins across all views
- Back links: Created
_back_link.html.erb shared partial with consistent chevron pattern
- Pill navigation: Replaced dropdowns with scrollable pills for profile edit and settings
- Auto-scroll pills: Created
scroll_to_selected_controller.js for selected pill visibility
- Save buttons: Applied
w-full sm:w-auto pattern for mobile-first buttons
- Directory filters: Collapsible on mobile with “Filters (X)” toggle button
UX Polish & Image Optimization (4.3)
- Event list card redesign: Mobile: full-width 8:5 image on top; Desktop:
w-48 thumbnail on right
- Dashboard column ordering: Events/District column shows first on mobile
- Image variant sizing audit: Fixed 7 files with variant/display size mismatches
_champion_card.html.erb: 64×64 → 56×56 (matches w-14)
directory/show.html.erb: 128×128 → 112×112 (matches sm:w-28)
events/show.html.erb: 800×500 → 800×450 (matches aspect-video 16:9)
_step_photo.html.erb: 200×200 → 160×160 (matches w-40)
_discussion_card_compact.html.erb: Square → rectangular variants
_event_card_compact.html.erb: Square → rectangular variants
_news_card_compact.html.erb: Square → rectangular variants
- Hero sections: Reduced heights to acceptable levels
- Message bubbles: Capped width at reasonable limit
- Long text truncation: Fixed awkward line breaks
Documentation
- DESIGN-GUIDELINES.md: Added §10 “Image Variant Sizing (Critical for Sharp Images)” with size reference table
- AGENTS.md: Added variant sizing principle with Tailwind reference sizes
Added - Phase 3: Discussion Boards (Complete)
Phase 3.6: National Alumni Champions Board
- National community: “Alumni Champions” with
national: true flag for special behavior
- Auto-join membership: All verified Champions auto-join on verification, cannot leave
- Notification muting: Per-community notification muting in Settings → Notifications
- Community visibility: Hidden from non-verified Champions, visible in nav for verified
- Dashboard integration: Popular national board posts surfaced on dashboard
- Content defaults: News/events without community assignment default to National Board
- Staff pinning: Community Leaders can pin posts to national board
Phase 3.7: Discussion Notifications
- Notification triggers: Replies to posts, replies to comments, reactions added, community posts (for CL)
- In-app notifications: Discussion activity in dropdown with unread badge
- Email digests: Daily/weekly digest emails (configurable in Settings → Notifications)
- Notification types:
discussion_reply, discussion_reaction, community_post
- User preferences: Email frequency (immediate, daily, weekly, off) per channel
- Blocked user filtering: Notifications from blocked users excluded from counts/display
- Digest background job: Heroku Scheduler integration for automated digest generation
Phase 3.8: Admin Discussion Analytics
- Admin dashboard:
/insights/discussion_boards with comprehensive metrics
- Key metrics: Total posts, comments, active discussers, pending flags
- Trends: Posts/comments per week with sparkline charts
- Community breakdown: Top 5 communities by activity (posts + comments)
- Moderation stats: Flags by status (pending/resolved/dismissed) and action counts
- Period selector: This Week, This Month, This Quarter, All Time views
- Quick actions: Pending flags count links to moderation queue
- Service layer:
EngagementStats::DiscussionBoardsService for metrics calculation (45+ tests)
Added - Phase 3.10 & 3.11: Index Page Reorganization & Dashboard Redesign
Index Page Redesign (3.10)
- News Index: Google News-style layout with featured + compact cards
- Events Index: Upcoming events with Google News layout, Past Events page with Load More
- Discussions Index: Cross-community “All Discussions” page (
/discussions route)
- Compact card partials:
_news_card_compact, _event_card_compact, _discussion_card_compact
- Event row partial:
_event_row with date badge styling
Dashboard Redesign (3.11)
- “From Your Communities” section: Google News layouts for Discussions and News (1 featured + 3 compact)
- Events section: Date badge rows with community pills (using
_event_row)
- Communities card: Combined with CL indicator (⭐), Leadership Dashboard button for CLs
- Community show page: Restructured to match dashboard (Google News layouts), width aligned to
max-w-7xl
- Community index (“Community Select”): Complete redesign with My Communities grid, warm welcome box, You’re Invited section
Layout & Styling
- Background opacity: Increased to 0.8 for campus image visibility
- Footer visibility: Fixed with gradient overlay adjustments
- Consistent widths: All community pages now use
max-w-7xl
Tests
- Updated tests for new page structure (h1/h2/h3 selectors)
- New
Cp::DiscussionsController tests
- Full suite: 2267 tests, 0 failures
Added - Phase 3.9: Public Landing Pages
Public Access for Link Sharing
- Public event pages: Global events and events in public communities viewable without login
- Public discussion pages: Posts in public communities viewable without login
- Public community landing: Non-members see “What’s Happening Here” and “Why Join” sections
- Activity metrics: Public community view shows member count, posts, events (no personal details)
Conversion Features
- Reusable CTA component:
_public_cta_box.html.erb with content-type-aware copy
- Events: “🎉 Want to RSVP?”
- Discussions: “💬 Join the conversation!”
- Communities: “🏠 Find your Belmont home in {City}”
- Return path storage: Visitors redirected to original content after signup/login
- Consistent CTA width: All CTAs render at
max-w-4xl (896px) regardless of page container
Helpers & Models
- MetaTagsHelper: Dynamic Open Graph and Twitter Card meta tags for rich link previews
public_display_name: Champion model method returns “FirstName L.” format for privacy
UI Changes
- Help page moved to footer: Removed from main nav, now publicly accessible
- Event CTA icon: Changed from calendar to party popper emoji
- Public view conditionals: Hide RSVP buttons, comments, reactions for non-members
Tests
- 89 controller tests for public access (events, boards, communities, help)
- Full suite: 2263 tests, 0 failures
Added - Phase 3.4 & 3.5: User Safety & Moderation
User Safety Controls (3.4)
- Flag content: Report posts/comments with reason (spam, inappropriate, harassment, off-topic, other)
- Hide content: Hide specific posts/comments from personal view
- Block users: Hide all content from blocked users, prevent messaging
- Blocked notification filter: Notifications from blocked users excluded from count and dropdown
CLC Moderation (3.5)
- Moderation actions: Hide/unhide content, lock/unlock posts, pin/unpin posts
- Escalate to staff: Flag content for Engagement Team review
- Moderation queue: Shows only pending flags (resolved/dismissed excluded)
- Moderation counts synced: Leadership index, community dashboard, and queue all use consistent counting
Staff Moderation (3.5)
- Discussions admin: New
/champions/discussions interface for staff
- Escalated tab: View content escalated by CLCs (unresolved only)
- All content tab: Browse all posts and comments
- Hidden/Deleted tabs: View moderated content
- Resolve escalations: Mark escalated content as handled
- Action items integration: Flagged + Escalated counts in navbar dropdown
- Sidebar badge: Shows unresolved escalation count
Documentation
- DISCUSSION_MODERATION.md: Comprehensive moderation system documentation
- All models and data structures
- Count calculation logic with code examples
- All 7 UI touchpoints documented
- Testing locations and workflow diagrams
Bug Fixes
- Fixed escalated count to exclude resolved escalations
- Fixed hidden filter to include posts with hidden comments
- Fixed CLC moderation queue to only show pending flags
- Fixed champion notification red dot to exclude blocked user messages
Tests
- All 2243 tests passing (0 failures, 0 errors)
Added - Phase 3.3: Engagement & Reactions
Reactions System
- Emoji reactions: 6 emoji options (👍 🎉 ❤️ 😂 🤔 👏) on posts and comments
- Horizontal bar UI: Messages-style emoji bar with reaction counts
- AJAX toggle: Turbo Streams for real-time reaction updates
- Activity tracking:
reaction_added event for analytics
Popularity Sorting
- Popularity score:
(reactions × 2) + (comments × 3) + (time_factor × 10) with 14-day decay
- Hot Discussions: Max 2 hot posts shown at top of discussion index (excluding pinned)
- Popular Discussions: Sorted by popularity on dashboard, community index, community show
New Files
app/views/cp/boards/_reactions.html.erb — Reusable reaction bar partial
app/views/cp/boards/_post_card.html.erb — Reusable post card with hot badge
app/javascript/controllers/board_reactions_controller.js — Stimulus controller
Tests
- All 2173 tests passing (0 failures, 0 errors)
Posting Features
- New post form: Create discussions with title (100 char max), rich text body (40K), optional cover image
- Post editing: Edit within 30-minute window, “Edited” indicator shown after save
- Post deletion: Soft-delete within 30-minute window with confirmation
- Post truncation: Long posts show “See more” link on listing pages
- Community Guidelines reminder: Shown in post composer
- Threaded replies: Comments with depth up to 2 levels (reply to reply)
- Comment editing: Edit within 30-minute window, “Edited” indicator shown
- Comment deletion: Soft-delete within 30-minute window with confirmation
- Character limit: 2,000 characters per comment
UI/UX Improvements
- Discussions in community cards: Featured discussions section in community index
- Discussion listing: Posts sorted by last activity with pagination
- Author identification: Photo, name, role badge, and relative timestamps
Help & FAQ
- 9 new FAQ entries added to Discussions category covering:
- What are discussion boards?
- Starting new discussions
- Replying to posts and comments
- Editing and deleting content
- Locked and pinned posts
- Emoji reactions
- Content guidelines
Tests
- 20 new controller tests for BoardCommentsController
- Updated model tests for 100-character title limit
- All tests passing (2161 total, 0 failures)
Added - Phase 3.1: Discussion Boards Infrastructure
Database & Models
- 8 new database tables:
cp_board_posts, cp_board_comments, cp_board_reactions, cp_post_flags, cp_user_blocks, cp_hidden_contents, cp_moderation_actions
- 7 new models:
Cp::BoardPost, Cp::BoardComment, Cp::BoardReaction, Cp::PostFlag, Cp::UserBlock, Cp::HiddenContent, Cp::ModerationAction
- ActionText integration: Rich text for posts (40K character limit) and comments (2K limit)
- National communities: Added
national boolean to cp_communities for auto-join behavior
- Community discussion toggle: Added
discussion_board_enabled boolean to cp_communities
Controller & Views
- BoardsController: Read-only
index and show actions for viewing discussions
- Board index view: Paginated post list sorted by last activity
- Post detail view: Threaded comments with depth indicators
- Community show enhancement: Discussions section with recent posts preview (Option C navigation)
Features
- National “Alumni Champions” community: Created via rake task (ID: 47) for cross-community discussions
- Activity tracking: New event types
board_view and post_view for analytics
- Comment threading: Unlimited nesting with MAX_DEPTH = 2 for display
- Emoji reactions: 6 emoji options (❤️ 😂 😮 😢 😠 👍) with toggle functionality
- Content moderation infrastructure: Flag reasons, moderation actions with audit trail
- User safety controls: Block users, hide content (infrastructure ready for 3.2 UI)
Tests
- 112 new tests: 96 model tests + 16 controller tests
- All tests passing (2140 total, 0 failures)
Added - Phase 2.4 Enhancement: CL Assignment Improvements
Engagement Score Visibility in CL Assignment
- CL assignment page (
/champions/community_leaders/new) now shows engagement scores for each champion
- Helps portal admins identify engaged members when selecting new Community Leaders
- Score badge displayed before CL badge using existing color-coded format
- New notification type:
community_leader_assigned
- When a champion is assigned as a Community Leader, they receive:
- In-app notification: “You’re now a Community Leader! 🎉”
- Immediate email: Sent right away (not batched in digest)
- Notification body: Includes welcome message and brief description of CL responsibilities (welcoming new members, fostering belonging)
- URL: Links to the community page
- Added to
Cp::Notification::NOTIFICATION_TYPES and DEFAULT_EMAIL_FREQUENCIES
- New
notify_community_leader_assigned(assignment:) method in Cp::NotificationService
- Test added for new notification type
Phase 2.1: Model Refactor
- ClcAssignment now links to Community instead of Region
- Migration: Added
community_id column to cp_clc_assignments
- Migration: Removed
region_id column (previously unused)
- Added unique constraint on
[cp_champion_id, community_id] to prevent duplicate assignments
- Added
assigned_at (auto-set) and assigned_by_id tracking columns
- Updated model associations and validations
- Updated all test fixtures
Phase 2.2: Admin CL Assignment Interface
- New route:
/champions/community_leaders — List all CL assignments
- Filtering: By community type, specific community, or search by champion name/email
- Sorting: By champion name, community name, or assigned date
- Assign CL: From champion detail page or community detail page
- Remove CL: With confirmation dialog, redirects back to origin page
- Visual indicator: CL count shown in Members column on community index
- Authorization: Requires
portal_admin or admin role
Improved - Contextual CL Assignment (Phase 2.2 Enhancement)
CL Assignment Now Requires Context
- Removed standalone “Assign Community Leader” button from CL index page
- CL assignments must now originate from either:
- Champion Detail → Assign as CL: Shows only communities the champion is already a member of
- Community Detail → Members List: Shows “Assign as CL” link next to each verified member
Multi-Select Capability
- CL assignment form now supports batch assignments via checkboxes
- From Champion context: Select multiple communities to assign champion as CL
- From Community context: Select multiple verified members to assign as CLs
Membership Enforcement
- Champions can only become CLs of communities they belong to
- Assignment form filters to show only eligible communities/members
- Clear messaging when no eligible assignments are available
- Added purple “Community Leader” badge for members who are already CLs
- Added “Assign as CL” link for verified members who aren’t yet CLs
- Links pass context to assignment form for seamless UX
Added - Profile Completion Visibility
Champion Show Page: Profile Completion Checklist
- New sidebar card: Shows which profile fields are complete/incomplete
- Weighted display: Shows field weights (Champion Role 25%, Location 20%, etc.)
- Visual indicators: Green checkmarks for complete, gray circles for incomplete
- Actionable: Incomplete items link to profile edit
Detailed Stats Page: Aggregate Profile Completion
- New section: Collective profile completion statistics for all verified Champions
- Per-field breakdown: Shows completion rate for each weighted field
- Progress bars: Color-coded (green 80%+, yellow 50-79%, red <50%)
- Average score: Overall average profile completion percentage
Bug Fix: Profile Completion Percentage Consistency
- Issue: List view showed different % than detail view for same champion
- Root cause: Two calculation methods existed (helper vs model)
- Fix: Consolidated on model method
Cp::Champion#profile_completion_percentage
- Weights: Champion Role (25%), Location (20%), Work (20%), Affinities (15%), Photo (10%), Bio (10%)
Improved - Test Infrastructure
New bin/test Wrapper Script
- Stale process cleanup: Kills test processes running >5 minutes before starting new tests
- Database cleanup: Terminates idle PostgreSQL connections >5 minutes old
- Controlled parallelism: Uses 4 workers by default (was 8 with
:number_of_processors)
- Environment variables:
PARALLEL_WORKERS, TEST_TIMEOUT, SKIP_CLEANUP, MAX_STALE_MINUTES
Test Timeout Protection
- 30-second per-test timeout: Prevents indefinite hanging
- Configurable: Set
TEST_TIMEOUT=60 for slower tests
- Clear failure message: Tells you to optimize or increase timeout
Database Pool Sizing
- Test pool now dynamic:
PARALLEL_WORKERS + 2 connections
- Default: 6 connections for 4 workers (prevents connection exhaustion)
Technical Details
- Rewrote
community_leaders_controller.rb new/create actions for multi-select
- Updated
communities/show.html.erb member list with CL badges and assign links
- Added
@existing_cl_champion_ids to communities controller show action
- Updated
community_leaders/new.html.erb with dual-form structure (champion or community context)
- Removed “Assign Community Leader” button from
community_leaders/index.html.erb
- Added
calculate_profile_completion_stats to Stats controller
- Updated
champions_helper.rb to delegate to model method
- Updated
test_helper.rb with timeout and parallel worker configuration
- Created
bin/test wrapper script with cleanup logic
- 21 controller tests, full test suite: 1962 tests, 0 failures, 0 errors
[1.0.23] - 2026-01-13
Fixed - OAuth “Remember Me” Layout Issue
Layout Bug in oauth_remember_choice View
- Issue: The “Remember Me for 30 days?” dialog was rendering with the Alumni Lookup layout instead of Champion Portal layout
- Symptom: JavaScript error “Missing target element ‘iconOpen’ for ‘navbar’ controller”
- Cause:
OmniauthCallbacksController didn’t specify a layout, defaulting to application.html.erb (Alumni Lookup)
- Fix: Added
layout 'champions' to OmniauthCallbacksController
- Result: Dialog now renders with correct Champion Portal styling and no JS errors
Files Changed
app/controllers/cp/omniauth_callbacks_controller.rb: Added layout declaration
[1.0.22] - 2026-01-13
Added - Post-OAuth “Remember Me” Dialog
Session Timeout Improvements
- Increased default session timeout from 30 minutes to 2 hours of inactivity
- Applies to all authentication methods (email/password and OAuth)
- Added “Remember Me” flow specifically for OAuth logins
Google OAuth “Stay Logged In?” Dialog
- After successful Google OAuth, Champions now see a friendly dialog asking if they want to “Remember Me for 30 days”
- Two options:
- “Yes, Remember Me” → Sets 30-day persistent session (via
remember_me cookie)
- “Not Now” → Session expires after 2 hours of inactivity
- Solves UX issue where “Remember Me” checkbox was hard to see on OAuth flow
- Includes security note: “Only use Remember Me on private devices”
Technical Implementation
- Modified
Cp::OmniauthCallbacksController:
handle_oauth() now stores champion ID in session and redirects to remember choice
- New
oauth_remember_choice() action shows the dialog (GET /oauth/remember-choice)
- New
oauth_confirm_signin() action completes signin with remember_me flag (POST /oauth/confirm-signin)
- Uses session variables to carry authentication state between OAuth and signin
- Added routes for the new remember choice workflow
- New view:
app/views/cp/omniauth_callbacks/oauth_remember_choice.html.erb
- Warm, welcoming design with clear button options
- Explanation of each choice’s implications
Files Modified:
app/controllers/cp/omniauth_callbacks_controller.rb
config/routes.rb
config/initializers/devise.rb (timeout increase)
- New:
app/views/cp/omniauth_callbacks/oauth_remember_choice.html.erb
Addresses: OAuth session timeout too short, unclear “Remember Me” UX for OAuth logins
[1.0.21] - 2026-01-12
Fixed - Notification Email URLs
NotificationMailer Improvements
- Added
default_url_options method that reads CHAMPION_PORTAL_HOST from environment
- Added
build_full_url helper to convert relative paths (stored in DB) to full URLs
- Fixes broken email links like
http://dashboard/ → https://beta.alumnichampions.com/dashboard
- Fixes community links like
http://communities/... → https://beta.alumnichampions.com/communities/...
Duplicate Email Prevention
- Added
TYPES_WITH_DEDICATED_MAILERS constant to NotificationService
- Skip immediate NotificationMailer emails for
join_request_approved and join_request_denied
- These types already have dedicated templates in CommunityMailer with better formatting
- Prevents duplicate emails when join requests are approved/denied
Files Modified:
app/mailers/cp/notification_mailer.rb
app/services/cp/notification_service.rb
[1.0.20] - 2026-01-10
Changed - Domain Migration (Jan 2026)
Domain Structure Migration
- Lookup Portal:
*.bualum.co → alumnilookup.com (dev/beta/prod: dev.alumnilookup.com, beta.alumnilookup.com, alumnilookup.com)
- Champion Portal:
*.bualum.co → alumnichampions.com (dev/beta/prod: dev.alumnichampions.com, beta.alumnichampions.com, alumnichampions.com)
- Email Domain:
email.bualum.co → mail.alumnichampions.com
Files Updated
- Routing constraint in
config/routes.rb — Now matches alumnichampions in host, champions.* prefix, or champions subdomain
- Test environment config
config/environments/test.rb — Added host authorization for all test domains
- All configuration files, mailers, helpers, views, and documentation updated with new domain references
- GitHub Actions workflows updated for staging/production health checks
Migration Documentation
- Created
docs/operations/DOMAIN_MIGRATION.md with complete migration checklist
- External services requiring updates: DNS, Heroku domains, Google OAuth, Mailgun
Added - Phase 1.16 Notifications System (Jan 2026)
Database & Models
cp_notifications table with aggregation support (count, last_aggregated_at)
cp_notification_preferences table for per-type settings (in_app_enabled, email_frequency)
Cp::Notification model with scopes (unread, for_champion, recent_first)
Cp::NotificationPreference model with DEFAULT_PREFERENCES for 9 notification types
- Champion model
notification_preference_for(type) method
Notification Types
new_message — New messages in threads (aggregates per thread)
new_member — New members in your communities (aggregates per community)
new_event — New events in your district
community_suggestion — Suggested communities to join
join_request_received — Someone requested to join your community (CLC only)
join_request_approved — Your join request was approved (immediate email)
join_request_denied — Your join request was denied (immediate email)
verification_pending — Account verification in progress (immediate email)
verification_approved — Account verified (immediate email)
Services & Jobs
Cp::NotificationService with smart aggregation for high-volume types
Cp::NotificationJob for async notification creation
Cp::NotificationDigestJob for daily (7am) and weekly (Monday 7am) email digests
Cp::NotificationCleanupJob for removing read notifications older than 30 days
Email System
Cp::NotificationMailer with immediate_notification, daily_digest, weekly_digest
- HTML and text email templates for all digest types
- Immediate emails for critical notifications (join requests, verification)
Navbar UI
- Bell icon in header with unread dot indicator
- Dropdown panel showing recent notifications (10 max)
- Mark as read, mark all read, clear all actions
notifications_controller.js Stimulus controller
Cp::NotificationsHelper for icon/styling helpers
Settings Integration
- Per-type notification preferences in Settings → Notifications
- In-app toggle and email frequency dropdown per type
- 4 groups: Messages, Community, Join Requests, Account
Integration Updates
MessagingService creates new_message notifications
MessageNotificationJob updated to use new preferences model
- Updated settings controller and views for new preference system
Tests
- 1931 tests, 0 failures
- New tests for all notification components
Added - Phase 1.15.4 Message Link Previews & Sharing (Jan 2026)
Cp::MessagesHelper with link detection and extraction methods
contains_community_links? detects community URLs in message text
extract_community_slugs extracts community slugs from URLs
strip_community_urls removes URLs for clean display
- Supports production, dev, and path-only URLs
_community_card.html.erb partial renders preview cards
- Cards show community name, type badge, member count, and thumbnail
- Automatic rendering for messages containing community links
- Visual indication of linked communities without cluttering message text
- “Invite a Champion” button on community pages
- “Copy Link” button with clipboard integration
clipboard_controller.js Stimulus controller for copy functionality
- Share flow pre-fills message composer with community link
community_share_message helper generates friendly share text
invited_community_id and invited_by_id columns on cp_champions
- Dashboard shows prompt when champion was invited to a community
- “Check out [Community]” card with inviter attribution
- Clears after champion visits the community
Tests
- 20 new helper tests in
Cp::MessagesHelperTest
- All 1841 tests passing
Added - Phase 1.15.3 Expanded Membership Management (Jan 2026)
Terminology Update
- Replaced “CLC” with “Community Leader Council” throughout documentation and UI
Request to Join Infrastructure
- New
cp_join_requests table for tracking join requests to private/custom communities
Cp::JoinRequest model with pending/approved/denied status enum
approve!(admin, note:) creates membership and sends approval email
deny!(admin, note:) requires explanation and sends denial email
- Validation ensures admin_note required when denying requests
Membership Tracking
- Added
source column to cp_champion_communities: self_joined, approved, auto_matched, admin_added, invitation
- Added
added_by_id foreign key to track who added members
- Added soft-delete columns:
removed_at, removed_by_id, removal_reason
- Added
visibility enum to communities (visibility_public, visibility_private)
- Added
invite_token for private community invite links
Champion Portal
- “Request to Join” button for private/custom communities
- Pending request status display
- Cancel request functionality
Admin Portal
- Members tab: Full member list with add/remove functionality
- Join Requests tab: Queue with approve/deny actions
- Add member modal with champion autocomplete
- Deny request modal with required explanation field
Mailers
Cp::CommunityMailer#join_request_submitted — Notifies Engagement Team
Cp::CommunityMailer#join_request_approved — Notifies champion
Cp::CommunityMailer#join_request_denied — Notifies champion with explanation
Tests
- 19 new JoinRequest model tests
- All 1779 tests passing
Added - Post-1.14 Dashboard & UI Enhancements (Jan 9, 2026)
Dashboard Restructure
- Sectioned feed with News, Events, and Discussions tabs
- Event cards show time with timezone, city/state in location, short descriptions
- Event thumbnails moved to right side with larger sizing
- Dashboard helper
extract_city_state for parsing addresses
- Dashboard helper
formatted_event_date_time using event’s formatted_time method
Site-Wide Background Image
- Added
/public/bg-campus.jpg background image to Champion Portal layout
- Fixed position with z-index layering (z-0 background, z-10 content)
- 8% opacity, grayscale filter with gradient fade overlay
- Applies to all Champion Portal pages
Event Address Field Split
- New database columns:
venue_street, venue_city, venue_state, venue_zip
- New
Cp::Event methods: city_state, city_state_zip, full_address
- Updated admin event form with separate address fields
- Updated event show page to use
full_address for display and Google Maps links
- Fixed “Your Community” generic text in news/events views
- Now shows actual community names when
communities.any?
- Shows “All Champions” badge when
globally_visible? is true
- Replaced text “Alumni Champions” with
/public/logo-horizontal-reversed.svg logo
- Updated Alumni Champion icon SVG
Bug Fixes
- Fixed
/directory?district=xxx parameter mismatch (now uses district_id)
- Automatic detection when 3+ verified Champions share an attribute
- Type-specific lookup using
district_id, college_code, major_code, affinity_code, industry
- Automatic community creation with proper naming and slug generation
- Automatic member assignment for all qualifying Champions
- Notification generation for all members of new communities
- Background job triggered after verification and profile edits
- Filters to specified community types for targeted detection
- Only processes verified Champions (
status_champion_verified?)
cp_community_notifications table with Champion and Community references
notification_type enum (currently threshold_reached)
- Read/unread tracking with timestamps
- Scopes:
unread, recent, for_dashboard
- Class methods:
create_threshold_notification!, notify_community_members!
Dashboard Integration (1.14.4)
- Notification banner when new communities are created
- Shows community name with link to landing page
- Dismiss and dismiss-all functionality via Turbo
- Activity tracking on dismiss (
notification_dismissed event)
Hybrid Triggers (1.14.5)
ChampionVerificationService → triggers college/major detection
ProfileWizardController → triggers on location, profession, affinities steps
ProfileController → triggers on location, profession, affinities section edits
Testing
- 55 new tests for Phase 1.14 features
- Full test suite: 1716 tests, 0 failures, 0 errors
Added - Phase 1.13 Community Landing Pages (Jan 8, 2026)
- Community landing pages at
/communities/:slug
- Members grid with Turbo Stream pagination (12 per page)
- News section showing community-scoped posts
- Events section showing community-scoped events
- Activity tracking (
community_view event)
Communities Index (1.13.2)
- Main communities page at
/communities
- “My Communities” flat list (not grouped by type)
- “Suggested for You” section with suggestions
- “Explore All Communities” link to browse all
- Explore view grouped by community type
- Replaced “Community Snapshot” with “My Communities” widget
- Shows champion’s communities with quick links
- Suggestion teaser when no communities joined
- Empty state with “Explore Communities” CTA
Navigation Updates (1.13.4)
-
| New nav order: Communities |
Directory |
Messages |
[Name] |
- Removed Dashboard from nav (logo links to Dashboard)
- Removed Events from main nav (accessed via communities)
- Updated desktop header and mobile bottom nav
Testing (1.13.5)
- Full test suite: 1661 tests, 0 failures, 0 errors
- Community controller: 21 tests
- Dashboard controller: 8 tests
Database & Models (1.12.1-1.12.4)
- cp_communities table: 6 community types (district, college, major, affinity, industry, custom)
- cp_champion_communities join table: With counter cache for member counts
- cp_community_suggestions table: Suggestion-based community discovery model
- Cp::Community model: Type-specific validations, scopes, factory methods
- Cp::ChampionCommunity model: Counter cache pattern for member counts
- Cp::CommunitySuggestion model: accept!/decline! methods
- Cp::CommunityMatchingService: Profile-based community suggestion generation
Profile Hooks (1.12.4)
- Champion profile updates trigger community matching
- Affinity changes trigger community matching
- Community ID added to posts and events for targeting
Admin Communities UI (1.12.6)
- Lookup Portal admin index at
/champion_admin/communities
- Community show page with members list and statistics
- Edit page for custom names, descriptions, thresholds, featured status
- Champion-facing “My Communities” page at
/communities
- Community show page with member list (members only)
- Join and leave community actions
- Accept/decline community suggestions
- Full test coverage: 29 tests, 74 assertions
Added - Phase 1.11 Admin Events (Jan 7-8, 2026)
Events System
- Database:
cp_events table with full event support
- Title, slug, dates (starts_at, ends_at), timezone
- Location (venue_name, venue_address, is_virtual, virtual_url)
- External RSVP link with customizable button text
- Status workflow (draft → published → archived)
- View counting and RSVP click tracking
- District Targeting:
cp_event_districts join table (mirrors News pattern)
- Events can be global (all Champions) or district-targeted
- Consistent targeting model for future Phase 1.12 unification
Admin Interface (Lookup Portal)
- Champions::EventsController: Full CRUD for event management
- Index View: Filterable by status (all/draft/published/archived) and scope (global/targeted)
- Form: Rich text description, cover image upload, district autocomplete
- Stimulus Controllers: event_form, event_scope, event_districts for interactive form
Champion Portal
- Cp::EventsController: Index, show, rsvp_click actions
- Index View: Upcoming/past tabs, district filtering, responsive card layout
- Show View: Cover image with date badge, location info, RSVP tracking
- Activity Tracking:
event_viewed and event_rsvp_clicked event types
- Navigation: Events link added to header (desktop + mobile) and Lookup Portal sidebar
Tests
- 37 model tests for
Cp::Event
- 4 model tests for
Cp::EventDistrict
- 19 controller tests for
Cp::EventsController
- 7 event fixtures, 2 event_district fixtures
Files Created:
db/migrate/*_create_cp_events.rb
db/migrate/*_create_cp_event_districts.rb
app/models/cp/event.rb
app/models/cp/event_district.rb
app/controllers/champions/events_controller.rb
app/views/champions/events/*
app/controllers/cp/events_controller.rb
app/views/cp/events/*
app/javascript/controllers/event_*.js (3 controllers)
test/models/cp/event_test.rb
test/models/cp/event_district_test.rb
test/controllers/cp/events_controller_test.rb
test/fixtures/cp/events.yml
test/fixtures/cp/event_districts.yml
Fixed - Email Deliverability & Admin Notifications (Jan 6, 2026)
Email Domain Configuration
- From Addresses: Changed all mailers to use
@mail.alumnichampions.com domain
Cp::ChampionMailer: champions@mail.alumnichampions.com
MessageMailer: champions@mail.alumnichampions.com
ApplicationMailer: noreply@mail.alumnichampions.com
- Uses existing Mailgun configuration already set up in Squarespace DNS
- Avoids CNAME conflict with
alumnichampions.com (reserved for Heroku app)
- Environment Variable: Set
MAILGUN_DOMAIN=mail.alumnichampions.com in Heroku
Admin Notifications Fix
- AdminNotificationMailer: Fixed
admin_recipients query to properly find portal_admin and admin users
- Changed from incorrect symbol array
%i[portal_admin admin] to string array ['portal_admin', 'admin']
- Now correctly matches User model’s enum role column
- Ensures admins receive notifications for all new Champion signups (auto-verified or pending)
Added - Phase 1.10.3 News Display Enhancements (Jan 5, 2026)
Image Carousel
- Stimulus Controller:
image_carousel_controller.js for multi-image posts
- Previous/next arrow navigation
- Dot indicators to jump to specific images
- Image counter (e.g., “1 / 3”)
- Touch/swipe support for mobile
- Keyboard navigation (arrow keys)
- Show Page: Full carousel for posts with multiple images
- Single image posts display without carousel UI
- “Ghost image” technique for height stability
- Post Cards: Image count badge when multiple images exist
- Image Upload: Fixed drag-drop/paste/file-picker with multipart form
- District Autocomplete: Replaced checkboxes with searchable autocomplete
- Pin Icon: Fixed pin icon (was showing scales icon)
- Card Aspect Ratio: Changed from 16:9 to 4:3 for better image display
Files Created:
app/javascript/controllers/image_carousel_controller.js
Files Modified:
app/views/cp/news/show.html.erb — Added carousel for multiple images
app/views/cp/news/_post_card.html.erb — Image count badge, aspect ratio, pin icon
app/views/champions/news_posts/_form.html.erb — District autocomplete, multipart form
app/javascript/controllers/news_post_images_controller.js — Drag-drop/paste support
Added - Phase 1.10.3 Champion Portal News Display (Jan 5, 2026)
Champion-Facing News System
- Controller:
Cp::NewsController with index and show actions
- Views: News index (
/news) and show pages (/news/:slug)
- Post card partial for reuse on dashboard and index
- Hero image display from
hero_image method
- Full content display via ActionText
- External link button (opens new tab)
- Pinned and regional scope badges
- Empty state when no posts available
- Paginated index (12 per page)
- Dashboard Integration: Real
Cp::NewsPost records replace sample data
load_news_posts method using visible_to(champion) scope
- “View all →” link to news index
- Analytics: View tracking with
news_viewed activity event
- Session-based deduplication prevents count inflation
- Increments
views_count counter cache
- Tests: 15 controller tests (1454 total project tests pass)
Files: app/controllers/cp/news_controller.rb, views in app/views/cp/news/
Added - Phase 1.10.2 Admin UI for News Posts (Jan 5, 2026)
Lookup Portal Admin Interface
- Controller:
Settings::NewsPostsController with full CRUD + publish/unpublish
- Views: Index, new, edit pages with comprehensive form
- Stats cards (total, published, drafts, archived)
- Filters by status and scope
- Search by title
- ActionText/Trix editors for excerpt and full content
- Region multi-select for regional posts
- Publish/unpublish actions with confirmation dialogs
- Navigation: “Champion Portal” section in Settings sidebar
- Authorization:
portal_admin and admin access via ensure_portal_admin!
- Tests: 19 controller tests (1439 total project tests pass)
Key fixes during implementation:
- Added
inverse_of to news_post_regions association for proper nested builds
- Controller finds by slug (model overrides
to_param)
- Form URL explicitly set to handle namespaced model class
Files: app/controllers/settings/news_posts_controller.rb, views in app/views/settings/news_posts/
Added - Phase 1.10.1 Database & Models (Jan 5, 2026)
News Post System Database Layer
Deferred (schema-ready, UI deferred): Image carousel, college/major/affinity tags, featured champions
Added - Phase 1.10 Planning Document (Jan 4, 2026)
Complete planning specification for admin-managed news/posts system with dashboard integration.
- Created:
docs/planning/champion-portal/phases/phase-1/1.10-community-news.md
- Full content model: posts, images, regional targeting, college/major/affinity tags
- Admin architecture: Staff in Lookup Portal (global), CLCs in Champion Portal (regional)
- Champion features: likes, view tracking, featured champions tagging
- Dashboard integration with 3-card preview and regional badge
- 6 implementation sub-phases (1.10.1-1.10.6)
- Friendly URLs:
/news/:id/:slug
- “Your Community” hub vision for future regional content consolidation
Deferred to future phases (schema ready):
- Major/Affinity tag filtering UI
- Scheduled post publishing
- RSS feed for Belmont Stories
- Instagram API integration
Cross-references updated:
app/controllers/champions/roadmap_controller.rb — Added 1.10 sub-phase
docs/planning/champion-portal/phases/phase-1/README.md — Added 1.10 to table
docs/planning/champion-portal/BACKLOG.md — Added 1.10 deferrals
docs/planning/champion-portal/phases/phase-1/1.9-pre-beta-polish.md — Updated Phase 3.3 refs to 1.10
docs/planning/champion-portal/development/DESIGN-GUIDELINES.md — Updated Phase 3.3 ref to 1.10
Added - Phase 1.9.5: Language & Messaging Audit (Jan 2026)
Language Style Guide
Comprehensive voice and tone guide for Champion Portal copy based on the Alumni Champions Verbal Style Guide.
- Created:
docs/planning/champion-portal/development/LANGUAGE_STYLE_GUIDE.md
- Thesis statement and tone pillars (invitational, illustrative, empowering, invested, informative, inspiring)
- Messaging pillars (belonging, action, accessibility, celebration)
- Key terms definitions (Champion, Bruin, Alumni Champions, Champion Portal)
- Context-specific guidelines for UI elements, empty states, notifications
- Words and phrases to avoid
Language Fixes
- Mailer Templates: Fixed “prospective students” in 4 templates
new_message_notification.html.erb → “Connect with fellow Bruins and grow our Champion community”
new_message_notification.text.erb → Same update
champion_mailer/invite.html.erb → “Share stories and celebrate wins”
champion_mailer/invite.text.erb → Same update
- Directory Empty State: “No champions found” → “No Champions match your search” with encouraging CTA
- Settings Flash Messages: Warmed up password and notification preference confirmations
Fixed
- Profile wizard completion now redirects to profile view instead of edit page
Files Created:
docs/planning/champion-portal/development/LANGUAGE_STYLE_GUIDE.md
Files Modified:
app/views/message_mailer/new_message_notification.html.erb
app/views/message_mailer/new_message_notification.text.erb
app/views/cp/champion_mailer/invite.html.erb
app/views/cp/champion_mailer/invite.text.erb
app/views/cp/directory/index.html.erb
app/controllers/cp/settings_controller.rb
app/controllers/cp/profile_wizard_controller.rb
Added - Phase 1.9.3: Profile Visual Refresh (Jan 1, 2026)
Directory Card Refresh
Simplified, scannable directory cards focused on connection.
- Card Design:
- Belmont Blue accent bar on left edge
- Large centered photo with role badge overlay
- Prominent name and role display
- Education with college SVG icons:
[icon] degree_code Major (YYYY)
- Truncated major text with tooltip, year always visible
- Mobile-first responsive layout
- Education Display:
- College icons from
DirectoryHelper#college_icon_svg
- SVG stroke-style icons matching site consistency
- Format:
[college icon] degree_code Major (YYYY)
- Proper eager loading:
includes(degrees: { major: :college })
- Profile Updates:
- Contact info (email/phone) moved to Settings page
- Profile header shows champion name, not “My Profile”
- Synced My Profile and Public Profile education format
- Removed industry/role filters from directory
Files Modified:
app/views/cp/directory/_champion_card.html.erb
app/views/cp/directory/index.html.erb
app/views/cp/profile/show.html.erb
app/views/cp/directory/show.html.erb
app/helpers/cp/directory_helper.rb
Help Center & FAQ
Self-service help resources for Champions to find answers without contacting support.
- Help Page:
/help route with comprehensive FAQ
- 6 organized categories:
- Getting Started (verification, Champion role)
- Your Profile (privacy, photo uploads)
- Finding Alumni (directory search, filters)
- Messaging (conversations, notifications)
- Account & Settings (email, password, connections)
- Community Guidelines (rules, reporting)
- Expandable accordion UI (
accordion_controller.js)
- Real-time FAQ search (
faq_search_controller.js)
- “Still need help?” link to alumni@belmont.edu
- All FAQ content in
config/faq.yml
Context-sensitive tooltips guide new Champions through key features.
- Tooltip System:
seen_tooltips JSONB column tracks dismissed tooltips per Champion
- Champion model methods:
tooltip_seen?, mark_tooltip_seen!, pending_tooltips, tooltip_for
Cp::TooltipsController with dismiss endpoint (POST /tooltips/:key/dismiss)
- Activity tracking:
tooltip_dismissed events for analytics
- 29 tests covering tooltip behavior and persistence
- Tooltip Infrastructure:
onboarding_tooltip_controller.js — Stimulus controller for display/dismiss
- Auto-display after 500ms delay
- Position support (top/bottom/left/right)
- Smooth fade-in/out animations
- Action button navigation
- AJAX persistence on dismiss
Cp::TooltipsHelper for easy view integration
- Initial Tooltips:
- Dashboard: “Complete your profile” (on incomplete profiles)
- Directory: “Find Champions in your city” (first search)
- Profile Edit: “Control who sees your contact info” (privacy settings)
- Messages: “Your message was sent!” (messaging intro)
Files Added:
config/faq.yml — FAQ content and tooltip definitions
app/controllers/cp/help_controller.rb
app/views/cp/help/index.html.erb
app/javascript/controllers/accordion_controller.js
app/javascript/controllers/faq_search_controller.js
app/javascript/controllers/onboarding_tooltip_controller.js
app/controllers/cp/tooltips_controller.rb
app/helpers/cp/tooltips_helper.rb
db/migrate/20251225172229_add_seen_tooltips_to_cp_champions.rb
test/controllers/cp/help_controller_test.rb
test/models/cp/champion_tooltip_test.rb
test/controllers/cp/tooltips_controller_test.rb
[1.0.18] - 2025-12-23
Added - Phase 1.8: Admin Dashboard & Engagement Scoring (Dec 23, 2025)
Champions Admin Dashboard
Complete admin interface for managing the Champion program with visibility into Champion activity and engagement.
- Champion Management:
/champions — Staff dashboard listing all Champions
/champions/:id — Detailed Champion profile view with complete activity timeline
- Search Champions by name or email
- Filter by verification status, primary role, or district
- Sort by signup date or last name
- Searchable/filterable Champions list with pagination (25 per page)
- Admin Features:
- Impersonation: “Log in as [Champion]” for testing/support (secure token-based)
- Environment banners (dev/staging/impersonation) for context
- Domain-specific routing (admin portal ↔ champion portal)
- One-click link to verification queue for unverified Champions
Engagement Scoring System
New activity-based scoring to measure Champion engagement and identify active community members.
- Scoring Service:
Cp::EngagementScoreService — Reusable service for score calculation
- Point values for 8 activity types:
- 5 pts: Logins, Conversations Started, Invites Sent
- 3 pts: Profile Edits, Messages Sent
- 1 pt: Profile Views, Message Reactions
- 0.5 pts: Directory Searches
- Support for date-range filtering (“score since 1 month ago”)
- Activity breakdown by type with counts and points
- Admin Visibility:
- Engagement column in Champions list (color-coded badge)
- Engagement Score card on Champion detail view
- Engagement levels: Highly Engaged (100+), Engaged (50-99), Active (10-49), New/Inactive (<10)
- Human-readable score formatting (e.g., “245.5 pts”, “1.2k pts”)
- Extensibility:
- Simple pattern for adding new activities (3 steps: define event, add points, record activity)
- Comprehensive documentation:
docs/features/champion_portal/ENGAGEMENT_SCORING.md
- Service-based architecture allows future enhancements (trending, percentiles, leaderboards)
- Testing:
- 8 comprehensive service tests covering all scenarios
- Tests for zero scores, date filtering, breakdown calculations
- Point value verification for all activity types
Dashboard Features
- Stats Cards (Top of Dashboard):
- Total Champions (verified)
- Total Engagement (aggregate score across all Champions)
- New Champions this month
- Messages exchanged
- Recent Activity (Timeline):
- Last 10 activities across all Champions
- Activity types: signups, logins, verifications, messages, profile updates
- Timestamps and actor names
Visual/UX Improvements
- Profile completion percentage calculation (10 core fields)
- Progress bar + color-coded completion badges (green/blue/yellow/red)
- Verification status badges for each Champion
- Color-coded engagement badges based on score ranges
- Responsive table design for mobile/tablet viewing
Changed
- Champions namespace routes reorganized for admin access
ensure_portal_admin! guard for dashboard access (staff, portal_admin, admin roles)
- Environment-aware routing helpers for cross-domain links
Documentation
- Created
docs/features/champion_portal/ENGAGEMENT_SCORING.md — Complete guide to engagement system
- Current point values and philosophy
- How to add new activities (3-step pattern)
- Usage examples and testing
- Architecture and caching opportunities
[1.0.17] - 2025-12-22
Added - Phase 1.7: In-App Messaging (Dec 22, 2025)
Messaging System
Complete 1:1 messaging system for Champions to communicate within the portal.
- Database Tables:
cp_message_threads — Conversation containers with message count tracking
cp_message_thread_participants — Per-user thread state (read, muted, archived)
cp_messages — Individual messages with soft delete support
cp_message_reactions — Emoji reactions on messages
- Controllers & Views:
Cp::MessagesController — Full CRUD for messaging (index, show, new, create, reactions)
- Inbox view with unread badges and conversation previews
- Thread view with emoji reactions (❤️ 😂 😮 😢 😠 👍)
- Compose modal accessible from directory profiles
- Services:
Cp::MessagingService — Thread management, message sending, reactions
MessageNotificationJob — Background job for 5-minute delayed email notifications
MessageMailer — Email templates for message notifications
- Features:
- Start conversation from Champion directory profile cards
- Emoji reactions with optimistic UI updates
- Unread message count in header badge
- Auto-mark-as-read when viewing thread
- Mute and archive thread controls
- Smart notifications (email only if unread after 5 minutes)
- Analytics:
- Activity event tracking for
message_thread_started and message_sent
Champion Profile Enhancements
- Timezone Support: Champions can set timezone for message timestamp display
- International Location: Champions outside US can enter country/city text
- Profile Wizard: Added location step with domestic/international toggle
Changed
- Header navigation now includes Messages link with unread badge
- Directory profile cards include “Send Message” button for verified champions
- Activity events now include messaging types
Added
- Champion dashboard now uses a unified community snapshot with verified-only counts.
- District preview cards now show degree/class year with smart fallbacks.
- District preview includes a ZIP prompt, verification messaging, and pre-filtered directory link.
- Own-profile banner on public profile view with an Edit Profile shortcut.
- Champion directory filters now include graduation year multi-select (decade toggles), college, district autocomplete, and affinity typeahead.
- Champion directory now supports sorting by recently joined.
- Affinity API now supports query-based typeahead for directory filtering.
Changed
- Dashboard copy now uses alumni-first language instead of repeating “Champion.”
- Profile view now emphasizes the editable profile with section edit links, and the public view banner links back to the editable profile.
- District-based lists and filters now exclude alumni who hide location details.
- Privacy “district only” visibility now matches on district for email, location, and contact preferences.
- Privacy settings no longer include a phone visibility option.
- Directory “recent” sort now uses
verified_at, and region/city filters have been removed.
- District autocomplete can submit district IDs as well as legacy codes.
Fixed
- Public profile header now hides district/city when location privacy is set to hidden.
- Directory region and district filters now exclude hidden-location alumni and respect district-only visibility.
Fixed
- Champion Role wizard quiz links now navigate to the quiz flow.
- Champion Role wizard step now persists selected roles on continue.
- Confirmation password form now shows inline validation errors for mismatched/invalid passwords.
- Changing a champion ZIP now updates city/state from the ZIP lookup.
- ZIP changes now clear stale district/city/state when no ZIP lookup is found.
Added - Phase 1.5: Admin Verification & District Management (Dec 19, 2025)
Champions Namespace (/champions)
New Portal Admin section for Champion Program management with dedicated layout and navigation.
Champions::BaseController — Base controller with ensure_portal_admin! guard
Champions::ChampionsController — Dashboard with quick stats, verification funnel, regional breakdown
Champions::VerificationsController — Queue for verifying champions by linking to alumni BUID
- Index shows pending champions (with/without suggested matches)
- Show displays champion details + pending alumni match
- Approve: Links pending_buid and sets
champion_verified status
- Reject: Clears pending_buid, keeps champion in queue for manual search
- Search alumni: AJAX search by name, email, or BUID (Turbo Frame)
- Link BUID: Manual verification by selecting alumni record
Champions::StatsController — Placeholder for detailed statistics (Phase 1B/6)
layouts/champion_admin.html.erb — Dedicated layout with sidebar navigation
champions/_sidebar.html.erb — Navigation with pending count badge
Routes Added:
GET /champions → Dashboard
GET /champions/verifications → Verification queue
GET /champions/verifications/:id → Review champion
POST /champions/verifications/:id/approve → Approve pending match
POST /champions/verifications/:id/reject → Reject pending match
GET /champions/verifications/:id/search_alumni → AJAX search
POST /champions/verifications/:id/link_buid → Manual BUID link
GET /champions/stats → Statistics page
District Management (Settings > Reference Data)
Admin-only interface for managing geographic districts.
Settings::DistrictsController — Index, edit, update, toggle_highlighted, load_more
- Features:
- View all 820 districts grouped by region (default) or flat list
- Filter by region, highlighted status, search by name
- Sort by name, alumni count, or champions count
- Edit district display name (preserves original csv_name for imports)
- Toggle highlighted status (star icon)
- Pagination with “Load more” button using Turbo Stream
- Real-time alumni/champions counts per district
- Performance: Single optimized SQL query for alumni counts instead of N+1
Routes Added:
GET /settings/districts → District list
GET /settings/districts/load_more → Paginated load (Turbo Stream)
GET /settings/districts/:id/edit → Edit form
PATCH /settings/districts/:id → Update district
PATCH /settings/districts/:id/toggle_highlighted → Toggle highlight
Files Created:
app/controllers/champions/base_controller.rb
app/controllers/champions/champions_controller.rb
app/controllers/champions/verifications_controller.rb
app/controllers/champions/stats_controller.rb
app/controllers/settings/districts_controller.rb
app/views/champions/_sidebar.html.erb
app/views/champions/champions/index.html.erb
app/views/champions/verifications/index.html.erb
app/views/champions/verifications/show.html.erb
app/views/champions/verifications/search_alumni.html.erb
app/views/champions/verifications/_search_results.html.erb
app/views/champions/stats/index.html.erb
app/views/layouts/champion_admin.html.erb
app/views/settings/districts/index.html.erb
app/views/settings/districts/edit.html.erb
app/views/settings/districts/_district_rows.html.erb
app/views/settings/districts/_empty_state.html.erb
app/views/settings/districts/load_more.turbo_stream.erb
app/javascript/controllers/champion_admin_sidebar_controller.js
test/controllers/champions/verifications_controller_test.rb (18 tests)
test/controllers/settings/districts_controller_test.rb (23 tests)
Lessons Learned:
- Turbo Stream vs Turbo Frame: For “Load more” functionality, use
data: { turbo_stream: true } on links (not data: { turbo_frame: "..." }) to ensure proper Accept: text/vnd.turbo-stream.html header
- Pagination > Lazy Loading: When sorting depends on counts, pre-calculate all counts and paginate rather than lazy-loading counts per row
- SQL Optimization: Single JOIN query for counts is faster than N+1 queries per district
[1.0.16] - 2025-12-18
Changed - Settings Navigation Order & Batch Search Enhancements (Dec 18, 2025)
Settings Navigation Reorganization
- Swapped “Data Imports” and “Reference Data” sections in Settings
- Data Imports now appears first, aligning with typical workflow (import first, then manage reference data)
- Updated both desktop and sidebar navigation
- Files Changed:
app/views/settings/settings/index.html.erb — Reordered sections
app/views/settings/_sidebar.html.erb — Reordered sections
Batch Search Enhancement Ideas Added to Backlog
- Documented 6 potential enhancements for future phases
- Includes features like advanced filters, saved searches, export functionality
- See
docs/planning/champion-portal/BACKLOG.md for full details
Created /tools namespace for staff-accessible utilities, separating them from admin-only Settings:
Tools::BaseController — Base controller with ensure_staff! authorization
/tools — Landing page with tool cards
/tools/batch_search — Moved from /batch_search
/tools/event_converter — Moved from /settings/event_converter
Why: Staff could access Event Converter in Settings but saw admin-only sidebar items (Settings, Users, Data Imports) which they couldn’t access. Now Tools has its own layout with staff-appropriate navigation.
Profile Layout Fix
- Non-admin users editing their profile (
/profile/edit) now see the regular application layout
- Admins editing users in Settings (
/settings/users/:id/edit) still see the settings layout
- Implemented via
determine_layout method in Settings::UsersController
Navbar Update
- Consolidated “Search” section into “Tools” dropdown
- Tools dropdown now contains: Batch Search, Event Converter
Files Changed
app/controllers/tools/base_controller.rb — NEW
app/controllers/tools/tools_controller.rb — NEW
app/controllers/tools/batch_search_controller.rb — Moved from batch_search_controller.rb
app/controllers/tools/event_converter_controller.rb — Moved from settings/event_converter_controller.rb
app/views/layouts/tools.html.erb — NEW (tools layout with sidebar)
app/views/tools/_sidebar.html.erb — NEW
app/views/tools/tools/index.html.erb — NEW (landing page)
app/views/tools/batch_search/* — Moved from batch_search/
app/views/tools/event_converter/* — Moved from settings/event_converter/
app/controllers/settings/users_controller.rb — Added determine_layout method
app/views/layouts/_navbar.html.erb — Updated Tools dropdown
app/views/settings/_sidebar.html.erb — Removed Tools section
app/views/settings/settings/index.html.erb — Added “Go to Tools” link
config/routes.rb — Added tools namespace routes
Route Changes
| Old Route | New Route |
|———–|———–|
| /batch_search | /tools/batch_search |
| /settings/event_converter/new | /tools/event_converter/new |
| N/A | /tools (landing page) |
Changed - Settings UI Modernization & Users Namespace (Dec 17, 2025)
Users Controller Moved to Settings Namespace
- Moved
UsersController to Settings::UsersController
- Users management now at
/settings/users (consistent with other settings pages)
- Updated all path helpers:
users_path → settings_users_path, etc.
- Profile edit still accessible via
/profile/edit
Modern UI Design for Settings Pages
Applied consistent modern design across all Settings pages:
- Rounded-xl cards with
ring-1 ring-gray-900/5 shadows
- Gradient icon backgrounds for visual hierarchy
- Hover states and transitions throughout
- Section headers with colored icon badges
Pages Updated
- Settings Dashboard (
/settings) — Redesigned with sectioned card layout
- Users Index (
/settings/users) — Added avatars, role badges (Admin/Portal Admin/Staff), Google auth indicators
- User Form — Card-based form with sections, modern input styling
- Engagement Activities — Level-grouped breakdown with activity counts
Development Environment
- Added wildcard SSL certificate generation for
*.alumnilookup.com
- Updated
Procfile.dev to use wildcard certificate (fixes SSL errors for both subdomains)
Files Changed
app/controllers/settings/users_controller.rb — NEW (moved from app/controllers/users_controller.rb)
app/views/settings/users/ — Moved from app/views/users/
config/routes.rb — Added resources :users to settings namespace
app/views/settings/settings/index.html.erb — Modern card design
app/views/settings/users/index.html.erb — Modern table with avatars, badges
app/views/settings/users/_form.html.erb — Card-based form design
app/views/settings/engagement_activities/index.html.erb — Level-grouped breakdown
Procfile.dev — Updated SSL certificate paths
Documentation Updated
docs/development/AUTHENTICATION.md — Updated UsersController references
docs/development/PERMISSIONS_MATRIX.md — Updated controller names
docs/development/TESTING_GUIDE.md — Updated path helper examples
docs/features/AUTH_AND_ROLES_SYSTEM.md — Updated file references
Added - Portal Admin Role & Navigation Reorganization (Dec 16, 2025)
New Portal Admin Role
- Added
portal_admin role to User model (between staff and admin)
- Added
portal_admin? helper method for authorization checks
- Added
ensure_portal_admin! guard in ApplicationController
- Role hierarchy:
staff → portal_admin → admin
Role Permissions
| Role | Capabilities |
|——|————-|
| staff | Search, Insights, Tools, Champion Signups (view/edit) |
| portal_admin | Staff + delete/merge signups, Champion Verification (future), Champion Metrics (future) |
| admin | Full access including Settings, User Management, Data Imports |
Reorganized Lookup Portal hamburger menu with clear sections:
- Search: Search Alumni, Batch Search
- Insights: Engagement Stats, Degree Stats, Top Engaged Alumni
- Tools: Event Converter
- Champion Program: Champion Signups, Verification (Coming Soon), Metrics (Coming Soon)
- Admin: Users, Engagement Activities, Alumni Degrees, Affinaquest Import, Colleges, Majors, Affinities
- Account: Your Profile, Sign Out
Controller Changes
ChampionSignupsController: Delete/merge actions now use ensure_portal_admin! (was ensure_admin!)
EngagementStatsController: Cache clearing now available to all staff (was admin-only)
Files Modified
app/models/user.rb — Added portal_admin role and portal_admin? helper
app/controllers/application_controller.rb — Added ensure_portal_admin! guard
app/controllers/champion_signups_controller.rb — Changed to ensure_portal_admin!
app/controllers/engagement_stats_controller.rb — Removed admin restriction on cache clearing
app/views/layouts/_navbar.html.erb — Reorganized menu structure
Documentation Updated
docs/development/PERMISSIONS_MATRIX.md — Added portal_admin role documentation
docs/features/AUTH_AND_ROLES_SYSTEM.md — Added Phase 7: Portal Admin Role
docs/planning/champion-portal/phases/PHASE-1.md — Updated Sub-Phase 1.5 prerequisites
.github/copilot-instructions.md — Added role hierarchy, pause point instructions
[1.0.15] - 2025-12-16
Added - Banner CSV Importer (Dec 16, 2025)
New Banner Import Feature
- New import workflow for Banner JH-0026c (SHRDGMR) and Commencement Regalia CSV files
- Supports preview/commit pattern - review changes before importing
- Creates both Alumni and Degree records in a single import
Column Mappings
- Alumni fields: BUID, First/FirstName, Last/LastName, PrefName
- Degree fields: DegreeCode/Degree1, Major1Code/Major1Deg1, GradDate
- Email fields:
CAMP_Email → email_school
PERS_Email → email_personal
- Phone: Combines
CP_Area_Code + CP_Phone_Number (ignores PR_* fields)
- Location: City, State from various column formats
Grad Date Override Option
- Optional date picker on upload form for Commencement CSVs that lack graduation dates
- Override applies to all rows in the import
Non-Destructive Updates
- Existing alumni: Only fills blank fields, never overwrites existing data
- Duplicate degree detection: Skips degrees that already exist (buid + degree_code + major_code)
Files Added
app/services/csv/banner_importer.rb - Import service with column mapping
app/views/settings/alumni/upload_banner.html.erb - Upload form
app/views/settings/alumni/import_banner_preview.html.erb - Preview page
test/services/csv/banner_importer_test.rb - 24 service tests
test/controllers/settings/alumni_controller_banner_test.rb - 9 controller tests
Routes Added
GET /settings/upload_banner - Upload form
POST /settings/import_banner_preview - Preview import
POST /settings/import_banner_commit - Commit import
Security - Alumni Match Verification Fix (Dec 16, 2025)
Problem Fixed
- Critical Security Issue: Name-based alumni matches were auto-linking BUID and auto-verifying champions, allowing potential identity spoofing
- A bad actor could verify their email, then claim to be a different alumnus by selecting their name match
New Behavior
- Email matches: Auto-link BUID + auto-verify (high confidence, unchanged)
- Name matches: Store as
pending_buid for staff review, do NOT auto-link or auto-verify
- Alumni location data from pending matches still used for ZIP code suggestion (convenience without security risk)
Database Changes
- Added
pending_buid column to cp_champions - stores BUID selected by champion awaiting staff verification
- Added
pending_buid_match_type column - stores how match was found (‘email’ or ‘name’)
- Added index on
pending_buid for finding champions needing verification
Model Changes
- Added
pending_alumni method to Cp::Champion - returns Alumni record for pending_buid
Tests Added
- Email match auto-links BUID and auto-verifies
- Name match stores pending_buid but does not auto-link
- Location step uses pending_alumni for ZIP suggestion
pending_alumni method tests
Added - Profile Completion Weighted Scoring (Dec 16, 2025)
Weighted Profile Completion
- Replaced simple percentage (5 areas × 20%) with weighted scoring system
- Weights reflect importance: Champion Role (25), Location (20), Work (20), Affinities (15), Photo (10), Bio (10)
- 80 points threshold for “complete” status
- New model methods:
profile_completion_percentage - returns 0-100 weighted score
profile_completion_details - returns hash with status per area
incomplete_profile_areas - returns array of missing areas sorted by priority
- Individual checks:
has_champion_role?, has_work_info?, has_affinities?, has_bio?
Smart Redirect Logic
- After wizard completion, redirects based on score:
- 80+ pts → Dashboard with “complete” message
- 60-79 pts → Profile edit with encouraging message
- < 60 pts → Profile edit with progress message
- Sign-in flow uses same three-way logic
Site-Wide Profile Completion Prompt
- New dismissable banner for incomplete profiles (< 80 pts)
- Shows on all pages except profile/wizard pages
- Displays highest-priority missing area with contextual message
- Uses
dismissable_controller.js Stimulus controller
- Dismisses for current day (sessionStorage)
Profile Completion Banner Updates
- Uses weighted scoring to determine what to display
- Shows priority-ordered missing areas
- Updated messaging to match new areas
Added - Champion Portal Name Field UX & Alumni Matching (Dec 15, 2025)
Legal First Name Field
- Added
legal_first_name column to cp_champions table
- Checkbox-driven UX: “My legal first name is different” reveals field
- Field only stored when different from
first_name
normalize_names callback clears legal_first_name if matches first_name
Help Us Find You Wizard Step
- New conditional step shown FIRST when no alumni match found
- Collects
legal_first_name and college_last_name to improve matching
- After submission, re-checks for alumni matches with all name fields
- Redirects to
confirm_education if match now found
Enhanced Alumni Matching Algorithm
find_potential_alumni_matches now uses ALL name fields:
first_name OR legal_first_name matched against alumni first_name/pref_name
last_name OR college_last_name matched against alumni last_name/maiden_name
- Generates OR conditions for all first name × last name combinations
Champion-to-Alumni Sync Service
- New
Cp::SyncChampionToAlumni service for syncing data to Alumni records
- Name sync rules: if
legal_first_name present → alumni.first_name=legal, alumni.pref_name=first
- All changes logged to
CrmDataChange for Affinaquest export
New Files
app/javascript/controllers/name_checkbox_controller.js - Stimulus controller for checkbox toggle
app/views/cp/profile_wizard/_step_help_find_you.html.erb - Help find you step view
app/services/cp/sync_champion_to_alumni.rb - Champion-to-Alumni sync service
docs/features/champion_portal/NAME_FIELDS_AND_EXPORT_MAPPING.md - Field mapping documentation
Bug Fixes
- Fixed
CrmDataChange#cp_champion association missing class_name: "Cp::Champion"
Fixed - Profile Wizard Bug Fixes (Dec 15, 2025)
Education Confirmation Security
- Only show alumni names when email is verified (match_type == :email)
- Name-based matches show degrees only to prevent information disclosure
Location Step Interactive Confirmation
- Added “Is this still your location?” yes/no UI when alumni record exists
- Uses ZipCode lookup as fallback when alumni city/state fields are blank
- New
location_confirm_controller.js Stimulus controller
Affinities Step Fixes
- Fixed missing closing
</div> tags causing broken navigation
- Added missing continue/skip buttons
- Clear search field when affinity is selected
- Fixed JS error:
filterAffinities() now accepts optional event parameter
Progress Indicator Fixes
- Added Champion Role step to progress indicator (was missing from array)
- Added
:sparkles icon case for Champion Role step
Wizard Completion Redirect
- Email confirmation now redirects to wizard instead of dashboard when
wizard_completed_at.blank?
Champion Role Step
- Added
@roles = champion_roles to load_step_data (was returning nil)
Name Normalization
- Added
before_save :normalize_names callback to Champion model
- Syncs
first_name ↔ pref_first_name when one is blank
Misc Fixes
- Removed “Already completed? Continue to dashboard” link from wizard
- Removed name preview box from basic info step
- Moved About Me field to dedicated profile edit tab
Added - Profile Wizard & Edit Profile Refactoring
New Wizard Flow (7 Steps)
- Step 1: Confirm Education — Only shown if alumni match exists
- Displays matched alumni record for confirmation
- Skip button if match is incorrect
- Step 2: Location — New dedicated location step
- Street address, city, state, ZIP code fields
- Removed ZIP from signup form (now part of wizard)
- Step 3: Profession — Renamed from “professional”
- Industry dropdown (20+ options)
- Job title and company fields
- LinkedIn URL field
- Step 4: Photo — Profile photo upload
- Drag-and-drop or click to upload
- File size and type validation
- Step 5: Affinities — Enhanced searchable affinity selector
- Type-to-search filtering
- Category dropdown for browsing by type
- Visual checkmarks for selected affinities
- Selected items display as removable chips
- “Suggest a new affinity” modal with AJAX POST
- Step 6: Privacy — New final step
- Email visibility toggle (Public, Alumni Only, Hidden)
- Location display toggle
- Career info visibility toggle
- Clear explanations for each setting
Champion Portal Auth Flow Updates
- Removed ZIP from signup — Cleaner initial registration
- Updated redirect logic — Uses
needs_profile_wizard? instead of zip_code.blank?
- Wizard completion tracking — New
wizard_completed_at timestamp column
Affinity Suggestions
- New
cp_affinity_suggestions table — Stores champion-submitted suggestions
- Fields: champion_id, suggested_name, category, reviewed, approved_at
- New
Cp::AffinitySuggestion model — Validates and stores suggestions
- New suggest-affinity endpoint — POST
/profile/wizard/suggest-affinity
Edit Profile Updates
- New section structure — Matches wizard steps
- Sections: basic_info, champion_role, location, profession, affinities, photo, privacy
- Section aliases — Backward compatibility for old URLs
location_contact → location
professional → profession
- Updated sidebar — New icons and sections including Privacy
Test Updates
- 914 tests passing (0 failures, 0 errors)
- Updated fixtures with
wizard_completed_at values
- New tests for privacy step and wizard flow
- Updated controller tests for new section names
Added - Champion Portal Phase 1C (Champion Role Selection & Quiz)
- ChampionRoleService — Single source of truth for 4 champion roles
- Connection Advisor (🤝), Digital Ambassador (🙌), Community Builder (👋), Giving Advocate (🙏)
- Metadata includes title, description, detailed activities, colors
narrative_for generates personalized role descriptions
- ChampionQuizService — 7-question role discovery quiz
- Answer scoring algorithm maps responses to role recommendations
calculate_primary_role finds most frequent match
generate_results returns primary role, all roles with percentages, narrative
- Test Coverage — 42 new tests (24 role service + 18 quiz service)
SubPhase 1C.2: Profile Wizard Integration
- Champion Role Selection in Profile Wizard — Integrated role discovery into onboarding
- Added
champion_role step as 2nd step in wizard (after basic_info)
- Role selection page with 4 role cards showing emoji, title, and description
- “Take the Quiz” option to discover role through 7-question assessment
- Manual role selection option for champions who already know their role
- Updated wizard progress indicator to 6-step icon-only design
- Champion Role Quiz Flow — Interactive 7-question assessment
- Full-screen quiz interface with progress indicator (Question X of 7)
- Randomized answer order (letters a-d maintained for scoring)
- Session-based answer storage during quiz
- Previous/Next navigation through quiz questions
- Exit quiz option returns to role selection page
- Quiz Results Page — Personalized role recommendation
- Primary role recommendation with emoji, title, and description
- Personalized narrative explaining role match based on quiz responses
- Complete role breakdown showing all roles with percentages
- “Select This Role” button to confirm and continue wizard
- Options to retake quiz or manually choose different role
- Test Coverage — 7 new controller tests for quiz flow
SubPhase 1C.3: Profile Edit Integration
- Champion Role in Profile Edit — Manage role after signup
- Added
champion_role section to profile edit sidebar
- Current role displayed in colored card with emoji when set
- “Choose a different role” expandable section with role cards
- “Retake the Quiz” link for role rediscovery
- Context-Aware Quiz Navigation — Quiz remembers where you came from
- Session-based referrer tracking (
session[:quiz_referrer])
quiz_return_path helper determines correct return destination
- Exit Quiz returns to Profile Edit when started from Profile Edit
- Exit Quiz returns to Wizard when started from Wizard
- “Choose a different role” on results page respects referrer
- Wizard Completion Logic — Updated profile sequencing
next_incomplete_wizard_step includes champion_role after basic_info
- Dashboard/profile links guide champions through role selection
- Test Coverage — Updated profile controller tests + model tests
UX Improvements
- Simplified Name Fields — Less bureaucratic onboarding flow
- Primary fields: First Name and Last Name (cleaner layout)
- Collapsible “Add additional name details” section for optional fields:
- Legal First Name (if different from preferred name)
- Last Name at Belmont (maiden name for searchability)
- Data Architecture: Clean validation requiring at least one name field
- Custom validation
at_least_one_name_present (pref_first_name OR first_name)
- No data duplication: doesn’t store same value in both fields
display_first_name method falls back from pref_first_name to first_name
- Backward compatible with existing data that only has first_name
- Stimulus controller
toggle-legal-name handles expand/collapse with animated icon
- Redesigned Role Selection UI — Clear state-based display
- When role IS set: Shows current role prominently in colored card
- When role NOT set: Shows quiz CTA prominently with hidden manual selection
- Stimulus controller
toggle-role-selection for expandable role options
- Bio/About Me Field — Added to basic_info step and profile edit
- Optional textarea for champions to introduce themselves
- Helps other champions connect and find common interests
- Profile Photo Management — Enhanced photo editing
- “Edit Photo” link overlays profile photo on hover in profile header
- “Remove Photo” button on edit page (only shown when photo exists)
- Removed photo from sidebar navigation (accessible via header)
Changed
- Wizard Flow — Expanded from 5 to 6 steps with champion_role as step 2
- Progress Indicator — Changed to icon-only circles (removed step text for space)
- Tailwind v4 Compatibility — Used CSS variable syntax for custom colors throughout
- Name Validation — Changed from
validates :first_name to validates :pref_first_name
- Quiz Display — Questions render with HTML entities, answers in randomized order without letter labels
- “Continue as [Role]” button to select role and advance wizard
- “Retake Quiz” and “Choose a different role” secondary actions
- “What’s Next?” informational section
Changed - Champion Portal SubPhase 1C.2
- ProfileWizardController — Updated for 6-step flow
- STEPS constant now includes
champion_role (6 steps instead of 5)
- Added 4 quiz-related actions:
quiz, save_quiz_answer, quiz_results, select_quiz_role
- Quiz answers stored in session during multi-question flow
- Session cleared after role selection
- Routes — Quiz routes added before wildcard :step route
- Added
GET /profile/wizard/quiz/:question for quiz questions
- Added
POST /profile/wizard/quiz/:question for answer submission
- Added
GET /profile/wizard/quiz-results for results display
- Added
POST /profile/wizard/select-role for role selection
- Critical: Quiz routes placed before
profile/wizard/:step to prevent route conflict
- Tests — 7 new integration tests for quiz flow
- Test quiz question display and navigation
- Test answer submission and advancement
- Test redirect to results after final question
- Test results page display
- Test role selection and wizard advancement
- Test session clearing after role selection
- Test quiz retake from beginning
- Updated existing tests for 6-step wizard flow
Technical
- All 912 tests passing (added 7 tests, updated 2 tests)
- Views created:
_step_champion_role.html.erb, quiz.html.erb, quiz_results.html.erb
- Route ordering critical: specific quiz routes must come before wildcard :step route
Added - Champion Portal SubPhase 1.4
- Profile Wizard — Linear onboarding flow for new champions
- 5-step wizard: Basic Info → Location & Contact → Professional → Affinities → Photo
- Progress indicator showing current step and completion
- Auto-advances to next step on successful save
- “Back” and “Skip” navigation options
- Redirects to dashboard when complete
- Profile Edit (Section-Based) — Tabbed editing for returning users
- Sidebar navigation with 5 sections matching wizard steps
- Stays on current section after save (no auto-advance)
- Completion checkmarks on sidebar for finished sections
- Accessible via “Edit Profile” from profile show page
- Profile Show (“How Others See You”) — Directory-style preview
- Shows champion’s profile as it appears to other champions
- Privacy indicators (“Hidden” badges) on private fields
- Education section from linked alumni record (degrees)
- District shown in header
- Contact section hidden entirely if all items are private
- Affinities Selection — Multi-select affinity picker
- Searchable dropdown with all available affinities
- Shows selected affinities with remove buttons
- Syncs to
cp_affinities join table
- Photo Upload — Profile photo with cropping preview
- Drag-and-drop or click to upload
- Client-side preview before save
- Stored via ActiveStorage
- Displayed in directory cards and profile
- OAuth BUID Conflict Resolution — Fixed duplicate account creation
- When OAuth email matches alumni with existing champion account (by BUID)
- Now links OAuth to existing account instead of failing with unique constraint error
- Added 5 tests covering all OAuth scenarios
Changed
- Profile Controller — Enhanced with section-based editing
SECTIONS constant defines available sections
section_params method scopes permitted params per section
- Added
bio to professional section params (was missing)
- Routes — Added section parameter for profile edit
GET /profile/edit(/:section) for section-based navigation
Fixed
- Contact section visibility — No longer reveals hidden information exists
- If all contact items are private, section is completely hidden
- Previously showed “Some information is hidden” message
[1.0.13] - 2025-12-11
Added
- Background scan with preview approval - Large file imports no longer timeout
- Upload triggers
AffinaquestScanJob running in background worker
- Shows “Scanning” progress page with auto-refresh polling
- When complete, redirects to full preview of ALL changes
- User reviews and clicks “Commit Import” to apply
AffinaquestApplyJob applies changes in background
- Redis manifest storage - Manifests shared across Heroku dynos
- New
ManifestStore service stores manifests in Redis with 1-hour TTL
- Solves ephemeral filesystem issue (worker saves, web reads)
- Auto-expires, no database bloat for large manifests
- Worker detection - Warning appears after 2 minutes if scan stuck
- Shows Heroku command to enable worker dyno
possibly_stuck? method on batch model
- Import change audit trail - All field updates logged to
crm_data_changes
- Each field change records old value, new value, and batch ID
- Batch detail page shows all field updates made during import
- New importer methods:
Csv::AffinaquestContactImporter.scan(file) - Read-only analysis, returns manifest
Csv::AffinaquestContactImporter.apply_manifest(manifest, imported_by:) - Targeted updates
- Redis added to CI workflow - ManifestStore tests now pass on GitHub Actions
Changed
- Two-pass import architecture - Complete rewrite of import workflow
- Pass 1 (Scan): Background job streams through file, builds change manifest
- Pass 2 (Apply): Background job processes only records that need changes
- Preview shows ALL changes for entire file, not just a sample
- Manifest stored in Redis (shared across dynos) between preview and commit
- New batch statuses:
scanning, scanned added to workflow
- Flow: pending → scanning → scanned → running → completed/failed
- Routes restructured for batch-based flow:
POST /settings/affinaquest/upload - Starts scan job
GET /settings/affinaquest/batches/:id/scanning - Progress page
GET /settings/affinaquest/batches/:id/preview - Shows manifest
POST /settings/affinaquest/batches/:id/commit - Starts apply job
Fixed
- Critical performance fix for Affinaquest import - Import was timing out on production
- Before: N+1 queries - each field for each row queried database for protection status
- After: Single query preloads all protections into hash for O(1) lookup
- Added
CrmDataChange.preload_protections(buids) for bulk loading
- Chunked processing (500 rows) with GC between chunks
- Preview accuracy - Previous version showed “No records to import” when first 50 rows had no changes
- Now scans entire file so preview reflects actual import results
- Heroku memory/timeout issues - 48K row files were causing R14 (793MB) and H12 (timeout)
- Background jobs run on worker dyno, not web dyno
- Chunked processing minimizes memory usage
Migration
- Added
manifest_path column to affinaquest_import_batches (stores Redis key)
[1.0.12] - 2025-12-10
Added
Fixed
- SSO-generated passwords now meet complexity requirements
- Enhanced matching for maiden names and compound first names in import
Data Fixes Applied
- Restored 97 records overwritten by initial Affinaquest import (78 pref_name, 18 last_name, 1 manual)
- Fixed 11 school email records (moved to email_school, personal emails restored)
[1.0.8 - 1.0.11] - 2025-12-09
- Phase 1: Database migrations for Affinaquest sync infrastructure
- Alumni fields:
email_school, email_personal, email_business, email_other, zip, affinaquest_updated_at, affinaquest_synced_at
- New tables:
affinaquest_import_batches, affinaquest_import_conflicts
- Unified CRM sync tables:
crm_data_changes, crm_data_export_batches
- Phase 2: Models for import and CRM sync
AffinaquestImportBatch - Track import history with lifecycle methods
AffinaquestImportConflict - Handle ID mismatches with resolve/dismiss workflow
CrmDataChange - Unified change tracking for CRM export (Affinaquest conflicts, Champion Portal updates, manual edits)
CrmDataExportBatch - Export batch management with convenience class methods
- Alumni model updates:
filter_by_any_email, filter_by_zip, with_degrees, without_degrees scopes; has_degree?, zip_district, zip_region, all_emails methods
- Phase 3: Import service
Csv::AffinaquestContactImporter - Main import orchestrator
- Full import mode with batch tracking and database writes
- Preview mode for reviewing changes before commit
- Recency-aware field updates (preserves newer local data)
- ID mismatch detection with conflict logging
- CRM sync integration via
CrmDataChange.log_affinaquest_conflict
- Phase 4: Controller & routes
Settings::AffinaquestController with 8 actions (index, preview, commit, conflicts, export, resolve, batches, show_batch)
- Routes at
/settings/affinaquest/* for full import workflow
- Conflict management with resolve/dismiss workflow
- CSV export of conflicts for Advancement Services
- Phase 5: Views & UI
- Upload form with file drag-and-drop and recent batches overview
- Preview page with stats, column mapping, sample rows, and commit button
- Conflicts page with resolve/dismiss modal workflow
- Batch history and details pages
- Settings sub-nav updated with Affinaquest link
- Phase 6: Search & Filtering Updates
- District autocomplete search with state disambiguation (e.g., “TN-04 (TN)”)
- API endpoint:
GET /api/districts/autocomplete?query=...
- Stimulus controller:
district_autocomplete_controller.js
- Multi-email search:
filter_by_any_email scope checks email, email_school, email_personal, email_business, email_other
AlumniLookupService and AlumniMatcher updated for multi-email matching
- Batch search supports matching on any email field
- Phase 7: Testing
- Full test coverage for models, services, and controllers
- 733 tests passing with 0 failures, 0 errors
- Phase 8: Documentation
docs/features/AFFINAQUEST_IMPORT.md - Complete feature documentation
Added - Event RSVP Converter (December 8-9, 2025)
- New utility for converting GiveCampus RSVP exports to event app format
- Accessible at Settings > Event Converter for all authenticated users
- Parses GiveCampus CSV format and transforms to event check-in app format
- Automatic alumni matching via
AlumniLookupService (BUID, BQID, email, name)
- Nickname matching support (e.g., “Maddie” matches “Madeline” via NICKNAME_MAP)
- Duplicate registration detection and removal with stats display
- Resolution UI for ambiguous name matches (select from candidates or confirm as guest)
- Preview with detailed statistics before download
- Smart guest resolution (December 9, 2025):
- Two-pass algorithm to identify registrations with BQID holders
- Empty guest info with BQID → uses registrant name (identifying themselves)
- Empty guest info without BQID on multi-ticket registration → marked as “+1”
- Guest first name = “Guest” → marked as “+1”
- +1 format:
first_name = "John", last_name = "Smith +1"
- Email/Phone comparison with system data:
rsvp_email / system_email / email_differs columns
rsvp_phone / system_phone / phone_differs columns
- Flags “YES” when RSVP data differs from Alumni Lookup data
- Helps identify outdated contact info in the system
- AlumniLookupService enhancements:
- Centralized bulk alumni matching with preload optimization (~3.7s for 49K records)
- Multiple matching strategies: BUID → BQID → Email → Name (in priority order)
- Nickname expansion via
generate_name_variations() at search-time
find_name_candidates() method for ambiguous match resolution
- New
filter_by_bqids scope on Alumni model
- Parallel to existing
filter_by_buids for batch BQID lookups
- New files created:
app/services/csv/event_rsvp_converter.rb — Core conversion logic
app/services/alumni_lookup_service.rb — Centralized alumni matching service
app/controllers/settings/event_converter_controller.rb — Upload, preview, download, resolve actions
app/views/settings/event_converter/new.html.erb — Upload form
app/views/settings/event_converter/preview.html.erb — Preview with resolution UI
docs/planning/event-checkin-integration/04-event-rsvp-converter.md — Feature documentation
- Documentation updates:
- Added AlumniLookupService section to
docs/development/AGENTS.md
- Added BQID UI feature request to
docs/development/TODO_BUGS.md
- Test coverage:
test/services/csv/event_rsvp_converter_test.rb — Unit tests
test/services/alumni_lookup_service_test.rb — Service tests
test/controllers/settings/event_converter_controller_test.rb — Controller tests
test/fixtures/files/givecampus_rsvp_sample.csv — Sample fixture data
Added - Champion Portal Phase 1.3: SSO Authentication (December 8, 2025)
- Google OAuth integration
- “Sign in with Google” button at top of login/signup pages
- OmniAuth configured with
/auth path prefix for multi-model Devise support
- Origin-based CSRF protection for OAuth requests
- Callback routes wrapped in
devise_scope blocks
- Profile management
Cp::ProfileController for profile view/edit
- ZIP code completion prompt for SSO signups (
/profile/complete)
- SSO users redirected to complete ZIP if missing
- Controllers created:
Cp::OmniauthCallbacksController — Google OAuth callback handling
Cp::ProfileController — profile show/edit/complete actions
- Test coverage:
test/controllers/cp/omniauth_callbacks_controller_test.rb
test/controllers/cp/profile_controller_test.rb
- Future enhancements deferred:
- Apple Sign In integration
- Facebook OAuth integration
- Account linking (SSO to existing email account)
Added - Champion Portal Phase 1.2: Email Authentication (December 5, 2025)
- Email-based account creation (progressive signup)
- Champions enter name + email + zip code, password set after email confirmation
- Custom Devise controllers in
Cp:: namespace for full control
- Mobile-first TailwindUI forms for signup, login, password reset
- Custom Champion Mailer
Cp::ChampionMailer for Champion Portal emails using champions subdomain
- Branded HTML+text templates: confirmation, password reset, email change, password change
- Uses champion’s first_name in greetings
- Development URL:
dev.alumnichampions.com:3000
- Champion Dashboard
- Profile completion card for incomplete profiles
- Quick stats: district, verification status, member since
- Quick action cards (placeholders for Phase 1.4+)
- Auth-aware layout with navigation
- Header Navigation
- Alumni Champions logo from
/public/alumni-champions.svg
- Desktop profile dropdown with Stimulus controller
- Mobile hamburger menu with toggle
- Sign out functionality in both desktop and mobile
- Stimulus Controllers
dropdown_controller.js - Desktop profile dropdown with click-outside close
mobile_menu_controller.js - Mobile hamburger menu toggle
- Controllers created:
Cp::BaseController - feature flag checking, layout setup
Cp::RegistrationsController - progressive signup flow
Cp::SessionsController - login/logout
Cp::ConfirmationsController - email confirm + password setting
Cp::PasswordsController - forgot/reset password
Cp::DashboardController - authenticated dashboard
- Test coverage (53 tests, 119 assertions):
test/controllers/cp/registrations_controller_test.rb (4 tests)
test/controllers/cp/sessions_controller_test.rb (5 tests)
test/controllers/cp/dashboard_controller_test.rb (4 tests)
test/controllers/cp/confirmations_controller_test.rb (8 tests)
test/controllers/cp/passwords_controller_test.rb (8 tests)
test/models/cp/champion_test.rb (14 tests)
test/mailers/cp/champion_mailer_test.rb (10 tests)
Added - Champion Portal Phase 1.1: Database & Models (December 5, 2025)
- Foundation tables migration
regions - 7 geographic regions
districts - 820 metro/micro areas
zip_codes - 39,305 ZIP codes with city/state
cp_champions - Champion Portal user accounts
cp_profile_changes - CRM export changelog
- Geographic seed data
- Rake task:
rails champion_portal:seed_geographic_data
- Two-pass import: assigns cross-region districts to majority region
- 14 multi-region MSAs handled correctly
- Model enhancements:
Cp::Champion with Devise authentication (email auth only for now)
- Custom mailer configuration via
devise_mailer method
- Verification status enum: unverified → email_verified → champion_verified
Region, District, ZipCode models with associations
Alumni → Cp::Champion association via BUID
Pending
- Champion Portal Phase 1.3: SSO Authentication (Google, Apple, Facebook)
- Champion Portal Phase 1.4: Profile & Directory
- Champion Portal Phase 1.5: Dashboard & Admin verification queue
- Event check-in integration (see
planning/event-checkin-integration/)
[1.0.7] - 2025-12-09
Added
- Rake task
alumni:cleanup_duplicate_buids to handle duplicate BUID records before adding unique index
[1.0.6] - 2025-12-08
Fixed
- Redirect alumnichampions.com to signup flow when Champion Portal feature flag is disabled
[1.0.5] - 2025-12-08
Added
- Event RSVP Converter - See “Added - Event RSVP Converter” section above
- Champion Portal Phase 1.3: SSO Authentication - See “Added - Champion Portal Phase 1.3” section above
[1.0.4] - December 4, 2025: Bug Fixes & Test Coverage Improvements ✅
Addressed TODO items: fixed bugs, added validation, and expanded test coverage.
Fixed
- Alumni affinities modal flow - Added dropdown selector for direct navigation (non-JS fallback)
- File:
app/views/alumni_affinities/_form.html.erb
- Users navigating directly to affinity form can now select affinities from grouped dropdown
- Edit mode continues to display the selected affinity name as before
Added
- EngagementActivity activity_code validation
- Model now validates
activity_code against registered EngagementType codes
- Invalid codes are rejected with clear error message
- Added cache for valid codes with
reset_valid_activity_codes_cache! method
- File:
app/models/engagement_activity.rb
- Controller test coverage
test/controllers/engagement_activities_controller_test.rb - 12 tests
- Authentication, staff access, filtering, statistics display
test/controllers/engagement_stats_controller_test.rb - 21 tests
- All 7 tabs, filters, cache operations, exports
test/controllers/batch_search_controller_test.rb - 21 tests
- Single/multiple names, emails, empty input, large batches (50-100 names)
- Special characters, accents, CSV export, Turbo Stream responses
- Model test coverage
test/models/engagement_activity_test.rb - 13 tests
- activity_code validation, associations, scopes, point calculation
Verified
- Instance variable naming convention - Confirmed
@alum/@alumni usage is consistent across all controllers
[1.0.3] - December 3, 2025: EngagementStats Service Refactoring & Feature Flags ✅
Complete refactoring of engagement stats to use dedicated services, fixing duplicate counting bug.
Fixed
- Demographics discrepancy - Goal #2 showed 283 engaged alumni while Demographics tab showed 461
- Root cause: Demographics counted degree records instead of unique alumni by BUID
- All 6 tabs now count unique alumni consistently
- Matrix scoring consistency - Now matches
EngagementScoreCalculator exactly
- Removed obsolete +5 attendance bonus from MatrixService
- Added proper SQL subqueries for activity-type caps (email_click: 5, event_rsvp: 2)
- Fixed incorrect hardcoded point values (was using wrong LEVEL_POINTS)
- Activity Pairs sorting - Now sorts by count DESC (was incorrectly sorting by level first)
Changed
- EngagementStatsController reduced from 1,755 lines → 745 lines (~1,010 lines removed)
- All tabs now delegate to dedicated services:
OverviewService - Goal #1, Goal #2, Goal #3-5 metrics
DemographicsService - Year/college/major breakdowns (uses COUNT(DISTINCT buid))
BreakdownService - Activity breakdown by role/level
MatrixService - Quadrant analysis with engagement/giving axes
AnalyticsService - Charts, score distributions, monthly activity stacks
ActivityPairsService - Activity combination analysis
- Smoke test delay reduced from 30s to 15s
Added
- Test coverage for all new services (111 service tests total, 409 tests overall)
test/services/engagement_stats/breakdown_service_test.rb
test/services/engagement_stats/matrix_service_test.rb
test/services/engagement_stats/analytics_service_test.rb
test/services/engagement_stats/activity_pairs_service_test.rb
test/services/engagement_stats/scoring_integration_test.rb - Ensures scoring consistency
- Enhanced
AnalyticsService with monthly_activity_stacks for stacked bar chart data
- Services use
BaseService#with_caching for consistent 1-4 hour cache expiration
Technical Notes
- All services inherit from
EngagementStats::BaseService
- Key rule enforced: Count unique alumni by BUID, not degree records
capped_level_to_points_sql now applies activity-type caps via SQL subqueries
- MatrixService fixed for ambiguous
buid column in joined queries
- ActivityPairsService fixed:
EngagementType.all.index_by (not .index_by directly)
Added (Feature Flags)
- Feature flag infrastructure for safe feature rollouts
feature_enabled? helper available in controllers and views
- ENV variable overrides for quick enable/disable without deploy
champion_portal flag ready for Champion Portal development
- See
docs/development/FEATURE_FLAGS.md for usage
Removed
- 9 unused helper methods across 4 files
- Duplicate
lib/tasks/populate_champion_roles.rb
Upgraded Ruby for 15-25% performance improvement.
Changed
- Ruby 3.2.3 → 3.3.8 with YJIT enabled on Heroku
- Added
csv gem proactively (becomes non-default in Ruby 3.4)
- All 298 tests passing
[1.0.1] - December 2, 2025: CI/CD Pipeline Complete ✅
Full CI/CD pipeline implemented with staging environment.
Added
- CI Workflow (
.github/workflows/ci.yml)
- Automated testing on every push
- PostgreSQL 14 service container
- Ruby 3.2.3 + Node 18 setup
- 298 tests passing
- Staging Environment (
alumni-lookup-staging)
- Heroku app with PostgreSQL Essential 0
- Custom domains: beta.alumnilookup.com, beta.alumnichampions.com
- Auto-deploy on push to main (after CI passes)
- Database copied from production for realistic testing
- Deploy Workflows
deploy-staging.yml - Triggers after CI passes on main
deploy-production.yml - Triggers on version tags (v*)
- Smoke tests verify endpoints after deploy
- Database Backups
- Production: Daily at 2:00 AM CT (7-day retention)
- Staging: Daily at 3:00 AM CT
- Monitoring
- UptimeRobot alerts for both domains
- Heroku built-in metrics and logs
- Documentation
docs/operations/DEPLOY_GUIDE.md - Step-by-step deploy instructions
docs/operations/ENV_SETUP_IMPLEMENTATION_CHECKLIST.md - Full setup checklist
Fixed
- Migrations that loaded Alumni model before columns existed (converted to raw SQL)
- Deploy workflow using
HEAD:main for tag-based production deploys
Pre-Release Versions
The following versions were developed before CI/CD was established.
They represent feature milestones but were not formally tagged releases.
[1.0.0-alpha.3] - November-December 2025: Authentication & Roles ✅
All 6 phases completed. Foundation ready for Champion Portal and Event Check-in.
Added
- Phase 6: Permission Flags Infrastructure — Patterns established for future permission flags
- No code changes needed; Phases 1-5 established all required patterns
- Documented pattern for adding
can_* boolean flags with admin bypass
- Ready for Event Check-in to add
can_event_checkin, can_event_manage flags
- Phase 5: Google SSO for Internal Users — Optional Google account linking for staff
- Added
omniauth-google-oauth2 and omniauth-rails_csrf_protection gems
- Added
google_uid and google_linked_at columns to users table
- Created
Users::OmniauthCallbacksController for Google OAuth2 callback
- Added
google_connected?, unlink_google!, and from_google_oauth methods to User model
- Login page shows “Sign in with Google” button when configured
- Profile page allows users to link/unlink their Google account
- Security: Only existing users can sign in with Google (admin creates accounts first)
- Auto-links Google UID on first SSO sign-in for existing users
- Added 13 integration tests for Google SSO flows
- Production Setup: Set
GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET env vars on Heroku
- Production Fixes:
- Moved Google Sign-In section outside main form to avoid nested form issues
- Added custom OmniAuth origin-based validation to handle fresh session CSRF edge cases
- Configured session store with
same_site: :lax for OAuth compatibility
- Phase 4: Forgot Password Flow — Self-service password reset now working
- Configured custom Mailgun API delivery method (
MailgunDeliveryMethod class)
- Fixed Devise
mailer_sender from placeholder to noreply@mail.alumnichampions.com
- Fixed ChampionSignupMailer to use instance method for
default_url_options (was polluting other mailers)
- Production uses
alumnilookup.com for Devise emails, alumnichampions.com for Champion emails
- Fixed Mailgun initializer to only load in production (was breaking tests)
- Styled password reset views (
new.html.erb, edit.html.erb) with Tailwind
- Branded password reset email template with Belmont header and styling
- Added “Forgot password?” link to login page (next to Remember me)
- Added 11 integration tests for password reset flow
- Phase 3: Admin vs Staff Separation — Role-based access control now enforced
- Added
ensure_staff! method to ApplicationController for staff-accessible features
- Staff users can now access: Alumni search, Engagement stats, Champion signups, Batch search, Statistics
- Staff users CANNOT access: Settings, User management, Cache clearing, Champion merge/delete
- Updated 7 controllers with
ensure_staff! or mixed staff/admin access
- Added comprehensive
staff_access_test.rb with 18 tests
- All role access comments updated in controllers
- Phase 2: Role Infrastructure — Added role enum to User model
- Added
role column with values: staff (default) and admin
- Migration backfills existing admins with ‘admin’ role
- Added
staff? helper method (returns true for both staff and admin)
- Updated
admin? to check role enum OR legacy admin boolean
- Updated user form with role dropdown (replaces admin checkbox)
- Updated users index to show role badges
- Added 7 new tests for role functionality
- Phase 1: Unified Admin Checks — Consolidated admin authorization pattern
- Added
admin? method to User model that uses the admin boolean
- Added
ensure_admin! method to ApplicationController as standardized authorization callback
- All Settings controllers now use centralized
ensure_admin! instead of local methods
- Removed deprecated
access_level column from users table
- Added 3 tests for admin? method
- Authentication & Roles Implementation Guide — Prerequisite project for Champion Portal and Event Check-in
features/AUTH_AND_ROLES_SYSTEM.md — Complete authentication & role system (moved from planning/)
- Unify admin checks, add role infrastructure, Admin/Staff separation
- Forgot Password flow, Google SSO for internal users
- Permission flags infrastructure for hybrid authorization model
- Event Check-in Integration Planning — Complete integration plan for alum-events functionality
planning/event-checkin-integration/00-overview.md — Problem, goals, recommendation
planning/event-checkin-integration/01-pros-cons-and-questions.md — Pros/cons analysis, pre-integration questions
planning/event-checkin-integration/02-phased-integration-plan.md — Phased development roadmap with Contact/Event/Registrant models
planning/event-checkin-integration/03-champion-portal-interaction.md — Champion Portal alignment and sequencing
- Hybrid Authorization Model — Documented Roles + Permission Flags approach in
PERMISSIONS_MATRIX.md
- Roles define baseline access (Admin, Staff, Champion, CLC)
- Permission flags for cross-cutting features that don’t fit role hierarchy
- Guidelines for when to add roles vs. permission flags
- Implementation patterns for User model and Pundit policies
Known Issues
See development/TODO_BUGS.md for tracked bugs and issues.
[1.0.0-alpha.2] - November 2025: Documentation & Preparation
Added
- Product Overview Documentation — Comprehensive stakeholder-ready overview (
PRODUCT_OVERVIEW.md)
- Champion Portal Planning — Complete specification for future Champion Portal development
- Development Cycle Preparation — Stages 1-5 completed preparing codebase for role-based access
Changed
- Authentication Standardization — All internal controllers now explicitly require authentication
- PublicController Base Class — External/public features inherit from dedicated base class
- EngagementStats Services — Extracted calculation logic into service objects
Fixed
affinity_name helper crash on nil input
- Alumni affinities form crash when
affinity_code is nil
UsersController#destroy — @user not set before action
- Alumni fixture naming (
alumnis.yml → alumni.yml)
Documentation
- Created
ARCHITECTURE.md — Two-domain architecture documentation
- Created
AUTHENTICATION.md — Devise setup and authorization patterns
- Created
API.md — Internal API endpoint documentation
- Created
PERMISSIONS_MATRIX.md — Role-based access blueprint
- Updated test suite to 241 tests, 573 assertions
[1.0.0-alpha.1] - August-October 2025: Champion Signup & Features
Added
- Champion Signup System v1.0 — Multi-step wizard at
alumnichampions.com
- Welcome → Personal Info → Questions → Role Selection → Interests → Completion
- Email notifications (Mailgun) for new signups
- Admin management interface on
alumnilookup.com
- CSV import/export functionality
- Duplicate detection and merging
- Lifestage interest data cleanup tools
- Alumni Contact ID Integration — Salesforce-compatible Contact IDs (C-000000000 format)
- Alumni Photos — Active Storage with Cloudinary for production
- Accent-Insensitive Search — PostgreSQL
unaccent extension for name searches
- Alumni Prospect Status — Manual prospect flagging with notes
- Top Engaged Alumni — Time-period filtering (30 days, 6 months, 1 year, all time)
- Engagement Scoring Improvements
- Activity caps: email_click (max 5), event_rsvp (max 2)
- Distance formula: √((score × 1.5)² + (capped_activity_count × 1.0)²)
- Removed bonus point system
- Flagship Event Tracking — Identify and track follow-up engagement
- Degree Upload Preview — Preview/commit workflow with auto-create alumni
Changed
- Memory Optimizations — 78% reduction in memory allocations for Engagement Stats
- Tailwind CSS 4.1 — Updated frontend framework
- Batch Search — Added email search capability
- Alumni Search Results — Redesigned with photo thumbnails
Fixed
- Engagement activity import from Emma reports
- Matrix export and chart issues
- Degree details display
- Score chart label issues
[1.0.0-alpha.0] - Early 2025: Initial Development
Added
- Core Alumni Management
- Alumni search with full-text PostgreSQL search
- Alumni profiles with contact information
- Degree tracking linked to majors and colleges
- Engagement Tracking System
- Level-based scoring (0-4 levels)
- Activity tracking with types and dates
- Engagement statistics dashboard
- Affinity Management
- Affinity categories (Athletics, Greek Life, Campus Life, etc.)
- Alumni-affinity associations with roles and years
- Data Import/Export
- CSV import for alumni, degrees, engagement activities
- CSV export for search results
- User Administration
- Devise authentication
- Admin/staff access levels
- User management (admin only)
- Settings Management
- Colleges, majors, affinities configuration
- Engagement types and activity codes
Technical Foundation
- Ruby on Rails 7.1.5.1
- PostgreSQL database
- Tailwind CSS with TailwindUI
- Hotwire/Turbo for partial page updates
- Import maps (no JavaScript bundler)
- Heroku deployment with S3/Cloudinary
Pre-1.0 Development
Initial Commit - Late 2024
- Project scaffolding and initial models
- Basic user authentication
- Alumni and degree models
- Settings pages framework
Version Numbering
This project uses semantic versioning:
- Major (X.0.0) — Significant new capabilities or breaking changes
- Minor (0.X.0) — New features, enhancements
- Patch (0.0.X) — Bug fixes, minor improvements
- Pre-release (-alpha.X) — Development versions before CI/CD was established
Version History
| Version |
Date |
Milestone |
| 1.0.0-alpha.0 |
Early 2025 |
Initial development |
| 1.0.0-alpha.1 |
Aug-Oct 2025 |
Champion Signup system |
| 1.0.0-alpha.2 |
Nov 2025 |
Documentation & prep |
| 1.0.0-alpha.3 |
Nov-Dec 2025 |
Auth & Roles system |
| 1.0.1 |
Dec 2, 2025 |
CI/CD Pipeline (first tagged release) |
| 1.0.2 |
Dec 2, 2025 |
Ruby 3.3.8 + YJIT |
| 1.0.3 |
Dec 3, 2025 |
Service refactoring + Feature flags |