@loj-lang/cli 0.5.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 +87 -0
- package/agent-assets/loj-authoring/SKILL.md +179 -0
- package/agent-assets/loj-authoring/agents/openai.yaml +3 -0
- package/agent-assets/loj-authoring/metadata.json +6 -0
- package/agent-assets/loj-authoring/references/backend-family.md +340 -0
- package/agent-assets/loj-authoring/references/backend-targets.md +171 -0
- package/agent-assets/loj-authoring/references/frontend-family.md +794 -0
- package/agent-assets/loj-authoring/references/frontend-runtime-trace.md +204 -0
- package/agent-assets/loj-authoring/references/policy-rules-proof.md +178 -0
- package/agent-assets/loj-authoring/references/project-and-transport.md +454 -0
- package/agent-assets/loj-authoring/references/workflow-flow-proof.md +263 -0
- package/dist/database-native-sql.d.ts +4 -0
- package/dist/database-native-sql.d.ts.map +1 -0
- package/dist/database-native-sql.js +266 -0
- package/dist/database-native-sql.js.map +1 -0
- package/dist/env.d.ts +31 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +229 -0
- package/dist/env.js.map +1 -0
- package/dist/fastapi-dev-runner.d.ts +3 -0
- package/dist/fastapi-dev-runner.d.ts.map +1 -0
- package/dist/fastapi-dev-runner.js +263 -0
- package/dist/fastapi-dev-runner.js.map +1 -0
- package/dist/flow-proof.d.ts +3 -0
- package/dist/flow-proof.d.ts.map +1 -0
- package/dist/flow-proof.js +2 -0
- package/dist/flow-proof.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5353 -0
- package/dist/index.js.map +1 -0
- package/dist/rules-proof.d.ts +3 -0
- package/dist/rules-proof.d.ts.map +1 -0
- package/dist/rules-proof.js +2 -0
- package/dist/rules-proof.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
# Frontend-Family Authoring (`.web.loj`, legacy `.rdsl`)
|
|
2
|
+
|
|
3
|
+
Use this reference when authoring or reviewing frontend-family source files.
|
|
4
|
+
|
|
5
|
+
## Defaults
|
|
6
|
+
|
|
7
|
+
- Prefer `.web.loj` for new files. Use `.rdsl` only for legacy edits.
|
|
8
|
+
- Current implemented compiler triple is:
|
|
9
|
+
|
|
10
|
+
```yaml
|
|
11
|
+
compiler:
|
|
12
|
+
target: react
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- Files are a strict YAML subset:
|
|
16
|
+
- no anchors
|
|
17
|
+
- no aliases
|
|
18
|
+
- no merge keys
|
|
19
|
+
- no custom tags
|
|
20
|
+
|
|
21
|
+
## File Shape
|
|
22
|
+
|
|
23
|
+
Root files may contain:
|
|
24
|
+
|
|
25
|
+
- `app:`
|
|
26
|
+
- optional `compiler:`
|
|
27
|
+
- optional `imports:`
|
|
28
|
+
- `model <Name>:`
|
|
29
|
+
- `resource <name>:`
|
|
30
|
+
- `readModel <name>:`
|
|
31
|
+
- `page <name>:`
|
|
32
|
+
|
|
33
|
+
Module files may contain:
|
|
34
|
+
|
|
35
|
+
- optional `imports:`
|
|
36
|
+
- `model <Name>:`
|
|
37
|
+
- `resource <name>:`
|
|
38
|
+
- `readModel <name>:`
|
|
39
|
+
- `page <name>:`
|
|
40
|
+
|
|
41
|
+
Module files may not contain `app:` or `compiler:`.
|
|
42
|
+
|
|
43
|
+
## Imports
|
|
44
|
+
|
|
45
|
+
- Use single-file for small demos.
|
|
46
|
+
- Split only when the app gets large:
|
|
47
|
+
- `4+` models
|
|
48
|
+
- `3+` resources
|
|
49
|
+
- complex custom pages
|
|
50
|
+
- `imports:` entries must be relative `.web.loj` / `.rdsl` paths or directories ending in `/`.
|
|
51
|
+
- Nested imports are allowed.
|
|
52
|
+
- Import cycles are invalid.
|
|
53
|
+
- Directory imports expand direct child family files only, sorted lexicographically.
|
|
54
|
+
|
|
55
|
+
## `app:`
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
app:
|
|
61
|
+
name: "Flight Booking"
|
|
62
|
+
theme: light
|
|
63
|
+
auth: jwt
|
|
64
|
+
style: '@style("./styles/theme")'
|
|
65
|
+
seo:
|
|
66
|
+
siteName: "Loj Air"
|
|
67
|
+
defaultTitle: "Loj Air"
|
|
68
|
+
titleTemplate: "{title} · Loj Air"
|
|
69
|
+
defaultDescription: "Flight booking demo"
|
|
70
|
+
defaultImage: '@asset("./assets/og-default.png")'
|
|
71
|
+
favicon: '@asset("./assets/favicon.png")'
|
|
72
|
+
navigation:
|
|
73
|
+
- group:
|
|
74
|
+
key: "nav.system"
|
|
75
|
+
defaultMessage: "System"
|
|
76
|
+
items:
|
|
77
|
+
- label:
|
|
78
|
+
key: "nav.users"
|
|
79
|
+
defaultMessage: "Users"
|
|
80
|
+
icon: users
|
|
81
|
+
target: resource.users.list
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Rules:
|
|
85
|
+
|
|
86
|
+
- `name` is required.
|
|
87
|
+
- `theme` is `light` or `dark`.
|
|
88
|
+
- `auth` is `none`, `jwt`, or `session`.
|
|
89
|
+
- `style` must use `@style("./styles/x")`.
|
|
90
|
+
- `seo` is optional and currently supports:
|
|
91
|
+
- `siteName`
|
|
92
|
+
- `defaultTitle`
|
|
93
|
+
- `titleTemplate`
|
|
94
|
+
- `defaultDescription`
|
|
95
|
+
- `defaultImage`
|
|
96
|
+
- `favicon`
|
|
97
|
+
- navigation targets are `page.<name>` or `resource.<name>.list`
|
|
98
|
+
- navigation `group` and item `label` currently accept either plain string or shared descriptor
|
|
99
|
+
shape `{ key?, defaultMessage?, values? }`
|
|
100
|
+
|
|
101
|
+
## Current `MessageLike` / Descriptor Surfaces
|
|
102
|
+
|
|
103
|
+
In the current implemented `.web.loj` slice, these user-facing copy surfaces accept either:
|
|
104
|
+
|
|
105
|
+
- plain string
|
|
106
|
+
- shared descriptor shape `{ key?, defaultMessage?, values? }`
|
|
107
|
+
|
|
108
|
+
Current supported surfaces:
|
|
109
|
+
|
|
110
|
+
- `resource.list.title`
|
|
111
|
+
- `resource.read.title`
|
|
112
|
+
- navigation `group`
|
|
113
|
+
- navigation item `label`
|
|
114
|
+
- `page.title`
|
|
115
|
+
- `page.blocks[].title`
|
|
116
|
+
- page/create handoff `label`
|
|
117
|
+
- read-model `dateNavigation.prevLabel`
|
|
118
|
+
- read-model `dateNavigation.nextLabel`
|
|
119
|
+
- SEO-facing copy directions such as `app.seo.defaultTitle`, `app.seo.titleTemplate`,
|
|
120
|
+
`app.seo.defaultDescription`, and `page.seo.description`
|
|
121
|
+
|
|
122
|
+
Current guardrails:
|
|
123
|
+
|
|
124
|
+
- use plain strings for fixed copy
|
|
125
|
+
- use descriptors when future i18n or scalar-literal interpolation matters
|
|
126
|
+
- descriptor `values` in these UI-copy surfaces currently accept only scalar literals, not
|
|
127
|
+
`{ ref: ... }`
|
|
128
|
+
- do not assume every string field in `.web.loj` is `MessageLike`
|
|
129
|
+
|
|
130
|
+
## `model <Name>:`
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
model User:
|
|
136
|
+
name: string @required @minLen(2)
|
|
137
|
+
email: string @required @email @unique
|
|
138
|
+
role: enum(admin, editor, viewer)
|
|
139
|
+
teamId: belongsTo(Team)
|
|
140
|
+
members: hasMany(Member, by: teamId)
|
|
141
|
+
createdAt: datetime @auto
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Types:
|
|
145
|
+
|
|
146
|
+
- `string`
|
|
147
|
+
- `number`
|
|
148
|
+
- `boolean`
|
|
149
|
+
- `datetime`
|
|
150
|
+
- `enum(a, b, c)`
|
|
151
|
+
- `belongsTo(Model)`
|
|
152
|
+
- `hasMany(Model, by: field)`
|
|
153
|
+
|
|
154
|
+
Decorators:
|
|
155
|
+
|
|
156
|
+
- `@required`
|
|
157
|
+
- `@email`
|
|
158
|
+
- `@unique`
|
|
159
|
+
- `@minLen(n)`
|
|
160
|
+
- `@auto`
|
|
161
|
+
|
|
162
|
+
Resource-backed records include implicit runtime `id: string`.
|
|
163
|
+
|
|
164
|
+
Relation rules:
|
|
165
|
+
|
|
166
|
+
- `belongsTo(Model)` is the narrow single-record relation field.
|
|
167
|
+
- `hasMany(Model, by: field)` is inverse metadata only; it does not generate a client model field.
|
|
168
|
+
- `hasMany(..., by: ...)` must point at a target-model field declared as
|
|
169
|
+
`belongsTo(CurrentModel)`.
|
|
170
|
+
- `hasMany(...)` does not support field decorators.
|
|
171
|
+
|
|
172
|
+
## `resource <name>:`
|
|
173
|
+
|
|
174
|
+
Example:
|
|
175
|
+
|
|
176
|
+
```yaml
|
|
177
|
+
resource bookings:
|
|
178
|
+
model: Booking
|
|
179
|
+
api: /api/bookings
|
|
180
|
+
|
|
181
|
+
list:
|
|
182
|
+
title:
|
|
183
|
+
key: "bookings.list.title"
|
|
184
|
+
defaultMessage: "Bookings"
|
|
185
|
+
style: listShell
|
|
186
|
+
filters: [reference, status, member.name]
|
|
187
|
+
columns:
|
|
188
|
+
- reference @sortable
|
|
189
|
+
- status @badge(DRAFT:gray, READY:blue, CONFIRMED:green)
|
|
190
|
+
- member.name @sortable
|
|
191
|
+
|
|
192
|
+
read:
|
|
193
|
+
title: "Booking Details"
|
|
194
|
+
style: detailShell
|
|
195
|
+
fields:
|
|
196
|
+
- reference
|
|
197
|
+
- member.name
|
|
198
|
+
|
|
199
|
+
create:
|
|
200
|
+
style: formShell
|
|
201
|
+
fields:
|
|
202
|
+
- reference
|
|
203
|
+
- status
|
|
204
|
+
includes:
|
|
205
|
+
- field: passengers
|
|
206
|
+
minItems: 1
|
|
207
|
+
fields:
|
|
208
|
+
- name
|
|
209
|
+
- seat
|
|
210
|
+
rules: '@rules("./rules/passenger-create")'
|
|
211
|
+
rules: '@rules("./rules/booking-create")'
|
|
212
|
+
|
|
213
|
+
edit:
|
|
214
|
+
style: formShell
|
|
215
|
+
fields:
|
|
216
|
+
- reference
|
|
217
|
+
- status
|
|
218
|
+
includes:
|
|
219
|
+
- field: passengers
|
|
220
|
+
fields:
|
|
221
|
+
- id
|
|
222
|
+
- name
|
|
223
|
+
- seat
|
|
224
|
+
rules: '@rules("./rules/passenger-edit")'
|
|
225
|
+
rules: '@rules("./rules/booking-edit")'
|
|
226
|
+
|
|
227
|
+
workflow:
|
|
228
|
+
source: '@flow("./workflows/booking-lifecycle")'
|
|
229
|
+
style: workflowShell
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Current resource surface rules:
|
|
233
|
+
|
|
234
|
+
- `workflow:` is optional and may be either:
|
|
235
|
+
- scalar `@flow("./workflows/x")`
|
|
236
|
+
- mapping `{ source: '@flow("./workflows/x")', style: workflowShell }`
|
|
237
|
+
- `list.style`, `read.style`, `create.style`, `edit.style`, and `workflow.style` are optional
|
|
238
|
+
shell-level style hooks only
|
|
239
|
+
- `list.title` / `read.title` currently accept plain string or descriptor
|
|
240
|
+
- `create.rules` / `edit.rules` may still use inline `visibleIf/enabledIf/allowIf/enforce`
|
|
241
|
+
mappings or linked `@rules("./rules/x")`
|
|
242
|
+
- linked form rules currently support only:
|
|
243
|
+
- `eligibility`
|
|
244
|
+
- `validate`
|
|
245
|
+
- `derive`
|
|
246
|
+
- frontend generated form consumers reject linked `allow/deny`
|
|
247
|
+
- linked `derive` currently supports only already-listed scalar form fields
|
|
248
|
+
- `create.includes` / `edit.includes` currently support one-level repeated-child forms over direct
|
|
249
|
+
`hasMany(Target, by: field)` relations
|
|
250
|
+
- repeated-child include rules may also use linked `.rules.loj`
|
|
251
|
+
- generated `edit.includes` submits one-level diff semantics:
|
|
252
|
+
- child rows with `id` update
|
|
253
|
+
- child rows without `id` create
|
|
254
|
+
- omitted existing child rows delete
|
|
255
|
+
|
|
256
|
+
Workflow-linked resource behavior today:
|
|
257
|
+
|
|
258
|
+
- linked workflow `model` must match the resource `model`
|
|
259
|
+
- linked workflow `field` must point to an `enum(...)` field on that model
|
|
260
|
+
- `wizard.steps` may set optional `surface: form | read | workflow`
|
|
261
|
+
- when omitted, the first wizard step defaults to `form` and later steps default to `workflow`
|
|
262
|
+
- create/edit/read/workflow surfaces derive narrow current/next-step summaries
|
|
263
|
+
- create/edit CTA labels become step-aware when a visible next step exists
|
|
264
|
+
- read and fixed `/:id/workflow` prioritize transitions that advance to the next visible step
|
|
265
|
+
- read/workflow also surface narrow `workflowStep` review handoff plus `Redo <previous step>`
|
|
266
|
+
|
|
267
|
+
## `readModel <name>:`
|
|
268
|
+
|
|
269
|
+
Example:
|
|
270
|
+
|
|
271
|
+
```yaml
|
|
272
|
+
readModel outwardFlightAvailability:
|
|
273
|
+
api: /api/outward-flight-availability
|
|
274
|
+
inputs:
|
|
275
|
+
outwardDate: date @required
|
|
276
|
+
cabin: enum(ECONOMY, BUSINESS) @required
|
|
277
|
+
result:
|
|
278
|
+
flightNo: string
|
|
279
|
+
fareBrand: string
|
|
280
|
+
quotedFare: number
|
|
281
|
+
rules: '@rules("./rules/outward-flight-availability")'
|
|
282
|
+
list:
|
|
283
|
+
groupBy: [flightNo]
|
|
284
|
+
pivotBy: fareBrand
|
|
285
|
+
columns:
|
|
286
|
+
- flightNo
|
|
287
|
+
- fareBrand
|
|
288
|
+
- quotedFare
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Current read-model rules:
|
|
292
|
+
|
|
293
|
+
- `api:` is required and points at a fixed GET endpoint
|
|
294
|
+
- `rules:` is optional and must use `@rules("./rules/x")`
|
|
295
|
+
- `inputs:` and `result:` must be YAML mappings, not field lists
|
|
296
|
+
- `inputs:` and `result:` currently support only scalar and enum field types
|
|
297
|
+
- `list:` is required only for `data: readModel.<name>.list`
|
|
298
|
+
- current frontend-family consumers are:
|
|
299
|
+
- page `table` blocks via `data: readModel.<name>.list`
|
|
300
|
+
- page `metric` blocks via `data: readModel.<name>.count`
|
|
301
|
+
- current frontend `readModel rules` consumption supports only:
|
|
302
|
+
- `eligibility`
|
|
303
|
+
- `validate`
|
|
304
|
+
- `derive`
|
|
305
|
+
- frontend `readModel rules` reject `allow/deny`
|
|
306
|
+
- `derive` runs client-side over fetched rows; it is not query pushdown
|
|
307
|
+
|
|
308
|
+
Grouped/table presentation today:
|
|
309
|
+
|
|
310
|
+
- `queryState: <name>` shares one URL-backed query state across multiple read-model consumers with
|
|
311
|
+
identical `inputs:`
|
|
312
|
+
- `list.groupBy:` is optional on read-model table consumers
|
|
313
|
+
- `list.pivotBy:` is optional on grouped table consumers
|
|
314
|
+
- `dateNavigation:` may set `field`, optional `prevLabel`, and optional `nextLabel`
|
|
315
|
+
- `selectionState: <name>` exposes one selected row to page-level handoff actions
|
|
316
|
+
|
|
317
|
+
Current copy rule:
|
|
318
|
+
|
|
319
|
+
- `dateNavigation.prevLabel` / `nextLabel` accept plain string or descriptor
|
|
320
|
+
- descriptor `values` in these UI-copy fields currently accept only scalar literals
|
|
321
|
+
|
|
322
|
+
## `page <name>:`
|
|
323
|
+
|
|
324
|
+
Example:
|
|
325
|
+
|
|
326
|
+
```yaml
|
|
327
|
+
page availability:
|
|
328
|
+
title: "Flight Availability"
|
|
329
|
+
style: pageShell
|
|
330
|
+
seo:
|
|
331
|
+
description: "Search outbound and homeward flights"
|
|
332
|
+
canonicalPath: "/availability"
|
|
333
|
+
image: '@asset("./assets/availability-og.png")'
|
|
334
|
+
actions:
|
|
335
|
+
- create:
|
|
336
|
+
resource: bookings
|
|
337
|
+
label: "Book selected itinerary"
|
|
338
|
+
seed:
|
|
339
|
+
outwardFlightNo:
|
|
340
|
+
selection: outwardFlight.flightNo
|
|
341
|
+
travelDate:
|
|
342
|
+
input: availabilitySearch.outwardDate
|
|
343
|
+
blocks:
|
|
344
|
+
- type: table
|
|
345
|
+
title: "Outbound Flights"
|
|
346
|
+
style: tableShell
|
|
347
|
+
data: readModel.outwardFlightAvailability.list
|
|
348
|
+
queryState: availabilitySearch
|
|
349
|
+
selectionState: outwardFlight
|
|
350
|
+
dateNavigation:
|
|
351
|
+
field: outwardDate
|
|
352
|
+
prevLabel: "Previous day"
|
|
353
|
+
nextLabel: "Next day"
|
|
354
|
+
- type: metric
|
|
355
|
+
title: "Matching flights"
|
|
356
|
+
data: readModel.outwardFlightAvailability.count
|
|
357
|
+
queryState: availabilitySearch
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Current page rules:
|
|
361
|
+
|
|
362
|
+
- `page.title` accepts plain string or descriptor
|
|
363
|
+
- `page.style` is optional and must reference a named style from the linked `app.style` program
|
|
364
|
+
- `page.seo` is optional and currently supports:
|
|
365
|
+
- `description`
|
|
366
|
+
- `canonicalPath`
|
|
367
|
+
- `image`
|
|
368
|
+
- `noIndex`
|
|
369
|
+
- `blocks[].title` accepts plain string or descriptor
|
|
370
|
+
- `blocks[].style` is optional and shell-level only
|
|
371
|
+
|
|
372
|
+
Current block types:
|
|
373
|
+
|
|
374
|
+
- `metric`
|
|
375
|
+
- `chart`
|
|
376
|
+
- `table`
|
|
377
|
+
- `custom`
|
|
378
|
+
|
|
379
|
+
Current `table` block behavior:
|
|
380
|
+
|
|
381
|
+
- `data:` may use:
|
|
382
|
+
- `<resource>.list`
|
|
383
|
+
- `readModel.<name>.list`
|
|
384
|
+
- `<resource>.<hasManyField>` on record-scoped relation pages
|
|
385
|
+
- read-model-backed table blocks may also declare:
|
|
386
|
+
- `queryState`
|
|
387
|
+
- `dateNavigation`
|
|
388
|
+
- `selectionState`
|
|
389
|
+
- narrow `rowActions.create`
|
|
390
|
+
- pages may also declare narrow `actions.create` handoff when those same pages already expose
|
|
391
|
+
`selectionState`
|
|
392
|
+
- record-scoped relation pages may reuse target resource list columns/filters/pagination/actions when
|
|
393
|
+
the target resource already defines `list:`
|
|
394
|
+
|
|
395
|
+
Current custom block rule:
|
|
396
|
+
|
|
397
|
+
- `custom` is still the strongest escape hatch tier
|
|
398
|
+
- record-scoped relation pages pass a narrow generated context object including `parentWorkflow` and
|
|
399
|
+
`relations` summaries
|
|
400
|
+
|
|
401
|
+
## Style DSL (`.style.loj`)
|
|
402
|
+
|
|
403
|
+
Use `.style.loj` when shell-level visual intent is stable enough to stay out of raw CSS but does
|
|
404
|
+
not belong in `.web.loj` business structure.
|
|
405
|
+
|
|
406
|
+
Current linking points:
|
|
407
|
+
|
|
408
|
+
- `app.style: '@style("./styles/theme")'`
|
|
409
|
+
- `page.style`
|
|
410
|
+
- `page.blocks[].style`
|
|
411
|
+
- `resource.list.style`
|
|
412
|
+
- `resource.read.style`
|
|
413
|
+
- `resource.create.style`
|
|
414
|
+
- `resource.edit.style`
|
|
415
|
+
- `resource.workflow.style` through `workflow: { source, style }`
|
|
416
|
+
|
|
417
|
+
Example:
|
|
418
|
+
|
|
419
|
+
```yaml
|
|
420
|
+
tokens:
|
|
421
|
+
colors:
|
|
422
|
+
surface: "#ffffff"
|
|
423
|
+
border: "#d9dfeb"
|
|
424
|
+
text: "#18212f"
|
|
425
|
+
accent: "#0f5fff"
|
|
426
|
+
spacing:
|
|
427
|
+
sm: 8
|
|
428
|
+
md: 16
|
|
429
|
+
lg: 24
|
|
430
|
+
borderRadius:
|
|
431
|
+
md: 16
|
|
432
|
+
lg: 24
|
|
433
|
+
elevation:
|
|
434
|
+
card: 3
|
|
435
|
+
panel: 5
|
|
436
|
+
typography:
|
|
437
|
+
body:
|
|
438
|
+
fontSize: 16
|
|
439
|
+
fontWeight: 400
|
|
440
|
+
lineHeight: 24
|
|
441
|
+
heading:
|
|
442
|
+
fontSize: 20
|
|
443
|
+
fontWeight: 700
|
|
444
|
+
lineHeight: 28
|
|
445
|
+
|
|
446
|
+
style pageShell:
|
|
447
|
+
display: column
|
|
448
|
+
gap: lg
|
|
449
|
+
padding: lg
|
|
450
|
+
typography: body
|
|
451
|
+
color: text
|
|
452
|
+
|
|
453
|
+
style resultShell:
|
|
454
|
+
extends: pageShell
|
|
455
|
+
maxWidth: 1360
|
|
456
|
+
backgroundColor: surface
|
|
457
|
+
borderRadius: lg
|
|
458
|
+
borderWidth: 1
|
|
459
|
+
borderColor: border
|
|
460
|
+
elevation: panel
|
|
461
|
+
escape:
|
|
462
|
+
css: |
|
|
463
|
+
width: 100%;
|
|
464
|
+
margin: 0 auto;
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Current token interpretation:
|
|
468
|
+
|
|
469
|
+
- `fontSize`: numeric px
|
|
470
|
+
- `lineHeight`: numeric px
|
|
471
|
+
- `fontWeight`: numeric CSS font-weight
|
|
472
|
+
|
|
473
|
+
Current token groups:
|
|
474
|
+
|
|
475
|
+
- `colors`
|
|
476
|
+
- `spacing`
|
|
477
|
+
- `borderRadius`
|
|
478
|
+
- `elevation`
|
|
479
|
+
- `typography`
|
|
480
|
+
|
|
481
|
+
Current style properties:
|
|
482
|
+
|
|
483
|
+
- layout:
|
|
484
|
+
- `display: row | column | stack`
|
|
485
|
+
- `gap`
|
|
486
|
+
- `padding`
|
|
487
|
+
- `paddingHorizontal`
|
|
488
|
+
- `paddingVertical`
|
|
489
|
+
- `alignItems: start | center | end | stretch`
|
|
490
|
+
- `justifyContent: start | center | end | spaceBetween | spaceAround`
|
|
491
|
+
- size:
|
|
492
|
+
- `width`
|
|
493
|
+
- `minHeight`
|
|
494
|
+
- `maxWidth`
|
|
495
|
+
- surface:
|
|
496
|
+
- `backgroundColor`
|
|
497
|
+
- `borderRadius`
|
|
498
|
+
- `borderWidth`
|
|
499
|
+
- `borderColor`
|
|
500
|
+
- `elevation`
|
|
501
|
+
- text:
|
|
502
|
+
- `typography`
|
|
503
|
+
- `color`
|
|
504
|
+
- `opacity`
|
|
505
|
+
- inheritance:
|
|
506
|
+
- `extends`
|
|
507
|
+
- escape:
|
|
508
|
+
- `escape.css`
|
|
509
|
+
|
|
510
|
+
Token-reference rules:
|
|
511
|
+
|
|
512
|
+
- `gap`, `padding`, `paddingHorizontal`, `paddingVertical` resolve bare refs from `spacing`
|
|
513
|
+
- `borderRadius` resolves bare refs from `borderRadius`
|
|
514
|
+
- `elevation` resolves bare refs from `elevation`
|
|
515
|
+
- `backgroundColor`, `borderColor`, and `color` resolve bare refs from `colors`
|
|
516
|
+
- `typography` resolves bare refs from `typography`
|
|
517
|
+
|
|
518
|
+
Current style guardrails:
|
|
519
|
+
|
|
520
|
+
- keep `.style.loj` for shell-level style intent
|
|
521
|
+
- do not expect table internals, form sections, read-related panels, or responsive/mobile variants
|
|
522
|
+
to have first-class hooks yet
|
|
523
|
+
- `escape.css` is the current narrow escape hatch for web-only styling details
|
|
524
|
+
- if a visual need is clearly DOM/CSS-structure-specific, prefer raw CSS escape rather than forcing
|
|
525
|
+
it into the shared style layer
|
|
526
|
+
|
|
527
|
+
Proof-driven style guidance from the current flight-booking proof:
|
|
528
|
+
|
|
529
|
+
- avoid stacking two shell systems on the same surface
|
|
530
|
+
- if a node already uses `loj-style-*`, do not also give that same node a heavy proof-local
|
|
531
|
+
`.rdsl-block` card treatment
|
|
532
|
+
- let `.style.loj` own the outer shell; use raw CSS for internals
|
|
533
|
+
- start from a compact business-UI token baseline
|
|
534
|
+
- large `xl/xxl` spacing, large radii, and strong elevation can make generated form/table pages
|
|
535
|
+
feel empty and inflated very quickly
|
|
536
|
+
- prefer tighter tokens first, then expand only after seeing the composed page
|
|
537
|
+
- good `.style.loj` candidates:
|
|
538
|
+
- page shell
|
|
539
|
+
- page block shell
|
|
540
|
+
- resource list/read/create/edit/workflow shell
|
|
541
|
+
- keep these in `escape.css` for now:
|
|
542
|
+
- button alignment and oversized pill fixes caused by current host/runtime flex behavior
|
|
543
|
+
- table overflow, table-cell density, and sticky-header tuning
|
|
544
|
+
- empty-state presentation
|
|
545
|
+
- filter-bar and form-grid internals
|
|
546
|
+
- workflow summary internals
|
|
547
|
+
- repeated-child include section internals
|
|
548
|
+
- if the visual bug comes from current generated DOM behavior rather than stable author-facing shell
|
|
549
|
+
intent, do not propose a new `.style.loj` primitive first
|
|
550
|
+
|
|
551
|
+
Current style escape example:
|
|
552
|
+
|
|
553
|
+
```yaml
|
|
554
|
+
style bookingListShell:
|
|
555
|
+
extends: resultShell
|
|
556
|
+
backgroundColor: surface
|
|
557
|
+
elevation: panel
|
|
558
|
+
escape:
|
|
559
|
+
css: |
|
|
560
|
+
background-image: linear-gradient(180deg, rgba(219, 232, 255, 0.35), rgba(255, 255, 255, 0.98));
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
Use `escape.css` for:
|
|
564
|
+
|
|
565
|
+
- gradients
|
|
566
|
+
- browser-specific decorative layering
|
|
567
|
+
- table/form/sidebar internals
|
|
568
|
+
- responsive details that are still outside the shared style contract
|
|
569
|
+
|
|
570
|
+
Do not use it to re-encode the whole shell when the shared style primitives already fit.
|
|
571
|
+
|
|
572
|
+
## Combined Example: Shared Read-Model Query + Selection Handoff
|
|
573
|
+
|
|
574
|
+
```yaml
|
|
575
|
+
page availability:
|
|
576
|
+
title: "Flight Availability"
|
|
577
|
+
style: availabilityPageShell
|
|
578
|
+
actions:
|
|
579
|
+
- create:
|
|
580
|
+
resource: bookings
|
|
581
|
+
label: "Book selected itinerary"
|
|
582
|
+
seed:
|
|
583
|
+
outwardFlightNo:
|
|
584
|
+
selection: outwardFlight.flightNo
|
|
585
|
+
homewardFlightNo:
|
|
586
|
+
selection: homewardFlight.flightNo
|
|
587
|
+
travelDate:
|
|
588
|
+
input: availabilitySearch.outwardDate
|
|
589
|
+
blocks:
|
|
590
|
+
- type: table
|
|
591
|
+
title: "Outbound Flights"
|
|
592
|
+
style: availabilityResultShell
|
|
593
|
+
data: readModel.outwardFlightAvailability.list
|
|
594
|
+
queryState: availabilitySearch
|
|
595
|
+
selectionState: outwardFlight
|
|
596
|
+
dateNavigation:
|
|
597
|
+
field: outwardDate
|
|
598
|
+
prevLabel: "Previous day"
|
|
599
|
+
nextLabel: "Next day"
|
|
600
|
+
- type: table
|
|
601
|
+
title: "Homeward Flights"
|
|
602
|
+
style: availabilityResultShell
|
|
603
|
+
data: readModel.homewardFlightAvailability.list
|
|
604
|
+
queryState: availabilitySearch
|
|
605
|
+
selectionState: homewardFlight
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
## Combined Example: Resource Workflow + Style + Rules + Includes
|
|
609
|
+
|
|
610
|
+
```yaml
|
|
611
|
+
resource bookings:
|
|
612
|
+
model: Booking
|
|
613
|
+
api: /api/bookings
|
|
614
|
+
|
|
615
|
+
list:
|
|
616
|
+
title: "Bookings"
|
|
617
|
+
style: bookingListShell
|
|
618
|
+
columns:
|
|
619
|
+
- reference @sortable
|
|
620
|
+
- status @badge(DRAFT:gray, READY:blue, CONFIRMED:green, FAILED:red)
|
|
621
|
+
|
|
622
|
+
read:
|
|
623
|
+
title: "Booking Details"
|
|
624
|
+
style: bookingDetailShell
|
|
625
|
+
fields:
|
|
626
|
+
- reference
|
|
627
|
+
- status
|
|
628
|
+
|
|
629
|
+
create:
|
|
630
|
+
style: bookingFormShell
|
|
631
|
+
fields:
|
|
632
|
+
- reference
|
|
633
|
+
- quotedFare
|
|
634
|
+
includes:
|
|
635
|
+
- field: passengers
|
|
636
|
+
minItems: 1
|
|
637
|
+
fields:
|
|
638
|
+
- name
|
|
639
|
+
- seat
|
|
640
|
+
rules: '@rules("./rules/passenger-create")'
|
|
641
|
+
rules: '@rules("./rules/booking-create")'
|
|
642
|
+
|
|
643
|
+
edit:
|
|
644
|
+
style: bookingFormShell
|
|
645
|
+
fields:
|
|
646
|
+
- reference
|
|
647
|
+
- quotedFare
|
|
648
|
+
includes:
|
|
649
|
+
- field: passengers
|
|
650
|
+
fields:
|
|
651
|
+
- id
|
|
652
|
+
- name
|
|
653
|
+
- seat
|
|
654
|
+
rules: '@rules("./rules/passenger-edit")'
|
|
655
|
+
rules: '@rules("./rules/booking-edit")'
|
|
656
|
+
|
|
657
|
+
workflow:
|
|
658
|
+
source: '@flow("./workflows/booking-lifecycle")'
|
|
659
|
+
style: bookingWorkflowShell
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
## Frontend Escape Hatches
|
|
663
|
+
|
|
664
|
+
Use escape hatches only when the current `.web.loj` slice cannot express the behavior directly.
|
|
665
|
+
|
|
666
|
+
Preferred order:
|
|
667
|
+
|
|
668
|
+
- built-in DSL
|
|
669
|
+
- `@expr(...)`
|
|
670
|
+
- `@fn(...)`
|
|
671
|
+
- `@custom(...)`
|
|
672
|
+
|
|
673
|
+
### `@expr(...)`
|
|
674
|
+
|
|
675
|
+
Use for pure boolean/value logic inside supported expression slots.
|
|
676
|
+
|
|
677
|
+
```yaml
|
|
678
|
+
edit:
|
|
679
|
+
fields:
|
|
680
|
+
- field: role
|
|
681
|
+
rules:
|
|
682
|
+
enabledIf: '@expr(currentUser?.role === "admin" && record?.status !== "archived")'
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
Rules:
|
|
686
|
+
|
|
687
|
+
- keep it pure and deterministic
|
|
688
|
+
- current runtime context is narrow:
|
|
689
|
+
- `currentUser`
|
|
690
|
+
- `record`
|
|
691
|
+
- `formData`
|
|
692
|
+
- `item` on repeated-child rows
|
|
693
|
+
|
|
694
|
+
### `@fn("./logic/x")`
|
|
695
|
+
|
|
696
|
+
Use when the logic is too complex for the shared expression language.
|
|
697
|
+
|
|
698
|
+
```yaml
|
|
699
|
+
edit:
|
|
700
|
+
rules:
|
|
701
|
+
allowIf: '@fn("./logic/canEditBooking")'
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
Path rules:
|
|
705
|
+
|
|
706
|
+
- extensionless logical ids are preferred
|
|
707
|
+
- frontend-family resolves extensionless logical ids to `.ts` first, then `.js`
|
|
708
|
+
- explicit `.ts` / `.js` suffixes are accepted as deliberate lock-in
|
|
709
|
+
- the path resolves relative to the `.web.loj` file that declares it
|
|
710
|
+
|
|
711
|
+
Current function shape:
|
|
712
|
+
|
|
713
|
+
```ts
|
|
714
|
+
export default function canEditBooking(context: {
|
|
715
|
+
currentUser?: unknown;
|
|
716
|
+
record?: unknown;
|
|
717
|
+
formData?: unknown;
|
|
718
|
+
item?: unknown;
|
|
719
|
+
}) {
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
Current file-shape rule:
|
|
725
|
+
|
|
726
|
+
- frontend `@fn(...)` points at a normal `.ts` / `.js` host file with an exported default function
|
|
727
|
+
- it is not a function-body snippet
|
|
728
|
+
- a normal function declaration is expected
|
|
729
|
+
- imports are fine in principle because this is a normal host file, but keep helpers narrow and
|
|
730
|
+
local if you want maximum portability
|
|
731
|
+
|
|
732
|
+
### `@custom("./components/x.tsx")`
|
|
733
|
+
|
|
734
|
+
Use for React-specific rendering/custom input surfaces.
|
|
735
|
+
|
|
736
|
+
Column custom cell:
|
|
737
|
+
|
|
738
|
+
```yaml
|
|
739
|
+
columns:
|
|
740
|
+
- fareBrand @custom("./components/FareBrandCell.tsx")
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
Props:
|
|
744
|
+
|
|
745
|
+
- `{ value, record }`
|
|
746
|
+
|
|
747
|
+
Field custom component:
|
|
748
|
+
|
|
749
|
+
```yaml
|
|
750
|
+
fields:
|
|
751
|
+
- seat @custom("./components/SeatPicker.tsx")
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
Props:
|
|
755
|
+
|
|
756
|
+
- `{ value, onChange }`
|
|
757
|
+
|
|
758
|
+
Block custom component:
|
|
759
|
+
|
|
760
|
+
```yaml
|
|
761
|
+
blocks:
|
|
762
|
+
- type: custom
|
|
763
|
+
title: "Recovery"
|
|
764
|
+
custom: "./components/RecoveryPanel.tsx"
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
Props:
|
|
768
|
+
|
|
769
|
+
- self-managed by default
|
|
770
|
+
- on record-scoped relation pages, generated props may also include narrow `parentWorkflow` and
|
|
771
|
+
`relations` summaries
|
|
772
|
+
|
|
773
|
+
Host-file rules:
|
|
774
|
+
|
|
775
|
+
- custom `.ts` / `.tsx` / `.js` / `.jsx` escape files may use relative static imports
|
|
776
|
+
- they may also import local `.css` / `.module.css`
|
|
777
|
+
- keep raw CSS inside host files, not inside `.web.loj`
|
|
778
|
+
- build/dev currently preserve those imported dependencies in generated output
|
|
779
|
+
|
|
780
|
+
## Commands
|
|
781
|
+
|
|
782
|
+
- `rdsl validate <entry.web.loj>`
|
|
783
|
+
- `rdsl build <entry.web.loj> --out-dir <dir>`
|
|
784
|
+
- `rdsl inspect <entry.web.loj|build-dir> [--node <id>]`
|
|
785
|
+
- `rdsl trace <entry.web.loj|build-dir> <generated-file:line[:col]>`
|
|
786
|
+
|
|
787
|
+
## Guardrails
|
|
788
|
+
|
|
789
|
+
- Do not invent generic query DSL or backend join syntax around `readModel`.
|
|
790
|
+
- Do not invent style hooks for table internals, form sections, or responsive/mobile variants beyond
|
|
791
|
+
the current shell-level slice.
|
|
792
|
+
- Do not invent broader workflow/page router syntax beyond linked resource workflow surfaces.
|
|
793
|
+
- Do not leak React component APIs into `.web.loj`; keep React-specific escape code in `@custom`,
|
|
794
|
+
`@fn`, or host-side files.
|