From ba76f6fccc63b315e9ebf9d05ce181b283a6c094 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Tue, 22 Sep 2026 22:11:05 +0300 Subject: [PATCH] spec: complete local frontend stack evolution OpenSpec change: evolve-frontend-stack --- .../changes/evolve-frontend-stack/design.md | 440 ++++++++++++++++++ .../changes/evolve-frontend-stack/proposal.md | 69 +++ .../specs/frontend-design-system/spec.md | 60 +++ .../changes/evolve-frontend-stack/tasks.md | 239 ++++++++++ 4 files changed, 808 insertions(+) create mode 100644 openspec/changes/evolve-frontend-stack/design.md create mode 100644 openspec/changes/evolve-frontend-stack/proposal.md create mode 100644 openspec/changes/evolve-frontend-stack/specs/frontend-design-system/spec.md create mode 100644 openspec/changes/evolve-frontend-stack/tasks.md diff --git a/openspec/changes/evolve-frontend-stack/design.md b/openspec/changes/evolve-frontend-stack/design.md new file mode 100644 index 0000000..aedb512 --- /dev/null +++ b/openspec/changes/evolve-frontend-stack/design.md @@ -0,0 +1,440 @@ +## Context + +Локальная архитектура реализована; независимая публикация отложена. Baseline исследования — frontend `3c99deb`, версия `0.3.0`, +2026-09-20. Требования — в +[proposal](proposal.md) и [delta](specs/frontend-design-system/spec.md). + +В приложении уже работают React, TypeScript, Vite, Mantine, TanStack Query, +RHF/Zod, JSON-токены, общий адаптер темы, Storybook, Vitest/RTL/MSW, +dependency-cruiser и Knip. Они сохраняются. Другие production-потребители +общего UI или клиентского SDK в исследованном workspace не обнаружены. + +На 2026-09-22 workspace-пакеты `@wealthledger/tokens`, `@wealthledger/ui` и +`@wealthledger/icons` используются локально как private packages. Это принятая +часть перехода. Описанные далее независимый release и Nexus остаются +предложением, отложенным до оценки пилота. Фактическое выполнение и ограничения +сохраняются в [tasks](tasks.md). + +## Goals / Non-Goals + +**Goals:** три небольшие границы поставки общего фундамента; единый каталог +источников; сохранение внешнего вида и предметных инвариантов; потребление +собранного пакета; ограниченные задачи, исполнимые по одной сессии. + +**Non-Goals:** другой UI framework, второй transport/store/form stack, +универсальные CRUD/table/form engines, монорепозиторий всего WealthLedger, +mobile adapter, тёмная тема, массовый rewrite CSS и тестирование внутренностей +Mantine. Календарь платежей требует отдельного продуктового запроса. + +## Decisions + +### 1. Репозиторий и направления зависимостей + +Рекомендация: **npm workspaces внутри существующего frontend**. Корневое +приложение сохраняет `src`, Vite entry, собственные providers и `private: true`. +Новые пакеты располагаются в `packages/tokens`, `packages/icons`, `packages/ui`. +Имена `@wealthledger/*` приняты для локальных packages; регистрация scope +и внешняя registry-поставка не подтверждены. + +| Вариант | Польза | Цена в этом проекте | Решение | +| --- | --- | --- | --- | +| Три workspace-пакета в frontend | Один lockfile, toolchain, Storybook и атомарный review источников с потребителем | Нужно явно расширить scopes проверок; локальный link может скрыть дефект поставки | Выбран; обязательна проверка packed consumer | +| Один отдельный design-system repo с тремя пакетами | Отдельный owner, release cadence, независимые потребители | Новый CI/catalog/install, синхронизация PR и доступа к registry при одном приложении | Отложить до второго независимого production consumer либо отдельного владельца/release cadence | +| Три отдельных Git repo | Полностью независимые права/жизненные циклы | Три workflow и совместимость без подтверждённой выгоды | Не выбирать | +| Только существующий shared | Уже работает, нет стоимости публикации | Не даёт запрошенной границы версионируемой поставки | Сохраняется до готовности package foundations, не конечная цель | + +Направление стрелки — «потребляет»; циклов нет: + +```text +frontend app / единственный Storybook + ├─ @wealthledger/ui ── @wealthledger/tokens + │ └─ Mantine + React (peers у ui) + ├─ @wealthledger/icons ── React (peer) + └─ entities/features/pages → shared/api, domain models, i18n + +tokens → нет runtime dependencies +ui → нет icons dependency до появления реального общего consumer +icons → нет ui/tokens/Mantine/router/API dependencies +``` + +Общие компоненты принимают уже подготовленные строки и slots. `EntryCard` +принимает `ReactNode` и не знает, какой пакет рисует icon/action. Domain выбирает +цвет по смыслу и форматирует деньги; UI только отображает переданное значение. + +### 2. Публичная поверхность и сборка + +Имена ниже в колонке «Цель» — принятые публичные exports локальных пакетов. +Внутренние `src`, `dist/*` и application alias `@/` не являются package API. + +| Пакет | Цель / entrypoints | Состав поставки | Зависимости | +| --- | --- | --- | --- | +| tokens | `@wealthledger/tokens/tokens.json` | Доверенный JSON v1; JS, CSS, React-адаптер и `.d.ts` на старте не нужны | Нет runtime dependencies | +| icons | Именованные `AddIcon`, `EditIcon`, `DeleteIcon` из `@wealthledger/icons` | ESM JS + declarations; CSS только если переносится собственная анимация | React peer + external; React DOM не используется | +| ui | `@wealthledger/ui`: `StateView`, `LoadingSkeleton`, `EntryCard` и их необходимые props types; `@wealthledger/ui/theme`: `theme`, `colorRoles`, `cssVariablesResolver`; `@wealthledger/ui/styles.css` | ESM JS + declarations + собранный CSS, включая CSS Modules | React/React DOM и используемые Mantine core/hooks — peers/external; tokens — точная dependency | + +`designTokens` нужен foundations story: адаптер может сохранить существующий +одноимённый export из `ui/theme`, который ссылается на JSON, а не копирует его. +JSON остаётся отдельно доступным без Mantine. Не добавлять JS-обёртку в tokens +только ради импорта JSON. Не выдавать non-web adapter за готовый. + +Использовать установленный Vite для library build ui/icons и TypeScript для +declarations. Один `tsc` недостаточен для CSS Modules `EntryCard`. Формат — ESM; +CJS/SSR build не нужен текущему Vite SPA. Настройки library build ещё предстоит +проверить на зафиксированной Vite 8.3.0; готовых конфигураций здесь нет. +Публикуемые `files`/`exports` ограничивают поверхность. CSS явно включается в +side effects; JS импорт сам не устанавливает provider и не читает приложение. +Library build использует отдельную конфигурацию нужных plugins, не наследует +app Vite config целиком: PWA/manifest/service worker и dev API proxy не должны +попасть в поставку библиотеки. + +Exports указывают на собранные artifacts, поэтому обычные local/CI entrypoints +сначала собирают packages, затем выполняют consumer typecheck/tests/build. +Сейчас `verify` начинает проверки до app build; этот порядок потребуется +дополнить при 2.2. Acceptance начинается на чистом checkout после `npm ci`, +без старого package dist/cache. Достаточно последовательности npm scripts, +без собственного build orchestrator. + +Docker — отдельный потребитель workspace: нынешний `Dockerfile` копирует только +root manifests перед `npm ci`, а `.dockerignore` не допускает packages. +В 2.1–2.2 нужны точечные manifests/source/build-config allowlists, установка +с workspace manifests и package build перед consumer. Host node_modules, +готовый локальный dist и реальные `.env` в контекст не попадают. Development +и production targets проверяются отдельно. В текущем Compose монтируются +только app src/public: изменения packages сначала требуют rebuild dev image. +Отдельный package bind-mount/watch в Compose отложен до фактической потребности; +host development может использовать package watch без изменения root repo. + +Сначала peer ranges узкие, по проверенной комбинации React 19.3.0 и Mantine +9.6.1; расширять их только после проверки потребителя. React/React DOM +devDependencies для разработки не должны попадать в bundle. Проверять +`npm ls react react-dom @mantine/core` и содержимое bundle/packed consumer; +`dedupe` не заменяет корректную упаковку. Две несовместимые версии Mantine +также нельзя скрыть локальным symlink. + +### 3. Токены: использовать уже существующий JSON + +В [tokens.json](../../../../frontend/packages/tokens/tokens.json) уже есть +переносимые значения и описания. **Не менять формат v1 ради упаковки.** +`primitive → semantic → component` — модель владения и ссылок, а не повод +немедленно переименовать все ключи в новый стандарт. + +| Уровень | Текущая основа и целевое правило | +| --- | --- | +| Primitive | `color.base`, `color.palettes`, `typography`, `spacing.values`, `radius.values`, `shadows.values`, `breakpoints.values`; исходные эффективные значения сохраняются | +| Semantic | Существующие `color.roles.*` с `color` + `shade` ссылаются на primitive; `primary` с `shade: null` сохраняет разрешение через primaryShade | +| Component | Пока новые не нужны. Добавлять только именованный совместно управляемый контракт реального общего компонента, если semantic role недостаточно; ссылаться на semantic, затем primitive при отсутствии смысловой роли | + +Имена описывают роль (`surface`, `border`, `positive`), а не экран или случайный +цвет. Ссылки однонаправленные и разрешимые; обратных ссылок из primitive на +semantic/component нет. Новый color role включает назначение и ограничение +применения. Семантический положительный цвет не вычисляет знак финансовой суммы. + +Сохранить единицы: размеры/тени в px адаптируются существующими `rem`/`rgba`, +breakpoints в em не масштабируются, line-height без единицы. Имеющиеся +[theme tests](../../../../frontend/packages/ui/src/theme.test.ts) защищают +именно преобразования и ссылки, не каждое значение JSON. + +Общие CSS-роли `--wl-color-*` производит прежний `CSSVariablesResolver`. +Spacing/radius/type scales продолжают поступать в Mantine theme и его CSS +variables. Статическая генерация второго `tokens.css` не нужна: появился бы +ещё один адаптер и риск расхождения scale. JSON экспортируется без сборочного +token engine; Style Dictionary, DTCG-converter и runtime schema отложены до +конкретного внешнего потребителя/формата. Источник остаётся доверенным файлом, +не внешним пользовательским документом. + +Светлая схема сохраняется. `primaryShade.dark` не означает готовую dark theme. +Бизнес-коды, суммы/валюты, московские даты, pagination limits, brand gradients +`BankCardFace` и случайная геометрия SVG не превращаются в глобальные токены. +Первое применение semantic roles — точные эквиваленты gray-3/gray-0/green-6/ +red-6 в `EntryCard`; перенос и эта правка идут отдельно. + +### 4. Mantine, тема и providers + +`Stack`, `Group`, `Button`, `Paper`, `ActionIcon`, поля, Modal, Dates/Charts +остаются прямыми импортами Mantine. Изменение defaults решается темой. +Wrapper нужен только собственному контракту: busy region у `LoadingSkeleton`, +empty/error composition у `StateView`, layout/image fallback/actions у `EntryCard`. + +App владеет MantineProvider, DatesProvider, QueryClientProvider, AuthProvider, +Router и i18n. Storybook имеет свой Mantine/Dates/i18n runtime с той же темой. +UI не создаёт `WealthLedgerProvider`, не импортирует локализацию продукта и +не включает app providers. `ui/styles.css` содержит существующую визуальную +основу и стили общих компонентов; app/preview подключают его явно после +Mantine CSS. Продуктовые route transitions и bank-card styles остаются в app. + +Theme Modal transition и глобальная CSS animation сейчас сосуществуют. +Их визуальный конфликт не доказан. После выделения выбрать один механизм +открытия по browser comparison, предпочтительно уже настроенный Mantine +transition; нельзя удалить второй механизм на основании одного сходства кода. + +### 5. Иконки и движение + +Начальный набор — реально повторяющиеся plus/pencil/trash из Card/Cards views. +Имена `AddIcon`, `EditIcon`, `DeleteIcon` — предлагаемая поверхность, не найденные +exports. Размер задаёт `size` (default 24), геометрия сохраняет `viewBox`, цвет +получается через `currentColor`. Существующие размеры и strokeWidth 1.5/2 +сохраняются у callers; выравнивание рисунка не совмещается с переносом. + +Канонический исходник SVG для небольшого набора — **SVG-разметка в именованных +React-компонентах** `packages/icons/src/svg/.tsx`. Это один редактируемый +источник, без параллельных ручных `.svg` и `.tsx`. Отдельные raw SVG-файлы в +первой поставке не публикуются: non-React consumer пока отсутствует. Если +потребуются исходные `.svg` как самостоятельный продуктовый артефакт, перенести +канон в `src/svg/*.svg` и сравнить штатный SVGR CLI с ручным поддержанием; +не писать собственный генератор. Сейчас ни dependency, ни pipeline для этого +не нужны. JS/types несут SVG-компоненты, это явно отражено в составе поставки. + +По умолчанию рисунок декоративный (`aria-hidden`, без фокуса). У standalone +meaningful icon потребитель задаёт доступное имя и image semantics. У +`ActionIcon` имя остаётся на действии; SVG не дублирует его. Общий пакет не +выбирает подпись «редактировать операцию» и не получает DTO. + +`BankOperationIcon` и `operation-appearance.ts` остаются в entity: mapping +category/kind → рисунок/цвет/анимация — бизнес-логика. Собственная геометрия и +движение рисунка могут перейти в icons позже при реальном reuse. Hover/focus +trigger по сценарию, route transitions, bank/card state остаются приложению. +Существующий `bind-icon-animation` переиспользуется, если переносится тот же +animation contract; он не копируется в пакет ради трёх статичных иконок. +Все переносимые движения обязаны сохранять reduced-motion поведение. + +### 6. Каталог источников компонентов + +Порядок поиска: **существующие компоненты приложения → собственная библиотека +→ Mantine → уже разрешённые dependencies → обоснованное новое решение**. +Новый компонент появляется у владельца реальной ответственности, а не потому, +что текущий компонент отсутствует в Storybook. Таблица — компактный маршрут, +не каталог каждого JSX-узла. Номера обозначают этапы в [tasks](tasks.md). + +| Назначение | Текущий источник | Целевой источник | Правило применения | Этап | +| --- | --- | --- | --- | --- | +| Layout, buttons, inputs, dialogs | `@mantine/core`, `@mantine/dates` | Те же пакеты | Прямые imports; variant/default через theme, не WL-копия каждого primitive | 1, 2 | +| Цветовые роли, шкалы | `shared/theme/tokens.json` | `tokens/tokens.json` + адаптер `ui/theme` | JSON — значения, адаптер — Mantine, бизнес-смысл — consumer | 2 | +| Тема/общие стили | `shared/theme` | `ui/theme`, `ui/styles.css` | Одна тема App/Storybook; providers у runtimes | 2 | +| Busy skeleton region | `shared/ui/LoadingSkeleton` | `ui` | Label и region общие, skeleton shapes принадлежат странице | 3.1 | +| Empty/error | `shared/ui/StateView` | `ui` | Строки/action props; query/error classification вне UI | 3.1 | +| Строка с изображением и значением | `shared/ui/EntryCard` | `ui` | Готовые strings/slots, fallback и responsive actions; без money parsing | 3.2–3.3 | +| Plus/edit/delete | Inline SVG Card/Cards views | `icons` | Named imports; label и disabled у ActionIcon | 4 | +| Навигация и active route | `shared/ui/AppShell` | `app/ui` | Product shell; не публичный компонент ui-пакета | 1.1 | +| Карта и её операции | `entities/bank-card` | Там же | `BankCardFace`, `BankOperationIcon`, `BankSummaryView`, `BankCardFields`, `BankFeedback` — domain | Без переноса | +| Период банковских данных | `entities/bank-card/BankPeriodPicker`, model/period | Там же | Draft/URL/query semantics сохраняются; не универсальный date service | 5, без extraction | +| Период дохода | `pages/account-income/.../IncomePeriodForm.view` | Та же page; pure helpers в её model | Изолировать story от VM, сохранить validation/submit | 5.3 | +| Позиции/структура портфеля | `pages/account/ui/components/PositionCard.view`, `PortfolioStructure.view` | Там же | Page-local композиция допустима; не переделывать под EntryCard | Без переноса | +| Недавние события | `entities/brokerage-event` + Account page | Там же; общий EntryCard из ui | Первый продуктовый пилот, финансовое представление у entity | 3.2 | +| Общие instrument details | `features/instrument-details` | Там же | Реальный reuse stocks/bonds; typed presentation contract отдельно от общего UI | Без переноса | + +Последние непакетные источники выбираются через существующие `index.ts`. +В таблице обозначения `shared/ui/...` описывают файлы до миграции; это не разрешение +на deep import. Текущие владельцы указаны в колонке цели. + +### 7. Предметный UI, API и конечные словари + +Рекомендация: оставить domain UI в `entities/features/pages`. Второй +производственный consumer, которому нужен тот же стабильный domain contract, +и отдельный release owner — проверяемое условие рассмотрения business package. +Storybook не считается таким потребителем. Повторная карточка может потребовать +переноса page → entity внутри приложения, но не нового npm-пакета. + +`shared/api` сохраняет snapshot, generated types, typed transport и auth-aware +boundary. Генерируются **типы**, а не весь handwritten client. Query keys, +normalization, freshness и invalidation остаются в entity/feature owner; +не входят ни в generated artifact, ни в ui. Отдельный `api-client` сейчас +потребовал бы отделить Vite environment, auth/retry lifecycle и query policy без +второго потребителя. Вернуться к вопросу при втором независимом приложении. + +Backend остаётся единственным editable HTTP source. Frontend получает +предоставленный snapshot, локально генерирует и сравнивает declarations; +соседний backend checkout не становится входом CI. DTO aliases из generated +`paths` сохраняются. UI form models допустимы, когда отличаются от transport. + +Для frontend собственных конечных словарей — принятый `as const` + union: +`enum` несовместим с текущим `erasableSyntaxOnly`. Набор operation kinds можно +свести у bank entity и проверить через generated `BankOperation['kind']`; +не объявлять вторую DTO enum. Wire literals сохраняются. `ProblemDetailsDto.code` +остаётся `string`, unknown code получает fallback; нельзя объявлять закрытый +список ошибок. Общий `apiErrorCode` извлекает code, одинаковое recovery +классифицируется ближайшим допустимым owner без обратных FSD imports. + +### 8. Политика библиотек + +Версии закреплены в frontend package manifests и lockfile. Решения ниже +не означают новой установки или обновления. + +| Область | Сейчас | Решение и причина | Этап | +| --- | --- | --- | --- | +| Build/React/Router | Vite, TS, React Compiler, React Router | Сохранить; расширить имеющийся build для library artifacts | 2 | +| Server/local state | TanStack Query / React state / URL | Сохранить owners; Redux из transitive deps не делает его app store | Все | +| Forms/validation | RHF, resolvers, Zod | Сохранить; vocabulary у entity, controller/view банковской формы отдельно | 5.2, 5.4 | +| UI/charts | Mantine; Recharts/react-is обеспечивают peer graph charts | Сохранить, не удалять peer deps из-за отсутствия direct imports | Все | +| Styles/условные классы | CSS Modules, template/ternary, data-* | Сохранить один этот подход; clsx/classnames не вводить без сложности, которой здесь нет | 3–5 | +| Icons | Inline SVG | Унифицировать повторяющиеся общие рисунки в малом собственном наборе | 4 | +| Motion | motion для routes, CSS/native для рисунков | Сохранить по разным владельцам; проверить дублирование Modal отдельно | 5.1 | +| Dates | Day.js + Mantine Dates + native Date/Intl | Сохранить роль каждого; Moscow/ISO policy остаётся domain/page | 5.3 | +| Money | String + BigInt cents, display formatters | Сохранить точность и unknown≠zero; decimal library не добавлять | Все | +| API/errors | openapi-fetch/react-query/typescript | Сохранить; не добавлять axios, второй generator или schema registry | Все | +| Tests | Vitest/RTL/MSW | Сохранить; `fireEvent` принят, user-event только transitively | 2–5 | +| Catalog | Storybook 10.6.0 | Один runtime, расширить glob и границы на packages | 2–4 | +| Quality | Biome, Knip, dependency-cruiser | Дополнить действующие scopes, не ставить Steiger/ещё один analyzer | 1–2 | +| Browser/visual CI | Принятого E2E runner/visual diff нет | Ручной browser proof пилота; Playwright — при повторяемой browser-only регрессии и владельце CI | Отложено | +| Release | release-it + native npm + Gitea | Использовать текущий подход, адаптировать workspace releases; Changesets/Lerna не нужны для трёх пакетов | 6 | + +### 9. Предлагаемое уточнение conventions + +Это предложения для будущих задач; действующие инструкции сейчас не меняются. +AGENTS остаётся коротким router, постоянный каталог переносится в подходящий +раздел existing UI convention/README после подтверждённого перехода. + +| Владелец правила | Конкретное уточнение | Когда | +| --- | --- | --- | +| `.ai/architecture.md` | Package graph вне FSD; src public APIs; app shell; никакого package → app или same-layer обхода | 1–2 | +| `.ai/conventions/ui.md` | Каталог источников и search order; тема против wrapper; props-only общие компоненты | 2–3 | +| UI + Storybook | JSON/semantic roles, explicit CSS import, светлая схема, иконки/currentColor/reduced-motion | 2–4 | +| `.ai/conventions/data.md`, TypeScript/React | Generated vocabulary vs local const unions; errors открыты; package не владеет query state | 5.2 | +| Engineering/dependencies | Workspace direct deps, peers, exact lock, packed consumer, ограничения exports | 2, 6 | +| Storybook | Один runtime, colocated package/product stories, новые reachability scopes | 2–3 | +| Testing | Поведение → риск → минимальная boundary; package artifact contract; browser proof не равен build | 2–3, 6 | + +### 10. Storybook и проверки + +Один существующий frontend Storybook показывает `Design System` и `Product`. +Общие stories переезжают вместе с ui/icons; foundations читают опубликованный +JSON/адаптер. Product stories остаются рядом с entities/pages. Обновляются +только globs/aliases/check scopes, не создаётся отдельное приложение каталога. +Mantine/theme/Dates/i18n уже есть в preview; Query/Auth/API providers не нужны. +Synthetic props и callbacks сохраняются; MSW остаётся в Vitest, не в worker. + +| Наблюдаемый контракт | Минимальный способ доказать | +| --- | --- | +| JSON → размеры/тени/semantic CSS, unscaled breakpoints | Перенести существующие theme unit tests; не проверять каждый literal | +| JS/types/CSS/JSON доступны вне workspace | Pack + чистый consumer с type/build/render и без source aliases | +| EntryCard image fallback, actions при focus/touch | Один небольшой RTL case на собственный fallback/действие, synthetic story и browser review | +| Деньги/version/idempotency/invalid input | Существующие targeted RTL/MSW tests формы; новые cases только если меняется соответствующая boundary | +| Query/auth/recovery | Имеющиеся MSW tests; no real backend; не тестировать это через stories | +| Keyboard/name/reduced motion/narrow layout | Выбранные собственные interactions в browser; примитивные гарантии Mantine не переписывать | + +`build:catalog` не исполняет `play`; существующий `storybook.test.tsx` проверяет +лишь выбранные группы. Не обещать screenshot regression по факту сборки. +Snapshot всех компонентов и полный новый E2E stack не планируются. + +Новые workspace-папки не охвачены автоматически: нынешние TS/Knip/deps/story +настройки главным образом ориентированы на `src`. Package type/build/lint, +dependencies и запрет app imports вводятся вместе с соответствующим пакетом. +Нельзя временно исключить packages целиком и назвать gate полным. Same-layer +rule сначала проверяется на текущем graph, затем включается для чистого scope; +legacy exception имеет имя, причину и задачу удаления. Existing coverage floors +не понижаются; перенос tests/coverage ownership не должен скрыть потерю сценария. + +### 11. Локальная работа и потребление + +Приняты приватные npm workspaces внутри frontend: один install/lockfile, +сборка пакетов и потребление через exports. Проверки охватывают приложение, +пакеты и Storybook. Собранные tarballs проверены вне workspace без source aliases; +эта проверка выявляет отсутствующие exports, типы, CSS и несовместимые peers. + +Публикация, независимый SemVer, package release automation и настройка Nexus +не входят в текущий переход. Вернуться к ним можно при подтверждённой пользе +для независимого потребителя; тогда отдельно определить registry, права, +неизменность версий, совместимость и откат. Текущий app release не меняется. + +## UI/UX Applicability and Package + +**Applicability: N/A для нового визуального дизайна.** Цель — сохранение уже +существующего приложения, без новых экранов и рисунков. Новый макет/visuals и +hash registry не создаются. Для проверки переноса выбран следующий scope. + +Пилот: `AccountPage → useAccountPageViewModel → useBrokerageEvents → +BrokerageEventsPreview → BrokerageEventCard → EntryCard`. Это read-only +preview выплат; `PositionCard` не использует EntryCard и не подходит для +переноса без изменения представления. Generic icons в пилоте отсутствуют и +мигрируют отдельно в Card/Cards. + +Сохраняются loaded/loading/error/empty, missing amount как unknown, а не ноль, +registry-date/account metadata, валюта, logo fallback, длинные строки, +preview limit и ссылка полного списка. Сверяются desktop и viewport 375px, +доступный focus и отсутствие горизонтального overflow. У generic EntryCard +actions дополнительно проверяются fine/coarse pointer и reduced motion на +synthetic story. Текущий RecentEvents error-priority не меняется молча. + +## Delivery Map + +| Area | Status | Architectural outcome or N/A reason | +| --- | --- | --- | +| frontend | affected | Workspaces tokens/icons/ui, ограниченная миграция, checks и package release integration | +| backend | N/A | Пакеты не меняют HTTP/auth/storage; замечание об errors вынесено в отложенный scope | +| specs | affected | Этот change и новые требования к поставке/иконкам; main specs обновятся только после принятой реализации | +| docs | affected | После поставки уточняются design overview и описание frontend container/release boundary | + +## Solution Assessment + +| Решение | Fit / зрелость / ограничения / стоимость | Выбор | +| --- | --- | --- | +| Существующие npm workspaces + Vite/TS | Стандартная упаковка; не требует нового Git repo; CSS/peers и published surface нуждаются в проверке | Да | +| Отдельный design-system repo | Ясная независимость ownership, но удваивает окружение каталога/CI при одном app | Позже по явному consumer/owner trigger | +| Style Dictionary или собственный token compiler | Полезны нескольким форматам/платформам; сейчас JSON + Mantine adapter уже решают задачу | Нет; runtime trust boundary не меняется | +| Ручные React SVG / SVGR pipeline | Несколько рисунков дешевле поддерживать в одном TSX source; SVGR нужен каноническим raw SVG exports | Ручные SVG-компоненты сейчас, без генератора | +| release-it/native npm / Changesets | Первый стек уже принят; второй полезен большому потоку независимых release contributions | Сохранить существующий app release; независимый package release отложен | + +Новые внешние runtime dependencies не требуются. Риск supply chain сохраняется +под существующим security gate; peer/version checks и registry ACL нельзя +заменить предположением о готовом Nexus. + +## Risks / Trade-offs + +- Workspace links скрывают отсутствующие exports/assets и конфликт React: + независимый packed consumer обязателен. +- Package graph расширяет проверяемую область: старый зелёный `deps src` не + подтверждает новые packages. Scopes расширяются при появлении владельца. +- CSS order/global reset влияют на всё приложение. Сначала перенос без + изменения значений, затем отдельно semantic roles и Modal consolidation. +- Общие исходники в frontend — осознанный выбор для одного потребителя; + публикация не означает уже существующий независимый каталог/команду. +- Изменение view/controller рядом с денежной формой рискованнее UI extraction: + это отдельная поздняя задача с сохранением tests version/idempotency. + +## Migration Plan + +Границы owners → package foundations → общие компоненты и read-only пилот → +иконки и точечное выравнивание → packed consumer → удаление совместимости. +Единственный подробный backlog и зависимости находятся в [tasks](tasks.md). + +Во время перехода `shared/theme` и три старых UI entrypoints могут быть тонкими +re-exports package API. Они не содержат второй JSON, CSS или реализации. +Новые consumers используют package paths; добавлять exports/логику в facade +запрещено. Удаление — когда поиск оставшихся runtime/test/story imports даёт +ноль и app/catalog/packed consumer прошли gates. Старые inline icons живут до +своего consumer batch, но новые копии извлечённых рисунков не добавляются. + +### Rollback Contract + +| Field | Decision | +| --- | --- | +| Status | required: меняются локальные imports, сборка и владельцы компонентов | +| Trigger | Нет exports/CSS/types, peer conflict, несовместимое представление либо failed pilot/recovery | +| Actions | Вернуть согласованный diff приложения, packages, imports и lockfile; при выпуске приложения использовать прежний app image | +| Data safety | API/storage/money wire format не меняются; не откатывать чужие продуктовые изменения и не сбрасывать пользовательские данные | +| Verification | Повторить соответствующий consumer gate и пилот на прежней версии; проверить восстановление согласованного состояния приложения и packages | + +### Осознанно отложенное + +| Находка/возможность | Причина и условие возврата | +| --- | --- | +| F8: RecentEvents error приоритетнее cached data | Реальное отличие от общего recovery convention. Не менять при extraction; отдельная product recovery задача после согласования expected stale/error UX, с refetch-error regression test | +| F9: ProblemDetails errors schema/example и validation mapping | Отдельная backend contract задача: metadata + HTTP behavior test, затем предоставленный snapshot; не основание вручную исправлять frontend DTO | +| F12: Основная frontend-quality-guardrails spec требует initial 500 kB и baseline lazy chunks, а действующий verify не содержит такого gate | Отдельно согласовать актуальность сохранённого требования и способ enforcement. Не считать Vite warning блокирующим budget gate; package migration сравнивает фактический размер сборки, не заявляя выполнения отсутствующей проверки | +| Единый recovery classifier всех investment errors | Одинаковые codes ещё не доказывают одинаковый UX; объединять только подтверждённый общий recovery без нарушения FSD | +| Business/API packages, dark/mobile, raw SVG pipeline, Playwright/visual service | Нет принятого второго consumer или значимого browser-only regression scope | + +## Решения для подтверждения перед зависимыми действиями + +1. Публичные имена `@wealthledger/tokens`, `@wealthledger/icons`, `@wealthledger/ui`, + scope ownership и видимость. Рекомендуется приватная поставка WealthLedger; + другое решение меняет доступ и release setup, не текущие данные. +2. Nexus npm endpoint, владелец publish/read credentials, политика неизменности + и хранения прошлых версий. Эти решения нужны только при отдельном запросе внешней публикации. +3. Расхождение F12 между сохранённой spec и текущими quality-командами: + рекомендуемый default — сохранять требование до отдельного решения владельца, + не ослаблять его и не объявлять выполненным. Автоматизация либо корректировка + требования оформляются отдельным ограниченным scope после уточнения причины. + +Остальное — аргументированные defaults данного предложения, а не список +неразрешённых вопросов. Перед реализацией нужен отдельный запрос пользователя; +workspaces внутри frontend уже реализованы, независимые версии остаются отложенными. diff --git a/openspec/changes/evolve-frontend-stack/proposal.md b/openspec/changes/evolve-frontend-stack/proposal.md new file mode 100644 index 0000000..e7404e3 --- /dev/null +++ b/openspec/changes/evolve-frontend-stack/proposal.md @@ -0,0 +1,69 @@ +## Why + +У WealthLedger уже есть общий визуальный фундамент, каталог компонентов и +типизированное взаимодействие с сервером. Следующий шаг — сделать границы +переиспользования и поставки этого фундамента явными: участник должен быстро +находить подходящее решение, а приложение — получать проверенную совместимую +версию без копирования реализаций. + +Исследование текущего frontend выявило конкретные незавершённые границы: +повторяющиеся рисунки иконок, обход существующих цветовых ролей, продуктовую +навигацию в общем UI и неполную автоматизацию архитектурных ограничений. + +## What Changes + +**Статус: локальная реализация и итоговая проверка завершены.** +Локальные границы приложения и private workspace-пакеты реализованы; +проверенные пункты и оставшиеся ограничения перечислены в [tasks](tasks.md). +Независимое версионирование и публикация через Nexus отложены до оценки пользы +пилота и не входят в требования этого перехода. + +Первоначальный комплект плановых Markdown-файлов был подготовлен по запросу +пользователя в одной сессии. Последующие разрешения касались ограниченных +этапов; они не разрешают публикацию, deployment или archive. + +- Выделить используемый общий визуальный фундамент в приватные workspace-пакеты, + сохранив внешний вид и работоспособность приложения между небольшими этапами. +- Установить единый порядок выбора компонентов и понятную границу между + общим представлением, предметным UI и сценариями приложения. +- Сделать повторно используемые собственные иконки доступными с единым + визуальным и доступным контрактом. +- Подтвердить переход существующим небольшим продуктовым срезом и проверкой + потребления собранных артефактов, а не только локальных исходников. +- Уточнить применимые инженерные правила после соответствующих изменений, + сохранить существующие проверки и поэтапно закрыть найденные пробелы. + +Сохраняются денежная точность, обработка неизвестных значений, поведение +авторизации, запросов, форм, восстановления после ошибок и существующих страниц. +Предметные расчёты и серверные контракты не становятся частью общего UI. + +Не входят редизайн, тёмная тема, мобильный адаптер, календарь обязательных +платежей, смена работающего стека, переустройство всех репозиториев и массовое +перемещение компонентов. Публикация, deployment, commit, push, PR и archive не разрешены. +Локальная реализация ограничена отдельно согласованными этапами. + +## Capabilities + +### New Capabilities + +Нет: новая capability на каждый пакет или каталог не требуется. + +### Modified Capabilities + +- `specs/frontend-design-system/spec.md`: добавить требования к потреблению + локальных пакетов общего фундамента и доступному контракту собственных + общих иконок. Существующие требования к единой теме и предметной независимости + сохраняются. [Delta](specs/frontend-design-system/spec.md). + +## Impact + +- Frontend: поставка общей основы, выбор источников компонентов, ограниченная + миграция потребителей и развитие существующих проверок. +- Backend: производственный контракт и поведение не меняются; отдельное + замечание к описанию ошибок остаётся отложенным исследованием владельца API. +- Specs: этот change и delta; основные as-built требования пока не изменяются. +- Docs: описание текущего локального устройства обновлено в 7.2; + независимая registry-поставка не заявляется. + +Решения, альтернативы и каталог источников — в [design](design.md), единственная +очередь работ и текущее состояние — в [tasks](tasks.md). diff --git a/openspec/changes/evolve-frontend-stack/specs/frontend-design-system/spec.md b/openspec/changes/evolve-frontend-stack/specs/frontend-design-system/spec.md new file mode 100644 index 0000000..0bc3306 --- /dev/null +++ b/openspec/changes/evolve-frontend-stack/specs/frontend-design-system/spec.md @@ -0,0 +1,60 @@ +## ADDED Requirements + +### Requirement: Общий фундамент доступен через проверяемые локальные пакеты + +Общий визуальный фундамент SHALL принадлежать приватным npm workspaces внутри +frontend и иметь явную публичную поверхность. Приложение SHALL использовать +пакеты через объявленные exports. Собранные артефакты MUST содержать модули, +типы, стили и данные согласно контракту каждого пакета и быть пригодными для +проверки вне рабочего дерева frontend. Общие пакеты MUST NOT требовать серверных +данных, авторизации, маршрутов или состояния WealthLedger. Совместимость пакетов +между собой и с приложением SHALL проверяться; workspace links MUST NOT быть +единственным подтверждением пригодности артефактов. Независимые релизы и +публикация в registry не входят в этот переход. + +#### Scenario: Приложение потребляет собранную поставку + +- **WHEN** потребитель устанавливает собранные артефакты в чистом окружении без + ссылок на рабочее дерево поставщика +- **THEN** публичные модули, типы, стили и данные доступны, общий компонент + отображается на заявленной теме без зависимостей от приложения поставщика + +#### Scenario: Локальная разработка скрыла отсутствующий артефакт + +- **WHEN** локальная ссылка разрешает импорт, отсутствующий в собранной поставке +- **THEN** поставка не признаётся готовой по результату одной локальной сборки; + потребление именно собранных артефактов должно быть подтверждено отдельно + +#### Scenario: Приложение использует локальные пакеты + +- **WHEN** frontend устанавливает зависимости из своего lockfile и собирается +- **THEN** приватные workspaces доступны через публичные exports без публикации + в registry; предметные компоненты и API-клиент остаются в приложении + +### Requirement: Собственные общие иконки имеют доступный контракт + +Общие иконки SHALL иметь предсказуемые размеры и возможность получать цвет +от потребителя, не определяя бизнес-смысл операции или состояние приложения. +Декоративная иконка MUST NOT создавать отдельную точку фокуса или повторять +доступное имя соседнего действия. Смысловая иконка без сопровождающего текста +MUST иметь доступное имя. Собственное движение иконки SHALL учитывать +предпочтение уменьшенного движения; выбор момента запуска по пользовательскому +сценарию SHALL оставаться у потребителя. + +#### Scenario: Иконка находится в именованном действии + +- **WHEN** собственная иконка дополняет кнопку с доступным именем +- **THEN** кнопка сохраняет имя и клавиатурное поведение, а рисунок декоративен + и не создаёт дополнительную точку фокуса + +#### Scenario: Рисунок самостоятельно передаёт смысл + +- **WHEN** иконка используется без текста и без именованного родительского + элемента как самостоятельное изображение +- **THEN** потребитель задаёт доступное имя, передающее её назначение + +#### Scenario: Пользователь уменьшил движение + +- **WHEN** включено предпочтение уменьшенного движения +- **THEN** необязательная анимация общей иконки не выполняется, а значение и + доступность действия сохраняются diff --git a/openspec/changes/evolve-frontend-stack/tasks.md b/openspec/changes/evolve-frontend-stack/tasks.md new file mode 100644 index 0000000..191238e --- /dev/null +++ b/openspec/changes/evolve-frontend-stack/tasks.md @@ -0,0 +1,239 @@ +# План перехода frontend + +**На 2026-09-22 выполнены и проверены 1.1–1.2, 2.1–2.2, 3.1–3.3, 4.1–4.2, 5.1–5.4 и 7.1–7.3** по отдельному +разрешению пользователя. Остальные задачи — предложения, не разрешение +на реализацию или публикацию. Результаты и ограничения приведены ниже. +Прочитать [proposal](proposal.md), затем нужное решение [design](design.md). +Здесь единственный backlog инициативы, отдельного roadmap нет. + +## Дорожная карта и зависимости + +```text +1.1 AppShell owner ────────┐ +1.2 import enforcement ────┴─ 2.1 tokens/workspaces ─ 2.2 ui theme + │ + 3.1 common states + │ + 3.2 EntryCard + pilot ─ 3.3 roles +2.1 ─ 4.1 icons ─ 4.2 Card/Cards consumers +2.2 ─ 5.1 Modal review +1.2 ─ 5.2 operation vocabulary ─ 5.4 form boundary +1.2 ─ 5.3 income period helper boundary + +3.3 + 4.2 + packed consumer proof ─ 7.1 retire facades +Выполненные локальные 1–5 ─ 7.2 текущее as-built описание +3.2–3.3 + 5.1–5.4 + 7.1–7.2 ─ 7.3 acceptance всей инициативы +``` + +1.1 и 1.2 независимы по смыслу, но могут менять общие конфиги: параллельные PR +согласуют diff. 5.1–5.4 не блокируют выпуск основы и не входят скрыто в пилот. +Иконки не нужны read-only пилоту RecentEvents. Пункты внешней публикации удалены из текущего плана по решению пользователя: +пакеты остаются приватными workspaces frontend. Остальные +задачи используют предложенные defaults после отдельного запроса реализации. + +### Общие условия каждой будущей сессии + +- Работать в repo владельца, сначала его AGENTS и только triggered conventions; + проверить Git status, текущий HEAD и чужие изменения. План привязан к + исследованным ревизиям, не к обещанию актуального remote develop. +- Перед Node-командой активировать местную `.nvmrc`: `nvm use --silent`. + Для code/dependency PR frontend требует `npm ci` и итоговый `npm run verify`; + targeted команды ниже дополняют этот gate, не заменяют его. +- Scope PR — одна задача. Не смешивать перемещение с новым поведением, + обновлением версий библиотек и исправлением соседнего продукта. +- Значимый тест начинается с наблюдаемого результата/риска; сохранять имеющиеся + regression tests. Не добавлять тест wrapper existence, Mantine internals + или snapshots всех вариантов ради переноса. +- По завершении повторить routing, проверить diff/новые файлы, Boy Scout review, + `git diff --check`, записать реально выполненные команды и ограничения. + Непройденная проверка не отмечается как выполненная задача. + +### Реальные команды и будущие проверки + +Сейчас доступны: `npm run deps`, `npm run deps:storybook`, `npm run knip`, +`npm run lint`, `npm run typecheck`, `npm run openapi:check`, +`npm run test:run -- <существующий path>`, `npm run test:coverage`, +`npm run build`, `npm run build:catalog`, `npm run verify`, +`npm run release:preview`. Их поведение исследовано, но они **не запускались** +в текущей Markdown-сессии. `typecheck` может записать tsbuildinfo; +tests/builds пишут cache/coverage/dist/catalog. `openapi:generate` меняет +tracked declarations и не нужен этой миграции. + +Package build/declarations/check scopes и packed consumer проверены. +Независимый package release не входит в текущий план. Не +считать вымышленный `test:packages` или `release:packages` действующей командой. +Native `npm pack --dry-run`, `npm pack`, `npm ls` использованы для проверки пакетов. `release:prepare` приложения +создаёт commit; не использовать для пробного package release. + +## 1. Уточнить владельцев и существующие ограничения + +- [x] 1.1 [frontend] Перенести route-aware AppShell к владельцу приложения. + - **Covers:** `N/A` — техническое исправление F3; product shell не должен стать публичной частью ui, согласно design §1/4. + - **Verification:** `npm run deps`, `npm run typecheck`, `npm run knip`, существующие route/navigation tests, итоговый `npm run verify`; browser navigation/focus и active route. + - **Читать / область:** `.ai/architecture.md`, UI, `src/app/ui/AppShell.tsx` и CSS (до переноса — shared/ui), его callers в `app/routes`, связанные auth/route tests. Найти все imports до переноса. + - **Изменить / готово:** AppShell и принадлежащие ему navigation styles находятся в `app/ui` с допустимым public import. Labels, links, Outlet, transitions, responsive shell прежние; shared/ui не знает product routes. Коротко уточнён legacy пункт architecture. + - **Не входит / сохранить:** Новая навигация, новый wrapper shell, миграция всех SVG, auth policy. Никаких изменений URL и bootstrap states. + - **Откат:** Вернуть перенос и imports одним PR; данных/внешних artifacts нет. Зависимость: нет. + +- [x] 1.2 [frontend] Закрыть same-layer imports действующим dependency-cruiser. + - **Covers:** `N/A` — enforcement уже принятого FSD profile, F4; не новая пользовательская возможность. + - **Verification:** Зафиксировать вывод существующего `npm run deps` до правки; после неё тот же gate отклоняет временный контролируемый forbidden import между двумя slices, чистый graph проходит; удалить пробный import, затем `npm run verify`. + - **Читать / область:** Architecture/imports, `dependency-cruiser.cjs`, реальные alias/relative imports в pages/features/entities; public API примеры account-operations и bank-card. + - **Изменить / готово:** Точные запреты соседних slices pages/features/entities, без запрета imports внутри одного slice и без ошибочного применения к shared segments. Если baseline выявит legacy, правило включается для чистой области с адресным исключением и условием удаления, без broad ignore. + - **Не входит / сохранить:** Новый analyzer, автоматический fixer, массовая раскладка FSD, запрет page-local composition. Не писать постоянный анализатор проверки анализатора. + - **Откат:** Вернуть только rule change, обязательный semantic review остаётся. Зависимость: нет. + +## 2. Подготовить пакеты без изменения представления + +- [x] 2.1 [frontend] Ввести workspace tokens и охват его контрактов. + - **Covers:** `frontend-design-system` — `Общий фундамент доступен через проверяемые локальные пакеты` + - **Verification:** Будущая package-конфигурация: `npm pack --dry-run` показывает JSON; чистый временный consumer вне workspace читает JSON обычным parser; `npm ci`, расширенные существующие type/lint/Knip/deps scopes, `npm run verify`; чистые Docker builds development/production с новым workspace manifest и JSON. + - **Читать / область:** Engineering/dependencies, package/lock, TS/Knip/deps configs, `Dockerfile`, `.dockerignore`, `packages/tokens/tokens.json`, `shared/theme/theme.ts`, `theme.test.ts`; design §1–3/10. После 1.1–1.2; имена остаются предлагаемыми до принятия design. + - **Изменить / готово:** `packages/tokens` владеет тем же JSON v1; root app private; один npm workspace lock, прямые зависимости, ограниченные files/exports. Адаптер читает package JSON. Проверки явно видят packages и запрещают package → app. Docker получает workspace manifests перед npm ci и только нужные package inputs через узкую allowlist, без host node_modules/dist/.env. + - **Не входит / сохранить:** Новая schema, Style Dictionary, mobile/dark, преобразование всех токенов и CSS. Не поддерживать две редактируемые копии JSON; app appearance и все units прежние. + - **Откат:** Вернуть JSON/imports/manifest/lock как связанный diff; package ещё не опубликован. + +- [x] 2.2 [frontend] Упаковать Mantine adapter и общие стили в ui. + - **Covers:** `frontend-design-system` — `Общий фундамент доступен через проверяемые локальные пакеты` + - **Verification:** Существующие theme tests на новом owner; будущие Vite library build + declaration emit и packed consumer type/build/render без app alias; на чистом checkout после `npm ci`, без прежнего package dist/cache, `npm run verify` сначала собирает packages, затем проверяет consumer. `npm ls react react-dom @mantine/core`; отдельные чистые Docker development/production builds и render темы. + - **Читать / область:** `packages/ui/src/{theme.ts,styles.css,theme.test.ts}`, facade `shared/theme/index.ts`, `app/ui/App.tsx`, `.storybook/{main.ts,preview.tsx,dependency-cruiser.cjs}`, `vite.config.ts`, `vitest.config.ts`, package scripts, `Dockerfile`, `.dockerignore`, design §2–4. Зависимость: 2.1. + - **Изменить / готово:** `ui/theme`/`ui/styles.css`, dependency tokens, peers/external React/Mantine, CSS side effects, JS/d.ts/CSS в package. App/preview явно подключают styles и прежние providers. Package build предшествует всем проверкам потребителя в local/CI/Docker; scope tests/coverage следует owner. `shared/theme` только re-export. Host dev допускает package watch; в Docker изменение packages требует документированного rebuild dev image, без нового root orchestration. + - **Не входит / сохранить:** Новый global provider, новый Storybook app, component wrappers, Modal animation cleanup. Library build не наследует app PWA/service-worker/proxy plugins. Не менять light scheme, source values, CSS order и effective theme; сравнить фактический bundle size с baseline, не заявляя отсутствующий budget gate F12. + - **Откат:** Вернуть adapter/styles/imports и manifest/lock единым PR. Временный facade удаляется в 7.1, новые consumers его не расширяют. + +## 3. Общие компоненты и продуктовый пилот + +- [x] 3.1 [frontend] Перенести StateView и LoadingSkeleton с их stories. + - **Covers:** `frontend-design-system` — `Общий фундамент доступен через проверяемые локальные пакеты` + - **Verification:** `npm run deps:storybook`, `npm run build:catalog`, существующие проверки StateView/LoadingSkeleton/composeStories в новом scope, будущий packed component render, `npm run verify`. + - **Читать / область:** Три UI-conventions темы, два `src/shared/ui` компонента/stories и callers, `src/test/storybook.test.tsx`, package ui. Зависимость: 2.2. + - **Изменить / готово:** Реализации и CSF stories у ui, один catalog glob включает package stories. Props без query/i18n/router; busy label/status и error alert/action сохраняются. Проверка Storybook reachability включает новый каталог. Если нужен facade, он только переэкспортирует. + - **Не входит / сохранить:** Перемещение page skeleton shapes, новый loading framework, полный набор stories для Mantine. No backend и реальные финансовые данные. + - **Откат:** Вернуть перенос; содержимое/поведение исходных components не менялось. Удаление facade — 7.1. + +- [x] 3.2 [frontend] Перенести EntryCard и подтвердить пилот RecentEvents. + - **Статус 2026-09-22:** Реализация, 278 tests/full verify, Docker, packed consumer и browser desktop/375px прошли. Пользователь подтвердил успешную ручную проверку coarse pointer/reduced motion по предложенному чек-листу; browser-ограничение закрыто. CSS перенесён побайтово. + - **Covers:** `frontend-design-system` — `Общий фундамент доступен через проверяемые локальные пакеты` + - **Verification:** `npm run test:run -- src/pages/account/ui/AccountPage.test.tsx`, существующие ledger/CardPage cases использующих EntryCard; минимальный UI-test собственного image fallback при необходимости; `npm run deps:storybook`, `npm run build:catalog`, packed consumer render, `npm run verify`; browser сравнение 375px/desktop. + - **Читать / область:** `EntryCard.tsx`/CSS и все callers; `AccountPage.vm.ts::toRecentEvents`, `useBrokerageEvents`, `BrokerageEventsPreview`, `BrokerageEventCard` и их stories, design UI/UX scope. Зависимость: 3.1. + - **Изменить / готово:** Один EntryCard в ui с тем же CSS/props; entity пилота импортирует публичный ui package. В Storybook — существующие RecentEvents states и малая generic story длинного контента/image fallback/actions. Пилот реально проходит через API owner до прежнего presentation, без смены data flow. Остальные callers могут пока использовать re-export. + - **Не входит / сохранить:** Generic icons, PositionCard rewrite, исправление F8. Сохраняются unknown≠zero, currency/date/account, logo fallback, preview limit/full-list link; у других callers — sign и disabled placeholder/archive guards. + - **Откат:** Вернуть consumer import и extraction PR. Не вводить вторую реализацию EntryCard для пилота. Facade removal — 7.1. + +- [x] 3.3 [frontend] Подключить существующие semantic roles внутри EntryCard. + - **Статус 2026-09-22:** Реализованы border/subtle/surface. Positive/negative уже были semantic (shade 9); neutral gray-7 оставлен без неточного сопоставления. npm ci/full verify (278 tests) и browser computed colors/narrow/focus прошли. Пользователь подтвердил ручную проверку coarse pointer/reduced motion по чек-листу; пункт закрыт. + - **Covers:** `N/A` — F1: применение уже существующей темы после структурного переноса, без нового UI behavior. + - **Verification:** Сравнить computed colors и выбранные EntryCard/RecentEvents states до/после; gray-3→border, gray-0→subtle эквивалентны; текущие positive/negative (shade 9) сохранены; background tint/white→surface проверен; `npm run verify` и browser narrow/focus/coarse-pointer/reduced-motion. + - **Читать / область:** ui EntryCard/CSS, tokens color.roles, ui/theme resolver, generic и product stories. Зависимость: 3.2. + - **Изменить / готово:** Прямые эквиваленты заменены semantic CSS variables; текущий вид и actions не меняются. Если эквивалентность конкретной пары не доказана, оставить её локальной и пояснить, не создавать токен автоматически. + - **Не входит / сохранить:** Новые palettes, card brand gradients, компонентный токен на каждое свойство, массовый CSS sweep. + - **Откат:** Вернуть только CSS/inline-role diff; package ownership остаётся. + +## 4. Собственные общие иконки + +- [x] 4.1 [frontend] Подготовить минимальный icons package. + - **Covers:** `frontend-design-system` — `Собственные общие иконки имеют доступный контракт`; `frontend-design-system` — `Общий фундамент доступен через проверяемые локальные пакеты` + - **Verification:** Будущие package build/declarations/pack consumer checks; synthetic decorative и meaningful icon examples с именем/размером/currentColor; `npm ls react`, `npm run deps:storybook`, `npm run verify`. + - **Читать / область:** Card/Cards inline plus/pencil/trash, `BankOperationIcon` как пример accessibility, `bind-icon-animation` только для границ ответственности, design §5. Зависимость: 2.1; после 2.2 можно переиспользовать package build pattern без общего самописного генератора. + - **Изменить / готово:** Именованные Add/Edit/Delete SVG-компоненты, один TSX source на рисунок, React peer/external, public types, ограниченные exports. Static package не тянет motion/UI/domain; декоративность по умолчанию и доступное meaningful использование документированы. + - **Не входит / сохранить:** SVG registry по строке, raw SVG+TSX дубли, SVGR/dependency, перенос категорий/навигационных анимаций. Сохранить geometry/stroke/size существующих рисунков, без редизайна. + - **Откат:** Удалить ещё не используемый package diff; до публикации внешнего контракта нет. Если 4.2 уже интегрирован, откатывать согласованно. + +- [x] 4.2 [frontend] Перевести только повторяющиеся Card/Cards actions на icons. + - **Covers:** `frontend-design-system` — `Собственные общие иконки имеют доступный контракт` + - **Verification:** `npm run test:run -- src/pages/card/ui/CardPage.test.tsx`, `npm run test:run -- src/pages/cards/ui/CardsPage.test.tsx`, `npm run verify`; browser accessible names/keyboard и appearance выбранных Add/Edit/Delete actions. + - **Читать / область:** `pages/card/ui/CardPage.view.tsx`, `pages/cards/ui/CardsPage.view.tsx`, ActionIcon consumers и imports. Зависимость: 4.1. + - **Изменить / готово:** Повторяющиеся SVG заменены named imports; размеры/strokeWidth задаются явно, если раньше различались. Имя и disabled остаются на action; внутри SVG нет дополнительного focus. В изменённом scope больше нет старых копий этих рисунков. + - **Не входит / сохранить:** BankOperationIcon mappings, формы/mutations, поведение edit/delete и confirm dialogs, иной визуальный стиль. + - **Откат:** Вернуть caller diff; пакет может оставаться неиспользуемым до отдельного решения, опубликованную версию не удалять. + +## 5. Адресные улучшения после основы + +Эти задачи самостоятельны. Их незавершённость не оправдывает расширение +пакетного PR или утверждение, что изменено поведение формы в пилоте. + +- [x] 5.1 [frontend] Проверить и при необходимости свести Modal animation к одному владельцу. + - **Статус 2026-09-22:** BankCardForm browser A/B показал различный lifecycle: CSS даёт mount entrance, Mantine — exit/callback. Production-код сохранён; причины документированы в UI convention. Пользователь подтвердил ручную проверку reduced motion, Escape/Отмена и возврата фокуса; пункт закрыт. + - **Covers:** `N/A` — F5, устранение подтверждённого дублирования механизмов без нового продукта. + - **Verification:** Browser comparison открытия/закрытия существующей банковской Modal: overlay, motion, focus return, reduced motion; relevant Modal/form tests и `npm run verify` только при code diff. + - **Читать / область:** ui theme Modal defaults, ui global styles, BankCardForm/OperationForm consumers. Зависимость: 2.2. + - **Изменить / готово:** Выбран один механизм либо документирован конкретный различный смысл обоих. При эквивалентности предпочтён Mantine transition и убран только лишний CSS. Наблюдение «видимых проблем нет» само по себе не требует нового кода. + - **Не входит / сохранить:** Новые timings, анимационная система, замена motion маршрутов. Keyboard/focus/closing rules неизменны. + - **Откат:** Вернуть animation-only diff; component API/data не меняются. + +- [x] 5.2 [frontend] Свести runtime vocabulary банковских operation kinds у entity. + - **Covers:** `N/A` — часть F6, одна типизированная коллекция для существующих schema/choices без изменения wire contract. + - **Verification:** `npm run typecheck`, `npm run test:run -- src/pages/card/ui/OperationForm.test.tsx`, `npm run deps`, `npm run verify`; все четыре прежних вида и defaults сохранены. + - **Читать / область:** `entities/bank-card/api/queries.ts` и index, `OperationForm.tsx`/`.view.tsx`/`.types.ts`, TypeScript/data conventions. Зависимость: 1.2. + - **Изменить / готово:** Schema и choices используют один `as const` набор, проверенный относительно generated `BankOperation['kind']`. Не добавлен enum, DTO copy или cast неизвестного code; строки UI остаются i18n. + - **Не входит / сохранить:** Generic errors registry, backend enum generation, механическое именование каждого literal, controller refactor. + - **Откат:** Вернуть маленький vocabulary/import diff; значения wire прежние. + +- [x] 5.3 [frontend] Отделить pure period helpers от AccountIncomePage VM. + - **Covers:** `N/A` — F7, устранение транзитивной зависимости presentation story от VM по design §6/10. + - **Verification:** Existing account-income targeted tests, `npm run deps:storybook`, `npm run build:catalog`, `npm run verify`; synthetic form submit/reset/error states без API/query providers. + - **Читать / область:** `AccountIncomePage.vm.ts`, `components/IncomePeriodForm.view.tsx`, их imports/tests, соседний page model и Storybook convention. Зависимость: 1.2. + - **Изменить / готово:** Только реально используемые pure date helpers у page model и оба callers используют их; полезная isolated presentation story проходит reachability. Нового global date helper/service нет. + - **Не входит / сохранить:** Общий DateRangePicker, изменение диапазонов/часового пояса и перенос логики в UI package. Moscow dates, validation и submit/reset прежние. + - **Откат:** Вернуть helper move/story diff; URL/API поведение не менялось. + +- [x] 5.4 [frontend] Отделить controller банковской OperationForm от presentation. + - **Covers:** `N/A` — часть F6, применение существующей container/VM/view границы без изменения денежных сценариев. + - **Verification:** До и после `npm run test:run -- src/pages/card/ui/OperationForm.test.tsx`; особенно conflict/draft/explicit version adoption, retained UUID, full refund и max money; `npm run deps:storybook`, затем `npm run verify`. + - **Читать / область:** Полные OperationForm/OperationForm.view/types/tests, entity form-validation/mutations и UI/data/testing conventions. Зависимость: 5.2. + - **Изменить / готово:** Query/mutation/recovery orchestration принадлежит page VM/controller; существующий fields view расширен только нужными presentation props/callbacks/slots. Runtime helpers сейчас приходят из banking barrel, который переэкспортирует API/hooks: эту транзитивную связь убрать через props/callbacks, type-only DTO imports допустимы. Не вводить deep-import exception и не ослаблять story gate. RHF context в fields остаётся; `CardPage` целиком не переписывается. + - **Не входит / сохранить:** Generic form engine, extraction в feature/package без второго consumer, новые requests/optimistic updates. Не менять amount string/BigInt, intent lifecycle, version adoption, source constraints, pending close guards и вложенное создание категории. + - **Откат:** Вернуть этот behavior-preserving PR. Любое необходимое изменение поведения остановить и согласовать отдельно, не прятать в refactor. + +## 7. Закончить переход и описать поставленное состояние + +- [x] 7.1 [frontend] Удалить только исчерпанные compatibility facades. + - **Уточнение scope:** Пользователь разрешил удалить локальные facades с проверкой packed consumer вместо registry consumer. Nexus и независимый release остаются отложенными. + - **Covers:** `N/A` — прекращение временного двойного import path после подтверждённого потребления, design Migration Plan. + - **Verification:** `rg` по runtime/test/story imports старых theme/UI paths даёт ноль; `npm run knip`, `npm run deps`, `npm run deps:storybook`, app/catalog/packed consumer checks и `npm run verify`. + - **Читать / область:** Facades `shared/theme` и EntryCard/StateView/LoadingSkeleton, все оставшиеся callers, component source catalog, frontend README и conventions. Зависимости: 3.3, 4.2 и подтверждённый packed consumer без workspace/source aliases. + - **Изменить / готово:** Оставшиеся consumers используют package API, ненужные re-exports удалены, нет дубля JSON/CSS/implementation. В existing conventions закреплены каталог и import rules, AGENTS только маршрутизирует темы. + - **Не входит / сохранить:** Массовая чистка unrelated shared/lib, запрет прямого Mantine, перенос domain UI. Старые подходы вне migrated scope не блокируются новым широким правилом. + - **Откат:** Вернуть thin facades/imports; они не восстанавливают копии реализаций. Условия удаления снова проверяются перед повтором. + +- [x] 7.2 [docs] Обновить только подтверждённое as-built описание. + - **Уточнение scope 2026-09-22:** По запросу следующего пункта без browser verification описано текущее локальное состояние после 5.4. Завершение 7.1 не требуется для этого описания: переходное состояние и отсутствие registry-поставки указаны явно; после 7.1 описание facades актуализировано. После будущей поставки документация обновляется вместе с ней. + - **Covers:** `N/A` — описание фактически принятой реализации и поставки; не новый behavior contract. + - **Verification:** Docs `npm run verify` по местному CONTRIBUTING, проверка локальных ссылок/диаграмм и semantic review по поставленному frontend; `git diff --check`. + - **Читать / область:** Docs AGENTS/README/CONTRIBUTING, `design/overview.md`, `architecture/containers.md`, при необходимости release boundary в `architecture/deployment.md`; frontend принятый package contract. Основание текущего описания: выполненные 1–5; удаление facades по 7.1 отражено в документации. + - **Изменить / готово:** Тематические страницы говорят о реально используемой теме/пакетах/границах frontend и каталога. Указывается, что raw SVG/mobile/dark/API package не поставлены. При неполной 5.x не обещать завершение этих пунктов. + - **Не входит / сохранить:** Анализ/план в permanent docs, новый roadmap, переописание backend, ADR на каждый маленький выбор. Один устойчивый ADR возможен лишь если принято значимое repository/release решение и нужен его rationale. + - **Откат:** Вернуть описание вместе с отменённой поставкой; не менять исторические archives. + +- [x] 7.3 [specs] Сверить реализацию с delta и закрыть плановые расхождения. + - **Статус 2026-09-22:** Semantic сверка завершена для согласованного локального объёма: proposal/design/delta согласованы с private workspaces и packed consumer; внешняя публикация исключена. Код и ручные browser checks проверены ранее. Specs verify, локальные ссылки и whitespace проверены после правок. После установки OpenSpec выполнена команда `openspec validate evolve-frontend-stack --type change --strict --store wealth-ledger-specs`: change valid. Пункт завершён. + - **Covers:** `frontend-design-system` — `Общий фундамент доступен через проверяемые локальные пакеты`; `frontend-design-system` — `Собственные общие иконки имеют доступный контракт` + - **Verification:** Semantic проверка всех delta scenarios, `npm run verify` specs, при доступном CLI `openspec validate evolve-frontend-stack --type change --strict --store wealth-ledger-specs`; локальные ссылки и `git diff --check`. + - **Читать / область:** Specs AGENTS/README, proposal/delta/design/tasks и фактические результаты consumer/rollback/accessibility checks. Зависимости полного acceptance: 3.2–3.3, 5.1–5.4 и 7.1–7.2; требования delta должны соответствовать согласованному локальному объёму. + - **Изменить / готово:** Tasks отражают только фактически проверенные результаты; согласованные изменения scope возвращены в planning artifacts, не скрыты. Main specs обновляются только в отдельно разрешённом штатном завершении после реального внедрения; untouched deferred findings остаются явно отложенными. + - **Не входит / сохранить:** Автоматический archive/approval, Evidence/state/hash registry, декларация успеха на основании одного build. Промежуточная сверка не означает завершения change. + - **Откат:** Исправить несоответствующее предложение/отметку; main specs не описывают ещё не реализованный переход. + +## Переходный период и отложенные вопросы + +Точный перечень отложенного — в design: F8 recovery RecentEvents, F9 backend +errors contract, F12 расхождение bundle-budget spec/gate, общий investment recovery classifier и новые потребители. +Они не входят скрыто в tasks выше. Если требуется менять HTTP/observable +recovery, нужен отдельный согласованный scope и соответствующий spec contract. + +Facades допустимы только от 2.2/3.x до 7.1; новых возможностей они не получают. +Новые components/imports следуют целевому каталогу. Старые inline рисунки +допустимы лишь до миграции своего caller batch, а новые дубли уже вынесенных +рисунков не создаются. Совместимость снимается по нулю imports и успешному +consumer proof, а не по истечении произвольного срока. + +Проверены задачи **1.1–1.2, 2.1–2.2, 3.1–3.3, 4.1–4.2, 5.1–5.4 и 7.1–7.3**. +Недостающие browser checks 3.2/3.3/5.1 выполнены вручную пользователем: +он подтвердил корректность всех пунктов переданного чек-листа. +Независимые версии и Nexus отложены до оценки пользы после пилота; +checkbox не разрешает дальнейшую реализацию или публикацию. + +Последний полный frontend `npm run verify`: 49 test files / 282 tests, +проверки зависимостей, типов, lint, Knip, OpenAPI и сборки app/catalog прошли. +Свежий UI tarball проверен вне workspace: install, typecheck, build и browser render. +Docs `npm run verify`: 45 Markdown-файлов, 0 issues.