[Fable 5][P2] apiFetch падает с SyntaxError на 204 No Content — DELETE кастомных POI и weight-профилей всегда «ошибается» после успешного удаления #1219

Closed
opened 2026-06-13 04:49:45 +00:00 by bot-backend · 0 comments
Collaborator

Из армейского ревью Claude Fable 5 (2026-06-11)

Confirmed by 2/2 адверсариальных верификатора, severity P2 (api-contract), confidence 0.85.

Файл

frontend/src/lib/api.ts:41

Evidence (цитата кода)

  if (!response.ok) {
    throw new Error(`API error ${response.status}: ${await response.text()}`);
  }
  return response.json() as Promise<T>;

Why bug

Backend DELETE /api/v1/custom-pois/{id} (custom_pois.py:108) и DELETE /api/v1/admin/site-finder/weight-profiles/{id} (admin_weight_profiles.py:101) объявлены status_code=204 с пустым телом. apiFetch безусловно вызывает response.json() — на пустом теле промис реджектится «Unexpected end of JSON input». Мутации useDeleteCustomPoi и useDeleteProfile уходят в onError, onSuccess-инвалидация списка не выполняется: POI/профиль реально удалён на сервере, но остаётся в UI (карта SiteMap, список профилей) до случайного рефетча, плюс пользователю показывается ошибка.

Suggested fix

В apiFetch обрабатывать 204/пустое тело как в apiFetchWithStatus: читать response.text(), парсить только непустую строку, иначе возвращать undefined; либо перевести оба DELETE-хука на apiFetchWithStatus.

Verify notes

  • [real/P2] Трасса: SiteMap.tsx:453 → useDeleteCustomPoi → apiFetch; backend отдаёт 204 с пустым телом; 204 проходит response.ok, response.json() реджектится SyntaxError → onSuccess/invalidateQueries не выполняются → удалённый POI остаётся на карте (staleTime 5 мин, refetchOnWindowFocus:false), 100% repro. apiFetchWithStatus особо обрабатывает 204 — баг известен команде. Уточнения: ошибка юзеру не показывается (silent fail), useDeleteProfile без UI-консьюмера (latent). P2.
  • [real/P2] Подтверждено по коду: backend отдаёт 204 с пустым телом, apiFetch на ok-ответе безусловно зовёт response.json() → reject, onSuccess-инвалидация useDeleteCustomPoi не выполняется. Защит нет: providers.tsx без MutationCache, SiteMap.tsx:453 fire-and-forget, CustomPoiLayer без optimistic-removal — POI удалён в БД, но висит на карте; повторный delete даёт 404. Нюансы: ошибка юзеру НЕ показывается (silent no-op), useDeleteProfile нигде не подключён (латентно). Живой путь — custom POI. P2.

Provenance

  • Finders: fe-app-lib
  • Confidence: 0.85
  • Originally batched at: main@d796027
## Из армейского ревью Claude Fable 5 (2026-06-11) Confirmed by 2/2 адверсариальных верификатора, severity **P2** (api-contract), confidence 0.85. ## Файл [`frontend/src/lib/api.ts:41`](frontend/src/lib/api.ts:41) ## Evidence (цитата кода) ``` if (!response.ok) { throw new Error(`API error ${response.status}: ${await response.text()}`); } return response.json() as Promise<T>; ``` ## Why bug Backend DELETE /api/v1/custom-pois/{id} (custom_pois.py:108) и DELETE /api/v1/admin/site-finder/weight-profiles/{id} (admin_weight_profiles.py:101) объявлены status_code=204 с пустым телом. apiFetch безусловно вызывает response.json() — на пустом теле промис реджектится «Unexpected end of JSON input». Мутации useDeleteCustomPoi и useDeleteProfile уходят в onError, onSuccess-инвалидация списка не выполняется: POI/профиль реально удалён на сервере, но остаётся в UI (карта SiteMap, список профилей) до случайного рефетча, плюс пользователю показывается ошибка. ## Suggested fix В apiFetch обрабатывать 204/пустое тело как в apiFetchWithStatus: читать response.text(), парсить только непустую строку, иначе возвращать undefined; либо перевести оба DELETE-хука на apiFetchWithStatus. ## Verify notes - [real/P2] Трасса: SiteMap.tsx:453 → useDeleteCustomPoi → apiFetch; backend отдаёт 204 с пустым телом; 204 проходит response.ok, response.json() реджектится SyntaxError → onSuccess/invalidateQueries не выполняются → удалённый POI остаётся на карте (staleTime 5 мин, refetchOnWindowFocus:false), 100% repro. apiFetchWithStatus особо обрабатывает 204 — баг известен команде. Уточнения: ошибка юзеру не показывается (silent fail), useDeleteProfile без UI-консьюмера (latent). P2. - [real/P2] Подтверждено по коду: backend отдаёт 204 с пустым телом, apiFetch на ok-ответе безусловно зовёт response.json() → reject, onSuccess-инвалидация useDeleteCustomPoi не выполняется. Защит нет: providers.tsx без MutationCache, SiteMap.tsx:453 fire-and-forget, CustomPoiLayer без optimistic-removal — POI удалён в БД, но висит на карте; повторный delete даёт 404. Нюансы: ошибка юзеру НЕ показывается (silent no-op), useDeleteProfile нигде не подключён (латентно). Живой путь — custom POI. P2. ## Provenance - Finders: `fe-app-lib` - Confidence: 0.85 - Originally batched at: main@d796027
bot-backend added the
Fable 5 ревью
label 2026-06-13 04:49:45 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1219
No description provided.