create-restforge-skills 0.1.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.
@@ -0,0 +1,489 @@
1
+ # Reference: UDF Catalog (UI Definition File)
2
+
3
+ > **Offline mirror.** This file mirrors `designer_get_udf_catalog` from the
4
+ > installed RESTForge designer. The live tool is authoritative — when this file
5
+ > and the tool disagree, trust the tool, then update this file. Field types and
6
+ > features depend on the active plugin version; always re-ground with the tool
7
+ > (and `designer_list_plugins`) before defining a UDF payload.
8
+
9
+ Source: `designer_get_udf_catalog` — installed designer version.
10
+ Use as grounding before defining or editing any UDF payload.
11
+
12
+ ---
13
+
14
+ ## Table of Contents
15
+
16
+ 1. [Payload Envelope](#payload-envelope)
17
+ 2. [App Config](#app-config)
18
+ 3. [Page Anatomy](#page-anatomy)
19
+ 4. [Field Types](#field-types)
20
+ 5. [Field Attributes](#field-attributes)
21
+ 6. [Field Rows (Layout)](#field-rows-layout)
22
+ 7. [Field States (Conditional)](#field-states-conditional)
23
+ 8. [Features](#features)
24
+ 9. [Data Source](#data-source)
25
+ 10. [Navigation](#navigation)
26
+ 11. [Homepage](#homepage)
27
+ 12. [Dashboard Page](#dashboard-page)
28
+ 13. [Master-Detail](#master-detail)
29
+ 14. [Workflow Actions](#workflow-actions)
30
+ 15. [ID Generation](#id-generation)
31
+ 16. [Live Sync](#live-sync)
32
+ 17. [Naming Conventions](#naming-conventions)
33
+ 18. [Plugins](#plugins)
34
+
35
+ ---
36
+
37
+ ## Payload Envelope
38
+
39
+ Root-level UDF structure. All properties at the top level.
40
+
41
+ ```json
42
+ {
43
+ "appConfig": { ... },
44
+ "pages": [ ... ],
45
+ "navigation": [ ... ],
46
+ "homepage": "page-id"
47
+ }
48
+ ```
49
+
50
+ **Multi-file support:**
51
+ - `extends`: inherit from a base file — `"extends": "./base.json"`
52
+ - `include`: inline another file's pages array — `"include": "./orders-pages.json"`
53
+
54
+ ---
55
+
56
+ ## App Config
57
+
58
+ | Property | Type | Required | Notes |
59
+ |---|---|---|---|
60
+ | `appName` | string | yes | Display name shown in page title and header |
61
+ | `appCode` | string | yes | Unique app identifier, kebab-case, used in file naming |
62
+ | `plugin` | string | yes | Output plugin: `vanilla-js-basic`, `vanilla-js-auth`, or custom |
63
+ | `apiBaseUrl` | string | yes | Base URL of the RESTForge backend API |
64
+ | `port` | integer | no | Local dev server port; default: `3000` |
65
+ | `numberFormat` | string | no | Locale for number display; default: `"id-ID"` |
66
+
67
+ Plugin-specific properties (for `vanilla-js-auth`):
68
+ - `authAppCode` — app code used for the auth API path
69
+ - `idleTimeout` — session idle timeout in seconds
70
+
71
+ ---
72
+
73
+ ## Page Anatomy
74
+
75
+ Each entry in `pages[]` represents one page in the application.
76
+
77
+ | Property | Type | Required | Notes |
78
+ |---|---|---|---|
79
+ | `pageId` | string | yes | Unique identifier; CRUD: `^[a-zA-Z0-9_-]+$`; dashboard: `^dash-[a-z0-9-]+$` |
80
+ | `pageTitle` | string | yes | Display title shown in header and sidebar |
81
+ | `pageSubtitle` | string | no | Subtitle shown below title |
82
+ | `pageIcon` | string | no | Icon class (e.g., Tabler Icons: `"ti ti-home"`) |
83
+ | `pageGroup` | string | no | Sidebar group name (Title Case); auto-derives navigation |
84
+ | `pageType` | string | no | `"crud"` (default) or `"dashboard"` |
85
+ | `apiPath` | string | yes (CRUD) | Backend endpoint path; e.g., `"/customer"` |
86
+ | `primaryKey` | string | yes (CRUD) | Primary key field name; e.g., `"customer_id"` |
87
+ | `displayField` | string | yes (CRUD) | Field used as record label in lookups |
88
+ | `fields` | array | yes (CRUD) | Field definitions for the page |
89
+ | `fieldRows` | array | no | Layout grouping for multi-column forms |
90
+ | `fieldStates` | array | no | Conditional visibility/readonly rules |
91
+ | `details` | array | no | Detail pages for master-detail pattern |
92
+ | `workflow` | object | no | Workflow state machine config |
93
+ | `workflowActions` | array | no | Action buttons for workflow transitions |
94
+ | `features` | object | no | Page-level feature flags |
95
+
96
+ ---
97
+
98
+ ## Field Types
99
+
100
+ 8 field types. Each type maps to a specific HTML element and optional library.
101
+
102
+ | Type | HTML Element | Library | Key attributes |
103
+ |---|---|---|---|
104
+ | `text` | `<input type="text">` | — | `maxlength`, `placeholder` |
105
+ | `textarea` | `<textarea>` | — | `rows`, `maxlength` |
106
+ | `number` | `<input type="number">` | — | `min`, `max`, `step` |
107
+ | `checkbox` | toggle switch | — | `defaultValue` (true/false), `checkboxText` |
108
+ | `select` | `<select>` | Select2 | `dataSource`, `tableField` |
109
+ | `date` | date picker | Flatpickr | `minDate`, `maxDate`, `dateFormat` |
110
+ | `timestamp` | datetime picker | Flatpickr | `minDate`, `maxDate`, `dateFormat` |
111
+ | `time` | time picker | Flatpickr | `minTime`, `maxTime` |
112
+
113
+ **Notes:**
114
+ - `select` requires a `dataSource` to populate options.
115
+ - `date`/`timestamp`/`time` require Flatpickr assets bundled by the plugin.
116
+ - `checkbox` renders as a toggle switch, not a standard checkbox input.
117
+
118
+ ---
119
+
120
+ ## Field Attributes
121
+
122
+ Common attributes for all field types.
123
+
124
+ | Attribute | Type | Notes |
125
+ |---|---|---|
126
+ | `name` | string | Field name matching the RDF payload field; snake_case |
127
+ | `label` | string | Display label shown on form and table header |
128
+ | `type` | string | One of the 8 field types above |
129
+ | `required` | boolean | Frontend validation — marks field as mandatory |
130
+ | `readonly` | boolean | Renders as read-only input |
131
+ | `inTable` | boolean | Show column in the data table; default: `true` |
132
+ | `tableOrder` | integer | Column order in table; lower = leftmost |
133
+ | `tableField` | string | Column value to display in table (for `select` — show label, not id) |
134
+ | `width` | integer | Column width in pixels |
135
+ | `placeholder` | string | Input placeholder text |
136
+ | `defaultValue` | any | Default value on new record form |
137
+ | `editorMode` | string | Form only (`"form"`) or table only (`"table"`) |
138
+ | `maxlength` | integer | Max character length for `text`/`textarea` |
139
+ | `rows` | integer | Row height for `textarea` |
140
+
141
+ ---
142
+
143
+ ## Field Rows (Layout)
144
+
145
+ `fieldRows[]` groups fields into a CSS Grid row for multi-column forms.
146
+ Without `fieldRows`, fields render in a single column.
147
+
148
+ ```json
149
+ "fieldRows": [
150
+ { "fields": ["first_name", "last_name"] },
151
+ { "fields": ["email", "phone", "status"] }
152
+ ]
153
+ ```
154
+
155
+ - Each entry in `fields[]` refers to a field `name` defined in `fields[]`.
156
+ - Fields within a row share the available width equally by default.
157
+ - Use `colSpan` on a field attribute to span multiple columns:
158
+ `"colSpan": 2` makes a field take 2 of the row's grid columns.
159
+ - Interacts with `features.fieldLayout`: `"horizontal"` places the label
160
+ to the left of the input; `"vertical"` (default) stacks them.
161
+
162
+ ---
163
+
164
+ ## Field States (Conditional)
165
+
166
+ `fieldStates[]` defines conditional visibility and readonly rules based on
167
+ field values. Applied at runtime when field values change.
168
+
169
+ ```json
170
+ "fieldStates": [
171
+ {
172
+ "when": { "field": "status", "value": "approved" },
173
+ "fields": ["approved_by", "approved_at"],
174
+ "state": "readonly"
175
+ },
176
+ {
177
+ "when": { "field": "order_type", "value": "internal" },
178
+ "fields": ["customer_id"],
179
+ "state": "hidden"
180
+ }
181
+ ]
182
+ ```
183
+
184
+ | Property | Notes |
185
+ |---|---|
186
+ | `when.field` | The triggering field name |
187
+ | `when.value` | The value that triggers the state change |
188
+ | `fields[]` | Fields to affect when the condition is met |
189
+ | `state` | `"readonly"` or `"hidden"` |
190
+
191
+ Global scope: `fieldStates` at page level applies to all form instances.
192
+
193
+ ---
194
+
195
+ ## Features
196
+
197
+ `features` is an object at page level controlling toolbar and layout behavior.
198
+
199
+ | Feature | Type | Notes |
200
+ |---|---|---|
201
+ | `enableSearch` | boolean | Show full-text search input in toolbar |
202
+ | `enableStatusFilter` | object | Dropdown filter for boolean/enum field; requires `field` and `options[]` |
203
+ | `enableDataFilter` | array | Dynamic dropdown filters; each entry requires `field` and `dataSource` |
204
+ | `enableLiveSync` | boolean | Enable WebSocket live data refresh; requires `liveSync` at root level |
205
+ | `fieldLayout` | string | Form field label placement: `"vertical"` (default) or `"horizontal"` |
206
+
207
+ `enableStatusFilter` example:
208
+ ```json
209
+ "enableStatusFilter": {
210
+ "field": "is_active",
211
+ "options": [
212
+ { "label": "Active", "value": "true" },
213
+ { "label": "Inactive", "value": "false" }
214
+ ]
215
+ }
216
+ ```
217
+
218
+ `enableDataFilter` example:
219
+ ```json
220
+ "enableDataFilter": [
221
+ { "field": "category_id", "dataSource": { "type": "api", "apiPath": "/category" } }
222
+ ]
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Data Source
228
+
229
+ Used by `select` fields and `enableDataFilter` to populate options.
230
+
231
+ **Type: static**
232
+ ```json
233
+ "dataSource": {
234
+ "type": "static",
235
+ "options": [
236
+ { "id": "active", "text": "Active" },
237
+ { "id": "inactive", "text": "Inactive" }
238
+ ]
239
+ }
240
+ ```
241
+
242
+ **Type: api**
243
+ ```json
244
+ "dataSource": {
245
+ "type": "api",
246
+ "apiPath": "/category",
247
+ "id": "category_id",
248
+ "text": "category_name"
249
+ }
250
+ ```
251
+
252
+ - `apiPath` calls the backend `/lookup` endpoint of that resource.
253
+ - `id` maps to the option value; `text` maps to the display label.
254
+ - The `tableField` attribute on the field should reference `text` to show
255
+ the label (not the id) in the data table column.
256
+
257
+ ---
258
+
259
+ ## Navigation
260
+
261
+ `navigation[]` defines the sidebar menu. Three item types: `page`, `group`,
262
+ `separator`.
263
+
264
+ ```json
265
+ "navigation": [
266
+ { "type": "page", "pageId": "customer", "icon": "ti ti-users" },
267
+ { "type": "separator" },
268
+ {
269
+ "type": "group",
270
+ "label": "Master Data",
271
+ "icon": "ti ti-database",
272
+ "items": [
273
+ { "type": "page", "pageId": "category" },
274
+ { "type": "page", "pageId": "supplier" }
275
+ ]
276
+ }
277
+ ]
278
+ ```
279
+
280
+ - Maximum nesting depth: 3 levels.
281
+ - When `pageGroup` is set on a page, the sidebar auto-derives navigation —
282
+ manual `navigation[]` is optional.
283
+ - Manual `navigation[]` overrides auto-derivation completely.
284
+ - `icon` uses Tabler Icons class format: `"ti ti-<name>"`.
285
+
286
+ ---
287
+
288
+ ## Homepage
289
+
290
+ `homepage` sets the default landing page when the app loads.
291
+
292
+ ```json
293
+ "homepage": "dashboard-main"
294
+ ```
295
+
296
+ - Value must match a `pageId` in `pages[]`.
297
+ - Supports both CRUD and dashboard pages.
298
+ - When absent, the first page in `navigation[]` is loaded.
299
+
300
+ ---
301
+
302
+ ## Dashboard Page
303
+
304
+ A page with `pageType: "dashboard"` renders a grid-based analytics view.
305
+
306
+ ```json
307
+ {
308
+ "pageId": "dash-overview",
309
+ "pageType": "dashboard",
310
+ "pageTitle": "Overview",
311
+ "dataSources": [
312
+ { "id": "ds-orders", "apiPath": "/order", "action": "aggregate" }
313
+ ],
314
+ "rows": [
315
+ {
316
+ "columns": [
317
+ { "colSpan": { "xl": 4, "lg": 6, "md": 12 }, "widget": { ... } },
318
+ { "colSpan": { "xl": 8, "lg": 6, "md": 12 }, "widget": { ... } }
319
+ ]
320
+ }
321
+ ]
322
+ }
323
+ ```
324
+
325
+ **7 widget types:**
326
+
327
+ | Widget type | Display | Key properties |
328
+ |---|---|---|
329
+ | `column` | Vertical bar chart | `dataSource`, `labelField`, `valueField`, `color` |
330
+ | `bar` | Horizontal bar chart | `dataSource`, `labelField`, `valueField`, `color` |
331
+ | `pie` | Pie / donut chart | `dataSource`, `labelField`, `valueField` |
332
+ | `area` | Area chart | `dataSource`, `labelField`, `valueField`, `color` |
333
+ | `mini-bar` | Compact bar (KPI card) | `dataSource`, `valueField`, `label`, `icon`, `color` |
334
+ | `progress` | Progress bar list | `dataSource`, `labelField`, `valueField`, `maxValue` |
335
+ | `list` | Simple data list | `dataSource`, `columns[]` |
336
+
337
+ `dataSources[]` at page level defines reusable data pools; widgets reference
338
+ them by `id`. Each data source calls the backend `/aggregate` endpoint.
339
+
340
+ ---
341
+
342
+ ## Master-Detail
343
+
344
+ A page with `details[]` renders a master-detail composite form.
345
+
346
+ ```json
347
+ {
348
+ "pageId": "sales-order",
349
+ "apiPath": "/order",
350
+ "primaryKey": "order_id",
351
+ "displayField": "order_no",
352
+ "fields": [ ... ],
353
+ "details": [
354
+ {
355
+ "pageId": "order-item",
356
+ "apiPath": "/order-item",
357
+ "foreignKey": "order_id",
358
+ "primaryKey": "item_id",
359
+ "displayField": "product_name",
360
+ "fields": [ ... ]
361
+ }
362
+ ]
363
+ }
364
+ ```
365
+
366
+ - Each entry in `details[]` is a sub-page bound to the master via `foreignKey`.
367
+ - Multiple detail tabs are supported — one entry per detail entity.
368
+ - Generates composite form: master header + detail items grid.
369
+ - Backend must have composite endpoints (`/create-composite`, etc.) generated
370
+ from the RDF master-detail payload.
371
+
372
+ ---
373
+
374
+ ## Workflow Actions
375
+
376
+ `workflowActions[]` adds status-transition buttons to the form toolbar.
377
+
378
+ ```json
379
+ "workflow": {
380
+ "statusField": "status"
381
+ },
382
+ "workflowActions": [
383
+ {
384
+ "action": "approve",
385
+ "label": "Approve",
386
+ "targetStatus": "approved",
387
+ "fromStatus": ["pending"],
388
+ "confirm": {
389
+ "message": "Approve this request?",
390
+ "summary": ["request_no", "requested_by", "amount"]
391
+ }
392
+ }
393
+ ]
394
+ ```
395
+
396
+ | Property | Notes |
397
+ |---|---|
398
+ | `workflow.statusField` | Field name that holds the current status |
399
+ | `action` | Action identifier sent to backend `/change-status` |
400
+ | `label` | Button label |
401
+ | `targetStatus` | Status value after transition |
402
+ | `fromStatus[]` | Allowed current statuses for this action to appear |
403
+ | `confirm.message` | Confirmation dialog message |
404
+ | `confirm.summary[]` | Fields to display in the confirmation dialog |
405
+
406
+ ---
407
+
408
+ ## ID Generation
409
+
410
+ Supports auto-generated field values via `defaultValue.source: "idgen"`.
411
+ Requires `IDGEN_ENABLED=true` in backend config.
412
+
413
+ ```json
414
+ {
415
+ "name": "order_no",
416
+ "type": "text",
417
+ "defaultValue": {
418
+ "source": "idgen",
419
+ "mode": "serial",
420
+ "prefix": "ORD",
421
+ "format": "XXXX-XXXX"
422
+ }
423
+ }
424
+ ```
425
+
426
+ 5 modes: `number` (sequential with prefix/format), `pin` (OTP numeric),
427
+ `code` (voucher-style), `serial` (license key pattern), `random`.
428
+ The field renders as readonly on the form; value is reserved on focus and
429
+ confirmed on submit.
430
+
431
+ ---
432
+
433
+ ## Live Sync
434
+
435
+ WebSocket-based live data refresh. Requires `LIVE_SYNC_ENABLED=true` and
436
+ `LIVE_SYNC_PORT` configured in backend.
437
+
438
+ Root-level config in UDF:
439
+ ```json
440
+ "liveSync": {
441
+ "url": "ws://localhost:3033",
442
+ "apiKey": "your-api-key"
443
+ }
444
+ ```
445
+
446
+ Per-page activation:
447
+ ```json
448
+ "features": {
449
+ "enableLiveSync": true
450
+ }
451
+ ```
452
+
453
+ When active, the page's data table auto-refreshes when the backend pushes
454
+ an event over the WebSocket channel.
455
+
456
+ ---
457
+
458
+ ## Naming Conventions
459
+
460
+ | Element | Pattern | Example |
461
+ |---|---|---|
462
+ | `pageId` (CRUD) | `^[a-zA-Z0-9_-]+$` | `sales-order`, `product_list` |
463
+ | `pageId` (dashboard) | `^dash-[a-z0-9-]+$` | `dash-overview`, `dash-monthly` |
464
+ | `widgetId` | `^[a-z][a-z0-9-]*$` | `chart-revenue`, `kpi-orders` |
465
+ | field `name` | snake_case | `order_no`, `customer_id` |
466
+ | `appCode` | kebab-case | `sales-app`, `inventory` |
467
+ | `apiPath` | kebab-case with leading `/` | `"/sales-order"`, `"/product"` |
468
+ | `pageGroup` | Title Case | `"Master Data"`, `"Reports"` |
469
+
470
+ ---
471
+
472
+ ## Plugins
473
+
474
+ Two built-in plugins. Run `designer_list_plugins` to confirm available versions.
475
+
476
+ | Plugin | Auth | Notes |
477
+ |---|---|---|
478
+ | `vanilla-js-basic` | None | Standard CRUD app, no login flow |
479
+ | `vanilla-js-auth` | JWT | Includes login page, token refresh, role-based visibility |
480
+
481
+ `vanilla-js-auth` requires additional `appConfig` properties:
482
+ - `authAppCode` — must match the backend auth module's app code
483
+ - `idleTimeout` — auto-logout after inactivity (seconds)
484
+
485
+ **Custom plugins:**
486
+ Use `designer_scaffold_plugin` to generate a plugin template. The plugin
487
+ structure contains `plugin.json` (metadata) and Jinja2 templates for each
488
+ file type (HTML, JS, CSS). Use `designer_inspect_plugin` to verify capabilities
489
+ before referencing a plugin in a UDF payload.