8.3 KiB
Context
Account Ledger already preserves the signed upstream payment and the operation identifiers, but the operations response has no instrument brand metadata. The portfolio resolves the same metadata from the existing server-side T-Bank boundary and uses the brand file name to render an image with an initial-based fallback. The account overview and the full ledger currently render separate, text-heavy operation layouts; the full ledger also keeps page data and JSX in one component instead of the finance view-model boundary.
The public OpenAPI contract is the frontend authority. T-Bank rate limiting, authentication and upstream failure handling remain owned by the backend.
Goals / Non-Goals
Goals:
- Supply nullable instrument brand metadata with each Ledger Entry without altering its monetary or business meaning.
- Reuse one accessible, responsive presentation of a Ledger Entry in the account overview and full ledger.
- Present the already-signed payment with a textual directional cue, a sign and semantic positive/negative/neutral styling.
- Preserve local error, loading, empty, filter and cursor states.
Non-Goals:
- No new ledger categories, financial calculations, historical balance or currency conversion.
- No changes to the T-Bank integration boundary, access rules, pagination or query semantics.
- No logo enrichment for Future Cash Events or Portfolio Positions outside the existing supplied metadata.
Decisions
Enrich Ledger Entries through a shared instrument-metadata resolver
The backend will extract the existing Portfolio instrument lookup and its
short-lived metadata caching into a reusable resolver. After obtaining a ledger
page, the service will resolve unique operation instrument identities through
that resolver and attach nullable logoName metadata to its entries. Lookup by
UID remains preferred; FIGI is the fallback. An unavailable or unresolvable
instrument remains a successful Ledger Entry with logoName: null; enrichment
must not hide a ledger page or change its money, category or identifiers.
This preserves the existing server-side T-Bank boundary and its rate limiting, deduplicates repeated instruments in one page and avoids a separate visual-data API. The shared resolver replaces Portfolio's private copy so the two consumers have identical lookup and failure semantics.
Make the additive contract explicit and nullable
BrokerageOperationDto gains the nullable logo file name as presentation
metadata. The generated backend OpenAPI and committed frontend snapshot remain
the only contract path. Existing consumers can ignore the additive field;
updated consumers must treat absence as normal and use the text fallback.
Use a shared Ledger Entry presentation component
The frontend will expose a focused entity-level presentation component for a Ledger Entry and use it both in the account overview and the full journal. It will show the logo image when available, otherwise a deterministic initial fallback; the visible operation and instrument text remains the primary label. The existing image URL formatter is reused. Logo loading failure falls back in place without moving the row.
The full ledger page is brought to the existing container → ViewModel → view
boundary while retaining its current query and interaction state. The ViewModel
prepares collection-level presentation data; the shared row owns only one
entry's display. This prevents duplication while keeping data fetching outside
presentation views.
Convey money direction independently of colour
The row derives direction only from the sign already present in payment.value:
positive is a labelled + «Поступление», negative is a labelled − «Списание»,
and missing payment is the existing neutral «Без движения денег» state. Semantic
Mantine theme tokens supply positive, negative and neutral tones; the sign and
visible Russian label make the state understandable without colour. Amounts
remain formatted from their decimal string and are not converted to JavaScript
numbers for the direction decision.
The row uses a three-part hierarchy: instrument identity on the left, operation and date/category as secondary metadata, and a right-aligned amount/direction group. At narrow widths the metadata may wrap below the primary line while the amount stays associated with its entry; the list never requires horizontal scrolling.
Delivery Map
| Area | Status | Architectural outcome or N/A reason |
|---|---|---|
| frontend | affected |
Generated contract update, reusable accessible Ledger Entry row, and aligned overview/full-ledger layouts through the finance VM/view boundary. |
| backend | affected |
Shared server-side instrument metadata resolver and nullable logo metadata on each operations response. |
| specs | affected |
Approved delta requirements become the versioned behavioural contract. |
| docs | affected |
Account Ledger as-built documentation describes the delivered metadata and directional row semantics. |
Contract Gate Decision
| Gate | Status | Reason |
|---|---|---|
| runtime-openapi | required |
Backend adds public response metadata and frontend consumes the generated contract. |
Solution Assessment
The change extends an existing external T-Bank metadata lookup and introduces a shared cache consumer, so it evaluates the available approaches.
| Option | Fit and maturity | Security and operating cost | Decision |
|---|---|---|---|
| Resolve every operation independently | Uses existing client but repeats calls for the same instrument and exposes journal latency to upstream volume. | Rate limiter prevents excess but adds queueing and avoidable upstream load. | Rejected. |
| Reuse Portfolio's private cache by duplicating it in the ledger | Works locally but creates divergent TTL, failure and fallback behaviour. | Low initial cost, growing maintenance and inconsistent cache pressure. | Rejected. |
| Extract a bounded, short-lived shared resolver around the existing client | Reuses mature authenticated boundary and gives both consumers one lookup policy. | No new credential or storage; cached public brand metadata is returned only after account access. Bounded memory and non-fatal failures limit operating risk. | Chosen. |
No third-party package or new external integration is justified: the T-Bank client already supplies the required brand metadata and resilience controls.
Risks / Trade-offs
- A page with many distinct instruments can still need several metadata lookups on a cold cache. Deduplication, bounded cache lifetime and the existing limiter trade initial completeness latency for upstream protection.
- Some past operations do not identify an instrument or have no brand asset. The mandatory text fallback keeps these rows legible without inventing data.
- An additive nullable OpenAPI field requires atomic runtime-contract and frontend-snapshot alignment. Until the compatible frontend is delivered, old consumers safely ignore the field.
- Green/red alone can exclude users; sign and text are required alongside semantic colours, and images remain supplementary to visible text.
Migration Plan
- Introduce the shared resolver and the nullable response metadata while preserving the operations payload and failure behaviour.
- Publish and sync the generated OpenAPI contract, then consume it in the frontend presentation boundary.
- Replace the duplicated overview and journal row rendering with the shared responsive component and update the as-built feature documentation after verification.
Rollback Contract
| Field | Decision |
|---|---|
| Status | required |
| Reason | The change adds a public API response field consumed by the frontend. |
| Trigger | Runtime OpenAPI mismatch, unacceptable additional ledger latency, or a regression that hides ledger entries. |
| Actions | Revert the frontend to the prior row presentation and revert the backend response enrichment; retain the pre-change response shape when rollback is complete. |
| Data safety | No persistent data, migration or credential changes occur. In-memory metadata and page caches can expire or be discarded safely. |
| Verification | Re-run affected canonical gates, the runtime OpenAPI contract gate, and the ledger scenarios for signed, unsigned and missing-logo entries. |