---
name: kodoceh_web_build_gotcha
description: "kodoceh-web — как ПРАВИЛЬНО собирать фронты (иначе Tailwind-утилиты выпиливаются, сайт без стилей); проверять визуально, не только текст"
metadata: 
  node_type: memory
  type: feedback
  originSessionId: b11e39bd-4f6f-47c9-ba7b-680ccf041a5b
---

Сборка фронтов kodoceh-web (Next.js + Tailwind в монорепо на mini): собирать **из папки приложения** — `cd ~/Разработки/kodoceh-web/apps/<app> && rm -rf .next && npx next build`, либо `npm run build -w apps/<app>`. НЕ `npx next build apps/<app>` из корня монорепо.

**Почему:** `tailwind.config` каждого приложения использует ОТНОСИТЕЛЬНЫЕ `content`-пути (`./app/**`, `../../packages/ui/src/**`). При сборке из корня CWD=корень → globs не находят файлы → Tailwind выпиливает ВСЕ утилиты (`.flex`, `.text-sm`, …) → CSS ~110 правил, сайт рендерится голым текстом (HTML и ассеты при этом 200, Content-Type верный — поэтому легко проглядеть). Хуже: битый CSS кэшируется в `.next`, и последующие сборки переиспользуют его → `rm -rf .next` обязателен при починке.

**How to apply:** (1) всегда собирай из папки приложения с чистым `.next`; (2) после сборки проверяй `grep -c '\.flex{' apps/<app>/.next/static/css/*.css` (должно быть ≥1) ИЛИ визуально — Playwright `document.styleSheets[..].cssRules.length` (норма сотни+, ~110 = битый) + скриншот. **Текстовые Playwright-проверки (`'Яндекс' in body`) НЕ ловят отсутствие стилей** — обязательно смотреть скриншот/rule-count при правках фронта. Инцидент 2026-06-26: всю сессию собирал из корня → все 4 сайта были без стилей, поймал только по скриншоту от юзера. Перезапуск фронта: [[kodoceh_web_admins_cabinets]] (kill по порту + `nohup npm start -w apps/<app>`).

**Мобайл-overflow (gotcha, 2026-06-26):** адаптивная сетка `grid … sm:grid-cols-2` БЕЗ базового `grid-cols-1` на мобиле = implicit `auto`(max-content) трек → элемент с длинным неразрывным контентом раздувает плитку шире вьюпорта, и она **обрезается за краем** (родитель клипает, `document.scrollWidth` остаётся = vw, поэтому «горизонтального скролла нет» — баг маскируется). Канон-фикс: **всегда задавать базовый `grid-cols-1`** (=`minmax(0,1fr)`) во всех адаптивных сетках. Плюс длинный неразрывный контент (VIN/OEM/email/деньги) — `min-w-0`+`break-all`/`break-words` на ячейке. Диагностика — не «на глаз», а Playwright-детектор `el.getBoundingClientRect().right > clientWidth` по всем `*` (скрипт `scratchpad/audit/overflow_probe.py`). Держать правило и для Автероа/Гаечкина. Решение D41.

