A Self-Service Sign-In Now Decides Which Product You Meant
Three products behind one login used to mean three separate bookmarks, a small self-service tax that added up every time someone forgot which one they’d saved. ZopNight now has a single shared sign-in at /zopnight/app/signin, where a user signs in once and picks the product they want: ZopNight, zopcloud, or ZopDay. The system remembers whichever one they used last and takes them straight there the next time, and each product’s own existing sign-in page still works exactly as it did before, going directly to that product without a detour through the picker.
The shared door reuses the login page that was already deployed at that path, just wearing zopdev branding instead of any one product’s identity. No new service was stood up, no ingress rule added, no environment variable introduced, and no migration run to ship it.
The Door You Arrive Through Is a Statement of Intent
The design rule underneath this is simple to state and easy to get wrong: the door a person arrives through is itself information. Signing in on ZopDay’s own login page is a stated intent to use ZopDay, so that choice is remembered and the person lands there directly, every time, without being asked again. Signing in through the shared door carries no such intent. Nothing is recorded until the person actually picks a product at the picker screen.
The full sequence for a new person is: sign in, create an organization if none exists yet, land on the picker, then land on the product they chose. The entire shared journey wears zopdev’s own branding throughout, on the page itself, the browser tab title, and the favicon, rather than flipping over to a product’s identity on the very screen where a password gets typed.
The Preference Belongs to a Person, Not a Machine
Where that remembered choice is stored matters as much as the fact that it’s remembered at all. It’s keyed to the person’s email address, not to the browser they happened to sign in from. A shared machine, a shared laptop at a desk two people use on different shifts, must never send the second person who signs in on it into the first person’s product just because the browser remembers something. Keying to email instead of browser is what prevents that.
A stored value that’s stale or has been hand-edited into something the system doesn’t recognize resolves back to the picker screen rather than attempting a redirect to a product that no longer applies. Sending someone to a broken destination is worse than asking them once more which product they meant.
A related fix in the same area made organization creation itself safe against a double click: a Redis-based fence combined with a membership lookup makes the request idempotent, so the organization automatically created during onboarding can’t end up duplicated by a retried or accidentally repeated submission.
A Reorder That Was Also a Default
A separate, smaller change reordered the picker’s three cards to read zopnight, zopcloud, zopday. On its own that sounds purely cosmetic: no copy changed, no color changed, no card’s destination changed. What actually changed underneath it is the default, because the picker pre-selects whichever product sits first in that list.
| Card position | Product | What happens if a visitor with no remembered preference does nothing else |
|---|---|---|
| First | ZopNight | Opens automatically as the pre-selected default |
| Second | zopcloud | Requires an active choice |
| Third | ZopDay | Requires an active choice |
Before the reorder, a visitor arriving at the picker with no history at all would have landed on whichever product sat first in the old order. After it, that same visitor lands on ZopNight instead. Reordering three cards on a screen turned out to be a product decision wearing a styling decision’s clothes.
A Passing Test That Was Checking the Wrong Thing
The reorder itself was verified the hard way, by measuring real pixel positions in an actual browser (x-coordinates 285, 604, and 924 for the three cards), specifically because the existing automated test for card order could never have caught a broken reorder here even if one had shipped. That test looked up each card by its internal test identifier through a mapped list of expected ids, which meant the first card the test checked was always whichever id the test’s own list happened to name first, not whichever card the browser actually rendered first. The test could pass forever regardless of what a real visitor saw on screen.
The fix makes the test query the rendered document directly instead of trusting its own internal list of expected identifiers. That one change moved the number of real defects this specific test suite is capable of catching from one to two, which is a small number, but it’s the difference between a test that verifies something and a test that only verifies its own assumptions about itself.
