Lanchester R&DTactical Exploration Lab
LT // BUILD-LOG
Product Engineering // Post-Mortem

The LEARN
Launchpad.

"One identity. One trust layer. Every product, one front door. Getting there took three rewrites."

01 // Origin

Why We
Built It.

LEARN — the Lanchester Early Access Research Network — is a centralised platform that lets users discover, follow, and access early-stage apps and experiments from a single identity: their LEARNid. It acts as a controlled gateway where access to products is tied to user intent, enabling structured onboarding, feedback collection, and usage tracking across Lanchester's entire product ecosystem.

For builders, LEARN provides tools to manage waitlists, referrals, access permissions, and direct communication with early users. The goal: turn fragmented product launches into a unified, data-driven ecosystem for testing, iteration, and growth. A single identity. A single trust layer. Every product, one front door.

The Core Hypothesis

"A unified identity layer across all early-access products eliminates fragmented onboarding, creates a single trust boundary, and gives builders meaningful signal on user intent from day one."

The hypothesis was right. The execution, the first few times, was not.

10
Failures Documented
3
Architecture Rewrites
1
Stable System

02 // What Broke

Ten
Failures,
Three
Themes.

Most of these weren't bugs. They were system-level concerns treated as UI problems.

Theme A // Identity & Trust
01 //
We split auth and legal across two apps.

We initially built critical user journeys — auth in one Vite app, legal acceptance in a separate Next.js app — treating them as independent concerns. This technically worked. But users were asked to agree in one place and authenticate in another. There was no way to enforce compliance or maintain a coherent mental model. It felt stitched together because it was.

▸ Lesson: Anything tied to identity or compliance must live inside a single system boundary.
02 //
Our auth flow was visually correct but logically wrong.

We placed a legal acceptance checkbox on the sign-in page. Clean, simple, obvious. Except OAuth and magic-link flows completely bypassed that UI layer. Users could authenticate without ever seeing — let alone interacting with — the checkbox. The system looked compliant. It wasn't.

▸ Lesson: Auth enforcement must happen post-auth at the backend. UI state is not a contract.
03 //
We had no concept of versioned truth.

Terms and Privacy were static pages. Not system data. So we couldn't answer basic compliance questions: Did this user accept the current version? When did they accept it? What happens when terms change? Nothing re-triggered consent. Nothing was auditable. Nothing was legally defensible.

▸ Lesson: Policy documents must be versioned, queryable, and enforced like data — not content.
04 //
The backend had no schema for legal acceptance.

There was no data model for legal state at all. The backend couldn't distinguish between a user who had never accepted, accepted an old version, or accepted the current one. You can't enforce state you can't represent. The feature didn't exist — it only appeared to.

▸ Lesson: If the data model doesn't exist, neither does the feature.
Theme B // Infrastructure
05 //
Simple routing was a system-level security risk.

We added vanity routes like /iru-assistant alongside /products/:slug, treating routing as a UI decision. Routing order and catch-all patterns caused valid pages to silently misresolve. Reserved paths like /admin and /api were at risk. Debugging pointed in the wrong direction because the failure mode was non-obvious.

▸ Lesson: Routing is core infrastructure with security implications — not a UI configuration.
06 //
Implicit behavior drove critical product outcomes.

We allowed fallback redirect logic (beta_cta_url) to determine launch behavior when a product wasn't explicitly configured. Products could silently redirect users to external sites without anyone making an intentional decision. We optimised for convenience and created a trust and security liability.

▸ Lesson: Critical behavior — especially redirects — must be explicit, not inferred from defaults.
07 //
Deployment inconsistencies created ghost bugs.

Different domains were serving different frontend bundles. One URL worked perfectly. Another showed bugs we'd already fixed. Same codebase, same deploy. It looked like mysterious regressions, slowed debugging massively, and eroded confidence in every fix we shipped.

▸ Lesson: Multi-domain setups require strict deployment consistency and post-deploy verification.
Theme C // Integration
08 //
A single failed request collapsed the entire page.

The /products/:slug page used Promise.all to fetch data. One failed sub-request — from a completely healthy API — caused the whole page to throw 'not found'. We spent time debugging routing when the issue was fault tolerance in our aggregation layer.

▸ Lesson: UI aggregation should use Promise.allSettled — partial failure should degrade gracefully, not collapse.
09 //
Email delivery was a completely separate system problem.

After fixing all the app logic, emails still failed. The system was generating links, dispatching requests, logging success — and still not delivering to users. The failures were upstream: provider reputation, SPF/DKIM/DMARC configuration, recipient-side filtering. You can't reproduce this locally. The feedback loop only exists in production.

▸ Lesson: Email is not a feature — it's an external system with its own failure modes and reputation lifecycle.
10 //
Verification required real-world conditions, not just code.

Final validation depended on OAuth providers, real inboxes, and real user acceptance states (stale vs. current terms). We couldn't fully test this in isolation — not from lack of discipline, but because some systems are inherently integration-heavy and only surface their failure modes under live conditions.

▸ Lesson: Some systems require production-like thinking. Code correctness is necessary but not sufficient.

03 // The Reframe

The Gold
Insight.

Pattern Recognition

"Most of the failures weren't bugs — they were cases where we treated system-level concerns as UI or configuration problems."

Auth isn't a form. Legal acceptance isn't a checkbox. Routing isn't a component. Email delivery isn't a function call. Each of these is a system with state, enforcement surfaces, failure modes, and external dependencies. We had to fail at each one to see it clearly.

The moment we reframed them — as stateful, enforceable, backend-driven systems that the UI merely surfaces — the architecture became stable. The failures stopped stacking. Problems that had felt random became predictable because we understood their actual nature.

Legal
Checkbox on sign-in page
Post-auth gate on every backend request
Terms
Static /terms page
Versioned Firestore document with timestamps
Routing
UI component routing
Server-enforced paths + reserved-path allow-list

04 // What Shipped

The
System
Today.

LEARN is live. Users sign in once with their LEARNid — via magic link or OAuth — and gain access to the products they've been granted permission for. Legal acceptance is enforced at the backend, version-tracked in Firestore, and re-triggered when terms change. Products are canonical data records with configurable access rules, not hardcoded routes.

Builders can manage waitlists, control access per product, track usage and cohort behaviour, and communicate directly with early users — all from a single admin layer. Routing is server-enforced with a reserved-path allow-list. Email delivery runs over a warmed domain with SPF/DKIM/DMARC in place. Each system layer is independently enforceable — not wired through the UI.

Tech Stack

Vite + ReactTypeScriptNode.js + ExpressFirebase AuthFirestoreGoogle Cloud RunFirebase HostingResend (Email)Framer Motion

Join the
network.

LEARN gives builders a structured way to launch early, gather real signal, and grow a user base that's invested from day one. If that sounds useful, let's talk.