Files
fin-tracker/docs/ai/design-system.md
T
Dmitry 62d36aa3e8 feat(app): новый визуальный язык, верхняя навигация, портфели, создание счетов и ручные события
Тема и общие виджеты (AssetIcon, ServiceMark, HelpTip, глоссарий), верхняя панель TopNav и вкладки Аналитики вместо NavSidebar, иконки PWA. Экраны: портфели, создание брокерского счёта, ручное событие, правка инструмента, Обзор карточками по скоупам и таблица активов, пересчёт метрик с опросом /metrics/status. design-system.md обновлён.
2026-09-19 22:14:21 +03:00

12 KiB

Визуальный язык

Клиент оформлен по образцу Snowball (сервис учёта инвестиций): графитово-фиолетовые поверхности, один яркий голубой акцент, плоские скруглённые карточки без рамки, заливные поля ввода, навигация вкладками сверху. Раньше был другой язык (сине-индиго, боковой сайдбар по референсу Grimmory) — он заменён целиком, кроме доменных экранов, которые сохранили свою раскладку и просто подхватили новые токены.

Одна тема на обе яркости: форма карточек, акцент и поведение навигации одинаковы, различаются только тона поверхностей. Приложение живёт на вебе, Linux, Windows и Android, и тема переключается в Настройках (themeModeProvider) — светлая версия не должна выглядеть недоделанной.

Токены: core/theme/app_theme.dart

