Frontend verify / commits (push) Skipped
Frontend verify / security (push) Successful in 9s
Frontend verify / verify (push) Successful in 6m21s
Frontend verify / commits (pull_request) Failing after 34s
Frontend verify / security (pull_request) Skipped
Frontend verify / verify (pull_request) Skipped
282 lines
21 KiB
Markdown
282 lines
21 KiB
Markdown
# WealthLedger frontend
|
||
|
||
React/Vite-клиент WealthLedger. Клиент получает данные только через typed API
|
||
backend; прямые вызовы T-Bank из frontend не допускаются.
|
||
|
||
## Быстрый старт
|
||
|
||
Требуется Node 24 (`.nvmrc`). Создайте `.env` из примера и установите
|
||
зависимости строго из lockfile:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
nvm use --silent
|
||
npm ci
|
||
npm run dev
|
||
```
|
||
|
||
`VITE_API_BASE_URL` по умолчанию указывает на `http://localhost:3000`.
|
||
|
||
Для commits установите [Gitleaks](https://github.com/gitleaks/gitleaks/releases/tag/v8.30.1)
|
||
8.30.1 в `PATH` (macOS: `brew install gitleaks`). Pre-commit сначала проверяет
|
||
staged diff с полной редакцией секретов, затем запускает установленный
|
||
`node_modules/.bin/lint-staged` только для staged JS/TS. Hook не скачивает tools и
|
||
не запускает full graph: полный `npm run verify` выполняется в CI и перед передачей.
|
||
Находка или отсутствие Gitleaks либо lint-staged блокирует commit.
|
||
Проверка этого поведения: `node --test scripts/test-pre-commit.mjs`.
|
||
|
||
## Контейнерный image
|
||
|
||
`Dockerfile` имеет targets `development` (Vite HMR и proxy `/api/` во внутренний backend)
|
||
и `production` (nginx на 8080, SPA fallback и same-origin API proxy). Node соответствует `.nvmrc`.
|
||
Build context ограничен `.dockerignore`; `.env`, local DB и host node_modules
|
||
не попадают в image. Изменение `packages/tokens` или `packages/ui` требует rebuild development
|
||
image, если локальный запуск использует bind-mount только `src`/`public`.
|
||
|
||
```bash
|
||
docker build --target development -t wealthledger-frontend:development .
|
||
docker build --target production -t wealthledger-frontend:production .
|
||
```
|
||
|
||
Gitea `image.yml` запускается на защищённом release tag `vX.Y.Z`, проверяет
|
||
package/lock version и canonical gates. Runner `ubuntu-latest` должен иметь
|
||
Docker/buildx. Образы хранятся в Nexus:
|
||
`nexus.personal.ksv741.ru/docker-hosted/wealthledger/frontend`. Пользователь `wealthledger-ci`
|
||
задаётся Actions secrets `NEXUS_USERNAME`/`NEXUS_PASSWORD`; эта же учётная
|
||
запись используется сервером для загрузки образов. Docker endpoint и token realm должны использовать
|
||
HTTPS; в `docker-hosted` включается запрет redeploy. Прежние Gitea secrets
|
||
сохраняются на время перехода и не передаются в Nexus.
|
||
Публикуются semver tags, без `latest`; существующие release tags не перезаписываются. Production выбирает опубликованный version tag.
|
||
|
||
Compose topology принадлежит workspace, эксплуатационная последовательность —
|
||
[документация](https://git.personal.ksv741.ru/WealthLedger/docs),
|
||
страница `architecture/deployment.md`.
|
||
Проверка публикации требует настроенного registry/runner; локальный build
|
||
не доказывает package permissions.
|
||
|
||
## Установка как приложение (PWA)
|
||
|
||
Production-сборка поддерживает установку WealthLedger в отдельное окно через
|
||
`vite-plugin-pwa`. Нужен HTTPS или localhost. В Chrome/Edge выберите «Установить
|
||
WealthLedger» в меню браузера или значок установки в адресной строке; на iPhone
|
||
и iPad — Safari → «Поделиться» → «На экран Домой». Доступность установки зависит
|
||
от браузера и ОС.
|
||
|
||
Service worker кеширует только статическую оболочку, включая страницы SPA.
|
||
Вход, финансовые данные и сохранение изменений требуют сети: API, токены и
|
||
ответы API не сохраняются в Cache Storage. Это не автономный финансовый
|
||
клиент. Новая версия загружается в фоне и применяется после закрытия всех
|
||
вкладок и окон WealthLedger и повторного открытия; открытые формы автоматически
|
||
не перезагружаются.
|
||
|
||
Локальная проверка: `nvm use --silent`, `npm run build`, `npm run preview`.
|
||
В DevTools → Application проверьте Manifest, Service Workers и Cache Storage.
|
||
В режиме `npm run dev` и в Storybook service worker не включается.
|
||
|
||
## Проверка
|
||
|
||
```bash
|
||
nvm use --silent
|
||
npm ci
|
||
npx --no-install playwright install chromium
|
||
npm run verify
|
||
```
|
||
|
||
Команда проверяет, что generated OpenAPI types соответствуют committed snapshot,
|
||
затем запускает strict TypeScript, ESLint и проверку его конфигурации,
|
||
один coverage-backed запуск Vitest,
|
||
Knip, dependency rules приложения и stories, сборки приложения и Storybook.
|
||
|
||
`npm run lint` запускает ESLint с `--max-warnings=0`; `npm run lint:fix`
|
||
применяет автофиксы. [Конфигурация](eslint.config.mjs) подключает рекомендованные
|
||
наборы всех установленных плагинов: TypeScript, React, Hooks, Refresh, Query,
|
||
Unicorn, import-x, SonarJS, RegExp, Security, no-unsanitized, Perfectionist, Stylistic,
|
||
Vitest, Testing Library, jest-dom и Storybook. Правила React, тестов и stories
|
||
применяются к соответствующим файлам; generated/build artifacts исключены.
|
||
|
||
Stylistic отвечает за форматирование JS/TS/TSX, Perfectionist — за сортировку.
|
||
Сохраняются табы, двойные кавычки и точки с запятой. Biome удалён; Prettier
|
||
не используется. CSS, JSON и Markdown не форматируются через ESLint.
|
||
`lint-staged` исправляет JS/TS/TSX перед commit. `lint:eslint` сначала собирает
|
||
private packages, чтобы анализ типов использовал актуальные declarations.
|
||
`npm run test:lint` проверяет действующие presets и ошибочные примеры.
|
||
`no-unsanitized` проверяет опасные DOM HTML-присваивания и вызовы;
|
||
React `dangerouslySetInnerHTML` отдельно проверяется React-плагином.
|
||
|
||
Точечные исключения в исходниках объясняют контракты и ограничения статического
|
||
анализа; изменение nullable API fields, query keys или порядка race-операций
|
||
ради линтера недопустимо. Архитектуру проверяет dependency-cruiser, поведение —
|
||
Vitest/MSW и Chromium. Линтеры не заменяют semantic/security review.
|
||
`jsx-a11y` без поддержки ESLint 10 остаётся отложенным в
|
||
[workspace issue #18](https://git.personal.ksv741.ru/WealthLedger/main/issues/18).
|
||
|
||
Vitest включает JSDOM-тесты и проверку геометрии секций позиций в headless
|
||
Chromium при ширинах 375 и 1280 px. Browser project не использует JSDOM setup
|
||
и его заглушки layout API. Только браузерная проверка:
|
||
`npm run test:run -- --project='browser-*'`. После обновления Playwright повторите
|
||
установку Chromium; CI устанавливает его с `--with-deps` для Linux.
|
||
Единый `.gitea/workflows/verify.yml` запускает `commits → security → verify`
|
||
последовательно на PR; на push в `develop`/`main` — `security → verify`.
|
||
Рабочие ветки проверяются после открытия PR, без дублирующего push-запуска.
|
||
Изменение заголовка PR запускает `.gitea/workflows/pr-title.yml` (то же имя workflow,
|
||
только job `commits`): статусы `security` и `verify` на HEAD не перезаписываются.
|
||
Новый запуск отменяет устаревший для того же PR/ветки и типа события
|
||
(требуется Gitea 1.26+). Ежедневно в 05:17 UTC выполняется только `security`.
|
||
Job `security` проверяет полную доступную историю Gitleaks 8.30.1 и выполняет
|
||
`npm audit --include=dev --audit-level=low`. Любой advisory блокирует security job;
|
||
исключений frontend нет. Локально можно выполнить те же команды:
|
||
`gitleaks git --log-opts=--all --redact=100 --no-banner` и
|
||
`npm audit --include=dev --audit-level=low`. Audit требует доступа к npm registry;
|
||
проверка секретов выполняется локально и не выводит их значения.
|
||
|
||
Имена jobs `commits`, `security`, `verify` сохранены, но Gitea включает имя
|
||
workflow в статус. При внедрении администратор должен заменить required checks
|
||
для защищённых веток на `Frontend verify / commits (pull_request)`,
|
||
`Frontend verify / security (pull_request)` и
|
||
`Frontend verify / verify (pull_request)`, удалив старые имена и push-статусы,
|
||
если они требовались. Все три PR-проверки должны оставаться обязательными.
|
||
Публикация образов по тегам остаётся в `image.yml`.
|
||
Последовательность jobs ограничивает один запуск; для ограничения нагрузки всего сервера настройте
|
||
`runner.capacity: 1` у общего runner и учтите остальные зарегистрированные
|
||
runners. Конфигурация runner управляется вне этого репозитория.
|
||
|
||
TypeScript остаётся на 5.9.3: `openapi-typescript@7.13.0` требует peer `^5.x`.
|
||
Обновление до TypeScript 7 из Renovate dashboard откладывается до совместимого
|
||
релиза генератора; `--force`/`--legacy-peer-deps` для обхода этого ограничения
|
||
не используются.
|
||
Если backend-контракт изменился,
|
||
обновите `src/shared/api/spec/openapi.json` копированием backend OpenAPI
|
||
document и затем выполните `npm run openapi:generate`.
|
||
|
||
## SonarQube
|
||
|
||
Проект `wealthledger-frontend` использует настройки из
|
||
`sonar-project.properties`. Анализируются исходники `src`; тесты учитываются
|
||
отдельно, а Storybook fixtures, test helpers, generated API types и OpenAPI
|
||
snapshot исключены. `npm run test:coverage` (также входит в `npm run verify`)
|
||
создаёт `coverage/lcov.info` для импорта покрытия.
|
||
|
||
Для первого анализа установите SonarScanner CLI, совместимый с версией сервера,
|
||
и выполните из корня frontend:
|
||
|
||
```bash
|
||
nvm use --silent
|
||
npm ci
|
||
npm run test:coverage
|
||
# SONAR_TOKEN передайте через окружение; не сохраняйте токен в репозитории.
|
||
sonar-scanner
|
||
```
|
||
|
||
В Gitea откройте frontend → Settings → Actions → Secrets и добавьте
|
||
`SONAR_TOKEN` с токеном анализа проекта. Переименовывать основную ветку в
|
||
SonarQube не требуется; `sonar.branch.name` не задаётся.
|
||
Используется Community Edition 9.9.8: CI отправляет только
|
||
push в `develop`, после успешного `verify`, и ожидает результат Quality Gate.
|
||
PR и остальные ветки проходят обычные проверки без отправки в SonarQube.
|
||
Отрицательный Quality Gate или ошибка scanner завершает `verify` с ошибкой.
|
||
|
||
Runner должен поддерживать Docker actions и иметь HTTPS-доступ к
|
||
`https://sonarqube.personal.ksv741.ru`, GitHub и Docker Hub. Docker action
|
||
SonarSource закреплён на commit версии 2.3.0; scanner запускается отдельно от
|
||
Node 24, используемого для сборки. `tsconfig.sonar.json` содержит совместимые
|
||
со старым анализатором TS настройки; приложение проверяется штатным TypeScript
|
||
5.9 через `npm run verify`. После обновления сервера пересмотрите scanner и
|
||
возможность использовать основной tsconfig. Успешный локальный `verify` не
|
||
доказывает доступность сервера и успешность анализа в CI.
|
||
|
||
## UI-каталог Storybook
|
||
|
||
После `nvm use --silent` выполните `npm run dev:catalog`; статическая сборка —
|
||
`npm run build:catalog`. Каталог использует общую тему приложения, вымышленные
|
||
данные и локальные действия без application API. Примеры не доказывают
|
||
восстановление реального запроса. Визуальные роли описаны в
|
||
[UI convention](.ai/conventions/ui.md#визуальная-основа).
|
||
|
||
## Документация
|
||
|
||
- [AGENTS: правила и источники](AGENTS.md)
|
||
- [Выбор owner/FSD](.ai/architecture.md)
|
||
- [UI, state, формы и компоненты](.ai/conventions/ui.md)
|
||
- [Data/API/cache](.ai/conventions/data.md)
|
||
- [Testing](.ai/conventions/testing.md)
|
||
- [UI-каталог Storybook](#ui-каталог-storybook)
|
||
|
||
Checkout frontend содержит все входы сборки и проверок. Публичная схема API
|
||
поступает как отдельный artifact; команды frontend используют только её
|
||
локальную копию и не обращаются к исходникам или CI сервера. Доставка новой
|
||
схемы — явное обновление snapshot с review и `npm run openapi:generate`.
|
||
Runtime URL API задаётся окружением, а не расположением другого репозитория.
|
||
Разработка начинается с Issue; lightweight или SDD выбирается по критериям
|
||
Engineering Workflow из AGENTS. OpenSpec — после явного решения Change Owner.
|
||
|
||
Knip исключает `conventional-changelog-conventionalcommits`: release-it загружает
|
||
preset по имени из `.release-it.json`. Версия preset закреплена напрямую.
|
||
|
||
## Релизы
|
||
|
||
Репозиторий выпускается независимо. `develop` — интеграция, `main` — принятые
|
||
релизы. Обычная работа: issue-linked ветка от актуального `develop`, PR в `develop`;
|
||
общие правила — в Engineering Workflow по ссылке из AGENTS.
|
||
Обычные PR допускают squash; release и обратная синхронизация сохраняют историю.
|
||
Commits и PR titles — Conventional Commits со ссылкой на Issue (кроме release),
|
||
включая `spec:`. Версии вручную не повышай.
|
||
|
||
1. Получи `main`, `develop` и теги: `git fetch origin --tags`. Убедись, что
|
||
`develop` содержит последний релиз. Создай `release/<name>` от проверенного
|
||
`develop`; `nvm use --silent`, `npm ci`. Hotfix начинается от `main` в
|
||
`release/hotfix-<name>` и проходит тот же процесс.
|
||
2. На чистой ветке выполни `npm run release:preview`, просмотри версию/changelog,
|
||
затем `npm run release:prepare`. Команда создаёт `chore(release): X.Y.Z`,
|
||
обновляет manifest/lockfile/CHANGELOG; тег и push не выполняет. Не обходи
|
||
guards ветки, чистого дерева, отсутствующих тегов или уже подготовленной версии.
|
||
3. Проверь diff и штатные gates этого репозитория. Отправь только release-ветку,
|
||
открой PR в `main` с заголовком `chore(release): X.Y.Z`. После зелёных
|
||
`verify`, `security`, `commits` выполни fast-forward-only merge. Если `main`
|
||
изменился, согласуй историю и повтори проверки кандидата.
|
||
4. Получи удалённый `main`, сравни его HEAD с проверенным release SHA и версией
|
||
manifest/lockfile. Создай аннотированный `vX.Y.Z` именно на этом SHA:
|
||
`git tag -a vX.Y.Z <release-sha> -m "Release X.Y.Z"`, затем отправь только
|
||
`git push origin refs/tags/vX.Y.Z`. Не запускай release-it повторно на `main`.
|
||
5. Верни release-коммит через PR `main → develop` без squash/rebase; проверь,
|
||
что `develop` содержит тег. Опубликованные теги не перемещай. При сбое после
|
||
merge создай отсутствующий тег без нового bump; до merge исправь и проверь
|
||
существующий кандидат без повторного повышения версии.
|
||
|
||
`feat:` повышает minor, `fix:`/`perf:` и прочие разрешённые типы — patch;
|
||
`!`/`BREAKING CHANGE:` — major, включая версии до 1.0. `chore(release):`
|
||
исключён из рекомендации и changelog. Конфигурация — `.release-it.json`.
|
||
В Gitea для `main`/`develop` должны быть запрещены прямой/force push и обход
|
||
администратором, обязательны `verify`, `security`, `commits`; теги `v*` защищены.
|
||
Workflow сам не включает эти настройки. Отдельное review approval для одного
|
||
владельца не требуется. Npm publishing, deploy и Gitea Releases не включены.
|
||
|
||
## Локальные пакеты
|
||
|
||
`packages/tokens` — private npm workspace с единственным export
|
||
`@wealthledger/tokens/tokens.json`. `packages/ui` экспортирует собранные
|
||
`@wealthledger/ui/theme` (JS + declarations) и `@wealthledger/ui/styles.css`.
|
||
Корневой export `@wealthledger/ui` содержит EntryCard, StateView и LoadingSkeleton;
|
||
Потребители импортируют компоненты напрямую из `@wealthledger/ui`.
|
||
CSS явно подключается после Mantine; providers остаются в приложении/Storybook.
|
||
Тема доступна через `@wealthledger/ui/theme`; compatibility re-exports удалены.
|
||
|
||
JSON не требует сборки. `npm ci` связывает workspaces, а существующие команды
|
||
разработки, проверок и сборки сначала выполняют `npm run build:packages`.
|
||
Library build использует отдельный Vite config без app plugins и TypeScript
|
||
для declarations. После изменения package sources перезапустите `npm run dev`
|
||
или `npm run dev:catalog`; отдельный watch-процесс пока не нужен.
|
||
Начальная версия manifest взята из приложения; отдельный release-процесс
|
||
и публикация не настроены.
|
||
|
||
Private `@wealthledger/icons` содержит все SVG приложения: действия,
|
||
навигацию, contactless/wallet и геометрию `OperationIcon`. Пакет поставляет
|
||
ESM, declarations и `@wealthledger/icons/styles.css`; приложение и Storybook
|
||
подключают CSS явно. В `src` нет raw SVG и компонентов только для рисования
|
||
иконок. Category/kind mapping остаётся в model `entities/bank-card`.
|
||
|
||
Статичные иконки принимают `IconProps`: `size` по умолчанию 24,
|
||
`strokeWidth` 1.5, цвет через `currentColor`. В кнопке иконка декоративная,
|
||
имя принадлежит действию. Для самостоятельного изображения передайте
|
||
`aria-hidden={false}`, `role="img"` и `aria-label` либо `aria-labelledby`.
|
||
Анимации сохраняют hover/focus lifecycle и `prefers-reduced-motion`.
|