Commit 39827f040f15aa63a087a59071c170565c6881a9

Authored by zichun
1 parent 634c18fe

docs(spec): metadata-driven forms & list views design (P3.2/P3.3/P3.6/R3)

Sub-project A of remaining v1.0 work. Key decisions:
- Hybrid: core forms stay handcrafted, renderer for user-task forms
- @rjsf/core with custom ERP widget registry
- Structured property editor (not drag-and-drop) for form designer
- Custom entities deferred to v1.1
docs/superpowers/specs/2026-04-10-metadata-forms-listviews-design.md 0 → 100644
  1 +# Metadata-Driven Forms & List Views — Design Spec
  2 +
  3 +> Sub-project A of the v1.0 remaining work. Covers P3.2 (form renderer),
  4 +> P3.3 (form designer), P3.6 (list view designer), and R3 (metadata admin UIs).
  5 +
  6 +## 1. Context & Motivation
  7 +
  8 +vibe_erp's Tier 1 extensibility promise is that business analysts can customize
  9 +the system through the web UI — no code, no build, no restart. The foundation
  10 +is live: custom fields on core entities (P3.1 + P3.4), metadata YAML loader,
  11 +`metadata__*` tables, and the `DynamicExtFields` SPA component.
  12 +
  13 +What's missing is the **designer and renderer layer** — the UIs that let key
  14 +users create form layouts, configure list views, and manage all metadata
  15 +through the browser. Without these, Tier 1 customization requires editing
  16 +YAML files and restarting the server.
  17 +
  18 +## 2. Scope Decisions (Agreed)
  19 +
  20 +| Decision | Choice | Rationale |
  21 +|---|---|---|
  22 +| Form renderer scope | **Hybrid** — core entity forms stay handcrafted; renderer handles user-task forms, custom fields, and future custom entities | Core forms have complex interactions (line items, state transitions) that don't map well to JSON Schema |
  23 +| Custom entity creation | **Deferred to v1.1** — no dynamic DDL or runtime endpoint registration in v1.0 | Large, risky feature; custom fields already give key users meaningful extensibility |
  24 +| Form renderer library | **@rjsf/core with custom widget registry** — React JSON Schema Form with ERP-specific widgets | FormSchema.kt already stores JSON Schema + UI Schema; @rjsf is the standard renderer |
  25 +| Form designer UX | **Structured property editor** — table/accordion UI with live preview tab | 90% of the value of drag-and-drop at 30% of the cost; upgradeable later |
  26 +
  27 +## 3. Architecture
  28 +
  29 +```
  30 +metadata__form / metadata__list_view (Postgres JSONB rows)
  31 + │
  32 + │ source = 'core' | 'plugin:<id>' | 'user'
  33 + │
  34 + MetadataController (REST API)
  35 + │
  36 + ├── GET /api/v1/_meta/metadata/forms
  37 + ├── GET /api/v1/_meta/metadata/forms/{slug}
  38 + ├── PUT /api/v1/_meta/metadata/forms/{slug} ← designer writes
  39 + ├── DELETE /api/v1/_meta/metadata/forms/{slug} ← user-created only
  40 + ├── GET /api/v1/_meta/metadata/list-views
  41 + ├── GET /api/v1/_meta/metadata/list-views/{slug}
  42 + ├── PUT /api/v1/_meta/metadata/list-views/{slug}
  43 + └── DELETE /api/v1/_meta/metadata/list-views/{slug}
  44 + │
  45 + SPA (React + TypeScript)
  46 + │
  47 + ├── MetadataFormRenderer (P3.2) — @rjsf/core, renders form from definition
  48 + ├── FormDesigner (P3.3) — structured editor, writes definitions back
  49 + ├── ListViewDesigner (P3.6) — column/filter/sort editor
  50 + └── MetadataAdmin (R3) — tabs: entities, permissions, menus,
  51 + custom fields, forms, list views
  52 +```
  53 +
  54 +### Source tagging
  55 +
  56 +All form and list-view definitions follow the existing `source` convention:
  57 +- `core` — shipped in metadata YAML, loaded by MetadataLoader at boot
  58 +- `plugin:<id>` — from plug-in JAR, loaded at plug-in start
  59 +- `user` — created through the designer UI, never touched by the loader
  60 +
  61 +The `PUT` and `DELETE` endpoints only accept `source='user'` rows. Core and
  62 +plugin definitions are read-only in the UI (shown grayed out with a lock icon).
  63 +
  64 +## 4. P3.2 — Form Renderer (`MetadataFormRenderer`)
  65 +
  66 +### Component
  67 +
  68 +```tsx
  69 +<MetadataFormRenderer
  70 + slug="plate-approval-task" // looks up from metadata__form
  71 + initialValues={{ ... }} // pre-fill for edit mode
  72 + onSubmit={(values) => { ... }} // called on valid submit
  73 + readOnly={false} // true for view / completed tasks
  74 +/>
  75 +```
  76 +
  77 +### Form definition schema (stored in `metadata__form.payload`)
  78 +
  79 +```typescript
  80 +interface FormDefinition {
  81 + entityName: string // "WorkOrder", "printing-shop.Plate"
  82 + slug: string // unique key: "plate-approval-task"
  83 + title: string // display title
  84 + purpose: 'create' | 'edit' | 'user-task' | 'view'
  85 + jsonSchema: object // JSON Schema draft 2020-12
  86 + uiSchema: object // @rjsf UI Schema (widget, order, sections)
  87 + version: number // monotonically increasing
  88 +}
  89 +```
  90 +
  91 +### @rjsf integration
  92 +
  93 +- **Library:** `@rjsf/core` + `@rjsf/validator-ajv8`
  94 +- **Theme:** Custom `VibeErpTheme` wrapping existing Tailwind CSS classes
  95 + (slate-700 labels, rounded-md inputs, btn-primary buttons) so rendered
  96 + forms look identical to handcrafted pages.
  97 +- **Custom widget registry:**
  98 +
  99 +| Widget ID | Purpose | Data source |
  100 +|---|---|---|
  101 +| `partner-picker` | Searchable partner dropdown | `GET /api/v1/partners` |
  102 +| `item-picker` | Searchable item dropdown | `GET /api/v1/catalog/items` |
  103 +| `uom-selector` | UoM dropdown | `GET /api/v1/catalog/uoms` |
  104 +| `location-picker` | Location dropdown | `GET /api/v1/inventory/locations` |
  105 +| `money-input` | Number + currency display | FieldType.Money |
  106 +| `quantity-input` | Number + UoM display | FieldType.Quantity |
  107 +
  108 +Widgets are registered in a `vibeWidgets` map and passed to `@rjsf/core`'s
  109 +`widgets` prop. Each widget is a standard React component that receives
  110 +`WidgetProps` from @rjsf.
  111 +
  112 +### User-task form bridge (P2.3)
  113 +
  114 +Flowable user-tasks carry a `formKey` in BPMN XML (e.g. `formKey="vibe:plate-approval-task"`).
  115 +When the SPA renders a pending user-task:
  116 +
  117 +1. Strip the `vibe:` prefix → slug `plate-approval-task`
  118 +2. Fetch form definition: `GET /api/v1/_meta/metadata/forms/plate-approval-task`
  119 +3. Fetch task variables: `GET /api/v1/workflow/tasks/{taskId}`
  120 +4. Render: `<MetadataFormRenderer slug="..." initialValues={taskVars} />`
  121 +5. On submit: `POST /api/v1/workflow/tasks/{taskId}/complete` with form values
  122 +
  123 +### Relationship with DynamicExtFields (P3.1)
  124 +
  125 +DynamicExtFields stays as-is for custom fields on core entity create/edit pages.
  126 +MetadataFormRenderer is a separate, more powerful component for full-form rendering.
  127 +They share the same Tailwind styling but are independent components — no refactor
  128 +of existing create/edit pages needed.
  129 +
  130 +## 5. P3.3 — Form Designer
  131 +
  132 +### UX: Structured property editor
  133 +
  134 +The form designer is a full-page editor with two panels:
  135 +
  136 +**Left panel — Field list (accordion/table):**
  137 +- One row per field in the form
  138 +- Each row shows: field key, label, type, required checkbox, width (1/2/3 cols)
  139 +- Rows are reorderable via up/down buttons
  140 +- "Add field" button at the bottom with a type picker
  141 +- "Add section divider" to group fields under headings
  142 +- Click a row to expand its property panel inline:
  143 + - Label (per locale, with translation inputs)
  144 + - Placeholder text
  145 + - Help text / description
  146 + - Validation rules (min, max, pattern, required)
  147 + - Visibility condition (simple: "show when field X equals Y")
  148 + - Widget override (dropdown of available widgets)
  149 +
  150 +**Right panel — Live preview:**
  151 +- Renders the current form definition using `<MetadataFormRenderer />`
  152 +- Updates in real-time as the user edits fields in the left panel
  153 +- "Preview" / "Edit" toggle
  154 +
  155 +**Top bar:**
  156 +- Form title (editable)
  157 +- Entity selector (which entity this form is for)
  158 +- Purpose selector (create / edit / user-task / view)
  159 +- Save button → `PUT /api/v1/_meta/metadata/forms/{slug}`
  160 +- Discard button
  161 +
  162 +### Visibility conditions
  163 +
  164 +Simple "show when" rules stored in the UI Schema:
  165 +
  166 +```json
  167 +{
  168 + "ui:field:press_id": {
  169 + "ui:visible": { "field": "item_type", "equals": "GOOD" }
  170 + }
  171 +}
  172 +```
  173 +
  174 +The MetadataFormRenderer evaluates these at render time — no server round-trip.
  175 +Only single-field equality conditions in v1.0; complex expressions deferred.
  176 +
  177 +### Form definition generation
  178 +
  179 +The designer generates a JSON Schema + UI Schema pair from the field list.
  180 +It never exposes raw JSON to the user — the structured editor is the only
  181 +interface. Power users who want raw JSON can use the R3 metadata admin UI's
  182 +"Raw JSON" tab.
  183 +
  184 +## 6. P3.6 — List View Designer
  185 +
  186 +### Definition schema (stored in `metadata__list_view.payload`)
  187 +
  188 +```typescript
  189 +interface ListViewDefinition {
  190 + entityName: string
  191 + slug: string // "sales-orders-default"
  192 + title: string
  193 + columns: Array<{
  194 + field: string // entity field key or custom field key
  195 + label: string // display header
  196 + width?: string // CSS width hint ("200px", "auto")
  197 + sortable: boolean
  198 + format?: 'date' | 'money' | 'status-badge' | 'link'
  199 + }>
  200 + defaultSort?: { field: string; direction: 'asc' | 'desc' }
  201 + filters?: Array<{
  202 + field: string
  203 + operator: 'eq' | 'contains' | 'gt' | 'lt' | 'in'
  204 + label: string
  205 + }>
  206 + pageSize: number // default 25
  207 + version: number
  208 +}
  209 +```
  210 +
  211 +### Designer UX
  212 +
  213 +Simple two-section editor:
  214 +
  215 +**Columns section:**
  216 +- Table of columns with checkboxes (show/hide), drag-to-reorder, label editing
  217 +- Available fields populated from entity's JSON Schema + custom fields
  218 +- Format selector per column (plain text, date, money, status badge, link)
  219 +
  220 +**Filters & sorting section:**
  221 +- Add filterable fields (creates a filter bar above the list)
  222 +- Set default sort column and direction
  223 +- Page size selector
  224 +
  225 +**Preview:** Shows a mock table with sample data using the current config.
  226 +
  227 +### Integration with existing list pages
  228 +
  229 +v1.0 list pages (ItemsPage, PartnersPage, etc.) continue to use the existing
  230 +`<DataTable>` component with hardcoded columns. The `ListViewDesigner` creates
  231 +definitions for future use (custom entities in v1.1, and opt-in override of
  232 +core list views). A `<MetadataListView entityName="..." />` component renders
  233 +a list from a `ListViewDefinition`, reusing `DataTable` internally.
  234 +
  235 +## 7. R3 — Metadata Admin UIs
  236 +
  237 +### Route: `/admin/metadata`
  238 +
  239 +A tabbed admin page with sections:
  240 +
  241 +| Tab | Content | Editable? |
  242 +|---|---|---|
  243 +| **Entities** | List of all registered entities (core + plugin + user) | Read-only (v1.0 has no custom entities) |
  244 +| **Custom Fields** | List/create/edit custom fields for any entity | Full CRUD for `source='user'` fields |
  245 +| **Permissions** | List of all permissions | Read-only (permissions come from YAML) |
  246 +| **Menus** | List of all menu entries with section/order | Read-only for core/plugin; editable for user |
  247 +| **Forms** | List of form definitions; click to open FormDesigner | Full CRUD for `source='user'` forms |
  248 +| **List Views** | List of list-view definitions; click to open ListViewDesigner | Full CRUD for `source='user'` views |
  249 +
  250 +Each tab shows a `source` badge (core / plugin:name / user) and a lock icon
  251 +for non-editable rows.
  252 +
  253 +### Custom field editor (inline in the admin)
  254 +
  255 +The custom field tab lets key users:
  256 +- Add a new custom field to any entity (creates a `source='user'` row)
  257 +- Choose: target entity, field key, type (from FieldType sealed set), required, PII flag
  258 +- Add label translations (en, zh-CN, etc.)
  259 +- Delete user-created custom fields (with confirmation)
  260 +
  261 +This replaces the need to edit YAML for Tier 1 custom fields — the full
  262 +key-user story is now possible through the browser.
  263 +
  264 +### Permissions
  265 +
  266 +- `admin.metadata.read` — view all metadata tabs
  267 +- `admin.metadata.write` — create/edit/delete user metadata (custom fields, forms, list views, menus)
  268 +
  269 +These are added to the core `identity.yml` metadata.
  270 +
  271 +## 8. Backend Changes
  272 +
  273 +### New REST endpoints (MetadataController)
  274 +
  275 +```
  276 +# Forms
  277 +GET /api/v1/_meta/metadata/forms → List<FormDefinition>
  278 +GET /api/v1/_meta/metadata/forms/{slug} → FormDefinition
  279 +PUT /api/v1/_meta/metadata/forms/{slug} → FormDefinition (upsert, source='user')
  280 +DELETE /api/v1/_meta/metadata/forms/{slug} → 204 (source='user' only)
  281 +
  282 +# List views
  283 +GET /api/v1/_meta/metadata/list-views → List<ListViewDefinition>
  284 +GET /api/v1/_meta/metadata/list-views/{slug} → ListViewDefinition
  285 +PUT /api/v1/_meta/metadata/list-views/{slug} → ListViewDefinition (upsert, source='user')
  286 +DELETE /api/v1/_meta/metadata/list-views/{slug} → 204 (source='user' only)
  287 +
  288 +# Custom fields (new write endpoints)
  289 +POST /api/v1/_meta/metadata/custom-fields → CustomField (source='user')
  290 +PUT /api/v1/_meta/metadata/custom-fields/{key} → CustomField (source='user')
  291 +DELETE /api/v1/_meta/metadata/custom-fields/{key} → 204 (source='user' only)
  292 +```
  293 +
  294 +The existing `GET` endpoints remain unchanged. Write endpoints require
  295 +`admin.metadata.write` permission.
  296 +
  297 +### Database
  298 +
  299 +The `metadata__form` and `metadata__list_view` tables already exist in
  300 +`000-platform-init.xml`. No new migrations needed — just new code that
  301 +reads/writes these tables.
  302 +
  303 +### MetadataLoader changes
  304 +
  305 +Add `forms:` and `listViews:` sections to the MetadataYaml schema so core
  306 +PBCs and plug-ins can ship default form and list-view definitions in their
  307 +metadata YAML. The loader processes them with the same delete-by-source
  308 +idempotency as entities, permissions, menus, and custom fields.
  309 +
  310 +### CustomFieldRegistry refresh
  311 +
  312 +After a custom field is created/updated/deleted through the REST API, the
  313 +registry must refresh. Add a `refresh()` call in the write endpoint handler
  314 +(same pattern as MetadataLoader already uses).
  315 +
  316 +## 9. SPA Dependencies
  317 +
  318 +New npm packages:
  319 +- `@rjsf/core` — React JSON Schema Form renderer
  320 +- `@rjsf/utils` — shared utilities
  321 +- `@rjsf/validator-ajv8` — JSON Schema validation via Ajv
  322 +
  323 +Estimated bundle impact: ~60KB gzipped (acceptable for an ERP SPA).
  324 +
  325 +## 10. File Inventory (New & Modified)
  326 +
  327 +### New files
  328 +
  329 +**Backend (Kotlin):**
  330 +- `platform/platform-metadata/src/.../web/FormDefinitionController.kt` — CRUD for form defs
  331 +- `platform/platform-metadata/src/.../web/ListViewDefinitionController.kt` — CRUD for list-view defs
  332 +- `platform/platform-metadata/src/.../web/CustomFieldWriteController.kt` — write endpoints for custom fields
  333 +- `platform/platform-metadata/src/.../yaml/FormYaml.kt` — YAML deserialization for forms
  334 +- `platform/platform-metadata/src/.../yaml/ListViewYaml.kt` — YAML deserialization for list views
  335 +
  336 +**Frontend (TypeScript/React):**
  337 +- `web/src/components/MetadataFormRenderer.tsx` — @rjsf wrapper with VibeErp theme
  338 +- `web/src/components/form-widgets/PartnerPicker.tsx` — partner search widget
  339 +- `web/src/components/form-widgets/ItemPicker.tsx` — item search widget
  340 +- `web/src/components/form-widgets/UomSelector.tsx` — UoM dropdown widget
  341 +- `web/src/components/form-widgets/LocationPicker.tsx` — location dropdown widget
  342 +- `web/src/components/form-widgets/MoneyInput.tsx` — money input widget
  343 +- `web/src/components/form-widgets/QuantityInput.tsx` — quantity input widget
  344 +- `web/src/components/form-widgets/index.ts` — widget registry
  345 +- `web/src/pages/FormDesignerPage.tsx` — structured property editor
  346 +- `web/src/pages/ListViewDesignerPage.tsx` — column/filter/sort editor
  347 +- `web/src/pages/MetadataAdminPage.tsx` — tabbed admin with sub-sections
  348 +
  349 +**Metadata YAML (reference):**
  350 +- Core PBCs ship default form definitions for user-task forms (if any)
  351 +- Printing-shop plug-in ships a `plate-approval-task` form definition
  352 +
  353 +### Modified files
  354 +
  355 +- `platform/platform-metadata/src/.../yaml/MetadataYaml.kt` — add `forms` + `listViews` sections
  356 +- `platform/platform-metadata/src/.../MetadataLoader.kt` — process forms + list views
  357 +- `platform/platform-metadata/src/.../web/MetadataController.kt` — add form/list-view GET endpoints
  358 +- `web/src/App.tsx` — add routes for designer + admin pages
  359 +- `web/src/api/client.ts` — add API functions for form/list-view/custom-field CRUD
  360 +- `web/src/i18n/messages.ts` — add message keys for new pages
  361 +- `web/src/types/api.ts` — add FormDefinition + ListViewDefinition types
  362 +- `web/package.json` — add @rjsf dependencies
  363 +
  364 +## 11. Testing Strategy
  365 +
  366 +**Backend unit tests:**
  367 +- FormDefinition CRUD (create, read, update, delete with source enforcement)
  368 +- ListViewDefinition CRUD (same)
  369 +- CustomField write endpoints (create, update, delete, registry refresh)
  370 +- MetadataLoader with forms + list views sections
  371 +- Source-tag enforcement (reject writes to core/plugin rows)
  372 +
  373 +**Frontend:**
  374 +- Manual smoke test: open form designer, create a form, preview it, save it
  375 +- Manual smoke test: open list view designer, configure columns, save
  376 +- Manual smoke test: open metadata admin, browse all tabs
  377 +
  378 +**Integration:**
  379 +- Printing-shop plug-in ships a `plate-approval-task` form definition
  380 +- Boot the framework, start the plate-approval BPMN process, verify the
  381 + user-task renders the form from metadata, complete it, verify values flow
  382 + through to the workflow
  383 +
  384 +## 12. Out of Scope (Deferred)
  385 +
  386 +- **Custom entities / dynamic DDL** — v1.1
  387 +- **Drag-and-drop form designer** — future upgrade of the structured editor
  388 +- **Complex visibility conditions** — v1.0 supports "show when X equals Y" only
  389 +- **Form versioning / migration** — v1.1 (for now, bumping version is manual)
  390 +- **List view as override for core pages** — v1.1 (core pages keep hardcoded columns)
  391 +- **Import/export of form/list-view definitions** — v1.1