Files
specs/openspec/changes/group-account-rows-by-instrument/ux-design.md
T
codex-bot f3a6b78461
Specs security / security (push) Successful in 7s
Specs verify / verify (push) Successful in 9s
spec: plan grouping of all-account rows by instrument (WealthLedger/main#47) (#64)
2026-10-30 07:28:31 +03:00

212 lines
22 KiB
Markdown

# 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 инвалидирован:
- прогнозный итог скрывается, если полнота прогноза не подтверждена;
- запись с нулевой суммой считается записью без денежного изменения и скрывает итог.