Files
specs/openspec/changes/archive/2026-08-26-improve-ledger-event-rows/design.md
T

8.3 KiB
Raw Blame History

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

  1. Introduce the shared resolver and the nullable response metadata while preserving the operations payload and failure behaviour.
  2. Publish and sync the generated OpenAPI contract, then consume it in the frontend presentation boundary.
  3. 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.