diff --git a/tradein-mvp/frontend/src/app/v2/__tests__/historyClickAfterSubmit.test.tsx b/tradein-mvp/frontend/src/app/v2/__tests__/historyClickAfterSubmit.test.tsx
new file mode 100644
index 00000000..e7d36a7d
--- /dev/null
+++ b/tradein-mvp/frontend/src/app/v2/__tests__/historyClickAfterSubmit.test.tsx
@@ -0,0 +1,121 @@
+/**
+ * #2425 — клик по строке «Предыдущие оценки» после отправки оценки в той же
+ * SPA-сессии должен показать ВЫБРАННУЮ запись, а не только что посчитанную.
+ *
+ * Раньше `freshResult` оставался выставленным: `useEstimate` получал null
+ * (восстановление по id не запускалось вовсе), а экран продолжал показывать
+ * свежий результат. Проверяется по значению: какой id ушёл в `useEstimate`,
+ * какой id получил HeroBar и чей адрес лёг в форму.
+ */
+import { fireEvent, render, screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+
+import { FIXTURE_ESTIMATE } from "@/app/ui-preview/estimate/fixture";
+import type { AggregatedEstimate } from "@/types/trade-in";
+
+const FRESH: AggregatedEstimate = {
+ ...FIXTURE_ESTIMATE,
+ estimate_id: "fresh-1",
+ target_address: "Екатеринбург, ул. Свежая, 1",
+};
+const HIST: AggregatedEstimate = {
+ ...FIXTURE_ESTIMATE,
+ estimate_id: "hist-1",
+ target_address: "Екатеринбург, ул. Историческая, 2",
+};
+
+const useEstimateMock = vi.hoisted(() => vi.fn());
+const router = vi.hoisted(() => ({ replace: vi.fn(), push: vi.fn() }));
+
+vi.mock("next/navigation", () => ({ useRouter: () => router }));
+vi.mock("@tanstack/react-query", () => ({
+ useQueryClient: () => ({ invalidateQueries: vi.fn() }),
+}));
+
+vi.mock("@/lib/trade-in-api", () => {
+ const empty = () => ({ data: undefined });
+ return {
+ useEstimate: useEstimateMock,
+ useEstimateMutation: () => ({
+ mutate: (_input: unknown, opts: { onSuccess: (e: AggregatedEstimate) => void }) =>
+ opts.onSuccess(FRESH),
+ isPending: false,
+ error: null,
+ }),
+ useEstimateHistory: empty,
+ useEstimateHouseAnalytics: empty,
+ useEstimatePlacementHistory: empty,
+ useEstimateSellTimeSensitivity: empty,
+ useLocationIndex: empty,
+ useSalesVsListings: empty,
+ useStreetDeals: empty,
+ };
+});
+vi.mock("@/lib/useQuota", () => ({ useQuota: () => ({ data: undefined }) }));
+vi.mock("@/lib/useMe", () => ({ useMe: () => ({ data: undefined }) }));
+vi.mock("@/lib/useLogout", () => ({ useLogout: () => ({ mutate: vi.fn() }) }));
+
+// Дочерние компоненты — заглушки: проверяется только, что страница им отдаёт.
+vi.mock("@/components/trade-in/v2/TopNav", () => ({
+ default: ({ onNavigate }: { onNavigate: (n: number) => void }) => (
+
+ ),
+}));
+vi.mock("@/components/trade-in/v2/HeroBar", () => ({
+ default: ({ estimateId }: { estimateId: string | null }) => (
+
{estimateId ?? ""}
+ ),
+}));
+vi.mock("@/components/trade-in/v2/ParamsPanel", () => ({
+ default: ({
+ onSubmit,
+ initialValues,
+ }: {
+ onSubmit: (i: unknown) => void;
+ initialValues?: { address?: string };
+ }) => (
+
+
{initialValues?.address ?? ""}
+
+
+ ),
+}));
+vi.mock("@/components/trade-in/v2/SectionOverlay", () => ({
+ default: ({ onSelectEstimate }: { onSelectEstimate: (id: string) => void }) => (
+
+ ),
+}));
+vi.mock("@/components/trade-in/v2/ResultPanel", () => ({ default: () => null }));
+vi.mock("@/components/trade-in/v2/LowConfidenceBanner", () => ({
+ LowConfidenceBanner: () => null,
+}));
+vi.mock("@/components/trade-in/v2/ObjectSummary", () => ({ ObjectSummary: () => null }));
+vi.mock("@/components/trade-in/v2/LeadForm", () => ({ LeadForm: () => null }));
+vi.mock("@/components/trade-in/v2/Footer", () => ({ Footer: () => null }));
+vi.mock("@/components/trade-in/v2/LocationDrawer", () => ({ LocationDrawer: () => null }));
+
+import TradeInV2Page from "../page";
+
+describe("#2425 — строка истории после submit", () => {
+ it("клик по другой оценке показывает её, а не только что посчитанную", () => {
+ useEstimateMock.mockImplementation((id: string | null) =>
+ id === "hist-1"
+ ? { data: HIST, isPending: false, isError: false, error: null }
+ : { data: undefined, isPending: id !== null, isError: false, error: null },
+ );
+
+ render();
+
+ fireEvent.click(screen.getByText("submit"));
+ expect(screen.getByTestId("hero-id")).toHaveTextContent("fresh-1");
+ expect(screen.getByTestId("form-address")).toHaveTextContent("ул. Свежая, 1");
+
+ fireEvent.click(screen.getByText("open-history"));
+ fireEvent.click(screen.getByText("row-hist-1"));
+
+ expect(useEstimateMock).toHaveBeenLastCalledWith("hist-1");
+ expect(router.replace).toHaveBeenLastCalledWith("/v2?id=hist-1", { scroll: false });
+ expect(screen.getByTestId("hero-id")).toHaveTextContent("hist-1");
+ expect(screen.getByTestId("form-address")).toHaveTextContent("ул. Историческая, 2");
+ });
+});
diff --git a/tradein-mvp/frontend/src/app/v2/page.tsx b/tradein-mvp/frontend/src/app/v2/page.tsx
index 4917d454..2c7a7b2a 100644
--- a/tradein-mvp/frontend/src/app/v2/page.tsx
+++ b/tradein-mvp/frontend/src/app/v2/page.tsx
@@ -1190,6 +1190,11 @@ export default function TradeInV2Page() {
// hands `urlId` the correct target id, mirroring how
// `freshResult` already overrides this same race for the
// post-submit path (handleSubmit above).
+ //
+ // #2425: a fresh submit and restore-by-id are mutually exclusive
+ // both ways (as in handleNew) — a leftover freshResult would
+ // disable useEstimate(urlId) and keep shadowing the clicked row.
+ setFreshResult(null);
setPendingRestoreId(id);
router.replace(`/v2?id=${id}`, { scroll: false });
setNav(0);