From c25f6bd85b88c4c7acb2f5ae6fc013aec77005b2 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Tue, 6 Oct 2026 22:05:24 +0300 Subject: [PATCH] refactor: move PortfolioStructure to widgets layer --- .ai/architecture.md | 14 ++++++++------ dependency-cruiser.cjs | 16 +++++++++++----- .../components/AccountPageEmpty.view.tsx | 2 +- .../components/AccountPageLoaded.view.tsx | 2 +- src/widgets/portfolio-structure/index.ts | 1 + .../ui}/PortfolioStructure.module.css | 0 .../ui}/PortfolioStructure.stories.tsx | 2 +- .../ui}/PortfolioStructure.test.tsx | 0 .../ui}/PortfolioStructure.tsx | 0 .../ui}/PortfolioStructure.view.tsx | 0 .../ui}/PortfolioStructure.vm.ts | 0 11 files changed, 23 insertions(+), 14 deletions(-) create mode 100644 src/widgets/portfolio-structure/index.ts rename src/{pages/account/ui/AccountPage/components/PortfolioStructure => widgets/portfolio-structure/ui}/PortfolioStructure.module.css (100%) rename src/{pages/account/ui/AccountPage/components/PortfolioStructure => widgets/portfolio-structure/ui}/PortfolioStructure.stories.tsx (98%) rename src/{pages/account/ui/AccountPage/components/PortfolioStructure => widgets/portfolio-structure/ui}/PortfolioStructure.test.tsx (100%) rename src/{pages/account/ui/AccountPage/components/PortfolioStructure => widgets/portfolio-structure/ui}/PortfolioStructure.tsx (100%) rename src/{pages/account/ui/AccountPage/components/PortfolioStructure => widgets/portfolio-structure/ui}/PortfolioStructure.view.tsx (100%) rename src/{pages/account/ui/AccountPage/components/PortfolioStructure => widgets/portfolio-structure/ui}/PortfolioStructure.vm.ts (100%) diff --git a/.ai/architecture.md b/.ai/architecture.md index a5b6fbb..0a9788f 100644 --- a/.ai/architecture.md +++ b/.ai/architecture.md @@ -8,7 +8,7 @@ Vitest — отдельную JSDOM конфигурацию. Точные versi [package.json](../package.json), реализация — source of truth. ```text -app → pages → features → entities → shared +app → pages → widgets → features → entities → shared page container → page VM → entity/feature query → shared/api → backend /api/v1 ↓ props → state/section views @@ -30,13 +30,15 @@ contract; frontend не читает его checkout для генерации | --- | --- | | Bootstrap, глобальные providers, routes | `app` | | Композиция маршрута и поведение, нужное только этому экрану | `pages/` | +| Крупная переиспользуемая композиция экрана | `widgets/` | | Самостоятельная пользовательская возможность с собственным смыслом | `features/` | | Представление/поведение бизнес-сущности | `entities/` | | Переиспользуемый код без WealthLedger domain meaning | `shared` | -Локальный FSD profile: `app → pages → features → entities → shared`, без `widgets`. -Крупный уникальный блок остаётся в своей page. Form, mutation или click handler -сами по себе не требуют feature. Два callers не делают domain helper generic shared. +Локальный FSD profile: `app → pages → widgets → features → entities → shared`. +Widget собирает несколько нижних слоёв в самостоятельный UI-блок; компонент для +одной page остаётся у page. Form, mutation или click handler сами по себе не +требуют feature. Два callers не делают domain helper generic shared. Slice именуется по ответственности, а не `helpers`, `hooks` или `types`. **Безопасный путь:** при сомнении оставь код у ближайшего допустимого owner. @@ -47,12 +49,12 @@ Slice именуется по ответственности, а не `helpers`, Cross-slice consumer использует `@/` и `index.ts` владельца, например `@/entities/portfolio`. Alias Vite и TypeScript согласован. Другой slice не -импортирует внутренний файл напрямую. Slices `pages`, `features`, `entities` не зависят от соседнего slice своего слоя; +импортирует внутренний файл напрямую. Slices `pages`, `widgets`, `features`, `entities` не зависят от соседнего slice своего слоя; композиция принадлежит вышележащему owner. Existing меж-slice исключения не являются разрешением добавлять новые. Направление зависимостей, public imports и запрет соседних slices в -`pages`, `features`, `entities` проверяет `npm run deps`. Внутренние imports +`pages`, `widgets`, `features`, `entities` проверяет `npm run deps`. Внутренние imports одного slice и связи shared segments этот запрет не ограничивает. Knip проверяет использование exports, а не смысл placement. diff --git a/dependency-cruiser.cjs b/dependency-cruiser.cjs index 39beab8..7aae82c 100644 --- a/dependency-cruiser.cjs +++ b/dependency-cruiser.cjs @@ -29,17 +29,17 @@ module.exports = { }, { from: { - path: "^src/(app|pages|features|entities|shared)/([^/]+)/", + path: "^src/(app|pages|widgets|features|entities|shared)/([^/]+)/", }, name: "slices-must-use-public-api", severity: "error", to: { - path: String.raw`^src/(app|pages|features|entities|shared)/([^/]+)/(?!index\.ts$)`, + path: String.raw`^src/(app|pages|widgets|features|entities|shared)/([^/]+)/(?!index\.ts$)`, pathNot: "^src/$1/$2/", }, }, { - from: { path: "^src/(pages|features|entities)/([^/]+)/" }, + from: { path: "^src/(pages|widgets|features|entities)/([^/]+)/" }, name: "no-same-layer-cross-slice-imports", severity: "error", to: { path: "^src/$1/", pathNot: "^src/$1/$2/" }, @@ -48,18 +48,24 @@ module.exports = { from: { path: "^src/shared/" }, name: "shared-must-not-depend-on-layers", severity: "error", - to: { path: "^src/(entities|features|pages|app)/" }, + to: { path: "^src/(entities|features|widgets|pages|app)/" }, }, { from: { path: "^src/entities/" }, name: "entities-must-not-depend-on-higher-layers", severity: "error", - to: { path: "^src/(features|pages|app)/" }, + to: { path: "^src/(features|widgets|pages|app)/" }, }, { from: { path: "^src/features/" }, name: "features-must-not-depend-on-higher-layers", severity: "error", + to: { path: "^src/(widgets|pages|app)/" }, + }, + { + from: { path: "^src/widgets/" }, + name: "widgets-must-not-depend-on-higher-layers", + severity: "error", to: { path: "^src/(pages|app)/" }, }, { diff --git a/src/pages/account/ui/AccountPage/components/AccountPageEmpty.view.tsx b/src/pages/account/ui/AccountPage/components/AccountPageEmpty.view.tsx index bf9c35d..d3377a6 100644 --- a/src/pages/account/ui/AccountPage/components/AccountPageEmpty.view.tsx +++ b/src/pages/account/ui/AccountPage/components/AccountPageEmpty.view.tsx @@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next"; import type { BrokerageAccount } from "@/entities/brokerage-account"; import { BrokerageEventsPreview } from "@/entities/brokerage-event"; +import { PortfolioStructure } from "@/widgets/portfolio-structure"; import type { AccountPageIncomeSummary, @@ -16,7 +17,6 @@ import type { import { AccountHeading } from "./AccountHeading.view"; import { IncomeSummary } from "./IncomeSummary.view"; -import { PortfolioStructure } from "./PortfolioStructure/PortfolioStructure"; import { RecentOperations } from "./RecentOperations.view"; type AccountPageEmptyProps = { diff --git a/src/pages/account/ui/AccountPage/components/AccountPageLoaded.view.tsx b/src/pages/account/ui/AccountPage/components/AccountPageLoaded.view.tsx index 892b1c9..01aa1f6 100644 --- a/src/pages/account/ui/AccountPage/components/AccountPageLoaded.view.tsx +++ b/src/pages/account/ui/AccountPage/components/AccountPageLoaded.view.tsx @@ -7,6 +7,7 @@ import type { BrokerageAccount } from "@/entities/brokerage-account"; import type { Portfolio } from "@/entities/portfolio"; import { BrokerageEventsPreview } from "@/entities/brokerage-event"; +import { PortfolioStructure } from "@/widgets/portfolio-structure"; import type { AccountPagePositionSection } from "../../../model/positions"; import type { @@ -19,7 +20,6 @@ import classes from "../AccountPage.module.css"; import { AccountHeading } from "./AccountHeading.view"; import { CurrencyCards } from "./CurrencyCards/CurrencyCards.view"; import { IncomeSummary } from "./IncomeSummary.view"; -import { PortfolioStructure } from "./PortfolioStructure/PortfolioStructure"; import { PositionSection } from "./PositionSection.view"; import { RecentOperations } from "./RecentOperations.view"; diff --git a/src/widgets/portfolio-structure/index.ts b/src/widgets/portfolio-structure/index.ts new file mode 100644 index 0000000..b89160e --- /dev/null +++ b/src/widgets/portfolio-structure/index.ts @@ -0,0 +1 @@ +export { PortfolioStructure } from "./ui/PortfolioStructure"; diff --git a/src/pages/account/ui/AccountPage/components/PortfolioStructure/PortfolioStructure.module.css b/src/widgets/portfolio-structure/ui/PortfolioStructure.module.css similarity index 100% rename from src/pages/account/ui/AccountPage/components/PortfolioStructure/PortfolioStructure.module.css rename to src/widgets/portfolio-structure/ui/PortfolioStructure.module.css diff --git a/src/pages/account/ui/AccountPage/components/PortfolioStructure/PortfolioStructure.stories.tsx b/src/widgets/portfolio-structure/ui/PortfolioStructure.stories.tsx similarity index 98% rename from src/pages/account/ui/AccountPage/components/PortfolioStructure/PortfolioStructure.stories.tsx rename to src/widgets/portfolio-structure/ui/PortfolioStructure.stories.tsx index 38900e5..aecd386 100644 --- a/src/pages/account/ui/AccountPage/components/PortfolioStructure/PortfolioStructure.stories.tsx +++ b/src/widgets/portfolio-structure/ui/PortfolioStructure.stories.tsx @@ -28,7 +28,7 @@ const meta = { }, }, tags: ["autodocs"], - title: "Product/PortfolioStructure", + title: "Widgets/PortfolioStructure", } satisfies Meta; export default meta; diff --git a/src/pages/account/ui/AccountPage/components/PortfolioStructure/PortfolioStructure.test.tsx b/src/widgets/portfolio-structure/ui/PortfolioStructure.test.tsx similarity index 100% rename from src/pages/account/ui/AccountPage/components/PortfolioStructure/PortfolioStructure.test.tsx rename to src/widgets/portfolio-structure/ui/PortfolioStructure.test.tsx diff --git a/src/pages/account/ui/AccountPage/components/PortfolioStructure/PortfolioStructure.tsx b/src/widgets/portfolio-structure/ui/PortfolioStructure.tsx similarity index 100% rename from src/pages/account/ui/AccountPage/components/PortfolioStructure/PortfolioStructure.tsx rename to src/widgets/portfolio-structure/ui/PortfolioStructure.tsx diff --git a/src/pages/account/ui/AccountPage/components/PortfolioStructure/PortfolioStructure.view.tsx b/src/widgets/portfolio-structure/ui/PortfolioStructure.view.tsx similarity index 100% rename from src/pages/account/ui/AccountPage/components/PortfolioStructure/PortfolioStructure.view.tsx rename to src/widgets/portfolio-structure/ui/PortfolioStructure.view.tsx diff --git a/src/pages/account/ui/AccountPage/components/PortfolioStructure/PortfolioStructure.vm.ts b/src/widgets/portfolio-structure/ui/PortfolioStructure.vm.ts similarity index 100% rename from src/pages/account/ui/AccountPage/components/PortfolioStructure/PortfolioStructure.vm.ts rename to src/widgets/portfolio-structure/ui/PortfolioStructure.vm.ts