spec: plan grouping of all-account rows by instrument (WealthLedger/main#47)
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
# 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 инвалидирован:
|
||||
- прогнозный итог скрывается, если полнота прогноза не подтверждена;
|
||||
- запись с нулевой суммой считается записью без денежного изменения и скрывает итог.
|
||||
Reference in New Issue
Block a user