Перейти к основному содержимому

План архитектурного рефакторинга BELF

Архив. Этот план заменён docs/quality-gates-refactor-plan.md как единственным исполняемым документом для текущего цикла. Он сохранён только как исторический контекст и не должен использоваться как источник параллельных задач или критериев готовности.

1. Назначение документа

Этот документ — исполняемое техническое задание для AI-агента или разработчика. Цель работы — не внести ещё одну локальную правку в navbar, а привести проект к предсказуемой архитектуре, сохранить текущий дизайн и закрыть визуальные регрессии автоматическими проверками.

Работа считается завершённой только тогда, когда выполнены все этапы и пройден quality gate из раздела 10.

2. Контекст проблемы

Текущая версия сайта работает и проходит базовые проверки, но архитектура пока не соответствует требованию «чистый и понятный код» в полной мере.

Основные проблемы:

  • стили страниц собраны в крупных монолитных файлах;
  • глобальные правила и правила отдельных секций частично смешаны;
  • navbar зависит от неявных DOM-контрактов и стилей страниц;
  • тема шапки определяется глобальным поиском элементов в DOM;
  • отдельные компоненты содержат слишком много независимых обязанностей;
  • проверка npm run check не включает Playwright;
  • GitHub Pages может начать публикацию без полного набора проверок;
  • автоматические тесты проверяют CSS-свойства navbar, но не гарантируют отсутствие визуального изменения прозрачности на уровне пикселей.

3. Обязательный результат

После рефакторинга проект должен обладать следующими свойствами:

  1. Navbar одинаково и предсказуемо работает на всех страницах.
  2. При открытии меню внешний вид шапки не меняется: фон, прозрачность, blur, контраст логотипа, кнопки открытия/закрытия и CTA остаются согласованными с текущей секцией.
  3. На мобильных устройствах меню является полноэкранной модальной страницей без градиента, просвечивания и смещения основного контента.
  4. На desktop меню компактное и не превращается в растянутую hero-секцию.
  5. Стили каждой секции находятся рядом с её компонентом и не протекают на другие страницы.
  6. Компоненты имеют одну понятную ответственность.
  7. Контакты, данные навигации и повторяемый контент имеют один источник истины.
  8. Любой pull request проходит форматирование, линтеры, unit/component tests, production build и браузерные тесты до публикации.
  9. Внешний вид на поддерживаемых разрешениях не ухудшается относительно исходной версии.

4. Неприкосновенные ограничения

  • Не менять дизайн, тексты, маршруты, SEO и пользовательский сценарий без прямого требования.
  • Не добавлять градиенты в navbar или его overlay.
  • Не менять прозрачность шапки только из-за открытия или закрытия меню.
  • Не размещать стили navbar в CSS страниц.
  • Не использовать !important как способ скрыть архитектурную проблему.
  • Не отключать правила ESLint, Stylelint, TypeScript или тесты ради зелёного CI.
  • Не удалять существующие тесты без равноценной или более сильной замены.
  • Не обновлять visual snapshots вслепую: каждое изменение сначала просмотреть.
  • Использовать иконки из уже установленного react-icons; не добавлять ещё одну библиотеку иконок без необходимости.
  • Сохранять SSR/build-поведение и существующие пути /, /partners/, /careers/.
  • Не коммитить и не отправлять изменения в remote без отдельного указания владельца репозитория.
  • Не стирать и не перезаписывать несвязанные изменения в рабочем дереве.

5. Целевая структура

Ориентир для структуры проекта:

src/
app/
entrypoints/
mountApp.tsx
components/
navigation/
SiteHeader/
SiteHeader.tsx
SiteHeader.module.css
SiteHeader.test.tsx
NavigationOverlay/
NavigationOverlay.tsx
NavigationOverlay.module.css
NavigationGroups/
NavigationContact/
model/
navigation.types.ts
useHeaderTheme.ts
useNavigationDialog.ts
footer/
ui/
features/
calculator/
components/
model/
tests/
pages/
home/
HomePage.tsx
sections/
HeroSection/
PlatformSection/
ModulesSection/
...
partners/
PartnersPage.tsx
sections/
careers/
CareersPage.tsx
sections/
shared/
data/
types/
utils/
styles/
fonts.css
tokens.css
reset.css
global.css

