Files
marketplaces/docs/context/adrs/ADR-0002-project-editor-config-schema-and-validation-engine.md
sdarbinyan 8d317f043c docs(project-editor): document config schema, form engine, validation
Milestone 6 (final) of the Configuration Engine sprint.

- docs/EDITOR.md: new "Configuration schema, form engine, and validation
  architecture" section covering the field-schema registry, centralized
  validators, live inline feedback, undo/redo, modified-field tracking, and
  pre-publish preview added in M1-M5. Updated the facade signal list and
  folder tour to include schema/.
- ADR-0002 (docs/context/adrs/): records the metadata-augmented-vs-fully-
  schema-driven decision, why severity splits blocking/advisory, and the
  accepted debt (partial [error] binding coverage, schema not yet driving
  template labels).
- FACTS.jsonl (project-editor): decision fact pointing at the ADR.

Note: `barry-cache` is a phantom devDependency (no bin resolves, confirmed in
M1) - ADR/FACTS were authored by hand matching the existing schema/format
rather than via `npm run barry -- adr new` / `validate`.

Gate: tsc --noEmit, npm test (33/33), arch:check, build all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 09:05:32 +04:00

4.7 KiB

id, title, status, date, tags
id title status date tags
ADR-0002 Project Editor field-schema registry, centralized validation, and metadata-augmented form engine active 2026-07-16
project-editor
schema
validation
undo-redo

Context

The Project Editor (src/app/features/project-editor/) edits the tenant BootstrapConfig across 11 hand-authored section templates, all built on the shared/ui field primitives (ADR established post-Sprint 30 redesign; see docs/EDITOR.md). Field labels/hints/defaults lived inline per template, ProjectValidator issues were not addressable to a field, there was no undo/redo, and no per-field modified/error state. Sprint X+1 ("Configuration Engine & Dynamic Form Foundation") required: a field-schema registry, centralized validation (JSON/CSS/URL/color/locale/duplicate-route/widget-config), live inline validation with publish-gating, pre-publish preview, dirty/modified-field tracking with a leave-warning, and session undo/redo — without duplicating form logic or validators, and without breaking draft/publish/import/export.

Decision

Metadata-augmented, not fully schema-driven. A field-schema registry (schema/field-schema.model.ts, schema/editor-schema.ts, schema/editor-schema.service.ts) declares every editable field (dot-path key, section, type, label/hint keys, default, required, validator refs) as the single source of truth for field identity and validator wiring — but section templates stay hand-authored. The schema drives validation and metadata; it does not render fields. This was chosen over a fully schema-driven renderer because 11 mature templates already exist on top of the shared/ui kit, and a renderer rewrite carried materially higher regression risk against "preserve all existing functionality" for no UX gain.

Validators are pure, composed, and tagged. schema/validators/primitives.ts holds one pure function per concern (hex color, HTTP URL, email, JSON, CSS brace-balance, style-block extraction, route normalization). ProjectValidator composes them and attaches section, fieldKey, and severity (error | warning) to every issue, so the same validator is never re-implemented per field or per section.

Severity splits blocking from advisory. publish() now gates on hasBlockingIssues() (severity === 'error') instead of "any issue exists." All 9 pre-existing checks stayed error (no behavior change); the new duplicate-routes and invalid-CSS checks are warning — informative, non-blocking, by design.

Undo/redo is a pure reducer wrapped in debounced facade state. schema/history.util.ts is a framework-free {past, future} snapshot reducer (commit/undo/redo, depth-capped). The facade debounces commits (~300ms) so a typing burst collapses into one undo step, and routes undo/redo through the same localStorage draft-save path as every other mutation so the autosave never desyncs from the undo stack.

Modified-field tracking is a schema diff, not a form-state library. modifiedFields walks every schema field and compares current vs. originalBootstrap by dot-path — no new dependency, reuses EditorSchemaService.getByPath.

Consequences

Positive:

  • One registry answers "what fields exist, what validates them, what do they mean" — new fields register once and get validation + inline-error wiring for free.
  • No validator is duplicated: JSON/CSS/color/URL/email logic lives in exactly one place each.
  • Zero changes to ProjectEditorIoService, ProjectEditorDraftStorageService, or the draft/publish/reset flow — full backward compatibility.
  • Undo/redo and modified-field tracking added without a state-management library.

Negative / accepted debt:

  • Inline [error] binding is wired on a subset of fields (theme palette, general name/domain, branding logo) — not yet every schema-backed field across all 11 sections. Section-level visibility (nav badges, save-bar issue list) covers the rest today.
  • The field-schema registry is not yet consumed by templates for label/hint rendering (still inline i18n keys in each template) — only for validation, diffing, and change-summary labels. A future pass could fully drive labels from the schema.
  • CSS/JSON validators have a thin binding surface today (CSS only via static-page <style> blocks; JSON only via import) since no dedicated customCss/raw-JSON field exists yet in BootstrapConfig.

Compliance Requirements

  • New editable BootstrapConfig fields should get a FieldSchema entry in editor-schema.ts alongside their template addition.
  • New validation rules must be added as a pure function in schema/validators/primitives.ts and composed into ProjectValidator — never inlined ad hoc in a section component.
  • severity: 'error' is reserved for checks that must block Publish; anything advisory is 'warning'.