alumni_lookup

Alumni Network — Backlog

Canonical sources: Portal philosophy, posture, and language live in /docs/planning/alumni-network/source/README.md.
Use these sources (including CHRIST_CENTERED__IDENTITY_STATEMENT.md) when writing specs or user-facing copy. Prefer quoting/paraphrasing over inventing new language.

Purpose: Track ideas, nice-to-haves, and intentionally deferred features for the Alumni Network. Not a task list — just a place to capture things so they aren’t lost.

When to Reference: During Sub-Phase Planning Checkpoint (see CLAUDE.md)

Last Updated: March 2026


Table of Contents

  1. Feature Backlog (Post-Beta)
  2. Architecture Ideas
  3. UX Enhancements
  4. Educational & Onboarding
  5. Technical Improvements
  6. Phase-Specific Deferrals
  7. Database Cleanup
  8. Future Enhancements
  9. How to Use This Document

1. Feature Backlog (Post-Beta)

These are significant features planned for after Beta launch. Prioritization will be based on alumni feedback and engagement data.

Feature Description Priority Effort Status
Alumni Business Directory Alumni can list their businesses for other Bruins to discover (“Hire a Bruin”) High Medium Backlog
Native Mobile App iOS/Android apps with push notifications High Very Large Backlog
Newsletter Tool Hard-coded sections + community content sections for staff-curated newsletters Medium Medium Backlog
Real-Time Messaging WebSocket-based instant messaging (currently async) Medium Large Backlog
Map View Visual map of alumni across the country Medium Medium Backlog
Advanced Search More filters, saved searches, search history Medium Medium Backlog
Job Board Alumni post job openingsReconsidering scope; may integrate with Business Directory TBD Large Backlog
Mentorship Center Structured mentorship matchingSuperseded by Phase 10: Connections Superseded
Staff Reporting & Analytics Community health dashboard and PDF report exports (Phase 6.2/6.3) — activity metrics & CSV exports already delivered in Phase 6.1; see Phase 6 README Medium Medium Backlog

1.1 Alumni Business Directory

Added: January 2026 (Phase restructure)

Description: Alumni with businesses can list them for discovery by other Bruins. Supports the “hire a Bruin” and “support Bruin businesses” use cases.

Key Features:

Data Model:

# cp_businesses table
t.references :champion          # Business owner
t.string :name, null: false
t.text :description
t.string :industry
t.string :city
t.string :state
t.string :website
t.string :phone
t.boolean :verified, default: false   # Staff can verify
t.timestamps

Priority: High — frequently requested; supports alumni economic network


1.2 Newsletter Tool

Added: January 2026 (Phase restructure)

User Spec: “hard-coded + community content sections”

Description: Staff-curated newsletters with both fixed content sections and dynamic community-specific sections.

Key Features:

Priority: Medium — valuable for re-engagement but email platform may handle this


1.3 Real-Time Messaging

Added: January 2026 (Phase restructure)

Description: Upgrade current async connection-gated messaging to WebSocket-based real-time delivery.

Key Features:

Implementation: ActionCable WebSocket channels

Priority: Medium — current async model works fine for MVP; connections system (Phase 10) already solid


1.4 Map View

Added: January 2026 (Phase restructure)

Description: Visual map showing alumni locations across the country.

Key Features:

Implementation: Leaflet.js or Google Maps API

Priority: Medium — visually impressive but directory search achieves same goal


Added: January 2026 (Phase restructure)

Description: Enhanced search capabilities beyond current directory filters.

Key Features:

Note: “Champions Like You” recommendations (Phase 5.4) already address the discovery use case. Current directory filters handle most search scenarios.

Priority: Medium — current filters handle most use cases


1.6 Mentorship Center — SUPERSEDED

Superseded by: Phase 10: Connections (February 2026)

Rationale: The Connections system replaces the planned Mentorship Center with a broader, less hierarchical approach. Instead of formal mentor/mentee roles, alumni request connections by type (Say Hi, Career Advice, Networking). This avoids the “loaded meaning” of mentorship while achieving the same goal: helping alumni find people 5 steps ahead or behind them for meaningful conversations.


1.7 CRM Workflow Improvements

Added: January 2026 (Phase 2.3/2.4)

Context: As more staff get access to the portal, the current CRM changes workflow (buried in /settings/affinaquest/crm_changes) needs to be more accessible and streamlined.

Current State:

Potential Improvements:

Priority: Medium — works for now, revisit as portal_admin usage grows


1.7a Student Groups in the CRM Sync

Added: August 2026 (Phase 22.6 — held, not deferred work)

Context: Signup-reported student groups were logged as CrmDataChange rows from 22.4 until 22.6, when they were pulled back out. The blocker is not code: nobody has decided how an alum-reported group becomes a validated affinity_code, or who owns that call, so the rows accrued in a queue staff work by hand and could not be acted on. affinity_other (free text) is the sharper version of the same question.

Current State:

To Re-Enable:

Priority: Blocked on an Advancement Services decision, not on engineering time.


1.8 Employer Entity Resolution

Added: July 2026 (deferred from Phase 24 — see phases/phase-24/README.md §5, §8)

Context: Phase 24 imports CRM employment as free text. “VUMC Vanderbilt University Medical Center” appearing 12 times is one employer, not 12 entities — but the employments table has no concept of an employer as a thing, only a string per row.

Current State:

Potential Improvements:

Priority: Low — fuzzy search covers the asks driving Phase 24. Revisit if staff start asking “how many alumni work at X” rather than “who works at X”, which is the question the string can’t answer.


2. Architecture Ideas

2.1 Unified Admin Portal (admin.alumnilookup.com)

Added: December 4, 2025 (Phase 1 planning)

Idea: Create a separate admin portal at admin.alumnilookup.com that consolidates settings from both Lookup Portal and Alumni Network.

Benefits:

Considerations:

Status: ⚠️ Superseded in part — reconciled July 30, 2026 (Phase 23.6).

Phase 23 Decision 1 rejected a generic /admin/ on exactly the premise this item assumes. Consolidating “all administrative functions” into one undifferentiated admin surface reintroduces the ambiguity the phase existed to remove, because two products have staff admins and they are not interchangeable:

Product Its staff admin Status
alumnichampions.com signup ecosystem /signup_admin/ ✅ Active
belmontalum.com member portal /alumni_network/ 🛑 Held

An admin path must name the product it administers. /champion_signups/ vs /champions/ — one character apart, administering different products — is the concrete failure this idea would recreate at the domain level, and it was violated often enough to require a hold gate and an enforcement test.

What survives: the underlying complaint (“where is that setting?”) is real, and /settings/ is genuinely split across surfaces. The answerable version is a cross-surface settings index that links into each product’s own admin, not a fourth surface that absorbs them. That is a navigation problem — see 2.2, which is the better-shaped version of the same need.

Do not implement 2.1 as written. It argues against the shipped architecture.


2.2 Cross-Portal Navigation

Added: December 4, 2025 (Phase 1 planning)

Idea: Users with permissions to multiple portals should have an easy way to switch between them.

Proposed Solution: Header dropdown showing available portals based on user permissions:

Status: NOT IMPLEMENTED — Should add before general availability

Priority: Medium — Quality of life for staff who work in both portals


2.3 District Name Management UI

Added: December 4, 2025 (Phase 1.1 implementation)

Idea: Admin interface to edit district display names without direct database access.

Context: Districts are imported from CSV with long MSA names (e.g., “Nashville-Davidson–Murfreesboro–Franklin”) but we want short display names (e.g., “Nashville”). The csv_name column preserves the original for import matching, while name is the editable display name.

Scope:

Priority: Medium — seed script auto-generates short names, but staff will want to customize ~50 key metros

Status: Seed script handles most cases. Manual DB edits sufficient for now.


3. UX Enhancements

3.1 ZIP Code Autocomplete

Added: December 4, 2025

Idea: Type-ahead autocomplete for ZIP code entry that shows city/state as user types.

Priority: Low (ZIP entry is one-time during signup)


3.2 Profile Photo Cropping

Added: December 4, 2025

Idea: Client-side image cropping before upload to ensure consistent profile photos.

Libraries to consider: Cropper.js, react-image-crop

Priority: Medium (better UX, but not blocking)


3.3 PWA Support

