Fixes#701. setActiveCompany upserted active_company_id without checking
the result, then set the gnubok-company-id cookie unconditionally. A failed
write — including an RLS-filtered UPDATE, which affects zero rows without
raising an error — looked like a successful switch: switchCompany returned
{}, the UI hard-reloaded, and middleware (which reads user_preferences, not
the cookie) resolved the old company.
- setActiveCompany now verifies the upsert with .select().single() and
throws a typed CompanyContextError ('not_member' | 'persist_failed');
the cookie is only set after the write is confirmed, so it can no longer
diverge from the database.
- switchCompany logs the failure and returns distinct error codes instead
of reporting every failure as a permissions problem.
- CompanySwitcher now shows a destructive toast on failure (it previously
failed with no feedback); BankIdCompanyPicker translates the codes.
Messages added to sv/en under company_switcher and select_company.
- The remaining fire-and-forget user_preferences writers (middleware
fallback write-back, team invite accept, auth callback invite accept)
now check and log errors; non-fatal by design since each has a working
fallback path.
- New tests cover every failure mode, including cookie-not-set on a failed
write and the silent zero-row write caught by the read-back.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>