# Metadata-Driven Forms & List Views — Design Spec > Sub-project A of the v1.0 remaining work. Covers P3.2 (form renderer), > P3.3 (form designer), P3.6 (list view designer), and R3 (metadata admin UIs). ## 1. Context & Motivation vibe_erp's Tier 1 extensibility promise is that business analysts can customize the system through the web UI — no code, no build, no restart. The foundation is live: custom fields on core entities (P3.1 + P3.4), metadata YAML loader, `metadata__*` tables, and the `DynamicExtFields` SPA component. What's missing is the **designer and renderer layer** — the UIs that let key users create form layouts, configure list views, and manage all metadata through the browser. Without these, Tier 1 customization requires editing YAML files and restarting the server. ## 2. Scope Decisions (Agreed) | Decision | Choice | Rationale | |---|---|---| | 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 | | 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 | | 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 | | 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 | ## 3. Architecture ``` metadata__form / metadata__list_view (Postgres JSONB rows) │ │ source = 'core' | 'plugin:' | 'user' │ MetadataController (REST API) │ ├── GET /api/v1/_meta/metadata/forms ├── GET /api/v1/_meta/metadata/forms/{slug} ├── PUT /api/v1/_meta/metadata/forms/{slug} ← designer writes ├── DELETE /api/v1/_meta/metadata/forms/{slug} ← user-created only ├── GET /api/v1/_meta/metadata/list-views ├── GET /api/v1/_meta/metadata/list-views/{slug} ├── PUT /api/v1/_meta/metadata/list-views/{slug} └── DELETE /api/v1/_meta/metadata/list-views/{slug} │ SPA (React + TypeScript) │ ├── MetadataFormRenderer (P3.2) — @rjsf/core, renders form from definition ├── FormDesigner (P3.3) — structured editor, writes definitions back ├── ListViewDesigner (P3.6) — column/filter/sort editor └── MetadataAdmin (R3) — tabs: entities, permissions, menus, custom fields, forms, list views ``` ### Source tagging All form and list-view definitions follow the existing `source` convention: - `core` — shipped in metadata YAML, loaded by MetadataLoader at boot - `plugin:` — from plug-in JAR, loaded at plug-in start - `user` — created through the designer UI, never touched by the loader The `PUT` and `DELETE` endpoints only accept `source='user'` rows. Core and plugin definitions are read-only in the UI (shown grayed out with a lock icon). ## 4. P3.2 — Form Renderer (`MetadataFormRenderer`) ### Component ```tsx { ... }} // called on valid submit readOnly={false} // true for view / completed tasks /> ``` ### Form definition schema (stored in `metadata__form.payload`) ```typescript interface FormDefinition { entityName: string // "WorkOrder", "printing-shop.Plate" slug: string // unique key: "plate-approval-task" title: string // display title purpose: 'create' | 'edit' | 'user-task' | 'view' jsonSchema: object // JSON Schema draft 2020-12 uiSchema: object // @rjsf UI Schema (widget, order, sections) version: number // monotonically increasing } ``` ### @rjsf integration - **Library:** `@rjsf/core` + `@rjsf/validator-ajv8` - **Theme:** Custom `VibeErpTheme` wrapping existing Tailwind CSS classes (slate-700 labels, rounded-md inputs, btn-primary buttons) so rendered forms look identical to handcrafted pages. - **Custom widget registry:** | Widget ID | Purpose | Data source | |---|---|---| | `partner-picker` | Searchable partner dropdown | `GET /api/v1/partners` | | `item-picker` | Searchable item dropdown | `GET /api/v1/catalog/items` | | `uom-selector` | UoM dropdown | `GET /api/v1/catalog/uoms` | | `location-picker` | Location dropdown | `GET /api/v1/inventory/locations` | | `money-input` | Number + currency display | FieldType.Money | | `quantity-input` | Number + UoM display | FieldType.Quantity | Widgets are registered in a `vibeWidgets` map and passed to `@rjsf/core`'s `widgets` prop. Each widget is a standard React component that receives `WidgetProps` from @rjsf. ### User-task form bridge (P2.3) Flowable user-tasks carry a `formKey` in BPMN XML (e.g. `formKey="vibe:plate-approval-task"`). When the SPA renders a pending user-task: 1. Strip the `vibe:` prefix → slug `plate-approval-task` 2. Fetch form definition: `GET /api/v1/_meta/metadata/forms/plate-approval-task` 3. Fetch task variables: `GET /api/v1/workflow/tasks/{taskId}` 4. Render: `` 5. On submit: `POST /api/v1/workflow/tasks/{taskId}/complete` with form values ### Relationship with DynamicExtFields (P3.1) DynamicExtFields stays as-is for custom fields on core entity create/edit pages. MetadataFormRenderer is a separate, more powerful component for full-form rendering. They share the same Tailwind styling but are independent components — no refactor of existing create/edit pages needed. ## 5. P3.3 — Form Designer ### UX: Structured property editor The form designer is a full-page editor with two panels: **Left panel — Field list (accordion/table):** - One row per field in the form - Each row shows: field key, label, type, required checkbox, width (1/2/3 cols) - Rows are reorderable via up/down buttons - "Add field" button at the bottom with a type picker - "Add section divider" to group fields under headings - Click a row to expand its property panel inline: - Label (per locale, with translation inputs) - Placeholder text - Help text / description - Validation rules (min, max, pattern, required) - Visibility condition (simple: "show when field X equals Y") - Widget override (dropdown of available widgets) **Right panel — Live preview:** - Renders the current form definition using `` - Updates in real-time as the user edits fields in the left panel - "Preview" / "Edit" toggle **Top bar:** - Form title (editable) - Entity selector (which entity this form is for) - Purpose selector (create / edit / user-task / view) - Save button → `PUT /api/v1/_meta/metadata/forms/{slug}` - Discard button ### Visibility conditions Simple "show when" rules stored in the UI Schema: ```json { "ui:field:press_id": { "ui:visible": { "field": "item_type", "equals": "GOOD" } } } ``` The MetadataFormRenderer evaluates these at render time — no server round-trip. Only single-field equality conditions in v1.0; complex expressions deferred. ### Form definition generation The designer generates a JSON Schema + UI Schema pair from the field list. It never exposes raw JSON to the user — the structured editor is the only interface. Power users who want raw JSON can use the R3 metadata admin UI's "Raw JSON" tab. ## 6. P3.6 — List View Designer ### Definition schema (stored in `metadata__list_view.payload`) ```typescript interface ListViewDefinition { entityName: string slug: string // "sales-orders-default" title: string columns: Array<{ field: string // entity field key or custom field key label: string // display header width?: string // CSS width hint ("200px", "auto") sortable: boolean format?: 'date' | 'money' | 'status-badge' | 'link' }> defaultSort?: { field: string; direction: 'asc' | 'desc' } filters?: Array<{ field: string operator: 'eq' | 'contains' | 'gt' | 'lt' | 'in' label: string }> pageSize: number // default 25 version: number } ``` ### Designer UX Simple two-section editor: **Columns section:** - Table of columns with checkboxes (show/hide), drag-to-reorder, label editing - Available fields populated from entity's JSON Schema + custom fields - Format selector per column (plain text, date, money, status badge, link) **Filters & sorting section:** - Add filterable fields (creates a filter bar above the list) - Set default sort column and direction - Page size selector **Preview:** Shows a mock table with sample data using the current config. ### Integration with existing list pages v1.0 list pages (ItemsPage, PartnersPage, etc.) continue to use the existing `` component with hardcoded columns. The `ListViewDesigner` creates definitions for future use (custom entities in v1.1, and opt-in override of core list views). A `` component renders a list from a `ListViewDefinition`, reusing `DataTable` internally. ## 7. R3 — Metadata Admin UIs ### Route: `/admin/metadata` A tabbed admin page with sections: | Tab | Content | Editable? | |---|---|---| | **Entities** | List of all registered entities (core + plugin + user) | Read-only (v1.0 has no custom entities) | | **Custom Fields** | List/create/edit custom fields for any entity | Full CRUD for `source='user'` fields | | **Permissions** | List of all permissions | Read-only (permissions come from YAML) | | **Menus** | List of all menu entries with section/order | Read-only for core/plugin; editable for user | | **Forms** | List of form definitions; click to open FormDesigner | Full CRUD for `source='user'` forms | | **List Views** | List of list-view definitions; click to open ListViewDesigner | Full CRUD for `source='user'` views | Each tab shows a `source` badge (core / plugin:name / user) and a lock icon for non-editable rows. ### Custom field editor (inline in the admin) The custom field tab lets key users: - Add a new custom field to any entity (creates a `source='user'` row) - Choose: target entity, field key, type (from FieldType sealed set), required, PII flag - Add label translations (en, zh-CN, etc.) - Delete user-created custom fields (with confirmation) This replaces the need to edit YAML for Tier 1 custom fields — the full key-user story is now possible through the browser. ### Permissions - `admin.metadata.read` — view all metadata tabs - `admin.metadata.write` — create/edit/delete user metadata (custom fields, forms, list views, menus) These are added to the core `identity.yml` metadata. ## 8. Backend Changes ### New REST endpoints (MetadataController) ``` # Forms GET /api/v1/_meta/metadata/forms → List GET /api/v1/_meta/metadata/forms/{slug} → FormDefinition PUT /api/v1/_meta/metadata/forms/{slug} → FormDefinition (upsert, source='user') DELETE /api/v1/_meta/metadata/forms/{slug} → 204 (source='user' only) # List views GET /api/v1/_meta/metadata/list-views → List GET /api/v1/_meta/metadata/list-views/{slug} → ListViewDefinition PUT /api/v1/_meta/metadata/list-views/{slug} → ListViewDefinition (upsert, source='user') DELETE /api/v1/_meta/metadata/list-views/{slug} → 204 (source='user' only) # Custom fields (new write endpoints) POST /api/v1/_meta/metadata/custom-fields → CustomField (source='user') PUT /api/v1/_meta/metadata/custom-fields/{key} → CustomField (source='user') DELETE /api/v1/_meta/metadata/custom-fields/{key} → 204 (source='user' only) ``` The existing `GET` endpoints remain unchanged. Write endpoints require `admin.metadata.write` permission. ### Database The `metadata__form` and `metadata__list_view` tables already exist in `000-platform-init.xml`. No new migrations needed — just new code that reads/writes these tables. ### MetadataLoader changes Add `forms:` and `listViews:` sections to the MetadataYaml schema so core PBCs and plug-ins can ship default form and list-view definitions in their metadata YAML. The loader processes them with the same delete-by-source idempotency as entities, permissions, menus, and custom fields. ### CustomFieldRegistry refresh After a custom field is created/updated/deleted through the REST API, the registry must refresh. Add a `refresh()` call in the write endpoint handler (same pattern as MetadataLoader already uses). ## 9. SPA Dependencies New npm packages: - `@rjsf/core` — React JSON Schema Form renderer - `@rjsf/utils` — shared utilities - `@rjsf/validator-ajv8` — JSON Schema validation via Ajv Estimated bundle impact: ~60KB gzipped (acceptable for an ERP SPA). ## 10. File Inventory (New & Modified) ### New files **Backend (Kotlin):** - `platform/platform-metadata/src/.../web/FormDefinitionController.kt` — CRUD for form defs - `platform/platform-metadata/src/.../web/ListViewDefinitionController.kt` — CRUD for list-view defs - `platform/platform-metadata/src/.../web/CustomFieldWriteController.kt` — write endpoints for custom fields - `platform/platform-metadata/src/.../yaml/FormYaml.kt` — YAML deserialization for forms - `platform/platform-metadata/src/.../yaml/ListViewYaml.kt` — YAML deserialization for list views **Frontend (TypeScript/React):** - `web/src/components/MetadataFormRenderer.tsx` — @rjsf wrapper with VibeErp theme - `web/src/components/form-widgets/PartnerPicker.tsx` — partner search widget - `web/src/components/form-widgets/ItemPicker.tsx` — item search widget - `web/src/components/form-widgets/UomSelector.tsx` — UoM dropdown widget - `web/src/components/form-widgets/LocationPicker.tsx` — location dropdown widget - `web/src/components/form-widgets/MoneyInput.tsx` — money input widget - `web/src/components/form-widgets/QuantityInput.tsx` — quantity input widget - `web/src/components/form-widgets/index.ts` — widget registry - `web/src/pages/FormDesignerPage.tsx` — structured property editor - `web/src/pages/ListViewDesignerPage.tsx` — column/filter/sort editor - `web/src/pages/MetadataAdminPage.tsx` — tabbed admin with sub-sections **Metadata YAML (reference):** - Core PBCs ship default form definitions for user-task forms (if any) - Printing-shop plug-in ships a `plate-approval-task` form definition ### Modified files - `platform/platform-metadata/src/.../yaml/MetadataYaml.kt` — add `forms` + `listViews` sections - `platform/platform-metadata/src/.../MetadataLoader.kt` — process forms + list views - `platform/platform-metadata/src/.../web/MetadataController.kt` — add form/list-view GET endpoints - `web/src/App.tsx` — add routes for designer + admin pages - `web/src/api/client.ts` — add API functions for form/list-view/custom-field CRUD - `web/src/i18n/messages.ts` — add message keys for new pages - `web/src/types/api.ts` — add FormDefinition + ListViewDefinition types - `web/package.json` — add @rjsf dependencies ## 11. Testing Strategy **Backend unit tests:** - FormDefinition CRUD (create, read, update, delete with source enforcement) - ListViewDefinition CRUD (same) - CustomField write endpoints (create, update, delete, registry refresh) - MetadataLoader with forms + list views sections - Source-tag enforcement (reject writes to core/plugin rows) **Frontend:** - Manual smoke test: open form designer, create a form, preview it, save it - Manual smoke test: open list view designer, configure columns, save - Manual smoke test: open metadata admin, browse all tabs **Integration:** - Printing-shop plug-in ships a `plate-approval-task` form definition - Boot the framework, start the plate-approval BPMN process, verify the user-task renders the form from metadata, complete it, verify values flow through to the workflow ## 12. Out of Scope (Deferred) - **Custom entities / dynamic DDL** — v1.1 - **Drag-and-drop form designer** — future upgrade of the structured editor - **Complex visibility conditions** — v1.0 supports "show when X equals Y" only - **Form versioning / migration** — v1.1 (for now, bumping version is manual) - **List view as override for core pages** — v1.1 (core pages keep hardcoded columns) - **Import/export of form/list-view definitions** — v1.1