TL;DR
Two threads today: an organization layer on top of an existing multi-tenant app, and driver-based password-reset backends in an identity portal. Both came down to the same idea — put the source of truth in the right place, then test it.
Multi-tenant app: an organization layer above tenancy
The product already had tenancy. What it lacked was a human-friendly layer on top: organizations users actually belong to, can switch between, and manage.
What landed:
| Area | Change |
|---|---|
| Org switcher | A sidebar switcher to move between organizations you belong to |
| Management | Create/update org, invitations, ownership transfer |
| Tenancy | Resolve the active tenant from the user's org — closed a leak |
| UI | Dark-mode pass + responsive fixes across the org views |
| Dashboards | Richer per-widget configuration from the UI |
The standout is the tenancy fix: the active tenant was being resolved from the request instead of the authenticated user. I pulled that into its own focused post — "Resolve the tenant from the user, not the request." Short version: if a value scopes data, it can't come from something the client controls.
Identity portal: make the reset backends swappable
The password-reset flow needed to support more than one backend, and let an admin decide the order they run in. Classic case for a driver-based abstraction — a contract plus interchangeable drivers, picked at runtime from config.
interface PasswordResetBackend
{
public function reset(User $user, string $password): void;
public function name(): string;
}
Two optional backends came back as drivers behind that contract, and the run order is now admin-reorderable instead of hard-coded. Adding a third backend later is a new class + a config line — no touching the flow itself.
The other half of the day was unglamorous but necessary: the test suite had drifted — stale tests for removed features, and env leakage between tests (one test's state bleeding into the next). Fixed the leakage, deleted the dead tests, and the suite is honest again.
Takeaway
Two different apps, one lesson repeated: decide where the truth lives (the user's org; the config-driven driver), make the boundary explicit, and pin it with a test. Everything else is UI.
Top comments (0)