ui/phase-25: backend turn-cutoff guard + auto-pause + UI sync protocol

Backend now owns the turn-cutoff and pause guards the order tab
relies on: the scheduler flips runtime_status between
generation_in_progress and running around every engine tick, a
failed tick auto-pauses the game through OnRuntimeSnapshot, and a
new game.paused notification kind fans out alongside
game.turn.ready. The user-games handlers reject submits with
HTTP 409 turn_already_closed or game_paused depending on the
runtime state.

UI delegates auto-sync to a new OrderQueue: offline detection,
single retry on reconnect, conflict / paused classification.
OrderDraftStore surfaces conflictBanner / pausedBanner runes,
clears them on local mutation or on a game.turn.ready push via
resetForNewTurn. The order tab renders the matching banners and
the new conflict per-row badge; i18n bundles cover en + ru.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-11 22:00:16 +02:00
parent bbdcc36e05
commit 2ca47eb4df
35 changed files with 2539 additions and 143 deletions
+7
View File
@@ -129,13 +129,20 @@ const ru: Record<keyof typeof en, string> = {
"game.sidebar.order.sync.in_flight": "синхронизация…",
"game.sidebar.order.sync.synced": "сохранено на сервере",
"game.sidebar.order.sync.error": "ошибка синхронизации: {message}",
"game.sidebar.order.sync.offline": "очередь — нет связи, повторим при восстановлении",
"game.sidebar.order.sync.conflict": "ход закрылся до отправки",
"game.sidebar.order.sync.paused": "игра на паузе — приказы не принимаются",
"game.sidebar.order.sync.retry": "повторить",
"game.sidebar.order.conflict.banner": "Ход {turn} закрылся до того, как приказ был принят. Отредактируй и отправь ещё раз.",
"game.sidebar.order.conflict.banner_no_turn": "Ход закрылся до того, как приказ был принят. Отредактируй и отправь ещё раз.",
"game.sidebar.order.paused.banner": "Игра на паузе. Приказы не принимаются, пока она не возобновится.",
"game.sidebar.order.status.draft": "черновик",
"game.sidebar.order.status.valid": "готова",
"game.sidebar.order.status.invalid": "ошибка",
"game.sidebar.order.status.submitting": "отправка",
"game.sidebar.order.status.applied": "принята",
"game.sidebar.order.status.rejected": "отклонена",
"game.sidebar.order.status.conflict": "конфликт",
"game.sidebar.order.label.placeholder": "{label}",
"game.sidebar.order.label.planet_rename": "переименовать планету {planet} → {name}",
"game.sidebar.order.label.planet_production": "сменить производство планеты {planet} → {target}",