The widget ran fine in SQLite during development. It broke in production on PostgreSQL. The same code, two different databases, two different ideas about what true means.
WidgetConfiguration::create() passed 1 for is_active. PostgreSQL boolean columns reject integers — they want actual booleans or DB::raw('true'). The JSON column allowed_domains rejected a PHP empty array — PostgreSQL wants a JSON string. Two casts that Laravel handles automatically in SQLite but leaves bare in Postgres.
The tenant scoping bug was more dangerous. The public widget runs on unauthenticated routes — no EnsureTenantContext middleware to set the request's tenant ID. The TenantScoped trait on OrderProduct expects a tenant ID on every create, and when it can't find one, it silently sets it to null. Orders were being created with products that belonged to no tenant. The fix: explicitly pass $data['tenant_id'] instead of relying on middleware that isn't there.
PrimeVue 4 checkbox selectors had changed from .p-checkbox-box.p-highlight to .p-checkbox.p-checkbox-checked .p-checkbox-box. The custom styles targeting the old selectors had no effect — checkboxes rendered in PrimeVue's default colors instead of the brand accent. Updated across ProductCard and CheckoutStep.
The checkout step showed the Stripe card form branded with the Nebula dark theme instead of the tenant's brand colors. Added brand font variable inheritance. The quote step lost its "Back" button — unnecessary friction — and "Continue" became "Place Order," a verb that matches what the customer is actually doing.
And a dev-mode flag had been baked into the production build assets. VITE_DEV_AUTH_ENABLED was set during a local build that got SCP'd to the server. Rebuilt on the server without it. The kind of leak that lets anyone bypass authentication by flipping a variable in the browser console.