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:
@@ -60,6 +60,10 @@ func (h *UserGamesHandlers) Commands() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
ctx := c.Request.Context()
|
||||
if err := h.runtime.CheckOrdersAccept(ctx, gameID); err != nil {
|
||||
respondGameProxyError(c, h.logger, "user games commands", ctx, err)
|
||||
return
|
||||
}
|
||||
mapping, err := h.runtime.ResolvePlayerMapping(ctx, gameID, userID)
|
||||
if err != nil {
|
||||
respondGameProxyError(c, h.logger, "user games commands", ctx, err)
|
||||
@@ -105,6 +109,10 @@ func (h *UserGamesHandlers) Orders() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
ctx := c.Request.Context()
|
||||
if err := h.runtime.CheckOrdersAccept(ctx, gameID); err != nil {
|
||||
respondGameProxyError(c, h.logger, "user games orders", ctx, err)
|
||||
return
|
||||
}
|
||||
mapping, err := h.runtime.ResolvePlayerMapping(ctx, gameID, userID)
|
||||
if err != nil {
|
||||
respondGameProxyError(c, h.logger, "user games orders", ctx, err)
|
||||
@@ -257,6 +265,12 @@ func respondGameProxyError(c *gin.Context, logger *zap.Logger, op string, ctx co
|
||||
switch {
|
||||
case errors.Is(err, runtime.ErrNotFound):
|
||||
httperr.Abort(c, http.StatusNotFound, httperr.CodeNotFound, "no runtime mapping for this user/game")
|
||||
case errors.Is(err, runtime.ErrTurnAlreadyClosed):
|
||||
httperr.Abort(c, http.StatusConflict, httperr.CodeTurnAlreadyClosed,
|
||||
"turn already closed; orders are not accepted while the engine is producing")
|
||||
case errors.Is(err, runtime.ErrGamePaused):
|
||||
httperr.Abort(c, http.StatusConflict, httperr.CodeGamePaused,
|
||||
"game is paused; orders are not accepted until it resumes")
|
||||
case errors.Is(err, runtime.ErrConflict):
|
||||
httperr.Abort(c, http.StatusConflict, httperr.CodeConflict, err.Error())
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user