alumni_lookup

Phase 27.8 — One Front Door, and the Code Leads

Status: ✅ Complete — August 12, 2026 Effort Class: Small. No migration, no new table, no new route. Surfaces: Signup (alum-facing) only Held surfaces touched: None. One held-surface defect was found and left unfixed — see §6. Prerequisites: 27.7, whose shipped flow this was written against.


1. The Question This Sub-Phase Answers

27.7 shipped. Somebody walked the flow. What did it get wrong?

Four things, none of which a test could have told us because each is a decision rather than a defect — except the fourth, which is a defect and had no test looking for it.

  Symptom §
1 The sign-in form existed on three pages, and only one had the code box §2, §3
2 The email led with the link, which loses your place §4
3 The landing page offered “Get Started” to somebody already signed in §5
4 Developer commentary rendered as page copy on a public page §6

Not an interview sub-phase. Requested directly, scoped in one exchange, with two product decisions confirmed before building (keep the box but swap the form for a link; convert the thank-you page too).

The through-line worth keeping: three of the four are a second copy of something — a second sign-in form, a second call to action, a second reading of what “home” means. The fourth is a comment that documented a second copy. 27.2 and 27.5 both created one deliberately and both drifted within a sub-phase. See §7.


2. The sign-in form now exists in exactly one place

27.2 gave the “email me a link” form a page of its own and left the landing page’s copy in place, with an entry_point param so each confirmation returned to its own page. The thank-you page had a third copy, inlined rather than rendered from the partial.

The copies had already drifted: 27.7 added the code box and the “keep me signed in” checkbox to /sign-in only, so submitting the landing form got you an email whose primary instruction — “enter this code on the page you were on” — pointed at a page with nowhere to enter it. That is the argument against a duplicated form generally, and it arrived within one sub-phase of the duplication.

Both copies are now links to /sign-in. Removed with them: the entry_point param and its hidden field, signin_entry_point?, the two-branch link_sent_path, and the landing page’s link_sent / link_request banners (nothing redirects there with either any more). The link_requested event type stays valid so historical rows keep their meaning, but has no writer — asserted, so a third form cannot quietly reintroduce one.

The thank-you page’s link carries return_to, matching the anonymous prompt on the opportunity form itself: signing in from a receipt returns to that receipt.

3. The confirmation carries the address back

link_sent_path appends the submitted email, and /sign-in prefills both forms from it, autofocusing the code box when it is filled. The person types six digits and nothing else.

Two properties this had to preserve, both tested:

27.7 put the code below the link, framed as “reading this on a different device?”. That is backwards for the common case. The link opens the profile hub on whatever device reads the mail, abandoning whatever the person was doing; the code returns them to the browser they left — with its stored return_to, so an opportunity form is still there.

The email now leads with the code and offers the link under “reading this somewhere else”. The subject follows, and only says “code” when there is one, so a codeless send stays truthful. Pinned as an order in both parts: a test asserting both are present passes on either arrangement.

5. The landing page kept selling to people who had already bought

Reported the same day: click the header logo while signed in, land on /sign-up, and the page offers Get Started. That button carries no signup_token, and handle_who_you_are reads a missing token as creating — so following it opens a second signup under the same address. The “Welcome back, Chip” banner sat directly above it.

The banner was 27.2-era mitigation: it told the truth about who you were and left the rest of the page arguing the opposite. Three CTAs disagreed with it (hero “Get Started”, bottom “Start Signing Up”, the “Already signed up?” section) plus one line of copy promising you’d choose an interest area “after you sign up”.

Fixed on both axes, per the request.

Home moves. Signup::BaseController#signup_home_path is now the single definition of where “home” is: the profile when signed in, the landing page otherwise. The header logo uses it, so there is no branch in the markup, and sessions#create / #new were collapsed onto it — they had grown a private profile_path_for doing the signed-in half.

The landing page has two modes. One page, one education_mode flag read by each section, because the middle of the page — what a Champion is, the four interest areas — is genuinely identical for both audiences. Splitting it into two templates would have produced the same drift the duplicated sign-in form did in §2, on more content.

  anonymous signed in
Hero Stay connected. Make an impact. → Get Started You’re an Alumni Champion, Name. → Go to my profile
Interest areas “you’ll tell us where you lean after you sign up” “you can change where you lean anytime from your profile”
Bottom Ready to jump in? → Start Signing Up Want to add more? → Open my profile
Returning section Already signed up? → Sign in hidden

The education itself is untouched in both, which was the point — the fix had to keep the page worth visiting, not delete it.

Tested as absences as well as presences. A returning CTA is precisely the failure, and a presence-only assertion cannot see one; assert_select ... false on the create form’s href is the one that would catch a regression. Sabotage-verified by forcing education_mode = false: three of the four new tests fail, and the fourth is the educational-content assertion, which is supposed to hold in both modes.

Not changed: Get Started with an active session but no token starts a fresh record. That behavior is still correct and still reachable — “Not you? Start a new signup” on the resume banner clears the session first, which is the shared-device case it exists for. What changed is that no page offers it to somebody who did not ask.

6. Finding: an ERB comment ends at its first %>

“Things you’ve said yes to” rendered four lines of developer commentary as page copy, on an alum’s own hub and on the public thank-you page. The receipt partial’s doc comment showed an example using the <%%= escape — which escapes the output delimiter but does nothing for the scanner, so the comment closed at that tag and the rest was emitted.

