Revalidate the draft pool on save, refuse duplicate swaps, refresh the Quest log after a draft
Feature 14 integration fix (Maestro 857e4c33-e250-46ec-b5ea-7dd4f3b719d9). - F14-01: storeDraft re-reads the draft pool (ListDraftPool, the same eligibility rule draft_week was given) inside the save transaction. A day whose dinner was archived, moved to tried, or planned on another current or later week while draft_week ran fails the job with the new retryable reason pool_changed and stores nothing. - F14-02: Swap refuses a dinner already on another day of the week inside the write transaction; the API answers 409 dinner_already_planned. - F14-03: a finished draft invalidates the Quest log query, as the swap path does, so the tablet strip and desktop panel drop dinners it planned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -972,6 +972,31 @@ describe.each([
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
||||
await waitFor(() => expect(posts(fetchMock)).toEqual([`${DRAFT} {}`]))
|
||||
})
|
||||
it('reads the Quest log again once a draft finishes, dropping the dinners it planned', async () => {
|
||||
let plan = planBody('draft')
|
||||
let job = draftJob('running')
|
||||
let questLog = [dinner('q1', 'Lemon orzo', 'easy', 25)]
|
||||
stubServer({
|
||||
...lists,
|
||||
[`GET ${THIS_WEEK}`]: () => jsonResponse(200, plan),
|
||||
[`POST ${DRAFT}`]: () => jsonResponse(202, { jobId: 'j1' }),
|
||||
[`GET ${JOB}`]: () => jsonResponse(200, job),
|
||||
[`GET ${QUEST_LOG}`]: () => jsonResponse(200, { dinners: questLog }),
|
||||
})
|
||||
await renderPage()
|
||||
|
||||
const list = await screen.findByRole('list', { name: layout === 'desktop' ? 'Quest log' : 'Quest log dinners' })
|
||||
expect(await within(list).findByText('Lemon orzo')).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Rebuild week' }))
|
||||
expect(await screen.findByRole('region', { name: 'Drafting this week' })).toBeInTheDocument()
|
||||
|
||||
plan = planBody('draft', ['Lemon orzo', ...NAMES.slice(1)])
|
||||
questLog = []
|
||||
job = draftJob('succeeded')
|
||||
const week = await screen.findByRole('list', { name: 'Dinners this week' }, { timeout: 3000 })
|
||||
expect(await within(week).findByText('Lemon orzo', {}, { timeout: 3000 })).toBeInTheDocument()
|
||||
await waitFor(() => expect(within(list).queryByText('Lemon orzo')).not.toBeInTheDocument())
|
||||
})
|
||||
})
|
||||
|
||||
// Dragging a dinner from desktop's panel or tablet's strip onto a day swaps
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useQueryClient } from '@tanstack/react-query'
|
||||
import { CalendarDays, Check, ChevronLeft, ChevronRight, Plus, RefreshCw, ShoppingCart } from 'lucide-react'
|
||||
import { useEffect, useId, useMemo, useState, type ReactNode } from 'react'
|
||||
import { useJob } from '../api/index.ts'
|
||||
import { QUEST_LOG_KEY } from '../discover/api.ts'
|
||||
import { Confirm } from '../settings/LeaveGuard.tsx'
|
||||
import { methodLabels } from '../settings/RegularsSettingsPage.tsx'
|
||||
import { formatClockTime, useHouseholdSettings } from '../settings/api.ts'
|
||||
@@ -57,6 +58,7 @@ const draftFailures: Record<string, string> = {
|
||||
ai_failed: "The AI assistant couldn't draft it.",
|
||||
confirm_required: 'The plan was pushed to Calendar while it was drafting.',
|
||||
plan_changed: 'The plan was edited while it was drafting.',
|
||||
pool_changed: 'A dinner it picked was archived, tried or planned for another week while it was drafting.',
|
||||
internal_error: 'Something went wrong on the server.',
|
||||
}
|
||||
|
||||
@@ -239,12 +241,14 @@ function WeekBoard({
|
||||
|
||||
// Once the job ends the plan is read again: a succeeded draft is followed
|
||||
// until that read lands; a failed one stays shown, and may have met a
|
||||
// plan changed meanwhile.
|
||||
// plan changed meanwhile. The Quest log is read again too, as after a
|
||||
// swap: the draft may have planned some of it.
|
||||
const [readJobId, setReadJobId] = useState<string | null>(null)
|
||||
const ended = job.status === 'succeeded' || job.status === 'failed'
|
||||
useEffect(() => {
|
||||
if (!ended) return
|
||||
let active = true
|
||||
void queryClient.invalidateQueries({ queryKey: QUEST_LOG_KEY })
|
||||
void queryClient.invalidateQueries({ queryKey: planKey(weekStart) }).then(() => {
|
||||
if (active) setReadJobId(jobId)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user