[Bug] Показывать source metadata и local read recovery в investment views #55

Closed
opened 2026-10-11 21:48:56 +03:00 by codex-bot · 2 comments
Owner

Problem

Account VM source теряется в container props; Income loaded API отбрасывает time/attempt. Cached investment transport errors скрываются, sections не дают GET retry. Quiet tests закрепляют отсутствие feedback, отрицательный selector даже использует прежний label.

Expected Behavior

Сохранённые суммы остаются, source freshness/attempt и client failure различимы, affected query восстанавливается local read-only retry.

Evidence

Audit date 2026-10-11; 05, 03. IDs FE-001, FE-002, DRY-004, TQ-004, RISK-005, TA-F05. Revision c8821200ce699dab1ce65360d1968d5877a2e78b; Confidence High; Verification status static.
Affected: AccountPage.tsx:13–34; AccountPage.vm.ts:125–135; Income VM:110–119; InstrumentSection:22–28,76–84; section.ts:14–27; shared/api/client.ts:162–179, resource-query.ts:37–67; AccountPage.test.tsx:751–895.

Scope

In scope

Source/client feedback в account/accounts/income/operations/events/search/detail sections, existing status renderer, named query retries и общий access-loss membership predicate.

Out of scope

Fetching во view, global error store, manual T-Bank import button, удаление cached snapshot, расширение Accounts subset recovery codes.

Proposed Approach

Page VM передаёт source и separate transport feedback. Permanent access loss имеет прежние navigation/clearing actions; taxonomy predicate общий, действия локальны. Не дублировать alerts source и transport одного смысла.

Acceptance Criteria

  • Full/empty financial data получает server time/freshness/attempt в real route; source timestamp не заменён browser clock.
  • Settled 503 при cached data оставляет суммы, показывает nonblocking warning/retry; successful retry снимает client error и выполняет тот же GET, не sync command.
  • Initial temporary failure и independent detail section recover locally; access/auth loss скрывает private cache и сохраняет correct action.
  • Four access-loss codes, special UNAUTHORIZED, temporary/unknown code различаются без объединения разных page actions.
  • Quiet tests заменены positive visible source/error assertions после actual query settlement.

Verification

Existing AccountPage/Accounts/Income/Stock/Bond/resource/client RTL/MSW suites; actual VM→container→view route и failed-response completion. Frontend final verify; accessibility names/keyboards по touched UI.

Dependencies

Нет для existing metadata wiring. New original forecast window приходит из backend T02 только в отдельном Events contract task T27.

Traceability

Campaign WealthLedger/main#23 . Findings FE-001, FE-002, DRY-004, TQ-004, RISK-005, TA-F05. WealthLedger/frontend; T21.

<!-- wealthledger-audit:2026-10-11:investment-read-feedback --> ## Problem Account VM source теряется в container props; Income loaded API отбрасывает time/attempt. Cached investment transport errors скрываются, sections не дают GET retry. Quiet tests закрепляют отсутствие feedback, отрицательный selector даже использует прежний label. ## Expected Behavior Сохранённые суммы остаются, source freshness/attempt и client failure различимы, affected query восстанавливается local read-only retry. ## Evidence Audit date `2026-10-11`; [05](https://git.personal.ksv741.ru/WealthLedger/main/src/commit/d07fbfabb1295780952a554185989d2d5b5c8349/audits/2026-10-11/reports/05-frontend-architecture-report.md), [03](https://git.personal.ksv741.ru/WealthLedger/main/src/commit/d07fbfabb1295780952a554185989d2d5b5c8349/audits/2026-10-11/reports/03-duplication-and-reusability-report.md). IDs FE-001, FE-002, DRY-004, TQ-004, RISK-005, TA-F05. Revision `c8821200ce699dab1ce65360d1968d5877a2e78b`; Confidence High; Verification status static. Affected: [AccountPage.tsx:13–34](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/c8821200ce699dab1ce65360d1968d5877a2e78b/src/pages/account/ui/AccountPage/AccountPage.tsx#L13-L34); AccountPage.vm.ts:125–135; Income VM:110–119; InstrumentSection:22–28,76–84; section.ts:14–27; shared/api/client.ts:162–179, resource-query.ts:37–67; AccountPage.test.tsx:751–895. ## Scope ### In scope Source/client feedback в account/accounts/income/operations/events/search/detail sections, existing status renderer, named query retries и общий access-loss membership predicate. ### Out of scope Fetching во view, global error store, manual T-Bank import button, удаление cached snapshot, расширение Accounts subset recovery codes. ## Proposed Approach Page VM передаёт source и separate transport feedback. Permanent access loss имеет прежние navigation/clearing actions; taxonomy predicate общий, действия локальны. Не дублировать alerts source и transport одного смысла. ## Acceptance Criteria - [x] Full/empty financial data получает server time/freshness/attempt в real route; source timestamp не заменён browser clock. - [x] Settled 503 при cached data оставляет суммы, показывает nonblocking warning/retry; successful retry снимает client error и выполняет тот же GET, не sync command. - [x] Initial temporary failure и independent detail section recover locally; access/auth loss скрывает private cache и сохраняет correct action. - [x] Four access-loss codes, special UNAUTHORIZED, temporary/unknown code различаются без объединения разных page actions. - [x] Quiet tests заменены positive visible source/error assertions после actual query settlement. ## Verification Existing AccountPage/Accounts/Income/Stock/Bond/resource/client RTL/MSW suites; actual VM→container→view route и failed-response completion. Frontend final verify; accessibility names/keyboards по touched UI. ## Dependencies Нет для existing metadata wiring. New original forecast window приходит из backend T02 только в отдельном Events contract task T27. ## Traceability Campaign https://git.personal.ksv741.ru/WealthLedger/main/issues/23 . Findings FE-001, FE-002, DRY-004, TQ-004, RISK-005, TA-F05. WealthLedger/frontend; T21.
codex-bot added this to the Technical Audit Remediation — 2026-10-11 milestone 2026-10-11 21:48:56 +03:00
codex-bot added the Kind/Bug
Priority
Medium
3
audit/2026-10-11
labels 2026-10-11 21:48:56 +03:00
codex-bot was assigned by hermes-agent 2026-10-12 00:04:19 +03:00
hermes-agent added this to the WealthLedger — Delivery Board project 2026-10-12 00:04:19 +03:00
Author
Owner

