7 Commits

Author SHA1 Message Date
b5616f359c feat(events): publish runId on a public calendar run entry (Rust D125)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 39s
PR Checks / client-build (pull_request) Successful in 42s
PR Checks / server-tests (pull_request) Successful in 5m54s
A run entry on GET /public/events now names its run, the same id the
event page already publishes on each occurrence and `?run=` takes. A
Rust map marker carries core's run id and nothing else about its event,
so without this the app could only find the event by fetching every
event page.

A projected entry has no runId: nothing is committed to it. Rehearsals
and unlisted events stay absent from the calendar, so their markers
stay unlinked. The web calendar ignores the field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-25 07:38:59 -05:00
702ab89ae2 Merge pull request 'fix(admin): stop a long action name printing over the activity row beside it' (#203) from fix/activity-action-overflow into main
All checks were successful
sync-project-tree / sync (push) Successful in 13s
Build container images / build (push) Successful in 1m29s
Build container images / deploy (push) Successful in 46s
SonarQube / analysis (push) Successful in 9m11s
Reviewed-on: #203
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-09-21 22:24:29 +00:00
9e1f591b25 fix(admin): stop a long action name printing over the activity row beside it
All checks were successful
PR Checks / client-build (pull_request) Successful in 57s
PR Checks / server-tests (pull_request) Successful in 6m9s
PR Checks / bot-tests (pull_request) Successful in 8m3s
The dashboard renders an activity row action in a fixed `width: 110` span with
`flex: none` and no overflow handling, so a name wider than that overflows its
box and prints on top of the detail text next to it.

Core own actions all fit. A module one need not: `module-rust` writes
`rust.account.unlink.staff` when staff sever a player Steam link, and it
overlapped `steamId: …` on a live dashboard. `module-uo` `uoLink.account.link`
is already close to the edge.

`minWidth` instead of `width` keeps the column aligned for every short name and
lets a longer one push the detail right rather than sit under it. One property,
verified in a browser with both rows on screen.

Found while walking module-rust phase 6; raised here rather than worked around
there, because a module may legitimately name an action and shortening one
module names only moves the ceiling to the next one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMH6bw1jXMgbyF3ZWGEzSM
2026-09-21 09:10:43 -05:00
efa9db7330 Merge pull request 'chore(tools): delete the cliloc converter the Asset Bridge replaced (Asset Bridge cutover, 2 of 5)' (#202) from edge into main
All checks were successful
sync-project-tree / sync (push) Successful in 34s
Build container images / build (push) Successful in 22s
Build container images / deploy (push) Successful in 38s
SonarQube / analysis (push) Successful in 9m11s
Reviewed-on: #202
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-09-15 10:20:24 +00:00
720103e3d4 Merge branch 'main' into edge
All checks were successful
PR Checks / client-build (pull_request) Successful in 30s
PR Checks / bot-tests (pull_request) Successful in 31s
PR Checks / server-tests (pull_request) Successful in 13m24s
2026-09-15 10:04:54 +00:00
f373f2e897 Merge pull request 'chore(tools): delete the cliloc converter the Asset Bridge replaced (Phase 2)' (#201) from feat/asset-bridge-p2 into edge
All checks were successful
PR Checks / client-build (pull_request) Successful in 40s
PR Checks / bot-tests (pull_request) Successful in 41s
PR Checks / server-tests (pull_request) Successful in 13m28s
Reviewed-on: #201
2026-09-10 16:20:24 +00:00
655fbf3f69 Merge pull request 'feat(events): the Event System — core (Phase 16b cutover, 2 of 6)' (#199) from edge into main
Some checks failed
sync-project-tree / sync (push) Successful in 1m32s
Build container images / build (push) Successful in 2m12s
Build container images / deploy (push) Successful in 49s
SonarQube / analysis (push) Failing after 33m0s
Reviewed-on: #199
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-09-10 00:43:29 +00:00
5 changed files with 62 additions and 3 deletions

View File

@@ -166,7 +166,12 @@ export default function Dashboard() {
className="sans"
style={{ display: 'flex', gap: 14, alignItems: 'center', padding: '13px 18px', borderBottom: '1px solid var(--line-soft)', fontSize: '0.86rem' }}
>
<span style={{ flex: 'none', color: 'var(--accent)', fontSize: '0.66rem', fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', width: 110, fontFamily: 'ui-monospace,Menlo,monospace' }}>
{/* `minWidth` rather than `width`: the column still lines up for core's
own short action names, and a longer one — a module's namespaced
action, say `rust.account.unlink.staff` — grows the box instead of
overflowing it and printing on top of the detail beside it. Found
on a live dashboard with a module installed. */}
<span style={{ flex: 'none', color: 'var(--accent)', fontSize: '0.66rem', fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', minWidth: 110, fontFamily: 'ui-monospace,Menlo,monospace' }}>
{a.action}
</span>
<span style={{ flex: 1, color: 'var(--text)' }}>{formatDetail(a)}</span>

View File

@@ -103,9 +103,18 @@ function phaseLabel(spec, phaseId) {
return (phase && (phase.label || phase.id)) || null
}
/** One calendar entry, from a materialised run. */
/**
* One calendar entry, from a materialised run.
*
* **`runId` is published because the event page already publishes it** on every
* occurrence, and `?run=` takes it. The calendar was the one public shape that
* named a run without saying which, so a client holding a run id from elsewhere
* (a module's map marker) had no way to find its event but to fetch every event
* page. A projection has none: nothing is committed to it.
*/
const publicRunEntry = (run) => ({
kind: 'run',
runId: run.id,
title: run.definition_title,
slug: run.definition_slug,
seriesName: run.series_name || null,

View File

@@ -26295,6 +26295,23 @@
}
}
},
"runId": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 3692
},
"description": {
"type": "string",
"example": "Runs only: the run this entry is, the same id `PublicEventOccurrence.runId` carries and `/events/{slug}?run=` takes. A projected entry has none, because nothing is committed to it."
}
}
},
"title": {
"type": "object",
"properties": {

View File

@@ -1247,6 +1247,12 @@ const doc = {
'One calendar entry. `kind` says which of two things it is: a `run` is a materialised occurrence, a `projected` entry is arithmetic past the materialisation horizon — a forecast with nothing committed to it, which a client should draw as such.',
properties: {
kind: { type: 'string', enum: ['run', 'projected'], example: 'run' },
runId: {
type: 'integer',
example: 3692,
description:
'Runs only: the run this entry is, the same id `PublicEventOccurrence.runId` carries and `/events/{slug}?run=` takes. A projected entry has none, because nothing is committed to it.',
},
title: { type: 'string', example: 'The Yew Invasion' },
slug: { type: 'string', example: 'the-yew-invasion' },
seriesName: { type: 'string', nullable: true, example: 'The Yew Campaign' },

View File

@@ -164,10 +164,32 @@ test('a calendar entry carries no operational field at all', async () => {
// The whole security property of this file, asserted positively: the entry has
// exactly these keys and gaining one is a deliberate act.
assert.deepEqual(Object.keys(entry).sort(), [
'kind', 'live', 'scheduledFor', 'seriesName', 'seriesSlug', 'slug', 'status', 'timezone', 'title',
'kind', 'live', 'runId', 'scheduledFor', 'seriesName', 'seriesSlug', 'slug', 'status', 'timezone',
'title',
])
})
test('a run entry names its run, and a projection names none', async () => {
// Rust phase 15, D125: a map marker carries core's run id, and the app finds
// the event it belongs to from this calendar. The id is the one the event page
// already publishes on each occurrence.
store.definitions[0].spec = {
...SPEC,
schedule: { kind: 'weekly', days: ['saturday'], time: '00:00' },
}
const result = await publicModel.calendar({ from: '2026-08-28', to: '2026-09-15', now: NOW })
const runs = result.entries.filter((e) => e.kind === 'run')
const projected = result.entries.filter((e) => e.kind === 'projected')
assert.equal(runs.length, 1)
assert.equal(runs[0].runId, store.runs[0].id)
assert.ok(projected.length > 0, 'the weekly schedule must forecast past the one run')
for (const entry of projected) assert.equal('runId' in entry, false)
const page = await publicModel.event('the-yew-invasion')
const occurrences = [page.event.current, page.event.next, ...page.event.upcoming, ...page.event.past]
assert.ok(occurrences.some((o) => o && o.runId === runs[0].runId))
})
test('the default window reaches back as well as forward', async () => {
// §I: this route is "upcoming, live and recent". The default used to start at
// `now`, which left no room for the third word — an event that finished an hour