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>
43 lines
4.7 KiB
Markdown
43 lines
4.7 KiB
Markdown
---
|
|
id: ADR-0002
|
|
title: Project Editor field-schema registry, centralized validation, and metadata-augmented form engine
|
|
status: active
|
|
date: 2026-07-16
|
|
tags: ["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'`.
|