Nothing could have caught it as written: valid HTML, 200 response, no warning, and the existing test asserted the answers were present, which they were — the leak renders above them. test/views/erb_comment_integrity_test.rb now scans every view on every build. Held surfaces are excluded by path, and one real instance sits behind that exclusion (cp/profile/_completion_banner.html.erb:6, leaking its own header onto the profile edit page) — unfixed because writing to Cp:: needs approval, and named here so the exclusion is a known debt rather than a clean baseline. Routed to /debug.


7. The pattern under all four

Every item in §1’s table is a second copy of a decision, and each drifted within a sub-phase of being created:

Created Second copy of Drifted by
27.2 the sign-in form (landing + /sign-in) 27.7, which added the code box and consent checkbox to one of them
21.6 the sign-in form again (thank-you page, inlined) never rendered from the partial, so it missed both
19.x the “home” destination (logo href vs. sessions#create’s own profile_path_for) 27.7, when signed-in became a real state
21.7 the answer-rendering rule, written as a census in a comment 27.5, which found the fifth consumer the count denied

The rule that would have prevented all four is already in CLAUDE.md §7 — “the shared layer is the first answer, not the last resort” — and each was a case where a second consumer looked cheaper than a shared one at the moment of writing. It is cheaper, once. The cost lands on whoever changes the behavior next, and they do not know the second copy exists.

The operational form, added to /patterns in this wrap: when a page needs a control another page already has, the default is a link to that page, not a copy of the control. Duplicate only when both copies render from the same partial and the partial names both consumers. Two of the three fixes here were “delete the copy, link instead” — no shared abstraction needed at all.


8. What shipped

Area Files
One front door signup/signups/steps/_landing, signup/opportunities/thank_you, signup/signups/_link_request_form, signups_controller#request_link / #link_sent_path
Email prefill signups_controller#link_sent_path, signup/sessions/new
Code-first email champion_signup_mailer#profile_link_email, both profile_link_email templates
Education mode signup/signups/steps/_landing, signup/signups/_header, Signup::BaseController#signup_home_path, Signup::SessionsController
ERB leak signup/opportunities/_response_receipt, test/views/erb_comment_integrity_test.rb

Removed: the entry_point param and its hidden field, signin_entry_point?, entry_point, the two-branch link_sent_path, Signup::SessionsController#profile_path_for, the landing page’s link_sent and link_request banners, and two inline copies of the link-request form.

No migration. No new route. No new model. ChampionSignupEvent gains no type; link_requested loses its last writer and stays valid for historical rows, asserted.

Tests

File Added
test/views/erb_comment_integrity_test.rb new — scans every view on every build
test/controllers/signup/hub_additions_test.rb leak absent on both consumers of the receipt
test/controllers/signup/signups_controller_test.rb education mode (5), header logo (2), one-front-door redirects
test/controllers/signup/sessions_controller_test.rb prefill, prefill escaping, one destination, neutrality-with-echo
test/mailers/champion_signup_mailer_test.rb code-before-link order in both parts, subject follows

bin/test: 5805 runs, 0 failures, 0 errors.

Two verified by sabotage rather than trusted: the ERB detector (empty HELD_TREES → reports the known cp/ instance) and education mode (force education_mode = false → 3 of 4 fail; the fourth asserts the educational content, which holds in both modes).


9. Postscript: the code needed a second axis after all (August 13, 2026)

Reported the day 27.8 reached staging, walking the flow from /sign-up:

The flow from /sign-up, where an existing email address is detected goes “email me my link” and then the code shows up at the top of the email (even though the link to the profile is below).

Correct, and a regression this sub-phase introduced. §4 made the code both unconditional and primary, reasoning only about /sign-in. But /profile-link has a second caller: step 1’s duplicate-check notice (27.1), which fetches and deliberately keeps the person on their half-filled signup form. Nothing on their screen accepts six digits. They asked for a link and got a puzzle.

The distinction §2 collapsed, and the one it should have kept

§2 deleted entry_point and was right to. That param asked which page rendered the form, which was cosmetic — it picked a redirect target and nothing else.

The axis that actually matters is a different question: is there a code box in front of this person right now? It has a consequence, so it earns a branch.

mode Caller Email
code (default) /sign-in — its code box appears the instant the send completes Leads with the six digits; profile link demoted to a small text line
link step 1’s duplicate notice — they stay mid-form Prominent profile button, no code issued at all

This is not the deleted param returning under a new name. It is the distinction that was hiding underneath it. That entry_point happened to correlate with it, for the two callers that existed in 27.2, is exactly why deleting it looked free.

Three things that had to be right

The event metadata changed with it: entry_point: "sign_in" (always one value) became mode: "code" | "link", which records something that varies and that decided what was sent.

What this says about §7

§7’s rule — link first, share second, copy never — is unchanged and still right. What this postscript adds is its failure mode: when you collapse two things into one, check whether they differed on an axis you weren’t looking at. §2 verified the two forms were visually identical and posted to the same endpoint. It did not ask what the recipients of the two sends were looking at when the mail arrived, and that was the difference.

Deduplication is a claim that two things are the same. Like any claim, it is worth stating what would make it false before acting on it.