Это ориентир, а не требование механически переименовать каждый файл. Любое отклонение допустимо, если оно уменьшает связанность и остаётся последовательным.

6. План выполнения

Этап 0. Зафиксировать исходное состояние

Перед первой правкой:

  1. Выполнить git status --short и сохранить список уже существующих изменений.
  2. Выполнить npm run check и npm run test:e2e.
  3. Снять эталонные скриншоты всех страниц с закрытым и открытым navbar на разрешениях:
    • 390 × 844;
    • 430 × 932;
    • 768 × 1024;
    • 1024 × 768;
    • 1440 × 900;
    • 1920 × 1080.
  4. Отдельно зафиксировать navbar над светлой секцией, над тёмной/зелёной секцией и немного ниже hero-секции.
  5. Не начинать рефакторинг, если baseline уже красный: сначала описать исходную ошибку и отделить её от новых изменений.

Этап 1. Установить архитектурные границы

  1. Оставить в глобальных CSS только:
    • подключение шрифтов;
    • design tokens;
    • reset;
    • базовые правила html, body, main;
    • действительно общие утилиты.
  2. Удалить из page CSS глобальные правила для html, body, main, .container и общих [id].
  3. Запретить страницам стилизовать внутренние классы navbar и footer.
  4. Перенести данные, общие для нескольких страниц, в единый модуль shared/data либо сохранить в src/data, если перенос не даёт реальной пользы.

Критерий завершения: глобальные стили не содержат правила конкретных секций, а компонентные стили не зависят от порядка подключения CSS страниц.

Этап 2. Изолировать navbar

  1. Собрать SiteHeader, NavigationOverlay, NavigationGroups и NavigationContact в самостоятельный модуль.
  2. Перевести их стили на CSS Modules или другую локально изолированную схему.
  3. Убрать зависимость от page-класса link-icon; размеры иконок должны задаваться самим компонентом, который ими владеет.
  4. Ввести явную модель состояния:
type HeaderTone = "light" | "dark";

type NavigationState = {
isOpen: boolean;
headerTone: HeaderTone;
mode: "desktop" | "mobile";
};
  1. Открытие меню должно менять только состояние диалога. Оно не должно переопределять фон, opacity, backdrop-filter или tone шапки.
  2. Крестик и логотип получают контрастный цвет из одного вычисленного headerTone, а не из набора несвязанных CSS-исключений.
  3. Mobile overlay должен иметь сплошной зелёный фон, position: fixed, корректный inset, собственный scroll и слой выше страницы.
  4. Desktop overlay должен иметь ограниченную ширину/высоту, достаточные внутренние отступы и не копировать композицию hero.

Критерий завершения: navbar визуально одинаков до и после открытия, кроме появления самого меню и замены menu icon на close icon.

Этап 3. Сделать тему шапки явной

Текущий глобальный поиск [data-nav-theme] заменить на явный контракт.

Предпочтительный вариант:

  1. Создать NavThemeRegion, который регистрирует секцию и её HeaderTone.
  2. Хранить реестр секций в React context.
  3. Использовать один IntersectionObserver для определения активной секции под контрольной линией шапки.
  4. Вынести высоту/контрольную точку шапки в именованную константу или CSS custom property; не использовать magic number 42.
  5. На страницах явно оборачивать только те секции, где меняется контраст.

Допустим более простой вариант с props, если он одинаково работает со всеми страницами и не возвращает глобальный querySelectorAll.

Критерий завершения: по коду страницы видно, какая секция требует светлую или тёмную шапку; механизм не зависит от случайных DOM-атрибутов.

Этап 4. Разрезать монолитные стили

