docs(log): UI-эпопея — 7 фаз стабилизации и выравнивания UI

This commit is contained in:
2026-09-08 22:12:49 +03:00
parent b94406e057
commit e5d8d4ed37

View File

@@ -531,3 +531,23 @@
- Как проверялось: vitest 129/129; check/lint/build чисто.
- Влияние на поиск/UX: нет (только корректное отображение ошибок).
- Подводные камни: часть правок ушла в параллельный коммит 0b2319b (ctx.tasks.sendMessage) — согласовано, потерь нет; apiRequest бросает BadRequestError на 400 — мутации с ожиданием {success:false} обязаны идти через сервисный слой с toServiceError.
---
## [UI-эпопея] Стабилизация и выравнивание UI (по аудиту «ui улучшения.md», 7 фаз)
- Статус: ✅ done (код, фазы 1-7 запушены: `7a98bf7`, `5d219ac`, `c4fdfe4`, `113ed45`, `aa8bf6a`, `820e886`, `b94406e`)
- Зачем: интерфейс «прыгал» (refetch-пачки на каждое SSE-событие, полноэкранные спиннеры, пересоздание Sidebar при навигации, вспышка скина/PWA-шрифта, location.href-перезагрузки), визуальный стиль разнороден (~1500 inline-размеров, Тема 2.0 потреблялась 4 файлами), чаты дублировали логику.
- Верификация чужого аудита по коду: направление верное, но часть утверждений опровергнута — Sidebar и MobileBottomNav НЕ смонтированы одновременно (early-return по useIsMobile); prefers-reduced-motion уже был; виртуализация уже была в TaskChat/TaskRegistry (нет только в мессенджере); дублирование чатов ~70% завышено (общий слой components/chat/ существовал); billing-ссылка — не location.href. Упущено аудитом: NotificationsDropdown.tsx:159 (полная перезагрузка по клику на уведомление), reload-петля safeLazy, двойной refetch Home.tsx+GlobalEventListener, мёртвые tw-animate-css и .btn-compact-классы, поллинг unread-count в обеих ветках навигации.
- Что изменено по фазам:
- Ф1 (`7a98bf7`): шкалы motion/z-index в `themes/tokens.ts` (BASE_CSS_VARS → :root в generated-skins.css) + Tailwind duration-fast/base/slow, ease-standard; boot-скрипт в `client/index.html` (theme/skin/font-size до первого paint — зеркалит ThemeProvider/usePwaFontSize); `design-system/recipes.ts` + ui/button,input,badge,card,table на рецептах (визуал по умолчанию неизменен); `layout.taskDetail` в скине (дефолт пресета карточки, маркер @preset в приоритете); удалены мёртвые .btn-compact/.btn-small/.btn-icon/.input-compact и зависимость tw-animate-css.
- Ф2 (`5d219ac`): `lib/throttledInvalidate.ts` (throttle 2с + invalidateQueries вместо refetchQueries в GlobalEventListener); убран дубль refetch в Home.tsx; `hooks/useUnreadBadges.ts` — единый хук счётчиков (SSE-инкременты, polling 30с только при обрыве SSE), useInboxCount удалён; TaskChat: polling 10с только при !isConnected, refetchInterval-fallback удалён; transition цветов на body/карточках (плавная смена скина); safeLazy — one-shot reload guard + ChunkLoadErrorFallback.
- Ф3 (`c4fdfe4`): `components/AppShell.tsx` — persistent shell (Sidebar/MobileBottomNav/биллинг/FAB монтируются один раз, список SHELL_ROUTE_PATTERNS); Layout.tsx — тонкий scroll-контейнер (23 страницы без правок); location.href → setLocation (NotificationsDropdown, тост «Ответить», TableEditor; /global-fields → wouter Redirect); `hooks/useScrollRestoration.ts`.
- Ф4 (`113ed45`): ui/skeleton.tsx — PageSkeleton/ListSkeleton/CardSkeleton/FormSkeleton; ~19 страниц мигрировано со спиннеров; `components/transitions/PageTransition.tsx` (fade 150ms, key=location, гейты skinComponents.motion.enabled + useReducedMotion) и `StaggerList.tsx` (Tasks, список диалогов Chat, моб. справочники); PageLoader → PageSkeleton; MotionConfig reducedMotion="user".
- Ф5 (`aa8bf6a`): ui/select,textarea,tabs,popover,tooltip,dropdown-menu,checkbox,dialog на рецептах/токенах (fallback = прежний вид); data-density/data-control-variant/data-button-radius на <html>; консолидация 44 мест `h-7 px-2.5 text-xs` → size="sm"; диалоги max-h-[85vh] + скролл; sheet-режим диалогов уже был реализован как full-screen на мобильном (не vaul) — оставлен.
- Ф6 (`820e886`): общий `chat/useMessagePolling.ts`; TaskChat на общий MessageBubble (generic + слоты renderContent/authorBadge/readReceipt/editingContent) и groupByDate; виртуализация мессенджера (порог 100) + автоскролл только у низа; TaskChat без RHF/zod (локальный стейт); анимация появления только последнего сообщения. TaskChat 2023→1754 строк.
- Ф7 (`b94406e`): self-host шрифты Inter var + DM Mono (8 woff2 в client/public/fonts, @font-face + preload, Google Fonts убран; DM Mono кириллицы не существует — системный fallback; italic синтезируется браузером); sw.js v13 (fonts в precache); focus-visible возвращён (`outline: 2px solid var(--ring)`, main:focus-visible без кольца — программный фокус); active:scale-[0.98] кнопок (починен мёртвый `active:not-disabled:` — Tailwind v3.4 не имеет not-* вариантов); биллинг-оверлей с exit-анимацией (AnimatePresence); тосты на --duration-base.
- Как проверялось: после каждой фазы `npm run check` (heap 6144), `npm run build`, `npx vitest run` (129/129), `npm run lint` (0 errors). Собранный CSS проинспектирован (font-face, focus-visible, duration-base, active:scale на месте).
- Влияние на поиск/UX: поиск не трогали (инвариант). UX: меньше запросов (нет refetch-пачек и постоянного 10-сек polling чата при живом SSE), нет перезагрузок по клику на уведомление, нет вспышки скина/шрифта, переходы/скелетоны/анимации.
- Подводные камни: (1) пузыри task-чата визуально унифицированы со стилем мессенджера (следствие общего MessageBubble) — семантика сохранена слотами; (2) boot-скрипт зеркалит ключи localStorage 'theme'/'skin'/'pwa-font-size-*' — при их изменении править и index.html; (3) Tailwind v3.4 НЕ поддерживает `not-*` варианты — классы молча не компилируются, проверять собранный CSS; (4) офлайн-очередь/reconciliation чатов не тронуты, но ручной smoke чатов (>100 сообщений, офлайн-отправка, «Загрузить ещё») на проде рекомендован; (5) организационный скин (activeSkinId) применяется после загрузки user — при расхождении с localStorage-скином возможна однократная смена после логина (не вспышка дефолта, приемлемо).
- Осталось за кадром (сознательно): slide-стек переходов на мобильном (решено: fade везде); полная миграция всех ~234 h-7 (сделаны 44 точных дубля); vaul bottom-sheet для диалогов (есть full-screen sheet); серверный /api/badges (не понадобился — polling стал fallback-only).