Changelog
All notable changes to the Alumni Lookup application are documented here.
The format follows Keep a Changelog.
[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.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
[Unreleased]
No unreleased changes.
[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 |