Files
specs/openspec/changes/archive/2026-08-27-consume-mantine-components/design.md
T

67 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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` и стилей.