From b1a32ab20798f1133aebbecfbf8a24e2a6e23968 Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Fri, 20 Mar 2026 19:16:03 -0700 Subject: [PATCH] fix: contacts fetch error degrades partially, not full panel blank Contacts 5xx no longer early-returns from fetchFor, leaving the entire right panel blank. A new contactsError ref surfaces the failure message in the Email tab only; JD tab, Cover Letter tab, and match score all render normally. Adds test asserting partial degradation behavior. --- web/src/stores/prep.test.ts | 30 +++++++++++++++++++++++++++++ web/src/stores/prep.ts | 19 +++++++++++++----- web/src/views/InterviewPrepView.vue | 15 ++++++++++++++- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/web/src/stores/prep.test.ts b/web/src/stores/prep.test.ts index d4c9604..6ac6840 100644 --- a/web/src/stores/prep.test.ts +++ b/web/src/stores/prep.test.ts @@ -151,6 +151,36 @@ describe('usePrepStore', () => { expect(store.research).toBeNull() expect(store.contacts).toEqual([]) + expect(store.contactsError).toBeNull() expect(store.currentJobId).toBeNull() }) + + it('fetchFor sets contactsError and leaves other data intact when contacts fetch fails', async () => { + const mockApiFetch = vi.mocked(useApiFetch) + mockApiFetch + .mockResolvedValueOnce({ data: { company_brief: 'Acme', ceo_brief: null, talking_points: null, + tech_brief: null, funding_brief: null, red_flags: null, accessibility_brief: null, + generated_at: '2026-03-20T12:00:00' }, error: null }) // research OK + .mockResolvedValueOnce({ data: null, error: { kind: 'http', status: 500, detail: 'DB error' } }) // contacts fail + .mockResolvedValueOnce({ data: { status: 'none', stage: null, message: null }, error: null }) // task OK + .mockResolvedValueOnce({ data: { id: 1, title: 'Engineer', company: 'Acme', url: null, + description: 'Build things.', cover_letter: null, match_score: 80, + keyword_gaps: null }, error: null }) // fullJob OK + + const store = usePrepStore() + await store.fetchFor(1) + + // Contacts error shown in Email tab only + expect(store.contactsError).toBe('Could not load email history.') + expect(store.contacts).toEqual([]) + + // Everything else still renders + expect(store.research?.company_brief).toBe('Acme') + expect(store.fullJob?.description).toBe('Build things.') + expect(store.fullJob?.match_score).toBe(80) + expect(store.taskStatus.status).toBe('none') + + // Top-level error stays null (no full-panel blank-out) + expect(store.error).toBeNull() + }) }) diff --git a/web/src/stores/prep.ts b/web/src/stores/prep.ts index 549b75c..50be754 100644 --- a/web/src/stores/prep.ts +++ b/web/src/stores/prep.ts @@ -42,6 +42,7 @@ export interface FullJobDetail { export const usePrepStore = defineStore('prep', () => { const research = ref(null) const contacts = ref([]) + const contactsError = ref(null) const taskStatus = ref({ status: null, stage: null, message: null }) const fullJob = ref(null) const loading = ref(false) @@ -62,6 +63,7 @@ export const usePrepStore = defineStore('prep', () => { _clearInterval() research.value = null contacts.value = [] + contactsError.value = null taskStatus.value = { status: null, stage: null, message: null } fullJob.value = null error.value = null @@ -82,17 +84,22 @@ export const usePrepStore = defineStore('prep', () => { error.value = 'Failed to load research data' return } - if (contactsResult.error) { - error.value = 'Failed to load contacts' - return - } if (jobResult.error) { error.value = 'Failed to load job details' return } research.value = researchResult.data ?? null - contacts.value = contactsResult.data ?? [] + + // Contacts failure is non-fatal — degrade the Email tab only + if (contactsResult.error) { + contactsError.value = 'Could not load email history.' + contacts.value = [] + } else { + contacts.value = contactsResult.data ?? [] + contactsError.value = null + } + taskStatus.value = taskResult.data ?? { status: null, stage: null, message: null } fullJob.value = jobResult.data ?? null @@ -141,6 +148,7 @@ export const usePrepStore = defineStore('prep', () => { _clearInterval() research.value = null contacts.value = [] + contactsError.value = null taskStatus.value = { status: null, stage: null, message: null } fullJob.value = null loading.value = false @@ -151,6 +159,7 @@ export const usePrepStore = defineStore('prep', () => { return { research, contacts, + contactsError, taskStatus, fullJob, loading, diff --git a/web/src/views/InterviewPrepView.vue b/web/src/views/InterviewPrepView.vue index 4934eae..ed95015 100644 --- a/web/src/views/InterviewPrepView.vue +++ b/web/src/views/InterviewPrepView.vue @@ -395,7 +395,10 @@ async function onGenerate() { role="tabpanel" aria-labelledby="tab-email" > -