Added: December 4, 2025

Idea: Add service worker and manifest for Progressive Web App capabilities.

Benefits:

Priority: Medium (good for mobile-first, enables future native app transition)


3.4 Batch Search Enhancements (Lookup Portal)

Added: December 17, 2025 (UX polish for Lookup Portal)

Context: Batch Search tool allows staff to search for multiple alumni by name/email. Current implementation works but has several UX gaps.

Enhancements:

3.4.1 Column Alignment

Fix result table column alignment. Use fixed-width columns or sticky headers.

3.4.2 Multiple Match Resolution

When a search returns multiple matches, allow user to select which result to export (radio buttons or clickable cards).

3.4.3 Standard Alumni Export Format

Audit all alumni export formats across the application. Create shared export format applied to batch search.

3.4.4 Filter: Show Only Matches

Toggle to hide rows with 0 matches.

3.4.5 Export Options: Matches / Non-Matches / All

Radio buttons before export: All, Matches only, Non-matches only.

3.4.6 Copy/Paste Support for Spreadsheets

Copy Table as TSV button, individual cell copy, XLSX export option.

Priority: Medium-High (staff-requested feature)

Effort: Low–Medium per item


3.5 Email Address Change Flow ✅ IMPLEMENTED

Implemented: January 2026 (Phase 1.9.1) — Settings page with email change flow, confirmation email, CRM data change logging.


3.6 Simplify First Name Fields ✅ IMPLEMENTED

Implemented: December 2025 (Phase 1C) — Single “First Name” field with collapsible section for legal name and maiden name.


3.7 Invite Others CTA from Dashboard

Added: December 22, 2025 (Phase 1.6.2)

Idea: When a verified alum is the only person in their district, show an “Invite another Belmont Alum” link/button on the dashboard that launches an invite flow.

Context: A placeholder link exists on the dashboard today but does not trigger any flow.

Priority: Medium — reinforces growth and community-building


3.8 Profile Page Visual Refresh ✅ IMPLEMENTED

Implemented: January 2026 (Phase 1.9.3) — Simplified directory cards, large photos with role badge, education display with college SVG icons, mobile-first layout.


4. Educational & Onboarding

Added: January 2026 (Phase 5.3 fixes). Updated: February 2026 (Phase 9 status).

Purpose: Track features, pages, and settings that need better user education — through tooltips, callouts, welcome emails, or in-app guidance.

4.1 Features Needing Tooltips

Status: All tooltip calls were removed in February 2026 during UX overhaul. The tooltip infrastructure (controller, helper, Stimulus controller, model methods) is preserved and ready for re-integration. When re-adding tooltips, design them around the ideal onboarding flow and current feature set.

Suggested tooltips for re-implementation:

Feature Location Tooltip Content Idea Priority
Directory Search Directory “Search for alumni by name, filter by college, district, or graduation year.” High
Community Discovery Home / Communities “Communities connect you with alumni who share your interests, location, or college.” High
Connection Requests Directory card / Profile “Send a connection request to start messaging this alum.” High
Career Resources Careers page “Access resume guides, interview prep, and alumni mentors.” Medium
Privacy Settings Settings → Privacy “Control who can see your location, contact info, and profile.” Medium
Notification Settings Settings → Notifications “Choose how you hear about new messages, events, and community activity.” Medium
Connections Preferences Settings → Connections “Set limits on who can send you connection requests.” Medium
Profile Completion Dashboard “A complete profile helps fellow Bruins find and connect with you.” Medium
Board Discussions Community Board “Start conversations, ask questions, and share updates with your community.” Low

4.2 Educational Callouts (Static Prompts)

Location Callout Content Status
Messages Index “You’re in control. Privacy Settings let you choose who can message you…” ✅ Implemented (Phase 5)
Directory Index “Your privacy matters. Privacy Settings let you control who can see your location…” ✅ Implemented (Phase 5)
Connections Page “Connections you make can reach you via direct messaging…” ⏳ Planned
Profile Edit “Your profile helps other alumni find and connect with you…” ⏳ Planned

4.3 Welcome Email Content

Email Trigger Content to Include Status
Account Verified Welcome! Here’s how to get started… ⏳ Backlog — Phase 9.6 spec ready, deferred March 2026
First Login Quick start guide: Find alumni, Update Profile, Join Communities ⏳ Backlog — Phase 9.6 spec ready, deferred March 2026
First Connection Made “Nice! You connected with [Name]. Here’s how messaging works…” ⏳ Planned

4.4 In-App Onboarding Ideas

Idea Description Priority
Onboarding Checklist Dashboard widget showing progress Medium
Feature Spotlight Modal popup for new features after major releases Low
Guided Tour Step-by-step tour on first login (Shepherd.js) Low
Contextual Help ”?” icons next to complex settings Medium

4.5 Documentation for Alumni

Resource Description Status
Help & FAQ Searchable FAQ page ✅ Implemented (Phase 1.9)
Privacy Guide Detailed explanation of privacy settings ⏳ Planned
Getting Started Guide Walkthrough for new alumni ⏳ Planned
Video Tutorials Short videos showing key features Future

5. Technical Improvements

5.0-A Engagement Stats — What We Measure and Who We Count

Added: August 4, 2026 (surfaced while fixing the Analytics/Breakdown timeouts)

Context: The timeout fix was purely about query shape — no measurement definition was changed. But profiling surfaced several counting decisions that are currently implicit and worth deciding on deliberately. Figures are from the dev database, August 2026.

1. Only the first major is counted. Education::AggregateScope selects the major with ORDER BY eaos.id ASC LIMIT 1. 4,870 educations have more than one concentration_level = 'major' — every one of those alumni is attributed to a single major. Same class of issue as the “show what we have on file” rule in .claude/skills/debug/SKILL.md. Decide: first-major, primary-major (needs a flag), or count the alum once per major.

2. Minors and concentrations are invisible to stats. 22,044 minor and 8,123 concentration rows never enter the aggregate scope at all.

3. 563 alumni can never match a college filter. Of the 604 alumni on the legacy Degree fallback (1.2% of the population), only 41 rows carry a college_code. They silently drop out of every college-filtered figure rather than appearing as “unknown.”

4. College attribution prefers current school over granting school. COALESCE(NULLIF(NULLIF(current_school_code,''),'00'), granting_school_code). An alum who earned a credential in one school but is now enrolled in another is counted under the school they are in now. 180 rows fall through to granting. Confirm this is intended — it is a real semantic choice, not an accident.

5. Education-first is now overwhelmingly the real path: 50,474 of 51,078 alumni (98.8%) resolve through educations + education_areas_of_study. The degrees + majors fallback covers 604. major_code is NULL on all 53,447 education rows, so that column in the aggregate scope is effectively dead.

6. The tabs do not agree on who counts when college AND year are both set. Overview and Analytics apply the two filters independently (any credential from that college, any credential from that year — not necessarily the same one). Breakdown, Matrix and Demographics require one credential satisfying both. On dev data, college=CE + year=2015 gives 46 engaged alumni on Overview and 42 on Breakdown. This predates all of the August 2026 work and is now explicit in BaseService as #population_engaged_buids (loose) vs #eligible_buids (strict), pinned by test/services/engagement_stats/population_semantics_test.rb. Pick one. Unifying them is a one-line change; deciding which is correct is the actual work, and it will move published numbers either way.

7. TimeoutProtection cannot interrupt a running query — it never could. Staging logs show Completed 302 Found in 15005ms (ActiveRecord: 14999.2ms) immediately followed by Heroku H12 ... service=30000ms status=503 on the same request id. Timeout.timeout raises via Thread#raise, which cannot preempt a thread blocked inside libpq, so the 15s guard only fires once the query returns on its own. Users see Heroku’s 30s 503 (“Something Went Wrong”), not the intended flash. A database-level statement_timeout is the mechanism that actually works; the current guard mostly adds a misleading message. Same class of hazard as the memoized-Redcarpet bug in .claude/skills/debug/SKILL.md.

