@eventmodelers/cli 1.0.154 → 1.0.156
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/shared/skills/ui-contract/SKILL.md +39 -62
- package/stacks/node/templates/.claude/skills/build-state-change/references/ui-contract-from-openapi.md +10 -14
- package/stacks/node/templates/root/package.json +1 -1
- package/stacks/node/templates/root/server.ts +3 -3
- package/stacks/react/templates/.claude/skills/build-state-change/SKILL.md +1 -1
- package/stacks/react/templates/.claude/skills/build-state-view/SKILL.md +1 -1
- package/stacks/supabase/templates/.claude/skills/build-state-change/references/ui-contract-from-openapi.md +10 -14
- package/stacks/supabase/templates/root/package.json +1 -1
- package/stacks/supabase/templates/root/server.ts +3 -3
- package/stacks/supabase-react/templates/.claude/skills/build-state-change/SKILL.md +3 -3
package/README.md
CHANGED
|
@@ -202,7 +202,7 @@ and the agent starts building — `--local` skips platform config and credential
|
|
|
202
202
|
|
|
203
203
|
**Backend → UI handover.** Besides the usual statuses a slice can be `Ready` (modeling done, waiting to be planned) and `Handover` (backend built, UI contract published, waiting for the UI kit). The flow is `Ready → Planned → InProgress → Handover → InProgress → Done`. Install with `init --handover` (or `re-init --handover`; switch later with `eventmodelers config --handover on|off`; for a single run `run --handover` overrides the setting without saving anything), which saves `"handover": true` in `.eventmodelers/config.json` — then a backend kit ends STATE_VIEW / STATE_CHANGE slices at `Handover` instead of `Done` (the `ui-contract` skill, Step 4, owns that rule); without the flag nothing changes: slices end at `Done`, exactly as before. A kit claims work from the status named in its `lib/kit.json` — `{ "pickupStatus": "Handover" }` for the `react` / `supabase-react` UI kits, absent (= `Planned`) for backend kits. A new UI kit ships that file and sets `Done` when its half is built; a new backend kit just invokes `ui-contract`. Needs a platform with the `Ready` / `Handover` statuses.
|
|
204
204
|
|
|
205
|
-
**UI contract.** The scaffolded `build-state-view` / `build-state-change` skills already end by invoking the shared `ui-contract` skill, which publishes each slice's read (`GET`) or write (`POST`) contract as a `## UI Contract` section in the slice's feedback-lane markdown node (exported in `slice.json` `notes[]`, so a UI build kit can look it up directly). Two things are yours to decide: *where the contract is derived from* (point the `UI Contract` section of those skills at your stack's machine-readable API description, if it has one — the `node`/`supabase` stacks use their `@openapi` blocks via a `ui-contract-from-openapi.md` reference — otherwise the skill
|
|
205
|
+
**UI contract.** The scaffolded `build-state-view` / `build-state-change` skills already end by invoking the shared `ui-contract` skill, which publishes each slice's read (`GET`) or write (`POST`) contract — the endpoint's OpenAPI operation, as a ```` ```yaml ui-contract ```` block — as a `## UI Contract` section in the slice's feedback-lane markdown node (exported in `slice.json` `notes[]`, so a UI build kit can look it up directly). Two things are yours to decide: *where the contract is derived from* (point the `UI Contract` section of those skills at your stack's machine-readable API description, if it has one — the `node`/`supabase` stacks use their `@openapi` blocks via a `ui-contract-from-openapi.md` reference, taking the operation verbatim — otherwise the skill writes the equivalent OpenAPI operation from the route code), and, if you need a different shape or location, a whole-skill override: ship `.claude/skills/ui-contract/SKILL.md` in the stack's templates and it replaces the shared one (stack templates are applied after `shared/skills/`).
|
|
206
206
|
|
|
207
207
|
Installing both a build stack and `init-modeling` into the same project reuses this one `.eventmodelers/config.json` — run whichever `init` command second and it finds the existing config already satisfies the required fields and skips straight past the credential prompt.
|
|
208
208
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventmodelers/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.156",
|
|
4
4
|
"description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, OpenCQRS, UmaDB, Kurrent, or modeling-only)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ui-contract
|
|
3
|
-
description: Publish the slice's UI contract (read
|
|
3
|
+
description: Publish the slice's UI contract — the OpenAPI operation of its endpoint (read = GET, write/state-change = POST) — as its own `## UI Contract` section in the slice's MARKDOWN node in the feedback lane, which the slice export carries in `slice.json` `notes[]` — so the UI build kit finds the contract in one lookup instead of parsing the whole note. Called by every build-state-view / build-state-change skill after the endpoint is built. A build kit can override it by shipping its own `ui-contract` skill.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# UI Contract
|
|
7
7
|
|
|
8
8
|
The UI build kit builds the frontend against what the backend slice **actually exposes**. This skill
|
|
9
|
-
records that as a contract — one per slice, in a fixed, machine-findable place.
|
|
9
|
+
records that as a contract — the endpoint's **OpenAPI operation**, one per slice, in a fixed, machine-findable place.
|
|
10
10
|
|
|
11
11
|
> Invoke `connect` first if not already connected (`BOARD_ID`). Prefer `mcp__eventmodelers__*` tools;
|
|
12
12
|
> the REST equivalents are in `learn-eventmodelers-api`.
|
|
@@ -27,58 +27,24 @@ route, not the plan), before the final commit:
|
|
|
27
27
|
| `STATE_CHANGE` (COMMAND) | **write** — `POST` (a state change) |
|
|
28
28
|
| `AUTOMATION` | none — no UI counterpart, skip |
|
|
29
29
|
|
|
30
|
-
## Step 1 — Derive the
|
|
30
|
+
## Step 1 — Derive the OpenAPI operation from the built endpoint
|
|
31
31
|
|
|
32
|
-
Never copy from the plan — the contract describes what was built.
|
|
32
|
+
Never copy from the plan — the contract describes what was built. The contract **is** the endpoint's
|
|
33
|
+
OpenAPI 3 operation (one path, one verb) — no second, hand-made shape.
|
|
33
34
|
|
|
34
35
|
1. **Use the source the stack names.** If the calling `build-state-view` / `build-state-change`
|
|
35
|
-
skill's `## UI Contract` section points to
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{
|
|
48
|
-
"kind": "read",
|
|
49
|
-
"slice": "<slice title>",
|
|
50
|
-
"sliceId": "<SLICE_BORDER node id>",
|
|
51
|
-
"readModel": "<READMODEL title>",
|
|
52
|
-
"method": "GET",
|
|
53
|
-
"path": "/api/<...>",
|
|
54
|
-
"pathParams": [{ "name": "customerId", "type": "UUID" }],
|
|
55
|
-
"queryParams": [{ "name": "page", "type": "Int", "optional": true }],
|
|
56
|
-
"response": {
|
|
57
|
-
"cardinality": "Single | List",
|
|
58
|
-
"fields": [{ "name": "status", "type": "String", "optional": false, "example": "active" }]
|
|
59
|
-
},
|
|
60
|
-
"errors": [{ "status": 404, "code": "not_found", "when": "no row for this id" }]
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
**Write contract** (`kind: "write"`):
|
|
65
|
-
|
|
66
|
-
```json
|
|
67
|
-
{
|
|
68
|
-
"kind": "write",
|
|
69
|
-
"slice": "<slice title>",
|
|
70
|
-
"sliceId": "<SLICE_BORDER node id>",
|
|
71
|
-
"command": "<COMMAND title>",
|
|
72
|
-
"method": "POST",
|
|
73
|
-
"path": "/api/<...>",
|
|
74
|
-
"pathParams": [],
|
|
75
|
-
"body": [{ "name": "itemId", "type": "UUID", "optional": false, "example": "..." }],
|
|
76
|
-
"response": { "status": 200, "body": [] },
|
|
77
|
-
"errors": [{ "status": 400, "code": "snake_case_code", "when": "business rule violated" }]
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Keep it to what a UI needs — no internal details (no table names, no auth plumbing beyond "requires bearer token" if `security` is set).
|
|
36
|
+
skill's `## UI Contract` section points to the stack's machine-readable API description (e.g. the
|
|
37
|
+
handler's `@openapi` block), take that operation **verbatim** — see the stack's
|
|
38
|
+
`ui-contract-from-openapi.md`. Do not rewrite it.
|
|
39
|
+
2. **Otherwise read the endpoint's route/handler code** and write the equivalent OpenAPI operation:
|
|
40
|
+
method, path, `parameters` (path / query / header), `requestBody` schema, and a response for the
|
|
41
|
+
success status and for every error status the handler really returns.
|
|
42
|
+
3. **Trim to what a UI needs**: drop `tags`, internal notes and anything about storage or auth plumbing
|
|
43
|
+
(keep `security` if set). Keep `summary`, `description`, parameter/field `example`s and the response
|
|
44
|
+
descriptions — they carry the business meaning of each error status.
|
|
45
|
+
4. **Cross-check against `slice.json`**: the request-body / response property names must equal the
|
|
46
|
+
command's / read model's `fields[]` (path parameters such as the generated id excepted). A mismatch
|
|
47
|
+
is an implementation bug — fix the code, don't publish a contract that disagrees with `slice.json`.
|
|
82
48
|
|
|
83
49
|
## Step 2 — Write the `## UI Contract` section into the slice's MARKDOWN node
|
|
84
50
|
|
|
@@ -102,18 +68,27 @@ Keep it to what a UI needs — no internal details (no table names, no auth plum
|
|
|
102
68
|
|
|
103
69
|
**READ** `GET /api/customers/{customerId}/reservation` — <one-line purpose>
|
|
104
70
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
{ ...the Step 1 JSON, verbatim... }
|
|
71
|
+
```yaml ui-contract
|
|
72
|
+
/api/customers/{customerId}/reservation:
|
|
73
|
+
get:
|
|
74
|
+
summary: ...
|
|
75
|
+
parameters: [...]
|
|
76
|
+
responses: {...}
|
|
112
77
|
```
|
|
113
78
|
````
|
|
114
79
|
|
|
115
|
-
|
|
116
|
-
|
|
80
|
+
`**READ**` for a `GET`, `**WRITE**` for a `POST`.
|
|
81
|
+
|
|
82
|
+
**Formatting is part of the contract.** The note renders as Markdown, so `meta.description` must contain
|
|
83
|
+
*real* line breaks — build the string with actual newlines (a multi-line string, or `\n` that the JSON
|
|
84
|
+
parser turns into a newline), never a single line and never a literal backslash-`n` in the stored text.
|
|
85
|
+
Blank line after the heading, blank line before and after the fenced block, the opening ```` ```yaml ui-contract ````
|
|
86
|
+
and the closing ```` ``` ```` each on their own line, and the YAML keeps its indentation (2 spaces). After
|
|
87
|
+
the write, read the node back once (`get_node`) and check that `description` has multiple lines and no
|
|
88
|
+
literal `\n`; if it is flat, rewrite it before moving on.
|
|
89
|
+
|
|
90
|
+
The fenced block whose info string is `ui-contract` is the single source: the operation alone, keyed by its path — no `openapi:`/`info:`/`servers:`
|
|
91
|
+
wrapper, no restated table.
|
|
117
92
|
|
|
118
93
|
## Step 3 — Nothing else to write
|
|
119
94
|
|
|
@@ -140,8 +115,10 @@ runner resolves `run --handover` over the project setting), else `"handover": tr
|
|
|
140
115
|
|
|
141
116
|
1. In the slice's `slice.json`, take the `notes[]` entry that contains a `## UI Contract` heading.
|
|
142
117
|
Ignore every other entry.
|
|
143
|
-
2. In that entry, take the fenced block whose info string is `ui-contract` (```` ```
|
|
144
|
-
and parse just that
|
|
118
|
+
2. In that entry, take the fenced block whose info string is `ui-contract` (```` ```yaml ui-contract ````)
|
|
119
|
+
and parse just that YAML — an OpenAPI operation keyed by its path: request = path/verb +
|
|
120
|
+
`parameters` + `requestBody`, success = the 2xx `responses` entry, errors = every non-2xx entry.
|
|
121
|
+
Ignore the rest of the note.
|
|
145
122
|
3. No such entry → the backend slice isn't built yet, or the slice wasn't re-fetched since; re-fetch
|
|
146
123
|
once, and if still absent build against `slice.json` and note the assumption.
|
|
147
124
|
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
# UI Contract —
|
|
1
|
+
# UI Contract — take the operation from the `@openapi` block
|
|
2
2
|
|
|
3
3
|
Used by the `ui-contract` skill. This stack's `routes.ts` carries an `@openapi` JSDoc block above every
|
|
4
|
-
handler (guarded by the `openapi-annotation` commit check)
|
|
5
|
-
|
|
4
|
+
handler (guarded by the `openapi-annotation` commit check). The contract **is** that operation —
|
|
5
|
+
publish it as is, don't translate it into another shape.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
| `response.status` (write) | the success response code |
|
|
15
|
-
| `errors[]` | every non-2xx response: `status` = the key, `code` / `when` = its description / `error` example |
|
|
16
|
-
| `type` | `string`+`uuid` → `UUID`, `integer`/`int32` → `Int`, `int64` → `Long`, `number`/`double` → `Double`, `number` → `Decimal`, `boolean` → `Boolean`, `date` / `date-time` → `Date` / `DateTime`, `object` → `Custom`, else `String` |
|
|
17
|
-
| `example` | the schema's `example`, nothing invented |
|
|
7
|
+
1. Take the YAML from the handler's `@openapi` block (strip the ` * ` comment prefix) — one path, one verb.
|
|
8
|
+
2. Keep it keyed by its path (`/api/...:` → `get:`/`post:`), with `summary`, `description`, `parameters`,
|
|
9
|
+
`requestBody` and every `responses` entry. Follow a `$ref` into `components.schemas` and inline it, so
|
|
10
|
+
the operation is self-contained.
|
|
11
|
+
3. Drop `tags` and anything internal. Add nothing the block doesn't say — examples come from the block.
|
|
12
|
+
4. Write it as the ```` ```yaml ui-contract ```` block of the `## UI Contract` section, with real line
|
|
13
|
+
breaks and 2-space indentation (see the skill's formatting rule).
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"dev": "node --env-file=.env --require ts-node/register server.ts",
|
|
8
8
|
"build": "tsc",
|
|
9
9
|
"start": "NODE_ENV=production node --env-file=.env --require ts-node/register server.ts",
|
|
10
|
-
"test": "tsx --test
|
|
10
|
+
"test": "tsx --test \"src/**/*.test.ts\"",
|
|
11
11
|
"run:checks": "node .build-kit/lib/check-commit-scope.cjs"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
@@ -16,15 +16,15 @@ async function startServer() {
|
|
|
16
16
|
const slicesBase = join(__dirname, 'dist/src/slices');
|
|
17
17
|
const routesPattern = join(slicesBase, '**/routes{,-*}.js');
|
|
18
18
|
|
|
19
|
-
const routeFiles = await glob(routesPattern, {nodir: true});
|
|
19
|
+
const routeFiles = await glob(routesPattern, {nodir: true, windowsPathsNoEscape: true});
|
|
20
20
|
console.log('Found route files:', routeFiles);
|
|
21
21
|
|
|
22
22
|
const processorPattern = join(slicesBase, '**/processor{,-*}.js');
|
|
23
|
-
const processorFiles = await glob(processorPattern, {nodir: true});
|
|
23
|
+
const processorFiles = await glob(processorPattern, {nodir: true, windowsPathsNoEscape: true});
|
|
24
24
|
console.log('Found processor files:', processorFiles);
|
|
25
25
|
|
|
26
26
|
const commonPattern = join(__dirname, 'src/common/routes{,-*}.@(ts|js)');
|
|
27
|
-
const commonRouteFiles = await glob(commonPattern, {nodir: true});
|
|
27
|
+
const commonRouteFiles = await glob(commonPattern, {nodir: true, windowsPathsNoEscape: true});
|
|
28
28
|
console.log('Found common route files:', commonRouteFiles);
|
|
29
29
|
|
|
30
30
|
|
|
@@ -32,7 +32,7 @@ From the slice definition, extract:
|
|
|
32
32
|
|
|
33
33
|
- **UI contract** — the backend slice's published write (`POST`) contract: look it up as described in the
|
|
34
34
|
`ui-contract` skill ("How the UI build kit looks the contract up": the `## UI Contract` entry in
|
|
35
|
-
`slice.json` `notes[]`, parse only its `ui-contract` block). It fixes the request:
|
|
35
|
+
`slice.json` `notes[]`, parse only its `ui-contract` block — an OpenAPI operation). It fixes the request: the path (+ path `parameters`), the exact `requestBody` properties, the 2xx `responses` entry, and every non-2xx response (status + description) — surface each error code as its own user-facing state, not one generic failure. When absent, the backend slice isn't
|
|
36
36
|
built or re-fetched yet: build against `slice.json` and note the assumption in `progress.txt`.
|
|
37
37
|
|
|
38
38
|
> **Comments & description**: each element carries a `comments: string[]` array (board comments) and a `description` field — use them as implementation hints, and resolve consumed comments via `POST <BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes/<nodeId>/comments/<commentId>/resolve`.
|
|
@@ -31,7 +31,7 @@ From the slice definition, extract:
|
|
|
31
31
|
|
|
32
32
|
- **UI contract** — the backend slice's published read (`GET`) contract: look it up as described in the
|
|
33
33
|
`ui-contract` skill ("How the UI build kit looks the contract up": the `## UI Contract` entry in
|
|
34
|
-
`slice.json` `notes[]`, parse only its `ui-contract` block). It fixes the request:
|
|
34
|
+
`slice.json` `notes[]`, parse only its `ui-contract` block — an OpenAPI operation). It fixes the request: the path (+ path/query `parameters`), the exact 2xx response schema (object = `Single`, `type: array` = `List`), and every non-2xx response — render each error status as a distinct state. When absent, the backend slice isn't
|
|
35
35
|
built or re-fetched yet: build against `slice.json` and note the assumption in `progress.txt`.
|
|
36
36
|
|
|
37
37
|
> **Comments & description**: each element carries a `comments: string[]` array (board comments) and a `description` field — use them as implementation hints, and resolve consumed comments via `POST <BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes/<nodeId>/comments/<commentId>/resolve`.
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
# UI Contract —
|
|
1
|
+
# UI Contract — take the operation from the `@openapi` block
|
|
2
2
|
|
|
3
3
|
Used by the `ui-contract` skill. This stack's `routes.ts` carries an `@openapi` JSDoc block above every
|
|
4
|
-
handler (guarded by the `openapi-annotation` commit check)
|
|
5
|
-
|
|
4
|
+
handler (guarded by the `openapi-annotation` commit check). The contract **is** that operation —
|
|
5
|
+
publish it as is, don't translate it into another shape.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
| `response.status` (write) | the success response code |
|
|
15
|
-
| `errors[]` | every non-2xx response: `status` = the key, `code` / `when` = its description / `error` example |
|
|
16
|
-
| `type` | `string`+`uuid` → `UUID`, `integer`/`int32` → `Int`, `int64` → `Long`, `number`/`double` → `Double`, `number` → `Decimal`, `boolean` → `Boolean`, `date` / `date-time` → `Date` / `DateTime`, `object` → `Custom`, else `String` |
|
|
17
|
-
| `example` | the schema's `example`, nothing invented |
|
|
7
|
+
1. Take the YAML from the handler's `@openapi` block (strip the ` * ` comment prefix) — one path, one verb.
|
|
8
|
+
2. Keep it keyed by its path (`/api/...:` → `get:`/`post:`), with `summary`, `description`, `parameters`,
|
|
9
|
+
`requestBody` and every `responses` entry. Follow a `$ref` into `components.schemas` and inline it, so
|
|
10
|
+
the operation is self-contained.
|
|
11
|
+
3. Drop `tags` and anything internal. Add nothing the block doesn't say — examples come from the block.
|
|
12
|
+
4. Write it as the ```` ```yaml ui-contract ```` block of the `## UI Contract` section, with real line
|
|
13
|
+
breaks and 2-space indentation (see the skill's formatting rule).
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"dev": "node --env-file=.env --require ts-node/register server.ts",
|
|
8
8
|
"build": "tsc",
|
|
9
9
|
"start": "NODE_ENV=production node --env-file=.env --require ts-node/register server.ts",
|
|
10
|
-
"test": "tsx --test
|
|
10
|
+
"test": "tsx --test \"src/**/*.test.ts\"",
|
|
11
11
|
"run:checks": "node .build-kit/lib/check-commit-scope.cjs"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
@@ -17,11 +17,11 @@ async function startServer() {
|
|
|
17
17
|
const slicesBase = join(__dirname, 'dist/src/slices');
|
|
18
18
|
const routesPattern = join(slicesBase, '**/routes{,-*}.js');
|
|
19
19
|
|
|
20
|
-
const routeFiles = await glob(routesPattern, {nodir: true});
|
|
20
|
+
const routeFiles = await glob(routesPattern, {nodir: true, windowsPathsNoEscape: true});
|
|
21
21
|
console.log('Found route files:', routeFiles);
|
|
22
22
|
|
|
23
23
|
const processorPattern = join(slicesBase, '**/processor{,-*}.js');
|
|
24
|
-
const processorFiles = await glob(processorPattern, {nodir: true});
|
|
24
|
+
const processorFiles = await glob(processorPattern, {nodir: true, windowsPathsNoEscape: true});
|
|
25
25
|
console.log('Found processor files:', processorFiles);
|
|
26
26
|
|
|
27
27
|
// Common routes (e.g. projection replay) are privileged/operational and are
|
|
@@ -29,7 +29,7 @@ async function startServer() {
|
|
|
29
29
|
const commonRoutesEnabled = process.env.COMMON_ROUTES_ENABLED === 'true';
|
|
30
30
|
const commonPattern = join(__dirname, 'src/common/routes{,-*}.@(ts|js)');
|
|
31
31
|
const commonRouteFiles = commonRoutesEnabled
|
|
32
|
-
? await glob(commonPattern, {nodir: true})
|
|
32
|
+
? await glob(commonPattern, {nodir: true, windowsPathsNoEscape: true})
|
|
33
33
|
: [];
|
|
34
34
|
console.log(commonRoutesEnabled
|
|
35
35
|
? `Found common route files: ${commonRouteFiles}`
|
|
@@ -56,9 +56,9 @@ Extract:
|
|
|
56
56
|
- `description` — implementation hints (validation rules, business constraints).
|
|
57
57
|
- **UI contract (write)** — the backend slice's published `POST` contract: look it up as described in
|
|
58
58
|
the `ui-contract` skill ("How the UI build kit looks the contract up" — the `## UI Contract` entry in
|
|
59
|
-
`slice.json` `notes[]`; parse only its `ui-contract` block). When present it **overrides
|
|
60
|
-
`apiEndpoint`** and fixes everything the component must honour:
|
|
61
|
-
`
|
|
59
|
+
`slice.json` `notes[]`; parse only its `ui-contract` block — an OpenAPI operation). When present it **overrides
|
|
60
|
+
`apiEndpoint`** and fixes everything the component must honour: the path (+ path `parameters`), the exact
|
|
61
|
+
`requestBody` properties, the 2xx `responses` entry, and every non-2xx response (status + description) — surface each error code as a
|
|
62
62
|
distinct user-facing state through `onError`, not one generic failure. When absent, the backend slice
|
|
63
63
|
isn't built or re-fetched yet: fall back to `apiEndpoint` / `fields[]` as above and note the assumption
|
|
64
64
|
in `progress.txt`. If contract and `command.fields[]` disagree, the contract wins for the request and
|