Files
specs/openspec/changes/group-account-rows-by-instrument/ux-design.md
T
ksv741 9aa390b8e9
Conventional commits / commits (pull_request) Successful in 9s
Specs security / security (pull_request) Successful in 6s
Specs verify / verify (pull_request) Successful in 8s
spec: plan grouping of all-account rows by instrument (WealthLedger/main#47)
2026-10-30 07:12:37 +03:00

22 KiB
Raw Blame History

UX Design

Задача пользователя: в режиме «Все счета» (/accounts/all/...) быстро увидеть, что несколько записей — это один и тот же факт (один день, одна бумага, один тип), понять общую сумму и при этом сразу видеть вклад каждого счёта. Нормативное поведение (ключ группы, состав, порядок, правила итога) задано в proposal.md и трёх spec; этот документ его не повторяет и не меняет, а только определяет представление.

Допущение: design system — Mantine + @wealthledger/ui по docs/design/overview.md (семантические роли, направление несут знак и текст, не только цвет).

Requirements Covered

  • brokerage-account-ledger — Full ledger presents compact rows (MODIFIED, группа в полном журнале)
  • brokerage-account-ledger — Account overview includes recent ledger events (MODIFIED, группа в обзоре)
  • brokerage-account-ledger — User can retrieve an account ledger (MODIFIED, режим all, partial)
  • brokerage-account-ledger — All-accounts ledger groups same-day entries of one instrument
  • brokerage-account-ledger — All-accounts ledger group shows each entry and an exact total
  • brokerage-account-ledger — All-accounts ledger grouping preserves order and limits
  • brokerage-account-upcoming-events — Account page presents an upcoming events list (MODIFIED, прогнозный итог группы)
  • brokerage-account-upcoming-events — All-accounts upcoming events group same-day events of one instrument
  • brokerage-account-upcoming-events — All-accounts upcoming event group shows each event and a forecast total
  • brokerage-account-upcoming-events — All-accounts upcoming event grouping preserves order and limits
  • brokerage-account-income-analytics — Realized income analytics is account-scoped and period-bound (MODIFIED, режим all)
  • brokerage-account-income-analytics — Income payments remain traceable to exact Ledger Entries (MODIFIED, «Выплаты» в режиме all)

User Flows

Цель пользователя — «что и сколько пришло/придёт по бумаге X и с каких счетов».

  1. Просмотр списка. Пользователь открывает любой из 4 списков в режиме «Все счета» → видит заголовок группы (дата, бумага, тип, итог) → читает строки счетов под ним, раскрывать ничего не нужно.
  2. Переход к счёту. Из строки группы (журнал, выплаты) пользователь открывает /accounts/{id} по имени счёта; возврат — кнопкой «Назад» браузера, список и позиция восстанавливаются как сейчас.
  3. Листание журнала. «Назад/Вперёд» меняют страницу; группа собирается заново из показанных записей страницы. Пользователь не получает пометок о неполноте на границе страницы.
  4. Обзор. На /accounts/all в блоках «Ближайшие события» и «Последние операции» группа занимает место первой записи; блок по-прежнему содержит не более 5 записей/событий, как сейчас.
  5. Одиночная запись / одиночный счёт. Запись без пары, без Instrument или любой список в режиме одного счёта выглядят как сейчас; пользовательский путь не меняется.

Ветвление по итогу: итог известен → показан в заголовке; итог скрыт (см. UI States) → пользователь складывает строки сам или смотрит на состояние страницы (partial-Alert).

Information Architecture

Группа — один визуальный блок (рамка Paper withBorder) внутри существующего списка, на месте первой записи. Состав блока: заголовок + вложенный список строк счетов. Заголовок повторяет композицию текущей карточки (иконка/логотип | название и метаданные | значение), строка счёта — компактнее, без логотипа.

Лейблы заголовка: заголовок = «<тип> · <инструмент>» (как в карточках событий/выплат), метаданные = дата (+ «дата реестра» для дивиденда по реестру), значение = итог, подпись значения = подпись типа (в событиях — «Прогноз»).

Журнал (/accounts/all/operations, обзор «Последние операции»)

┌──────────────────────────────────────────────────────────┐
│ [logo]  Купон · X                        +300 RUB        │  заголовок
│         12 мая 2026                      Купон           │
│ ──────────────────────────────────────────────────────── │
│    Брокерский                           +200 RUB  Купон │  строка
│    ИИС                                  +100 RUB  Купон │  строка
└──────────────────────────────────────────────────────────┘

Строка: имя счёта слева; справа денежное изменение со знаком и подпись типа (по Full ledger presents compact rows). Количество и цена в строках группы не показываются (Presentation Decisions, п. 3). Имя счёта — обычный текст во всех списках, как в текущей метадате строки (withDetails в этих списках не используется).

События (/accounts/all/events, обзор «Ближайшие события»)

┌──────────────────────────────────────────────────────────┐
│ [logo]  Дивиденд · X                     10 USD          │
│         12 мая 2026 · дата реестра       200 RUB         │  итоги по валютам
│                                          Прогноз         │
│ ──────────────────────────────────────────────────────── │
│    Брокерский                            10 USD          │
│    ИИС                                   Сумма неизвестна│
└──────────────────────────────────────────────────────────┘

Пример выше показывает два разных состояния сразу; на практике итог при строке «Сумма неизвестна» скрыт (см. UI States). Строки счёта в событиях — только текст (ссылки на счёт в текущей карточке события нет).

Выплаты (/accounts/all/income)

┌──────────────────────────────────────────────────────────┐
│ [logo]  Купон · X                        +300 RUB        │
│         Исполнено 12 мая 2026            Купон           │
│ ──────────────────────────────────────────────────────── │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Брокерский                            +200 RUB      │ │  отдельная карточка
│ └──────────────────────────────────────────────────────┘ │  внутри группы
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ИИС                                   +100 RUB      │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘

В «Выплатах» spec требует «отдельную сразу видимую карточку» записи внутри группы: вложенные карточки без логотипа и с границей, но визуально легче внешней. Подпись «Исполнено» сохраняет различие факта и прогноза.

Размещение в 4 списках: страница журнала и «Выплаты» — вертикальный стек с зазором sm; страница событий — тот же стек; блоки обзора — тот же компонент группы без дополнительной рамки блока. Позиция — по правилам порядка из spec (группа на месте первой записи).

UI States

Состояние Представление
Одиночная запись Как сейчас: текущая карточка, без группы и вложенного списка
Группа с итогом Заголовок с итогом +300 RUB (тон по знаку), строки счетов
Мультивалютный итог В заголовке столбец значений: по строке на валюту, +150 RUB над +10 USD; порядок — по первому появлению валюты в записях группы (Presentation Decisions, п. 2); без конвертации и общей суммы
Итог скрыт Значение в заголовке отсутствует; строки счетов видны. Причина: запись без суммы / событие с неизвестной суммой / период partial. Для экранных дикторов — скрытый текст «Итог недоступен» (без различения причин)
Нулевой итог 0 RUB без знака, нейтральный тон; строки с их знаками
Offer без сумм Группа без итога; строки: имя счёта + «Сумма не определена» (существующий текст events.noAmount)
Прогноз Итог и суммы строк в событиях несут подпись «Прогноз» (существующий payout.forecast); тон нейтральный, как у текущей карточки прогноза
Строка без денег (ledger) Нейтральный текст payout.noMoney/«без движения денег» как в одиночной записи; итог группы скрыт
Обзор, 5 записей Не более 5 записей/событий, как сейчас; группа может занимать часть из них, например 2 из 5; кнопка «Все …» ведёт на полную страницу
Граница страницы журнала Группа из показанных записей, без пометки о неполноте и без «ещё N на другой странице»
Loading / empty / error / partial-Alert Не меняются: LoadingSkeleton, StateView, Alert и SourceSyncStatus из существующих страниц; группы строятся только для загруженных данных

Знак: «+» и «−» всегда в тексте; цвет (positive/negative/neutral) — дополнение.

Visual Package

Решение: reuse + один новый тонкий контейнер. Изображения (PNG/SVG) не создаются: изменение — повторная композиция существующих карточек, а структура и тексты описаны текстовыми макетами выше; отдельный макет дал бы расхождение с живыми компонентами и токенами Mantine.

Переиспользуется:

  • EntryCard (@wealthledger/ui) — заголовок группы: логотип/инициал, title, metadata (дата), value (итог), valueLabel (тип/«Прогноз»), tone. Отличие: в value нужно выводить несколько значений для нескольких валют (сейчас одна строка) и допустить отсутствие значения.
  • LedgerEntryRow / PayoutCard (executed) — единичные записи и карточки внутри группы выплат; в группе опускаются логотип, дата и название инструмента (они в заголовке).
  • BrokerageEventCard / PayoutCard (forecast) — единичные события; тексты events.type.*, events.noAmount, payout.forecast.
  • Форматирование: formatMoney, formatDate, instrumentLogoUrl; тексты ledger.noInstrument, payout.noMoney.
  • Токены: --wl-color-border, --wl-color-subtle, --wl-color-surface, Paper withBorder.

Новое (предложение, с обоснованием «нужно показать иерархию заголовок → строки»):

  • Контейнер группы (рамка, заголовок, вложенный список) и компактная строка счёта (имя счёта слева, сумма справа, без Avatar). EntryCard всегда рисует Avatar, поэтому для строки счёта нужна его облегчённая форма либо отдельная разметка; выбор — за реализацией. Отступ строк слева — под текстовым блоком заголовка (≈ 40 px аватар + sm), либо без отступа на узкой ширине.

Responsive

  • Базовый макет — mobile-first: заголовок — сетка из трёх колонок как EntryCard (auto | 1fr | auto).
  • ≥ 768 px: заголовок в одну строку; строки счетов — имя слева, сумма и подпись справа.
  • < 576 px (узкий мобильный): колонка значения заголовка переносится под название (название и метаданные — вверху, итог — следующей строкой справа/слева), мультивалютный итог — столбцом; название инструмента и имя счёта переносятся (wrap, без lineClamp), отступ строк слева убирается. Строка счёта: имя счёта сверху, сумма с подписью типа ниже, если вместе не помещаются; длинное имя счёта не обрезается.
  • Никакого горизонтального скролла; минимальная ширина — 320 px.
  • Ввод: touch и мышь; в группе нет новых целей нажатия.

Accessibility

Ориентир — WCAG 2.2 AA.

  • Семантика списка. Внешний список — <ul>; каждая одиночная запись и каждая группа — <li>. Внутри группы-<li> заголовок (обычный блок, не <h*>, чтобы не засорять навигацию по заголовкам страницы) и вложенный <ul> со строками-<li>. Группы в обзоре — внутри уже существующих секций с их заголовками.
  • Доступное имя группы. Вложенный <ul> получает aria-label вида: «Купон, X, 12 мая 2026, итог +300 RUB» (дата, инструмент, тип, итог, мультивалютный итог — через запятую). При скрытом итоге — «… итог недоступен». Для знаков в озвучке: «плюс 300 рублей», «минус …» — через aria-label значения либо скрытый текст; сам знак в тексте остаётся.
  • Итог скрыт. Визуально значения нет; в DOM есть visually-hidden текст «Итог недоступен» и он включён в имя группы. Причина скрытия partial уже объявляется существующим Alert role="status".
  • Фокус. Заголовок и строки группы не фокусируемы: новых интерактивных элементов нет. Если одиночная карточка содержит ссылку, она остаётся как есть, а порядок Tab совпадает с порядком DOM.
  • Не только цвет. Направление передаёт знак «+»/«−» (минус как «−»), подпись типа и слова «Прогноз»/«Исполнено»; цвет только усиливает. Контраст текста ≥ 4.5:1 (dimmed gray.6 на subtle проверить), границы групп ≥ 3:1 не требуются — это декор, смысл не теряется без границы.
  • Динамика. Смена страницы журнала не перемещает фокус неожиданно; пагинация — как сейчас.
  • Масштаб. Текст масштабируется до 200 % без потери информации (перенос строк).

Presentation Decisions

Решения, которые относятся только к представлению и не меняют поведение по specs:

  1. Видимая причина скрытого итога. Видимого маркера нет. Скринридер получает текст «Итог недоступен». Строки с суммами остаются видны, так что отсутствие итога не выглядит пустотой.
  2. Несколько валют в заголовке. Каждая валюта на отдельной строке, в порядке первого появления в записях группы.
  3. Количество и цена в строках сделок. Не показываются, как и в несгруппированном журнале. Specs требуют только счёт и денежное изменение.
  4. Ссылка на счёт. Сохраняется текущее поведение: имя счёта — текст во всех четырёх списках.
  5. Подпись «Исполнено» / «Прогноз». Ставится у итога в заголовке. В строках событий подпись остаётся у суммы, как в одиночной карточке.
  6. Дата реестра. Применяется только к группам прогнозных событий. Исполненные выплаты датируются датой исполнения.

Findings → Specs

Нет. Два пробела, найденных при проектировании, закрыты решением Change Owner и внесены в specs, G2 инвалидирован:

  • прогнозный итог скрывается, если полнота прогноза не подтверждена;
  • запись с нулевой суммой считается записью без денежного изменения и скрывает итог.