Мигрировать не весь CSS одним большим переписыванием, а по одной секции:

  1. Выбрать секцию.
  2. Перенести её компонент и стили в одну директорию.
  3. Локализовать селекторы.
  4. Проверить страницу визуально на всех контрольных ширинах.
  5. Удалить перенесённые правила из монолитного файла.
  6. Запустить Stylelint и релевантные Playwright-тесты.
  7. Только после этого переходить к следующей секции.

Очередность:

  1. navigation;
  2. footer;
  3. home hero;
  4. calculator;
  5. остальные home sections;
  6. partners;
  7. careers.

Итог: home.css, partners.css и careers.css должны исчезнуть либо остаться тонкими файлами композиции без стилей отдельных секций.

Этап 5. Нормализовать design tokens

  1. Проинвентаризировать цвета, отступы, радиусы, border, shadow и типографику.
  2. Создать семантические токены, например:
:root {
--color-brand: #159334;
--color-brand-dark: #07531f;
--color-surface: #ffffff;
--color-surface-muted: #f3f6f3;
--color-text: #111411;
--color-text-on-brand: #ffffff;
--color-border-subtle: rgb(17 20 17 / 14%);
}
  1. Не создавать токен на каждый единичный hex. Токены должны выражать назначение, а не просто переименовывать цвет.
  2. Удалять hardcoded-значения постепенно, одновременно с переносом секций.

Критерий завершения: повторяемые визуальные решения используют семантические токены; уникальные иллюстративные значения могут оставаться локальными.

Этап 6. Разделить крупные компоненты

  1. Разбить CalculatorSection на:
    • оболочку секции;
    • форму конфигурации;
    • элементы управления;
    • сводку/результат;
    • чистую модель расчёта;
    • форматирование результата.
  2. Разбить HeroSection, только если дочерние части имеют самостоятельную ответственность; не создавать компоненты ради каждой строки JSX.
  3. Разделить PartnersProgramSections и CareersSections по принципу «одна смысловая секция — один компонент».
  4. Оставить page-компоненты тонкой композицией секций.
  5. Вынести mailto, clipboard и форматирование контактов из JSX в тестируемые функции.

Критерий завершения: компонент можно понять без чтения нескольких несвязанных сценариев в одном файле.

Этап 7. Усилить тесты navbar

Unit/component tests должны проверять:

  • открытие и закрытие кнопкой;
  • закрытие по Escape;
  • возврат фокуса на trigger;
  • focus trap;
  • блокировку scroll;
  • закрытие после выбора ссылки;
  • корректные aria-expanded, aria-controls, role="dialog" и accessible name;
  • единый источник контактов;
  • вычисление контрастной темы на светлой и зелёной секции.

Playwright должен проверять для каждого маршрута и viewport:

  • отсутствие горизонтального overflow;
  • открытие/закрытие меню;
  • mobile full-screen overlay;
  • desktop compact overlay;
  • сохранение computed background-color, opacity, backdrop-filter и цвета logo/button до и после открытия;
  • отсутствие смещения документа;
  • корректную прокрутку длинного мобильного меню;
  • navbar над hero и ниже hero;
  • работоспособность клавиатурной навигации.

Этап 8. Добавить visual regression

  1. Добавить Playwright screenshots для navbar в ключевых состояниях.
  2. Использовать стабильные шрифты, отключённые переходы и ожидаемую загрузку ресурсов перед снимком.
  3. Начальный порог maxDiffPixelRatio: не более 0.003.
  4. Если допустимое динамическое содержимое создаёт шум, маскировать только этот конкретный элемент.
  5. Запрещено повышать порог глобально, чтобы скрыть реальную регрессию.

Критерий завершения: изменение прозрачности, неожиданный градиент, неверный цвет крестика и смещение панели обнаруживаются автоматически.

Этап 9. Встроить quality gate в CI

В package.json добавить единый публичный скрипт:

{
"scripts": {
"quality": "npm run check && npm run test:e2e"
}
}

GitHub Actions разделить минимум на следующие зависимости:

quality ─┐
├──> deploy
e2e ─────┘

