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.
This commit is contained in:
parent
fc645d276f
commit
e4f4b0c67f
3 changed files with 58 additions and 6 deletions
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ export interface FullJobDetail {
|
|||
export const usePrepStore = defineStore('prep', () => {
|
||||
const research = ref<ResearchBrief | null>(null)
|
||||
const contacts = ref<Contact[]>([])
|
||||
const contactsError = ref<string | null>(null)
|
||||
const taskStatus = ref<TaskStatus>({ status: null, stage: null, message: null })
|
||||
const fullJob = ref<FullJobDetail | null>(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 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,
|
||||
|
|
|
|||
|
|
@ -395,7 +395,10 @@ async function onGenerate() {
|
|||
role="tabpanel"
|
||||
aria-labelledby="tab-email"
|
||||
>
|
||||
<template v-if="prepStore.contacts.length">
|
||||
<div v-if="prepStore.contactsError" class="error-state" role="alert">
|
||||
{{ prepStore.contactsError }}
|
||||
</div>
|
||||
<template v-else-if="prepStore.contacts.length">
|
||||
<div
|
||||
v-for="contact in prepStore.contacts"
|
||||
:key="contact.id"
|
||||
|
|
@ -710,6 +713,16 @@ async function onGenerate() {
|
|||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
/* Inline error state for tab panels (e.g. contacts fetch failure) */
|
||||
.error-state {
|
||||
background: color-mix(in srgb, var(--color-error) 8%, var(--color-surface));
|
||||
color: var(--color-error);
|
||||
border: 1px solid color-mix(in srgb, var(--color-error) 25%, transparent);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
/* ── Research sections ───────────────────────────────────────────────────── */
|
||||
.research-sections {
|
||||
display: flex;
|
||||
|
|
|
|||
Loading…
Reference in a new issue