**UX-проход 2 (2026-06-27, D44) — переиспользуемые правила:**
- **Плавающие/fixed поверхности ≠ `bg-panel`** (`--panel` ≈ 4% alpha — для инлайн-карточек, прозрачно для тоста). Завести непрозрачный `--elevated` в per-product globals.css (НЕ в общий `packages/tokens`). Тост ВакХантера был невидим именно из-за этого.
- **Ошибка fetch ≠ пусто и ≠ вечная «Загрузка…».** `.catch(()=>{})`/`.catch(()=>setItems([]))` — антипаттерн (висит спиннер / ложное «нет данных»). Канон: отдельный флаг `err`, общий `RetryBox` (ошибка + «Повторить» → перевызов load), таймаут-сторож на висящих `/auth/me`/poll. В Гаечкине все data-вью + админка, в Автерое auth/link.
- **Контраст мелкого акцентного текста:** бренд-акцент на светлом часто не проходит (Гаечкин #D8902E ~2.45:1). Завести `--accent-ink` (затемнённый) для текста; заливки/иконки — яркий акцент. `--muted/--muted-soft` в светлой теме держать ≥ ~#5b6b61 (как в ВакХантере). Иконку-как-единственный-смысл (в `@cbr/ui` нет warning-иконки) заменять текст-чипом.
- **Метод аудита:** фан-аут Workflow (read-only агенты по экранам, structured findings) → правки руками последовательно (одни файлы конфликтуют) → реальный прогон + Playwright. Онбординг-модалка `@cbr/ui` перехватывает клики в Playwright — гасить `localStorage['cbr_onb_<storageKey>']='1'` через add_init_script.

**Kill по ПОРТУ, не по имени (инцидент 2026-07-02, студия):** `pkill -f "next start"` НЕ убивает прод-процесс — в ps он называется `next-server (v14…)`. Итог: старый процесс пережил «рестарт», держал HTML старой сборки в памяти и после `rm -rf .next`+build отдавал старые css-хеши → 404/MIME text/html → сайт без стилей. Канон: `lsof -nP -iTCP:<port> -sTCP:LISTEN -t | xargs kill` перед стартом. Порт студии: 20410.

**Hero на тёмном видео/фоне:** классы, тянущие `var(--text)` (`.text-gradient`), нечитаемы на тёмном медиа в светлой теме — для таких мест theme-независимый класс (студия: `.text-gradient-onvideo`, белый→#5FD6A6).

**Деплой фронтов (обновлено 2026-07-02):** vakhunter-фронт УЖЕ под launchd `local.vakhunter-frontend` (WorkingDirectory=apps/vakhunter, `npm run start`); остальные могли остаться ручными `npm start` (detached, ppid=1). ⚠️ Инцидент 2026-07-02: старый РУЧНОЙ orphan `next-server` держал :20412 → launchd-инстанс crashloop EADDRINUSE («spawn scheduled»), а orphan отдавал СТАРЫЙ HTML → 400 на чанки новой сборки (страница белая). Канон перед рестартом: `lsof -nP -iTCP:<port> -sTCP:LISTEN` и если pid ≠ launchd-ребёнок — `kill <pid>`, потом kickstart. Рестарт после сборки ОБЯЗАТЕЛЕН (старый `next-server` держит чанки в памяти → после `rm -rf .next`+build отдаёт 404 на новые): `kill <npm-start-pid> <next-server-pid>` → `cd ~/Разработки/kodoceh-web/apps/<app> && export PATH=/opt/homebrew/opt/node@22/bin:$PATH && nohup npm start >/tmp/<app>-front.log 2>&1 & disown`. Порты: vakhunter 20412, gaechkin 20411, avtero 20413. CSS-здоровье после билда: ~37-39KB, `grep -c '\.flex{'`≥1.

**UX-проход 3 (2026-06-27, D46-D48) — обнаруживаемость:**
- **«Фича есть, но её не видно» = дефект, равный отсутствию.** Перед тем как делать новую фичу по просьбе юзера — проверь, не закопана ли существующая (fit-AI «Подойду ли я?» уже жила в дровере, автор её не нашёл). 
- **Онбординг `@cbr/ui Onboarding` монтирует РОДИТЕЛЬ** в `apps/<app>/app/app/page.tsx` (показ сам по `localStorage cbr_onb_<key>`). У ВакХантера был НЕ подключён (у Автероа — да; Гаечкин тоже без). Чек-лист нового продукта: «онбординг смонтирован?».
- **Обнаруживаемость без шума:** онбординг (ментальная модель) + кликабельный заголовок карточки (`group-hover:underline`) + вес ключевой кнопки в дровере (залить акцентом) — НЕ выносить AI-кнопки на карточку ленты (юзер против шума).
