Живая проверка в браузере (клик по строке "Предыдущие оценки" на проде)
подтвердила: router.replace() — асинхронный client-nav (RSC round-trip),
window.location.search не обновляется синхронно, а readUrlId() читает
window.location.search напрямую (useSearchParams намеренно не используется —
ломает next build через Suspense). Единственный надёжный источник urlId
в синхронном ре-рендере — override-state, выставленный в том же тике,
что и клик.
Предыдущая версия (`pendingRestoreId ?? readUrlId()`) использовала null
для двух разных смыслов сразу: "нет override" и "принудительно пусто".
handleNew() выставлял null именно во втором смысле (сброс после 404),
но ?? тут же трактовал null как "нет override" и падал обратно на
readUrlId() — который на тот момент ещё возвращал старый плохой id
(router.replace("/v2") ещё не успел домотать). Итог: "НОВАЯ ОЦЕНКА"
после битой ссылки могла не сбрасывать ошибку.
Три состояния убирают двусмысленность: undefined = нет override (readUrlId
рулит), null = принудительно пусто, string = override на конкретный id.