INVYONE Component Spec v1.0

Draft — 2026-04-08 — Principle: Universality, Compatibility, Extensibility

Core Problem: In vex, ColumnConfig (354 lines), FilterConfig, and FormField are all different specs. Data conversion between components causes errors. invyone unifies everything into FieldConfig (~30 lines).

Design Principles

1
One FieldConfig, Everywhere
Table columns, form inputs, search filters — all use the same FieldConfig. No conversion needed between components.
2
DataPort Communication
Components don't reference each other directly. Standard DataPorts handle all data exchange via output → input matching.
3
3-Layer Config
BaseConfig (common) → TypeConfig (per-type) → InstanceConfig (per-instance override). Clean separation.

Data Flow Overview

[table_type_columns DB]
| (auto load)
[Template.fields: FieldConfig[]] ← single source of truth
| (shared)
+----+----+
| | |
List Create Edit ← 3 views share same fields
| | |
[Component[]] ← each view's component array
|
[DataPort connections] ← Search → Table → Form auto flow
|
[Renderer] ← FieldConfig.type → appropriate UI

Extension Points

+ New FieldType
1. Add to FieldType union
2. Add renderer for table/form/search
Done. FieldConfig structure unchanged.
+ New ComponentType
1. Add to ComponentType union
2. Define TypeConfig
3. Define default DataPorts
4. Implement renderer
Done. No impact on existing specs.
+ New ActionType
1. Add to ActionType union
2. Implement handler
Done.

FieldType (10 Types)

Each FieldType determines how a field renders across table, form, and search contexts.

FieldConfig Properties

All properties of the unified FieldConfig interface (~30 lines replacing vex's 354-line ColumnConfig).

Property Type Required Default Description Used By

FieldConfig Live Editor

Edit the JSON on the left to see how the field renders as table cell, form input, and search filter.

✎ FieldConfig JSON
👁 Preview

ComponentType (11 Types)

Click a type to view its TypeConfig details.

Component Base Structure

Every component, regardless of type, follows this structure.

Component
+-- id: string // unique identifier
+-- type: ComponentType // 11 types
+-- label: string // display name in builder
+-- position: Position // { x, y, w, h } on grid
+-- responsive?: Responsive // { lg?, md?, sm? } overrides
+-- dataSource?: DataSource // { table, mode }
+-- fields?: FieldConfig[] // the ONE field spec
+-- inputs?: DataPort[] // received data
+-- outputs?: DataPort[] // emitted data
+-- config: TypeConfig // type-specific settings

Position

PropertyTypeDescription
xnumberHorizontal position (grid units)
ynumberVertical position (grid units)
wnumberWidth (grid units)
hnumberHeight (grid units)

Responsive Override

Breakpoint-specific position/size overrides. Only changed properties need to be specified.

BreakpointConditionType
lg≥ 1200pxPartial<Position>
md≥ 768pxPartial<Position>
sm< 768pxPartial<Position>

DataSource

PropertyTypeDescription
tablestringBinding target table name
mode'read' | 'write' | 'readwrite'Access mode

TypeConfig Details

Select a component type to see its specific configuration.

DataPortType (4 Types)

DataPort standardizes all inter-component communication.

row
Single record: Record<string, any>
E.g., a selected table row passed to a form.
Record<string, any>
■■
rows
Multiple records: Record<string, any>[]
E.g., selected rows for bulk delete.
Record<string, any>[]
value
Single value: any
E.g., a button click event or total count.
any
params
Search parameters: Record<string, any>
E.g., search filters passed to a table.
Record<string, any>

DataPort Interface

TypeScriptinterface DataPort { name: string; // port name (e.g. 'selectedRow') type: DataPortType; // row | rows | value | params connectedTo?: string; // connected component ID }

Connection Interface

TypeScriptinterface Connection { id: string; from: { componentId: string; port: string; }; to: { componentId: string; port: string; }; }

Default Ports by Component Type

Each component type has predefined input and output ports.

Component Default Outputs Default Inputs

DataPort Flow Diagram

Hover over components to highlight connected ports. Data dots animate along connections.

How it works: The builder visually sets connections. At runtime, an event bus automatically routes data through output → input matching. All data is Record<string, any> — no conversion needed.

Template Structure

One screen = one Template. List + create popup + edit popup in a single bundle.

Template
+-- templateId: string
+-- name: string
+-- category: string // sales, production, purchase...
+-- description?: string
+-- primaryTable: string // main binding table
+-- fields: FieldConfig[] // *** single source of truth ***
+-- views
| +-- list: ViewConfig // list screen
| +-- create: ViewConfig // create popup
| +-- edit: ViewConfig // edit popup (can extend create)
+-- connections: Connection[] // DataPort wiring
+-- companyCode: string
+-- version: number
+-- status: 'draft' | 'published'
+-- createdAt: string
+-- updatedAt: string

ViewConfig

PropertyTypeDescription
componentsComponent[]Components placed in this view
extends?'create'Edit view inherits from create view
size?{ w: number; h: number }Popup dimensions (for create/edit)
Key insight: Template.fields is the single source of truth. All three views (list, create, edit) share the same field definitions. Each view's components reference these shared fields — no duplication.

3 Views Visualization

Switch between list, create, and edit views to see their typical component layout.

List View
Create View
Edit View

vex vs invyone Comparison

Side-by-side comparison of the two approaches.

Aspect vex invyone
Field Spec ColumnConfig (354 lines) + FilterConfig + FormField — all different FieldConfig (~30 lines) — one spec for everything
Inter-component Communication Custom implementation per component, all different DataPort standard protocol — output → input matching
Component Types 33 types (v2-*), each with unique spec 11 types, shared base + concise TypeConfig
Table Config TableListConfig — 354 lines TableConfig — ~15 lines
Screen Structure List / Create / Edit = 3 separate screens 1 Template with 3 embedded views
Field Sharing Defined separately per screen Template.fields shared by all views
Data Transfer Custom events per component — conversion required output → input auto-matching — no conversion

Code Comparison: Field Definition

vex ColumnConfig (excerpt from 354 lines)
// vex: ColumnConfig has 354 lines in types.ts // Same "order_date" field needs 3 different definitions: // 1. For table (ColumnConfig): { columnName: 'order_date', header: 'Order Date', dataType: 'date', sortable: true, filterable: true, width: 120, format: 'YYYY-MM-DD', align: 'center', // ... 20+ more properties } // 2. For form (FormField) - different structure! { name: 'order_date', label: 'Order Date', inputType: 'datepicker', required: true, // ... different props } // 3. For search (FilterConfig) - yet another! { field: 'order_date', filterType: 'dateRange', // ... different again }
invyone FieldConfig (complete)
// invyone: ONE definition for everything { column: 'order_date', label: 'Order Date', type: 'date', visible: true, order: 3, required: true, editable: true, format: 'YYYY-MM-DD', searchable: true, sortable: true } // Table: renders as <td>2026-04-08</td> // Form: renders as <DatePicker /> // Search: renders as <DateRangePicker /> // // Same object. No conversion. // The renderer decides how to display it.

Data Transfer Comparison

vex: Conversion Required
// Table row click → form load // Need to convert ColumnConfig row to FormField format const formData = convertTableRowToFormData( selectedRow, columnConfigs, // table format formFields // form format ); // Conversion code = error-prone // What if a field name doesn't match?
invyone: Zero Conversion
// Table row click → form load // DataPort sends the row as-is // table.output(selectedRow) → form.input(loadRow) // Both use the same FieldConfig // Same column names, same types // The renderer just switches mode: // table mode → display cell // form mode → input widget