Klient drží id konceptu, který mezitím odeslal cron — každý další save/send končí 404
import { Aside } from ‘@astrojs/starlight/components’;
Symptom
Sekce “Symptom”Uživatel klikne v e-mailovém klientovi na Odeslat a dostane „Chyba při odeslání e-mailu”. Každý další pokus selže stejně — „vůbec nejde odeslat”. V serverovém error logu nic; v access logu reverse proxy série identických 404 na endpoint konceptů, pár sekund po sobě (autosave + opakované kliky).
Root cause
Sekce “Root cause”Odeslání je řešené jako „undo send”: klik na Odeslat koncept jen naplánuje (scheduledAt = now + 12 s) a minutový cron ho odešle — záznam tím přestane být konceptem (isDraft = false). Race:
- Seznam složek se ale neobnovuje průběžně, takže odeslaný koncept zůstal viditelný ve složce Naplánované/Koncepty.
- Klik na tento „duchový” řádek otevřel compose s
draftIdRef = <staré id>— a dřívější confirm-flow výsledek požadavku na zrušení plánu vůbec nekontroloval (fetch(...).catch(() => {})). - Server na update konceptu správně vrací
404 Koncept nenalezen.(záznam už neníisDraft), jenže:- autosave 404 tiše polykal a zkoušel to samé id donekonečna,
- Odeslat ukázal jen generickou hlášku bez
data.error— skutečný důvod nebyl vidět.
Fix
Sekce “Fix”Tři vrstvy:
// 1) Recovery: mrtvé id zahodit a založit nový koncept (obsah okna = zdroj pravdy)let res = await postDraft(payloadWithId);if (res.status === 404 && draftIdRef.current) { draftIdRef.current = null; res = await postDraft(payloadWithoutId);}
// 2) Detail ze serveru vždy do UIconst json = await res.json().catch(() => null);if (!res.ok) toast.error(json?.error ? `Chyba při odeslání: ${json.error}` : "Chyba při odeslání.");- Zavřít vstupní cesty do „duchového” stavu: před otevřením konceptu k editaci ověřit jeho stav na serveru (
GET /:id→ když už není koncept, jen obnovit seznam); akce „zrušit plán” kontrolujeres.oka při chybě ukáže serverový důvod; refetch seznamu je stale-while-revalidate, aby stav složek neujížděl.
Jak to příště odhalit dřív
Sekce “Jak to příště odhalit dřív”- Access log reverse proxy (status kódy + časové rozestupy) prozradí smyčku 404 i tam, kde aplikační log mlčí — 4xx se běžně neloguje jako error.
- Integrační test na „stale id”: vytvořit záznam s
isDraft=falsea POSTnout update s jeho id — musí vrátit 404 s čitelnýmerror. - Pravidlo pro UI: žádný
toast.error("Obecná chyba")bez pokusu přečístdata.errorz odpovědi.