feat(events): the authoring UI proper (Phase 13) #195
Reference in New Issue
Block a user
No description provided.
Delete Branch "feature/events-p13-authoring-ui"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Events Phase 13. One of two:
website,docs(docs#223), both ontoedge. Spec of record:docs/website/EVENTS.md§I.MODULE_API_VERSIONis untouched. Every schema this form renders was already in the catalog — a param's name, type, whether it is required, its description, its example, and the option source behind it. The module contract needed nothing.The two placeholders, replaced
Phase 3 shipped a step's params and a phase's
whereas raw JSON boxes, each captioned in the source as a placeholder for this phase. The interesting half is not that they are forms now — it is when they stop being one.A value the editor cannot round-trip is shown rather than silently rewritten, and that is the condition builder's rule ported rather than a new idea: flattening
A and (B or C)intoA and B and Cand dropping a param the action does not declare are the same mistake, a save that looks clean and means something else. Three things force a step into JSON and the screen names which — the action is dormant, the step carries an undeclared param (which the save refuses by name, and which is what the author needs to read), or a value no single control can hold.The gate builder is
conditionRowsFrom/conditionsFromRows/coerceLiteral, imported, not reimplemented. The grammar behind a phase gate IS the engagement grammar: the server validates it withengagement/conditions.jsand renders the diagnosis panel's sentence from the same labels. The coercion is the half worth reviewing — every value in an HTML input is a string, and{ cmp: 'gt', value: "5" }against anintvariable is rightly refused by a server whose error the author would then read as a complaint about JSON rather than about what they typed.One route, and why it is a route
POST /admin/events/price,admin, editor— the gateverifyhas, for a stronger version of its reason.A module's
cost()is a server function of params (§F), so a meter has nothing to add up until something asks. The dry run is the wrong thing to ask on a debounce twice over: it dispatches every step through the module — and through it to a sidecar and a game tick — and a pass against a published version is RECORDED, which is the stamp §K's unattended-start gate reads. This dispatches nothing and records nothing, and takes the spec in the body because the plan being priced is unsaved between keystrokes.A step it cannot price is reported in
unpriced, never counted as free. All three ways that happens make the totals an under-count, and an author trusting a number smaller than what will happen is worse off than one with no number. An undeclared dimension is still counted: the action really will try to spend it — the step is refused at dispatch for exactly that — so the amount is true and it is the enforcement that is missing.Two defects in already-merged code
events/newwas added besideevents/:idand binds no param, and React Router ranks a static segment above a dynamic one whatever the order — soisNewwas false, the editor fetched/admin/events/undefined, and the 400 was invisible:!formis true for every failed load, so the error state sat behind a spinner that never stopped. One route now,newa value of:id, and the error is checked before the form.qon the options route,searchableon the answer — and the only UI that reads a source never sent a term. The spawner list is 6,707 entries againstMAX_OPTIONS' 2,000, so the one screen that picks a spawner was picking from a truncation of two thirds of the world with nothing saying so. Which control is drawn follows the source's ownsearchable, never the length of the answer.Smaller decisions worth a line
falsefor every param nobody touched."":checkParamsreadsundefined,nulland''alike, so a blank required field is answered "is required" instead of with a type complaint.coerceLiteral's rule, so-does not becomeNaNmid-keystroke.Checks
Server
npm test: 2113 tests, 2024 pass, 0 fail (89 DB-skipped) — the neweventPrice.test.js(13) plus the 403 walk's new row. Clientnpm test: 380 pass, 0 fail, 18 new. The client builds.routes:manifestandswaggerregenerated — one route added, none moved.Walked in a browser against the local stack, with a throwaway module registering a costed action, a budget dimension, a 6,707-entry searchable source and a trigger with typed variables — core alone declares no budget and no searchable source, so there would otherwise have been nothing to meter or search. The walk authored a multi-phase event with no raw JSON at any point, and ended with three things agreeing: the run came back keyed
walk:Yew(params reaching the concurrency-key template), the runner refused the step with "asks for 45 of rig.creatures; 0 of 30" exactly as the meter had warned, and the diagnosis panel rendered the builder's predicate as "region is "Yew" and tier is at least 3". The stored tree came back as{"cmp":"gte","value":3}— an int, not"3".🤖 Generated with Claude Code
https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
Replaces the two raw JSON boxes Phase 3 shipped as explicit placeholders: a step's params are a form rendered from the action's own declaration, and a phase's advance condition is the engagement condition builder. Adds the live cap meter, the searchable option source's first consumer, and a start dialog carrying the three fields the route has taken since Phase 10. One route: POST /admin/events/price, admin+editor. A module's cost() runs on the server and only there, so a meter has nothing to add up until something asks -- and the dry run is the wrong thing to ask on a debounce twice over: it dispatches every step through the module and a pass against a version is RECORDED, which is the stamp K's unattended-start gate reads. This dispatches nothing and records nothing, and takes the spec in the body because the plan being priced is unsaved between keystrokes. A form gives way to JSON on the condition builder's own rule: a value the editor cannot round-trip is SHOWN rather than silently rewritten. Dropping a param the action does not declare and flattening `A and (B or C)` are the same mistake. Two defects fixed in already-merged code: * Creating an event has been impossible since Phase 6. `events/new` was added beside `events/:id` and binds no param, and React Router ranks a static segment above a dynamic one whatever the order -- so the editor was handed no id and fetched /admin/events/undefined. Worse, the failure was invisible: `!form` is true for every failed load, so the error state sat behind a spinner that never stopped. * 12b's searchable sources had no consumer. The server half shipped and the only UI that reads a source never sent a term, so the 6,707-entry spawner list was picked from a 2,000-entry truncation with nothing saying so. Server: 2113 tests, 2024 pass, 0 fail (89 DB-skipped). Client: 380 pass, 0 fail. routes:manifest and swagger regenerated -- one route added, none moved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4