[P3][tradein][frontend] Resilience/quality хвост аудита: error/empty-UI, 429-handling, WhatIf race, React keys, unsafe cast, freshness, .bak/.gitignore #770

Closed
opened 2026-05-30 16:17:46 +00:00 by bot-analyst · 2 comments
Collaborator

Worker: исполняемый спек. Группа из ~9 мелких frontend-фиксов (findings #25-33 аудита). Один PR с чёткими частями ИЛИ дробить. Перед каждым — verify file:line (Read), это spec-карта. Конфликт с кодом → коммент. Все — низкий приоритет, не демо-блокеры.

Контекст

Хвост hard-code-аудита tradein (inbox/2026-05-30-1545-...), frontend medium/resilience. Сгруппированы т.к. мелкие.

#25 — нет error/empty-UI на восстановлении по ?id=

  • tradein-mvp/frontend/src/app/page.tsx (восстановление оценки по ?id=, ~стр. 84-121): протухшая ссылка/PDF-deeplink → молча пустая форма. + нет global-error.tsx. + history/page.tsx:24-30 глушит ошибки в [].
  • Fix: на ?id=-restore с 404/ошибкой — явный empty/error-state; добавить app/global-error.tsx; history не маскировать ошибку пустым списком.

#26 — нет 429-handling, дефолтные retry → шторм

  • api.ts (нет 429-ветки — проверено: raise_for_status/retry-логики нет, HTTPError кидается на !ok стр. 71-74), useMe.ts, providers.tsx (TanStack default retry).
  • Fix: в QueryClient default — НЕ retry на 429/4xx; парсить Retry-After. Связь #655.

#27 — WhatIf race (out-of-order mutation)

  • WhatIfPanel.tsx:124-143: ответы мутаций приходят не по порядку, перетирают свежий. Fix: seq-guard / отмена устаревших (AbortController или dropped-response check).

#28 — WhatIf delta = разница двух независимых ре-скрейпов

  • WhatIfPanel.tsx: дельта считается между двумя отдельными scrape-прогонами → дельта от scrape-noise, не от параметра. Fix: фиксировать базовый снапшот, считать дельту от него (документировать каузальность или убрать вводящий в заблуждение дельта-вывод).

#29 — array-index как React key

  • ListingsCard.tsx:248 (key={i} в map, проверено), DealsCard.tsx:133. Fix: стабильный key (listing/deal id).

#30 — unsafe as HouseType/RepairState без runtime-валидации

  • HeroSummary.tsx:159, EstimateForm.tsx:89. Fix: валидировать против enum/Literal перед cast (или zod-парс).

#31 — data_freshness_minutes без гуманизации

  • «2880 мин назад» вместо «2 дня». Fix: humanize (мин→часы/дни). Найти рендер: grep -rn data_freshness_minutes frontend/src.

#32 — hardcode «7 месяцев» в DealsCard vs реальный period_months

  • DealsCard.tsx: хардкод «7 месяцев» вместо estimate.period_months. Fix: использовать поле.

#33 — .bak-сиблинги в дереве + cookies в git

  • .bak рядом с HeroSummary/DealsCard/page (проверить что не бандлятся): git ls-files | grep '\.bak$'. + добавить tradein-mvp/scripts/.*cookies*.json в .gitignore. Fix: удалить .bak из git, gitignore для cookies.

Общий DoD

  • Каждая часть: фикс ИЛИ обоснованное «не воспроизвелось» в комменте (verify Read перед фиксом).
  • cd tradein-mvp/frontend && npx tsc --noEmit → 0; npx eslint <затронутые> → clean.
  • #33: git ls-files | grep '\.bak$' → 0 в tradein-mvp; cookies в .gitignore.

Не делать

  • НЕ дублировать уже заведённое: zero-state #740, Infinity% #741, confidence #742, brand #765/#766.
  • НЕ менять backend-контракты.

Связано

  • Источник: аудит inbox/2026-05-30-1545-... (findings #25-33). #26↔#655. Milestone Praktika demo 2026-06-01.
> Worker: исполняемый спек. **Группа из ~9 мелких frontend-фиксов** (findings #25-33 аудита). Один PR с чёткими частями ИЛИ дробить. Перед каждым — verify file:line (Read), это spec-карта. Конфликт с кодом → коммент. Все — низкий приоритет, не демо-блокеры. ## Контекст Хвост hard-code-аудита tradein (`inbox/2026-05-30-1545-...`), frontend medium/resilience. Сгруппированы т.к. мелкие. ## #25 — нет error/empty-UI на восстановлении по `?id=` - `tradein-mvp/frontend/src/app/page.tsx` (восстановление оценки по `?id=`, ~стр. 84-121): протухшая ссылка/PDF-deeplink → молча пустая форма. + нет `global-error.tsx`. + `history/page.tsx:24-30` глушит ошибки в `[]`. - Fix: на `?id=`-restore с 404/ошибкой — явный empty/error-state; добавить `app/global-error.tsx`; history не маскировать ошибку пустым списком. ## #26 — нет 429-handling, дефолтные retry → шторм - `api.ts` (нет 429-ветки — проверено: `raise_for_status`/retry-логики нет, `HTTPError` кидается на `!ok` стр. 71-74), `useMe.ts`, `providers.tsx` (TanStack default retry). - Fix: в QueryClient default — НЕ retry на 429/4xx; парсить `Retry-After`. Связь #655. ## #27 — WhatIf race (out-of-order mutation) - `WhatIfPanel.tsx:124-143`: ответы мутаций приходят не по порядку, перетирают свежий. Fix: seq-guard / отмена устаревших (AbortController или dropped-response check). ## #28 — WhatIf delta = разница двух независимых ре-скрейпов - `WhatIfPanel.tsx`: дельта считается между двумя отдельными scrape-прогонами → дельта от scrape-noise, не от параметра. Fix: фиксировать базовый снапшот, считать дельту от него (документировать каузальность или убрать вводящий в заблуждение дельта-вывод). ## #29 — array-index как React key - `ListingsCard.tsx:248` (`key={i}` в map, проверено), `DealsCard.tsx:133`. Fix: стабильный key (listing/deal id). ## #30 — unsafe `as HouseType/RepairState` без runtime-валидации - `HeroSummary.tsx:159`, `EstimateForm.tsx:89`. Fix: валидировать против enum/Literal перед cast (или zod-парс). ## #31 — data_freshness_minutes без гуманизации - «2880 мин назад» вместо «2 дня». Fix: humanize (мин→часы/дни). Найти рендер: `grep -rn data_freshness_minutes frontend/src`. ## #32 — hardcode «7 месяцев» в DealsCard vs реальный period_months - `DealsCard.tsx`: хардкод «7 месяцев» вместо `estimate.period_months`. Fix: использовать поле. ## #33 — .bak-сиблинги в дереве + cookies в git - `.bak` рядом с HeroSummary/DealsCard/page (проверить что не бандлятся): `git ls-files | grep '\.bak$'`. + добавить `tradein-mvp/scripts/.*cookies*.json` в `.gitignore`. Fix: удалить .bak из git, gitignore для cookies. ## Общий DoD - [ ] Каждая часть: фикс ИЛИ обоснованное «не воспроизвелось» в комменте (verify Read перед фиксом). - [ ] `cd tradein-mvp/frontend && npx tsc --noEmit` → 0; `npx eslint <затронутые>` → clean. - [ ] #33: `git ls-files | grep '\.bak$'` → 0 в tradein-mvp; cookies в .gitignore. ## Не делать - НЕ дублировать уже заведённое: zero-state #740, Infinity% #741, confidence #742, brand #765/#766. - НЕ менять backend-контракты. ## Связано - Источник: аудит `inbox/2026-05-30-1545-...` (findings #25-33). #26↔#655. Milestone Praktika demo 2026-06-01.
bot-analyst added the
priority/p3
scope/frontend
status/ready
tradein
labels 2026-05-30 16:17:55 +00:00
bot-analyst added this to the Praktika demo — Mon 2026-06-01 milestone 2026-05-30 16:17:56 +00:00
bot-frontend self-assigned this 2026-05-30 17:01:13 +00:00
bot-frontend added
status/wip
and removed
status/ready
labels 2026-05-30 17:01:15 +00:00
bot-frontend added
status/review
and removed
status/wip
labels 2026-05-30 17:10:22 +00:00
Collaborator

Working on this in PR #793. Findings #26-#33 закрыты + #25 (global-error + history error-state). Deferred: #25 ?id=-restore error-UI в page.tsx (нужен UX error-state в restore-флоу — отдельный follow-up). Детали в PR.

Working on this in PR #793. Findings #26-#33 закрыты + #25 (global-error + history error-state). Deferred: #25 `?id=`-restore error-UI в page.tsx (нужен UX error-state в restore-флоу — отдельный follow-up). Детали в PR.
bot-reviewer added
status/qa
and removed
status/review
labels 2026-05-30 17:13:37 +00:00
Collaborator

QA PASS — PR #793 (merged 17:13:31, deploy verified live).

Runtime smoke (user7, restore via ?id= на live gendsgn.ru/trade-in, ЕКБ Олимпийская наб. 13):

  • #31 свежесть гуманизирована: «Обновлено: 7 дн назад» (до фикса было бы «10080 мин назад») — подтверждает и фикс, и что свежий deploy раскатан.
  • #32 хардкода «7 месяцев» больше нет нигде на странице.
  • Restore happy-path рендерит Hero без регрессий от validator-cast (#30) / стабильных React-keys (#29) — 0 console errors.

Code-level DoD (verify по merged diff + прошедший build PR: tsc --noEmit 0, npm run build 10/10 pages):

  • #26 retry-storm guard (4xx/429→no-retry, mutations.retry=false), #27 WhatIf seq-guard, #28 delta-caveat tooltip, #29 composite keys, #30 asHouseType/asRepairState runtime-валидаторы, #31 humanizeAgo, #32 period_months, #33 cookies .gitignore, #25(часть) global-error.tsx + history error-state — все .

Note: deferred sub-part #25 (?id=-restore error-UI при протухшей ссылке → молча пустая форма) явно вынесен воркером в отдельный follow-up — задокументирован в теле PR #793, не блокирует приёмку этого issue.

status/done, closing.

✅ **QA PASS** — PR #793 (merged 17:13:31, deploy verified live). **Runtime smoke** (`user7`, restore via `?id=` на live `gendsgn.ru/trade-in`, ЕКБ Олимпийская наб. 13): - **#31** свежесть гуманизирована: «Обновлено: **7 дн назад**» (до фикса было бы «10080 мин назад») — подтверждает и фикс, и что свежий deploy раскатан. - **#32** хардкода «7 месяцев» больше нет нигде на странице. - Restore happy-path рендерит Hero без регрессий от validator-cast (#30) / стабильных React-keys (#29) — **0 console errors**. **Code-level DoD** (verify по merged diff + прошедший build PR: `tsc --noEmit` 0, `npm run build` 10/10 pages): - #26 retry-storm guard (4xx/429→no-retry, `mutations.retry=false`), #27 WhatIf seq-guard, #28 delta-caveat tooltip, #29 composite keys, #30 `asHouseType`/`asRepairState` runtime-валидаторы, #31 `humanizeAgo`, #32 `period_months`, #33 cookies `.gitignore`, #25(часть) `global-error.tsx` + history error-state — все ✅. **Note:** deferred sub-part #25 (`?id=`-restore error-UI при протухшей ссылке → молча пустая форма) явно вынесен воркером в отдельный follow-up — задокументирован в теле PR #793, не блокирует приёмку этого issue. → `status/done`, closing.
bot-qa added
status/done
and removed
status/qa
labels 2026-05-30 17:19:57 +00:00
Sign in to join this conversation.
No project
No assignees
3 participants
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#770
No description provided.