Партия: совместное заполнение формы завершения на клиенте

Раскладка, комментарии, причина победы и комментарий о партии уходят в общий
черновик через ~0.6с после последнего действия, а чужие правки подтягиваются по
SSE. Чужой черновик применяется, только если с момента моего последнего действия
прошло больше 1.5с — иначе правка соседа перетирала бы тайл прямо под рукой.

Перед «Завершить» отложенная запись дожимается: иначе последняя правка попала бы
в результаты, но не в черновик, и второй участник увидел бы не то, что записалось.
Само завершение по-прежнему шлёт тело запроса, так что работает и без черновика.

Под подсказкой о перетаскивании появилась строка «Результаты заполняет также
<Ник>», а если партию завершил кто-то другой — тост вместо молча исчезающей формы.

В админке у идущей партии вместо формы правки — пояснение и переход на страницу
партии: сервер её результаты всё равно не примет.

#28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoiJK9ux8peeyjLb8TYjFf
This commit is contained in:
2026-09-09 19:39:42 +03:00
co-authored by Claude Opus 5
parent 8be9a23c0c
commit 32d64c94af
6 changed files with 254 additions and 8 deletions
+98
View File
@@ -548,6 +548,29 @@ export interface paths {
patch: operations["update_match_api_matches__match_id__patch"];
trace?: never;
};
"/api/matches/{match_id}/finish-draft": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
/**
* Save Finish Draft
* @description Общий черновик формы завершения: то, что видят все, кто заполняет партию.
*
* Права те же, что у самой формы. Версию партии запись черновика не двигает —
* иначе «Завершить» у второго участника ловил бы STALE_WRITE на каждую чужую правку.
*/
put: operations["save_finish_draft_api_matches__match_id__finish_draft_put"];
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/matches/{match_id}/attachments": {
parameters: {
query?: never;
@@ -1603,6 +1626,45 @@ export interface components {
/** Expected Version */
expected_version?: string | null;
};
/**
* MatchFinishDraftData
* @description Состояние формы завершения: блоки мест (внутри блока — ничья), выбывшие,
* комментарии об игроках и причина победы. Промежуточное состояние, поэтому
* места не валидируются — человек раскладывает их постепенно.
*/
MatchFinishDraftData: {
/**
* Blocks
* @default []
*/
blocks: number[][];
/**
* Eliminated
* @default []
*/
eliminated: number[];
/**
* Comments
* @default {}
*/
comments: {
[key: string]: string;
};
/** Win Reason */
win_reason?: ("objectives" | "worlds" | "plastic" | "resources") | null;
/** Overall Comment */
overall_comment?: string | null;
};
/** MatchFinishDraftRead */
MatchFinishDraftRead: {
data: components["schemas"]["MatchFinishDraftData"];
/** Updated By */
updated_by?: number | null;
/** Updated By Nickname */
updated_by_nickname?: string | null;
/** Updated At */
updated_at: string;
};
/** MatchFinishParticipant */
MatchFinishParticipant: {
/** User Id */
@@ -1775,6 +1837,7 @@ export interface components {
* @default []
*/
attachments: components["schemas"]["AttachmentRead"][];
finish_draft?: components["schemas"]["MatchFinishDraftRead"] | null;
};
/** MatchUpdate */
MatchUpdate: {
@@ -3238,6 +3301,41 @@ export interface operations {
};
};
};
save_finish_draft_api_matches__match_id__finish_draft_put: {
parameters: {
query?: never;
header?: never;
path: {
match_id: number;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["MatchFinishDraftData"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["MatchRead"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
add_attachment_api_matches__match_id__attachments_post: {
parameters: {
query?: never;