8. EngagementStatsController duplicates the service layer. It carries its own #filtered_alumni, #engaged_alumni_count, #build_filtered_engagement_scope and #capped_level_to_points_sql, which is why the v1.0.82 service fixes missed the Matrix quadrant drill-down entirely. Both copies now do the same thing, but they will drift again. #quadrant_alumni, #load_top_alumni_data and the CSV exports should move into services (EngagementStats::QuadrantService), leaving the controller to set filters and render. Also note #load_quadrant_alumni issues one query per alum in the quadrant for their activity list — negligible today (~0.04s for 73 alumni) but it is a real N+1.

9. Modal/AJAX endpoints have no timeout guard. TimeoutProtection is only: [:index], so #quadrant_alumni runs until Heroku’s 30s H12 and the modal that issued the fetch silently swallows the 503 — the spinner just stops, with no error shown. Either give these endpoints their own guard returning a JSON error, or have the client check status/content-type (see “Modals with API Calls” in the debug skill).

Also worth revisiting: TimeoutProtection’s message (“Please filter by college or year to reduce the data set”) has misdiagnosed the cause every time it has fired, and its advice makes the query more expensive. It also redirects to request.referer, and Timeout::timeout raising asynchronously mid-query is the same hazard documented for memoized Redcarpet renderers.

Related: the four EngagementStats::*Service namespace violations already listed in this backlog are unaffected by this work.

5.0 Async Digest Delivery with Synchronous Dedup

Added: February 18, 2026 (Hotfix for duplicate daily digest emails)

Context: The daily/weekly digest job used deliver_later, causing dedup race conditions. Fixed in v1.0.45 by switching to deliver_now. This is fine up to ~500 recipients.

Current State: ✅ Fixed with deliver_now (v1.0.45). Works reliably for current scale.

When to Revisit: When daily digest recipient count exceeds ~300-500.

Long-Term Fix — Synchronous Dedup + Async Delivery:

Write the EmailLog dedup record synchronously (with ‘queued’ status) before deliver_later:

log = Cp::EmailLog.log_email(champion: champion, email: champion.email,
  type: 'daily_digest', subject: 'Your Belmont Champions Update', status: 'queued')
Cp::NotificationMailer.daily_digest(champion, notifications, ...).deliver_later

Implementation Steps:

  1. Add 'queued' to Cp::EmailLog::EMAIL_TYPES status validations
  2. Update dedup check to include queued records
  3. Update EmailLoggingInterceptor to find+update existing queued log
  4. Add failed callback for Sidekiq delivery failures
  5. Add cleanup job for stale queued records (> 1 hour old → mark failed)

Priority: Low (current fix handles up to ~500 recipients reliably)


5.1 Account Merge Tool ✅ IMPLEMENTED

Added: December 4, 2025 — corrected and closed August 7, 2026 (Phase 27.1)

Idea: Tool for staff to merge duplicate alumni accounts if someone signs up multiple times before verification.

The entry was already stale when Phase 27 found it. ChampionSignupMerger had existed for some time — newest row wins, blanks fill from older rows, open text combines with date tags, sources soft-delete — with a staff UI behind it: BUID assignment with suggested alumni matches, per-BUID merge, and a bulk pass. What was actually missing was never the tool. It was the key: both entry points required a non-blank BUID, and BUID assignment is a manual staff action, so a signup with no matching Alumni row could never be merged at all.

Phase 27.1 added merge_duplicates_for_email (staff-reviewed, one pair at a time, refused when the rows resolve to different BUIDs), fixed duplicates_by_email, and surfaced email duplicates alongside the BUID ones in Signup Admin.

Phase 28.1 removed all of that (August 14, 2026) — not because it was wrong, but because a partial unique index on LOWER(email) WHERE deleted_at IS NULL removed the population it served. An address names at most one active row, so an email-keyed merge has nothing to merge and the shared-family-inbox conflict it refused can no longer arise. The tests were what forced the deletion: every one of them described a state the database can no longer hold. ChampionSignupMerger now asserts that neither merge_duplicates_for_email nor merge_all_duplicates_by_email! responds, so the key cannot grow back.

BUID is now the only merge key, and the only duplicate shape the app can produce — two different addresses on one BUID, which at 95% BUID coverage is ordinary.

Still open, deliberately: this entry says alumni accounts. Merging covers champion_signups only. Reconciling duplicates across alumni / champion_signups / cp_champions is Phase 26’s question and is blocked on the portal unfreezing.


5.2 API-First Endpoints

Added: December 4, 2025

Idea: Build Alumni Network features with RESTful API endpoints to support future native mobile app.

Status: Keep in mind during implementation. Controller actions should be API-friendly.


5.3 “Smaller Area” District Handling

Added: December 4, 2025

Status: ✅ Partially addressed in Phase 1.9.4 — dashboard shows region fallback when alum is first in their district with “You’re the first here!” celebration and invite CTA.

Remaining: Directory filtering could still benefit from region expansion for small districts.


6. Phase-Specific Deferrals

Items intentionally deferred from specific phases. Updated July 2026.

Namespace & Naming Unwind (Phase 23)

Spec: phases/phase-23/README.md. Phase 23 was re-scoped July 29, 2026 and again July 30, 2026, both times after measuring value against churn — the items below were cut deliberately.