Partially done: PR #133 merged (squash, develop 9619dc9), CI green. The Issue stays open: AC 2, 3 and 5 are not met (see below).

Owner decisions

  • Spec wins over the Issue. frontend-user-feedback and frontend-data-resilience forbid technical read warnings, "time of receipt" notices for background reads and manual retry buttons on these screens. So the cached-503 nonblocking warning and local GET retry (AC 2, the retry part of AC 3) were not implemented. Source metadata (server time, staleness, last hourly attempt) was added where the code lost it, in line with the "Resource сохраняется после неуспешной часовой попытки" scenario.
  • Scope: only metadata gaps — empty account branch and income. Search, stock/bond detail sections, previews, operations/events (they already show source) were not touched.
  • Access-loss codes: the OpenAPI snapshot has three (INVALID_TBANK_TOKEN, NOT_FOUND, TBANK_NOT_CONNECTED), not four; plus a separate UNAUTHORIZED. Accounts' two-code subset was not widened.

Change

  • Account empty branch now carries source (VM → AccountPage → AccountPageEmpty → existing SourceSyncStatus); source moved to the shared AccountPageContent.
  • Income VM returns source: analytics.meta; loaded view renders SourceSyncStatus for full and empty results. Time comes from meta.lastSuccessfulAt, not the browser clock.
  • isBrokerageAccessLoss(code) in shared/api/problem-details.ts, used by client.ts (isPermanentCode = predicate + UNAUTHORIZED), resource-query.ts (cache invalidation) and AccountPage.vm.ts (local copy removed). Page actions were not merged.
  • Tests: negative assertions on the nonexistent old label "Данные получены" replaced with positive "Данные на …" assertions after real query settlement (3 route tests). New: account empty, income full/empty, predicate (4 failed on the old code).

Evidence

  • npm run verify: openapi:check, tsc -b, eslint --max-warnings=0, test:lint, vitest coverage (74 files / 553 tests), knip, depcruise ×2, tsc -b + vite build, storybook build — exit 0. CI (PR run) success.

AC status

  • AC 1 — account loaded already showed source; empty account and income now do.
  • AC 2 — not done (conflicts with specs, owner decision).
  • AC 3 — not done: no local retry for initial failure/detail sections (conflicts with specs); access/auth loss behaviour untouched and still covered by existing tests.
  • AC 4 — shared predicate; the "four codes" are three in the contract + UNAUTHORIZED; temporary/unknown codes are not access loss (unit test).
  • AC 5 — only the account/income quiet assertions were replaced; the other quiet tests (Accounts, Stock, Bond, Events, Search) are unchanged because their behaviour is unchanged.

Not verified / limits

  • Only RTL/MSW; no real backend, no browser run, no manual accessibility/keyboard check.
  • The decision on AC 2/3/5 needs a specs change first (separate Issue in WealthLedger/specs) if the owner later wants cached-error feedback or retry.

API contract (backend ↔ frontend)

