create-restforge-skills 0.2.0 → 0.4.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 +148 -150
- package/package.json +33 -30
- package/skills/restforge/SKILL.md +832 -559
- package/skills/restforge/references/auth.md +2 -2
- package/skills/restforge/references/config-schema.md +238 -173
- package/skills/restforge/references/dbschema-catalog.md +245 -238
- package/skills/restforge/references/design-to-sdf.md +621 -618
- package/skills/restforge/references/field-validation.md +247 -173
- package/skills/restforge/references/rdf-advanced.md +695 -488
- package/skills/restforge/references/udf-catalog.md +623 -496
|
@@ -1,496 +1,623 @@
|
|
|
1
|
-
# Reference: UDF Catalog (UI Definition File)
|
|
2
|
-
|
|
3
|
-
> **Offline mirror.** This file mirrors `designer_get_udf_catalog`
|
|
4
|
-
>
|
|
5
|
-
>
|
|
6
|
-
>
|
|
7
|
-
>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
|
62
|
-
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
|
85
|
-
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
|
109
|
-
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
|
125
|
-
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
`
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
`
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
"
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
`
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
-
|
|
368
|
-
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
"
|
|
380
|
-
"
|
|
381
|
-
|
|
382
|
-
"
|
|
383
|
-
{
|
|
384
|
-
"
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
`
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
---
|
|
432
|
-
|
|
433
|
-
##
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
`
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
"
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
|
461
|
-
|
|
462
|
-
| `
|
|
463
|
-
| `
|
|
464
|
-
| `
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
1
|
+
# Reference: UDF Catalog (UI Definition File)
|
|
2
|
+
|
|
3
|
+
> **Offline mirror.** This file mirrors `designer_get_udf_catalog` (the
|
|
4
|
+
> `npx restforge-designer catalog` output, built from the validator constants) plus
|
|
5
|
+
> the rules in `restforge-handbook/catalogs/udf/`. The live tool is
|
|
6
|
+
> authoritative — when this file and the tool disagree, trust the tool, then
|
|
7
|
+
> update this file. Field types and features depend on the active plugin
|
|
8
|
+
> version; always re-ground with the tool (and `designer_list_plugins`) before
|
|
9
|
+
> defining a UDF payload.
|
|
10
|
+
|
|
11
|
+
Source: `designer_get_udf_catalog` — installed designer version.
|
|
12
|
+
Use as grounding before defining or editing any UDF payload.
|
|
13
|
+
|
|
14
|
+
**Derive, do not hand-write.** When a backend RDF exists, create the UDF with
|
|
15
|
+
`codegen_migrate_payload` and edit its output. Migrate derives field types,
|
|
16
|
+
`required`, `maxlength`, `decimalPlaces`, `format`, `temporalType`, lookups,
|
|
17
|
+
`details[]`, `statusFilter`, `statusBadge`, and `appConfig.dateFormat` /
|
|
18
|
+
`dateTimeFormat` from the RDF and the backend config. Hand-author a page only
|
|
19
|
+
when no RDF exists.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Table of Contents
|
|
24
|
+
|
|
25
|
+
1. [Payload Envelope](#payload-envelope)
|
|
26
|
+
2. [App Config](#app-config)
|
|
27
|
+
3. [Page Anatomy](#page-anatomy)
|
|
28
|
+
4. [Field Types](#field-types)
|
|
29
|
+
5. [Field Attributes](#field-attributes)
|
|
30
|
+
6. [Field Rows (Layout)](#field-rows-layout)
|
|
31
|
+
7. [Field States (Row Lock)](#field-states-row-lock)
|
|
32
|
+
8. [Features](#features)
|
|
33
|
+
9. [Data Source](#data-source)
|
|
34
|
+
10. [Navigation](#navigation)
|
|
35
|
+
11. [Homepage](#homepage)
|
|
36
|
+
12. [Dashboard Page](#dashboard-page)
|
|
37
|
+
13. [Master-Detail](#master-detail)
|
|
38
|
+
14. [Workflow Actions](#workflow-actions)
|
|
39
|
+
15. [ID Generation](#id-generation)
|
|
40
|
+
16. [Live Sync](#live-sync)
|
|
41
|
+
17. [Naming Conventions](#naming-conventions)
|
|
42
|
+
18. [Plugins](#plugins)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Payload Envelope
|
|
47
|
+
|
|
48
|
+
Root-level UDF structure:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"extends": "app-config.json",
|
|
53
|
+
"appConfig": { ... },
|
|
54
|
+
"pages": [ ... ],
|
|
55
|
+
"navigation": { "items": [ ... ] },
|
|
56
|
+
"homepage": "contact",
|
|
57
|
+
"liveSync": { ... }
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
| Key | Required | Notes |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `extends` | no | Path (relative to this file) to a base UDF. `appConfig` comes entirely from the base; `pages` from this file; other root keys (e.g. `auth`, `navigation`) merge with this file winning |
|
|
64
|
+
| `appConfig` | yes | See App Config |
|
|
65
|
+
| `pages` | yes | At least one entry: an inline page object or `{ "include": "pages/contact.json" }` |
|
|
66
|
+
| `navigation` | no | Object with `items[]` — see Navigation |
|
|
67
|
+
| `homepage` | no | `pageId` loaded at the app root |
|
|
68
|
+
| `liveSync` | no | WebSocket config — see Live Sync |
|
|
69
|
+
| `auth` | no | Read by auth-capable plugins — see Plugins |
|
|
70
|
+
|
|
71
|
+
An `include` file must itself contain a `pages` array (not a bare page object);
|
|
72
|
+
the loader appends its pages in place of the entry, recursively.
|
|
73
|
+
|
|
74
|
+
**Migrate output layout.** `codegen_migrate_payload` writes a split set into the
|
|
75
|
+
output folder (default `frontend/payload/`): `app-config.json`, one file per
|
|
76
|
+
page under `pages/`, an aggregator `<appCode>.json` that `extends` the config
|
|
77
|
+
and `include`s the pages, and snapshots under `.meta/pages/`. Point every
|
|
78
|
+
designer tool at the aggregator.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## App Config
|
|
83
|
+
|
|
84
|
+
| Property | Type | Required | Notes |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| `appName` | string | yes | Display name shown in page title and header |
|
|
87
|
+
| `appCode` | string | yes | Unique app identifier, kebab-case |
|
|
88
|
+
| `plugin` | string | yes | Output plugin: `vanilla-js-basic`, `vanilla-js-auth`, `vanilla-js-custom`, or a custom plugin |
|
|
89
|
+
| `apiBaseUrl` | string | yes | Backend base URL, joined with each page `apiPath`. A loopback host (`localhost`, `127.x`, `[::1]`, `0.0.0.0`) is re-resolved in the browser from the page's own host and port; use a relative path such as `/api/myapp` behind a reverse proxy on a non-standard port |
|
|
90
|
+
| `port` | integer | no | Port of the static preview server (`app-start.bat`), 1–65535; a string is rejected |
|
|
91
|
+
| `numberFormat` | object | no | `locale`: `en-US` (default, `12,500,000.00`) or `id-ID` (`12.500.000,00`); no other value is accepted. `currencyPrefix`: text before `format: "currency"` values, e.g. `"Rp "` |
|
|
92
|
+
| `dateFormat` | string | no | Must equal backend `DATEFORMAT`. One of `yyyy-MM-dd` (default), `dd/MM/yyyy`, `dd-MM-yyyy`, `MM/dd/yyyy`, `yyyy/MM/dd` |
|
|
93
|
+
| `dateTimeFormat` | string | no | Must equal backend `DATETIMEFORMAT`: `<date pattern> <time pattern>`, time `HH:mm`, `HH:mm:ss`, or `HH:mm:ss.SSS`. Default `yyyy-MM-dd HH:mm:ss.SSS` |
|
|
94
|
+
|
|
95
|
+
- Decimal places are **not** an app setting: `numberFormat.decimalPlaces` is
|
|
96
|
+
rejected. Put `decimalPlaces` on the field.
|
|
97
|
+
- `dateFormat` / `dateTimeFormat` are copied from the backend config by every
|
|
98
|
+
`codegen_migrate_payload` run and always overwritten there. Never keep a
|
|
99
|
+
locally edited value: the frontend cannot detect a pattern that differs from
|
|
100
|
+
the backend and would misread dates silently.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Page Anatomy
|
|
105
|
+
|
|
106
|
+
Each entry in `pages[]` is one page. CRUD page properties:
|
|
107
|
+
|
|
108
|
+
| Property | Type | Required | Notes |
|
|
109
|
+
|---|---|---|---|
|
|
110
|
+
| `pageId` | string | yes | `^[a-zA-Z0-9_-]+$`; used as the output file name |
|
|
111
|
+
| `pageTitle` | string | yes | Page heading |
|
|
112
|
+
| `pageSubtitle` | string | no | Text under the title |
|
|
113
|
+
| `pageIcon` | string | no | Icon name |
|
|
114
|
+
| `pageGroup` | array of string | no | Sidebar group path, max 2 levels (e.g. `["Master", "People"]`); drives auto-derived navigation |
|
|
115
|
+
| `pageType` | string | no | `"crud"` (default) or `"dashboard"` |
|
|
116
|
+
| `pageSubject` | string | no | Singular object name used in `{addVerb} {pageSubject}`, `Edit {pageSubject}`, and success messages. Default `pageTitle` |
|
|
117
|
+
| `addVerb` | string | no | `Add` (default), `Create`, `Upload`, or `Invite`; anything else is rejected |
|
|
118
|
+
| `showNewBadge` | boolean | no | `New` badge on rows created today, default `true`; needs `created_at` in the list data |
|
|
119
|
+
| `apiPath` | string | yes | Backend resource path, joined with `apiBaseUrl` |
|
|
120
|
+
| `primaryKey` | string | yes | Primary key column; need not be in `fields` |
|
|
121
|
+
| `displayField` | string | yes | Field used as the record label (dialogs, breadcrumb) |
|
|
122
|
+
| `versionField` | string | no | Row-version column sent as `options.expectedVersion` on edit; the backend RDF needs a `concurrency` block for it to be checked |
|
|
123
|
+
| `actions` | object | no | `{ "create", "read", "update", "delete" }` booleans, default all `true`. Hides basic CRUD controls when there is no `auth` block; with `auth`, permissions decide and `false` only produces a warning |
|
|
124
|
+
| `fields` | array | yes | Field definitions (min 1) |
|
|
125
|
+
| `fieldRows` | array | no | Form grid layout |
|
|
126
|
+
| `fieldStates` | array | no | Status values that lock a row |
|
|
127
|
+
| `features` | object | no | Toolbar and form features |
|
|
128
|
+
| `details` | array | no | Master-detail grids |
|
|
129
|
+
| `workflow` | object | no | `statusField` and `transitions` |
|
|
130
|
+
| `workflowActions` | array | no | Status transition buttons |
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Field Types
|
|
135
|
+
|
|
136
|
+
8 field types:
|
|
137
|
+
|
|
138
|
+
| Type | Renders as | Type-specific attributes |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `text` | `<input type="text">` | `maxlength`, `placeholder` |
|
|
141
|
+
| `textarea` | `<textarea>` | `rows`, `maxlength`, `placeholder` |
|
|
142
|
+
| `number` | number input | `min`, `max`, `step`, `format`, `decimalPlaces` |
|
|
143
|
+
| `checkbox` | toggle switch | `defaultValue`, `checkboxText.checked`, `checkboxText.unchecked`, `statusBadge` |
|
|
144
|
+
| `select` | Select2 dropdown | `dataSource` (required), `tableField`, `dependsOn`, `statusBadge` (static only) |
|
|
145
|
+
| `date` | Flatpickr | `dateFormat` |
|
|
146
|
+
| `timestamp` | Flatpickr with time | `dateFormat`, `temporalType` |
|
|
147
|
+
| `time` | Flatpickr time only | — |
|
|
148
|
+
|
|
149
|
+
- `date` is a calendar date; it never shifts with the reader's time zone.
|
|
150
|
+
- `timestamp` with `temporalType: "timestamp"` (default) is wall-clock time shown
|
|
151
|
+
as stored. `temporalType: "timestamptz"` is an absolute moment converted to
|
|
152
|
+
the reader's time zone and sent as ISO UTC; migrate sets it for `timestamptz`
|
|
153
|
+
columns.
|
|
154
|
+
- A `select` without `dataSource` renders empty (warning).
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Field Attributes
|
|
159
|
+
|
|
160
|
+
Required on every field: `name`, `label`, `type`.
|
|
161
|
+
|
|
162
|
+
| Attribute | Type | Default | Notes |
|
|
163
|
+
|---|---|---|---|
|
|
164
|
+
| `name` | string | — | Column name, snake_case |
|
|
165
|
+
| `label` | string | — | Form label and table header |
|
|
166
|
+
| `type` | string | — | One of the 8 types |
|
|
167
|
+
| `required` | boolean | `false` | Client-side required check |
|
|
168
|
+
| `readonly` | boolean | `false` | Read-only in every mode; not validated, not sent on save |
|
|
169
|
+
| `readonlyOnEdit` | boolean | `false` | Read-only in edit mode only (e.g. a code set once on create). Cannot be combined with `readonly` or `editorMode` |
|
|
170
|
+
| `editorMode` | string | — | `"hidden"`: rendered but invisible, filled from `defaultValue` on add and from the record on edit, then sent like a normal field. `"readonly"`: same as `readonly: true` |
|
|
171
|
+
| `inTable` | boolean | `false` | Show as a list column |
|
|
172
|
+
| `tableOrder` | integer | — | Column order (from 1); a warning when `inTable` is set without it |
|
|
173
|
+
| `tableField` | string | — | Column to display in the list instead of `name`, e.g. `city_name` for `city_id` |
|
|
174
|
+
| `width` | string | auto | CSS width of the list column, e.g. `"200px"`, `"30%"` |
|
|
175
|
+
| `placeholder` | string | — | Input placeholder |
|
|
176
|
+
| `maxlength` | integer | — | `text` / `textarea` |
|
|
177
|
+
| `defaultValue` | any | — | Value on add: a literal, `today`/`now`, or an idgen object |
|
|
178
|
+
| `format` | string | — | `number` only: `"number"` (thousand separators) or `"currency"` (prefix from `numberFormat.currencyPrefix`). Without `format`, the value is shown raw |
|
|
179
|
+
| `decimalPlaces` | integer | `0` | `number` only, 0–20; migrate derives it from the column scale |
|
|
180
|
+
| `dateFormat` | string | `appConfig.dateFormat` | `date` / `timestamp`: display pattern of the date part for this field only; parsing always uses the app patterns |
|
|
181
|
+
| `temporalType` | string | `timestamp` | `timestamp` fields only: `timestamp` or `timestamptz` |
|
|
182
|
+
| `statusBadge` | boolean | `false` | Show as the fixed-palette Status column. `checkbox` or static `select` only; max one per page. A `checkbox` with `statusBadge` also adds `Deactivate` / `Activate` to the row Actions menu |
|
|
183
|
+
| `checkboxText` | object | — | `{ "checked": "Active", "unchecked": "Inactive" }` |
|
|
184
|
+
|
|
185
|
+
`valueFormat` is no longer supported and is rejected. `min`/`max` on `date`
|
|
186
|
+
fields are not read by the validator.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Field Rows (Layout)
|
|
191
|
+
|
|
192
|
+
`fieldRows[]` places several fields side by side in one CSS grid row. Without
|
|
193
|
+
it, fields render one per row.
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
"fieldRows": [
|
|
197
|
+
{ "fields": ["first_name", "last_name"] },
|
|
198
|
+
{ "fields": ["email", "phone", "status"] }
|
|
199
|
+
]
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
- Each name refers to a `name` in `fields[]`; the column count equals the number
|
|
203
|
+
of fields in the row.
|
|
204
|
+
- A field in `fields` that is not referenced in `fieldRows` is not rendered
|
|
205
|
+
(warning), except `editorMode: "hidden"` fields.
|
|
206
|
+
- `features.fieldLayout` `"horizontal"` puts the label left of the input;
|
|
207
|
+
`"vertical"` (default) stacks them.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Field States (Row Lock)
|
|
212
|
+
|
|
213
|
+
`fieldStates[]` marks status values that make a row read-only: `Edit`,
|
|
214
|
+
`Delete`, and `Deactivate`/`Activate` are shown disabled with an info tooltip.
|
|
215
|
+
It does not hide or lock individual form fields.
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
"fieldStates": [
|
|
219
|
+
{ "when": { "status": ["posted", "cancelled"] }, "state": "readonly" }
|
|
220
|
+
]
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
| Property | Notes |
|
|
224
|
+
|---|---|
|
|
225
|
+
| `when` | Key = the status field name (`workflow.statusField`, default `status`); value = array of status values |
|
|
226
|
+
| `state` | `"readonly"` |
|
|
227
|
+
|
|
228
|
+
Takes effect only with `vanilla-js-auth` or `vanilla-js-custom`, and only on a
|
|
229
|
+
page with at least one `workflowActions` item. The validator also accepts
|
|
230
|
+
`state: "hidden"`, a `fields` list, and a string `when`, but the generator
|
|
231
|
+
ignores them. `fieldStates` is frontend only: enforce the same rule in the
|
|
232
|
+
backend with a component hook (e.g. `onBeforeUpdate`, `onBeforeDelete`).
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Features
|
|
237
|
+
|
|
238
|
+
`features` is an object on a CRUD page.
|
|
239
|
+
|
|
240
|
+
| Feature | Type | Notes |
|
|
241
|
+
|---|---|---|
|
|
242
|
+
| `enableSearch` | boolean | Search box; server-side search |
|
|
243
|
+
| `enableStatusFilter` | boolean | Status dropdown in the toolbar; configured by `statusFilter` |
|
|
244
|
+
| `statusFilter` | object | `{ field, label, options: [{ value, text }] }`; required when `enableStatusFilter` is `true` |
|
|
245
|
+
| `enableDataFilter` | boolean | Extra filters; configured by `dataFilters` |
|
|
246
|
+
| `dataFilters` | array | Entries with keys `name`, `field`, `label`, `dataSource`, `dependsOn`, `display` (`inline` or `dropdown`), `width`. `field` is required for `vanilla-js-auth` and `vanilla-js-custom` |
|
|
247
|
+
| `enableLiveSync` | boolean | WebSocket refresh; needs the root `liveSync` block |
|
|
248
|
+
| `autoRefresh` | object | `{ "interval": <seconds, min 10> }` polling; not together with `enableLiveSync`; not in `vanilla-js-basic` |
|
|
249
|
+
| `fieldLayout` | string | `"vertical"` (default) or `"horizontal"` |
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
"features": {
|
|
253
|
+
"enableSearch": true,
|
|
254
|
+
"enableStatusFilter": true,
|
|
255
|
+
"statusFilter": {
|
|
256
|
+
"field": "is_active",
|
|
257
|
+
"label": "Status",
|
|
258
|
+
"options": [ { "value": "true", "text": "Active" }, { "value": "false", "text": "Inactive" } ]
|
|
259
|
+
},
|
|
260
|
+
"enableDataFilter": true,
|
|
261
|
+
"dataFilters": [
|
|
262
|
+
{ "name": "city_id", "field": "city_id", "label": "City",
|
|
263
|
+
"dataSource": { "type": "api", "resource": "city", "select": ["city_id", "city_name"] } }
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
At most 2 filters (status + data filters) are shown inline; the rest move to a
|
|
269
|
+
"More Filters" popup unless `display` overrides it. Migrate generates
|
|
270
|
+
`statusFilter` for the main status column.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Data Source
|
|
275
|
+
|
|
276
|
+
Used by `select` fields and `dataFilters[]`.
|
|
277
|
+
|
|
278
|
+
**Type: static**
|
|
279
|
+
```json
|
|
280
|
+
"dataSource": {
|
|
281
|
+
"type": "static",
|
|
282
|
+
"options": [
|
|
283
|
+
{ "value": "active", "text": "Active" },
|
|
284
|
+
{ "value": "inactive", "text": "Inactive" }
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
With `required: true` the first option is preselected; otherwise a
|
|
290
|
+
`-- Select --` placeholder comes first.
|
|
291
|
+
|
|
292
|
+
**Type: api**
|
|
293
|
+
```json
|
|
294
|
+
"dataSource": {
|
|
295
|
+
"type": "api",
|
|
296
|
+
"resource": "category",
|
|
297
|
+
"select": ["category_id", "category_name"]
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
| Key | Notes |
|
|
302
|
+
|---|---|
|
|
303
|
+
| `resource` | Backend resource; the request goes to `POST <apiBaseUrl>/<resource>/lookup`, which answers `{ id, text }` rows (configured by RDF `fieldNameLookup`) |
|
|
304
|
+
| `select` | Columns requested from `/lookup` |
|
|
305
|
+
| `url` | Explicit lookup path; used only in master-detail grids, ignored on master pages |
|
|
306
|
+
| `lookupMode` | `"static"` (load once, default) or `"dynamic"` (AJAX search while typing) |
|
|
307
|
+
| `lookupDisplay` | `"select2"` (default) or `"modal"` (search dialog with a table) |
|
|
308
|
+
| `optionColumns` | `[{ column, format: "number", decimalPlaces, align }]`; columns shown in the dropdown or modal, each must be in `select` |
|
|
309
|
+
| `autofill` | Copies columns of the chosen row into other fields of the same form or grid row |
|
|
310
|
+
|
|
311
|
+
`dependsOn` (a sibling `select` field name) turns a field into a cascade. Set
|
|
312
|
+
`tableField` on the field so the list shows the label column, not the id.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Navigation
|
|
317
|
+
|
|
318
|
+
`navigation` is an **object** with `items[]`. Without it, the sidebar is
|
|
319
|
+
derived from `pageGroup`; with it, `pageGroup` is ignored (warning).
|
|
320
|
+
|
|
321
|
+
```json
|
|
322
|
+
"navigation": {
|
|
323
|
+
"items": [
|
|
324
|
+
{ "type": "page", "pageRef": "dashboard", "icon": "home" },
|
|
325
|
+
{ "type": "separator" },
|
|
326
|
+
{
|
|
327
|
+
"type": "group",
|
|
328
|
+
"label": "Master Data",
|
|
329
|
+
"icon": "data",
|
|
330
|
+
"children": [
|
|
331
|
+
{ "type": "page", "pageRef": "category" },
|
|
332
|
+
{ "type": "page", "pageRef": "supplier", "badgeColor": "info" }
|
|
333
|
+
]
|
|
334
|
+
},
|
|
335
|
+
{ "type": "link", "label": "Payroll Report", "href": "report-payroll.html", "permission": "REPORT_PAYROLL_READ" }
|
|
336
|
+
]
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
| Item type | Keys |
|
|
341
|
+
|---|---|
|
|
342
|
+
| `page` | `pageRef` (required, a `pageId`), `label` (default `pageTitle`), `icon`, `badgeColor` |
|
|
343
|
+
| `group` | `label` (required), `children` (required, non-empty), `icon` |
|
|
344
|
+
| `link` | `label` and `href` (required; a hand-written HTML file), `permission` (string or array; used only with an `auth` block), `icon`, `badgeColor` |
|
|
345
|
+
| `separator` | — |
|
|
346
|
+
|
|
347
|
+
- `badgeColor`: `danger`, `info`, `primary`, `success`, `warning`.
|
|
348
|
+
- Maximum nesting depth: 3. `icon` renders at depth 1 only.
|
|
349
|
+
- Icons are Keenicons (`vanilla-js-auth`): one word (`"data"`) renders
|
|
350
|
+
`ki-outline ki-data fs-2`; a value with spaces is used as the class string.
|
|
351
|
+
Do not use Tabler classes (`ti ti-*`).
|
|
352
|
+
- With an `auth` block, a `page` item is hidden when the user lacks
|
|
353
|
+
`permissions.<pageRef>.read`; groups whose items are all hidden disappear.
|
|
354
|
+
- Re-running migrate keeps a hand-built `navigation` and adds new pages at the
|
|
355
|
+
top level.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Homepage
|
|
360
|
+
|
|
361
|
+
`homepage` sets the page loaded at the app root.
|
|
362
|
+
|
|
363
|
+
```json
|
|
364
|
+
"homepage": "dashboard-main"
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
- Value must match a `pageId` in `pages[]`; CRUD and dashboard pages both work.
|
|
368
|
+
- When absent, the first page in `pages[]` is the default.
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## Dashboard Page
|
|
373
|
+
|
|
374
|
+
A page with `pageType: "dashboard"` renders a widget grid. It reads data from
|
|
375
|
+
URLs you declare; it does not call `/aggregate` by itself.
|
|
376
|
+
|
|
377
|
+
```json
|
|
378
|
+
{
|
|
379
|
+
"pageId": "overview",
|
|
380
|
+
"pageType": "dashboard",
|
|
381
|
+
"pageTitle": "Overview",
|
|
382
|
+
"refreshInterval": 300,
|
|
383
|
+
"dataSources": {
|
|
384
|
+
"revenueMonthly": { "url": "/api/myapp/dash-sales/dashboard", "method": "POST", "body": {} }
|
|
385
|
+
},
|
|
386
|
+
"rows": [
|
|
387
|
+
{
|
|
388
|
+
"columns": [
|
|
389
|
+
{
|
|
390
|
+
"colSpan": { "base": 12, "md": 6, "lg": 4 },
|
|
391
|
+
"widgets": [
|
|
392
|
+
{
|
|
393
|
+
"widgetId": "revenue-chart",
|
|
394
|
+
"widgetType": "column",
|
|
395
|
+
"title": "Revenue per Month",
|
|
396
|
+
"dataSource": "revenueMonthly",
|
|
397
|
+
"chartEngine": "apexcharts",
|
|
398
|
+
"chart": { "xField": "month", "yField": "revenue" }
|
|
399
|
+
}
|
|
400
|
+
]
|
|
401
|
+
}
|
|
402
|
+
]
|
|
403
|
+
}
|
|
404
|
+
]
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
| Part | Rules |
|
|
409
|
+
|---|---|
|
|
410
|
+
| `pageId` | `^[a-z][a-z0-9-]*$` (no underscore, no uppercase); no `dash-` prefix needed |
|
|
411
|
+
| Allowed page keys | `dataSources`, `pageGroup`, `pageIcon`, `pageId`, `pageSubject`, `pageSubtitle`, `pageTitle`, `pageType`, `refreshInterval`, `rows` |
|
|
412
|
+
| Forbidden keys (error) | `apiPath`, `primaryKey`, `displayField`, `fields`, `fieldRows`, `details`, `workflowActions`, `workflow`, `fieldStates`, `features` |
|
|
413
|
+
| `dataSources` | Required object `name → { url, method, body }`; `method` is `GET` (default) or `POST`, `body` is sent with `POST` only |
|
|
414
|
+
| `refreshInterval` | Seconds, 15–3600, default 300 |
|
|
415
|
+
| `colSpan` | Per breakpoint `base`, `sm`, `md`, `lg`, `xl`, `xxl`, values 1–12 |
|
|
416
|
+
| `widgets[]` | `widgetId` (`^[a-z][a-z0-9_-]*$`, unique per page), `widgetType`, `title`, `subtitle`, `dataSource` (a `dataSources` key), `chartEngine`, `chart`, `display` |
|
|
417
|
+
|
|
418
|
+
7 widget types:
|
|
419
|
+
|
|
420
|
+
| `widgetType` | Needs `chart` + `chartEngine` | `chart` keys |
|
|
421
|
+
|---|---|---|
|
|
422
|
+
| `column`, `bar`, `area` | yes | `xField`, `yField`, `format` |
|
|
423
|
+
| `pie` | yes | `xField`, `yField`, `shape` (`pie`, `donut`, `semi-pie`, `semi-donut`), `format` |
|
|
424
|
+
| `mini-bar` | yes | `xField`, `yField`, `format` |
|
|
425
|
+
| `progress`, `list` | no — `chart`/`chartEngine` are rejected | — |
|
|
426
|
+
|
|
427
|
+
`chartEngine`: `apexcharts` or `amcharts`. `chart.format.<xAxis|yAxis|tooltip|dataLabel>`
|
|
428
|
+
accepts `prefix`, `suffix`, `decimalPlaces`. `chart.library` and
|
|
429
|
+
`chart.orientation` are deprecated.
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Master-Detail
|
|
434
|
+
|
|
435
|
+
A CRUD page with `details[]` renders the master form plus one editable grid per
|
|
436
|
+
detail. Migrate builds `details[]` from the RDF `masterDetail` block.
|
|
437
|
+
|
|
438
|
+
```json
|
|
439
|
+
"details": [
|
|
440
|
+
{
|
|
441
|
+
"detailId": "items",
|
|
442
|
+
"detailTitle": "Order Items",
|
|
443
|
+
"primaryKey": "item_id",
|
|
444
|
+
"fields": [
|
|
445
|
+
{ "name": "product_id", "label": "Product", "type": "select",
|
|
446
|
+
"dataSource": { "type": "api", "resource": "product", "select": ["product_id", "product_name", "price"],
|
|
447
|
+
"autofill": { "price": "price" } } },
|
|
448
|
+
{ "name": "qty", "label": "Qty", "type": "number", "defaultValue": 1 },
|
|
449
|
+
{ "name": "price", "label": "Price", "type": "number", "format": "number", "decimalPlaces": 2 },
|
|
450
|
+
{ "name": "subtotal", "label": "Subtotal", "type": "number", "readonly": true,
|
|
451
|
+
"calculated": { "formula": "qty * price" }, "format": "number", "decimalPlaces": 2 }
|
|
452
|
+
],
|
|
453
|
+
"summary": { "totalItems": true, "totalQtyField": "qty", "grandTotalField": "subtotal" }
|
|
454
|
+
}
|
|
455
|
+
]
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
| Property | Required | Notes |
|
|
459
|
+
|---|---|---|
|
|
460
|
+
| `detailId` | yes | Detail key in the composite payload |
|
|
461
|
+
| `detailTitle` | no | Grid heading; default = `detailId` in Title Case |
|
|
462
|
+
| `primaryKey` | yes | Detail row primary key |
|
|
463
|
+
| `fields` | yes | Same field structure as the master |
|
|
464
|
+
| `summary` | no | `totalItems`, `totalQtyField`, `grandTotalField` |
|
|
465
|
+
|
|
466
|
+
- `calculated.formula` supports only `"<fieldA> * <fieldB>"`.
|
|
467
|
+
- Only one detail level; no nested details.
|
|
468
|
+
- The backend RDF needs `masterDetail` and the composite actions.
|
|
469
|
+
- To keep a user-defined row order, store it in a `line_number` column the user
|
|
470
|
+
fills in; the server does not number rows.
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## Workflow Actions
|
|
475
|
+
|
|
476
|
+
`workflowActions[]` adds a **Change Status** item to each row's Actions menu.
|
|
477
|
+
The dialog shows one button per allowed target status.
|
|
478
|
+
|
|
479
|
+
```json
|
|
480
|
+
"workflow": {
|
|
481
|
+
"statusField": "status",
|
|
482
|
+
"transitions": {
|
|
483
|
+
"pending": ["paid", "cancelled"],
|
|
484
|
+
"paid": ["shipped"],
|
|
485
|
+
"shipped": [],
|
|
486
|
+
"cancelled": []
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
"workflowActions": [
|
|
490
|
+
{
|
|
491
|
+
"actionId": "paid",
|
|
492
|
+
"label": "Paid",
|
|
493
|
+
"icon": "check",
|
|
494
|
+
"style": "success",
|
|
495
|
+
"confirm": { "title": "Mark this order as paid?", "message": "Stock will be deducted.", "confirmButton": "Yes, mark as paid", "cancelButton": "Cancel" },
|
|
496
|
+
"api": { "endpoint": "sales-order/change-status", "payload": { "sales_order_id": "$primaryKey", "status": "paid" } },
|
|
497
|
+
"onSuccess": { "notification": { "type": "success", "message": "Order marked as paid." } },
|
|
498
|
+
"onError": { "display": "modal", "title": "Payment rejected" }
|
|
499
|
+
}
|
|
500
|
+
]
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
| Property | Notes |
|
|
504
|
+
|---|---|
|
|
505
|
+
| `workflow.statusField` | Required when `workflowActions` exists; the column must be returned by `/datatables` |
|
|
506
|
+
| `workflow.transitions` | `current → [targets]`. A button renders only for a target listed here **and** having an action with the same `actionId`. Without `transitions` no button ever shows. Keep it identical to the RDF `workflow.transitions` |
|
|
507
|
+
| `actionId` | Required, unique per page, **equal to the target status** |
|
|
508
|
+
| `label` | Required |
|
|
509
|
+
| `icon`, `style` | Optional; `style` = `primary` (default), `success`, `danger`, `secondary`, `info`, `warning` |
|
|
510
|
+
| `confirm` | Optional: `title`, `message`, `confirmButton`, `cancelButton` |
|
|
511
|
+
| `api` | Required: `endpoint` (default `<apiPath>/change-status`, no leading slash) and/or `payload` (`"$primaryKey"` is replaced) |
|
|
512
|
+
| `onSuccess.notification` | `{ type, message }` |
|
|
513
|
+
| `onError` | `{ display: "modal" \| "toast", title }` |
|
|
514
|
+
|
|
515
|
+
`confirm.summary[]` is accepted by the validator but not rendered. A backend
|
|
516
|
+
rejection (422 invalid transition, 400 hook rejection, 502 blocking hook failure)
|
|
517
|
+
is shown with the response `message`.
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## ID Generation
|
|
522
|
+
|
|
523
|
+
`defaultValue.source: "idgen"` fills a field from the backend ID generator.
|
|
524
|
+
Requires `IDGEN_ENABLED=true` in the backend config.
|
|
525
|
+
|
|
526
|
+
```json
|
|
527
|
+
{
|
|
528
|
+
"name": "invoice_no",
|
|
529
|
+
"type": "text",
|
|
530
|
+
"defaultValue": {
|
|
531
|
+
"source": "idgen",
|
|
532
|
+
"mode": "number",
|
|
533
|
+
"resource": "invoice",
|
|
534
|
+
"format": "yyyymm",
|
|
535
|
+
"numDigits": 5,
|
|
536
|
+
"separator": "-",
|
|
537
|
+
"reserve": true,
|
|
538
|
+
"ttl": 60
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
| Key | Notes |
|
|
544
|
+
|---|---|
|
|
545
|
+
| `mode` | `number`, `pin`, `code`, `serial`, `random` |
|
|
546
|
+
| `resource` | Required namespace; must not contain `:` |
|
|
547
|
+
| `format` | `number` mode: `text`, `yyyy`, `yyyymm`, `yyyymmdd` |
|
|
548
|
+
| `numDigits`, `separator` | `number` mode |
|
|
549
|
+
| `digits` | `pin` mode (default 6) |
|
|
550
|
+
| `pattern` | `code`, `serial`, `random` modes |
|
|
551
|
+
| `reserve`, `ttl` | Reserve the value while the form is open; max one `reserve: true` field per page |
|
|
552
|
+
|
|
553
|
+
Allowed field types: `number`, `text`, `textarea`.
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
## Live Sync
|
|
558
|
+
|
|
559
|
+
WebSocket-based list refresh. Requires `LIVE_SYNC_ENABLED=true` and
|
|
560
|
+
`LIVE_SYNC_PORT` in the backend config.
|
|
561
|
+
|
|
562
|
+
Root-level config:
|
|
563
|
+
```json
|
|
564
|
+
"liveSync": {
|
|
565
|
+
"url": "ws://localhost:3033",
|
|
566
|
+
"apiKey": "your-api-key"
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
Per-page activation: `"features": { "enableLiveSync": true }`. `url` must start
|
|
571
|
+
with `ws://` or `wss://`. A page with `enableLiveSync` and no root `liveSync` is
|
|
572
|
+
an error; a `liveSync` block no page uses is a warning.
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
|
|
576
|
+
## Naming Conventions
|
|
577
|
+
|
|
578
|
+
| Element | Pattern | Example |
|
|
579
|
+
|---|---|---|
|
|
580
|
+
| `pageId` (CRUD) | `^[a-zA-Z0-9_-]+$` | `sales-order`, `product_list` |
|
|
581
|
+
| `pageId` (dashboard) | `^[a-z][a-z0-9-]*$` | `overview`, `monthly-sales` |
|
|
582
|
+
| `widgetId` | `^[a-z][a-z0-9_-]*$` | `chart-revenue`, `kpi_orders` |
|
|
583
|
+
| field `name` | snake_case | `order_no`, `customer_id` |
|
|
584
|
+
| `appCode` | kebab-case | `sales-app`, `inventory` |
|
|
585
|
+
| `pageGroup` entries | consistent casing | `["Master Data"]` |
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
## Plugins
|
|
590
|
+
|
|
591
|
+
Built-in plugins. Run `designer_list_plugins` to confirm what the installed
|
|
592
|
+
version provides — do not hardcode this list.
|
|
593
|
+
|
|
594
|
+
| Plugin | Auth | Notes |
|
|
595
|
+
|---|---|---|
|
|
596
|
+
| `vanilla-js-basic` | None | Standard CRUD app, no login flow |
|
|
597
|
+
| `vanilla-js-auth` | Auth + RBAC | Login page, token refresh, role-based access |
|
|
598
|
+
| `vanilla-js-custom` | Auth + RBAC | Customisable markup/CSS/JS; auth + RBAC capable (confirm via `designer_list_plugins`) |
|
|
599
|
+
|
|
600
|
+
Plugin auth is built into the app at generation time. Disable it with
|
|
601
|
+
`noAuth: true` (`--no-auth`) on `designer_init_project` to get the plugin's UI
|
|
602
|
+
without auth. **Plugin auth (with RBAC) is distinct from the embedded `rfx_auth`
|
|
603
|
+
extension** (`designer_auth_create`, no RBAC) — see references/auth.md.
|
|
604
|
+
|
|
605
|
+
Auth-capable plugins read a root-level `auth` block, not `appConfig` keys:
|
|
606
|
+
|
|
607
|
+
```json
|
|
608
|
+
"auth": {
|
|
609
|
+
"appCode": "MY_APP",
|
|
610
|
+
"authApiUrl": "http://localhost:3000/api/auth",
|
|
611
|
+
"idleTimeoutMinutes": 30
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
`designer_init_project` fills this block from its auth app code and idle
|
|
616
|
+
timeout options. It also leaves an `appConfig.authAppCode` copy, which no plugin
|
|
617
|
+
reads; edit `auth.appCode`, never `appConfig.authAppCode`.
|
|
618
|
+
|
|
619
|
+
**Custom plugins:**
|
|
620
|
+
Use `designer_scaffold_plugin` to generate a plugin template. The plugin
|
|
621
|
+
structure contains `plugin.json` (metadata) and Jinja2 templates for each
|
|
622
|
+
file type (HTML, JS, CSS). Use `designer_inspect_plugin` to verify capabilities
|
|
623
|
+
before referencing a plugin in a UDF payload.
|