micro-contracts 0.12.3 → 0.14.0
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 +54 -0
- package/dist/cli/templates.d.ts +6 -0
- package/dist/cli/templates.d.ts.map +1 -1
- package/dist/cli/templates.js +68 -0
- package/dist/cli/templates.js.map +1 -1
- package/dist/cli.js +148 -4
- package/dist/cli.js.map +1 -1
- package/dist/generator/index.d.ts +1 -0
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +4 -1
- package/dist/generator/index.js.map +1 -1
- package/dist/generator/linter.d.ts +2 -0
- package/dist/generator/linter.d.ts.map +1 -1
- package/dist/generator/linter.js +159 -9
- package/dist/generator/linter.js.map +1 -1
- package/dist/generator/templateProcessor.d.ts +68 -1
- package/dist/generator/templateProcessor.d.ts.map +1 -1
- package/dist/generator/templateProcessor.js +209 -0
- package/dist/generator/templateProcessor.js.map +1 -1
- package/dist/types.d.ts +79 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/docs/micro-contracts-x-event-spec-change.md +731 -0
- package/docs/screen-spec.md +547 -0
- package/package.json +1 -1
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
# Screen Spec — Frontend Screen Contracts in OpenAPI
|
|
2
|
+
|
|
3
|
+
The Screen Spec feature repurposes standard OpenAPI constructs (`paths`, `schemas`, `links`, `security`) to declaratively define **frontend screen contracts** — bridging the API layer and UI components. A single YAML file drives frontend types, test scaffolding, and quality analysis.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Screen Spec (OpenAPI YAML)
|
|
7
|
+
├── micro-contracts generate ──┬── ViewModel Types (type-safe)
|
|
8
|
+
│ ├── Navigation Map (typed routing)
|
|
9
|
+
│ └── Event Hooks (typed analytics)
|
|
10
|
+
├── E2E scaffold ─── Playwright test stubs + coverage
|
|
11
|
+
└── Data lineage ─── ViewModel → API → DB traceability
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
> **Important**: This is NOT "auto-generated UI from schema" but rather **screen-level contract definition**, which aligns with micro-contracts' "Contract-first vertical slices" philosophy.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## The HTTP-Screen Analogy
|
|
19
|
+
|
|
20
|
+
Standard OpenAPI semantics map directly to screen specification concepts:
|
|
21
|
+
|
|
22
|
+
| OpenAPI Construct | API Usage | Screen Spec Usage |
|
|
23
|
+
| ---------------------- | ---------------------- | ------------------------------------------------------- |
|
|
24
|
+
| `paths: /home` | Endpoint | React/Vue route |
|
|
25
|
+
| `GET /home` | Fetch resource | **Render screen** — what the user sees |
|
|
26
|
+
| `POST /home` | Create/modify resource | **User action** — what the user can do |
|
|
27
|
+
| `parameters` | Filter/identify | **Screen init params** — route/query params |
|
|
28
|
+
| `responses.200.schema` | Response shape | **ViewModel** — typed interface for the screen |
|
|
29
|
+
| `requestBody` | Submitted data shape | **Action payload** — typed user action interface |
|
|
30
|
+
| `links` | HATEOAS related resources | **Forward navigation** — statically known screen targets |
|
|
31
|
+
| `security` | Auth requirement | **Auth guard** — does the screen require login? |
|
|
32
|
+
|
|
33
|
+
All standard OpenAPI tooling works out of the box: linters, documentation generators, and diff tools.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Required Extensions (`x-*`)
|
|
38
|
+
|
|
39
|
+
Only concerns with **no OpenAPI-native representation** need extensions:
|
|
40
|
+
|
|
41
|
+
| Extension | Required | Purpose | Justification |
|
|
42
|
+
| ------------------- | -------- | ----------------------------------------- | --------------------------------------------------------------- |
|
|
43
|
+
| `x-screen-const` | Yes* | Stable constant name (e.g., `HOME`) | No OpenAPI equivalent for generated symbol names |
|
|
44
|
+
| `x-screen-id` | Yes | Traceability ID (e.g., `SCR-001`) | Links to requirements and E2E annotations |
|
|
45
|
+
| `x-screen-name` | Yes* | Generated symbol name (e.g., `HomePage`) | Drives `use{Name}Events()`, `wrap{Name}ViewModel()` |
|
|
46
|
+
| `x-back-navigation` | No | History-based back navigation support | `links` only cover forward navigation with known targets |
|
|
47
|
+
| `x-event` | No | Inline analytics event declaration | Type auto-inferred from placement; supports string, object, `$ref` |
|
|
48
|
+
| `x-interactions` | No | In-page interaction bindings | Declares non-navigation UI interactions (swipe, selection, etc.) |
|
|
49
|
+
| `x-view-model` | No | Explicit ViewModel schema name | Alternative to auto-inference from `$ref` |
|
|
50
|
+
| `x-view-props` | No | ViewProps interface name | For framework-specific wrapper generation |
|
|
51
|
+
|
|
52
|
+
**Deprecated extensions:**
|
|
53
|
+
|
|
54
|
+
| Extension | Status | Migration |
|
|
55
|
+
| ----------- | ------ | --------- |
|
|
56
|
+
| `x-events` | Deprecated (v0.14) — removed in v0.15 | Replace with inline `x-event` on GET, links, actions, or interactions |
|
|
57
|
+
|
|
58
|
+
\* When `x-screen-id` is present, `x-screen-const` and `x-screen-name` are required (enforced by lint).
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
### 1. Initialize a Screen Module
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx micro-contracts init myScreens --screens
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This command generates:
|
|
71
|
+
- `spec/myScreens/openapi/myScreens.yaml` — Starter screen spec (2-screen scaffold)
|
|
72
|
+
- `spec/default/templates/screen-navigation.hbs` — Navigation map template
|
|
73
|
+
- `spec/default/templates/screen-events.hbs` — Event hooks template
|
|
74
|
+
- `micro-contracts.config.yaml` — Module config with `screen: true`
|
|
75
|
+
|
|
76
|
+
### 2. Edit the Screen Spec
|
|
77
|
+
|
|
78
|
+
Edit `spec/myScreens/openapi/myScreens.yaml` to add your screen definitions.
|
|
79
|
+
|
|
80
|
+
### 3. Generate Code
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx micro-contracts generate
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Generated artifacts:
|
|
87
|
+
- `packages/contract/myScreens/schemas/types.ts` — ViewModel TypeScript types
|
|
88
|
+
- `frontend/src/screens/navigation.generated.ts` — Navigation map
|
|
89
|
+
- `frontend/src/screens/events.generated.ts` — Analytics event hooks
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Configuration
|
|
94
|
+
|
|
95
|
+
### `micro-contracts.config.yaml`
|
|
96
|
+
|
|
97
|
+
Screen modules are enabled with the `screen: true` flag:
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
modules:
|
|
101
|
+
myScreens:
|
|
102
|
+
openapi: spec/myScreens/openapi/myScreens.yaml
|
|
103
|
+
screen: true
|
|
104
|
+
outputs:
|
|
105
|
+
frontend-api:
|
|
106
|
+
enabled: false # Not needed for screen modules
|
|
107
|
+
screen-navigation:
|
|
108
|
+
output: frontend/src/screens/navigation.generated.ts
|
|
109
|
+
template: screen-navigation.hbs
|
|
110
|
+
screen-events:
|
|
111
|
+
output: frontend/src/screens/events.generated.ts
|
|
112
|
+
template: screen-events.hbs
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Effects of `screen: true`:
|
|
116
|
+
- Suppresses `x-micro-contracts-service` / `x-micro-contracts-method` lint warnings
|
|
117
|
+
- Populates `TemplateContext.screens` with pre-parsed `ScreenContext[]`
|
|
118
|
+
- Enables screen-specific lint rules (`x-screen-id` requires `x-screen-const`/`x-screen-name`)
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Screen Spec YAML Structure
|
|
123
|
+
|
|
124
|
+
### Basic Example (inline `x-event`)
|
|
125
|
+
|
|
126
|
+
```yaml
|
|
127
|
+
openapi: '3.1.0'
|
|
128
|
+
info:
|
|
129
|
+
title: App Screen Specification
|
|
130
|
+
version: '1.0.0'
|
|
131
|
+
description: Screen contracts — not a server API
|
|
132
|
+
servers:
|
|
133
|
+
- url: /
|
|
134
|
+
description: Screen routes (client-side)
|
|
135
|
+
|
|
136
|
+
paths:
|
|
137
|
+
/home:
|
|
138
|
+
get:
|
|
139
|
+
operationId: renderHomePage
|
|
140
|
+
security: [{session: []}]
|
|
141
|
+
x-screen-const: HOME
|
|
142
|
+
x-screen-id: SCR-001
|
|
143
|
+
x-screen-name: HomePage
|
|
144
|
+
x-back-navigation: false
|
|
145
|
+
x-event: home_view # → type: "screen_view" (auto-inferred)
|
|
146
|
+
x-interactions:
|
|
147
|
+
- name: daySwipe
|
|
148
|
+
description: Horizontal day-by-day swipe
|
|
149
|
+
x-event:
|
|
150
|
+
name: day_swipe
|
|
151
|
+
params:
|
|
152
|
+
direction: string
|
|
153
|
+
summary: Render home page
|
|
154
|
+
responses:
|
|
155
|
+
'200':
|
|
156
|
+
description: Home page ViewModel
|
|
157
|
+
content:
|
|
158
|
+
application/json:
|
|
159
|
+
schema:
|
|
160
|
+
$ref: '#/components/schemas/HomePageViewModel'
|
|
161
|
+
links:
|
|
162
|
+
goToSettings:
|
|
163
|
+
operationId: renderSettingsPage
|
|
164
|
+
goToDetail:
|
|
165
|
+
operationId: renderDetailPage
|
|
166
|
+
x-event: # → type: "user_action" (auto-inferred)
|
|
167
|
+
$ref: '#/components/x-event-defs/itemTap'
|
|
168
|
+
post:
|
|
169
|
+
operationId: actionHomePage
|
|
170
|
+
x-event: mode_switch # → type: "user_action" (auto-inferred)
|
|
171
|
+
responses:
|
|
172
|
+
'200':
|
|
173
|
+
description: OK
|
|
174
|
+
|
|
175
|
+
components:
|
|
176
|
+
x-event-defs:
|
|
177
|
+
itemTap:
|
|
178
|
+
name: item_tap
|
|
179
|
+
params:
|
|
180
|
+
itemId: string
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### `x-event` — Three Forms
|
|
184
|
+
|
|
185
|
+
```yaml
|
|
186
|
+
# 1. String form (most common)
|
|
187
|
+
x-event: home_view
|
|
188
|
+
|
|
189
|
+
# 2. Object form (type override / extra params)
|
|
190
|
+
x-event:
|
|
191
|
+
name: oauth_callback_result
|
|
192
|
+
type: system
|
|
193
|
+
params:
|
|
194
|
+
success: boolean
|
|
195
|
+
|
|
196
|
+
# 3. $ref form (reusable definitions)
|
|
197
|
+
x-event:
|
|
198
|
+
$ref: '#/components/x-event-defs/itemTap'
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`$ref` resolves to `#/components/x-event-defs/*` (local references only).
|
|
202
|
+
|
|
203
|
+
### `x-event` Placement and Type Inference
|
|
204
|
+
|
|
205
|
+
| Placement | Inferred `type` | Params auto-derivation |
|
|
206
|
+
|-----------|-----------------|------------------------|
|
|
207
|
+
| `get` operation | `screen_view` | From path parameters (e.g., `/calendar/{yearMonth}` → `{ yearMonth: string }`) |
|
|
208
|
+
| `responses.200.links.*` | `user_action` | From target route's path parameters |
|
|
209
|
+
| `post` / `put` / `patch` / `delete` | `user_action` | None — specify explicitly in `params` |
|
|
210
|
+
| `x-interactions.*` | `user_action` | None — specify explicitly in `params` |
|
|
211
|
+
|
|
212
|
+
`type` can be overridden explicitly in object form. Recognized values: `screen_view`, `user_action`, `system`.
|
|
213
|
+
|
|
214
|
+
### `x-interactions`
|
|
215
|
+
|
|
216
|
+
Declares in-page interaction bindings that don't map to HTTP operations or link navigations.
|
|
217
|
+
|
|
218
|
+
```yaml
|
|
219
|
+
x-interactions:
|
|
220
|
+
- name: daySwipe
|
|
221
|
+
description: Horizontal day-by-day swipe navigation
|
|
222
|
+
x-event:
|
|
223
|
+
name: day_swipe
|
|
224
|
+
params:
|
|
225
|
+
direction: string
|
|
226
|
+
- name: itemSelect
|
|
227
|
+
description: Select an item from choices
|
|
228
|
+
x-event:
|
|
229
|
+
name: item_select
|
|
230
|
+
params:
|
|
231
|
+
itemId: string
|
|
232
|
+
module: '@ui/selector' # project-specific field (passed through)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Required field: `name`. All other fields are optional. Project-specific fields (e.g., `module`, `factory`) are passed through to templates in `extras`.
|
|
236
|
+
|
|
237
|
+
### Reusable Event Definitions
|
|
238
|
+
|
|
239
|
+
```yaml
|
|
240
|
+
components:
|
|
241
|
+
x-event-defs:
|
|
242
|
+
itemTap:
|
|
243
|
+
name: item_tap
|
|
244
|
+
params:
|
|
245
|
+
itemId: string
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Referenced with `$ref: '#/components/x-event-defs/itemTap'` from any `x-event` placement.
|
|
249
|
+
|
|
250
|
+
### Field Reference
|
|
251
|
+
|
|
252
|
+
**`operationId`** — Unique identifier referenced by generated code. The naming convention `render` + screen name + `Page` is recommended.
|
|
253
|
+
|
|
254
|
+
**`security`** — Auth guard. `[{session: []}]` means authentication is required; `[]` means no authentication needed.
|
|
255
|
+
|
|
256
|
+
**`x-screen-const`** — SCREAMING_SNAKE_CASE name used for constant access like `SCREENS.HOME`.
|
|
257
|
+
|
|
258
|
+
**`x-screen-id`** — Unique ID for requirements and test traceability. The format `SCR-{domain}-{number}` is recommended.
|
|
259
|
+
|
|
260
|
+
**`x-screen-name`** — PascalCase name for generated symbols like `useHomePageEvents()`.
|
|
261
|
+
|
|
262
|
+
**`x-back-navigation`** — When `true`, declares that the screen supports browser back or UI back button navigation. Unlike forward navigation covered by `links`, the back destination is determined at runtime.
|
|
263
|
+
|
|
264
|
+
**`x-event`** — Inline analytics event. Type is auto-inferred from placement. Supports string, object (with `name`, `type?`, `params?`), and `$ref` forms. Params are auto-derived from path parameters for `get` and `links` placements; for actions and interactions, specify params explicitly.
|
|
265
|
+
|
|
266
|
+
**`x-interactions`** — Array of in-page interaction bindings. Each entry has `name` (required), `description` (recommended), optional `x-event`, and any project-specific fields.
|
|
267
|
+
|
|
268
|
+
**`responses.200.links`** — Forward navigation targets. References target screens by `operationId`. Each link may have an optional `x-event`.
|
|
269
|
+
|
|
270
|
+
**`POST` / `PUT` / `PATCH` / `DELETE` operations** — Mutation operations on the same path define user actions (form submissions, button taps, deletions, etc.). Each may have an optional `x-event`.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## TemplateContext.screens
|
|
275
|
+
|
|
276
|
+
When `screen: true` is enabled, `TemplateContext` includes a pre-parsed `screens: ScreenContext[]`. This eliminates the need to iterate over deeply nested `spec.paths` in templates.
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
interface ScreenContext {
|
|
280
|
+
route: string; // '/home'
|
|
281
|
+
screenConst: string; // 'HOME'
|
|
282
|
+
screenId: string; // 'SCR-001'
|
|
283
|
+
screenName: string; // 'HomePage'
|
|
284
|
+
operationId: string; // 'renderHomePage'
|
|
285
|
+
supportsBack: boolean; // false
|
|
286
|
+
viewModelSchema: string; // 'HomePageViewModel'
|
|
287
|
+
links: ScreenLink[]; // forward navigation targets
|
|
288
|
+
requiresAuth: boolean; // true
|
|
289
|
+
pathParams: string[]; // ['yearMonth'] for /calendar/{yearMonth}
|
|
290
|
+
|
|
291
|
+
// Inline x-event (v0.14+)
|
|
292
|
+
screenEvent?: InlineEventDefinition; // GET x-event (screen_view)
|
|
293
|
+
actions: ScreenAction[]; // post/put/patch/delete with events
|
|
294
|
+
interactions: ScreenInteraction[]; // x-interactions with events
|
|
295
|
+
|
|
296
|
+
/** @deprecated Use screenEvent / links[].event / actions[].event instead */
|
|
297
|
+
events: ScreenEventDefinition[]; // legacy x-events (backward compat)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
interface ScreenLink {
|
|
301
|
+
name: string; // 'goToSettings'
|
|
302
|
+
targetRoute: string; // '/settings'
|
|
303
|
+
targetOperationId: string; // 'renderSettingsPage'
|
|
304
|
+
event?: InlineEventDefinition; // link x-event (user_action)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
interface ScreenAction {
|
|
308
|
+
method: string; // 'post', 'put', 'patch', 'delete'
|
|
309
|
+
operationId: string; // 'actionHomePage'
|
|
310
|
+
summary: string;
|
|
311
|
+
schemaRef: string; // request body $ref name
|
|
312
|
+
event?: InlineEventDefinition; // action x-event (user_action)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
interface ScreenInteraction {
|
|
316
|
+
name: string; // 'daySwipe'
|
|
317
|
+
description: string; // 'Horizontal day-by-day swipe'
|
|
318
|
+
event?: InlineEventDefinition; // interaction x-event (user_action)
|
|
319
|
+
extras: Record<string, unknown>; // project-specific fields
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
interface InlineEventDefinition {
|
|
323
|
+
name: string; // 'home_view'
|
|
324
|
+
type: string; // 'screen_view' | 'user_action' | 'system'
|
|
325
|
+
params?: Record<string, string>; // auto-derived or explicit
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Template Usage Comparison
|
|
330
|
+
|
|
331
|
+
**Using `screens[]` (recommended):**
|
|
332
|
+
|
|
333
|
+
```handlebars
|
|
334
|
+
{{#each screens}}
|
|
335
|
+
{{screenConst}}: { id: '{{screenId}}', route: '{{route}}' },
|
|
336
|
+
{{/each}}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Iterating `spec.paths` directly (not recommended):**
|
|
340
|
+
|
|
341
|
+
```handlebars
|
|
342
|
+
{{#each spec.paths}}
|
|
343
|
+
{{#each this}}
|
|
344
|
+
{{#unless (eq @key "parameters")}}
|
|
345
|
+
{{#unless (eq @key "servers")}}
|
|
346
|
+
{{#unless (eq @key "summary")}}
|
|
347
|
+
{{#unless (eq @key "description")}}
|
|
348
|
+
{{#if this.x-screen-id}}
|
|
349
|
+
{{this.x-screen-const}}: { id: '{{this.x-screen-id}}', route: '{{@../key}}' },
|
|
350
|
+
{{/if}}
|
|
351
|
+
{{/unless}}
|
|
352
|
+
{{/unless}}
|
|
353
|
+
{{/unless}}
|
|
354
|
+
{{/unless}}
|
|
355
|
+
{{/each}}
|
|
356
|
+
{{/each}}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Default Templates
|
|
362
|
+
|
|
363
|
+
### screen-navigation.hbs
|
|
364
|
+
|
|
365
|
+
Generates a navigation map. Outputs a `SCREENS` constant and `*_PAGE_LINKS` for each screen.
|
|
366
|
+
|
|
367
|
+
Example output:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
const _OP_ROUTES = {
|
|
371
|
+
renderHomePage: '/home',
|
|
372
|
+
renderSettingsPage: '/settings',
|
|
373
|
+
renderChatPage: '/chat',
|
|
374
|
+
} as const;
|
|
375
|
+
|
|
376
|
+
export const SCREENS = {
|
|
377
|
+
HOME: { id: 'SCR-001' as const, route: _OP_ROUTES.renderHomePage, supportsBack: false } as const,
|
|
378
|
+
SETTINGS: { id: 'SCR-002' as const, route: _OP_ROUTES.renderSettingsPage, supportsBack: true } as const,
|
|
379
|
+
} as const;
|
|
380
|
+
|
|
381
|
+
export const HOME_PAGE_LINKS = {
|
|
382
|
+
goToSettings: _OP_ROUTES.renderSettingsPage,
|
|
383
|
+
goToChat: _OP_ROUTES.renderChatPage,
|
|
384
|
+
} as const;
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### screen-events.hbs (inline x-event)
|
|
388
|
+
|
|
389
|
+
Generates analytics event hooks using the new inline `x-event` structure. Templates can use `screenEvent`, `links[].event`, `actions[].event`, and `interactions[].event`.
|
|
390
|
+
|
|
391
|
+
Example template:
|
|
392
|
+
|
|
393
|
+
```handlebars
|
|
394
|
+
{{#each screens}}
|
|
395
|
+
{{#if screenEvent}}
|
|
396
|
+
export function use{{screenName}}ScreenView() {
|
|
397
|
+
const tracked = useRef(false);
|
|
398
|
+
useEffect(() => {
|
|
399
|
+
if (tracked.current) return;
|
|
400
|
+
tracked.current = true;
|
|
401
|
+
trackEvent('{{screenEvent.name}}', '{{screenEvent.type}}', {});
|
|
402
|
+
}, []);
|
|
403
|
+
}
|
|
404
|
+
{{/if}}
|
|
405
|
+
|
|
406
|
+
{{#each links}}
|
|
407
|
+
{{#if this.event}}
|
|
408
|
+
export function navigateVia{{capitalize this.name}}(
|
|
409
|
+
{{eventParamsSignature this.event.params}}
|
|
410
|
+
) {
|
|
411
|
+
trackEvent('{{this.event.name}}', '{{this.event.type}}',
|
|
412
|
+
{ {{#each this.event.params}}{{@key}}{{#unless @last}}, {{/unless}}{{/each}} });
|
|
413
|
+
return '{{this.targetRoute}}';
|
|
414
|
+
}
|
|
415
|
+
{{/if}}
|
|
416
|
+
{{/each}}
|
|
417
|
+
|
|
418
|
+
{{#each actions}}
|
|
419
|
+
{{#if this.event}}
|
|
420
|
+
export function track{{capitalize this.operationId}}Event(
|
|
421
|
+
{{eventParamsSignature this.event.params}}
|
|
422
|
+
) {
|
|
423
|
+
trackEvent('{{this.event.name}}', '{{this.event.type}}',
|
|
424
|
+
{ {{#each this.event.params}}{{@key}}{{#unless @last}}, {{/unless}}{{/each}} });
|
|
425
|
+
}
|
|
426
|
+
{{/if}}
|
|
427
|
+
{{/each}}
|
|
428
|
+
{{/each}}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Handlebars Helpers
|
|
432
|
+
|
|
433
|
+
| Helper | Description | Example |
|
|
434
|
+
|--------|-------------|---------|
|
|
435
|
+
| `hasEvent` | Returns `true` if the screen has any events (screenEvent, link events, action events, or interaction events) | `{{#if (hasEvent this)}}...{{/if}}` |
|
|
436
|
+
| `eventParamsSignature` | Expands params map to TypeScript argument list | `{{eventParamsSignature screenEvent.params}}` → `yearMonth: string` |
|
|
437
|
+
|
|
438
|
+
### Legacy screen-events.hbs (deprecated x-events)
|
|
439
|
+
|
|
440
|
+
The legacy `x-events` array is still available in `events` for backward compatibility:
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
export function useHomePageEvents() {
|
|
444
|
+
return {
|
|
445
|
+
trackView: () =>
|
|
446
|
+
trackEvent('home_view', 'screen_view', {}),
|
|
447
|
+
trackSignOut: () =>
|
|
448
|
+
trackEvent('sign_out', 'user_action', {}),
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## Lint Rules
|
|
456
|
+
|
|
457
|
+
`micro-contracts lint` performs the following screen-specific checks:
|
|
458
|
+
|
|
459
|
+
| Code | Level | Description |
|
|
460
|
+
| -------------------------------- | ------- | -------------------------------------------------------------- |
|
|
461
|
+
| `SCREEN_MISSING_CONST` | Error | `x-screen-id` present but `x-screen-const` missing |
|
|
462
|
+
| `SCREEN_MISSING_NAME` | Error | `x-screen-id` present but `x-screen-name` missing |
|
|
463
|
+
| `SCREEN_MISSING_OPERATION_ID` | Error | Screen operation missing `operationId` |
|
|
464
|
+
| `SCREEN_INVALID_X_EVENT` | Error | `x-event` is not a string, `{name}` object, or `{$ref}` |
|
|
465
|
+
| `SCREEN_CONFLICTING_EVENT_DEFS` | Error | `x-events` and `x-event` coexist on the same operation |
|
|
466
|
+
| `SCREEN_INVALID_INTERACTIONS` | Error | `x-interactions` is not an array |
|
|
467
|
+
| `SCREEN_INVALID_INTERACTION` | Error | `x-interactions` entry missing `name` |
|
|
468
|
+
| `SCREEN_DEPRECATED_X_EVENTS` | Warning | `x-events` (flat list) is deprecated — use inline `x-event` |
|
|
469
|
+
| `SCREEN_INVALID_EVENTS` | Error | `x-events` is not an array (legacy validation) |
|
|
470
|
+
| `SCREEN_INVALID_EVENT` | Error | `x-events` item missing `name` or `type` (legacy validation) |
|
|
471
|
+
|
|
472
|
+
In `screen: true` modules, `MISSING_X_SERVICE` / `MISSING_X_METHOD` warnings are suppressed (screen specs do not need service/method declarations).
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## Custom Templates
|
|
477
|
+
|
|
478
|
+
You can extend the default templates or create new ones for additional generated artifacts. All `ScreenContext` fields are available in templates.
|
|
479
|
+
|
|
480
|
+
### ViewProps Template Example
|
|
481
|
+
|
|
482
|
+
`spec/default/templates/screen-view-props.hbs`:
|
|
483
|
+
|
|
484
|
+
```handlebars
|
|
485
|
+
{{#each screens}}
|
|
486
|
+
export interface {{screenName}}ViewProps {
|
|
487
|
+
// ViewModel: {{viewModelSchema}}
|
|
488
|
+
// Screen ID: {{screenId}}
|
|
489
|
+
}
|
|
490
|
+
{{/each}}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
Add to config:
|
|
494
|
+
|
|
495
|
+
```yaml
|
|
496
|
+
outputs:
|
|
497
|
+
screen-view-props:
|
|
498
|
+
output: frontend/src/screens/view-props.generated.ts
|
|
499
|
+
template: screen-view-props.hbs
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Available Template Context
|
|
503
|
+
|
|
504
|
+
| Field | Type | Description |
|
|
505
|
+
| ----------------- | --------- | -------------------------------------------------- |
|
|
506
|
+
| `screens` | array | `ScreenContext[]` — array of screen definitions |
|
|
507
|
+
| `screens[].screenEvent` | object? | `InlineEventDefinition` — GET screen_view event |
|
|
508
|
+
| `screens[].actions` | array | `ScreenAction[]` — mutation operations with events |
|
|
509
|
+
| `screens[].interactions` | array | `ScreenInteraction[]` — interaction bindings with events |
|
|
510
|
+
| `screens[].pathParams` | string[] | Path parameter names for this route |
|
|
511
|
+
| `screens[].links[].event` | object? | `InlineEventDefinition` — link navigation event |
|
|
512
|
+
| `spec` | object | Raw OpenAPI spec (for direct access when needed) |
|
|
513
|
+
| `title` | string | OpenAPI title |
|
|
514
|
+
| `version` | string | OpenAPI version |
|
|
515
|
+
| `moduleName` | string | Module name |
|
|
516
|
+
| `contractPackage` | string | Contract package import path |
|
|
517
|
+
| `schemaNames` | string[] | All schema names |
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## Tiered Integration
|
|
522
|
+
|
|
523
|
+
### Tier 1 — Core (built into micro-contracts)
|
|
524
|
+
|
|
525
|
+
- Type definitions: `x-screen-*`, `x-event`, `x-interactions` on `OperationObject`
|
|
526
|
+
- Screen context extraction: `TemplateContext.screens` (with `screenEvent`, `actions`, `interactions`, `pathParams`)
|
|
527
|
+
- `$ref` resolution: `components.x-event-defs` local references
|
|
528
|
+
- Params auto-derivation: path params for `get` and `links` placements
|
|
529
|
+
- Lint rules: Screen spec consistency, inline event validation, interaction validation
|
|
530
|
+
- `init --screens`: Starter file generation
|
|
531
|
+
- Default templates: `screen-navigation.hbs`, `screen-events.hbs`
|
|
532
|
+
- Handlebars helpers: `hasEvent`, `eventParamsSignature`
|
|
533
|
+
|
|
534
|
+
### Tier 2 — Optional Packages / Official Recipes (future)
|
|
535
|
+
|
|
536
|
+
| Package | Purpose |
|
|
537
|
+
| ------------------------------ | ------------------------------------------------------------------------ |
|
|
538
|
+
| `@micro-contracts/e2e-scaffold`| Parse screen spec to generate Playwright test stubs with coverage |
|
|
539
|
+
| ViewProp / data-testid pattern | Derive stable test IDs from ViewModel fields + `x-screen-const` |
|
|
540
|
+
|
|
541
|
+
### Tier 3 — Documentation Only (future)
|
|
542
|
+
|
|
543
|
+
| Pattern | Description |
|
|
544
|
+
| ------------------------- | -------------------------------------------------------------------- |
|
|
545
|
+
| Design tool integration | Link screen IDs to design tool frames via `x-pen-node` or similar |
|
|
546
|
+
| Requirements traceability | Link screens to user stories via `x-satisfies` |
|
|
547
|
+
| Data lineage declarations | Declare upstream API/DB origins on ViewModel schemas via `x-lineage-source` |
|
package/package.json
CHANGED