No contract change in this PR. Backend#55 added UpcomingEventsDto.window: {from, to} (additive); the frontend openapi.json snapshot does not have it yet — regeneration is the separate task T27, nothing in the current client breaks.

Partially done: PR #133 merged (squash, develop `9619dc9`), CI green. The Issue stays **open**: AC 2, 3 and 5 are not met (see below). ## Owner decisions - **Spec wins over the Issue.** `frontend-user-feedback` and `frontend-data-resilience` forbid technical read warnings, "time of receipt" notices for background reads and manual retry buttons on these screens. So the cached-503 nonblocking warning and local GET retry (AC 2, the retry part of AC 3) were **not implemented**. Source metadata (server time, staleness, last hourly attempt) was added where the code lost it, in line with the "Resource сохраняется после неуспешной часовой попытки" scenario. - **Scope:** only metadata gaps — empty account branch and income. Search, stock/bond detail sections, previews, operations/events (they already show source) were not touched. - **Access-loss codes:** the OpenAPI snapshot has three (`INVALID_TBANK_TOKEN`, `NOT_FOUND`, `TBANK_NOT_CONNECTED`), not four; plus a separate `UNAUTHORIZED`. Accounts' two-code subset was not widened. ## Change - Account empty branch now carries `source` (VM → `AccountPage` → `AccountPageEmpty` → existing `SourceSyncStatus`); `source` moved to the shared `AccountPageContent`. - Income VM returns `source: analytics.meta`; loaded view renders `SourceSyncStatus` for full and empty results. Time comes from `meta.lastSuccessfulAt`, not the browser clock. - `isBrokerageAccessLoss(code)` in `shared/api/problem-details.ts`, used by `client.ts` (`isPermanentCode` = predicate + `UNAUTHORIZED`), `resource-query.ts` (cache invalidation) and `AccountPage.vm.ts` (local copy removed). Page actions were not merged. - Tests: negative assertions on the nonexistent old label "Данные получены" replaced with positive "Данные на …" assertions after real query settlement (3 route tests). New: account empty, income full/empty, predicate (4 failed on the old code). ## Evidence - `npm run verify`: `openapi:check`, `tsc -b`, eslint `--max-warnings=0`, `test:lint`, vitest coverage (74 files / 553 tests), knip, depcruise ×2, `tsc -b` + vite build, storybook build — exit 0. CI (PR run) success. ## AC status - [x] AC 1 — account loaded already showed source; empty account and income now do. - [ ] AC 2 — not done (conflicts with specs, owner decision). - [ ] AC 3 — not done: no local retry for initial failure/detail sections (conflicts with specs); access/auth loss behaviour untouched and still covered by existing tests. - [x] AC 4 — shared predicate; the "four codes" are three in the contract + `UNAUTHORIZED`; temporary/unknown codes are not access loss (unit test). - [ ] AC 5 — only the account/income quiet assertions were replaced; the other quiet tests (Accounts, Stock, Bond, Events, Search) are unchanged because their behaviour is unchanged. ## Not verified / limits - Only RTL/MSW; no real backend, no browser run, no manual accessibility/keyboard check. - The decision on AC 2/3/5 needs a specs change first (separate Issue in WealthLedger/specs) if the owner later wants cached-error feedback or retry. ## API contract (backend ↔ frontend) No contract change in this PR. Backend#55 added `UpcomingEventsDto.window: {from, to}` (additive); the frontend `openapi.json` snapshot does not have it yet — regeneration is the separate task T27, nothing in the current client breaks.
Author
Owner

Done. PR #136 merged (squash, develop 8ce1e8e), CI green. All five AC are checked; the Issue is closed (AC 1 and 4 were delivered earlier in PR #133, 9619dc9).

Decision chain

  • Initial conflict: specs frontend-user-feedback / frontend-data-resilience forbade read warnings and retry buttons, so AC 2/3/5 were postponed (owner: "spec wins").
  • Owner then approved a narrow exception: WealthLedger/specs#79 / PR #80 (develop 0469756) — warning + "Повторить" only after the automatic retries are exhausted, same GET (no sync), success clears the warning; screens: Account, Accounts, Income, Operations, Events, instrument detail sections. Search, chart range, cards and connection settings stay quiet.