AppTheme.light() / AppTheme.dark() строятся одной функцией _build(Brightness).

  • АкцентAppTheme.seed = 0xFF14AFFF (тот же, что ChartColors.slot1Blue, главный ряд графиков). В светлой теме primary чуть темнее (0xFF0A98E6) ради контраста на белом.
  • Тёмные поверхности заданы явно поверх ColorScheme.fromSeed, потому что тонировка Material 3 даёт синий, а не графитово-фиолетовый: surface #25252F (фон страницы) → surfaceContainerLowest #1F1F28 (верхняя панель) → surfaceContainer #2F2F3C (карточки) → surfaceContainerHigh #3A3A49 (поля ввода, вторичные кнопки) → surfaceContainerHighest #474757 (активная «таблетка» навигации).
  • Плюс и минусChartColors.gain (мятный #3DDC97) и ChartColors.loss (коралловый #F26B6B); signColor (portfolio/labels.dart) возвращает именно их. Категориальные цвета графиков (slot1…slot5) не менялись.
  • cardTheme — радиус 12, elevation: 0; в тёмной теме без рамки, в светлой с тонкой outlineVariant. Глобальный токен: меняет вид Card везде.
  • inputDecorationTheme — заливка surfaceContainerHigh, без обводки, радиус 8, акцентная рамка в фокусе. Действует на все формы и диалоги без правки экранов.
  • Кнопки (радиус 8), tabBarTheme, dialogTheme, popupMenuTheme, chipTheme — из тех же токенов.

Все цвета берутся из ColorScheme или ChartColors; хардкод вне них — только градиент логотипа.

Навигация: верхняя панель

features/shell/top_nav.dart (TopNav) показывается от 600 px и шире; уже 600 — нижний NavigationBar с «Ещё» (как раньше). Список направлений — nav_destinations.dart.

  • Слева знак сервиса (core/widgets/service_mark.dart, тот же рисунок, что web/favicon.svg: диск с градиентом от акцента к фиолетовому и три растущих столбца; тап ведёт на Обзор) и четыре основные вкладки-«таблетки»: Обзор, Аналитика, Портфель, Счета (порядок как в Snowball). Экраны «Операции» (События, Импорт, Потоки, Категории, Операции, Правила) собраны в выпадающем меню «Операции ▾»; на нём же подписано имя открытого экрана.
  • Справа «Добавить» (событие, брокерский счёт, портфель, отчёт брокера), значок Здоровья со счётчиком замечаний (dataQualityProvider), Настройки, аватар с меню (email, «Выйти»).
  • До 900 px «Добавить» сжимается до иконки.
  • Контент центрируется и не шире 1440 px.

NavSidebar и NavigationRail удалены. Наблюдение за dataQualityProvider и meProvider остаётся в TopNav, значит эти запросы уходят при каждом заходе, а не только на трёх экранах; для одного пользователя это не проблема, но стоит знать при правке core/cache/*.

Экраны

  • Обзор — сверху сетка карточек «Портфели»: по одной на all, портфель и счёт (home/scope_cards.dart). В карточке: стоимость, прибыль (₽ и %), за день, доходность (XIRR с начала), пассивный доход (% и ₽ в год). Один запрос GET /analytics/overview; тап открывает Портфель в этом скоупе. Ниже прежние блоки (капитал, месяц, запас хода, графики).
  • Аналитика — общая полоса вкладок под верхней панелью (shell/analytics_tabs.dart): Общее, Дивиденды, Ребалансировка, Цели, Налоги, Портфели. Каждая вкладка — свой маршрут (/analytics, /income, …), остаются ссылаемыми. «Общее» (AnalyticsHubPage) — четыре плитки (Стоимость, Прибыль, Доходность, Пассивный доход; portfolio/overview_tiles.dart), график стоимости, доходность, бенчмарки. На телефоне вместо полосы остаётся список разделов.
  • Портфель — таблица активов (portfolio/holdings_table.dart) с вкладками-пресетами колонок (Мои активы, Общее, Дивиденды, Прибыль, Облигации), поиском и двухстрочными ячейками (сумма и, ниже, цена за штуку или доля). Колонки растягиваются на карточку, при нехватке места таблица прокручивается вбок. Неизвестная величина — «—», а не «0 ₽»; нулевые дивиденды тоже «—». HoldingsTab принимает HoldingsSections (all, table, overview), чтобы Портфель и «Аналитика → Общее» брали одни и те же провайдеры и не могли разойтись в числах.
  • Счета — один компактный список (accounts/accounts_page.dart): поиск, чипы статуса с числами (Все / Активные / Отключённые / Архивные) и типа (ликвидные, сбережения, инвестиции, долги). В строке — источник (ZenMoney, T-Invest, отчёты), тип-«таблетка» с меню, баланс или стоимость, переключатели «В капитал» и «Активен». Отключённые и архивные приглушены и идут в конце. Чекбоксы включают панель массовых действий (отключить, включить, сменить тип, учёт в капитале) — по одному запросу на счёт и один пересчёт метрик на всю пачку (accounts/actions.dart). До 880 px строка двухстрочная.
  • Настройки — карточка с вкладками сверху (Аккаунт, Отображение, Сервер) и меню разделов слева; до 720 px меню превращается в ряд чипов.

Что не менялось

Экраны Счета, События, Импорт, Потоки, Категории, Операции, Правила, Здоровье, Цели, Налоги, Ребалансировка, карточка инструмента сохранили раскладку и подхватили токены (карточки, поля, кнопки, вкладки). Переделывать их по образцу Snowball можно тем же способом: экран за экраном.

Иконки активов

AssetIcon (core/widgets/asset_icon.dart) — круг с логотипом эмитента; стоит в колонке «Актив» таблицы и в шапке карточки инструмента. Логотип берётся из брендовых данных T-Invest: синк раскладывает brand.logo_name и brand.logo_base_color по instrument.logo_name / logo_color (сначала по tinvest_uid, потом по ISIN — так подхватываются и бумаги из отчётов Сбера и ВТБ; заполненное не перезаписывается). API отдаёт готовый logo_url (branding.logo_url, публичный CDN invest-brands.cdn-tinkoff.ru, 160 px, без ключа, с access-control-allow-origin: *) и logo_color. Нет логотипа, он ещё грузится или не загрузился — круг цвета бренда (нейтральный, если цвета нет) с иконкой класса актива (assetClassIcon), так что подписи по типу остаются запасным вариантом.

Подсказки к терминам

Рядом с терминами и аббревиатурами (XIRR, TWR, НКД, ЛДВ, «Запас хода», «Пассивный доход» и т. д.) стоит приглушённый значок «?»: на вебе и десктопе подсказка появляется по наведению, на телефоне по тапу (core/widgets/help_tip.dart, HelpTip).

  • TermLabel(text) — подпись, которая сама ищет объяснение в словаре (core/glossary.dart, glossaryHint) по тексту подписи (без учёта регистра и «ё»). Нет записи — обычный Text, поэтому его безопасно ставить под любую подпись. Параметр hint перекрывает словарь там, где одно слово значит разное (например, «Прибыль» в налогах).
  • StatTile и SectionCard уже рисуют подписи через TermLabel, поэтому Обзор, доходы, цели и налоги получили подсказки без правки экранов; заголовки таблиц (Портфель, Налоги), карточки на Обзоре, плитки Аналитики, детали события и карточка инструмента — тоже.
  • Новый термин — добавить строку в _entries словаря; определения сверяйте с кодом аналитики (analytics/*.py), а не с общими представлениями: например, запас хода считается от «обычного» расхода за три последних полных месяца и не включает инвестиции.