Возможны два варианта:

  • один job выполняет npm run quality, а deploy зависит от него;
  • отдельные jobs quality и e2e, а deploy имеет needs: [quality, e2e].

Обязательные условия CI:

  1. использовать npm ci;
  2. зафиксировать поддерживаемую версию Node;
  3. установить Playwright Chromium с системными зависимостями;
  4. кэшировать npm и браузер только безопасным штатным способом;
  5. загружать Playwright report при падении;
  6. не запускать deploy при красном quality gate;
  7. публиковать только артефакт, построенный после успешных проверок.

7. Архитектурные лимиты

Лимиты служат сигналом для ревью, а не поводом искусственно дробить код:

  • page-компонент: желательно до 100 строк;
  • обычный UI-компонент: желательно до 200 строк;
  • сложный feature-компонент: желательно до 250 строк;
  • CSS Module секции: желательно до 300 строк;
  • один глобальный CSS-файл: желательно до 150 строк;
  • cyclomatic complexity функции: желательно не выше 10;
  • public component props должны иметь явный TypeScript-тип;
  • бизнес-логика не должна жить в CSS или разметке страницы.

Превышение допустимо только с кратким объяснением в отчёте.

8. Обязательная проверка после каждого этапа

После каждого законченного этапа выполнить:

npm run format:check
npm run lint
npm run lint:css
npm run typecheck
npm run test
npm run build

После изменений UI дополнительно выполнить:

npm run test:e2e

После добавления финального скрипта итоговая команда:

npm run quality

Также обязательно:

git diff --check
git status --short

9. Порядок работы AI-агента

AI-агент должен работать небольшими проверяемыми итерациями.

Для каждого этапа:

  1. назвать конкретную проблему;
  2. перечислить файлы, которые планируется менять;
  3. внести минимально достаточное изменение;
  4. выполнить релевантные проверки;
  5. визуально проверить результат, если затронут UI;
  6. сравнить с baseline;
  7. сообщить, что изменилось и какие риски остались;
  8. не переходить к следующему этапу при красной проверке.

Если обнаружена несвязанная ошибка или чужие изменения:

  • не исправлять их автоматически;
  • зафиксировать факт в отчёте;
  • продолжать только если их можно безопасно обойти.

10. Финальный quality gate

Работа может быть объявлена завершённой только при одновременном выполнении всех условий:

  • npm run quality завершился с кодом 0;
  • git diff --check не нашёл проблем;
  • все три маршрута открываются напрямую и после навигации;
  • navbar проверен на всех шести viewport;
  • кнопка закрытия контрастна на светлом и зелёном фоне;
  • открытие navbar не меняет прозрачность/фон шапки;
  • на мобильном navbar полностью перекрывает экран;
  • на desktop navbar остаётся компактным;
  • в navbar нет градиента;
  • отсутствуют горизонтальные overflow и layout shift;
  • клавиатурная навигация и focus management работают;
  • visual regression snapshots просмотрены человеком;
  • deploy зависит от успешных quality/e2e jobs;
  • монолитные page CSS устранены или сведены к композиционному минимуму;
  • page-компоненты не содержат стили и внутреннюю логику navbar;
  • контакты и навигационные данные имеют один источник истины;
  • README отражает фактические команды и архитектуру;
  • ни одно несвязанное пользовательское изменение не потеряно;
  • изменения не отправлены в remote без отдельного разрешения.

11. Формат итогового отчёта

AI-агент обязан закончить работу отчётом следующей структуры:

Результат
- что исправлено для пользователя;
- что изменено в архитектуре.

Изменённые области
- список модулей и их новых обязанностей.

Проверки
- команда: результат;
- команда: результат;
- visual review: маршруты и viewport.

Quality gate
- каждый пункт: PASS/FAIL.

Оставшиеся риски
- конкретный риск и причина;
- либо «не обнаружены».

Git
- изменённые файлы;
- коммиты, если владелец отдельно разрешил их создать;
- статус push/deploy.

Фраза «всё готово» недопустима, если хотя бы один обязательный пункт quality gate имеет статус FAIL или не был проверен.