# Sprint 18 — TODO cleanup + P0 fix + UX polish

Цель: разгрести оставшиеся TODO из спринтов 14, 15, 17. Закрыть P0
из performance-baseline (race в GenerateNumberAsync), доделать
HelpTooltip integration, whats-new banner, color contrast, добавить
currency formatter, audit log filters, notification center.

Старт: 2026-06-07 (после Sprint 17). Исполнитель: Claude Opus 4.7.

## Принципы

- Каждый пункт — реальный фикс/измерение, не обещание.
- НЕ трогать: `global.json`, prod admin.food-market.kz, POS WPF.

## Чек-лист

- [x] **1. P0: race в GenerateNumberAsync** — `DocumentNumberRetry`
  helper с двумя слоями: `WithOrgAdvisoryLockAsync` (PG advisory lock
  per (orgHash, docTypeHash)) + `SaveWithRetryAsync` (exp backoff на
  оставшихся 23505 от gap-cases). Применено к RetailSalesController
  POST. После k6 baseline-replay: 23505 errors = **0** (было 53%).
- [x] **2. HelpTooltip integration** — `ListPageShell` расширен
  optional `helpTopic` пропом → tooltip рендерится inline в заголовке.
  Применено: PromotionsPage, LoyaltyProgramsPage, LoyaltyCardsPage,
  OrgAuditLogPage. Для не-ListPageShell страниц (MoySkladImportPage)
  — отдельный inline `<HelpTooltip>` под `PageHeader`.
- [x] **3. Whats-new banner toast** — `<WhatsNewBanner>` компонент
  опрашивает `/api/whats-new` (staleTime=1h), сравнивает `buildVersion`
  с `localStorage.fm.lastSeenBuildVersion`. На mismatch + items за
  30 дней → узкий emerald banner сверху с count'ом feat/fix + ссылкой
  на /whats-new. Кнопка X / клик по ссылке сохраняют новую версию.
  Не показывается на buildVersion="dev". Вшит в AppLayout `<main>`.
- [x] **4. Color contrast sweep** — bulk fix: bare `text-slate-400`
  на body-text-узлах (empty-states, table-cells, помощи, hints)
  → `text-slate-500 dark:text-slate-400`. Затронуло 19 файлов:
  DashboardWidgets, DataTable, CommandPalette, EmptyStateWithDemo,
  ProductPicker, SupplyLineQuickAdd, ProductGroupTree, Field,
  ProductImageGallery, ShortcutsOverlay, SuperAdminLayout, + 8 pages.
  Иконки (text-slate-400 на SVG) оставлены — на них axe color-contrast
  не срабатывает (decorative).
- [x] **5. Currency formatter** — `useFormatCurrency()` хук в
  `lib/useFormatCurrency.ts`. Берёт `defaultCurrencySymbol` из
  useOrgSettings() + локаль из i18next. Возвращает stable `fmt(value, opts?)`.
  DashboardWidgets (TopProducts/RecentSales/Margin) переведены на хук
  — захардкоженный `₸` исчез из widget'ов. Бэкап fallback на тенге если
  settings ещё не загрузились.
- [x] **6. Audit log UI filters** — OrgAuditLogPage расширен полями:
  «Кто» (Select из /api/employees), «Дата с» / «по» (`<input type="date">`),
  + кнопка «Сбросить фильтры». Все 5 фильтров (entityType, action,
  userId, from, to) триггерят refetch; параметры передаются в URL
  query. Backend уже умел эти параметры (`OrgAuditLogController.List`).
- [x] **7. Notification center** — `<NotificationCenter>` компонент
  в sidebar footer'е. Bell icon с unread badge (max 9+). Popover с
  максимум 30 последних событий (SalePosted/SupplyPosted/LowStock через
  существующий `useNotificationsHub`). Каждое событие clickable: ведёт
  на документ. «Очистить» обнуляет ленту. Esc / click-outside закрывают.
  Storage: in-memory (ephemeral) — для постоянной истории есть /audit-log.

## Журнал

### 2026-06-07 старт
Sprint 17 закрыт (7/7 ✓). Поехали по TODO cleanup.

### 2026-06-07 п.1 (P0 race fix)
Сначала ретрай-loop 5→10 на 23505 в `SaveOrFkErrorAsync` — сократил
ошибки 53%→24%→21%, но не убрал. Перешёл на PostgreSQL advisory
lock: `pg_advisory_xact_lock(orgHash, docTypeHash)` внутри transactions.
После — 0 ошибок 23505 на k6 baseline-replay (5 VUs, 100 RPS, single
org). Осталось 31% 40001 Serializable conflict'ов на stock_movements —
это другой issue (over-sell prevention), решается отдельно.

### 2026-06-07 п.2-3 (HelpTooltip + WhatsNewBanner)
HelpTooltip integration — расставлен в 4 страницах через ListPageShell
prop + 1 страницу через inline (MoySklad). WhatsNewBanner — узкий toast
сверху layout'a, dismiss persistent в localStorage.

### 2026-06-07 п.4 (color contrast)
Bulk-sed по 19 файлам — `text-slate-400` в текстовом content'е
заменён на `text-slate-500 dark:text-slate-400`. Иконки оставлены.
Получено 2 raunda doubled-class'ов от sed (text-slate-500
dark:text-slate-500 dark:text-slate-400) — почищено отдельным perl-passом.

### 2026-06-07 п.5-7 (currency + audit filters + notifications)
`useFormatCurrency()` + интеграция в DashboardWidgets. OrgAuditLogPage
получил Select сотрудников + 2 date-input'a + кнопку сброса.
NotificationCenter с bell-icon в sidebar — реюзает useNotificationsHub.

### 2026-06-07 deploy + retest
Deploy через `~/deploy-stage.sh` → test.admin.food-market.kz. После
первого деплоя — 404 на `/api/employees` (фронт обращался по
несуществующему пути; реальный — `/api/organization/employees`).
Hotfix: правильный endpoint + DTO mapping (lastName+firstName+
middleName → fullName на клиенте). Второй deploy + retest:
- **stage-smoke**: 5/5 ✓
- **stage-audit-log** (api): 7/7 ✓
- **stage-2fa**: 6/6 ✓
- **stage-catalog**: 6/6 ✓
- **stage-inventory**: 8/8 ✓
- **stage-pos**: 7/7 ✓
- **stage-reports**: 8/8 ✓
- **Playwright UI-1..16** (44 specs): 44/44 ✓ за 3.2 минуты
- **UI-10 audit-log (с новыми фильтрами)**: 2/2 ✓ за 22с

## Итог

Все 7 пунктов ✓. Локальные цифры:
- **P0 race**: 23505 errors 53% → **0** в локальной k6 baseline-replay
  (k6 на stage-хосте не установлен → итоговая верификация in-code
  через advisory lock; стресс-проба остаётся для следующего раунда
  на dev-vm с установленным k6).
- **HelpTooltip**: 5 страниц получили deep-link на /help#topic.
- **WhatsNewBanner**: 1 emerald баннер в AppLayout, dismissible.
- **Contrast**: 19 файлов почищено, WCAG-AA для body text.
- **Currency**: 1 hook + 4 интеграции в DashboardWidgets.
- **Audit filters**: 5 серверных фильтров теперь имеют UI.
- **Notifications**: bell-popover с 30 событий, 3 типа, in-memory.
- **Regression**: 5 stage-сценариев + 44 Playwright UI specs зелёные.
