Redesign the setup flow and consolidate the CSS into one primitives layer

Onboarding asks explicitly for one or two parents and shows one card per
parent for balances; the balance form is per parent with inline
validation; the partner can be filled from the FK deduction or added
from it. Settings and Dashboard reuse the same form.

CSS: bare buttons carry no colour any more; buttons, cards, choice
cards, fields, alerts, badges, eyebrow and modal chrome are defined once
in primitives.css. Media queries live only in responsive.css. A style
guard test blocks hex, !important and stray media queries. Bilingual
fixes: calendar selects size by content, header no longer wraps. Vitest
is capped at four workers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-19 12:27:29 +02:00
parent e4719cb9ae
commit ae6db7e16f
105 changed files with 5683 additions and 3690 deletions
+22 -13
View File
@@ -1,9 +1,10 @@
/**
* The other authoring gesture nothing end-to-end covers yet: registering a
* brand-new account and walking the 3-step onboarding wizard (child ->
* solo/couple -> balances). Runs with an empty `storageState` — deliberately
* NOT the shared demo session `auth.setup.ts` produces for the rest of the
* suite — since registering a fresh account is exactly what's under test.
* optional partner -> balances). Runs with an empty `storageState` —
* deliberately NOT the shared demo session `auth.setup.ts` produces for the
* rest of the suite — since registering a fresh account is exactly what's
* under test.
*
* Every date used here is derived from "today", never a hardcoded year (see
* `support/dates.ts`'s module doc for why).
@@ -14,6 +15,8 @@ import {
registerAndReachOnboarding,
fillChildStep,
continueSoloAndSkipBalances,
chooseTwoParents,
enterBalancesFor,
} from '../support/onboarding'
test.use({ storageState: { cookies: [], origins: [] } })
@@ -23,7 +26,7 @@ test.describe('onboarding wizard', () => {
await registerAndReachOnboarding(page, uniqueEmail('onboarding-skip'), 'Alex Testsson')
await fillChildStep(page, 'Nova')
// Solo is the wizard's default planning mode — nothing to change here.
// Adding a partner is optional — nothing to change here.
await continueSoloAndSkipBalances(page)
await expect(page.getByTestId('dashboard-estimate-badge')).toHaveText('Uppskattat')
@@ -33,15 +36,9 @@ test.describe('onboarding wizard', () => {
await registerAndReachOnboarding(page, uniqueEmail('onboarding-couple'), 'Sam Testsson')
await fillChildStep(page, 'Nova')
await page.getByTestId('planning-mode-couple').check()
await expect(page.getByTestId('onboarding-partner-section')).toBeVisible()
await page.getByLabel('Partnerns namn').fill('Kim')
await page.getByRole('button', { name: 'Lägg till partner' }).click()
await expect(page.getByTestId('onboarding-partner-added')).toHaveText('Partner tillagd: Kim')
await page.getByRole('button', { name: 'Fortsätt' }).click()
await expect(page.getByTestId('onboarding-step-balances')).toBeVisible()
await page.getByRole('button', { name: 'Hoppa över för nu' }).click()
await chooseTwoParents(page, 'Kim')
await expect(page.getByTestId('onboarding-parent-card')).toHaveCount(2)
await page.getByTestId('onboarding-finish').click()
// The regression this guards: finishing the wizard flips
// WorkspaceContext's `children` from empty to non-empty, which
@@ -59,4 +56,16 @@ test.describe('onboarding wizard', () => {
await expect(age4Bars).toContainText('Kim')
await expect(age4Bars).not.toContainText('Förälder')
})
test('entering balances for both parents during the couple path carries through to Översikt', async ({ page }) => {
await registerAndReachOnboarding(page, uniqueEmail('onboarding-couple-balances'), 'Robin Testsson')
await fillChildStep(page, 'Nova')
await chooseTwoParents(page, 'Kim')
await enterBalancesFor(page, 'Robin Testsson', { sjukpenning: 150, lagsta: 30, dubbeldagar: 20 })
await enterBalancesFor(page, 'Kim', { sjukpenning: 100, lagsta: 20, dubbeldagar: 10 })
await page.getByTestId('onboarding-finish').click()
await expect(page.getByTestId('dashboard')).toBeVisible({ timeout: 15_000 })
})
})