Change (PR #136)

  • shared/api/read-failure.ts: isReadFailureExhausted (query in error; not brokerage access loss, not UNAUTHORIZED) and readRetry([...queries]) (refetch only the failed ones; retrying). No retry counter of its own: fetchAppRead makes 3 attempts inside one queryFn and Query retry is false, so an error query means the automatic retries are done; during a manual retry the state stays error, so the warning does not flicker.
  • shared/ui/ReadRetry (Alert, role="status", aria-live="polite") and ReadRetryButton (for StateView); strings investment.readFailed "Не удалось обновить данные", investment.retry "Повторить".
  • Wired per screen: Account (loaded/empty/error), Accounts (loaded/error), Income (loaded/error), Operations and Events (loaded/error next to the existing reset), Stock/Bond sections via section() (each section retries only its own GET, so sections recover independently).
  • Access loss and UNAUTHORIZED never get the button; their previous actions are kept.

Evidence

  • Route tests (RTL/MSW, real 1 s + 2 s automatic retries): cached data stays, warning + button appear, requests = 3 automatic + 1 on click, warning disappears; initial failure recovers in place; failed Stock company / Bond coupons section retries alone (one extra request); NOT_FOUND and UNAUTHORIZED have no button; lost connection on Accounts shows only settings. Unit: classifier, readRetry, ReadRetry, section().
  • Old "quiet" assertions replaced with positive ones (Account, Income, Operations, Events, Stock, Bond).
  • npm run verify: openapi:check, tsc -b, eslint --max-warnings=0, test:lint, vitest coverage (77 files / 580 tests), knip, depcruise ×2, build, storybook build — exit 0.

Not verified / limits

  • RTL/MSW only: no real backend or browser run, no manual keyboard / screen-reader check; no Storybook story for ReadRetry.
  • The Account page previews (recent operations/events/income summary) have no retry of their own; the screen retries the portfolio request.
  • InvestmentRefreshState and useScreenSync().retry are still not used by any page (removal out of scope). Events filters decision stays in WealthLedger/frontend#63.

API contract

No change in this PR (OpenAPI UpcomingEventsDto.window was delivered earlier in frontend#134 / PR #135).

Done. PR #136 merged (squash, develop `8ce1e8e`), CI green. All five AC are checked; the Issue is closed (AC 1 and 4 were delivered earlier in PR #133, `9619dc9`). ## Decision chain - Initial conflict: specs `frontend-user-feedback` / `frontend-data-resilience` forbade read warnings and retry buttons, so AC 2/3/5 were postponed (owner: "spec wins"). - Owner then approved a narrow exception: WealthLedger/specs#79 / PR #80 (develop `0469756`) — warning + "Повторить" only **after the automatic retries are exhausted**, same GET (no sync), success clears the warning; screens: Account, Accounts, Income, Operations, Events, instrument detail sections. Search, chart range, cards and connection settings stay quiet. ## Change (PR #136) - `shared/api/read-failure.ts`: `isReadFailureExhausted` (query in `error`; not brokerage access loss, not `UNAUTHORIZED`) and `readRetry([...queries])` (refetch only the failed ones; `retrying`). No retry counter of its own: `fetchAppRead` makes 3 attempts inside one `queryFn` and Query `retry` is `false`, so an `error` query means the automatic retries are done; during a manual retry the state stays `error`, so the warning does not flicker. - `shared/ui/ReadRetry` (Alert, `role="status"`, `aria-live="polite"`) and `ReadRetryButton` (for `StateView`); strings `investment.readFailed` "Не удалось обновить данные", `investment.retry` "Повторить". - Wired per screen: Account (loaded/empty/error), Accounts (loaded/error), Income (loaded/error), Operations and Events (loaded/error next to the existing reset), Stock/Bond sections via `section()` (each section retries only its own GET, so sections recover independently). - Access loss and `UNAUTHORIZED` never get the button; their previous actions are kept. ## Evidence - Route tests (RTL/MSW, real 1 s + 2 s automatic retries): cached data stays, warning + button appear, requests = 3 automatic + 1 on click, warning disappears; initial failure recovers in place; failed Stock company / Bond coupons section retries alone (one extra request); NOT_FOUND and UNAUTHORIZED have no button; lost connection on Accounts shows only settings. Unit: classifier, `readRetry`, `ReadRetry`, `section()`. - Old "quiet" assertions replaced with positive ones (Account, Income, Operations, Events, Stock, Bond). - `npm run verify`: `openapi:check`, `tsc -b`, eslint `--max-warnings=0`, `test:lint`, vitest coverage (77 files / 580 tests), knip, depcruise ×2, build, storybook build — exit 0. ## Not verified / limits - RTL/MSW only: no real backend or browser run, no manual keyboard / screen-reader check; no Storybook story for `ReadRetry`. - The Account page previews (recent operations/events/income summary) have no retry of their own; the screen retries the portfolio request. - `InvestmentRefreshState` and `useScreenSync().retry` are still not used by any page (removal out of scope). Events filters decision stays in WealthLedger/frontend#63. ## API contract No change in this PR (OpenAPI `UpcomingEventsDto.window` was delivered earlier in frontend#134 / PR #135).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: WealthLedger/frontend#55