refactor: move portfolio structure into account page, drop widgets (WealthLedger/frontend#139) #140

Merged
codex-bot merged 1 commits from refactor/139-remove-widgets-layer into develop 2026-11-04 18:01:01 +03:00
11 changed files with 14 additions and 22 deletions
Showing only changes of commit 0ac46fd3bb - Show all commits
+6 -7
View File
@@ -8,7 +8,7 @@ Vitest — отдельную JSDOM конфигурацию. Точные versi
[package.json](../package.json), реализация — source of truth.
```text
app → pages → widgets → features → entities → shared
app → pages → features → entities → shared
page container → page VM → entity/feature query → shared/api → backend /api/v1
↓
props → state/section views
@@ -30,14 +30,13 @@ contract; frontend не читает его checkout для генерации
| --- | --- |
| Bootstrap, глобальные providers, routes | `app` |
| Композиция маршрута и поведение, нужное только этому экрану | `pages/<page>` |
| Крупная переиспользуемая композиция экрана | `widgets/<widget>` |
| Самостоятельная пользовательская возможность с собственным смыслом | `features/<capability>` |
| Представление/поведение бизнес-сущности | `entities/<entity>` |
| Переиспользуемый код без WealthLedger domain meaning | `shared` |
Локальный FSD profile: `app → pages → widgets → features → entities → shared`.
Widget собирает несколько нижних слоёв в самостоятельный UI-блок; компонент для
одной page остаётся у page. Form, mutation или click handler сами по себе не
Локальный FSD profile: `app → pages → features → entities → shared`.
Слой `widgets` не используется: композиция экрана и компонент для одной page
остаются у page. Form, mutation или click handler сами по себе не
требуют feature. Два callers не делают domain helper generic shared.
Slice именуется по ответственности, а не `helpers`, `hooks` или `types`.
@@ -49,12 +48,12 @@ Slice именуется по ответственности, а не `helpers`,
Cross-slice consumer использует `@/` и `index.ts` владельца, например
`@/entities/portfolio`. Alias Vite и TypeScript согласован. Другой slice не
импортирует внутренний файл напрямую. Slices `pages`, `widgets`, `features`, `entities` не зависят от соседнего slice своего слоя;
импортирует внутренний файл напрямую. Slices `pages`, `features`, `entities` не зависят от соседнего slice своего слоя;
композиция принадлежит вышележащему owner. Existing меж-slice исключения
не являются разрешением добавлять новые.
Направление зависимостей, public imports и запрет соседних slices в
`pages`, `widgets`, `features`, `entities` проверяет `npm run deps`. Внутренние imports
`pages`, `features`, `entities` проверяет `npm run deps`. Внутренние imports
одного slice и связи shared segments этот запрет не ограничивает.
Knip проверяет использование exports, а не смысл placement.
+5 -11
View File
@@ -29,17 +29,17 @@ module.exports = {
},
{
from: {
path: "^src/(app|pages|widgets|features|entities|shared)/([^/]+)/",
path: "^src/(app|pages|features|entities|shared)/([^/]+)/",
},
name: "slices-must-use-public-api",
severity: "error",
to: {
path: String.raw`^src/(app|pages|widgets|features|entities|shared)/([^/]+)/(?!index\.ts$)`,
path: String.raw`^src/(app|pages|features|entities|shared)/([^/]+)/(?!index\.ts$)`,
pathNot: "^src/$1/$2/",
},
},
{
from: { path: "^src/(pages|widgets|features|entities)/([^/]+)/" },
from: { path: "^src/(pages|features|entities)/([^/]+)/" },
name: "no-same-layer-cross-slice-imports",
severity: "error",
to: { path: "^src/$1/", pathNot: "^src/$1/$2/" },
@@ -48,24 +48,18 @@ module.exports = {
from: { path: "^src/shared/" },
name: "shared-must-not-depend-on-layers",
severity: "error",
to: { path: "^src/(entities|features|widgets|pages|app)/" },
to: { path: "^src/(entities|features|pages|app)/" },
},
{
from: { path: "^src/entities/" },
name: "entities-must-not-depend-on-higher-layers",
severity: "error",
to: { path: "^src/(features|widgets|pages|app)/" },
to: { path: "^src/(features|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)/" },
},
{
@@ -9,7 +9,6 @@ import type { InvestmentResourceMeta } from "@/shared/api";
import { BrokerageEventsPreview } from "@/entities/brokerage-event";
import { ReadRetry, type ReadRetryProps, SourceSyncStatus } from "@/shared/ui";
import { PortfolioStructure } from "@/widgets/portfolio-structure";
import type {
AccountPageIncomeSummary,
@@ -19,6 +18,7 @@ import type {
import { AccountHeading } from "./AccountHeading.view";
import { IncomeSummary } from "./IncomeSummary.view";
import { PortfolioStructure } from "./PortfolioStructure/PortfolioStructure";
import { RecentOperations } from "./RecentOperations.view";
type AccountPageEmptyProps = {
@@ -16,7 +16,6 @@ import type { InvestmentResourceMeta } from "@/shared/api";
import { BrokerageEventsPreview } from "@/entities/brokerage-event";
import { formatMoney } from "@/shared/lib";
import { ReadRetry, type ReadRetryProps, SourceSyncStatus } from "@/shared/ui";
import { PortfolioStructure } from "@/widgets/portfolio-structure";
import type { Concentration as ConcentrationModel, Slice } from "../../../model/concentration";
import type { AccountPagePositionSection } from "../../../model/positions";
@@ -31,6 +30,7 @@ import { AccountHeading } from "./AccountHeading.view";
import { Concentration } from "./Concentration/Concentration.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";
@@ -28,7 +28,7 @@ const meta = {
},
},
tags: ["autodocs"],
title: "Widgets/PortfolioStructure",
title: "Product/PortfolioStructure",
} satisfies Meta<typeof PortfolioStructure>;
export default meta;
-1
View File
@@ -1 +0,0 @@
export { PortfolioStructure } from "./ui/PortfolioStructure";