fix(events): the public phase label finds the phase by key (runicnpc D314)
eventPublic.phaseLabel matched a run's current_phase against p.id, but spec.js validates and the engine writes the phase's `key`, so every live run's public page read "Under way" since Events Phase 14a, UO's events included. It now matches `key` first and still accepts `id` second. The test fixture moves to `key`, as real specs are, and a unit test covers a UO-shaped spec, the id fallback, key-before-id and an unknown phase. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -92,16 +92,22 @@ const isLive = (status) => publicStatus(status) === 'live'
|
|||||||
/**
|
/**
|
||||||
* The label of the phase a run is in, resolved from the PINNED version's spec.
|
* The label of the phase a run is in, resolved from the PINNED version's spec.
|
||||||
*
|
*
|
||||||
* A phase id is a slug an author typed and the label is what they meant it to
|
* A phase key is a slug an author typed and the label is what they meant it to
|
||||||
* read as, so a page rendering the id would show `phase-2` to the public. A
|
* read as, so a page rendering the key would show `phase-2` to the public. A
|
||||||
* phase the spec does not name answers null and the page shows nothing, which is
|
* phase the spec does not name answers null and the page shows nothing, which is
|
||||||
* the right answer for a version edited since: the run pinned the old spec and
|
* the right answer for a version edited since: the run pinned the old spec and
|
||||||
* the old spec is what it is executing.
|
* the old spec is what it is executing.
|
||||||
|
*
|
||||||
|
* **A run's `current_phase` is the phase's `key`**, which is what `spec.js`
|
||||||
|
* validates and the engine writes. This matched `id` until RunicNPC stage 9
|
||||||
|
* (D314), so every live run read "Under way" since Events Phase 14a; `id` is
|
||||||
|
* still accepted second, for a spec stored in that older shape.
|
||||||
*/
|
*/
|
||||||
function phaseLabel(spec, phaseId) {
|
function phaseLabel(spec, phaseKey) {
|
||||||
if (!phaseId || !spec || !Array.isArray(spec.phases)) return null
|
if (!phaseKey || !spec || !Array.isArray(spec.phases)) return null
|
||||||
const phase = spec.phases.find((p) => p && p.id === phaseId)
|
const phase =
|
||||||
return (phase && (phase.label || phase.id)) || null
|
spec.phases.find((p) => p && p.key === phaseKey) || spec.phases.find((p) => p && p.id === phaseKey)
|
||||||
|
return (phase && (phase.label || phase.key || phase.id)) || null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -40,8 +40,9 @@ const NOW = new Date('2026-09-01T12:00:00Z')
|
|||||||
const SPEC = {
|
const SPEC = {
|
||||||
schedule: { kind: 'manual' },
|
schedule: { kind: 'manual' },
|
||||||
phases: [
|
phases: [
|
||||||
{ id: 'muster', label: 'The muster', steps: [{ id: 's1', action: 'core.announce' }] },
|
// `key`, as spec.js validates and every stored spec has it (D314).
|
||||||
{ id: 'assault', label: 'The assault', steps: [] },
|
{ key: 'muster', label: 'The muster', steps: [{ id: 's1', action: 'core.announce' }] },
|
||||||
|
{ key: 'assault', label: 'The assault', steps: [] },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,6 +369,24 @@ test('only the live occurrence carries progress, asked for the reader (1.12.0)',
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('phaseLabel finds a phase by key first, still accepts id, and never shows a step', () => {
|
||||||
|
const { phaseLabel } = publicModel
|
||||||
|
// A UO event's shape: keyed phases, steps of UO's actions.
|
||||||
|
const uo = {
|
||||||
|
phases: [
|
||||||
|
{ key: 'gather', label: 'The gathering', steps: [{ action: 'uo.creature.spawn' }] },
|
||||||
|
{ key: 'siege', label: 'Siege of Yew', steps: [] },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
assert.equal(phaseLabel(uo, 'gather'), 'The gathering')
|
||||||
|
assert.equal(phaseLabel(uo, 'siege'), 'Siege of Yew')
|
||||||
|
assert.equal(phaseLabel({ phases: [{ id: 'old', label: 'An older shape' }] }, 'old'), 'An older shape')
|
||||||
|
// A key and an id both matching: the key wins, because the key is what the engine writes.
|
||||||
|
assert.equal(phaseLabel({ phases: [{ id: 'x', label: 'By id' }, { key: 'x', label: 'By key' }] }, 'x'), 'By key')
|
||||||
|
assert.equal(phaseLabel(uo, 'nope'), null)
|
||||||
|
assert.equal(phaseLabel(uo, null), null)
|
||||||
|
})
|
||||||
|
|
||||||
test('a phase the pinned version does not name renders nothing rather than an id', async () => {
|
test('a phase the pinned version does not name renders nothing rather than an id', async () => {
|
||||||
store.runs[0].status = 'running'
|
store.runs[0].status = 'running'
|
||||||
store.runs[0].current_phase = 'a-phase-since-renamed'
|
store.runs[0].current_phase = 'a-phase-since-renamed'
|
||||||
|
|||||||
Reference in New Issue
Block a user