Files

7.6 KiB
Raw Permalink Blame History

Context

В frontend UI почти полностью построен на @mantine/core (v9.5.1). Единственное место с собственной ручной реализацией хлебных крошек — src/app/ui/AppBreadcrumbs.tsx: внутренний BreadcrumbTrail вручную рендерит <nav aria-label> → <span class="breadcrumbs-*"> → сепаратор «→», плюс кастомные классы breadcrumbs, breadcrumbs-entry, breadcrumbs-separator-N, breadcrumbs-item-N и медиа-правило data-depth="3" в src/app/styles.css. Тест src/app/ui/AppBreadcrumbs.test.tsx проверяет data-depth, aria-current, aria-label, navigation role и ссылки.

Mantine Breadcrumbs (v9.5.1) — штатный аналог: рендерит children с сепаратором между ними, поддерживает separator, separatorMargin и произвольные children (элементы или текст). Рендерится как <Box> (<div> по умолчанию), принимает component и data-*/aria-атрибуты. Нативно НЕ скрывает промежуточные крошки по глубине и НЕ выставляет aria-current — адаптивность и текущую крошку нужно сохранить на уровне представления/CSS.

Мобильная навигация AppShell (bottom-nav) выходит из scope по решению Change Owner — остаётся кастомной.

Goals / Non-Goals

Goals:

  • Заменить ручную разметку BreadcrumbTrail на штатный Mantine Breadcrumbs.
  • Сохранить идентичное пользовательское и доступное поведение: те же цепочки, ссылки только у предшествующих крошек, текущая страница — текст, aria-label, navigation role, data-depth, адаптивное скрытие промежуточных крошек на узком экране.

Non-Goals:

  • Не менять маршруты, иерархию, тексты, API и данные.
  • Не трогать другую кодовую базу (уже на Mantine) и мобильную навигацию AppShell.

Decisions

D1. Использовать Mantine Breadcrumbs с component="nav" и aria-label

Ручной <nav> заменяется на <Breadcrumbs component="nav" aria-label={t("breadcrumbs.label")} data-depth={items.length} separator="→">. data-depth и aria-label передаются в root через BoxProps/aria-атрибуты — сохраняет тесты navigation role, label и data-depth.

Rationale: Breadcrumbs — это и есть штатная замена собственной разметки сепаратора/навигации. Альтернатива (оставить кастом) отвергнута — цель change удалить дублирующий кастом.

D2. Ссылки и текущая крошка рендерятся как children

Каждая крошка — элемент в children: предшествующие — <Link to={...}>{label}</Link> (получает Mantine breadcrumb class), текущая — <span aria-current="page">{label}</span>. Сепаратор «→» задаётся через separator="→" вместо ручных breadcrumbs-separator-N spans.

Rationale: сохраняет поведение «ссылки только предшествующие, текущая — текст с aria-current». Mantine автоматически ставит сепаратор между children.

D3. Убрать теперь неиспользуемые кастомные CSS-классы, оставить только адаптивное правило

Удаляются breadcrumbs, breadcrumbs-entry, breadcrumbs-separator-N, breadcrumbs-item-N, а также селекторы и правило data-depth="3" из src/app/styles.css. Стиль оставшихся крошек даёт Mantine (accent-цвет ссылки можно сохранить через минимальную настройку Breadcrumbs/.mantine-класс или переиспользовать существующий --ft-color-accent).

Адаптивное скрытие промежуточных крошек на узком экране НЕ переносится на компонент: Mantine Breadcrumbs не умеет прятать по глубине. Сохраняется как кастомное CSS-правило по data-depth на root. Рассматривалась альтернатива — рендерить сокращённую цепочку на mobile через props — отвергнута как избыточная переделка поведения ради минимизации CSS.

Delivery Map

Area Status Architectural outcome or N/A reason
frontend affected Замена AppBreadcrumbs.tsx на Mantine Breadcrumbs, обновление AppBreadcrumbs.test.tsx, чистка кастомных breadcrumbs-* стилей в src/app/styles.css.
backend N/A Нет изменений backend API, данных или контрактов.
specs affected Change объявляет skip_specs: true — чистый рефакторинг, observable behavior и требования app-breadcrumb-navigation не меняются.
docs N/A Ни один as-built документ в docs/ не описывает эти компоненты; правки не требуются.

Contract Gate Decision

Gate Status Reason
runtime-openapi N/A API contract не меняется ни с одной стороны; рефакторинг UI-представления только во frontend.

Risks / Trade-offs

  • Разметка сепаратора/классов меняется — Mantine оборачивает children и рендерит сепаратор иначе. Митигируется тем, что тест проверяет поведение (roles, aria-current, data-depth, ссылки), а не внутренние классы.
  • Адаптивное скрытие остаётся кастомным CSS — не «чистая» замена, но это осознанный trade-off: Mantine не предоставляет скрытия по глубине, а переписывание поведения на props добавило бы риск регрессий.
  • aria-current и navigation role — должны быть явно построены, т.к. Mantine не добавляет их сам. Покрыто тестом.

Migration Plan

Один атомарный шаг во frontend: заменить реализацию BreadcrumbTrail на Mantine Breadcrumbs (с сохранением логики AccountBreadcrumbs/AppBreadcrumbs нетронутой), обновить тест, почистить кастомные breadcrumbs-* стили, прогнать npm run verify.

Rollback Contract

N/A — безопасный обратимый рефакторинг UI-представления без auth/security, data migration, публичного контракта или труднообратимых решений. Откат — возврат прежней реализации BreadcrumbTrail и стилей.