From 29db24210c7f0f1decd445006032728549c7cf4b Mon Sep 17 00:00:00 2001 From: "shaggy (mosaic-dev box)" Date: Mon, 10 Aug 2026 22:43:47 -0500 Subject: [PATCH] fix(web): P4-1 restore graceful degradation for missions/tasks fetch on project detail --- .../web/src/spa/pages/project-detail.spec.tsx | 23 +++++++++++++++++-- apps/web/src/spa/pages/project-detail.tsx | 4 ++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/apps/web/src/spa/pages/project-detail.spec.tsx b/apps/web/src/spa/pages/project-detail.spec.tsx index 00280ab5..ffb7b795 100644 --- a/apps/web/src/spa/pages/project-detail.spec.tsx +++ b/apps/web/src/spa/pages/project-detail.spec.tsx @@ -134,17 +134,36 @@ describe('ProjectDetailPage', () => { expect(container.querySelector('[role="dialog"]')).toBeNull(); }); - it('renders a visible alert when any project detail request fails and lets the user navigate back', async () => { + it('renders the project with an empty missions tab when the missions request fails', async () => { apiMock .mockResolvedValueOnce(projectFixtures[0]) .mockRejectedValueOnce(new Error('Missions request failed')) .mockResolvedValueOnce(taskFixtures.filter((task) => task.projectId === 'project-1')); + await renderProjectDetailPage(); + + expect(container.textContent).toContain('Mosaic Stack'); + expect(container.querySelector('[role="alert"]')).toBeNull(); + + await act(async () => { + clickButtonByText('Missions (0)'); + }); + + expect(container.textContent).toContain('No missions for this project'); + }); + + it('renders a visible alert when the project request fails and lets the user navigate back', async () => { + apiMock + .mockRejectedValueOnce(new Error('Project request failed')) + .mockResolvedValueOnce(missionFixtures) + .mockResolvedValueOnce(taskFixtures.filter((task) => task.projectId === 'project-1')); + const router = await renderProjectDetailPage(); const alert = container.querySelector('[role="alert"]'); expect(alert).toBeTruthy(); - expect(alert?.textContent).toContain('Missions request failed'); + expect(alert?.textContent).toContain('Project request failed'); + expect(container.textContent).not.toContain('Mosaic Stack'); await act(async () => { clickButtonByText('Back to projects'); diff --git a/apps/web/src/spa/pages/project-detail.tsx b/apps/web/src/spa/pages/project-detail.tsx index 0f060c99..20253b5e 100644 --- a/apps/web/src/spa/pages/project-detail.tsx +++ b/apps/web/src/spa/pages/project-detail.tsx @@ -76,8 +76,8 @@ export function ProjectDetailPage(): ReactElement { void Promise.all([ api('/api/projects/' + id), - api('/api/missions'), - api('/api/tasks?projectId=' + id), + api('/api/missions').catch(() => [] as Mission[]), + api('/api/tasks?projectId=' + id).catch(() => [] as Task[]), ]) .then(([loadedProject, allMissions, loadedTasks]) => { if (cancelled) return;