refactor: establish local frontend workspace boundaries
Conventional commits / commits (pull_request) Successful in 24s
Frontend security / security (pull_request) Successful in 9s
Frontend verify / verify (pull_request) Successful in 5m18s
Frontend security / security (push) Successful in 10s
Frontend verify / verify (push) Successful in 6m45s

OpenSpec change: evolve-frontend-stack
This commit was merged in pull request #34.
This commit is contained in:
2026-09-22 22:11:02 +03:00
parent c86be7bf73
commit 85fcfe3d07
101 changed files with 1487 additions and 666 deletions
+20 -11
View File
@@ -51,23 +51,32 @@ Cross-slice consumer использует `@/` и `index.ts` владельца,
композиция принадлежит вышележащему owner. Existing меж-slice исключения
не являются разрешением добавлять новые.
Направление зависимостей и public imports частично проверяет
`npm run deps`; Knip проверяет использование exports, а не смысл placement.
Направление зависимостей, public imports и запрет соседних slices в
`pages`, `features`, `entities` проверяет `npm run deps`. Внутренние imports
одного slice и связи shared segments этот запрет не ограничивает.
Knip проверяет использование exports, а не смысл placement.
## UI foundation
`shared/theme/tokens.json` — переносимый source design tokens; `theme.ts`
Private workspace `packages/tokens` экспортирует переносимый source design tokens
как `@wealthledger/tokens/tokens.json`. Private workspace `packages/ui`
адаптирует доверенный JSON к Mantine без runtime schema. App и Storybook читают
public API `shared/theme`. Storybook показывает synthetic presentation states,
`@wealthledger/ui/theme` и явно подключают `@wealthledger/ui/styles.css` после
Mantine CSS. Потребители используют public exports пакетов напрямую. Storybook показывает synthetic presentation states,
не использует application API. RTL/MSW проверяют поведение приложения отдельно.
Existing route-aware AppShell расположен в shared/ui; это legacy placement,
а не образец нового shared primitive. При изменении foundation прочитай
Route-aware AppShell и его navigation styles принадлежат `app/ui`;
`ProtectedLayout` использует public API этого владельца. При изменении foundation прочитай
[UI](conventions/ui.md#визуальная-основа) и [Storybook](conventions/storybook.md).
Статичные Add/Edit/Delete и публичный `IconProps` принадлежат private
`packages/icons`; package не зависит от UI, domain или motion.
`BankOperationIcon` и mappings остаются у entity. Card/Cards используют package
Add/Edit/Delete; restore/category SVG остаются локальными.
## Примеры
- Общая busy-область: существующий [LoadingSkeleton](../src/shared/ui/LoadingSkeleton.tsx), а не domain widget в shared.
- Общая busy-область: существующий [LoadingSkeleton](../packages/ui/src/LoadingSkeleton.tsx), а не domain widget в shared.
- Account-income state views: [страница](../src/pages/account-income/ui/AccountIncomePage.tsx) и её flat `components/*.view.tsx`; новый page-local filter остаётся здесь.
- Portfolio query: [entity public API](../src/entities/portfolio/index.ts); новая UI-сводка с бизнес-смыслом не становится shared только из-за нескольких экранов.
@@ -75,7 +84,7 @@ Existing route-aware AppShell расположен в shared/ui; это legacy p
После placement/import change: `npm run deps`, `npm run typecheck`, `npm run knip`;
после собственного behavior — применимые tests. Финальный gate указан в AGENTS.
Existing dependency config не запрещает все same-layer imports: их и смысл
owner проверяет review. Не объявляй Steiger установленным; graph проверяет
текущий dependency-cruiser. Review отдельно проверяет смысл owner: разрешённый dependency graph не доказывает
правильный выбор слоя.
Graph `src` и `packages` проверяет текущий dependency-cruiser; зависимости
из packages в код приложения `src` запрещены. Generated `packages/*/dist`
не анализируется как исходник; все package sources остаются в graph. Review отдельно проверяет смысл
owner: разрешённый dependency graph не доказывает правильный выбор слоя.