feat: add quiet reading with rare confirmations

Part of the quiet-user-feedback change; docs and specs land in their own repositories.
This commit is contained in:
2026-10-06 07:01:58 +03:00
parent c096fc70ef
commit 5b30bb802a
77 changed files with 1352 additions and 1259 deletions
@@ -86,7 +86,7 @@ const InstrumentEvents: FC<InstrumentEventsProps> = (props) => {
<Group justify="space-between" mt="md">
<Button
disabled={!vm.hasPrevious || vm.operationsState.refreshing}
disabled={!vm.hasPrevious}
onClick={vm.onPrevious}
variant="default"
>
@@ -94,7 +94,7 @@ const InstrumentEvents: FC<InstrumentEventsProps> = (props) => {
</Button>
<Button
disabled={!vm.operations.hasNext || vm.operationsState.refreshing}
disabled={!vm.operations.hasNext}
onClick={vm.onNext}
variant="default"
>
@@ -1,21 +1,17 @@
import type { FC, PropsWithChildren, ReactNode } from "react";
import { Avatar, Button, Group, Paper, Text, Title } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { Avatar, Group, Paper, Text, Title } from "@mantine/core";
import classes from "./InstrumentHeader.module.css";
type InstrumentHeaderProps = PropsWithChildren<{
logoSrc: null | string;
name: string;
onRefresh: () => void;
refreshing: boolean;
subtitle: ReactNode;
}>;
const InstrumentHeader: FC<InstrumentHeaderProps> = (props) => {
const { children, logoSrc, name, onRefresh, refreshing, subtitle } = props;
const { t } = useTranslation();
const { children, logoSrc, name, subtitle } = props;
return (
<Paper className={classes.header} component="header" withBorder>
@@ -31,10 +27,6 @@ const InstrumentHeader: FC<InstrumentHeaderProps> = (props) => {
<Text c="dimmed" size="sm">{subtitle}</Text>
</div>
</div>
<Button disabled={refreshing} onClick={onRefresh} variant="subtle">
{t("investment.refresh")}
</Button>
</Group>
{children}
@@ -1,6 +1,6 @@
import type { FC } from "react";
import { Anchor, Button, Skeleton, Stack, Text, Title } from "@mantine/core";
import { Anchor, Skeleton, Stack, Text, Title } from "@mantine/core";
import { LoadingSkeleton } from "@wealthledger/ui";
import { useTranslation } from "react-i18next";
import { Link } from "react-router";
@@ -11,11 +11,10 @@ type InstrumentPageStateProps = {
code?: string;
kind: "bond" | "stock";
loading: boolean;
retry: () => void;
};
const InstrumentPageState: FC<InstrumentPageStateProps> = (props) => {
const { code, kind, loading, retry } = props;
const { code, kind, loading } = props;
const { t } = useTranslation();
if (loading) return (
<LoadingSkeleton label={t(`${kind}.loading`)}>
@@ -55,10 +54,6 @@ const InstrumentPageState: FC<InstrumentPageStateProps> = (props) => {
<Anchor component={Link} to="/settings">
{t("accounts.openSettings")}
</Anchor>
<Button onClick={retry} variant="default">
{t("accounts.retry")}
</Button>
</Stack>
);
};
@@ -2,7 +2,6 @@ import type { FC, PropsWithChildren, ReactNode } from "react";
import {
Alert,
Button,
Group,
Paper,
Skeleton,
@@ -13,8 +12,6 @@ import {
import { LoadingSkeleton } from "@wealthledger/ui";
import { useTranslation } from "react-i18next";
import { SourceSyncStatus } from "@/shared/ui";
import type { SectionState } from "../model/section";
import classes from "./InstrumentDetails.module.css";
@@ -33,10 +30,6 @@ type InstrumentSectionProps = PropsWithChildren<{
const InstrumentSection: FC<InstrumentSectionProps> = (props) => {
const { t } = useTranslation();
const readError = t(
props.state.hasData ? "accounts.refreshError" : "stock.sectionError",
);
const sectionClassName = props.embedded
? classes.embeddedSection
: classes.section;
@@ -80,35 +73,11 @@ const InstrumentSection: FC<InstrumentSectionProps> = (props) => {
{props.controls}
</Group>
<SourceSyncStatus
lastSuccessfulAt={props.state.lastSuccessfulAt}
stale={props.state.stale}
/>
{!!props.state.partial && <Text role="status">{t("investment.partial")}</Text>}
{!!props.state.refreshing && (
<Text c="dimmed" role="status" size="xs">
{t("accounts.refreshing")}
</Text>
)}
{!!props.state.error && (
{!!(props.state.error && !props.state.hasData) && (
<Alert aria-live="polite" color="orange" mb="sm" role="status">
<Group justify="space-between">
<Text size="sm">
{props.state.sourceError ? t("investment.syncFailed") : readError}
</Text>
<Button
disabled={props.state.refreshing}
onClick={props.state.retry}
size="xs"
variant="default"
>
{t("accounts.retry")}
</Button>
</Group>
<Text size="sm">{t("stock.sectionError")}</Text>
</Alert>
)}