feat(events): an event action's progress() on a live run's public page (MODULE_API 1.12.0)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 38s
PR Checks / client-build (pull_request) Successful in 49s
PR Checks / server-tests (pull_request) Successful in 20m42s

An event action may declare an optional `progress(envelope)`: how its
step stands while the run is live. Core accepts two shapes and
interprets neither:

- a count: { label, left, of }, rendered "Bandits: 3 of 8 left";
- a percentage: { label, percent }, rendered "The Juggernaut: 62%".

How core asks:

- `events/progress.js` asks each step of a live run whose action has
  one. It passes the step's own held ledger rows and the reader's
  account id, so the module applies its own visibility.
- Each call has a 2 s deadline. A throw, a late answer or any other
  shape is a line not shown, never an error on the page.
- One answer per run and reader is cached for 5 s.
- The public event's live occurrence carries the lines as `progress`.
  The route takes an optional session for the reader.
- The page renders them under the phase label and re-reads them every
  15 s while the event is live.

Core stays game-agnostic: no game's words are in it. Additive, so a
minor bump. Module-uo declares ^1.10.0, registers no action with
progress(), and its actions and pages are unchanged.

From RunicNPC stage 8 (D304, D305).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-06 02:47:00 -05:00
parent f0e7d2aa2a
commit 9ddb59a326
18 changed files with 623 additions and 17 deletions

View File

@@ -17348,7 +17348,7 @@
"Public · Events"
],
"summary": "One event",
"description": "The storyline, the arc it belongs to, what is live, what is next, what happened recently, and a results table once one has been published. A draft, an archived definition and an unlisted one all answer 404, indistinguishable from a slug that never existed. The plan behind the event — phases, steps, actions and their params — is never published; a live run carries the LABEL of the phase it is in and nothing more.",
"description": "The storyline, the arc it belongs to, what is live, what is next, what happened recently, and a results table once one has been published. A draft, an archived definition and an unlisted one all answer 404, indistinguishable from a slug that never existed. The plan behind the event — phases, steps, actions and their params — is never published; a live run carries the LABEL of the phase it is in and nothing more, and `progress`: how its steps stand, each line a label with either `left` of `of` or a `percent`, in the words the steps\\' modules chose. What a module shows may depend on who is reading, so sending a session is optional and may widen it.",
"parameters": [
{
"name": "slug",
@@ -17393,7 +17393,16 @@
"503": {
"description": "Service Unavailable"
}
}
},
"security": [
{},
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/public/modules": {
@@ -26751,6 +26760,100 @@
"example": true
}
}
},
"progress": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "array"
},
"description": {
"type": "string",
"example": "On the live occurrence only: how its steps stand, in step order, in the words their modules chose (MODULE_API 1.12.0). Each line is a `label` with either `left` of `of` (\"Bandits: 3 of 8 left\") or a `percent` (\"The Juggernaut: 62%\"). Core renders them as text and interprets nothing. Empty when no step has anything to say, or its module did not answer in time."
},
"items": {
"$ref": "#/components/schemas/PublicEventProgressLine"
}
}
}
}
}
}
},
"PublicEventProgressLine": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"description": {
"type": "string",
"example": "One line of a live run’s progress: a `label` with either `left` of `of`, or a `percent`. Never both."
},
"properties": {
"type": "object",
"properties": {
"label": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "Bandits"
}
}
},
"left": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 3
}
}
},
"of": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 8
}
}
},
"percent": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"minimum": {
"type": "number",
"example": 0
},
"maximum": {
"type": "number",
"example": 100
},
"example": {
"type": "number",
"example": 62
}
}
}
}
}