LoVisual/frontend/ARCHITECTURE.md
loki5512344 1c94e0ebea
refactor(frontend): bring shared/ui and pages/public under the 4-file rule
Two directories had grown past the plan's ≤4 files per folder constraint:
shared/ui (5 primitives, and the kit keeps growing) and pages/public (5 pages,
counting auth/ as a subfolder).

Split shared/ui by concept — text-field/ and share-code/ each with their own
tests/ — and move PageTitle to shared/layout, where the rest of the page chrome
lives. Flat UI tests become per-component tests; the PageTitle test follows the
component. Move LoginPage and RegisterPage into the existing pages/public/auth/
beside the AuthSplit layout they both use.

No behaviour change: 83 tests, clean tsc build, 1 known oxlint warning.
2026-09-28 13:21:18 +02:00

110 lines
7.1 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.

# Frontend Architecture (продумано заранее, реализация — следующий план)
> Статус: структура согласована до написания implementation-плана фронтенда,
> чтобы план сразу проектировался под правильные границы, а не переписывался
> после первого клубка спагетти. Стек: React + Vite + TypeScript + Tailwind
> (см. `TODO.md`, Фаза 10). Правила платформы (≤250 строк/файл и т.д.) — там же.
## Принцип: структура по фичам, не по типу файла
Раскладывать `src/` на `/components`, `/hooks`, `/services`, `/types` вперемешку —
самая частая ошибка в React-проектах: одна фича размазывается по всему дереву
папок, и её нельзя удалить или вынести, не задев остальное. Вместо этого —
каждая фича владеет всем, что ей нужно, в одной папке.
```
frontend/src/
features/
auth/ # регистрация/логин, device-link экран
components/
hooks/
api.ts # вызовы к accounts-service через gateway
types.ts
configs/ # 4 слота, share-коды, витрина
components/
hooks/
api.ts
types.ts
avatars/
admin/ # /admin, видим только role=admin
shared/ # только то, что реально используют 2+ фичи
components/ # кнопки, инпуты, модалки — дизайн-система
hooks/ # useAuth (общий контекст сессии), useDebounce и т.п.
api/
client.ts # общий fetch-wrapper: base URL gateway, JWT в заголовке,
# обработка 401 (refresh) и 429 (rate-limit) в одном месте
types.ts # общие DTO, если совпадают на нескольких фичах
i18n/ # LanguageSwitch, common.ru.ts / common.en.ts (typed dictionaries)
pages/ # роуты верхнего уровня, тонкие — просто собирают
# фичи в layout, никакой бизнес-логики
app/ # App.tsx, routes.tsx, i18n.ts (initI18n), i18next.d.ts (typed keys)
main.tsx
```
Каждая фича, у которой есть строки для пользователя, также владеет своей
`i18n/` подпапкой (`ru.ts`, `en.ts`) — одно пространство имён i18next на
фичу (`auth`, `landing`, ...), русские словари как источник (`as const`),
английские типизированы через `satisfies Translation<typeof ru>` из
`shared/i18n/common.ru.ts`, так что расхождение ключей — ошибка компиляции.
Правило раздела shared/feature: если код используется ровно в одной фиче —
он живёт в этой фиче, а не в `shared/`. Переносить в `shared/` только когда
появился второй потребитель — не заранее "на всякий случай" (YAGNI).
**Лимиты из правил платформы (`TODO.md`) действуют и тут**: ≤250 строк на
файл, ≤4 файла на папку (подпапки не считаются) — если в `components/`
внутри фичи появляется 5-й файл, выносим смысловую подпапку, а не копим.
## Антипаттерны, которых сознательно избегаем
- **Barrel-файлы `index.ts` на каждую папку** — удобны на вид, но ломают
tree-shaking и IDE go-to-definition на больших проектах; экспортируем
напрямую из файла, где определено
- **Prop drilling через 4+ уровня** — если проп передаётся через компоненты,
которым он сам не нужен (просто транзитом) — это сигнал на React Context
(для auth-сессии) или на подъём компонента ниже по дереву, а не терпеть
drilling
- **Бизнес-логика в `pages/`** — страницы только компонуют фичи и layout;
если в файле страницы появляется `fetch`/сложная derived-логика — она
переезжает в соответствующую фичу
- **Один гигантский `api.ts` на весь проект** — обращения к API живут в
папке своей фичи (`features/configs/api.ts` и т.д.), общий только
low-level `shared/api/client.ts` (fetch-wrapper)
- **any вместо нормальных типов** — DTO с бэкенда типизируются по контрактам
из `backend/PLAN.md` (Task 6: `RegisterResponse`, `LoginResponse` и т.д.),
не `any`/`unknown` без сужения
## Тестирование
Тесты живут в подпапке `tests/` внутри фичи (или `shared/*`), которую они
проверяют — не рядом с файлом (`Component.test.tsx`) и не в отдельном дереве
на верхнем уровне. Это всё ещё удаляется вместе с фичей при её удалении/
переносе, но не съедает бюджет "≤4 файла на папку" — колокация `X.test.tsx`
рядом с `X.tsx` вдвое сокращала бы вместимость каждой папки под этим правилом.
```
frontend/src/
features/
auth/
components/
hooks/
api.ts
types.ts
tests/
api.test.ts
components.test.tsx
shared/
ui/
Button.tsx
text-field/
TextField.tsx
tests/
text-field.test.tsx
```
Источники: [Robin Wieruch — React Folder Structure Best Practices 2026](https://www.robinwieruch.de/react-folder-structure/), [Mastering Modern React + Vite Folder Structure, Medium](https://sandeshrathnayake.medium.com/mastering-modern-react-vite-folder-structure-a-production-ready-guide-for-scalable-applications-9ad8e233f8b9).
## Единственное допустимое ребро shared → feature
`shared/layout/` (шапка и каркас приложения) импортирует `useSession` из
`features/auth/session`: шапке нужно знать, вошёл ли пользователь. Это
единственный случай, когда `shared/` зависит от фичи; остальной `shared/`
от фич не зависит.