Canonical sources: Portal philosophy, posture, and language live in
/docs/planning/champion-portal/source/README.md. Use these sources (includingCHRIST_CENTERED__IDENTITY_STATEMENT.md) when writing specs or user-facing copy. Prefer quoting/paraphrasing over inventing new language.
Status: ✅ Complete (all sub-phases 13.1–13.8 implemented, root route switched to /home)
Completed: February 26, 2026
Tests: 3856 total suite, 0 failures, 0 errors
cp/home#show (removed progressive_dashboard feature flag conditional)cp_dashboard_path references (70+) updated to cp_home_path across 20 controllers, 10 views, 3 layouts, 12 test files/home/dashboard route preserved for backward compatibilityprogressive_dashboard removed from config/routes.rb; environment configs remain but are no longer referencedrole_color replaces hardcoded amber throughout contextual prompts_primary card partialtarget="_top" for navigationcp_community_news_index_path
Estimated Effort: 6–8 weeks
Prerequisites: Phase 11 Complete, Phase 12 Review Complete (findings inform this phase)Implemented: February 23, 2026
Tests: 3706 total suite, 0 failures — 58 new tests (25 Tierable + 24 DashboardVisibility + 9 HomeController + 3 helper/view)
13.1 — Journey Engine & Tier Detection
app/models/concerns/cp/tierable.rb — Cp::Tierable concern in Cp::Champion; engagement_tier, compute_journey_stage (8 stages), recompute_journey_stage!, has_contributions?, tier predicatesdb/migrate/20260223000001_add_journey_stage_to_cp_champions.rb — Integer column with index, default 0, enum mappingapp/services/cp/dashboard_visibility.rb — Maps 8 journey stages to visible sections; tier-based sidebar orderinglib/tasks/journey_stage.rake — rake journey_stage:recompute_allPROFILE_COMPLETION_THRESHOLD=75, COMMUNITIES_THRESHOLD=2, CONNECTION_REQUESTS_THRESHOLD=3, REACTIONS_CONTRIBUTION_THRESHOLD=513.2 — Progressive Dashboard Layout
app/controllers/cp/home_controller.rb — Selective data loading per stage (13 categories)config/routes.rb — GET /home cp_home_path; feature-flagged root switchingconfig.x.features.progressive_dashboard = false (all 3 envs)app/views/cp/home/show.html.erb — Layout orchestrator: hero, 2-col grid, mobile events-firstapp/views/cp/home/_hero.html.erb, cards/ (3 tiers), sidebar/ (7 partials), stages/ (5 partials)app/helpers/cp/home_helper.rb — thread_other_champion with multi-API fallbackImplemented: February 2026
Tests: 3752 total suite, 0 failures
13.4.1 — Onboarding Stage (New Arrival + Profile Building)
app/views/cp/home/stages/_onboarding.html.erb — “What to Do Next” checklist with per-item routing (each item links to its own wizard step/profile section, not all to a single next step)@next_wizard_step → @next_profile_section → fallback to cp_profile_path13.4.2 — Exploring Stage (Community Discovery)
app/views/cp/home/stages/_exploring.html.erb — Community type grid explaining 4 community types (District, College, Industry, Student Activity)13.4.3 — Connecting & Engaged Member Stage (Career + Directory)
app/views/cp/home/stages/_engaging.html.erb — Activity feed, “Suggested Communities” with type labels + member counts + descriptionsseen_tooltips)13.4.4 — Language Updates
Beyond Spec — Additional Deliverables
engaged_member stage (shows “X% complete · Edit →” or “Profile complete ✓”)Implemented: February 2026 (commit afcc9a132ca9d9129c6ae7c229677c0caaa9b5d0)
Tests: Included in suite (14 files, 1188 insertions)
app/services/cp/activity_feed_service.rb — Unified feed engine merging discussions, news, and photo albums with scoring algorithm (recency 0-40, engagement 0-40, unseen +15, community relevance +10); FeedItem struct; top 7 items returnedapp/views/cp/home/_activity_feed.html.erb — Featured item (larger) + compact items + footer links to content indexes; empty state with Browse Communities CTA; “New” badges; type icons; engagement summarylast_dashboard_visit_at column on cp_champions — tracks dashboard visits for “new” detection_exploring.html.erb, _engaging.html.erb, _champion.html.erb, _community_leader.html.erbGET /home/recommendations)Implemented: February 26, 2026 Tests: 3852 total suite, 0 failures — 29 new tests
13.8.1 — “What’s New” Summary Bar
app/views/cp/home/_whats_new_bar.html.erb — Compact summary below hero for returning users (stages 3+): “Since Tuesday: 3 new discussions · 1 new article · 2 new events”Cp::DashboardHelper#whats_new_since_label — Human-friendly timestamps (earlier today, yesterday, day name, date)13.8.2 — Milestone Celebrations
db/migrate/20260226181830_create_cp_milestones.rb — New table: champion_id, milestone_type (enum), achieved_at, displayed_at; unique indexapp/models/cp/milestone.rb — 8 milestone types with enum, scopes (undisplayed, by_recency)app/services/cp/milestone_service.rb — Auto-detection of 8 milestones on dashboard load; per-champion deduplication; celebration messages with warm copy; max 1 per visit; race-condition safeapp/views/cp/home/_milestone_celebration.html.erb — Gold-accented celebration banner with trophy icon, message, and subtitlemilestone_achieved activity event type added13.8.3 — Connections Sidebar Redesign
app/views/cp/home/sidebar/_connections.html.erb — Shows connected people (photo, name, “View” profile link) instead of message threads13.8.4 — Community Activity Badge Labels
app/views/cp/home/sidebar/_communities.html.erb — Per-community activity badges: blue badge (new posts since last visit), amber badge (upcoming events)Beyond Spec — Role Card Visibility Expansion
role_ideas visibility extended from [champion, active_champion, community_leader] to include [exploring, connecting, engaged_member]_exploring.html.erb and _engaging.html.erbRelated Documents:
The dashboard currently presents the entire application at once. A new user lands and sees 13+ sections representing every feature built across 11 phases. No amount of reordering fixes a scope-of-information problem. Three root causes need addressing:
This phase transforms the dashboard from a static feature catalog into a journey-aware, tier-conscious, narrative-driven experience that reveals the right content at the right time.
This is the structural backbone. The portal serves three concentric rings:
Posture: “You belong here. Welcome home.” Focus: Reactive — discover, consume, connect
Core Activities:
Promotional Mechanisms: Give members access to do things a Champion would do — and after doing it, remind them that they’re acting like a Champion, and why not make it official? (Submit a news story, submit an event, comment on a discussion, etc.)
Posture: “You’re already showing up — let us recognize that.” Focus: Proactive — plan events, share news, connect actively, take role-aligned actions
Opt-in mechanism: Complete the Champion Role discovery quiz (already built in Phase 11). Low bar — takes ~2 minutes. It’s recognition of what they’re already doing, not a new commitment.
Recognition signals:
Role-based coaching: At the Champion’s preference, provide ideas on how to engage through their role:
These prompts answer the question: “How can I get more deeply involved / engaged?”
Posture: “You’re the boots on the ground.” Focus: Enable Champions, lead communities, bridge to Alumni Engagement team
We don’t need a new model or role field. The tier is derived from existing data:
| Tier | Detection | Code |
|---|---|---|
| Tier 3: Community Leader | Has CLC assignments | champion.community_leader? / champion.is_community_leader? |
| Tier 2: Alumni Champion | Has completed role quiz | champion.has_champion_role? (checks primary_role.present?) |
| Tier 1: Alumni Member | Default — everyone else | !has_champion_role? && !community_leader? |
The feature flag system (config.x.features.* + feature_enabled?() helper) provides the control layer on top for gradual rollout.
Internal naming stays the same: The model is Cp::Champion — renaming it would be a massive refactor for no user-facing benefit. Internally “champion” remains the model name. User-facing, “alumni” / “Bruin” / “member” is the default. “Champion” only appears after opt-in and in celebratory moments.
This is the narrative the dashboard should guide users through, step by step. The dashboard reveals features progressively as the user advances.
| Step | Action | Dashboard Response |
|---|---|---|
| 1 | Join the Alumni Network — signs up and lands in portal | Welcome experience, minimal UI |
| 2 | Complete 75%+ of their profile — via wizard or edit profile | Profile completion is the obvious first thing to do |
| 3 | Discover Communities — understand the site is community-driven | Explain: communities = ways to find alumni who share your journey (district, major/college, industry, student activities). Joining lets you (re)connect, discuss, read news, find events |
| 4 | Join 2+ communities — and explore what they offer | Community suggestions become prominent |
| 5 | Discover Career Resources — application, resume, interview resources; career fairs; alumni willing to connect for professional growth | Career resources section appears |
| 6 | Discover the Alumni Network — other alumni in their communities willing to connect | Directory and recommendations surface |
| 7 | Send 3+ connection requests — for career or reconnection | Connection prompts and “Bruins You Might Know” |
| 8 | Discover the Champion Opportunity — understand they can go deeper by opting in; roles reflect their natural posture toward helping; 2-minute quiz | Champion opt-in nudge after performing Champion-like actions |
| 9 | Take the Champion Role quiz — choose a Champion Role | Transition to Tier 2 dashboard experience |
| 10 | Contribute to Communities — comment on discussions, add topics, submit news, submit events, welcome new members, invite others | Contribution prompts and coaching based on role |
The dashboard reveals features as the user progresses through their journey. A brand-new user does not see 13 sections. They see what matters right now for where they are.
Implementation approach: A journey_stage helper method on the champion (or computed in the controller) determines which dashboard sections render. Sections have visibility rules tied to journey progress, not just “has content.”
The dashboard should make the site feel fresh, relevant, continually updated, and active. Discussions, News, and Photos should be about what you might have missed. A relevance algorithm should combine:
…to surface the most important content on the main dashboard.
Combine dynamic content into one section — or have something that feels more like a feed on the left side — to make the dashboard more scrollable and less overwhelming. Less “feature inventory,” more “here’s what’s happening in your world.”
| Tier | Dashboard Emphasis |
|---|---|
| Tier 1 (Member) | Discovery, profile completion, community joining, connection-making. Sprinkle in gentle nudges toward Champion opt-in after performing Champion-like actions. |
| Tier 2 (Champion) | Role-aligned coaching, contribution prompts, recognition signals. Surfaces “ways you can help” based on chosen role. |
| Tier 3 (Community Leader) | Community health metrics, new member welcoming, bridging to the Alumni Engagement team. |
| Sub-Phase | Name | Status | Effort |
|---|---|---|---|
| 13.1 | Journey Engine & Tier Detection | ✅ Complete | Medium |
| 13.2 | Progressive Dashboard Layout | ✅ Complete | High |
| 13.3 | Activity Feed | ✅ Complete | High |
| 13.4 | Tier 1 — Member Experience | ✅ Complete | Medium-High |
| 13.5 | Tier 2 — Champion Experience | ✅ Complete | Medium |
| 13.6 | Tier 3 — Community Leader Experience | ✅ Complete | Medium |
| 13.7 | Champion Nudge Engine | ✅ Complete | Medium |
| 13.8 | Freshness, Signals & Milestones | ✅ Complete | Medium-High |
| Goal | Metric |
|---|---|
| Progressive disclosure | New user sees ≤ 4 sections on first dashboard visit; sections increase as they progress |
| Tier-aware dashboard | Tier 2 Champions see role coaching and contribution prompts that Tier 1 Members do not |
| Narrative-driven onboarding | User can identify the one thing to do next within 3 seconds of landing |
| Feed-like freshness | Returning user sees what’s changed since last visit; dashboard feels alive |
| Language alignment | Zero instances of gatekeeping language (“qualify”, “earn”, “become”) for Tier 1 users |
| Champion opt-in lift | Measurable increase in Champion Role quiz starts after nudge engine ships |
| Mobile-first | Engaging content visible within 1 scroll on mobile at every tier |
| Performance maintained | Dashboard load time equal or better than current (lazy loading + reduced initial queries) |
/home RouteThe new progressive dashboard is built as a separate controller and view tree alongside the existing dashboard. This avoids destructive changes to the working dashboard during development.
| Current Dashboard | New Home | |
|---|---|---|
| Route | GET /dashboard |
GET /home |
| Controller | Cp::DashboardController |
Cp::HomeController |
| Views | app/views/cp/dashboard/ |
app/views/cp/home/ |
| Named route | cp_dashboard_path |
cp_home_path |
| Auth root | root to: 'cp/dashboard#show' |
Feature-flagged swap |
Why parallel, not replace:
config.x.features.progressive_dashboard) controls which controller the authenticated root points to/home directly while /dashboard remains the defaultRoot switching (in config/routes.rb):
authenticated :cp_champion do
if Rails.configuration.x.features.progressive_dashboard
root to: 'cp/home#show', as: :cp_authenticated_root
else
root to: 'cp/dashboard#show', as: :cp_authenticated_root
end
end
Note: Both routes remain accessible at all times (
/dashboardand/home). The feature flag only controls where the authenticated root redirects. This means a user can always access either dashboard by URL during the transition.
Deprecation plan (post-Phase 13):
true in staging, QA the new homeCp::DashboardController, app/views/cp/dashboard/, and related routesCp::HomeController to Cp::DashboardController and update routes (or keep as /home)| What | Why Not |
|---|---|
| New data model / role field for tiers | Tiers derive from has_champion_role? and community_leader? — no schema change needed |
Renaming Cp::Champion model |
Massive refactor, no user-facing benefit; internal naming stays |
| Real-time updates (WebSocket) | Turbo Frames for lazy loading, but no Action Cable |
| Full portal redesign | This is dashboard-focused; other pages get tier-awareness and language updates only as needed |
| Rebuilding authentication/verification | Verification flow stays; only the language around it evolves |
| Event RSVP tracking | Prerequisite for some future counters, but out of scope for Phase 13 |
Left Column (2/3 width, lg:col-span-2):
| # | Section | Current Weight | Problem |
|—|———|—————|———|
| 1 | Welcome Hero | Medium | Static — same for new and returning users |
| 2 | Community Notifications | Low | Conditional — fine |
| 3 | Invited Community | Low | Conditional — fine |
| 4 | Next Steps | Medium | Only 3 possible steps (profile, verification, location) — no journey awareness |
| 5 | Community Suggestions | Medium | Too prominent for housekeeping |
| 6 | Role Card | Medium | Shows to everyone equally — no tier context |
| 7 | Discussions (Google News layout) | Medium | Buried below 4 cards; visually identical to News |
| 8 | News (Google News layout) | Medium | Same layout as Discussions — redundant |
| 9 | Photo Albums | Medium | Buried at bottom |
| 10 | Champions You Might Know | Medium | Buried at bottom |
| 11 | Quick Actions | Medium | Hidden on mobile; duplicates navigation |
Right Column (sidebar, renders first on mobile): | # | Section | Current Weight | Problem | |—|———|—————|———| | 1 | District Card | Medium | On mobile: appears before all content | | 2 | Upcoming Events | Medium | Good content, wrong position | | 3 | Your Communities | Medium | Activity badges unlabeled | | 4 | Connections | Medium | Shows message threads, titled “Connections” | | 5 | Your Profile | Medium | Completion % useful but buried |
Root problem: Everything has the same visual weight. 14 queries on page load. No awareness of user journey or tier. A new user and a 2-year veteran see the same dashboard.
| Existing System | How It Serves Phase 13 |
|---|---|
has_champion_role? (Phase 11) |
Tier 2 detection — primary_role.present? |
community_leader? / is_community_leader? |
Tier 3 detection — clc_assignments.exists? |
profile_completion_percentage / profile_complete? |
Journey stage gating (Step 2) |
cp_communities membership count |
Journey stage gating (Step 4) |
connection_count column |
Journey stage gating (Step 7) |
Champion Role quiz + ChampionQuizService |
Opt-in mechanism for Tier 2 (Step 9) |
RoleIdeaPack / RoleIdeaPackService |
Tier 2 coaching content (already built) |
Cp::OnboardingEvent |
Analytics tracking for journey progression |
seen_tooltips JSONB |
Dismissal tracking for nudges |
notification_preferences JSONB |
Coaching delivery preferences (digest, in-app) |
Feature flag system (config.x.features.*) |
Gradual rollout control |
last_sign_in_at / current_sign_in_at |
“What’s new since last visit” baseline |
Problem: The dashboard has no concept of where a user is in their journey or what tier they belong to. Every user sees the same 13+ sections.
Goal: Build the foundational logic that determines a champion’s engagement tier and journey stage, so subsequent sub-phases can render tier- and stage-appropriate content.
Add a helper/concern that exposes the champion’s engagement tier:
# app/models/concerns/cp/tierable.rb (or in Cp::Champion directly)
def engagement_tier
return :community_leader if community_leader?
return :champion if has_champion_role?
:member
end
def tier_member?
engagement_tier == :member
end
def tier_champion?
engagement_tier == :champion || engagement_tier == :community_leader
end
def tier_community_leader?
engagement_tier == :community_leader
end
engagement_tier method to Cp::Champion (or a Tierable concern)tier_member?, tier_champion?, tier_community_leader?)current_tier)tier_champion? returns true for community leaders (they are also champions)Define journey stages that map to the Ideal User Journey. Each stage is determined by what the user has completed:
| Stage | Name | Condition | Journey Steps Covered |
|---|---|---|---|
| 0 | new_arrival |
Just signed up, wizard incomplete | Step 1 |
| 1 | profile_building |
Wizard complete, profile < 75% | Step 2 |
| 2 | exploring |
Profile >= 75%, < 2 communities | Steps 3-4 |
| 3 | connecting |
2+ communities, < 3 connection requests sent | Steps 5-7 |
| 4 | engaged_member |
3+ connection requests, no champion role | Step 8 |
| 5 | champion |
Has champion role | Steps 9-10 |
| 6 | active_champion |
Champion + has contributed (posts, news, events) | Step 10+ |
| 7 | community_leader |
Is a community leader | Tier 3 |
def journey_stage
return :community_leader if community_leader?
return :active_champion if has_champion_role? && has_contributions?
return :champion if has_champion_role?
return :engaged_member if sent_connection_requests_count >= 3
return :connecting if cp_communities.count >= 2
return :exploring if profile_completion_percentage >= 75
return :profile_building if wizard_completed_at.present?
:new_arrival
end
journey_stage method to Cp::Championhas_contributions? helper (checks for board posts, news submissions, event submissions)sent_connection_requests_count helper (or use existing query)@journey_stage and @engagement_tier to dashboard controller show actionDefine which sections are visible at each journey stage:
| Section | new_arrival | profile_building | exploring | connecting | engaged_member | champion | active_champion | community_leader |
|---|---|---|---|---|---|---|---|---|
| Welcome / Getting Started | YES | YES | — | — | — | — | — | — |
| Profile Completion Prompt | YES | YES | YES* | — | — | — | — | — |
| Community Discovery | — | YES | YES | YES* | — | — | — | — |
| Activity Feed | — | — | YES | YES | YES | YES | YES | YES |
| Career Resources | — | — | — | YES | YES | YES | YES | YES |
| Connections / Recommendations | — | — | — | YES | YES | YES | YES | YES |
| Champion Nudge | — | — | — | — | YES | — | — | — |
| Role Ideas (coaching + contextual) | — | — | — | — | — | YES | YES | YES |
| Community Health | — | — | — | — | — | — | — | YES |
| Events (sidebar) | — | — | YES | YES | YES | YES | YES | YES |
| Your Communities (sidebar) | — | — | YES | YES | YES | YES | YES | YES |
| District (sidebar) | — | — | YES | YES | YES | YES | YES | YES |
* = shown in reduced/compact form after the user has progressed past that stage’s focus
DashboardVisibility service or concern that returns visible sections for a given journey stageNote: Progressive disclosure is additive. Sections appear as the user progresses; they don’t disappear. Once a section unlocks, it stays visible. The table above shows when a section first appears.
Problem: The current dashboard renders all sections unconditionally, creating a wall of cards. The layout needs to adapt to the user’s journey stage and tier.
Goal: Restructure the dashboard view to render conditionally based on journey stage, replace the “feature inventory” with a narrative-driven layout.
show.html.erbThe new home is built as Cp::HomeController with views in app/views/cp/home/. The existing Cp::DashboardController is untouched.
# app/controllers/cp/home_controller.rb
class Cp::HomeController < Cp::BaseController
def show
@engagement_tier = current_cp_champion.engagement_tier
@journey_stage = current_cp_champion.journey_stage
# Load only the data needed for this stage (via DashboardVisibility)
end
end
<%# app/views/cp/home/show.html.erb %>
<%# Stage-specific primary content area %>
<% case @journey_stage %>
<% when :new_arrival, :profile_building %>
<%= render "cp/home/stages/onboarding" %>
<% when :exploring %>
<%= render "cp/home/stages/exploring" %>
<% when :connecting, :engaged_member %>
<%= render "cp/home/stages/engaging" %>
<% when :champion, :active_champion %>
<%= render "cp/home/stages/champion" %>
<% when :community_leader %>
<%= render "cp/home/stages/community_leader" %>
<% end %>
Cp::HomeController with show actionget 'home', to: 'home#show', as: :cp_home (within cp scope)app/views/cp/home/show.html.erb with stage-based renderingapp/views/cp/home/stages/ directory with stage partialsapp/views/cp/home/ partialsCp::DashboardController and views remain untouchedThe hero should reflect where the user is in their journey:
| Stage | Hero Content |
|---|---|
new_arrival |
“Welcome home, Bruin!” + profile wizard CTA |
profile_building |
“Let’s get you set up” + completion progress bar |
exploring |
“Discover your communities” + community count |
connecting+ |
Warm greeting + “What’s new” summary bar |
champion+ |
Greeting + Champion badge + role name |
community_leader |
Greeting + CL badge + led communities count |
_hero.html.erb with stage-conditional contentSidebar sections appear progressively and reorder based on tier:
| Tier 1 (Member) Sidebar | Tier 2 (Champion) Sidebar | Tier 3 (CL) Sidebar |
|---|---|---|
| 1. Upcoming Events | 1. Upcoming Events | 1. Community Health |
| 2. Your Communities | 2. Your Communities | 2. Upcoming Events |
| 3. District | 3. Your Impact | 3. Your Communities |
| 4. Profile Completion | 4. District | 4. Your Impact |
| 5. Connections | 5. Connections | 5. Connections |
Introduce 3 visual card treatments to break the “wall of same” problem:
| Tier | Style | Used For |
|---|---|---|
| Primary | Larger header, left-border accent in Belmont Blue, more padding | Activity Feed, Getting Started, Champion Nudge |
| Standard | Current card style with consistent bg-white + shadow |
Events, Communities, Connections |
| Compact | No header bar, tighter padding, inline style | Suggestions, Role Ideas, Impact stats |
bg-white with subtle shadow (remove gradient backgrounds)Problem: Discussions, News, and Photo Albums use separate sections with identical layouts (Google News style: 1 featured + 3 compact). This creates visual redundancy and a “feature inventory” feel. Combined, they should feel like a living feed of what’s happening in the user’s world.
Goal: Replace three separate content sections with a single unified activity feed that surfaces the most relevant, recent, and engaging content.
Create a service that merges and ranks content:
# app/services/cp/activity_feed_service.rb
class Cp::ActivityFeedService
def initialize(champion)
@champion = champion
end
def items(limit: 7)
# Merge discussions, news, photos from the champion's communities
# Score by: recency + engagement (comments/likes) + unseen bonus
# Return sorted, mixed-type collection
end
end
Scoring factors:
Community relevance: Content from the user’s communities ranks higher
Cp::ActivityFeedService with scoring algorithmlast_dashboard_visit_at column to cp_champions (migration) for “unseen” detectionlast_dashboard_visit_at on each dashboard load+---------------------------------------------------+
| What's Happening |
| ------------------------------------------------- |
| [Featured item -- larger card with image/preview] |
| ------------------------------------------------- |
| Discussion title... Nashville 2d |
| News headline... Music Biz 3d |
| Photo album name... District 5d |
| Discussion title... CIS 6d |
| ------------------------------------------------- |
| View All: Discussions - News - Photos |
| |
| + Start a Discussion |
+---------------------------------------------------+
_activity_feed.html.erb partialThe dashboard controller currently runs 14 queries. With stage-based rendering, many are already skipped. For remaining below-fold sections:
Problem: New users land and see everything at once with no guidance. The dashboard should tell a story: here’s what to do first, then next, then next.
Goal: Build the Tier 1 (Member) dashboard experience that guides users through Steps 1-8 of the Ideal User Journey.
For journey_stage :new_arrival or :profile_building, show a focused onboarding experience:
+---------------------------------------------------+
| Welcome Home, Bruin! |
| |
| We're glad you're here. Let's get your profile |
| set up so other alumni can find you. |
| |
| [============-------] 45% complete |
| |
| Next: Add your location |
| [Continue Setting Up ->] |
+---------------------------------------------------+
_stages/onboarding.html.erb with welcome card + profile progressnext_incomplete_wizard_step or next_incomplete_profile_sectionFor journey_stage :exploring, the dashboard shifts focus to communities:
+---------------------------------------------------+
| Discover Your Communities |
| |
| Communities connect you with alumni who share |
| your journey -- where you live, what you studied, |
| your industry, or activities you did as a |
| student. By joining, you can: |
| |
| - (Re)connect with alumni who share your |
| experiences and interests |
| - Join in discussions on relevant topics |
| - Read news that pertains to your world |
| - Find events related to your interests |
| |
| [Browse Communities ->] |
| |
| -- Suggested for You -- |
| [District community] [College community] |
| [Industry community] |
+---------------------------------------------------+
Plus: Activity Feed begins to appear (if the user has joined any communities), Upcoming Events in sidebar.
_stages/exploring.html.erbFor journey_stage :connecting, the dashboard surfaces connections and career resources:
Sections visible:
Events, Communities, District in sidebar
_stages/engaging.html.erb (used for both :connecting and :engaged_member)seen_tooltips JSONB or similar)| Current | Updated | Rationale |
|---|---|---|
| “Champions You Might Know” | “Alumni You Might Know” | Inclusive — they don’t need to be Champions |
| “Next Steps” | Stage-specific headings | More narrative, less task-list |
| “No discussions yet” | “Conversations will appear as your communities grow” | Hope-oriented |
| “No news posts yet” | “Stories and updates will show up here” | Forward-looking |
| “No upcoming events” | “Events will appear here — check back soon!” | Encouraging |
| “No connections yet” | “Your first connection is waiting” | Forward-looking |
| “Complete your profile” | “Let’s get you set up” / “Continue setting up” | More personal |
| “Find Alumni in Nashville” | “Find Fellow Alumni in Nashville” | Warmer |
dashboard_helper.rb text-generating methodsStatus: ✅ Complete (February 2026)
What Was Implemented:
- ContextualPromptGenerator service with 8 role-aware data generators
- Contextual prompts mixed into role card ideas (amber “Now” badge, up to 2 contextual + static fill)
- resolve_contextual_cta_route helper for deep-linking from prompts
- Alumni Champion SVG icon badge on directory cards, discussion posts, discussion comments, profile page
- Role ideas appended to daily/weekly digest emails (“Your Idea for Today” / “Your Champion Idea”)
Spec Deviations:
- 13.5.1 (Champion Dashboard Layout) and 13.5.2 (Your Impact Widget) deferred — existing champion/active_champion stage partials already provide good layouts. Impact widget can be added in a future iteration.
- Badge design uses inline SVG hexagonal icon (Belmont Red) rather than emoji pill — user preference.
coaching_in_digestpreference not implemented — role ideas always show for Tier 2+ in digests.Files Created:
app/services/cp/contextual_prompt_generator.rb,test/services/cp/contextual_prompt_generator_test.rb,test/helpers/cp/champion_roles_helper_test.rbFiles Modified:app/helpers/cp/champion_roles_helper.rb,app/views/cp/dashboard/_role_card_ideas.html.erb,app/views/cp/dashboard/_role_card.html.erb,app/views/cp/discussions/show.html.erb,app/views/cp/boards/show.html.erb,app/views/cp/boards/_comment.html.erb,app/views/cp/directory/_champion_card.html.erb,app/views/cp/profile/show.html.erb,app/mailers/cp/notification_mailer.rb,app/views/cp/notification_mailer/daily_digest.html.erb,app/views/cp/notification_mailer/weekly_digest.html.erb,app/controllers/cp/home_controller.rb,app/controllers/cp/dashboard_controller.rbTests: 3784 runs, 0 failures, 0 errors
Problem: Champions who have opted in via the role quiz see the same dashboard as brand-new users. There’s no recognition, no role-aligned coaching, and no differentiation.
Goal: Build the Tier 2 dashboard experience that celebrates the Champion’s commitment and provides role-aligned coaching and contribution prompts.
For journey_stage :champion or :active_champion:
Left column:
RoleIdeaPackService; see 13.5.3)Right sidebar:
_stages/champion.html.erb_role_card.html.erb — shows both coaching ideas and contextual prompts (see 13.5.3)A compact sidebar widget showing the Champion’s contributions:
+----------------------------------+
| Your Impact |
| +----+ +----+ +----+ +----+ |
| | 3 | | 2 | | 1 | | 5 | |
| |conn| |comm| |news| |post| |
| +----+ +----+ +----+ +----+ |
+----------------------------------+
Counts: Connections made, Communities joined, News submitted, Board posts + comments
Note: Event RSVP tracking does not yet exist — do not include event attendance counts. Add counter later when functionality ships.
load_impact_stats method to dashboard controllerconnection_count, cp_communities.count, news posts authored, board posts + comments countKey insight: The existing
RoleIdeaPacksystem (115+ seed ideas, daily pack of 3, anti-repeat, admin CRUD, template variables, CTA deep-linking) already does 80% of what “Contribution Prompts” would need. Rather than building a separateCp::ContributionPromptService, evolve the RoleIdeaPack system to also serve contextual, data-driven prompts alongside the existing static coaching ideas.
What exists today (Phase 11):
Cp::RoleIdea — pre-authored ideas with title, body (template vars: ,, ``), cta_label, cta_route, role, target, priority, statusCp::RoleIdeaPack — daily pack of 3 ideas per champion, weighted random selection, 30-day anti-repeat windowCp::RoleIdeaPackService — generates/refreshes daily packs, handles CL mixing_role_card.html.erb State B) with refresh buttonWhat needs to evolve:
Add a contextual prompt layer that generates dynamic prompts from live community data and mixes them into the daily pack:
| Type | Source | Example |
|---|---|---|
| Static (existing) | Pre-authored cp_role_ideas |
“Start a conversation in about something you’re passionate about” |
| Contextual (new) | Generated from community data | “A new Bruin just joined Nashville District — say hello!” |
| Contextual (new) | Generated from community data | “There’s a discussion in College of Music with no replies yet” |
| Contextual (new) | Generated from community data | “Music City Meetup is 5 days away — help spread the word” |
Contextual prompt sources (role-aware):
| Role | Contextual Triggers |
|---|---|
| Connection Advisor | New members in their communities (last 7 days); alumni in their district who aren’t connected |
| Digital Ambassador | Quiet communities (no posts in 14+ days); photo albums with no photos |
| Community Builder | Unanswered discussions (posts with 0 comments); communities they lead with < 3 discussions this month |
| Giving Advocate | Upcoming events in their communities (next 14 days); events with low visibility |
Implementation approach:
# Extend Cp::RoleIdeaPackService (or extract to a new Cp::ContextualPromptGenerator)
# to produce 0-2 contextual prompts per pack refresh, mixed with static ideas.
#
# Contextual prompts are NOT persisted as RoleIdea records — they're
# generated on the fly and injected into the pack display.
# The pack remains 3 items total: up to 2 contextual + remainder static.
Cp::ContextualPromptGenerator service that checks community data for role-relevant triggers{ title:, body:, cta_label:, cta_route:, source: :contextual }RoleIdeaPackService#today_pack to mix 0-2 contextual prompts into the daily pack (contextual first, fill remaining with static ideas)_role_card_ideas.html.erb to handle both persisted ideas and contextual prompt hashesseen_tooltips JSONB pattern — key by prompt type + entity ID) — DeferredRole ideas are ideal add-on content for digest emails. They’re not important enough to trigger a digest on their own, but when a digest is already being sent (new discussions, events, community activity), role ideas can be appended for Champions.
Rules:
render_body with context)notification_preferences JSONB — only if the champion hasn’t opted out of coaching contentPosition: bottom of the digest, after the main content, with a soft header like “Your Champion Idea for Today” or “A Way to Get Involved”
has_champion_role?)primary_idea from RoleIdeaPackService (reuse existing pack, don’t generate a new one)coaching_in_digest preference to notification_preferences JSONB (default: true) — Deferred (always shows for Tier 2+)Status: ✅ Complete (February 2026)
What Was Implemented:
- “New Members to Welcome” section in CL stage partial — shows unwelcomed members with icons, linked to profiles
- Auto-marks members as welcomed when CL visits their profile
- activity_event tracking for welcoming actions
Spec Deviations:
- 13.6.1 (Community Health Widget) — Already existed from Phase 12:
_community_health.html.erbwith member counts, discussion counts, event counts. No changes needed.- 13.6.3 (CL Dashboard Stage) — Already existed from Phase 12:
_stages/community_leader.html.erbwith full layout. New member welcoming was integrated into the existing stage partial.- “Send Welcome” quick action deferred — profile visit auto-welcome is simpler and sufficient for now.
Files Created: None (integrated into existing partials) Files Modified:
app/views/cp/dashboard/_stages/community_leader.html.erb,app/controllers/cp/profile_controller.rb,app/models/cp/community_membership.rbTests: 3784 runs, 0 failures, 0 errors
Problem: Community Leaders currently see the same dashboard as everyone else. They need tools to monitor community health, welcome new members, and bridge to the Alumni Engagement team.
Goal: Add CL-specific dashboard sections that support their leadership role.
For community leaders, the top of the sidebar shows health metrics for their led communities:
+----------------------------------+
| Your Communities |
| -- Nashville District -- |
| 12 members - 3 new this month |
| 4 discussions - 1 event upcoming|
| -- College of Music -- |
| 8 members - 1 new this month |
| 2 discussions - 0 events |
| [Manage Communities ->] |
+----------------------------------+
_community_health.html.erb partialSurface new members who haven’t been welcomed yet:
_stages/community_leader.html.erbProblem: There’s no mechanism to gently guide Tier 1 Members toward becoming Tier 2 Champions. The role quiz exists (Phase 11) but users only encounter it if they find the Role Card on a cluttered dashboard.
Goal: Build a contextual nudge system that recognizes when a Member performs “Champion-like” actions and suggests making it official.
A dedicated page at /champion-info that tells the Champion story. This is the “Learn More” destination — it gives curious-but-not-ready users a place to understand the program without the commitment of starting the quiz.
Page structure:
“What is an Alumni Champion?” — Frame it as recognition, not recruitment: Champions are alumni who choose to stay actively connected — not because it’s required, but because they want to. Every alum is already part of the community; Champions simply choose to show up more intentionally.
“The Three Ways Alumni Engage” — Visual representation of the three tiers. Emphasis: most alumni are Tier 1 and that’s wonderful. The tiers aren’t a ladder — they’re concentric circles with most people in the outer ring by design.
Tone: Warm, zero-pressure, celebratory. This page should make someone feel seen, not sold to. The framing is “we notice you’re already doing this” not “here’s what we need you to do.”
Cp::ChampionInfoController with show actionget 'champion-info', to: 'champion_info#show' (within cp namespace)app/views/cp/champion_info/show.html.erbcp_profile_wizard_quiz_path(1)Cp::ActivityRecorder.record(:viewed_champion_info)show actionDefine actions that trigger a Champion nudge:
| Action | Nudge Message |
|---|---|
| Submit a news story | “You’re sharing stories like a Champion! Want to make it official?” |
| Submit an event | “Organizing events is a Champion move — take the 2-minute quiz?” |
| Comment on 3+ discussions | “You’re sparking conversations — that’s what Champions do.” |
| Send 5+ connection requests | “You’re building the network — Champions help others do the same.” |
| Complete 100% profile | “Your profile is looking great — ready to go deeper?” |
seen_tooltips JSONB (or a dedicated nudge_history JSONB column)Nudges appear as a gentle inline card on the engaged_member stage dashboard:
+---------------------------------------------------+
| ✦ You're sparking conversations — |
| that's what Champions do. |
| |
| Some alumni choose to go a step further — not |
| because it's expected, but because they want to. |
| It takes about 2 minutes to find out how you |
| already show up. |
| |
| [Find Your Role →] [Learn More] [Not Now] |
+---------------------------------------------------+
Dual CTA pattern:
cp_profile_wizard_quiz_path(1))cp_champion_info_path)The secondary CTA is important — it gives the user a low-commitment path. They may not be ready to take a quiz, but they might be willing to read a page. The discovery page then funnels them to the quiz when they’re ready.
_champion_nudge.html.erb partial with dual CTAsengaged_member dashboard between Activity Feed and RecommendationsBeyond the dashboard, show a brief inline nudge immediately after performing a Champion-like action:
✅ Phase 13.7 Complete — Champion Nudge Engine + Champion Role Microsite:
/champion-info): Educational “What & Why” page — purely informational, no inline role management. 3 sections (What is a Champion, Three Engagement Tiers, What Changes), conditional CTA (has role: summary card → role detail; no role: Explore Roles + quiz). Canonical source language._champion_microsite_nav.html.erb):
/champion-info — Educational overview (What & Why)/roles — Explore & Choose with rich role cards, direct “Choose this role” buttons, quiz CTA/roles/:role — Role detail + management (retake quiz, change role, remove role with amber confirmation dialog)max-w-3xl container with consistent paddingchampion-info/select-role and champion-info/remove-role; added DELETE /roles/remove; all entry points updated (dashboard, profile, wizard, nudge)engaged_member stage with dual CTAs (quiz + learn more) and dismiss link, turbo-frame wrapped for clean removalapp/views/cp/shared/_champion_microsite_nav.html.erbapp/views/cp/champion_info/show.html.erb, app/views/cp/roles/show.html.erb, app/views/cp/roles/index.html.erb, app/controllers/cp/champion_info_controller.rb, app/controllers/cp/roles_controller.rb, config/routes.rb, app/views/cp/dashboard/_role_card.html.erb, app/views/cp/profile/show.html.erb, app/controllers/cp/profile_wizard_controller.rb, app/views/cp/home/_champion_nudge.html.erb/champion-info evolved from a standalone discovery page into a cohesive microsite hub alongside /roles and /roles/:role.Problem: Nothing signals what’s new since the last visit. Returning users see a static layout. There’s no recognition of progress or milestones.
Goal: Make the dashboard feel alive for returning users, and celebrate progress milestones.
Below the hero greeting for returning users (stages 3+):
3 new discussions - 1 new event - 2 new photos since Tuesday
last_dashboard_visit_at (added in 13.3.1) as baselineDefine milestone events and celebrate them:
| Milestone | Trigger | Display |
|---|---|---|
| First Community | Join first community | Celebration banner |
| Profile Complete | >= 75% profile completion | Celebration banner |
| First Connection | First accepted connection | Celebration banner |
| First Post | First board post | Celebration banner |
| Champion Opt-In | Complete role quiz | Celebration banner + badge |
| 1-Year Anniversary | created_at anniversary |
Dashboard banner |
| 10 Connections | Reach 10 connections | Achievement card |
| Community Leader | Promoted to CL | Dashboard banner |
cp_milestones table: champion_id, milestone_type, achieved_at, displayed_atCp::MilestoneService to check and record milestones on dashboard loaddisplayed_at)The sidebar card currently shows message threads but is titled “Connections”:
cp_connections_pathThe tiny colored badges in “Your Communities” are unlabeled:
| Phase 12 ID | Finding | Addressed In |
|---|---|---|
| L-01 | Christ-centered identity through hospitality posture | 13.4 (warm language, belonging-first), 13.2 (hero messaging) |
| L-05 | Replace gatekeeping language | 13.4.4 (language updates), 13.7 (nudge framing) |
| L-07 | “User” -> “Champion/person” | 13.4.4 |
| L-12 | “alumni network” -> “alumni community” | 13.4.4 |
| F-01 | Weave champion roles into ongoing experience | 13.5 (Tier 2 experience), 13.7 (nudge engine) |
| F-02 | Rewrite empty states with warmth + CTAs | 13.4.4, 13.4.1 (onboarding stage replaces empty states) |
| O-01 | Identity grounding | 13.4 (language), 13.1 (tier model aligns identity with action) |
Cp::Champion#engagement_tier — all tier states (member, champion, community_leader)Cp::Champion#journey_stage — all 8 stages with boundary conditionsDashboardVisibility — correct sections for each stageCp::ActivityFeedService — scoring, ranking, content type mixing, empty statesCp::ContextualPromptGenerator — contextual prompts per role, data-driven generationRoleIdeaPackService (evolved) — mixed pack rendering (static + contextual), digest integrationCp::MilestoneService — detection, deduplication, priority orderingDashboardHelper — updated text methods, greeting sub-linesCp::HomeController#show loads correctly at each journey stageCp::DashboardController#show still works unchanged (parallel build)progressive_dashboard feature flag| Criteria | Measurement |
|---|---|
| Progressive disclosure works | New user sees <= 4 sections; no “wall of cards” |
| Journey narrative is clear | User identifies the one next action within 3 seconds |
| Tier differentiation is visible | Champion dashboard is noticeably different from Member dashboard |
| Champion opt-in increases | Measurable increase in quiz starts after nudge engine ships |
| Freshness drives return visits | “What’s New” summary visible to returning users |
| Language is warm and inclusive | Zero instances of “qualify,” “earn,” “become” for Tier 1 |
| Mobile-first | Priority content visible within 1 scroll at every tier |
| Performance maintained | Dashboard load time equal or better (fewer queries per stage + lazy loading) |
| Milestone recognition | Users see at least 1 celebration in their first month |
GET /home is the authenticated root for all environmentsGET /dashboard remains accessible for backward compatibilityprogressive_dashboard removed from route conditional; environment configs remain inertcp_dashboard_path references replaced with cp_home_path (controllers, views, layouts, tests)/dashboardlast_dashboard_visit_at column), 13.8.2 (cp_milestones table)Cp::HomeController (13.2), Cp::ChampionInfoController (13.7)ActivityFeedService), 13.5.3 (ContextualPromptGenerator), 13.8.2 (MilestoneService)RoleIdeaPackService gains contextual prompt mixing + digest email supportModel methods added in 13.1 (engagement_tier, journey_stage) and services (ActivityFeedService, etc.) are available to both controllers. The old dashboard could optionally consume them too, but the spec doesn’t require it.
To be resolved during sub-phase planning interviews.
| # | Question | Options | Notes |
|---|---|---|---|
| 1 | Journey stage thresholds | Hardcoded vs configurable | Configurable allows tuning without deploys, but adds complexity |
| 2 | Nudge frequency | 1/session vs 1/week | Too frequent = annoying; too infrequent = invisible |
| 3 | Feed algorithm weights | Recency-heavy vs engagement-heavy | Needs tuning after launch — start recency-heavy, iterate |
| 4 | Champion badge design | Icon badge vs text label vs profile ring | UX decision — reference DESIGN-GUIDELINES.md |
| 5 | Career Resources section | Dedicated card vs feed integration | May warrant its own discovery moment vs just another feed item |
Completed during Phase 13 wrap (February 26, 2026).
docs/features/ENGAGEMENT_TIERS.md (new) — covered in FUNCTIONALITY_OVERVIEWdocs/features/PROGRESSIVE_DASHBOARD.md (new) — covered in FUNCTIONALITY_OVERVIEWdocs/features/CHAMPION_NUDGE_ENGINE.md (new) — covered in FUNCTIONALITY_OVERVIEWdocs/features/CHAMPION_DISCOVERY_PAGE.md (new) — covered in FUNCTIONALITY_OVERVIEWdocs/features/ROLE_IDEAS.md (evolution: contextual prompts + digest integration)config/faq.yml) — no new FAQ entries needed