At 9:52 on April 2, I opened a ticket: a staff-only tournament signup that should not surface to regular users. The ticket was short. The obvious read was that I needed a new entry point, gated by staff role, sitting alongside the existing public signup flow.
I almost built that. Then I opened the tournament controller, found the admin link flow already sitting there, and realized the door I needed was already in the building.
what “hidden” actually meant
The request was for a signup path that staff could hand to a customer for a specific tournament without surfacing it in the marketing site or a normal account view. That is a discoverability requirement, not an authorization strategy: any access and validation requirements still belong in the underlying flow.
That’s a different problem than “build a private route.” A private route still has to exist. A hidden path that piggybacks on an existing private mechanism doesn’t need to exist as a new thing at all. It just needs the existing thing to do one more job.
The admin section already had a “link to existing site” flow. Staff could pick a tournament, pick an existing customer site, and connect them. The flow lived in the admin tournament controller, had its own JavaScript module, and already passed the relevant context to the downstream signup handling. Its placement made it a sensible internal starting point, while the downstream flow still owned any customer-facing validation.
The signup work was about teaching the signup handler to accept the arrival from that flow and behave appropriately on the other side. That’s a much smaller piece of work.
what i actually shipped
Three commits: teach the signup handler, extend the admin linking UI, then fill in the “add a brand-new site” branch.
The first taught the signup handler to recognize the staff-arrival shape and skip the parts of the public signup flow that don’t apply. Thirty-one lines of changes, plus a CSS rule and a JS adjustment. The diff was small because the signup machinery already handled most of the variations.
The second extended the admin tournament controller and its JS twin with the actual linking UI: pick a tournament, pick how it’s getting connected, fire the redirect into the signup flow with the right context. This commit was the largest of the three, around 500 lines added, because it included the dialog, the field handling, and the API change to support the lookup.
The third filled in the “add a brand-new site from this flow” branch, in case staff need to create the customer site at the same moment they’re attaching a tournament. A new admin nav module, template CSS adjustments, and a small change in the page layout layer so the new path renders inside the admin chrome instead of the customer chrome.
The work fit into an afternoon because the existing signup machinery already handled most of the variations. A separate route would have introduced another entry point, another integration surface, and an ongoing synchronization decision.
the part i almost got wrong
My first instinct, before I opened the tournament controller, was to draft a new signup URL. Something like /signup/tournament-staff?token=X. Authenticated against a staff session, validated against a one-time token, dropped into a stripped-down signup component. I had the rough sketch in my head already.
That sketch was wrong, but in a way that doesn’t announce itself until you go look at what’s already there. The “new URL” plan would have created a parallel signup flow that has to be maintained alongside the public one forever. Every future change to public signup would need to be re-applied to the staff flow or explicitly noted as not applying. The staff flow would drift from the public flow over time, because that’s what parallel implementations do.
The admin link flow has the opposite property. It already exists, it already has staff there, and it already integrates with signup on the back end. The marginal cost of one more capability inside it is small and gets smaller every time I touch that area for another reason.
I had the wrong frame. “Build a hidden route” became “extend the route that’s already hidden by location.” Different work. Smaller surface area. No new discovery problem to solve, because the discovery problem was already solved by the admin section’s existence.
why this keeps happening
The default mode for an engineering ticket is “build the thing the ticket describes.” On an older codebase, that can miss an adjacent mechanism that already does much of the job. Looking first is a small discovery step that can prevent a second implementation from becoming permanent maintenance work.
I have noticed I do this better with an agent in the loop than without one. Not because the agent is smarter about the codebase. It isn’t. But because typing “where does staff currently hand a customer a signup link” into a Claude Code session forces me to articulate the question before I write any code. The agent reads the area, surfaces the relevant files, and I see the existing flow before my fingers start drafting the new one.
The agent didn’t tell me to reuse the existing flow. I told the agent to look at it, and looking is what changed my mind. The tool’s value is that it lowers the cost of looking enough that I do it on every ticket, not just the ones where I already suspect there’s something to find.
the closing principle
A useful new feature may turn out to exist in adjacent form, and extending a mechanism you already maintain can reduce surface area. Reuse is not automatic: confirm that the existing flow has the right authorization, data boundaries, and user experience. The first step is still to look at the building before you draw.
Related
- A New Feature Is the Best Fuzz-Tester for Your Existing Data Model: another case where a new request exposed an adjacent shared mechanism that needed a focused improvement
- A Fluent DOM Builder for Legacy ASP: Modernize Without a Rewrite: an incremental modernization choice that extended existing conventions instead of creating a parallel system