docs(website): the authoring UI proper, and the one route it needed (Phase 13)
EVENTS.md I gains what the phase settled -- the rule by which a form gives way to the JSON box, why the condition builder is the engagement one rather than a second one shaped like it, and the live cap meter as a fifth affordance beside the dry run it must not be mistaken for. The screens table's two unfinished rows are marked built; rehearsal is marked reachable, which it had not been since Phase 10 shipped it. The API surface table gains POST /admin/events/price, and a paragraph on why it is a route rather than arithmetic in the browser: cost() is a server function of params, and the dry run both dispatches and records, neither of which belongs behind a keystroke. EVENTS_PLAN.md carries Phase 13 as built, with the four decisions, the two defects it closes in already-merged code, and the six things the build settled. BACKEND_DESIGN.md's /events/* rows are Phase 1's surface and have not moved since; a note says so and points at EVENTS.md as canonical rather than re-listing eleven routes that file already owns. Checks: docs has no CI. Every relative link and in-page anchor in the new sections was resolved by hand; the files are CRLF in the working tree and each diff is content-sized (8/0, 64/5, 67/0), so none carries the \r\r\n full-file rewrite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
This commit is contained in:
@@ -1621,6 +1621,73 @@ rather than a silent revert of their change.
|
||||
|
||||
### Phase 13 — The authoring UI proper (`website`)
|
||||
|
||||
> **Complete.** `edge` in `website` and `docs`. The two raw JSON boxes Phase 3 shipped as explicit
|
||||
> placeholders are forms; the meter, the searchable dropdown and rehearsal are reachable. **One
|
||||
> route added** — `POST /admin/events/price` — and `MODULE_API_VERSION` is untouched: every schema
|
||||
> the form renders was already in the catalog.
|
||||
>
|
||||
> **Four decisions the org lead settled (2026-09-07), all as recommended:**
|
||||
>
|
||||
> - **The meter gets a route of its own rather than riding the dry run.** A module's `cost()` is a
|
||||
> server function of params, so nothing can be totted up in the browser — and the dry run is the
|
||||
> wrong call to make 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. `POST /admin/events/price` dispatches
|
||||
> nothing and records nothing, takes the spec in the body because the plan being priced is unsaved
|
||||
> between keystrokes, and is `admin, editor` for `verify`'s reason.
|
||||
> - **The advance-condition builder is in scope**, though the phase sentence did not name it. P13 is
|
||||
> the last authoring phase, so *"a later phase"* was this one or none. It is the engagement builder
|
||||
> — the same `conditionRowsFrom`/`conditionsFromRows`, not a second one shaped like it — because
|
||||
> the grammar behind a phase gate IS the engagement grammar, validated on the server by
|
||||
> `engagement/conditions.js` and rendered into the diagnosis panel's sentence by the same labels.
|
||||
> - **Start now opens a dialog carrying `rehearsal`, `scope` and `params`.** All three have been on
|
||||
> the route since Phase 10 and this screen posted `{}`.
|
||||
> - **The searchable option source gets its consumer**, the JSON box stays as a per-step escape
|
||||
> hatch, and the timeline gains the per-phase cap draw §I asks it for.
|
||||
>
|
||||
> **Two defects this phase closes in already-merged code, and neither was visible from a test:**
|
||||
>
|
||||
> - **12b's searchable sources had no consumer.** The server half shipped — `q` on
|
||||
> `/catalog/options/:sourceId`, `searchable` on the answer — and the only UI that reads a source
|
||||
> never sent a term. The spawner list is 6,707 entries against `MAX_OPTIONS`' 2,000, so the one
|
||||
> screen that picks a spawner was picking from a truncation of two thirds of the world, with
|
||||
> nothing on it saying so.
|
||||
> - **An event whose concurrency key names a `{placeholder}` could not be started correctly by
|
||||
> hand.** The key is rendered from the run's own params; a start posting `{}` rendered the same key
|
||||
> every time, so the second manual run was refused as an overlap with the first — the failure
|
||||
> looking exactly like the safety feature working.
|
||||
>
|
||||
> **What the build settled.**
|
||||
>
|
||||
> - **A form gives way to the JSON box on the CONDITION BUILDER's rule, not on a new one.** 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)` into `A and B and C` are the same mistake, a
|
||||
> save that looks clean and means something else. Three things force it — a dormant action, an
|
||||
> undeclared param, a value no single control can hold — and the screen names which.
|
||||
> - **A step core cannot price is reported, never counted as free.** The three ways that happens all
|
||||
> make the total 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 the one that 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.
|
||||
> - **A boolean param is a three-value select, not a checkbox.** A checkbox cannot say *"not set"*,
|
||||
> and for an optional boolean that is a real third state: the action's own default. A checkbox
|
||||
> would have posted `false` for every param nobody touched.
|
||||
> - **An empty field removes the key rather than posting `""`.** `checkParams` reads `undefined`,
|
||||
> `null` and `''` alike, so a required param left blank comes back as *"is required"* — the error
|
||||
> the author needs — instead of a type complaint about an empty string.
|
||||
> - **A half-typed number is kept as typed.** `coerceLiteral` is borrowed from the engagement builder
|
||||
> unchanged, and its rule matters here for the same reason: turning `-` into `NaN` mid-keystroke
|
||||
> either posts a value nobody wrote or makes a negative impossible to enter.
|
||||
> - **The meter debounces AND counts generations.** Requests 400ms apart do not necessarily answer in
|
||||
> that order, and an older answer landing last leaves the meter describing a plan that has already
|
||||
> changed — stale in the one direction that matters, with nothing on screen to say so. A failure
|
||||
> leaves the last answer standing, dimmed, rather than blanking it: the plan is still saveable,
|
||||
> dry-runnable and publishable without a meter.
|
||||
>
|
||||
> **Verify, as run.** Server `npm test` (the new `eventPrice.test.js`, 13 tests, plus the 403 walk's
|
||||
> new row); client `npm test` — **380 pass, 0 fail**, 18 of them new; the client builds.
|
||||
> `routes:manifest` and `swagger` regenerated — one route added, none moved.
|
||||
|
||||
Replaces P3's placeholders. The phase timeline (**not** a node graph — the condition grammar has no
|
||||
branching and a canvas would advertise power the engine does not have); the step editor rendering each
|
||||
action's declared params, with option-source dropdowns; the live cap meter; dry run; rehearsal.
|
||||
|
||||
Reference in New Issue
Block a user