fix(tradein/v2): audit #2081 residual — 404 short-circuit, disabled-tab tooltips, stale hex token, ДКП decode
All checks were successful
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m5s

L3: sibling dashboard hooks (analytics/location-coef/placement-history/
sell-time) now short-circuit once restore-by-id confirms 404, instead
of each firing their own doomed request against the same dead
estimate id.
L5: dimmed non-interactive user-menu items (Профиль/Настройки/Помощь)
get a hover title explaining why they're disabled.
L7: TopNav logo subtitle used a stale literal #6f8195 predating the
C1 contrast sweep; now references tokens.muted.
M12: HistoryView's "ДКП-сделки" card gets the same one-time ДКП decode
convention ObjectSummary already uses.

H2/M8/M9/M10/M11 re-verified live/by-code — already correct, no
changes needed (see issue comment). L4 (HistoryView outlier clamp)
and L6 (blueprint-grid background-image) left as follow-ups
(mapper-level change / no contrast payoff respectively).

Refs #2081
This commit is contained in:
bot-backend 2026-07-04 10:32:43 +03:00
parent 2538fc02e2
commit 08cedad1bf
3 changed files with 50 additions and 11 deletions

View file

@ -439,9 +439,24 @@ export default function TradeInV2Page() {
// Skip the restore fetch once we already hold a fresh result.
const restored = useEstimate(freshResult === null ? urlId : null);
// L3 — once the restore-by-id fetch has confirmed the estimate does not
// exist (404), `currentEstimateId` below drops to null so the sibling
// dashboard hooks (analytics/location-coef/placement-history/sell-time,
// all `enabled: estimate_id !== null`) don't each fire their own doomed
// request against the same dead id. Hoisted above the sub-hooks (was
// computed further down, after they'd already fired on the stale id).
const restoreActive = urlId !== null && freshResult === null;
const restoreError = restoreActive && restored.isError;
const restoreNotFound =
restoreError &&
restored.error instanceof HTTPError &&
restored.error.status === 404;
const estimate: AggregatedEstimate | null =
freshResult ?? restored.data ?? null;
const currentEstimateId = freshResult?.estimate_id ?? urlId;
const currentEstimateId = restoreNotFound
? null
: (freshResult?.estimate_id ?? urlId);
// Per-user side data, independent of the current estimate. Both fail-soft
// (retry:false): history feeds the «Предыдущие оценки» overlay (04), quota
@ -486,13 +501,7 @@ export default function TradeInV2Page() {
const sellTimeData = sellTime.data ?? null;
// ── Derived state flags ───────────────────────────────────────────────────
const restoreActive = urlId !== null && freshResult === null;
const restoreLoading = restoreActive && restored.isPending;
const restoreError = restoreActive && restored.isError;
const restoreNotFound =
restoreError &&
restored.error instanceof HTTPError &&
restored.error.status === 404;
const loading = mutation.isPending || restoreLoading;
const insufficient =

View file

@ -175,6 +175,19 @@ export default function HistoryView({
>
{data.dkpHeader.title}
</div>
{/* M12 расшифровка «ДКП» один раз на секцию, тем же lowercase
hint-стилем что ObjectSummary.rowHint() (fontSize 8/letterSpacing
0.2/muted3), а не uppercase ResultPanel §M8 стилем. */}
<div
style={{
fontSize: "8px",
letterSpacing: 0.2,
color: tokens.muted3,
marginTop: 2,
}}
>
ДКП · договор купли-продажи
</div>
</div>
<div
style={{

View file

@ -156,7 +156,9 @@ export default function TopNav({
fontSize="27"
fontWeight="400"
letterSpacing="11"
fill="#6f8195"
// L7 — stale literal from before the C1 contrast sweep darkened
// the muted scale; token keeps this logo subtitle in sync.
fill={tokens.muted}
>
ОЦЕНКА НЕДВИЖИМОСТИ
</text>
@ -370,7 +372,14 @@ export default function TopNav({
</div>
</div>
<div style={menuItemDisabledStyle} aria-disabled="true">
{/* L5 `title` gives the dimmed/non-interactive item a hover
tooltip explaining WHY it's disabled, instead of a silent dead
row (mirrored on Настройки/Помощь below). */}
<div
style={menuItemDisabledStyle}
aria-disabled="true"
title="Раздел «Профиль» скоро появится"
>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" aria-hidden="true">
<circle
cx="7.5"
@ -412,7 +421,11 @@ export default function TopNav({
</span>
</div>
<div style={menuItemDisabledStyle} aria-disabled="true">
<div
style={menuItemDisabledStyle}
aria-disabled="true"
title="Раздел «Настройки» скоро появится"
>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" aria-hidden="true">
<circle
cx="7.5"
@ -430,7 +443,11 @@ export default function TopNav({
Настройки
</div>
<div style={menuItemDisabledStyle} aria-disabled="true">
<div
style={menuItemDisabledStyle}
aria-disabled="true"
title="Раздел «Помощь» скоро появится"
>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" aria-hidden="true">
<circle
cx="7.5"