feat: [dasc] Form v2#436
Closed
kozmaadrian wants to merge 86 commits into
Closed
Conversation
added 30 commits
May 11, 2026 20:30
|
Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch.
Commits
|
Contributor
Author
|
Closed in favor of #441 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Test url: https://da.live/form?nx=form-v2#/kozmaadrian/da-sc/forms/demo-v2
Form Block — v2 Review Guide
This guide covers the scope and motivation behind the change, and walks through the architecture and design decisions of the new implementation.
Part 1 — v1 vs v2
Scores
The problems in v1
1. Hard to follow. Schema resolution, state, mutations, validation, serialization, and network saves were all compressed into a single
FormModelclass with shared mutable state. There was no clear entry point, and the loading state relied on implicitnull/undefinedconventions that were invisible in the render logic. A new engineer had nowhere safe to start.2. Mixed layers and responsibilities. There were no enforced boundaries between concerns. Data handling, UI logic, and network calls were coupled throughout — touching one area could silently break another, and any new feature had to navigate the entire codebase to understand where it belonged.
3. Not testable. The business logic had no clean seams. Testing any single concern required constructing the entire model with a full document, schema, and path — there was no way to verify schema compilation, validation, or a mutation in isolation. The only way to catch a regression was manually, in a browser, which meant bugs reached users before they were found.
4. Not reusable. With no headless layer, any external consumer — the DA import tool, an MCP server, an AI agent — had to re-implement the same logic the editor uses: validation, schema resolution, serialization. Copied logic drifts. There was no shared source of truth.
5. Active data loss bug. Every mutation immediately fired a network save with no coordination between concurrent calls. If two saves raced, the older response landing last would silently overwrite the user's latest change.
File structure
The shift is not just a rename — the three folders enforce a strict dependency rule:
ui/can import fromcore/, butcore/has no knowledge ofui/,app/, or the browser. That constraint is what makes the core headless and testable.Part 2 — Overview of v2
Why it is built the way it is
The core design principle of v2 is that each concern lives in exactly one place and cannot bleed into another. Instead of one class that does everything, v2 has three layers with explicit, documented boundaries:
core/is the most important layer. It has zero browser dependencies — no DOM, nofetch, no globals. The entire form's business logic can be loaded in Node.js, in an MCP server, in a CLI script, or in a test runner without a browser. Theui/layer knows about the DOM but knows nothing about schemas or validation. Theapp/layer is the thin glue between them. The boundary is enforced by convention and import structure, not by a build tool, so it is worth checking in review.Why it is more future-proof
AI, MCP, and agent integration is already possible. Because
core/has no browser dependencies, an AI agent or MCP server can callcreateCore(), load a schema and document, validate it, mutate fields, and read the result — all without a browser. The headless-consumer.md file in the block documents exactly how to do this. This was impossible with v1.It has a stable public API.
createCore()returns a fixed, named set of functions. Any consumer — a UI, a test, a script, an agent — programs against that contract. The internals can change without breaking callers, as long as the contract holds.Saves are correct by construction. The single-flight queue is built into the core. No caller can accidentally bypass it. No new mutation type will accidentally introduce the v1 overwrite bug.
Errors are visible by design. Schema issues (unresolvable refs, unsupported composition) and save failures are part of the state shape, not console logs. Any UI or consumer that reads state will see them.
It ships documentation as part of the block. architecture.md, schema-spec.md, and request-flow.md live alongside the code. A new engineer can read the architecture document before reading a single source file.
Part 3 — Main Components and Their Responsibilities
form.jsapp/context.jsready,select-schema,no-schemas, orblocked(with a typed reason). Replaces v1's implicitnull/undefinedconventions.app/da-api.js{ html }or{ error, status }.app/serialize.js+html2json.js+json2html.jscore/index.jscreateCore()returnsload,setField,addItem,insertItem,removeItem,moveItem,getState. Everything else in core is internal.core/schema.js$ref, detects cycles, flags unsupported composition keywords (allOf/oneOf/anyOf) asschemaIssuesinstead of dropping them silently.core/model.jscore/mutate.jscore/validation.jserrorsByPointerwith user-facing error strings, one entry per failing field.ui/editor.jsaria-invalid. Stateless: reads core state, calls core mutations.ui/sidebar.jsui/preview.jsFurther reading
v2 — same UX