Plan exchange workspace and transaction journeys #25

Merged
tech merged 3 commits from exchange-shell-24 into main 2026-09-12 10:13:01 +00:00
2 changed files with 382 additions and 0 deletions
Showing only changes of commit 91151c6cf3 - Show all commits

No files matched your search

+12
View File
@@ -141,3 +141,15 @@ current packages, app/website commands, verification previews, analytics limits,
skill location and contribution workflow. Local links and diff checks pass; skill location and contribution workflow. Local links and diff checks pass;
commands and prerequisites were checked against repository configuration. commands and prerequisites were checked against repository configuration.
Documentation only; no runtime tests were run. Documentation only; no runtime tests were run.
## Exchange workspace UX plan — task #24
- Plan-only scope confirmed by the user; no application code changed.
- [Exchange workspace plan](ux/exchange-workspace.md) defines accounts, top-up,
exchange, payout, Activity, Limits and Settings; account-owned language replaces
repeated page selectors in the proposed interface.
- Includes authoritative quotes/status/limits, idempotent recovery, account-profile
language persistence, accessible/responsive behavior and semantic funnel metrics.
- Standalone documentation branch `exchange-shell-24` from main. Future interface
implementation should incorporate KYC PR #21 and usage PR #23.
- Validation: documentation links and diff checks; no runtime changes or tests.
+370
View File
@@ -0,0 +1,370 @@
# Exchange workspace — UX plan
Task [#24](https://git.sendico.io/tech/sxchnge/issues/24). **Plan only; no app changes.**
## Product direction
Make it easy to fund a currency account, exchange into another currency, and pay
out. Accounts answer “what can I use?”, Activity answers “what happened?”, Limits
answers “what can I do next?”, and Settings owns account preferences.
A new user should see a clear first step: **Top up an account**. A funded user
should be able to exchange or pay out from that account without navigating through
settings or a generic dashboard. Unverified is a valid state with basic limits.
KYC is offered when an upgrade is useful, not as a forced onboarding detour.
Use the existing SXCHNGE theme, rounded surfaces, typography and semantic colors.
Keep decoration secondary to balances, amounts, recipients and transaction terms.
## Navigation and screen hierarchy
| Destination | What it contains | Main next action |
|---|---|---|
| Accounts (default) | Currency accounts, available balances, reserved amounts, recent activity, actionable pending items | Top up, exchange, pay out |
| Currency account | Available/booked balance, holds, relevant activity and applicable limits | Act on this currency |
| Activity | All operations, filters, search, processing items | Inspect an operation or resolve an actionable issue |
| Limits | Current caps, used, reserved, available, period/reset information and upgrade benefits | Verify identity when useful |
| Settings | Profile, language, security, notifications and help | Manage the account |
Desktop: a persistent sidebar with **Accounts · Activity · Limits · Settings**.
Mobile: the same four destinations in bottom navigation. Highlight the active
section. Identity verification is reached from Limits and Settings; it does not
need a separate primary navigation item.
Top up, exchange and pay out are actions, not three competing dashboard tabs.
On Accounts and currency-detail screens, put them beside the selected account on
wide screens and in a clearly labeled action area beneath its balance on mobile.
The transaction flow uses a focused page with Back and Close, a visible source
account and a review step. Avoid full-screen wizard chrome on simple settings.
Proposed routes, to be finalized during implementation:
| Route | Purpose |
|---|---|
| `/accounts` | Overview |
| `/accounts/:accountId` | Currency account detail |
| `/top-up` | Account → method → instructions/payment → operation |
| `/exchange` | Source/destination/amount → quote review → operation |
| `/payout` | Account/method/recipient/amount → review → operation |
| `/activity` | Operation list |
| `/activity/:operationId` | Canonical operation detail |
| `/limits` | Usage, constraints and KYC upgrade |
| `/identity` | Existing identity verification flow |
| `/settings` | Account settings |
| `/settings/preferences` | Language and other supported preferences |
| `/settings/security` | Supported security actions |
Use opaque IDs in paths. Never put recipient details, document data, amounts,
verification tokens or sensitive filter text into route parameters/history.
URLs identify screens; providers hold transient input. Deep links to operation
status must reload authorized service data, not depend on a previous screen.
## Accounts overview
The first viewport should show account balances and useful actions, rather than
charts or promotional blocks.
```text
ACCOUNTS Account menu
Your accounts [Add currency account, if supported]
[ EUR · available balance ] [ USD · available balance ] …
Selected account: EUR
Available: … Reserved: …
[Top up] [Exchange] [Pay out]
Action needed / in progress (only when there is something useful to show)
Recent activity [See all]
```
- Each card shows currency code/name, available amount and a text status when
restricted. Do not rely on flags to identify currencies.
- Distinguish available, booked and reserved balances. “Available” is the default
spendable number; disclose the balance breakdown on account detail. Backend
balances are authoritative; a list of recent operations is not a balance ledger.
- Do not sum different currencies directly. A future total in a reporting currency
must be labeled estimated, with valuation currency/time and a service-provided
conversion. It is not needed for the first interface.
- Zero balance: explain that the account is ready and make **Top up** primary.
Funded account: make **Exchange** primary, with Top up and Pay out visible.
Never rearrange actions while the user is interacting with them.
- No accounts: offer account creation only if the backend supports it. Otherwise
explain provisioning status and provide Refresh/Help; do not invent accounts.
- Partial failure: keep successfully loaded accounts usable. Mark stale balances,
offer refresh, and obtain fresh eligibility/quote data before execution.
- Context travels into the flow: starting from EUR preselects EUR. Account switching
invalidates any quote and refreshes methods and eligibility.
## Top-up journey
**Outcome: funds are credited and become available**, not merely instructions shown
or a payment button pressed.
1. Choose the receiving account. If there is one eligible account, preselect it.
2. Choose from service-provided methods. Show fees, minimum/maximum, expected timing
when supplied, and whether an external step is required. Do not advertise rails
that are not actually available.
3. Collect only the fields required by the selected method. Amount is required
only when that method needs it. Validate currency precision and current bounds.
4. Review what the user pays, fees, and what is expected to be credited. For bank
instructions, show exact destination/reference from the service with labeled
copy actions and “Copied” feedback. For redirects, explain the next step before
opening the provider. Never collect card details in generic custom fields.
5. Create/continue the operation and show its canonical status. “Awaiting payment”
or “Processing” remains pending until the service confirms credit.
6. On credit, refresh balances, usage and activity. Offer **Exchange this balance**
with the funded currency preselected, plus View account.
Returned from a provider does not mean paid. Reconcile using the operation ID.
Handle abandoned redirects, payment decline, expired instructions, wrong/partial
amount, delayed credit and service failure with method-specific service responses.
Do not invent a completion SLA. Never expose a “Mark paid” control as proof of funds.
## Exchange journey
**Outcome: the exchange is executed and both currency accounts reflect the result.**
1. One composition screen: source account, destination currency/account, “You send”
and “You receive.” Clearly mark which amount is editable; editing the other
side requests a new quote if the backend supports receive-exact quotations.
2. Show available source balance and eligibility near the input. Currency swap
exchanges the selection and invalidates the prior quote. Unsupported pairs are
explained before the review step. Offer Add account only if supported.
3. Quote response owns the rate, fees, total debit, net credit, quote ID and expiry.
Show **1 source currency = … destination currency**, the actual debit and net
receipt; never bury fees behind an unexplained estimate.
4. **Review exchange** summarizes both accounts, amounts, fees and rate. The final
button says **Exchange [amount/currency]**. Disable final execution while the
quote is missing, stale, expired or being refreshed, with a nearby reason.
5. If terms change, show the new quote and require confirmation again. Never
silently execute a refreshed rate. Prevent double submission and reuse the
idempotency key when recovering the same request.
6. Processing leads to operation detail. Only service-confirmed execution shows
**Exchange complete**. Refresh both accounts, activity and limits. Offer
**Pay out [destination currency]** and View account.
“Max” must use a service-provided spendable amount inclusive of fees and applicable
limits; do not assume the displayed balance can all be exchanged. If the amount is
above the current permitted limit, show the actual blocker and an upgrade link only
when KYC would help. Keep the entered amount when requesting a new quote.
## Payout journey
**Outcome: the payout reaches a service-confirmed terminal result.** Distinguish
“submitted,” “sent” and “received” when those are separate states in the contract.
1. Preselect the source currency account. Choose a supported payout method and an
existing recipient, or enter a new destination. Saved recipients are optional
future functionality; do not imply they exist without a service.
2. Method-specific fields and validation come from the service contract. Clearly
identify the beneficiary/destination, bank or network when applicable. Do not
guess which currencies support which destination types.
3. Enter amount; show fee, total debit, destination amount/currency, available funds,
limits and expected timing supplied by the backend.
4. Review the recipient and full destination sufficiently to detect a mistake,
alongside the amount and costs. The final button says **Pay out [amount/currency]**.
Explain irreversibility where applicable; avoid a redundant confirmation dialog
after an already clear review page. Perform step-up authentication when required
by the backend, without losing the pending operation context.
5. Show processing/status with a reference. Cancellation is offered only when the
service says the operation remains cancellable. If supported, confirm the
cancellation and refresh status rather than optimistically removing the payout.
6. On confirmed success, show the outcome and account/activity links. A returned or
reversed payout remains visible with its own explanation and linked reversal.
Insufficient funds should offer **Top up this account** or **Exchange into this
currency**, preserving the draft locally. After funding, revalidate recipient,
fees, available balance and limits before allowing submission. Never auto-submit.
A transfer destination must never be included in analytics, logs or URLs.
## Shared transaction states and recovery
| State | User-facing behavior |
|---|---|
| Initial/draft | Clear primary action, no invented balances/rates |
| Loading balance/methods/quote | Specific progress; keep unrelated navigation usable |
| Validation issue | Inline field message and focus on first problem; preserve input |
| Not eligible | Reason plus relevant next step: email, limits, account funding or support |
| No supported method/pair | Explain unavailability; let user change the selection |
| Quote expired | Keep input; request a new quote and require fresh confirmation |
| Submission in progress | Prevent duplicate execution and show progress |
| Connection lost after submit | “Checking the outcome”; retrieve by operation/idempotency ID, not a fresh submission |
| Pending external/human processing | Status and next useful action; pending is not failure or abandonment |
| Confirmed failure | Safe reason, support/reference where available; retry only when service permits |
| Confirmed success | Authoritative receipt, refreshed balances/limits and useful next action |
| Session expired | Reauthenticate and rehydrate operation status; do not retain sensitive fields in browser storage |
Close/back preserves an in-memory draft for a clear session-scoped lifetime.
Warn before discarding meaningful recipient/payment input. Quotes always expire
independently of the draft. Hard reload can discard draft input; submitted operations
must survive because they are reloaded from the backend. Logging out clears all
account-scoped providers and drafts to prevent cross-account leakage.
## Activity and operation detail
Activity is the common destination for the outcome of every transaction flow.
Show type, source/destination currencies, meaningful amount(s), time, status and
reference. Exchanges are one parent operation showing both debit and credit, not
two unrelated rows that look like duplicate transactions.
- Filters: all/top-ups/exchanges/payouts, status, currency/account and date range.
Preserve filters when returning from a detail page. Provide Clear filters and
distinguish “no operations yet” from “no matching results” and a load failure.
- Account-detail activity is the same view scoped to that account. Paginate with
stable service cursors; refresh without duplicating rows.
- Detail includes status timeline, amount breakdown, fees, relevant destination,
timestamps, available receipt/reference and service-permitted actions.
- Pending items show what is known and whether the user needs to act. Avoid endless
spinners or a fake percentage for external settlement.
- **Try again** creates a new draft only after confirmed failure and backend
permission; never replays a consequential request from a history button.
## Limits and identity
Reuse the current KYC and usage work ([PR #21](https://git.sendico.io/tech/sxchnge/pulls/21),
[PR #23](https://git.sendico.io/tech/sxchnge/pulls/23)). Limits must show used,
reserved, available and caps for the relevant operation/currency/period. Include
window/reset information only when supplied; do not imply a calendar reset for a
rolling window. Preserve service ordering and applicability.
The full page explains active tier and what verification would unlock. Transaction
forms show the relevant constraint in context and link to that page. A KYC upgrade
must not claim to resolve insufficient funds, an unsupported method or an unrelated
restriction. Submission/pending review does not increase limits; refetch the actual
backend projection after any decision.
## Account settings and language
**Language belongs to the account.** Remove page-level language selectors from the
main workspace and identity pages. Settings → Preferences contains English/Russian
with one clear selected value. Change language without resetting drafts or filters.
- Profile service owns `preferredLocale`; an account provider loads and updates it.
Device storage is a cache/guest fallback, not the source of truth for a signed-in
account. Use an injectable stub until profile endpoints are available.
- On sign-in, the server preference takes precedence. Before account creation,
use browser/device locale plus a remembered guest preference. If a guest needs
a manual override, expose it once through the auth shell's settings/help entry,
not on every login/verification page.
- On save failure, clearly state “Not saved to your account,” retain the choice for
the current session and offer Retry. Do not report a successful account update.
- Server-provided human-readable labels must be fetched for the selected language;
preserve stable IDs and in-progress inputs. Avoid mixed-language stale responses.
- Security settings show only capabilities actually implemented (password change,
sessions, MFA when supported). Do not present decorative toggles as protection.
- Identity status is linked here but managed in the existing verification flow.
Notification preferences require a real preference service; support has a genuine
destination before production. No placeholder action should pretend to complete.
## Service and architecture boundaries
Keep shared DTO → mapper → service → model → provider ownership for future mobile
reuse. Widgets consume provider state; they do not compute financial outcomes.
Proposed domains, not speculative API endpoints:
| Domain service | Owns authoritative data |
|---|---|
| Accounts | Account IDs, currencies, booked/available/held balances, status |
| Funding | Eligible methods, instructions, fees, top-up creation/status |
| Exchange | Pair eligibility, quotes/expiry, idempotent execution |
| Payout | Method/recipient validation, quotes, submission and permissible actions |
| Operations | History, canonical status, references, receipts and reconciliation |
| Limits | Applicable caps, committed/reserved usage, remaining and upgrade projection |
| Profile | Account preferences including language |
Use currency-aware decimal/minor-unit values with currency precision metadata;
never floating-point money or a universal two-decimal assumption. Balance held
amounts and limits reserved usage are different concepts, even when values match.
Backend authorization and limit reservations remain necessary at execution time.
A visible balance, valid quote or frontend KYC state cannot authorize a payment.
For the demo, shared stub state must be coherent across screens: exchange affects
both balances; pending payouts hold capacity; failures release simulated holds
where appropriate; activity and limits reflect the same simulated operation.
Never mix a simulated transaction with a live success claim. Avoid seeded history
that contradicts displayed balances or usage without explaining the fixtures.
## Responsiveness, accessibility and trust
- Mobile: one-column forms and review summaries; no horizontal table required to
complete a task. Wide screens may place quote summary beside input.
- Large values and long beneficiary names wrap safely. Use EN/RU, realistic currency
precision and text scaling in design/test fixtures.
- Monetary fields identify currency and precision; keyboard input accepts the active
locale's decimal separator without silently altering value. Avoid formatting the
value under the user's cursor on every keystroke.
- Provide labeled 48px targets, visible keyboard focus and predictable focus after
validation/navigation. Statuses have text/icons, never color alone.
- Announce meaningful quote/state changes, not every countdown tick. Honor reduced
motion. An expiry countdown accompanies a clear expired state.
- Mask sensitive destinations in lists, reveal enough in explicit review/detail
to verify them. No session replay/autocapture on money, recipient or identity forms.
- Keep unrelated accounts/history accessible during a pending operation. Do not
block the whole product behind an exchange spinner.
## Journey measurement plan
Reuse the typed analytics abstraction and maintain a separate journey per active
transaction draft. Random journey IDs correlate steps; opaque operation IDs link
accepted requests to server outcomes. Retries reuse the journey and idempotency
lineage. Never capture amount, balance, currency-account IDs, recipient, form input,
URLs or raw exceptions in client UX events by default.
| Journey | Start | Useful states | Actual success |
|---|---|---|---|
| Top up | User begins funding | Method ready, instructions/payment ready, accepted/pending, failure | Service-confirmed credit |
| Exchange | User begins exchange | Valid quote, reviewed, accepted/pending, expired/failure | Service-confirmed execution |
| Payout | User begins payout | Valid destination/quote, reviewed, accepted/pending, failure | Contract-defined confirmed payout outcome |
Measure start-to-acceptance and acceptance-to-terminal conversion separately. Use
one count per journey for draft conversion and one per operation for financial
outcomes. Track review-to-submit drop-off, quote expiry, recoverable failure rate,
time to acceptance and server time to terminal outcome. Classify failures with safe
enums, such as insufficient funds, limits, expired quote, unavailable method,
session and service error; emitted events must have a concrete diagnostic use.
An explicit draft discard or 30 minutes of settled inactivity is draft abandonment.
Changing tabs while preserving a draft is not immediate abandonment. Accepted
operations leave the draft abandonment window and remain pending until authoritative
status; app closure must not fabricate failure. Multi-session reconciliation and
server-side outcome events are required before claiming production funnel coverage.
Deduplicate by event/operation identity. Analytics errors must not delay execution.
Account/language preference updates reuse profile outcome signals; do not add
click events for every setting, card or navigation tab. Simulated events are always
flagged and excluded from real transaction/verification conversion dashboards.
## Implementation sequence after approval
1. **Shared workspace foundation:** accounts/operations/profile contracts and coherent
stubs, shell/routes, account overview/detail, Activity/detail, account language
provider/settings. Move language selectors into settings. Preserve auth/email
and existing identity/limits navigation.
2. **Top-up vertical slice:** method/instruction/review/status, balance refresh and
an exchange handoff. Include cancellation, pending and failed outcomes.
3. **Exchange vertical slice:** input/quote/review, expiry, idempotent execution,
coherent balances/history/usage and payout handoff.
4. **Payout vertical slice:** method/destination/review, step-up contract hooks,
reconciliation and confirmed failure/return paths.
5. **Integrated review:** cross-flow drafts, limits/KYC return paths, language changes,
responsive/keyboard/screen-reader checks, shared-service tests and semantic event
assertions. Complete each slice's tests as it is built, not only at the end.
Actual supported currencies/rails, account provisioning, fee/rate policies,
recipient requirements, cancellation and support contracts must come from backend
capabilities. Demo fixtures can illustrate them but must be labeled and isolated.
Real execution is outside this UX-plan task.
## Plan acceptance and validation boundary
The plan connects all requested areas and defines optional KYC, usage, language
ownership, consequential review, state recovery, privacy and measurement. Before
calling implementation complete, walk: new account → top up → exchange → payout →
Activity; pending/unknown outcome recovery; exceeded limits → KYC → resume/requote;
and locale change with a preserved draft. Test stale/out-of-order responses,
repeated submit, expired quote, zero balances, no methods, denied KYC, and logout.
This document is a design deliverable, not evidence that these screens exist or
that accessibility/payment flows have been tested. Existing demo implementation
and pending PRs remain unchanged by this task.