Retry a failed portrait from the notice with the same scene builder.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-21 01:48:30 +03:00
co-authored by Cursor
parent 3c70227229
commit 3061e09d3e
32 changed files with 712 additions and 105 deletions
+22 -4
View File
@@ -814,6 +814,20 @@ Owner-only close of a pausing notice. `204` even when that id is already gone. G
sticky warning/error. Socket dismiss (`0x08`) is the live intent; this HTTP route is the
same right check for clients that are not on the socket.
### `POST /api/schools/{id}/notices/{noticeId}/generate`
Owner-only portrait from a sticky notice whose `action` is `generateImage`. Uses the same
`GetPortraitBuildInput` assembly as the person card (scene included). Kind is the one stored
on the notice when Swarm failed, or `full`. Success is the same `201` body as
`POST /api/schools/{id}/people/{personId}/portrait`; Swarm down is the same `503` / `502` /
`504`. The notice stays on the board — a PNG does not dismiss it or start the clock.
| Status | `code` | When |
| --- | --- | --- |
| `400` | `notice-cannot-generate` | No person on the notice, or action is not `generateImage`. Swarm is not called. |
| `403` | `not-owner` | Guest. |
| `404` | `unknown-school` / `unknown-notice` | Missing school or that id is not a sticky notice. |
## Dev endpoints
These exist only when `HSchool:AllowSaveReload` is true (headless AppHost tests). They are never
@@ -868,9 +882,11 @@ read the `World`. Unknown `{id}` is `404` `unknown-school`.
### `POST /api/dev/schools/{id}/notices`
Posts a catalog `EventDef` onto that school's board without going through Swarm. Body:
`{ "defName": "GenerationFailed", "personId": 0 }`. Success is `{ "id", "defName", "pause" }`.
`{ "defName": "GenerationFailed", "personId": 0, "person": "a0.p0", "kind": "full" }`.
`person` is the roster id to put on the notice (empty/omitted = nobody in frame). `kind` is
the portrait kind to retry (`avatar` / `custom` / `full`). Success is `{ "id", "defName", "pause" }`.
Unknown `{id}` is `404` `unknown-school`. Unknown def or a full sticky queue is `409`
`notice-rejected`. Tests use this; portraits emit `generationFailed` in a later phase.
`notice-rejected`. Tests use this; a live Swarm failure posts `GenerationFailed` itself.
## WebSocket message ids
@@ -1100,8 +1116,9 @@ on this frame; the client builds «говорит с Машей о футбол
One school event for every connection that has that school open. Not glued to the clock frame.
Info is not written to the save and is not resent on OpenSchool. Warning/error with `pause=1`
stop the clock, stay in the save (at most eight), and are sent again on Open. The ninth sticky
notice is not emitted. `personId` is `0` when nobody is in frame (the generate-image button is a
later phase).
notice is not emitted. `personId` is `0` when nobody is in frame. `action` is the catalog value
(`none` or `generateImage`); the generate button is owner-only HTTP, not a new socket id.
Layout extra after the v10 personId field; protocol version stays 10.
| Offset | Type | Field |
| --- | --- | --- |
@@ -1112,6 +1129,7 @@ later phase).
| … | `u8` | `1` pause (clocks stop until dismiss + Play), `0` clocks keep running |
| … | `u32` | `ttlMs`; `0` stays until dismiss |
| … | `u32` | `personId`; `0` = none |
| … | string | `action`; `none` or `generateImage` |
## Guarantees and limits