1002 lines
104 KiB
Markdown
1002 lines
104 KiB
Markdown
# Читаемость и сопровождаемость WealthLedger
|
||
|
||
## 1. Область исследования
|
||
|
||
Отчёт от 27 сентября 2026 года. Результат — семь подтверждённых возможностей
|
||
упрощения и небольшие порции работы. Наиболее полезный безопасный старт —
|
||
отделить чистое преобразование будущих выплат от сервисной оркестрации.
|
||
Затронутые области рекомендаций: backend investments/T-Bank, frontend
|
||
страницы инструментов, адаптер ресурсных запросов и несколько тестов.
|
||
Изменений реализации в рамках исследования нет.
|
||
|
||
Это ревью стоимости понимания и изменения существующего кода. Бизнес-баги,
|
||
безопасность, производительность и полнота продукта не оценивались.
|
||
Количество файлов, строк и тестов использовалось для навигации, а не как
|
||
оценка качества. Ни одно удаление production-кода не предлагается на основании
|
||
одного поиска по имени.
|
||
|
||
### Репозитории и глубина чтения
|
||
|
||
Все доступные first-party пакеты с production-кодом включены в обследование.
|
||
Root, frontend, backend, docs и specs — самостоятельные Git-репозитории.
|
||
В начале исследования рабочие деревья были чистыми. Пути в отчёте указаны
|
||
от корня workspace; диапазоны строк относятся к следующим исходным HEAD:
|
||
|
||
| Репозиторий | HEAD | Обследованная область |
|
||
| --- | --- | --- |
|
||
| frontend | `f6db28defa51bd9d8fe932ceae4c3c720aba7eb3` | Приложение, три workspace-пакета, публичные экспорты, статический граф, тестовые группы |
|
||
| backend | `4796d9d3f0160d96004a982f6c2526f199de4b9f` | Nest-модули, integrations, Prisma-границы, DTO, unit-тесты и выбранные HTTP/SQLite-сценарии |
|
||
| docs | `e4838e14855e9e696683a310efbc6afaaae51164` | Правила документации, архитектура, свежесть, доход, будущие выплаты и относящиеся к ним ADR |
|
||
| root, specs | Навигация и правила локальных checkout | Manifest/workspace-конфигурация и требования как контекст; самостоятельного runtime-пакета здесь нет |
|
||
|
||
Прочитаны локальные AGENTS и применимые архитектурные, инженерные и тестовые
|
||
правила соответствующих репозиториев. Общие правила root не переносились на
|
||
приложения. Размещение этого отчёта в docs — прямо запрошенное пользователем
|
||
исключение из местного запрета на хранение исследовательских отчётов; правила
|
||
репозитория и навигационные индексы не изменялись.
|
||
|
||
В frontend прослежены маршруты и сессия → API/query → entity → page VM → view;
|
||
особенно подробно — resource lifecycle, синхронизация экрана, акции/облигации,
|
||
портфель, фильтры операций/дохода и банковские формы. Прочитаны реализации
|
||
и экспорты tokens/ui/icons. В backend прослежены auth, банковские расчёты и
|
||
транзакции, инвестиционные ресурсы, операции/доход/прогноз, источники каталога
|
||
и свечей, T-Bank/MOEX/НКР. Для находок прочитаны конкретные реализации,
|
||
вызывающий код и относящиеся к ним тесты.
|
||
|
||
**Ограничения:** это не построчное ревью каждого файла. Крупные наборы
|
||
AccountIncomePage, банковских UI/HTTP-тестов, persistence-тестов и отдельных
|
||
view/story-файлов читались выборочно; перечни остальных сценариев использованы
|
||
для карты покрытия. Не исследованы полностью миграции, эксплуатационные
|
||
скрипты, контейнерный runtime и все ветви каждого scheduler. Архивы,
|
||
`references/**`, generated-код, vendor, сборки и lockfile не оценивались как
|
||
авторский код. Источники OpenAPI-генерации и граница передачи схемы учитывались.
|
||
Graphify-графа в checkout нет; он не создавался, чтобы не оставлять дополнительные
|
||
артефакты. Реальные внешние API, production и потребители за пределами доступных
|
||
checkout не проверялись. Признак `private` в manifest не доказывает отсутствие
|
||
неизвестного внешнего использования.
|
||
|
||
### Фактический стек
|
||
|
||
Версии ниже взяты из manifest и сверены с установленными пакетами для основных
|
||
runtime-библиотек. Это фиксация контекста, а не предложение обновить зависимости.
|
||
|
||
| Область | Используемые пакеты и версии |
|
||
| --- | --- |
|
||
| Frontend workspace | `wealth-ledger-frontend` 0.4.0; `@wealthledger/tokens`, `@wealthledger/ui`, `@wealthledger/icons` — 0.3.0; все private |
|
||
| UI и состояние | React/React DOM 19.3.0; React Router 8.3.1; Mantine core/hooks/dates/charts 9.6.1; TanStack Query 5.102.8 |
|
||
| Формы и API | React Hook Form 7.88.0; resolvers 5.9.1; Zod 4.6.4; openapi-fetch 0.17.0; openapi-react-query 0.5.4 |
|
||
| Представление | i18next 26.4.2; react-i18next 17.0.14; motion 13.2.0; dayjs 1.11.23; recharts 3.10.1 |
|
||
| Frontend tooling | TypeScript 5.9.3; Vitest 5.0.0; MSW 2.15.0; Vite 8.3.0 и Storybook 10.6.0 по manifest |
|
||
| Backend | `wealth-ledger-backend` 0.2.3; один пакет без npm workspaces |
|
||
| Сервер и хранение | Nest common/core/platform-express/swagger/jwt 12.0.1; config/passport 12.0.0; Prisma/client/SQLite adapter 7.10.0; better-sqlite3 13.0.3 |
|
||
| Внешние границы | grpc-js 1.14.4; proto-loader 0.8.1; class-validator 0.15.1; class-transformer 0.5.1; bcrypt 6.0.0; Cheerio 1.2.0; lru-cache 11.5.2 |
|
||
| Серверное окружение | nestjs-pino 5.1.0; pino-http 11.0.0; RxJS 7.8.2; TypeScript 6.0.3; Jest 30.5.1 |
|
||
| Документация и root | docs 0.1.2, root 0.2.0 — tooling; markdownlint-cli2 0.23.2, relative-links 5.1.2; Node 24.21.0 из локальных `.nvmrc` |
|
||
|
||
### Выполненные проверки
|
||
|
||
Перед Node-командами активирована локальная версия через
|
||
`source ~/.nvm/nvm.sh && nvm use --silent`. Использованы уже установленные
|
||
зависимости, без auto-fix и установки инструментов.
|
||
|
||
| Где | Команда | Результат |
|
||
| --- | --- | --- |
|
||
| frontend | `./node_modules/.bin/depcruise src packages --config dependency-cruiser.cjs --output-type err-long` | 322 модуля, 1381 зависимость; нарушений настроенных правил нет |
|
||
| frontend | `./node_modules/.bin/vitest run --no-cache` | 54 файла, 357 тестов прошли |
|
||
| frontend | `./node_modules/.bin/tsc -p tsconfig.app.json --noEmit --incremental false` | Успешно; это проверка app-конфигурации, не отдельная сборка каждого пакета |
|
||
| frontend | `./node_modules/.bin/knip --no-progress` | Exit 0; неиспользуемые элементы не заявлены; configuration hint: исключённые `.mdx` не обходятся |
|
||
| backend | `npm test -- --runInBand --no-cache` | 36 suites, 209 тестов прошли; snapshots: 0 |
|
||
| backend | `npm run typecheck:app && npm run typecheck:test` | Обе проверки прошли |
|
||
| docs | `npm run verify` | Успешно: Markdown lint проверил 47 файлов, замечаний нет; `git diff --check` прошёл |
|
||
|
||
Существующие backend HTTP/SQLite/e2e, durability/container gates и OpenAPI parity
|
||
в этой сессии не запускались. Не выполнялись браузерная/визуальная проверка,
|
||
полный application verify, сборка и генерация артефактов. Успех unit-тестов
|
||
не подменяет эти проверки и не доказывает поведенческую эквивалентность будущих
|
||
изменений. Для самого отчёта достаточно docs gate и смысловой сверки; в порциях
|
||
ниже указана дополнительная проверка будущего рефакторинга.
|
||
|
||
## 2. Краткий вывод
|
||
|
||
Основная сложность возникает на стыках уже полезных механизмов:
|
||
сервисный граф загружается ради чистого mapper; одинаковый cursor-протокол
|
||
поддерживается в двух VM; смысл периода передаётся через callback и индекс;
|
||
результат предметного ресурса маскируется типом нативного Query result.
|
||
Это локальные проблемы ответственности и явности контрактов. Перестройка
|
||
всей архитектуры или смена библиотек не обоснована.
|
||
|
||
Предлагаемый порядок: маленький mapper-пилот, единое распознавание ошибки
|
||
T-Bank, общая пагинация двух инструментальных страниц; затем отдельно
|
||
типизация resource-result и более рискованная граница income/operations.
|
||
Два замечания к тестам имеют меньший приоритет: неточное описание проверок
|
||
и зависимость layout-теста от внутреннего CSS Mantine.
|
||
|
||
**Оставить без переписывания:**
|
||
|
||
- FSD-направление импортов, публичные package exports и backend `*.api.ts`.
|
||
В обследованном frontend-графе не обнаружена утечка application/domain в
|
||
общие пакеты. Переэкспорты здесь обозначают реальные границы.
|
||
- `EntryCard`, `StateView`, `LoadingSkeleton`: общая структура, доступность
|
||
и состояния имеют собственную ответственность. Это не повод заменить
|
||
каждый компонент прямым вызовом Mantine.
|
||
- Tokens/theme adapter и три SVG-компонента icons: локальные преобразования
|
||
и явные компоненты проще фабрики или нового runtime-валидатора trusted JSON.
|
||
- React.lazy-адаптеры именованных экспортов: они сохраняют lazy-loading.
|
||
Роутер не нуждается в универсальном конфигурационном языке.
|
||
- Состояние/refs сессии, single-flight, очередь/Web Locks, generation/access
|
||
epochs и очистку кэша. Похожее состояние обслуживает разные временные
|
||
границы; простое удаление разрушает защиту поздних ответов.
|
||
- Разделение transport error, failed source, missing/partial/complete и
|
||
сохранённого snapshot. TanStack Query не знает этого предметного протокола.
|
||
- Draft/applied фильтры и стабилизацию периода в income VM: эффект reset формы
|
||
зависит от идентичности значения; механическое удаление memo не обосновано.
|
||
- Точную decimal/bigint-арифметику, банковский `BankErrorCode`, транзакции,
|
||
проверки версий, idempotency и возвраты. Не заменять их float-арифметикой,
|
||
новым money-пакетом или generic repository над Prisma.
|
||
- Валидацию MOEX/НКР/T-Bank, ограниченные retry/admission и fencing публикации.
|
||
Типы TypeScript не проверяют внешний ответ. Scheduler свечей, операций и
|
||
каталога различаются обязанностями; единый job engine не предлагается.
|
||
|
||
## 3. Рабочие критерии рефакторинга
|
||
|
||
1. Правка должна уменьшать число мест, которые нужно открыть для изменения
|
||
конкретного правила. Новое имя/файл оправдано понятной ответственностью,
|
||
а не ограничением длины функции.
|
||
2. Использовать существующего владельца: UI — packages/ui, API транспорта —
|
||
shared/api, история инструмента — instrument-details, особенности провайдера —
|
||
integrations/tbank, периоды дохода — income. Не складывать предметные правила
|
||
в общий `utils` и не заводить новые пакеты для этих находок.
|
||
3. Объединять совпадающее поведение, а не похожий синтаксис. У страницы всех
|
||
операций иной scope и фильтры, у купона и дивиденда разные даты/единицы.
|
||
4. Сохранять локальный OpenAPI input и wire values. Generated-файлы вручную
|
||
не править; независимый литерал в контрактном тесте может быть полезен.
|
||
5. Сначала искать определение предметного значения. Существующий `as const`
|
||
не дефект. Во frontend `erasableSyntaxOnly` исключает обычный строковый enum;
|
||
предпочтение enum из запроса не применяется вопреки этому ограничению.
|
||
6. Тип должен описывать фактический результат. Удаление assertion полезно,
|
||
если после него виден настоящий контракт; замена на `any`/`unknown as` не
|
||
является упрощением.
|
||
7. Не удалять редкие ветви без доказательства эквивалентной защиты. Сохранить
|
||
отмену, доступ, late responses, атомарность, точность и accessibility.
|
||
8. Сначала получить тот же наблюдаемый результат существующим тестом.
|
||
Добавлять точечную проверку только там, где затрагиваемый контракт не
|
||
защищён; не тестировать библиотеку ради формального покрытия.
|
||
9. Проверять affected area после diff: не появился ли второй способ решать
|
||
задачу, не потеряны ли публичный экспорт или уникальное утверждение теста.
|
||
Размер diff и зелёный lint сами по себе этого не доказывают.
|
||
|
||
## 4. Карта ответственности и зависимостей
|
||
|
||
| Владелец | Ответственность и публичная граница | Потребители и направление | Решение |
|
||
| --- | --- | --- | --- |
|
||
| frontend app | Маршруты, auth composition, страницы; `app → pages → features → entities → shared` | Браузерное приложение; page собирает сценарий из нижних слоёв | Сохранить; не создавать общий page engine |
|
||
| `@wealthledger/tokens` | Значения дизайна, export `./tokens.json` | UI theme adapter | Сохранить trusted data boundary |
|
||
| `@wealthledger/ui` | Общие визуальные компоненты и тема; exports `.`, `./theme`, `./styles.css` | App/Storybook; зависит от tokens, peer React/Mantine | Доменной логики в прочитанной реализации не найдено |
|
||
| `@wealthledger/icons` | SVG и доступные props; export `.` | App/UI stories, peer React | Не добавлять зависимости на domain, UI или motion |
|
||
| frontend shared/api | OpenAPI client, session guards, адаптация resource lifecycle | Entities и features | Полезная граница; уточнить тип результата по R-04 |
|
||
| frontend instrument-details | Представление секций и истории инструмента | StockPage/BondPage | Сюда подходит общее поведение пагинации R-03 |
|
||
| backend auth/banking | Локальная сессия; ручная финансовая история и её транзакции | HTTP controllers, собственные services/Prisma | Не насаждать ports/adapters на каждый CRUD |
|
||
| backend investments | Счета, портфель, операции, доход, прогноз, инструменты, screen sync | Контроллеры → application; domain для чистых правил, infrastructure для хранения/адаптации | R-01 и R-05 уточняют локальные границы |
|
||
| backend integrations | T-Bank gRPC, MOEX HTTP, НКР HTML; публичные `*.api.ts` | Investment-модули; интеграции не зависят от бизнес-модулей | R-02 оставляет классификацию upstream-ошибки у интеграции |
|
||
| `InstrumentSourcesModule` | Каталог, enrichment, свечи и repositories | PortfolioModule и InstrumentsModule | Сохранить отдельный модуль источников |
|
||
| `InstrumentsModule` | Stock/bond business services и HTTP controllers | Использует sources и PortfolioModule | Не сливать с sources: портфелю не нужен обратный импорт всего InstrumentsModule |
|
||
| Backend OpenAPI → frontend snapshot | DTO/Nest metadata — источник wire-контракта; frontend получает артефакт явно | Нет обязательного чтения соседнего checkout при CI frontend | Сохранить независимость репозиториев |
|
||
| Root/docs/specs | Навигация/tooling; объяснение системы; поведенческие требования | Не runtime-зависимости приложений | Не переносить сюда код или общий реестр констант |
|
||
|
||
Существенная подтверждённая проблема зависимостей — инфраструктурный runtime-import
|
||
ради чистых преобразований (R-01). Две другие связи скрыты в протоколах, а не
|
||
в циклическом импорте: income ↔ порядок массивов (R-05), resource ↔ обещания типа
|
||
Query result (R-04). Depcruise не сообщил нарушений настроенного frontend-графа;
|
||
это не доказательство отсутствия всех возможных динамических связей. Полная
|
||
автоматическая проверка циклов backend не проводилась, утверждения «циклов нигде
|
||
нет» здесь нет. Knip не даёт оснований для массового удаления экспортов.
|
||
|
||
Карта согласуется с [контейнерами](architecture/containers.md),
|
||
[моделью свежести](architecture/cross-cutting/data-freshness.md),
|
||
[границей OpenAPI](adr/014-trusted-openapi-input.md) и
|
||
[решением о локальном чтении](adr/017-local-tbank-reads-and-background-sync.md).
|
||
Эти ссылки объясняют существующее устройство, не вводят новые требования.
|
||
|
||
## 5. Подтверждённые находки
|
||
|
||
Приоритет означает выигрыш в сопровождаемости с учётом риска, а не серьёзность
|
||
предполагаемого бизнес-бага. R-01 — первый пилот; R-02/R-03 — следующие локальные
|
||
упрощения; R-04/R-05 требуют более осторожной проверки; R-06/R-07 — тестовый долг.
|
||
|
||
### R-01. Чистые проекции импортируются через сервисный граф
|
||
|
||
**Места и повторения.** Backend:
|
||
|
||
- `src/modules/investments/upcoming-events/upcoming-events.service.ts:205–248,267–278` —
|
||
`dividendEvent`, `bondEvent`, `projectedMoney`; вызовы в `get`, строки 149–160.
|
||
- `src/modules/investments/upcoming-events/upcoming-events.service.spec.ts:1–5,14–54` —
|
||
тестирует эти чистые функции, но подменяет `PrismaService` для загрузки imports.
|
||
- `src/modules/investments/instruments/stocks/application/stock.spec.ts:1–7` —
|
||
тот же setup для `metrics`, `stockDividends`, `stockQuote`; определения:
|
||
`get-stock-company.use-case.ts:39`, `stock-income.service.ts:101`,
|
||
`stock-market.service.ts:62` в том же каталоге.
|
||
- `src/modules/investments/instruments/bonds/application/bond.spec.ts:1–7` —
|
||
такой же Prisma mock для `bondCoupons`, `bondMetrics`, `bondQuote`;
|
||
определения в `bond-income.service.ts:128,186`, `bond-market.service.ts:66`.
|
||
|
||
**Стоимость.** Чтобы понять и проверить умножение количества на выплату,
|
||
приходится учитывать Nest/Prisma import graph. Тестовый setup маскирует лишнюю
|
||
связь с инфраструктурой. Причина не в длине сервиса и не в наличии функций
|
||
рядом с классом, а в том, что самостоятельный расчёт не загружается независимо.
|
||
|
||
**Минимальная правка.** В пилоте перенести только две upcoming-event проекции
|
||
и их общий `projectedMoney` в один соседний mapper-модуль. Сервис и существующий
|
||
тест импортируют его, тест перестаёт мокать Prisma. DTO и provider records нужны
|
||
только как type imports. Это преобразование границы провайдера, а не новый
|
||
универсальный domain layer.
|
||
|
||
До: `test → upcoming-events.service → services/repositories → Prisma`.
|
||
После: `test/service → upcoming-event mapper → exact decimal`.
|
||
|
||
Новая ответственность ясна: «provider event + holding → event DTO».
|
||
Не добавлять Injectable mapper, интерфейс с одной реализацией, отдельный файл
|
||
для каждой функции или общий движок stock/bond. Остальные helpers сервиса
|
||
не переносить автоматически. Stock/bond рассматривать после оценки пилота.
|
||
|
||
**Сохранить.** Registry/event date kind, точные знаковые дробные количества,
|
||
нормализацию валюты, `null` при неизвестной сумме/валюте, оферту и погашение.
|
||
Фильтрация отменённых дивидендов, окно дат, сортировка, totals, account scope
|
||
и атомарная публикация остаются в текущем сервисном потоке.
|
||
|
||
**Зависимости, риск, проверка.** Независимый небольшой шаг, риск низкий при
|
||
механическом переносе. Существующие три теста проекций сохраняются со всеми
|
||
assertions; app/test typecheck проверяют imports. Для связанного runtime
|
||
сохраняется `test/upcoming-events.e2e-spec.ts:255–284`: HTTP-проекция, суммы,
|
||
account/all и отсутствие private holdings в общем источнике. Этот e2e здесь
|
||
прочитан, но не запускался. Порция B-01.
|
||
|
||
### R-02. Распознавание ошибки токена повторяет протокол интеграции
|
||
|
||
**Места.** Источник ошибки —
|
||
`backend/src/integrations/tbank/tbank.client.ts:520–524`: gRPC auth-коды
|
||
нормализуются в `Error('INVALID_TBANK_TOKEN')`. Прочитанные потребители:
|
||
|
||
- `src/integrations/tbank/tbank-token.service.ts:24–31` — перевод в HTTP exception;
|
||
- `src/integrations/tbank/tbank-membership.service.ts:61–65` — аргумент `failMembership`;
|
||
- `src/integrations/tbank/instrument-metadata.service.ts:128–136,181–186` — запрет
|
||
fallback UID→FIGI при token error и отклонение membership;
|
||
- `src/modules/investments/portfolio/application/portfolio.service.ts:372–378` —
|
||
token rejection вместо обычного `repository.fail`;
|
||
- `src/modules/investments/operations/application/brokerage-operations-sync.service.ts:286–291` —
|
||
признак ошибки для repository.
|
||
|
||
Тот же предикат найден и прочитан в
|
||
`src/modules/investments/instruments/application/catalog-discovery.service.ts:348–356`,
|
||
`catalog-enrichment.service.ts:500–509`, `candle.service.ts:443–450`.
|
||
В каждом случае повторяется именно `error instanceof Error && error.message === ...`.
|
||
Записи `errorCode` в repositories — отдельная часть persisted/wire-контракта;
|
||
их не следует заменять без разбора только ради устранения всех строк.
|
||
|
||
**Стоимость.** Знание о форме upstream-ошибки распространено по orchestration.
|
||
При изменении распознавания приходится сверять несколько обработчиков и
|
||
отделять его от разных действий восстановления.
|
||
|
||
**Минимальная правка.** Один integration-owned `isInvalidTbankToken(error)` с
|
||
нынешним точным предикатом, доступный через существующий `tbank.api.ts`.
|
||
До: каждый consumer знает `instanceof Error` и текст сообщения.
|
||
После: consumer вызывает predicate, но сам выбирает `fail`, rejection или HTTP.
|
||
Это единая семантика распознавания, а не глобальный каталог всех ошибок.
|
||
Не менять класс исключения, публичную строку, правила retry и значения хранения;
|
||
не вводить второй enum ошибок frontend поверх generated API.
|
||
|
||
**Сохранить.** Не считать произвольный объект `{message: ...}` подходящим Error;
|
||
не расширять совпадение по подстроке. Сохранить немедленный отказ auth RPC,
|
||
разные side effects и fencing каждого attempt. `BankErrorCode` уже существует
|
||
у banking owner и не относится к этой миграции.
|
||
|
||
**Зависимости, риск, проверка.** Владелец найден; от B-01 не зависит. Риск низкий
|
||
для предиката, средний при широкой замене catch-ветвей. TbankClient unit-сценарии
|
||
строк 267–277 проверяют auth-коды и отсутствие retry; `tbank-token.service.spec.ts`
|
||
проверяет перевод и сохранение несвязанных ошибок. При переносе достаточно
|
||
точечной проверки точного Error/non-Error распознавания и этих тестов;
|
||
не создавать тесты наличия константы. Проверить соответствующие existing
|
||
persistence-сценарии отказа/смены token, если затронута оркестрация. Порция B-02.
|
||
|
||
### R-03. Одинаковая пагинация истории инструмента живёт в двух VM
|
||
|
||
**Места.** Frontend:
|
||
|
||
- `src/pages/stock/ui/StockPage/StockPage.vm.ts:41–61,78–86,158–166`;
|
||
- `src/pages/bond/ui/BondPage/BondPage.vm.ts:41–61,78–86,151–159`.
|
||
|
||
Прочитаны scope `${uid}:${filter}`, стек cursor, запрос `all` с instrumentUid
|
||
и limit 20, recovery `INVALID_OPERATIONS_CURSOR`, exact invalidation первой
|
||
страницы и переходы назад/вперёд. Вызывающее представление истории уже общее
|
||
в `features/instrument-details`.
|
||
|
||
**Стоимость.** Одно изменение cursor/recovery-протокола требует синхронно
|
||
редактировать два несоседних page controller. Его трудно отделить взглядом
|
||
от котировок, свечей и специфичных доходов инструмента.
|
||
|
||
**Минимальная правка.** Один hook поведения истории инструмента у существующего
|
||
`features/instrument-details`, принимающий UID/scope и категории, возвращающий
|
||
operations и действия пагинации. Invalid-cursor recovery принадлежит этому
|
||
поведению; обычный screen retry остаётся callback страницы. Страница продолжает
|
||
владеть URL, различием coupons/dividends и составом `useScreenSync`.
|
||
|
||
До: StockPage и BondPage содержат одинаковые state/query/recovery блоки.
|
||
После: каждая собирает свои ресурсы и один общий `useInstrumentOperations(...)`.
|
||
Не создавать параметризованную фабрику страниц, generic pagination framework
|
||
или переносить всё в shared.
|
||
|
||
**Сохранить.** UID, account=`all`, limit=20, canonical query key, disable до
|
||
появления UID, точную invalidation, историю cursor и URL replace. Особенно
|
||
сохранить нынешнюю семантику A→B→A: scope mismatch временно скрывает старый стек,
|
||
но сам по себе не обнуляет state. «Очистить в эффекте» — уже другое поведение.
|
||
`AccountOperationsPage.vm.ts` имеет иные account/date/category scope, limit 100
|
||
и reset; его не включать по внешнему сходству.
|
||
|
||
**Зависимости, риск, проверка.** Независимо от R-04; риск средний из-за state
|
||
lifecycle. `StockPage.test.tsx:377–408` и `BondPage.test.tsx:378–399` проверяют
|
||
видимую следующую страницу и запрос без cursor после смены фильтра. Сохранить
|
||
оба page-level сценария: они проверяют подключение разных страниц. В прочитанных
|
||
сценариях нет отдельного доказательства invalid-cursor recovery и A→B→A;
|
||
при извлечении добавить только эту адресную проверку наблюдаемого запроса/результата,
|
||
а не тестировать внутренний массив state. Порция B-03.
|
||
|
||
### R-04. Resource-result обещает нативный Query result после изменения его смысла
|
||
|
||
**Места.** `frontend/src/shared/api/resource-query.ts:21–44`,
|
||
`unwrapResourceResult`; использование в `useResourceQuery:154–158` и напрямую
|
||
в entity stocks. Потребители — portfolio/accounts/income/operations/events,
|
||
stocks/bonds; `features/tbank-connection/model/screen-sync.ts` использует
|
||
отдельный `transport.status`, а `features/instrument-details/model/section.ts`
|
||
преобразует состояния в представление.
|
||
|
||
**Стоимость.** Wrapper раскрывает envelope в `data`, меняет `status`,
|
||
`isPending`, `isSuccess`, но приводит весь spread к
|
||
`UseQueryResult<NonNullable<T['data']>, E>`. Непереопределённый `refetch`
|
||
по-прежнему возвращает исходный envelope; `isLoading` остаётся нативным,
|
||
хотя successful HTTP с `data:null` объявляется resource-pending.
|
||
Читатель должен знать исключения из обещанного типа. Это подтверждённая
|
||
неясность контракта, а не утверждение о найденном пользовательском сбое.
|
||
|
||
**Минимальная правка.** Убрать assertion «это полный нативный результат с новым
|
||
T»; вывести фактический тип spread + overrides либо явно описать только
|
||
переопределяемую часть, сохранив реальные типы методов и остальных полей.
|
||
Разделение transport/resource уже есть — использовать его при именовании
|
||
и чтении статусов. Исправить только потребителей, опирающихся на ложное
|
||
типовое обещание; runtime-поля не удалять под видом сокращения интерфейса.
|
||
|
||
До: envelope query преобразован и объявлен другим `UseQueryResult<T>`.
|
||
После: явно типизированный resource result с исходным transport/refetch и
|
||
предметным `data/meta/status`. Не строить второй query cache/state machine,
|
||
не добавлять runtime schema и новые FSD-пакеты.
|
||
|
||
**Сохранить.** `null` payload → отсутствие готового ресурса, last-known data,
|
||
transport/source distinction, auth/access isolation, canonical cache key,
|
||
`observe` без нового import и проверку notified stateVersion. Отдельное
|
||
`as unknown as K` в построении observational query (`126–139`) связано с
|
||
адаптацией generated query key; R-04 не даёт основания просто удалить механизм.
|
||
|
||
**Зависимости, риск, проверка.** Риск средний: широко используемая граница.
|
||
Сначала typecheck всех затронутых callers, затем существующие
|
||
`resource-lifecycle.test.tsx`, `authenticated-query.test.tsx`, screen-sync,
|
||
section и page suites. Проверять разные transport/resource состояния и результат
|
||
refetch; не заменять проверки кэша тестом нового type alias. Минимум — текущие
|
||
контракты в T-02 ниже; весь frontend unit-набор достаточно дешёв для финального
|
||
прогона. Порция B-04, отдельно от извлечения пагинации.
|
||
|
||
### R-05. Период дохода передаётся побочным присваиванием и позицией в массиве
|
||
|
||
**Места.** Backend:
|
||
|
||
- `src/modules/investments/income/income-analytics.service.ts:49–68,79–110` —
|
||
`summary`/`analytics` меняют внешнюю `let plan` внутри callback; затем
|
||
распаковывают `[current, prior, year]`.
|
||
- `src/modules/investments/operations/application/brokerage-operations.service.ts:439–483,493–501,557–559` —
|
||
`collectRanges` передаёт callback в несколько чтений, а имя ресурса выводит
|
||
через `['selected', 'previous', 'yearAgo'][index]`.
|
||
- `src/modules/investments/operations/infrastructure/brokerage-operations.repository.ts:55–68,97–111,202–205` —
|
||
`readPage` получает openedAt и вызывает planner внутри Prisma-транзакции.
|
||
- `src/modules/investments/income/income-analytics.service.spec.ts:35,58,192` —
|
||
mock также должен воспроизводить вызов callback. Production callers
|
||
`collectRanges` найдены только в этих двух методах income.
|
||
|
||
**Стоимость.** Чтобы понять, какой plan соответствует итоговому snapshot,
|
||
нужно проследить побочное присваивание через repository и повторные чтения.
|
||
Чтобы изменить именование периода, нужно знать позиционный договор двух
|
||
модулей. При этом наружу уже передаются явные роли; внутренняя неявность не
|
||
необходима публичному контракту.
|
||
|
||
**Минимальная правка.** Сделать callback чистым и вернуть из `collectRanges`
|
||
данные планирования, относящиеся именно к возвращаемому snapshot, вместе с
|
||
именованными диапазонами. Достаточно конкретного результата: openedAt/нужные
|
||
метаданные snapshot и periods. Income может восстановить свой чистый plan
|
||
из аргументов и возвращённого openedAt; метку диапазона задаёт income, а
|
||
operations переносит её при формировании resourceKey, не выводит из индекса.
|
||
Для отсутствующего snapshot явно сохранить планирование с `undefined`.
|
||
|
||
До: `let plan; await collectRanges(openedAt => { plan = ...; return ranges; })`.
|
||
После: чистый planner + возвращаемые `snapshot context / labeled periods`;
|
||
income собирает ответ из данных одного результата.
|
||
Не нужен универсальный planner framework, реестр периодов, новый слой или
|
||
generic callback, переносящий произвольный application object через Prisma.
|
||
|
||
**Сохранить.** Нельзя сначала отдельно прочитать openedAt, затем получить
|
||
операции: это разорвёт существующий snapshot. Сохранить transaction boundary,
|
||
passive observe, scheduling/fencing, Moscow/date-only границы, историю открытия,
|
||
недоступный leap-day/year comparison, `null` для неполного сравнения, точные
|
||
суммы, hashes и внешние resourceKey. Порядок legacy dependencies и роли
|
||
`selected`, `previous`, `year` также остаются прежними, несмотря на внутреннюю
|
||
метку `yearAgo`.
|
||
|
||
**Зависимости, риск, проверка.** От других находок не зависит, риск выше среднего
|
||
из-за snapshot/coverage. Обязательны income-period и income-analytics unit,
|
||
`test/tbank-income-local.e2e-spec.ts` и соответствующая OpenAPI contract-проверка.
|
||
Прочитанные e2e строки 124–203 отдельно защищают exact totals, повторные ID разных
|
||
счетов, reopen без RPC, missing/partial/confirmed empty и независимые comparison.
|
||
Это не гарантирует все пути concurrent scheduling: их необходимо сохранить
|
||
и адресно проверить при изменении финального snapshot. Порция B-05.
|
||
|
||
### R-06. Layout-тест зависит от внутренней генерации CSS Mantine
|
||
|
||
**Места.** `frontend/src/pages/account/ui/AccountPage/AccountPage.test.tsx:663–681`
|
||
ищет во всех `<style>` строку `--sg-cols:1`, отсутствие `@media` и других
|
||
значений, затем ожидает ровно один найденный стиль.
|
||
`components/PositionSection.view.tsx:13–16` того же каталога задаёт реальный
|
||
контракт: expanded sections имеют `SimpleGrid cols={1}`, остальные responsive.
|
||
|
||
**Стоимость.** Тест назван проверкой одной колонки на любой ширине, но не
|
||
измеряет раскладку. Изменение способа генерации/дедупликации CSS Mantine
|
||
может сломать его при той же геометрии. Совпадение стиля другого элемента
|
||
также не является доказательством контракта нужной секции.
|
||
|
||
**Минимальная правка.** Заменить только это утверждение проверкой геометрии
|
||
нужной секции в настоящем браузере на узкой и широкой ширине, когда есть
|
||
согласованный воспроизводимый способ такого запуска. Контент/доступные роли
|
||
проверять существующими DOM-тестами. Не заводить новый e2e framework или
|
||
полную визуальную матрицу ради одного теста; mock props SimpleGrid тоже
|
||
не является доказательством реальной раскладки.
|
||
|
||
До: число строк CSS с внутренней переменной.
|
||
После: элементы нужной секции располагаются вертикально при двух контрольных
|
||
ширинах. Это проверяет выбранное приложением поведение, а не устройство Mantine.
|
||
|
||
**Сохранить.** Одна колонка у expanded sections и responsive other section;
|
||
логотипы, подписи и суммы не теряют своих существующих проверок.
|
||
|
||
**Зависимости, риск, проверка.** Сейчас равнозначная исполняемая layout-проверка
|
||
не подтверждена, установленного project e2e gate для неё не выявлено.
|
||
Следовательно, текущее решение — **оставить до замены**, а не удалить.
|
||
Риск удаления сейчас — потеря единственной найденной проверки этого свойства.
|
||
B-07 условная, после появления доказанной замены; ручной снимок сам по себе
|
||
не заменяет постоянную регрессионную защиту.
|
||
|
||
### R-07. Названия двух query-тестов обещают больше, чем assertions
|
||
|
||
**Места.** Frontend:
|
||
|
||
- `src/entities/portfolio/api/usePortfolio.test.tsx:11–24` — «30 second cache
|
||
and no retry», но тело ждёт success и проверяет positions;
|
||
- `src/entities/broker-operation/api/useBrokerageOperations.test.tsx:11–30` —
|
||
«without retries», но тело проверяет нормализацию категорий и успешную страницу.
|
||
|
||
**Стоимость.** Название создаёт ложную уверенность, что изменение staleTime
|
||
или retry будет обнаружено. Успешный запрос этого не доказывает; общий test
|
||
QueryClient к тому же отключает retry.
|
||
|
||
**Минимальная правка.** Переименовать тесты по существующим утверждениям:
|
||
«загружает портфель счёта» и «нормализует категории и загружает страницу».
|
||
Сохранить полезную проверку параметров/данных. Не добавлять fake-timer suite
|
||
библиотечного кэширования только для оправдания старого заголовка.
|
||
|
||
**Сохранить.** Тела тестов, настройку клиента и background-refetch тест ниже
|
||
в usePortfolio. Его проверка сохранённого snapshot имеет отдельный контракт.
|
||
|
||
**Зависимости, риск, проверка.** Самостоятельная правка двух названий, низкий
|
||
риск; достаточно запуска двух существующих файлов. Если нужен пересмотр
|
||
политики retry, это отдельная задача, не следствие этой находки. Порция B-06.
|
||
|
||
## 6. Решения по существенным группам тестов
|
||
|
||
«Оставить» означает, что полезный контракт найден либо доказательств для
|
||
удаления недостаточно. Это не заявление об идеальности каждого assertion.
|
||
Таблица охватывает существенные группы доступных пакетов; внутри крупных
|
||
групп решение основано на перечисленных примерах и выборочном чтении,
|
||
а не на построчном доказательстве всех тестов.
|
||
|
||
### Frontend
|
||
|
||
| ID / группа | Наш контракт и обнаруживаемая регрессия | Детали реализации и пересечение уровней | Решение |
|
||
| --- | --- | --- | --- |
|
||
| T-01 auth/client/authenticated-query, AuthProvider, AuthForm, routes | Восстановление сессии, очистка personal cache, корректная навигация; поздний ответ прежней сессии не становится данными новой | Mocks транспорта/времени обслуживают нашу координацию. Маршрутный UI не заменяет concurrency-сценарий клиента, клиент не доказывает видимую навигацию | Оставить; не удалять refs/generation вместе с «повторяющимися» проверками |
|
||
| T-02 `resource-lifecycle`, `screen-sync`, `sync`, section | Каноническая cache identity, observe после уведомления, missing payload, отказ доступа, поздние ответы, transport/source error; регрессия — неверный scope или возвращение скрытого snapshot | Это подключение Query к нашему протоколу, не тест реализации query cache. Чтение cache уместно для границы, но пользовательский результат предпочтителен в page-тесте | Оставить; R-04 меняет тип, не сокращает эти сценарии |
|
||
| T-03 entity API hooks accounts/portfolio/events/operations | Выбор endpoint/параметров, раскрытие ответа, сохранённые данные после фоновой ошибки | Happy path не доказывает retry/staleTime. Два завышенных названия подтверждены R-07; usePortfolio background-refetch имеет отдельную ценность | Упростить названия по R-07, assertions оставить |
|
||
| T-04 StockPage/BondPage, каталоги stocks/bonds | UID/ticker routing, сохранение фильтров, отдельные состояния секций; percent облигации не превращается в деньги; pagination даёт видимый результат | Stock `377–408` и Bond `378–399` похожи, но проверяют wiring двух страниц. Моки API здесь граница, не структура hook | Оставить page wiring; после R-03 общий алгоритм проверять адресно, не размножать полную матрицу на обе страницы |
|
||
| T-05 AccountPage VM/view, position-groups, CurrencyCards, PortfolioStructure, display | Группировка позиций/валют, суммы и доступные действия, неполные значения; регрессия — смешение групп или потеря подписанного показателя | Pure grouping и показ пользователю — разные гарантии. Соседние проверки логотипов/сумм не доказывают число колонок | Оставить, кроме условной замены единственного CSS assertion по R-06 |
|
||
| T-06 account-operations/events/income pages и filters | Draft против applied URL, reset/back, cursor, выбранный период, доступность сравнения, восстановление ошибки | Большие файлы не доказывают дублирование. Прочитаны VM и выбранные сценарии, не все тела income suite | Оставить; нет доказательства эквивалентного покрытия для удаления |
|
||
| T-07 банковские schema/period/summary | Signed64, точные копейки, выбранные комбинации полей, реальные календарные даты/Moscow-период; регрессия — округление или принятие невозможной даты | Это наши ограничения поверх Zod и Date, не повтор тестов `string()`/`required()`. UI happy path не покрывает арифметические границы | Оставить |
|
||
| T-08 banking API, BankCardForm, OperationForm, DeleteOperation, CategoryManager, Card/CardsPage | Сохранение intent, передача версии, принятие актуальных данных после конфликта, архив, связь refund/edit, видимое обновление результата | Проверка правильных параметров/invalidations защищает наше wiring. Unit формы и backend транзакция не взаимозаменяемы; большие группы читались выборочно | Оставить; не сокращать mocks без сохранения сценария конфликта/повтора |
|
||
| T-09 packages/ui и icons, theme/contrast, animation binding | Общие роли/status, визуальная семантика theme adapter, SVG accessibility, lifecycle анимации; регрессия — потерянные props/cleanup/доступное имя | EntryCard fallback отчасти использует Mantine, но проверяет выбранные нами props и интеграцию. Удаление требует отдельного доказательства ненужности | Оставить; новые фабрики и snapshots не нужны |
|
||
| T-10 Storybook isolation | Отобранные composeStories не обращаются к fetch/XHR/storage/cookie при render/действии | Проверка вызовов — сам контракт изоляции fixture. Depcruise контролирует imports, не runtime side effects. Список stories явный, это не доказательство проверки всех возможных stories | Оставить, не считать дублем dependency gate |
|
||
| T-11 shell: breadcrumbs, document title, error boundary, accounts list | Наши подписи/навигация/восстановление при ошибке и доступные роли | Render React Router сам по себе не цель; выбранное нами назначение title, ссылки и сообщения — отдельный контракт | Оставить; массовые удаления по малому размеру теста не обоснованы |
|
||
|
||
### Backend
|
||
|
||
| ID / группа | Наш контракт и обнаруживаемая регрессия | Детали реализации и пересечение уровней | Решение |
|
||
| --- | --- | --- | --- |
|
||
| T-12 decimal, rub-money, bank calculations/summary | Точная арифметика, границы значений, знак, состав итога; регрессия — округление/неправильный вклад операции | Pure function здесь публичная граница правила. HTTP сценарии не перечисляют все арифметические случаи | Оставить; не заменять только happy path формы |
|
||
| T-13 auth service и `app.e2e-spec.ts` | Ротация/повторы сессии и HTTP cookie/guard boundary | Mocked service concurrency и настоящий configured app проверяют разные обязанности. Это не повторное тестирование Nest | Оставить; e2e в сессии не запускался |
|
||
| T-14 bank-cards HTTP и durability | CAS, idempotency, атомарные transfer/refund, owner scope, restart; пример `bank-cards.e2e-spec.ts:372–442` проверяет видимые остатки и replay после edit/delete | Реальная SQLite транзакция не заменяется моками Prisma; SQL constraints относятся к выбранной модели. Durability отдельна от in-process результата | Оставить; большие наборы прочитаны выборочно и не запускались |
|
||
| T-15 TbankClient, token/source/account policy, crypto | Наши RPC arguments, нормализация, admission/retry/deadline, допустимые accounts, шифрование; регрессия — лишний retry auth или обход очереди | Счёт RPC и fake timers обоснованы контрактом ограничений; не просто гарантия grpc-js. R-02 не должен ослабить проверки | Оставить; при R-02 упростить распознавание в production, не assertions протокола |
|
||
| T-16 MOEX/НКР | Exact ISIN/board, корректные источник/дата/единицы, malformed против not-found/empty, withdrawn против missing | Cheerio/fetch mock отделяет внешнюю границу. TS тип не делает вход trusted, HTTP 200 не означает валидные данные | Оставить |
|
||
| T-17 investment-resource mapper, sync-plan, tbank-screen, coverage/planner | Availability/freshness/work, выбранный scope и интервалы; регрессия — partial становится complete или планирует другую область | Проверять конкретное решение planner полезно. Не удалять как «внутренний helper»: это владелец предметной политики | Оставить |
|
||
| T-18 portfolio/accounts, instrument-position/structure, operation mapper | Объединение локальных источников, точные позиции, account attribution и внешняя нормализация | Много зависимостей portfolio mock не доказывает бесполезность orchestration-теста. Полезно упрощать чистые mapper imports, но не подменять связь всех ресурсов happy path | Оставить; массовая перепись fixtures не обоснована |
|
||
| T-19 stock/bond/upcoming projection unit | Даты, цены против процентов, точные quantity/payment, неизвестная сумма/оферта; три upcoming примера прочитаны целиком | Prisma mock нужен из-за import graph, а не вычислению. Это setup-долг R-01, не лишний контракт | Упростить setup через R-01, оставить все финансовые утверждения |
|
||
| T-20 income-period/analytics и `tbank-income-local` | Calendar/history availability, selected/previous/year, валюты и точность, IDs разных счетов, partial против confirmed empty | Callback mock связан с реализацией R-05. HTTP/SQLite `124–203` дополнительно проверяет сохранение и отсутствие RPC, unit даёт адресные расчётные случаи | Оставить контракты; упростить mock после явного результата R-05 |
|
||
| T-21 persistence: metadata/catalog/candles/operations/local reads/ledger/connection/migration | Stage→publish, coverage, старый attempt/revision, reopen, confirmed absence; регрессия — поздняя работа публикует старые данные или промежуточный результат становится полным | Похожие гонки у разных owners не равнозначные тесты: меняются repositories и ключи. Полностью все тела не прочитаны | Оставить; доказательств для удаления нет |
|
||
| T-22 screen sync, all-accounts, portfolio structure, stocks/bonds/upcoming HTTP | Composition/DTO, account/all scopes, источник публичных/личных данных, passive observe; upcoming `255–284` дополняет pure mapper | HTTP integration не заменяет точные signed/fractional примеры unit; unit controller не проверяет Nest pipes/guards | Оставить |
|
||
| T-23 тонкие operations/upcoming controller unit | Делегирование user/scope/query в нужный service | Вызов метода напрямую не проверяет validation/auth. Для каждого утверждения эквивалентная configured HTTP-проверка не установлена; тест stocks другого endpoint не эквивалент | Пока оставить. Замена допустима только после точного сопоставления сценария, не создавать новый batch на предположении |
|
||
| T-24 env/main/filter/logging/DTO и OpenAPI contract | Выбранная конфигурация, безопасная форма ProblemDetails, поля/коды валидации и wire schema | Это проектное подключение библиотек. Generated schema и UI типизация не доказывают конфигурацию реального HTTP app | Оставить; contract e2e не запускался |
|
||
|
||
**Удалить:** ни для одной существенной группы достаточного доказательства
|
||
отсутствия уникального контракта не получено. Предложения удаления целых suites
|
||
нет. Единственная адресная замена — R-06, причём действующий тест сохраняется
|
||
до воспроизводимой проверки того же поведения. Переименование R-07 не
|
||
ослабляет assertions; перенос R-01 не меняет предметные примеры.
|
||
|
||
## 7. Последовательность небольших порций
|
||
|
||
Это план будущей реализации. Ни одна порция в этой сессии не начата.
|
||
Проверки ниже — минимум для локального доказательства конкретной правки;
|
||
перед завершением будущей implementation-задачи также выполняются актуальные
|
||
обязательные gates её репозитория. Команды запускать после местного `nvm use`.
|
||
Новые проверочные скрипты, зависимости и governance-документы не требуются.
|
||
|
||
### B-01. Пилот: независимая проекция upcoming event
|
||
|
||
- **Находка:** R-01. **Граница:** upcoming-events service, один соседний mapper,
|
||
существующий projection test; stock/bond и общий scheduler не трогать.
|
||
- **Зависимости:** нет. **Сохранить:** все значения DTO/decimal/date/null,
|
||
поведение вызывающего `get` и exports, которыми он пользуется.
|
||
- **Минимальные проверки:** `npm test -- --runInBand --runTestsByPath src/modules/investments/upcoming-events/upcoming-events.service.spec.ts`,
|
||
`npm run typecheck:app`, `npm run typecheck:test`; проверить HTTP forecast
|
||
имеющимся SQLite runner при подтверждённом актуальном build.
|
||
- **Готово, когда:** тест проекций проходит без Prisma mock; mapper не загружает
|
||
Nest/repository runtime; assertions и сериализованные результаты сохранены.
|
||
Оценить выигрыш по чтению import graph, не по количеству новых файлов.
|
||
|
||
Перенос stock/bond из R-01 — последующее локальное решение после пилота,
|
||
только для функций с тем же подтверждённым препятствием независимой загрузке.
|
||
Это не разрешение на массовое извлечение каждого helper.
|
||
|
||
### B-02. Одно распознавание invalid token
|
||
|
||
- **Находка:** R-02. **Граница:** integration-owned predicate, `tbank.api.ts`,
|
||
перечисленные проверки catch; сами recovery-ветви и persisted codes прежние.
|
||
- **Зависимости:** от пилота технически не зависит. **Сохранить:** точный Error/message
|
||
критерий, wire value, разные последствия и attempt/revision fencing.
|
||
- **Минимальные проверки:** TbankClient и token service unit, точный predicate
|
||
на matching/nonmatching Error и non-Error; app/test typecheck. Если diff
|
||
затронул больше predicate, соответствующий existing persistence test обязателен.
|
||
- **Готово, когда:** consumers больше не знают форму message-протокола;
|
||
нет глобального error registry и расширения того, что считается token error.
|
||
|
||
### B-03. Общая история инструмента для двух страниц
|
||
|
||
- **Находка:** R-03. **Граница:** StockPage/BondPage VM, instrument-details hook
|
||
и его публичный экспорт, затронутые проверки. AccountOperations не включать.
|
||
- **Зависимости:** независимо от R-04; не объединять два рефакторинга.
|
||
**Сохранить:** scope/cursor/limit/UID, URL, ordinary retry и exact recovery,
|
||
включая нынешний возврат A→B→A.
|
||
- **Минимальные проверки:** обе page suites через Vitest; адресный сценарий
|
||
invalid cursor и возврата scope с наблюдением запросов/строк; app typecheck
|
||
и существующий dependency-cruiser gate.
|
||
- **Готово, когда:** state/query/recovery истории определены в одном месте,
|
||
специфичные источники страницы читаются прямо, новый hook не требует
|
||
конфигурационного описания всей страницы.
|
||
|
||
### B-04. Честный тип resource-result
|
||
|
||
- **Находка:** R-04. **Граница:** `shared/api/resource-query.ts` и только реальные
|
||
callers, которым требуется уточнение типа/смысла статуса.
|
||
- **Зависимости:** нет; отдельно от нового hook. **Сохранить:** runtime API,
|
||
тип возвращаемого envelope из refetch, canonical keys, transport/resource
|
||
distinction и все access/observe guards.
|
||
- **Минимальные проверки:** app typecheck; T-02 и затронутые pages, затем полный
|
||
текущий Vitest-набор. Убедиться, что тип метода не исправлен очередным assertion.
|
||
- **Готово, когда:** тип показывает фактические поля и методы, целого приведения
|
||
к другому `UseQueryResult` нет, cache/request/visible-state поведение прежнее.
|
||
|
||
### B-05. Явный результат чтения периодов дохода
|
||
|
||
- **Находка:** R-05. **Граница:** income planner/analytics, `collectRanges`,
|
||
данные возвращаемого repository snapshot и их тестовые fixtures.
|
||
- **Зависимости:** не требует других batches; выполнять отдельно после менее
|
||
рискованных шагов. **Сохранить:** единый snapshot, cold fallback, observe,
|
||
scheduling, coverage, dates, roles/order/resourceKey/hashes и exact totals.
|
||
- **Минимальные проверки:** income-period/analytics unit, app/test typecheck,
|
||
`tbank-income-local.e2e-spec.ts` через штатный SQLite runner и OpenAPI contract.
|
||
Использовать актуальную сборку по локальным правилам; не считать старый dist
|
||
доказательством нового исходника. При изменении repeated-read пути адресно
|
||
проверить выбор финального snapshot и старую revision.
|
||
- **Готово, когда:** callback не меняет внешнюю переменную, роль диапазона
|
||
не угадывается по индексу в operations, итоговый plan объясняется возвращёнными
|
||
данными того же snapshot. Публичный ответ не меняется.
|
||
|
||
### B-06. Точные названия двух тестов
|
||
|
||
- **Находка:** R-07. **Граница:** только заголовки двух entity hook tests.
|
||
- **Зависимости:** нет. **Сохранить:** assertions, fixtures, retry/client policy.
|
||
- **Минимальные проверки:** Vitest по `usePortfolio.test.tsx` и
|
||
`useBrokerageOperations.test.tsx`.
|
||
- **Готово, когда:** каждое обещание в названии подтверждается существующим
|
||
телом теста. Новых проверок библиотеки нет.
|
||
|
||
### B-07. Условная замена проверки одной колонки
|
||
|
||
- **Находка:** R-06. **Граница:** один layout assertion AccountPage и его
|
||
равноценная проверка. **Зависимость:** сначала доступный воспроизводимый
|
||
browser/layout способ без установки нового framework в рамках этого плана.
|
||
- **Сохранить:** expanded/other layout и все content/accessibility assertions.
|
||
- **Минимальные проверки:** геометрия конкретной секции на узкой и широкой ширине;
|
||
действующий AccountPage suite. Доказать чувствительность замены к намеренному
|
||
переходу expanded section в несколько колонок до удаления CSS assertion.
|
||
- **Готово, когда:** регрессия колонок обнаруживается без чтения внутреннего CSS
|
||
Mantine. Пока это не доказано, batch отложен и существующий тест остаётся.
|
||
|
||
### Проверка самого отчёта
|
||
|
||
Смысловой review выполнен по affected areas: рекомендации разделены по причинам,
|
||
публичные и persisted значения сохранены в ограничениях, для каждой находки
|
||
есть owner, код и способ проверки. Не предложены новые зависимости, массовая
|
||
перестройка, удаление защитных ветвей или tests только ради числа строк.
|
||
Результаты запуска тестов отделены от чтения e2e и от требований к будущим
|
||
изменениям. Финальная сверка staged/unstaged/new файлов пяти репозиториев
|
||
показала только новый `docs/code-quality-review.md`; staged-изменений нет.
|
||
Новое содержимое просмотрено отдельно, поскольку обычный `git diff` не
|
||
показывает untracked-файл. Единственный создаваемый файл — этот отчёт.
|
||
|
||
## 8. Выполненные изменения
|
||
|
||
### B-01 — независимая проекция upcoming event, 27 сентября 2026
|
||
|
||
**Статус: выполнено; связанная находка R-01 закрыта в границах пилота.**
|
||
Записи разделов 1–7 сохраняют исходное состояние исследования. Другие порции
|
||
и аналогичные преобразования stock/bond в этом запуске не выполнялись.
|
||
|
||
В backend добавлен соседний
|
||
`src/modules/investments/upcoming-events/upcoming-event.mapper.ts`:
|
||
`dividendEvent`, `bondEvent` и приватный `projectedMoney` перенесены дословно.
|
||
Service вызывает mapper; DTO/provider imports нового файла — type-only.
|
||
Расчёт можно прочитать и загрузить отдельно от orchestration, Nest и Prisma.
|
||
Фильтрация, окно дат, сортировка, totals, access и публикация остались в сервисе.
|
||
HTTP DTO, публичный `upcoming-events-read.api.ts` и сигнатуры сервиса не менялись.
|
||
|
||
Существующий `upcoming-events.service.spec.ts` переименован в
|
||
`upcoming-event.mapper.spec.ts` и импортирует mapper. Сохранены все три сценария,
|
||
fixtures и assertions: точные знаковые дробные выплаты, registry date/валюта,
|
||
неизвестная сумма и оферта, сумма погашения. Удалён только Prisma mock, который
|
||
чистому преобразованию больше не нужен. Новых тестовых сценариев не добавлено;
|
||
сравнение с HEAD подтвердило дословное сохранение функций и тел тестов.
|
||
|
||
**Фактические проверки** (перед Node-командами — местный `nvm use --silent`):
|
||
|
||
| Проверка | До изменения | После изменения |
|
||
| --- | --- | --- |
|
||
| `npm ci --offline --no-audit --no-fund` | Успешно по существующему lockfile; manifests/lockfile не изменены | Повторная установка не требовалась |
|
||
| Backend `npm run verify` | Lint, app/test typecheck и 36 suites / 209 тестов прошли; build упёрся в sandbox `EPERM` на кэше Prisma | Полный gate прошёл: lint, оба typecheck, 36 suites / 209 тестов с coverage и build |
|
||
| Backend `npm run build` | После предоставления доступа к кэшу Prisma прошёл | Прошёл в составе verify |
|
||
| `node test/sqlite.cjs upcoming-events.e2e-spec.ts` | 20 тестов прошли | Те же 20 тестов прошли |
|
||
| Targeted Jest для `upcoming-event.mapper.spec.ts` | Три исходных теста прошли в общем unit-наборе | Отдельный запуск: 3 теста прошли без Prisma mock |
|
||
| Импорт собранного mapper через Node | Отдельного mapper ещё не было | Обе функции экспортируются; в `require.cache` нет Nest/Prisma runtime-модулей |
|
||
| Docs `npm run verify` и review diff | Docs gate прошёл | Docs gate и проверка diff выполнены после записи результата |
|
||
|
||
Исходный отказ build был ограничением файлового sandbox, не падением кода;
|
||
сборка и HTTP-тесты повторены с необходимым доступом к кэшу и локальному порту.
|
||
SQLite runner создавал и удалял временную базу, upstream подменялся тестами;
|
||
production-данные и реальные внешние API не использовались. При установке npm
|
||
сообщил о deprecated packages и неразрешённых install scripts двух зависимостей;
|
||
политика scripts и версии не менялись, проверкам это не помешало.
|
||
|
||
Boy Scout review: нет новой DI-прослойки, barrel, зависимостей или дублирующего
|
||
расчёта; mapper остаётся у существующего владельца upcoming-events. Остальные
|
||
модули не затронуты. Полный набор backend e2e, frontend/browser и отдельный
|
||
OpenAPI parity не запускались: wire metadata и frontend не менялись; проверен
|
||
существующий HTTP-сценарий затронутой области. Commit/push не выполнялись.
|
||
|
||
### B-02 — единое распознавание invalid token, 27 сентября 2026
|
||
|
||
**Статус: выполнено; R-02.** Проверка текущего кода подтвердила завершённый
|
||
пилот B-01 и девять одинаковых сравнений ошибки в восьми сервисах.
|
||
Добавлен `backend/src/integrations/tbank/tbank-error.policy.ts` с чистым
|
||
`isInvalidTbankToken(error: unknown): boolean`; функция экспортирована через
|
||
существующий `tbank.api.ts`. Внутри интеграции используются прямые импорты,
|
||
investment-сервисы используют публичную границу. Новый файл не имеет imports
|
||
и не создаёт цикл или DI-provider.
|
||
|
||
Token translation, membership, metadata, portfolio, operations sync,
|
||
catalog discovery/enrichment и candles теперь вызывают один предикат.
|
||
Он дословно сохраняет прежнее условие `instanceof Error` и точное равенство
|
||
`message`. Recovery, UID→FIGI fallback, fencing attempt/revision, действия
|
||
repositories, retry и исходный `Error('INVALID_TBANK_TOKEN')` не изменены.
|
||
Значения HTTP/storage codes сохранены. Теперь при чтении catch видно действие
|
||
сервиса, а форма upstream-ошибки определена в одном месте. Реестр ошибок,
|
||
новый класс исключения и зависимости не добавлялись.
|
||
|
||
**Тесты.** Все существующие проверки сохранены. В
|
||
`tbank-token.service.spec.ts` добавлены четыре случая через существующую
|
||
границу `translateInvalidToken`: объект с таким же message, строка, Error с
|
||
префиксом и `null` должны выйти тем же значением, без перевода в token error.
|
||
Они защищают от расширения распознавания до duck typing/substring и от ошибки
|
||
при null. Сценарии сначала прошли на старой production-реализации. В итоговом
|
||
тесте отказ внешней операции задаётся штатным `mockRejectedValue`, без подавления
|
||
lint или TypeScript. Отдельная инфраструктура и файл тестов предиката не нужны.
|
||
|
||
| Проверка | До production-правки B-02 | После |
|
||
| --- | --- | --- |
|
||
| `npm run lint`, `npm run typecheck:app`, `npm run typecheck:test` | Прошли | Прошли в составе полного verify |
|
||
| Jest `--runInBand --runTestsByPath` для `tbank-token.service.spec.ts` и `tbank.client.spec.ts` | 22 исходных теста; после добавления четырёх защитных случаев — 26 прошли на старой реализации | Те же сценарии прошли в полном unit-наборе |
|
||
| Backend `npm run verify` | Полный gate уже проходил после B-01; в этом запуске до правки выполнены проверки строк выше | Прошёл целиком: lint, оба typecheck, 36 suites / 213 тестов с coverage, Prisma generate и build |
|
||
| SQLite `tbank-connection.e2e-spec.ts` | 4 теста прошли при повторном запуске после native SIGABRT | 4 теста прошли |
|
||
| SQLite `tbank-metadata-persistence.e2e-spec.ts` | 15 тестов прошли | 15 тестов прошли при повторном запуске после native SIGABRT |
|
||
| SQLite `tbank-catalog-discovery.e2e-spec.ts` | 10 тестов прошли при повторном запуске после native SIGABRT | 10 тестов прошли |
|
||
| SQLite `tbank-operations-sync.e2e-spec.ts` | 17 тестов прошли | 17 тестов прошли |
|
||
| Docs `npm run verify`, `git diff --check` и смысловой review | Исходный отчёт и незакоммиченные изменения сохранены | Проверены после записи результата |
|
||
|
||
SQLite suites запускались штатным `node test/sqlite.cjs <suite...>` на временных
|
||
базах; реальные upstream заменены mocks. Во всех Node-командах активирована
|
||
местная `.nvmrc`. Использованы зависимости, восстановленные успешным `npm ci`
|
||
в B-01; manifest/lockfile и установленные версии не менялись.
|
||
|
||
**Исходные падения и ограничения.** До изменения production-кода два запуска
|
||
оборвались с SIGABRT, после — один. В сохранённом выводе catalog/metadata видны
|
||
`node::RemoveEnvironmentCleanupHook`, assertion `(env) != nullptr` и destructor
|
||
statement native better-sqlite3. Повторные запуски тех же suites прошли без
|
||
изменения кода, тестов или окружения. Это не assertion теста; точная причина
|
||
нестабильности в B-02 не исследовалась и не исправлялась. Успешные повторы не
|
||
означают её устранения. Полный e2e-набор, browser/frontend и OpenAPI parity
|
||
не запускались; публичный wire-контракт не менялся.
|
||
|
||
Boy Scout review подтвердил: в production осталось одно сравнение message
|
||
для этой классификации; действия всех catch-ветвей сохранены, новый policy
|
||
принадлежит T-Bank. Контрольные суммы файлов B-01 совпадают с началом запуска.
|
||
Следующая порция B-03 не начата. Commit/push не выполнялись.
|
||
|
||
### B-03 — общая пагинация истории инструмента, 27 сентября 2026
|
||
|
||
**Статус: выполнено; R-03.** В существующий frontend slice
|
||
`features/instrument-details` перенесён `useInstrumentOperations`; StockPage
|
||
и BondPage используют его через публичный index. Стек cursor, запрос истории
|
||
и recovery теперь определены один раз. Страницы сохраняют URL replace,
|
||
свои категории доходов, состав screen sync и отображение строк.
|
||
|
||
Сохранены account=`all`, limit=20, ожидание UID, canonical query key,
|
||
exact invalidation первой страницы при `INVALID_OPERATIONS_CURSOR`
|
||
и делегирование остальных ошибок screen retry. Scope mismatch по-прежнему
|
||
только скрывает стек: возврат A→B→A восстанавливает cursor. Hook не импортирует
|
||
соседнюю feature; callback screen retry передаётся при вызове действия.
|
||
AccountOperations, resource-query, API и зависимости не менялись.
|
||
|
||
**Тесты.** Все существующие сценарии обеих страниц сохранены. Stock-сценарий
|
||
пагинации дополнен возвратом фильтра и переходом назад. Два адресных recovery
|
||
случая проверяют свежую сумму после ошибки: invalid cursor возвращает первую
|
||
страницу, обычная ошибка повторяет текущую через `sync=observe`. Проверяются
|
||
параметры HTTP и доступность кнопки «Назад», без доступа к внутреннему state.
|
||
Новые assertions сначала прошли на старой production-реализации.
|
||
|
||
| Проверка | До переноса production-кода | После |
|
||
| --- | --- | --- |
|
||
| `npm ci --offline --no-audit --no-fund` | Успешно по существующему lockfile | Manifest/lockfile не изменены |
|
||
| `npm run lint`, `npm run typecheck` | Прошли | Прошли в полном verify |
|
||
| `npm run test:run -- src/pages/stock/ui/StockPage/StockPage.test.tsx src/pages/bond/ui/BondPage/BondPage.test.tsx` | 19 исходных тестов; с дополнительными assertions — 21 прошёл | Те же 21 прошли |
|
||
| Frontend `npm run verify` | До edits выполнены проверки выше | Полностью прошёл: OpenAPI check, TypeScript, Biome, 54 suites / 359 тестов, coverage, Knip, оба dependency-cruiser gate, app и Storybook build |
|
||
| Docs `npm run verify`, `git diff --check` | Прошли | Повторены после записи результата |
|
||
|
||
Coverage: statements 92,26%, branches 82,58%, functions 87,92%, lines 93,27%;
|
||
пороги не менялись. Первый финальный verify остановился на форматировании двух
|
||
новых imports; после локального форматирования полный повтор прошёл.
|
||
При установке npm сообщил о deprecated glob и install script MSW, при Storybook
|
||
build — о размере chunks. Версии, script policy и build limits не менялись.
|
||
Во всех Node-командах активирована местная `.nvmrc`.
|
||
|
||
Boy Scout review: один предметный hook для двух текущих потребителей,
|
||
без новых эффектов, generic framework, конфигурации страниц или циклов.
|
||
Изменения предыдущих порций в backend сохранены. Проверки UI выполнены через
|
||
RTL/MSW; отдельный браузерный прогон не выполнялся, markup и стили не менялись.
|
||
Backend suites повторно не запускались. B-04 не начата; commit/push не выполнялись.
|
||
|
||
### B-04 — фактический тип resource-result, 27 сентября 2026
|
||
|
||
**Статус: выполнено; R-04.** В `frontend/src/shared/api/resource-query.ts`
|
||
удалено приведение всего результата к `UseQueryResult` с распакованным payload.
|
||
Теперь TypeScript выводит тип spread и переопределённых полей; локальные
|
||
аннотации `T["data"]`, `T["meta"]` и status сохраняют конкретный DTO и словарь
|
||
статусов. Методы, включая `refetch`, сохраняют настоящий тип envelope.
|
||
Это убирает скрытое исключение из контракта без нового wrapper или type alias.
|
||
|
||
Runtime-поля сохранены: успешный HTTP с `data:null` остаётся resource-pending,
|
||
а transport — successful; нативный `isLoading` не переопределён. Query keys,
|
||
observe/stateVersion, guards доступа и session isolation не изменены.
|
||
Специализированная metadata дохода, включая dependency role, сохранена через
|
||
`T["meta"]`; ни одному production-потребителю не потребовалась правка.
|
||
|
||
**Тесты.** Все существующие проверки оставлены. В `resource-lifecycle.test.tsx`
|
||
уточнено различие transport/resource flags и добавлен один сценарий: refetch
|
||
возвращает envelope сначала с null, затем со свежим портфелем, а hook раскрывает
|
||
payload и meta. Сценарий сначала прошёл на старом коде; после исправления типа
|
||
добавлено прямое чтение `refetch().data.data` и суммы из payload, проверяемое
|
||
также TypeScript. Отдельная тестовая инфраструктура не добавлялась.
|
||
|
||
| Проверка | До изменения production | После |
|
||
| --- | --- | --- |
|
||
| `npm ci --offline --no-audit --no-fund` | Прошла по существующему lockfile | Зависимости и lockfile сохранены |
|
||
| `npm run lint`, `npm run typecheck` | Прошли | Прошли в полном verify |
|
||
| `npm run test:run --` для resource-lifecycle, authenticated-query, tbank-connection/model, section, StockPage и BondPage | 7 suites / 49 тестов; дополненный lifecycle отдельно — 5 тестов | 7 suites / 50 тестов |
|
||
| Frontend `npm run verify` | Выполнены адресные проверки выше | Прошёл полностью: OpenAPI check, TypeScript, Biome, 54 suites / 360 тестов, coverage, Knip, оба dependency-cruiser gate, app и Storybook build |
|
||
| Docs `npm run verify`, `git diff --check` | Прошли | Повторены после записи результата |
|
||
|
||
Coverage: statements 92,27%, branches 82,58%, functions 87,92%, lines 93,28%;
|
||
пороги сохранены. Промежуточный typecheck выявил потерю конкретного metadata
|
||
типа при выводе из generic constraint; исправлено у владельца через `T["meta"]`,
|
||
без assertions и изменений callers. Исходных падений проверок не было.
|
||
Остались предупреждения npm о glob/install script MSW и Storybook о chunks;
|
||
версии и настройки не менялись. Перед Node-командами активирована `.nvmrc`.
|
||
|
||
Boy Scout review: production diff ограничен одним преобразованием результата,
|
||
новых cache/state machine, runtime schema и экспортов нет. Diff и checksum
|
||
файлов B-03 совпадают с началом запуска; backend не редактировался.
|
||
Браузерный прогон и backend suites не запускались: UI и wire-контракт не менялись.
|
||
B-05 не начата. Commit/push не выполнялись.
|
||
|
||
### B-05 — явный snapshot периодов дохода, 27 сентября 2026
|
||
|
||
**Статус: выполнено; R-05.** `collectRanges` теперь возвращает `openedAt`
|
||
и именованные `periods` из итогового repository snapshot. В существующий
|
||
operations domain добавлен конкретный тип `OperationPeriod`: date-only range
|
||
с именем, которое задаёт income. Repository сохраняет исходные даты и имя
|
||
внутри прежней read transaction; operations переносит имя в resourceKey.
|
||
Позиционный массив `selected/previous/yearAgo` удалён из operations.
|
||
|
||
Income выбирает результаты по имени; callback больше не присваивает внешний
|
||
`plan`. Summary использует прочитанный range, analytics восстанавливает чистый
|
||
план по выбранным датам и `openedAt` того же snapshot. Даты уже выполненного
|
||
чтения не заменяются новым default-периодом после await. Начальная валидация
|
||
analytics до access/scheduling сохранена. При отсутствии snapshot используются
|
||
периоды, построенные с `undefined`, и прежний cold response.
|
||
|
||
Transaction, повторные чтения, revision fence, observe и scheduling сохранены.
|
||
Дополнительные name/range не передаются в scheduling targets. Сохранены
|
||
resourceKey, binding/hash inputs, coverage и внешние dependency roles/order
|
||
`selected`, `previous`, `year`; внутреннее имя `yearAgo` не меняет HTTP API.
|
||
Расчёт денег, buckets, календарные правила и DTO не редактировались.
|
||
|
||
**Тесты.** Все 16 income unit-сценариев и их assertions сохранены; fixtures
|
||
адаптированы к явному результату, повторные mocks сведены к существующему seed.
|
||
В SQLite/HTTP suite добавлены три случая: изменение openedOn в обе стороны
|
||
во время scheduling должно отразиться в итоговом year comparison и revision;
|
||
замена подключения после первого snapshot не должна запускать старую revision
|
||
или возвращать её данные, но должна сохранить cold period identity.
|
||
Все три проверки сначала прошли на старой production-реализации.
|
||
|
||
| Проверка | До production-правки | После |
|
||
| --- | --- | --- |
|
||
| `npm ci --offline --no-audit --no-fund` | Прошла по текущему lockfile | Manifest/lockfile не изменены |
|
||
| `npm run lint`, `npm run typecheck:app`, `npm run typecheck:test` | Прошли | Прошли в полном verify |
|
||
| Jest `--runInBand --runTestsByPath` для income-period и income-analytics | 2 suites / 16 тестов | Те же 16 прошли |
|
||
| `npm run build` и `node test/sqlite.cjs tbank-income-local.e2e-spec.ts openapi-contract.e2e-spec.ts` | После разрешения доступа к кэшу Prisma: 7 income + 3 OpenAPI теста; дополненная income suite — 10 тестов на старом коде | После актуальной сборки: 10 income + 3 OpenAPI теста прошли |
|
||
| Backend `npm run verify` | Выполнены адресные проверки выше | Полностью прошёл: lint, оба typecheck, 36 suites / 213 unit-тестов с coverage, Prisma generate и build |
|
||
| Docs `npm run verify`, `git diff --check` | Прошли | Повторены после записи результата |
|
||
|
||
Исходный build получил sandbox `EPERM` на кэше Prisma; повтор с доступом к кэшу
|
||
и локальному HTTP-порту прошёл. Native SIGABRT в этом запуске не наблюдался.
|
||
SQLite suites использовали временные базы и synthetic upstream; production
|
||
данные и реальные внешние API не вызывались. npm сообщил о deprecated packages
|
||
и install scripts двух зависимостей; версии и политика scripts не менялись.
|
||
Перед Node-командами активирована местная `.nvmrc`.
|
||
|
||
Boy Scout review: новые файлы, DI-слои и generic planner не добавлены;
|
||
планирование и snapshot остаются у прежних владельцев. Diff B-01/B-02 и staged
|
||
index побайтово совпали с началом запуска; frontend не редактировался.
|
||
Полный backend e2e-набор и browser/frontend проверки не запускались.
|
||
B-06 не начата. Commit/push не выполнялись.
|
||
|
||
### B-06 — точные названия query-тестов, 27 сентября 2026
|
||
|
||
**Статус: выполнено; R-07.** Изменены только два заголовка:
|
||
`usePortfolio.test.tsx` — `loads the account portfolio`;
|
||
`useBrokerageOperations.test.tsx` —
|
||
`normalizes categories, preserves the cursor and loads a page`.
|
||
Названия больше не обещают проверку staleTime/retry. Сравнение с HEAD подтвердило
|
||
дословное сохранение тел тестов, fixtures и assertions, включая background
|
||
refetch. Production-код, клиент и зависимости не менялись; новых тестов нет.
|
||
|
||
До edits `npm run test:run -- src/entities/portfolio/api/usePortfolio.test.tsx
|
||
src/entities/broker-operation/api/useBrokerageOperations.test.tsx` прошёл:
|
||
2 файла / 3 теста. После — полный frontend `npm run verify`: 54 suites /
|
||
360 тестов, coverage, TypeScript, Biome, OpenAPI check, Knip, оба dependency-cruiser
|
||
gate, app и Storybook build прошли. Использованы зависимости из успешного
|
||
`npm ci` B-04; перед Node-командами активирована `.nvmrc`. Падений нет;
|
||
предупреждение Storybook о размере chunks осталось без изменения настроек.
|
||
Docs `npm run verify` и `git diff --check` выполнены до/после записи результата.
|
||
|
||
Boy Scout review подтвердил соответствие каждого заголовка assertions и scope
|
||
в две строки тестов. Предыдущие изменения и staged-файлы сохранены.
|
||
Backend/browser проверки не запускались. B-07 не начата; commit/push не выполнялись.
|
||
|
||
### B-07 — условие замены layout-проверки, 27 сентября 2026
|
||
|
||
**Статус: отложено; R-06 остаётся открытой.** Повторная проверка подтвердила
|
||
актуальность CSS assertion в `AccountPage.test.tsx` и `cols={1}` для expanded
|
||
sections в `PositionSection.view.tsx`. Условие безопасной замены не выполнено:
|
||
`vitest.config.ts` использует JSDOM, package scripts и Gitea verify не содержат
|
||
browser/layout gate. Storybook build не исполняет `play`; существующий
|
||
`src/test/storybook.test.tsx` также использует RTL/JSDOM. Ни story отдельной
|
||
секции с геометрической проверкой, ни воспроизводимого browser runner в проекте
|
||
не обнаружено. Доступный агенту браузерный инструмент сам по себе не становится
|
||
регрессионной проверкой репозитория.
|
||
|
||
Согласно условию B-07, существующий тест оставлен целиком. Production-код,
|
||
остальные assertions, зависимости и конфигурации не менялись. Геометрия при
|
||
двух ширинах и чувствительность к намеренной регрессии не проверялись: без
|
||
постоянно исполняемой замены это не даёт основания удалить текущую защиту.
|
||
Введение нового browser runner выходит за согласованную границу этой порции.
|
||
|
||
`npm run test:run -- src/pages/account/ui/AccountPage/AccountPage.test.tsx`
|
||
прошёл: 41 тест. Docs `npm run verify` и `git diff --check` проверены до/после
|
||
записи; перед Node-командами активирована `.nvmrc`. Полный frontend verify
|
||
повторно не запускался: после успешного B-06 изменена только эта запись.
|
||
Boy Scout review и routing подтвердили отсутствие code diff этой порции;
|
||
предыдущие staged/unstaged изменения сохранены. Commit/push не выполнялись.
|
||
|
||
Порции B-01–B-06 выполнены; B-07 требует отдельного решения о постоянном
|
||
браузерном запуске, затем проверки геометрии и доказательства обнаружения
|
||
регрессии до удаления CSS assertion.
|
||
|
||
### B-07 — браузерная проверка геометрии, 27 сентября 2026
|
||
|
||
**Статус: выполнено; R-06.** Пользователь явно разрешил узкое исключение
|
||
из запрета на новые библиотеки: браузерный провайдер текущего Vitest, Chromium
|
||
в verify/CI и одну проверку геометрии на двух ширинах с намеренной регрессией.
|
||
Добавлены devDependencies `@vitest/browser-playwright@5.0.0` и
|
||
`playwright@1.63.0`; версии существующих пакетов в lockfile не изменились.
|
||
|
||
`vitest.config.ts` разделяет JSDOM project с прежним setup и браузерный project
|
||
с двумя Chromium instances: 375 и 1280 px. Существующий `npm run verify`
|
||
исполняет оба вместе с coverage; пороги сохранены. Оба Gitea workflow,
|
||
вызывающих verify, устанавливают Chromium через `playwright install --with-deps`.
|
||
README описывает локальный запуск; `.vitest` с артефактами исключён из Git.
|
||
|
||
`position-layout.browser.test.tsx` использует production `PositionSection`,
|
||
тему и CSS, три synthetic позиции и реальные bounding rectangles их видимых
|
||
названий. Акции и облигации остаются вертикальными на обеих ширинах;
|
||
`other` — вертикальным на узкой и в три колонки на широкой. Проверка
|
||
не читает CSS-переменные, классы или style tags Mantine, не подменяет layout API.
|
||
Удалён только прежний CSS-тест из `AccountPage.test.tsx`; остальные
|
||
content/accessibility assertions и production-код не изменены.
|
||
|
||
До удаления старого теста новая проверка прошла, AccountPage suite прошёл
|
||
41 тест. Временное `cols={2}` вызвало ожидаемое падение на 375 px.
|
||
После окончательной настройки instances оба браузерных запуска снова прошли;
|
||
мутация только облигаций на responsive grid вызвала падение `bonds at 1280px`
|
||
(left 501 вместо 69), узкий запуск остался зелёным. После каждого опыта
|
||
исходный компонент восстановлен побайтово. Конфликт типов browser matchers
|
||
с существующими JSDOM assertions устранён отказом от импорта `vitest/browser`
|
||
в тест: ширины задаёт конфигурация. Существующие assertions не переписывались.
|
||
|
||
Выполнены `npm ci`, установка Chromium, целевой browser run и полный
|
||
`npm run verify`: 56 запусков suites / 361 тест (359 JSDOM и два запуска
|
||
одного browser test), coverage, TypeScript, Biome, OpenAPI, Knip, оба
|
||
dependency-cruiser gate, app и Storybook build прошли. Coverage: statements
|
||
92,3%, branches 82,66%, functions 87,92%, lines 93,3%. Docs `npm run verify`
|
||
и `git diff --check` прошли. Перед Node-командами активирована `.nvmrc`.
|
||
|
||
**Ограничения:** `npm audit --include=dev --audit-level=low` не выполнен
|
||
успешно: настроенный Nexus вернул HTTP 400 с требованием настроить
|
||
Lifecycle/Firewall. Отсутствие уязвимостей не подтверждено. Vitest сообщает
|
||
о проигнорированном Vite hook `configureServer`; Storybook — о размере chunks.
|
||
Оба предупреждения не остановили проверки. CI workflow изменены, но удалённый
|
||
Linux runner и публикация образа не запускались; browser proof получен локально
|
||
в Chromium на macOS. Другие браузеры и полная визуальная матрица вне scope.
|
||
|
||
Boy Scout review: проверка находится у AccountPage, отдельный e2e runner,
|
||
новые production abstractions и изменения контрактов не добавлены.
|
||
Сравнение diff подтвердило сохранность предыдущих staged/unstaged изменений.
|
||
Порции B-01–B-07 завершены; commit/push не выполнялись.
|