# INVYONE Component Spec v1.0 — Interactive Viewer

INVYONE Component Spec v1.0

# 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

Property | Type | Description
--- | --- | ---
`x` | number | Horizontal position (grid units)
`y` | number | Vertical position (grid units)
`w` | number | Width (grid units)
`h` | number | Height (grid units)

## Responsive Override

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

Breakpoint | Condition | Type
--- | --- | ---
`lg` | ≥ 1200px | `Partial<Position>`
`md` | ≥ 768px | `Partial<Position>`
`sm` | < 768px | `Partial<Position>`

## DataSource

Property | Type | Description
--- | --- | ---
`table` | string | Binding 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

Property | Type | Description
--- | --- | ---
`components` | `Component[]` | 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