Based on the public form studio schema.mjs, app.js and ten existing core tests rerun on 2 November 2026. The source uses native DOM controls and a custom v1 format; it is not a JSON Schema engine, an Ant Design form, or a published business platform.
01 / A form is more than a field list
A configuration can say that a form contains a topic, email and request type. It does not by itself specify what happens to typed values when a field is moved, renamed or removed. Those decisions are the first useful design problem to solve.
A drag-and-drop editor is one possible configuration interface. Rendering, validation, version compatibility and persistence are separate responsibilities. The current demo uses buttons to reorder fields; calling it a visual page builder would overstate its scope.
02 / Give each state a single owner
- Definition: field keys, types, labels and validation rules describe the form. The demo keeps these in schema.
- Values: what the user entered, indexed by field key. They belong to a particular form session or saved record, not to a reusable template.
- Runtime state: touched fields, submission attempts, pending requests and error messages. In the demo, submitted controls when errors are shown; no backend submission is performed.
- Editor state: selected field, open panel or focused control. Saving the template should not accidentally save the editor selection.
03 / Inspect a small configuration
This example follows the demo format and can be pasted into its JSON editor. Importing it successfully replaces the form and clears preview values. Try it after saving any configuration you want to keep.
{
"version": 1,
"id": "study_request",
"title": "Study request",
"fields": [
{
"key": "topic",
"label": "Topic",
"labelZh": "学习主题",
"type": "text",
"required": true,
"maxLength": 80
},
{
"key": "hours",
"label": "Hours",
"labelZh": "预计小时数",
"type": "number",
"required": true,
"min": 0,
"max": 40
}
]
}Keep values such as topic="Schema design" outside this template. Numeric zero is valid here; an empty value is a different case.
04 / A label is not a stable identity
The renderer restores values with values[field.key]. Changing the label or sorting the field therefore need not change the stored value. Using array positions would instead attach a value to whichever field occupies that position after reordering.
A key must also keep its meaning. The demo clears the affected value when a field type changes, and clears all values after a valid import. Preserving a key while silently changing its unit or meaning would require an explicit migration rule.
nextKey avoids duplicates among current fields, but it can reuse a deleted field number. It is not a globally unique identifier for database records or schema revisions. A persistent system needs a separate identity policy.
05 / A short hands-on review
- Enter a topic, change its label, then move it down: the same input should follow the field key.
- Change the field type: the old value should be cleared. This is a deliberate compatibility policy.
- Enter malformed JSON or repeat a field key: the current configuration and typed values should survive.
- Apply a valid new configuration: preview values should reset. Refreshing also reloads the initial configuration; changes are tab-local.
06 / What I would improve next
I would keep the current rendering and validation core small, then make the reset/preserve rules explicit before adding a component library. React or Vue can change the rendering layer, but neither determines the semantics of importing or migrating a form.
The same reason makes generated configuration a candidate, not a release. AI can draft fields and labels; the application still has to validate the format, permitted components, constraints and data meaning before applying it.
Sources & further reading
Compiled from code and documents, with AI-assisted drafting and translation. Examples and proposed checks are learning material; only linked evidence establishes what was run.