diff --git a/frontend/src/i18n/en/calendar.ts b/frontend/src/i18n/en/calendar.ts
index 52298de..38195bb 100644
--- a/frontend/src/i18n/en/calendar.ts
+++ b/frontend/src/i18n/en/calendar.ts
@@ -80,6 +80,10 @@ export const calendar = {
'{name} ({start}–{end}): none of the {weeksWished} week(s) requested could be funded — {shortfallDays} day(s) short',
'calendar.toastShortfallPartial':
'{name} ({start}–{end}): only {weeksFunded} of {weeksWished} week(s) funded — {shortfallDays} day(s) short',
+ 'calendar.toastShortfallNoneDays':
+ '{name} ({start}–{end}): none of the {daysWished} day(s) requested could be funded — {shortfallDays} day(s) short',
+ 'calendar.toastShortfallPartialDays':
+ '{name} ({start}–{end}): only {daysFunded} of {daysWished} day(s) funded — {shortfallDays} day(s) short',
'calendar.undoButton': 'Undo',
'calendar.undoError': 'Failed to undo the change',
'calendar.dismissToastAria': 'Dismiss the message',
diff --git a/frontend/src/i18n/sv/calendar.ts b/frontend/src/i18n/sv/calendar.ts
index 0c2e9fb..ba2e208 100644
--- a/frontend/src/i18n/sv/calendar.ts
+++ b/frontend/src/i18n/sv/calendar.ts
@@ -80,6 +80,10 @@ export const calendar = {
'{name} ({start}–{end}): inget av de {weeksWished} önskade veckorna kunde finansieras — {shortfallDays} dag(ar) saknas',
'calendar.toastShortfallPartial':
'{name} ({start}–{end}): endast {weeksFunded} av {weeksWished} veckor finansierades — {shortfallDays} dag(ar) saknas',
+ 'calendar.toastShortfallNoneDays':
+ '{name} ({start}–{end}): inget av de {daysWished} önskade dagarna kunde finansieras — {shortfallDays} dag(ar) saknas',
+ 'calendar.toastShortfallPartialDays':
+ '{name} ({start}–{end}): endast {daysFunded} av {daysWished} dagar finansierades — {shortfallDays} dag(ar) saknas',
'calendar.undoButton': 'Ångra',
'calendar.undoError': 'Det gick inte att ångra ändringen',
'calendar.dismissToastAria': 'Stäng meddelandet',
diff --git a/frontend/src/pages/Dashboard.test.tsx b/frontend/src/pages/Dashboard.test.tsx
index 72b21f8..2c08f58 100644
--- a/frontend/src/pages/Dashboard.test.tsx
+++ b/frontend/src/pages/Dashboard.test.tsx
@@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
-import { render, screen } from '@testing-library/react'
+import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { BrowserRouter } from 'react-router-dom'
import { Dashboard } from './Dashboard'
@@ -159,6 +159,49 @@ describe('Dashboard', () => {
expect(screen.queryByTestId('dashboard')).not.toBeInTheDocument()
})
+ it('refetches workspace members once onboarding hands off (children goes from empty to non-empty)', async () => {
+ // Regression: a couple's partner is added mid-onboarding via a direct
+ // `api.addPartner` call, invisible to this component's own
+ // `useWorkspaceMembers` instance (fetched once, on mount, back when
+ // there was only one member). `Onboarding` only ever refreshes
+ // `WorkspaceContext` — flipping `children` from empty to non-empty is
+ // the only externally-observable signal this component gets that the
+ // member list it already fetched might now be stale.
+ const refreshMembers = vi.fn().mockResolvedValue(undefined)
+ vi.mocked(hooks.useWorkspaceMembers).mockReturnValue(
+ makeWorkspaceMembersResult({ refresh: refreshMembers, data: [] }),
+ )
+ const soloWorkspace = makeWorkspaceContextValue({
+ workspace: { id: 'ws-1', name: 'Test Workspace', planningMode: 'couple', createdAt: '2026-01-01' },
+ workspaceId: 'ws-1',
+ children: [],
+ selectedChildId: null,
+ personalDataUuid: 'pd-1',
+ parentName: 'Test Parent',
+ })
+ vi.mocked(useWorkspace).mockReturnValue(soloWorkspace)
+
+ const { rerender } = renderWithRouter()
+ expect(screen.getByTestId('onboarding-page')).toBeInTheDocument()
+ expect(refreshMembers).not.toHaveBeenCalled()
+
+ // Onboarding finishes: `WorkspaceContext.children` flips non-empty.
+ vi.mocked(useWorkspace).mockReturnValue(
+ makeWorkspaceContextValue({
+ ...soloWorkspace,
+ children: [{ id: 'c-1', name: 'Child 1', birth_date: '2026-01-01' }],
+ selectedChildId: 'c-1',
+ })
+ )
+ rerender(
+
+
+ ,
+ )
+
+ await waitFor(() => expect(refreshMembers).toHaveBeenCalledTimes(1))
+ })
+
describe('estimated-figures badge', () => {
it('is hidden once real parental-leave balances have been fetched (the default mock)', () => {
renderWithRouter()
diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx
index 36d4f6f..5f050f2 100644
--- a/frontend/src/pages/Dashboard.tsx
+++ b/frontend/src/pages/Dashboard.tsx
@@ -1,4 +1,4 @@
-import { ReactNode, useState } from 'react'
+import { ReactNode, useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useWorkspace } from '@/context'
import {
@@ -47,7 +47,7 @@ export function Dashboard(): ReactNode {
)
const { data: plannedDays, isLoading: pdLoading } = usePlannedDays(selectedChildId ?? undefined)
const { data: parentalLeave, refresh: refreshParentalLeave } = useParentalLeave(personalDataUuid, selectedChildId ?? null)
- const { data: members, isLoading: membersLoading } = useWorkspaceMembers(workspace?.id ?? null)
+ const { data: members, isLoading: membersLoading, refresh: refreshMembers } = useWorkspaceMembers(workspace?.id ?? null)
const { data: tips, dismiss: dismissTip } = useOptimizationTips(workspace?.id ?? null)
const scraper = useScraperFetch()
@@ -56,6 +56,23 @@ export function Dashboard(): ReactNode {
const [isManualFetchSaving, setIsManualFetchSaving] = useState(false)
const [manualFetchError, setManualFetchError] = useState(null)
+ // `useWorkspaceMembers` fetches once per `workspace.id`, which never
+ // changes across onboarding — so a couple's partner, added mid-wizard via
+ // `Onboarding.tsx`'s own direct `api.addPartner` call, is invisible to
+ // this hook's already-fetched `data` until something explicitly refetches
+ // it. `Onboarding` only refreshes `WorkspaceContext` (which flips
+ // `children` from empty to non-empty) when it finishes — it has no way to
+ // reach into this component's `useWorkspaceMembers` instance. Watching
+ // that same empty-to-non-empty transition here is what tells this
+ // component "onboarding just finished, the members list might be stale."
+ const wasAwaitingFirstChildRef = useRef(children.length === 0)
+ useEffect(() => {
+ if (wasAwaitingFirstChildRef.current && children.length > 0) {
+ void refreshMembers()
+ }
+ wasAwaitingFirstChildRef.current = children.length === 0
+ }, [children.length, refreshMembers])
+
const isLoading = wsLoading || analyticsLoading || pdLoading
if (isLoading) {