Item Original Phase Reason Deferred Status
Cp::An:: and Cp::ChampionMember (all of sub-phase 23.5) 23.5 Cut July 30, 2026, before any work began. 389 files, 4,413 Cp:: references, 56 tables, 109 FK/index schema lines, and the phase’s only migration — whose polymorphic type-string UPDATEs are the one change in Phase 23 that fails silently (a missed column makes associations return nothing rather than error). The phase’s own ranking already answered “prevents misroutes? No” — Cp:: is a fossil but an unambiguous one, so nothing has ever been misfiled into it. The counter-argument was purely about timing (“on-hold is the only zero-conflict window”), which does not buy the largest file count and the only irreversible step in the phase. 23.4 absorbed the three consequences that mattered: resources :members, both layout renames, and the Cp::Champion::INDUSTRIES collapse. See phase-23 §8 Backlog — revisit only with the monolith split, on the same terms as lookup/
lookup/ namespace for Alumni Lookup (Lookup::, /lookup/*) 23 Measured at ~927 route-helper refs across 155 files (settings_* alone is 536), on the only active surface in the phase — so the only sub-phase that could break daily staff work — while preventing zero known misroutes, since nothing competes with root-level. Superseded by definition-by-exclusion: once signup/, signup_admin/, cp/, and alumni_network/ exist, “in the lookup” means everything else. See phase-23 Decision 6 Backlog — revisit only if the monolith split is revived
Rename people_controlleralumni_import (it is an alumni CSV import, not a “people” surface) 23 Was carried by the dropped lookup/ sub-phase; unaccompanied now Backlog
Move app/views/opportunities/app/views/shared/opportunity_fields/ (shared partials used by both the public form and the staff CMS) 23 Same — carried by the dropped sub-phase. Note it must not nest under champion_signups; that would break the staff CMS Backlog
Relocate EngagementStats::RetentionService and EngagementStats::DiscussionBoardsService out of engagement_stats/ 23.1 These are wholesale Cp:: analytics living in engagement_stats/ — a live violation of CLAUDE.md’s “Never put Cp:: analytics in engagement_stats.” Relocating them is held-surface work and would expand 23.1 beyond enforcement. Allowlisted in the 23.1 architecture-test baseline instead. 23.4’s held approval did not cover them — it was scoped to the rename, and folding ~45 more references into a 1,401-reference diff would have wrecked reviewability Backlog — allowlisted, ratchet down later
Merge statistics into engagement_stats 23 Investigated: they are not duplicative — statistics is demographic/grad-year bucketing, engagement_stats is activity scoring Rejected, not deferred
Relocate the three OnboardingInsights::* services and ActionItemsService 23.1 Found while capturing the 23.1 baseline, not during planning. All four are root-level (nominally Alumni Lookup) but query Cp::ActionItemsService alone holds 20 references, the largest single coupling in the repo. Same disposition as the engagement_stats pair: allowlisted, not moved, because the fix is held-surface work Backlog — allowlisted, ratchet down later
Split AlumniHelper’s signup-domain methods into a shared presenter 23.3 Nine of its methods are signup presentation (champion_signup_lean_*, prospect_champion_seal, has_completed_champion_signup?) living in a root-level helper, which under definition-by-exclusion reads as Alumni Lookup. Nothing is broken — Rails’ global helper inclusion means the location has no functional effect — so moving nine methods and their tests is real churn for a documentation-shaped benefit. 23.3 adds a header flagging them as reusable instead Backlog — inventory recorded, not moved
A canonical US state list 23.3 state appears on zip_codes and address fields with no shared list. Nothing is broken today, and creating a constant with no caller is an orphan (CLAUDE.md rule 11). Filed so the second app to need a state dropdown finds a decision instead of a blank page — this is how industry diverged Backlog — create on first real caller, in the shared layer
Markdown link checker in CI 23.6 23.6 swept all of docs/**, not just docs/planning/**: 115 broken relative links → 34. 68 were de-linked in historical phase specs (prose kept — see the policy below), 11 repointed at targets that had moved, 2 were Phase 23’s own. Ad-hoc fixing does not stop regression; a checker does. Two things it must tolerate or it cries wolf on arrival: qa/PHASE_21_LAUNCH_GUIDE.md’s documented javascript:alert(1 XSS test payload, and absolute app routes in the compliance drafts (/privacy, /community-guidelines, /cookie-policy) which are Rails paths, not files Backlog
Write the two missing canonical source documents 23.6 19 of the 34 residual broken links point at ALUMNI_CHAMPIONS__ONBOARDING_PHILOSOPHY.md and HOPE_TRANSFORMS__FUNDING_PRIORITIES.md, both promised by alumni-network/source/README.md and neither ever written. This is a gap in the source corpus, not link rot — and CLAUDE.md forbids inventing theological language, so they cannot be filled by an agent. Left linked deliberately: the links are the record of what is owed Backlog — needs an author, not a fix
Monolith split into five apps + API 23 Previously proposed and rejected; worth reapproaching after namespacing, since clean namespaces are step one either way. The 23.1 allowlist doubles as the real coupling inventory. Note that four of the five apps are now namespaced by directory; the fifth (Lookup) is defined by exclusion, and the split is the one scenario that would justify reviving both lookup/ and 23.5 Backlog — revisit with data after a few phases of ratcheting
Rename app/helpers/alumni_network/admin_helper.rb’s generic methods into shared 23.4 render_sort_header, format_engagement_score and completion_badge_class are surface-agnostic table/formatting helpers that happen to live in a held tree. Moving them is a shared-layer write, which is allowed — but they have exactly one consumer today, and a shared file that cannot name a second consumer is misfiled, not shared (CLAUDE.md). Filed so the second consumer finds a decision Backlog — move on first cross-surface caller

Returning Alum Identity (Phase 27)

Spec: phases/phase-27/README.md. Added August 7, 2026 while building 27.2.

Item Found Reason Deferred Status
One-time emailed code (OTP) alongside the magic link 27.2 QA Raised as a possible replacement for magic links when sign-in didn’t feel like signing in. It wasn’t the cause — an OTP that doesn’t establish a session behaves identically, and the real gap was #show never adopting the session (fixed in 27.2). Where OTP genuinely wins is cross-device: read the email on your phone while working on a laptop, which a link cannot solve. Costs a new endpoint, a code store or signed code, its own throttle, and funnel friction Shipped in 27.7 on exactly the predicted trigger — cross-device. ChampionSignupSigninCode: 15 minutes, one use, five attempts, its own signin-code/ip throttle, riding in the SAME email as the link so there is no second send. 27.8 then made it the lead and demoted the link to a fallback: the link opens the hub on whatever device reads the mail, which loses the reader’s place; the code returns them to the browser they left. The “funnel friction” cost priced in here did not materialize, because the code is optional — the link still works
Slim secondary header bar for signed-in state 27.2 interview The sign-in/sign-out affordance is currently grouped with the Belmont logo in the existing sticky bar. A thin strip below it has more room, at the cost of vertical space on a conversion funnel. Pinned deliberately rather than built Revisit in 27.3, when “Not you?” and the recognized tier add a third and fourth state to that group
Reconcile last_seen_at with the denormalized last_return_visit_at stamp 27 spec §5 Reconciled at the Phase 27 wrap (August 12, 2026): they do not merge, and the note that framed them as duplicates was wrong. ChampionSignupSession#last_seen_at describes a device and only exists for someone who ticked “keep me signed in” — so a returned scope built on it would silently under-count every alum who did not, which is most of them. champion_signups.last_return_visit_at describes a person, exists for everyone, and has six consumers (returned / never_returned / returned_or_enriched scopes, Signup Admin’s GREATEST(...) sort, the return-activity badges, and Alumni::SignupProfile#returned_at). The 27.3 comment calling last_seen_at “a better return-activity signal” is corrected in champion_signup_session.rb. Nothing to remove ✅ Closed — no work
Vendor the five jsDelivr JavaScript modules locally 27.6 Found while writing the privacy policy, and it is the last third-party origin on the alum-facing surface. config/importmap.rb pins flowbite, trix, chart.js, chart.js/auto and @kurkle/color to cdn.jsdelivr.net, and javascript_importmap_tags emits <link rel="modulepreload"> for each — an eager fetch on every page load, including /sign-up. Removing the preload does not help: application.js imports flowbite and trix unconditionally. Two distinct exposures: jsDelivr sees every visitor’s IP (the same §3.6 fact pattern as the fonts), and third-party JavaScript executes on the page where alumni type their contact details. phase-27 §9 scoped this out (“note the link; do not pull it in”), so 27.6 corrected the claim instead — both policies now name jsDelivr. Fix is bin/importmap pin --download for the five pins Backlog — do this with CSP enforcement, which it makes materially cheaper; pinned as a ratchet in signup_compliance_test.rb so no new origin can be added quietly
Self-host Material Icons (and Sofia Sans) 27.6 Google Fonts turned out to be in seven layouts, not the one §3.6 named — but only signup and application loaded Montserrat. signup_admin, tools and settings load Material Icons; application also loads Sofia Sans and a Chart.js copy from cdnjs.cloudflare.com. These are staff-only surfaces, so the privacy stakes are lower than the alum-facing one 27.6 closed, and an icon font is a bigger job than a text font (ligature CSS, codepoint coverage) Backlog — lower priority than the jsDelivr row above
The two held layouts still load Google Fonts 27.6 layouts/alumni_network (Montserrat + Material Symbols) and layouts/alumni_network_admin (Material Icons) were left untouched — editing them is a held-surface write. The work is deleting three lines and rendering shared/_fonts Do at portal unfreeze; shared/_fonts already exists and names its consumers
cp/profile/_completion_banner leaks its own doc comment onto the page 27.8 Found by the repo-wide ERB comment scanner 27.8 added, which is the second live instance of the bug it was written for: the header comment shows Usage: <%%= render ... %>, so the comment closes at that tag and lines 7–15 (the weighted-scoring table) render as page copy on the portal’s profile edit page. The fix is deleting one ERB tag from a comment. Not done because app/views/cp/** is a held surface and CLAUDE.md rule 2 requires explicit approval — and because a one-line fix is not worth spending an approval on a frozen page nobody is loading. test/views/erb_comment_integrity_test.rb excludes held trees by path and names this instance in a comment, deliberately rather than adding it to a baseline: a named exclusion reads as debt, a baseline entry reads as clean. Delete the exclusion in the same commit that fixes it Do at portal unfreeze — or sooner with one line of approval
Phase 27 is not in roadmap_controller.rb 27.2 Resolved in 27.1, which added all six 27.x entries. Phase 26 is still absent — CLAUDE.md rule 7’s source of truth does not know that phase exists, and 26 is the one blocked on the portal unfreezing, so nothing else will surface it Backlog — add Phase 26’s roadmap entry

Signup Funnel Position (Phase 28)

Spec: phases/phase-28/28.3-step-one-and-prefill.md. Added August 14, 2026 while building 28.3.

Item Found Reason Deferred Status
status is derived from field presence, not funnel position — replace it with an explicit furthest_step column 28.3 This has now caused two separate regressions and will cause a third. ChampionSignup#calculate_status infers where someone is in the funnel by asking which fields are filled, so moving a field between steps silently changes everyone’s status — and before_save :ensure_status_updated re-derives it on every save, so no data fix survives. (1) ZIP moved into step 1 around November 2025 and every v2 signup became “Completed” on creation. (2) Phase 25.5 then collapsed the Alumni Champion Signup filter from six options to two because of it (alumni.rb:362) — a derived field nobody trusted quietly removed four filters from a staff tool. 28.3 repaired the rule (top rung now needs a ZIP plus evidence of progress) but not the mechanism. The durable fix is an explicit furthest_step column, backfilled across 244 rows, with every reader of completed? / scope :completed / status_label re-pointed. Deferred for cost and blast radius — it touches Signup Admin reporting and Alumni prospect logic — not because the derived version is sound Backlog. Do this before moving another field between steps. Sequence it ahead of any funnel restructure, not after
Revisit Phase 25.5’s collapsed Champion Signup filter 28.3 25.5 cut four of six filter options on the reasoning that “every v2 signup is complete on creation, so four of those options could only ever surface v1 stragglers.” That premise is now false — after 28.3 a signup reports its real position, so “signups in progress” distinguishes something again. The options were removed, not hidden, so restoring them is real work and wants the furthest_step row above first Backlog — blocked on nothing, but cheaper after furthest_step
The hub’s missing-ZIP banner is now the primary ZIP collection path 28.3 ZIP moved off step 1, so every new signup lands on the hub with the amber “We’re missing where you live” banner showing. That banner was built for an edge case and is now the main road: worth measuring whether it converts, and whether “Where you are now” is the right destination for someone who came to type five digits Backlog — measure before changing anything

Identity & Write Direction (→ Phase 26)

Raised July 30, 2026 while planning 23.3. These are not backlog items to forget — they are drafted as Phase 26. Listed here so a search for the symptom finds the phase.

Item Found Status
Two contradictory write policies for the same fieldsCp::SyncChampionToAlumni writes alumni; Alumni::SignupProfile never writes and resolves at read time. Same alum, same ZIP, different outcome depending on which app they used 23.3 planning Phase 26, Decision A. 23.3’s vocabulary map removes the last visible sign of this conflict — do not treat 23.3 as having fixed it
Cp::SyncChampionToAlumni has 17 test references and zero application callers — the only bridge from a portal edit to crm_data_changes. Dev counts: change_source: "champion_portal" = 1 row, Cp::ProfileChange = 327 23.3 planning Phase 26.3. Harmless while the portal is on hold; the moment it unfreezes, alum-reported changes silently stop reaching Advancement Services
cp_profile_changes is superseded by crm_data_changes — the newer table carries all three subject FKs, natural keys, source discrimination and export lifecycle 23.3 planning Phase 26.4. ⚠️ It lost its forcing moment. This was filed on the assumption that 23.5’s cp_*an_* migration would force the question — port the table and you make it permanent by inertia. 23.5 was cut, so no migration is coming and nothing will surface this on its own. Phase 26.4 has to raise it unaided
“Newest active signup” is a private policy — correct, but known only to Alumni::SignupProfile. A second consumer picking differently produces a different answer for the same person, silently 23.3 planning Phase 26.1 (Alumni::Identity)
Field gaps: alumni has no street column; champion_signups has no preferred-name column; champion_signups has both address and street; cp_champions.state is limit: 2 while others are unbounded 23.3 planning 23.3 records them in IdentityField::MAP; Phase 26.5 resolves them

Namespace & Naming Unwind (Phase 23) — continued

Measured coupling inventory (23.1 baseline, July 29, 2026). CP_REFERENCES_OUTSIDE_HELD_TREES in test/architecture_test.rb is the live version of this list; it may shrink but not grow.

Alumni Opportunities (Phase 21)

Feature doc: docs/features/ALUMNI_OPPORTUNITIES.md. Everything below was a deliberate MVP boundary, not an oversight.

Item Original Phase Reason Deferred Status
belmontalum.com / Cp:: Opportunities surface 21 Deferred with the whole portal. Models are namespace-neutral, so this is an additive nullable champion_id column + a second controller on the same tables — a migration, not a rewrite (spec §7) Blocked on the portal resuming
Anonymous view analytics 21.2 Only submissions are tracked in MVP; an anonymous view has no signup to hang a ChampionSignupEvent on Backlog
Public index page listing all opportunities 21.2 Staff prefer targeted links; no index means an opportunity only gets the traffic staff send it. 21.7’s listed flag is the piece that would make one safe — an index would show listed forms only Backlog
Per-response status workflow (applied → confirmed → completed) 21.4 MVP is lead capture + notify + export; staff follow up offline. reviewed_at is read/unread for the badge only, explicitly not a fulfillment state Revisit if staff need fulfillment tracking
Export across all opportunities 21.4 Answer keys differ per opportunity, so a union CSV is sparse and grows a column with every new form. Export is always scoped to one Won’t build as specced
Emailing applicants a confirmation of their own submission 21.2 Shipped in 21.8 as send_receipt_email, opt-in per opportunity. The boundary held while the email could only repeat the page; structured event details gave it something a person needs later, in a place they can find it Done
“Add to calendar” (.ics) on the thank-you page and the receipt email 21.8 The natural follow-on now that starts_at is a real timestamp. Only meaningful for an opportunity with a precise time, which an ongoing one deliberately has none of Backlog
Cover images on opportunity cards 21.9 The event card’s strongest visual element and the one thing 21.9 did not carry over. Needs an Active Storage attachment plus a staff upload field — a real feature, not a restyle Backlog
A “past opportunities” view 21.9 Expiry hides an opportunity outright (21.8); the Alumni Network keeps past events browsable. Nothing has asked for it here Backlog
Per-opportunity timezone 21.8 Every opportunity is a Belmont event, and config.time_zone is Central. Revisit if a virtual one draws a national audience Backlog
Recurring / multi-date opportunities 21.8 An ongoing opportunity covers “no fixed date” and a range covers “these days”. A real recurrence model — separate dates, per-date capacity — is a different feature, not a column Backlog
Multi-select field type 21.5 Shipped in 21.7 as checkbox. The fence held as designed: a code change, and now available to every opportunity Done
New field types beyond the six (file upload, number, …) 21.5 A code change by design — that’s the fence that keeps this a row editor rather than a form builder. Once added, available to every opportunity Backlog
Conditional / branching form logic and custom per-field validation 21.5 Explicitly out of scope; that’s a form builder Won’t build
SMS opt-in on the opportunity form 21.2 Phone stays a plain optional field; keeps the form short Revisit if staff start texting opportunity leads
Staff notification batching 21.4 One StaffNotification per response, and as of 21.7 one email per response on opted-in forms Trigger: a widely shared link floods the queue or an inbox despite the rate limit → collapse to “N new responses” per opportunity per hour
Drag-handle field reordering after responses exist 21.5 Allowed (order is display-only and doesn’t affect answer keys), but simple up/down is enough Backlog
rake opportunities:export/import for staging→production content promotion 21.5 Re-entering through the CMS on production takes ~10 min and re-verifies the editor. Build only if re-entry proves annoying Backlog — see launch guide §2
Global/default notification recipient list 21.7 Rejected at interview: recipients are per-opportunity by nature (board vs. newsletter vs. general), and a global default would mean a widely shared link mails somebody by default — the exact inbox-flood risk 21.4 wrote its no-email rule to avoid Won’t build as specced
Move the staff response detail’s inline signup → alumni walk onto OpportunityResponseDigest 21.7 The view does the same three-hop lookup the digest now does, inline. A second consumer is the argument for the shared service; deliberately not folded in during 21.7 to keep the diff scoped Backlog
Revisit Cp::WelcomeContentGenerator.markdown_to_html sanitization 21.5 It sets neither escape_html nor filter_html and ends in .html_safe. Safe for its own developer-authored YAML input, but worth a separate look. Out of scope for Phase 21, which shipped its own OpportunityMarkdown renderer instead Backlog

Alumni Employment Data (Phase 24)

Importer doc: docs/features/CSV_IMPORTERS.md. Spec: phases/phase-24/. All deliberate scope boundaries.

Item Original Phase Reason Deferred Status
Employment history 24 The source feed emits current employment only. A history table would be populated with fiction, not data Won’t build without a history-bearing source
Employer entity resolution / canonical employers table 24 Fuzzy search answers “who works at X” without committing to a canonical table. See §1.8 above for the full write-up Backlog
Staff editing employment in the app 24 Manual edits would be silently reverted by the next import unless field protection is designed first — that is CrmDataChange territory Deferred to Phase 22
Industry classification 24 Not in the source export; would need its own source Backlog
Employment columns on alumni 24 alumni is Belmont-provided source data and stays clean; a joined table is the right shape for one-to-many anyway Won’t build
Employment on any belmontalum.com / Cp:: surface 24 Held surface Blocked on the portal resuming
Wiring Phase 22.4’s signup employment diff 24 This phase provides the data; Phase 22 consumes it. 22.4’s “alumni has no employment to diff against” premise is now false — see phase-24 §2 In scope for Phase 22

Signup Data in the Lookup Portal (Phase 25)

Spec: phases/phase-25/. All deliberate scope boundaries from the July 29, 2026 planning interview.

Item Original Phase Reason Deferred Status
Duplicate-signup warning on alumni#show 25 Not selected in planning. One canonical signup (newest active) drives every derived value, so acting on stale data would take an alum with two signups where the older one is the fuller — rare enough not to earn a banner Closed — Phase 28.1 (August 14, 2026). The premise is gone: an address names at most one active signup, so the “two signups, older one fuller” case it warned about cannot occur by email. The BUID case (two addresses, one alum) remains and is already banner’d on the signup record itself
signup_version / signup_date / last_return_visit / last_enriched CSV columns 25 Not selected. The history high points land on the profile, where a human is reading one record; an export column would carry them for 49K rows nobody filters on Backlog
Consent state + held-CRM-update count on the profile 25 Not selected. Both are already visible in Signup Admin, which is where the person acting on them works Backlog
Free-text city / ZIP search 25 Out of scope — the location search surface is the district autocomplete, and Phase 25 made that see signup data. A second location input is a different feature Backlog
Recomputing v1 interest spectra for list views 25 A ChampionQuizService call per row, 50× a search page and 49,000× an export, to populate a decorative chip. The profile page recomputes because it handles one record Won’t build as specced
Writing signup affinities into alumni_affinities 25 The CRM is the system of record for confirmed groups. Signup-reported ones reach it through the crm_data_changes export and come back on a sync — writing them locally would manufacture CRM facts Won’t build — by design
Removals of signup-reported affinities 22.4 / 25 Unchanged from Phase 22.4: a form that omits a group is silence, not a departure Backlog — by design

Education Data Model Migration

Item Original Phase Reason Deferred Status
(None from 18.2 deliverables) Phase 18.2 All 18.2 deliverables shipped; remaining work is planned in 18.3-18.7 (not deferred) Tracked in phase plan
Swap remaining secondary CP partials to Alumni::EducationProfile (cp/profile_wizard/_step_confirm_education, cp/careers/_career_connect_cards, cp/leadership/{community,welcome_message,members}) Phase 18.4 Out of scope for 18.4 (core profile/search paths only); these views still render via alumni.degrees and work correctly In scope for Phase 18.6
Migrate Api::AlumniController V1 serializer to call Alumni::EducationProfile directly (currently preserved via Entry#as_json contract) Phase 18.4 Planned for 18.5 (API backwards compatibility phase) Tracked in Phase 18.5
Rename AlumnusEngagementDecorator -> AlumniEngagementDecorator (alumni is singular and plural per coding standards) Phase 18.4 Out of scope for read-path work; pure rename Backlog
V2 API enrichment block — optional ?include=educations (or /api/v2/) that exposes the full educations + areas_of_study structure for downstream apps wanting richer data than the 18.4 check-in blob Phase 18.5 Out of scope for backwards-compatibility phase; V1 contract preserved with strictly-additive _source field Backlog — candidate for V2
Alumni search filter “Data source” (education / degree_fallback / none) with a matching column on results — exposes the legacy-fallback roster in the UI. Stopgap rake task alumni:legacy_fallback_buids ships in 18.5 Phase 18.5 Belongs with UI rollout phase where alumni search/show pages are already being touched Deferred to Phase 18.9 UI rollout
Top Minors / Concentrations breakdown chart on stats page (concentration_level IN ('minor','concentration')) Phase 18.6 Top Majors filtered to concentration_level = 'major' for parity with legacy; minor/concentration is purely additive Backlog — no phase target
Remove side-by-side ?source=legacy\|education toggle and Coverage banner from /statistics and /engagement_stats Phase 18.6 Toggle exists only to validate parity during 18.6/18.7; legacy view must stay live until exports cut over ✅ Shipped in Phase 18.9 (Group B)
Collapse aggregate_stats_v2:* cache namespace back into engagement_stats_* Phase 18.6 Separate namespace during cutover so legacy and new caches don’t fight ✅ Shipped in Phase 18.9 (Group B)
Drop legacy degrees table and associated model/scope code Phase 18.6 Read path fully decommissioned in Phase 18.9 (all reads via Education; major filter migrated to education_areas_of_study.major_code). degrees is no longer part of the site except the intentional EducationProfile→degrees fallback for alumni without educations rows, which goes inert once the production education import lands. Table retained for legacy/rollback only and CAN be dropped. colleges and majors are NOT removable — they remain active reference tables (colleges is the canonical college-code→label source; majors backs the major dropdown, community naming, and banner-import validation) degrees deferred to post-18.9 cleanup tag (coverage ≥ 99%); colleges/majors retained as reference
Alumni.filter_by_major still reads legacy degrees (educations carry no major_code) Phase 18.9 Resolved: education_areas_of_study.major_code now carries the CRM “Area of Study: External Id” (program code, valid for majors/minors/concentrations). filter_by_major matches on it via educations ✅ Shipped in Phase 18.9
Drop denormalized enrollment fields from alumni (current_school_code, current_program_desc, intended_degree_code, expected_graduation_year) Phase 18.8 Shipped as deploy 1 of 2 with read-path migration first; destructive schema drop intentionally postponed until post-import validation Deferred to Phase 18.8 deploy 2
Remove Csv::AffinaquestContactImporter#clear_enrollment_fields_if_awarded! Phase 18.8 Legacy cleanup deferred until deploy 2 when alumni denormalized enrollment columns are dropped Deferred to Phase 18.8 deploy 2

Champion Signup v2.0 (Phase 19)

Item Original Phase Reason Deferred Status
Full affinity browse/search UX on public signup (affinity_codes, lifestage_interest, vocation) modeled after /profile/wizard/affinities Phase 19.2 follow-up Light interest capture (checkboxes + note) shipped in 19.3; full affinity dataset deferred to keep scope focused Deferred to Phase 19.4
Full role interaction on public signup (/sign-up?step=role) including direct-pick and quiz outcomes Phase 19.2 follow-up Stub page shipped for manual path testing (You → Belmont → Interests → Job → Role) before quiz/result work was restored ✅ Done (Phase 19.3)
Final post-implementation revision pass before closing Phase 19 Phase 19.5 wrap 19.1-19.5 deliverables are complete, but final polish/revision scope will be confirmed after feedback Pending decision: close as-is or define Phase 19.6
~3-day nudge email for signups who completed step 1 but none of the 3 optional sections Phase 19.7 Deferred (2026-06-25) until the delayed-job/queue story is confirmed; would also want a nudge_sent_at column. 19.7 ships only the immediate welcome email + profile link Backlog — revisit with delayed-job/scheduler decision
Manual 4-slider direct interest-rating path (alternative to the quiz on the Interests step) Phase 19.7/19.8 Raised during 19.7 planning; quiz-only ships first. Add if quiz drop-off warrants a faster self-rate path Backlog — pending quiz drop-off data

Feature Flags & Phased Launch (Phase 20)

Item Original Phase Reason Deferred Status
Gate news/events/photos content in the weekly digest mailer (cp/notification_mailer/weekly_digest) by feature flag Phase 20.2 Digests are a separate notifications concern; with content flags off at launch there’s little new content to surface, and any links resolve to the dashboard via the controller guards rather than erroring Backlog — revisit if a flag stays off long-term

Authentication & Signup

Item Original Phase Reason Deferred Status
Apple Sign In Phase 1.3 Focus on Google first Backlog
Facebook OAuth Phase 1.3 Requires FB Developer App Backlog
Microsoft / Entra sign-in Phase 28.2 Belmont is a Microsoft shop (confirmed Aug 14, 2026), so the 20 belmont.edu / bruins.belmont.edu signups can never match on a Google identity. Deferred because it buys them little: those addresses belong to current staff and recent grads who will click with a personal account anyway, and 28.2’s BUID fallback already resolves that to the right record. It would only matter for someone whose sole email identity is the university one. 28.2’s resolver makes it cheap when wanted — a config entry, a microsoft_uid column, one more branch Backlog — revisit if step-4 misses concentrate in that domain
SSO account linking Phase 1.3 Complexity (edge case handling) Resolved for the signup surface — Phase 28.2. The edge cases that deferred it are enumerated as the four-way resolver. Row stays open for the Lookup/staff surface
Account settings (connect/disconnect Google) Phase 1.3 ✅ Phase 1.9.1

Profile & Data

Item Original Phase Reason Deferred Status
Sync imported affinities to profile Phase 1.4 Requires BUID verification first Backlog
Affinity export mechanism (cp_affinities → crm_data_changes) Phase 1.4 Was “awaiting Affinaquest export format” — but that blocked on the held portal’s cp_affinities. The Lookup-side alumni_affinities table (unique on buid + affinity_code) makes signup-reported orgs diffable today Resolved — Phase 22.4 (July 28, 2026). ChampionSignupCrmLogger logs additions under source_table: "affinities"; they export with everything else at /settings/affinaquest/crm_changes
Track affinity adds/removes for Advancement Services Phase 1.4 Same unblock. Adds only — absence from a signup form is not an assertion that someone left an organization, so removals stay deferred by design Adds resolved — Phase 22.4 (July 28, 2026). Removals remain Backlog by design, not by omission — a form that omits a group is silence, not a departure

Discussion Boards

Item Original Phase Reason Deferred Status
@Mentions in discussions Phase 3 Complexity (name resolution, notifications) Backlog
Discussion board search Phase 3 Full-text search across posts/comments Backlog
Discussion bookmarks Phase 3 Save posts for later reading Backlog
Mute discussion threads Phase 3 Stop notifications for specific threads Backlog
Additional sort options (Top, Controversial) Phase 3 Beyond MVP needs Backlog
Multiple images per post Phase 3 Currently single image Backlog
User temp bans Phase 3 24hr/7day/permanent bans by CL If needed
Advanced discussion analytics Phase 3 Exportable reports beyond basic metrics Backlog

UI & Polish

Item Original Phase Reason Deferred Status
Profile wizard header alignment Phase 4 Low priority — vertical rhythm on step 3 Backlog
Message reactions sizing Phase 4 Minor polish; already functional Backlog

Membership & Verification

Item Original Phase Reason Deferred Status
Almost Alumni BUID verification Phase 5.2 Students have BUID but no degree records Discussion
Degree import hook (Almost Alumni → Alumni upgrade) Phase 5.2 Automatic upgrade when degree recorded Pre-MVP
Almost Alumni upgrade email notification Phase 5.2 Notify when upgraded from Student to Alumni Pre-MVP
Admin “Recheck Education” action Phase 5.2 Staff action to manually trigger upgrade Pre-MVP

Public Signup v2.0

19.1-19.5 implementation is complete. Final phase closeout is intentionally pending a short revision pass and may be tracked as 19.6 if scope exceeds minor copy/polish.

Connections & Messaging

Item Original Phase Reason Deferred Status
Conversational connect scripts Phase 10 Pre-written message templates Backlog
Connection notes tracker Phase 10 Private notes on connections Backlog
Connection expiration / dormancy Phase 10 Auto-flag inactive connections Backlog
Proactive match suggestions Phase 10 System-suggested connections Backlog
Delayed / queued sends Phase 10 Schedule messages for later Backlog
Connection-based recommendation changes Phase 10 Adjust algorithm based on connection graph Backlog
Group connections Phase 10 Connect with multiple alumni at once Backlog

Champion Role & Engagement

Item Original Phase Reason Deferred Status
Activity-gap personalization Phase 11 Prioritize ideas based on activity gaps Backlog
Success metrics dashboard (role ideas) Phase 11 Admin dashboard with role adoption metrics Backlog
Multi-role support Phase 11 Allow multiple Champion roles; currently single-role Backlog

Source Alignment & Language

Item Original Phase Reason Deferred Status
Generosity-as-formation language (F-03) Phase 12 Add giving framing to contribution touchpoints Backlog
Source guardrail phrases (F-04) Phase 12 Incorporate prescribed vocabulary more broadly Backlog
i18n infrastructure (F-06) Phase 12 Extract user-facing strings to i18n YAML Backlog
Roles woven into experience (F-01) Phase 12 Weave roles into dashboard, empty states, notifications Backlog

Content Submissions

Item Original Phase Reason Deferred Status
Photo album submissions Phase 14 News/events done first; albums deferred Backlog
Rich text submission editor Phase 14 Plain text + image for MVP Backlog
Champion self-edit after submission Phase 14 Must ask staff via thread currently Backlog
Item Original Phase Reason Deferred Status
CSP enforcement mode Phase 16.5 Deployed in report-only mode first; switch to enforcement after log monitoring confirms no violations. 27.6 made this cheaper by removing fonts.googleapis.com and fonts.gstatic.com from the alum-facing surface — and identified the remaining blocker: cdn.jsdelivr.net and cdnjs.cloudflare.com are still in script_src because five JS modules are pinned there. See the jsDelivr row under Phase 27 deferrals Backlog — bundle with vendoring the jsDelivr pins
GDPR rights management Phase 16 No EU users in beta audience; deletion/export infrastructure is in place Backlog (If EU expansion)
Cookie consent banner Phase 16 Resolved by Phase 27 Decision E, implemented in 27.3/27.6 — not deferred, decided. The old trigger (“if tracking added”) was the wrong condition and would have read as pre-approval for a case it never considered: 27.3’s 6-month remembered-device cookie is not tracking, so it never trips that trigger, yet it is exactly the non-strictly-necessary cookie the deferral was implicitly guarding against. Consent is collected at the point of use instead — an unticked “Keep me signed in on this device for 6 months” checkbox on /sign-in. Still no banner, because there is no banner-worthy category: no analytics, no pixels, no ad tech, no sale or sharing. A banner on a conversion funnel is a tax, and a badly-built one is its own liability Won’t build — revisit only if an actual analytics or advertising cookie is ever added, which would be a new decision, not this one

Progressive Dashboard & Journey Tiers

Item Original Phase Reason Deferred Status
Compact mobile hero (condensed height for returning users) Phase 13.2 Low priority polish Backlog
“What’s new” contextual sub-line in hero Phase 13.2 ✅ Delivered in Phase 13.8 (What’s New summary bar below hero) ✅ Complete
Mobile sidebar fold (2 items above fold, rest collapsed) Phase 13.2 UX polish; desktop parity first Backlog
Card standardization (unified card component across tiers) Phase 13.2 Refactor; no new functionality Backlog
Icon color variation per section type Phase 13.2 Visual polish Backlog
Activity feed (discussions + news + photos combined) Phase 13.3 ✅ Delivered in Phase 13.3 (ActivityFeedService with scoring engine) ✅ Complete
Nudge engine (gentle CTA after Champion-like actions) Phase 13.4 ✅ Delivered in Phase 13.7 (Champion Nudge Engine + Role Microsite) ✅ Complete
Personalization (activity-gap based content ordering) Phase 13.5 Requires activity history Backlog
Stage transition animations & coach marks Phase 13.6 Polish layer Backlog
Champion Dashboard Layout (separate _stages/champion.html.erb) Phase 13.5.1 Existing champion/active_champion stage partials sufficient Backlog
“Your Impact” sidebar widget (connection/community/post counts) Phase 13.5.2 Nice-to-have; can add in future iteration Backlog
Dismissable contextual prompts (seen_tooltips JSONB pattern) Phase 13.5.3 Not needed for initial launch Backlog
coaching_in_digest notification preference toggle Phase 13.5.3b Always shows for Tier 2+; add opt-out later if requested Backlog
Champion scoring boost in directory/recommendations Phase 13.5.4 Requires tuning; defer to avoid unintended ranking effects Backlog
Champion opt-in nudge via journey stage Phase 13.7 ✅ Delivered in Phase 13.7 (dashboard nudge card on engaged_member stage) ✅ Complete
Bulk approve/decline Phase 14 One at a time currently Backlog

Career Center

Item Original Phase Reason Deferred Status
Video career resources Phase 7 PDF-first; add video if demand emerges Backlog
OCPD content CMS Phase 7 Hardcoded section sufficient; content changes rarely Backlog
Resource ratings/feedback Phase 7 Gauge demand first Backlog
In-app PDF viewer Phase 7 Browser handles PDF viewing Backlog

Notifications

Item Original Phase Reason Deferred Status
Push notifications Phase 1 ✅ Implemented (Phase 8.6) — VAPID web push for staff (Lookup Portal) and Champions
Onboarding email drip series Phase 9.6 Portal needs more users before optimizing activation flow Backlog (High)
SMS/phone platform (Twilio) Phase 1.9 Cost/complexity for beta Backlog

Seeded Questions

Item Original Phase Reason Deferred Status
Seeded question daily scheduler job Phase 9.2 Auto-publish on schedule Backlog
Seeded question A/B testing Phase 9.2/9.3 Test variants for engagement Backlog
Seeded question response analytics Phase 9.3 Engagement metrics per question Backlog

Staff Analytics (Phase 6)

Rationale for deferral: Build up the user base first — analytics are more valuable once there is meaningful activity to measure. Phase 9.4 covers onboarding metrics. Full spec in phases/phase-6/README.md.

Item Original Phase Description Priority
Activity Metrics Dashboard (6.1) Phase 6 Staff dashboard: total Champions, active users, growth trends, event/story submission counts, time period filters Medium
Region Health Dashboard (6.2) Phase 6 Per-region health scores (active %, events, posts, CLC assigned), color-coded table with drill-down Medium
Reports & Exports (6.3) Phase 6 Monthly summary, region detail, CLC performance report templates; PDF/CSV export; scheduled CRM export Medium

7. Database Cleanup

7.1 Drop cp_profile_changes Table

Added: December 9, 2025

Context: The cp_profile_changes table was created in Phase 1.1 as a changelog for CRM exports. It has been superseded by the unified crm_data_changes table.

Current Status:

Migration Plan:

  1. ✅ Create new crm_data_changes table (Affinaquest Phase 1)
  2. ✅ Update profile editing to use crm_data_changes
  3. ⏳ Drop cp_profile_changes table
  4. ⏳ Remove Cp::ProfileChange model

Target: Next cleanup sprint


8. Future Enhancements

8.1 Event RSVP Functionality

Added: January 14, 2026

Idea: Built-in RSVP system for Alumni Network events, rather than relying on external RSVP links.

Features:

Why Deferred: Current system uses external RSVP links to simplify MVP. Built-in RSVP adds complexity around capacity management, notifications, and cancellations.

Priority: Medium — valuable for engagement tracking but external links work for now


8.2 In-App Notification System ✅ IMPLEMENTED

Implemented: Phase 8 (Notifications & Digests) — Bell icon, unread badge, notification dropdown, per-type preferences, daily/weekly email digests.


8.3 Community Email Notifications ✅ IMPLEMENTED

Implemented: Phase 8 (Notifications & Digests) — New members, new events, discussion activity notifications with per-type preferences and digest scheduling.


8.4 Sync Imported Affinities

Added: December 8, 2025

Idea: For verified alumni (those with BUID linked), show their CRM-imported affinities from alumni_affinities and allow bulk add to their cp_affinities profile.

Flow:

  1. Alum is verified (BUID linked to account)
  2. System checks alumni_affinities for their BUID
  3. If affinities exist, show “We found X groups from your time at Belmont”
  4. Alum can review list and “Add all” or select individual ones
  5. Selected affinities copied to cp_affinities

Priority: Medium — nice engagement boost, but self-selection works fine for MVP


8.5 Almost Alumni BUID Verification

Added: January 2026 (Phase 5.2 implementation)

Context: Current students may exist in the Alumni table with a BUID but have no degree records.

Current Behavior:

Future Enhancement Options:

  1. Verify via @belmont.edu email
  2. Banner/enrollment data integration
  3. Self-service graduation claim
  4. Batch upgrade job when degrees are added

8.6 SMS Platform

Added: December 23, 2025 (Phase 1.9 planning). Deferred: Cost/complexity for beta.

Future Value (High):

Implementation Notes:


9. Shared Layer Follow-Ons (Phase 23.3)

Filed July 30, 2026, while building the shared layer. Each was found in the tree and deliberately left alone — see 23.3-shared-layer.md §13.

9.1 Split the signup methods out of AlumniHelper

Deferred from: 23.3 §7.3 — inventory only.

Nine methods in root-level app/helpers/alumni_helper.rb are signup-domain presentation, not Lookup presentation: champion_signup_lean_key/name/classes/icon, prospect_champion_seal, prospect_champion_seal_image, has_completed_champion_signup?, alumni_champion_champion?, affinity_chip_classes. Under Decision 6, root-level reads as Alumni Lookup, so the file’s location asserts something false.

Why it stayed: nothing is broken, all callers work, and include_all_helpers means location has no functional effect. Moving nine methods with their tests is real churn for a documentation-shaped benefit. A header now names them instead.

Do it when: something else already touches this file, or Signup Admin gains its own lean rendering (at which point the shared home stops being cosmetic).

9.2 US state taxonomy

Deferred from: 23.3 §6.4 — no caller, so creating it would be an orphan (CLAUDE.md rule 11).

state appears on zip_codes, alumni, cp_champions, and champion_signups, and there is no canonical list anywhere. Nothing is broken today. It is filed because it is the next industry: the first app to need a state dropdown will hardcode one, and the second will hardcode a different one.

Decision, so the second app finds one instead of a blank page: it belongs in the shared layer as UsState::ALL, alongside Industry. Note the width trap recorded in IdentityField::WIDTH_CONSTRAINTScp_champions.state is limit: 2 while the other two are unbounded, so the canonical form is the two-letter code and the full name is display only.

9.3 Cp::Champion#normalize_names makes pref_first_name unusable

Found: 23.3 §6.3, while building IdentityField. Held-surface behavior change — out of 23.3’s scope (§13).

Cp::Champion’s before_save :normalize_names assigns pref_first_name = first_name on every save where first_name is present. So:

The comment calls the column “legacy but still used by some display methods,” which is true and is also how it survived. Recorded as IdentityField::PREF_NAME_IS_MIRRORED and pinned by a test, so fixing it fails loudly and prompts updating the map.

Belongs to Phase 26 — it is a write-policy question, not a namespacing one.

9.4 Cp::ChampionRolesHelper#champion_icon_svg is a third copy of the mark

Found: 23.3 §5, while moving the artwork.

23.3’s spec listed this method as a render site to update. It is not — it holds a hardcoded inline <svg> string rather than rendering the shared partial, and it is a different mark (the hexagonal Alumni Champion icon, fill #b21029) from what shared/champion_interest/_icon renders via ApplicationHelper#role_icon_svg.

So the artwork has three homes, two of which are now shared and one of which is a string literal inside a held helper. Consolidating it is a held-surface change with a visible result, which puts it outside a pure-refactor sub-phase.

9.5 cp_communities.industry is still unvalidated

Found: 23.3 §6.1. ChampionSignup now validates industry against Industry::ALL and Cp::Champion already did, but Cp::Community does not — the third table holding the taxonomy (§4.2). Adding the validation is a one-line held write; it was not in the approved scope for 23.3.


How to Use This Document

Adding Items

  1. Add new ideas as they come up during development
  2. Include context — when was it added, what triggered the idea
  3. Estimate priority — High/Medium/Low
  4. Assign target phase — if known, add to Phase-Specific Deferrals table

Per-Phase Backlog Review Workflow

Before starting any sub-phase, the Sub-Phase Planning Checkpoint (see CLAUDE.md) includes a backlog review:

  1. Check this file for items targeting the current phase
  2. Present items to user — ask if they should be included now
  3. User decides: include, defer to later phase, or skip entirely
  4. Update this file — remove implemented items, update target phases for deferred items

After Phase Completion

  1. Move completed items to CHANGELOG.md
  2. Update deferred items with new target phases
  3. Mark implemented sections with ✅ IMPLEMENTED and implementation date

Last updated: March 2026