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>
This commit is contained in:
@@ -10,6 +10,7 @@ src/app/features/project-editor/
|
||||
sections/ one component per editor tab (see below)
|
||||
components/ shared editor UI (save bar, HTML editor)
|
||||
models/ ProjectEditorState, EDITOR_SECTION_BOOTSTRAP_KEYS
|
||||
schema/ field-schema registry, validators/, history.util (Sprint X+1, see below)
|
||||
services/ ProjectValidator, ProjectEditorDraftStorageService, LocaleSyncService
|
||||
facade/ ProjectEditorFacade
|
||||
```
|
||||
@@ -18,7 +19,7 @@ Route: `/edit/:section` or `/{lang}/edit/:section`.
|
||||
|
||||
## Facade
|
||||
|
||||
`ProjectEditorFacade` exposes: `loadBootstrap()`, `updateBootstrap(updater)`, `exportBootstrap()`, `importBootstrap()`, `preview()`, `save()`, `publish()`, plus signals `bootstrap`, `status` (`draft|published`), `dirty`, `lastSavedAt`, `lastPublishedAt`, `validationIssues`, `homepageWidgets`, `homepagePage`. Components in `sections/*` inject this facade directly (an accepted exception to the presentational-component rule, per ADR-006 — these are container/section components, not shared UI).
|
||||
`ProjectEditorFacade` exposes: `loadBootstrap()`, `updateBootstrap(updater)`, `exportBootstrap()`, `importBootstrap()`, `preview()`, `save()`, `publish()`, `undo()`, `redo()`, plus signals `bootstrap`, `status` (`draft|published`), `dirty`, `canUndo`, `canRedo`, `lastSavedAt`, `lastPublishedAt`, `validationIssues`, `blockingIssues`, `hasBlockingIssues`, `issuesByField`, `issuesBySection`, `modifiedFields`, `modifiedSections`, `changeSummary`, `homepageWidgets`, `homepagePage`, plus the `fieldError(key)` method. Components in `sections/*` inject this facade directly (an accepted exception to the presentational-component rule, per ADR-006 — these are container/section components, not shared UI). See "Configuration schema, form engine, and validation architecture" below for the schema/validator/undo internals.
|
||||
|
||||
## Sections
|
||||
|
||||
@@ -46,9 +47,43 @@ Route: `/edit/:section` or `/{lang}/edit/:section`.
|
||||
- **Per-field reset is not implemented** — no per-field default registry exists; only section- and project-level reset.
|
||||
- **No backend persistence exists for any of this today** — see `docs/BACKEND.md` item 2 for the endpoints needed.
|
||||
|
||||
## Validation
|
||||
## Configuration schema, form engine, and validation architecture (Sprint X+1)
|
||||
|
||||
`ProjectValidator` (`services/project-validator.service.ts`) runs on every save-bar render and blocks Publish (not Save) on: missing `branding.logoUrl`, no supported locales, invalid `tenant.websiteBaseUrl`, duplicate static-page slugs (falls back to `route`), empty homepage, a homepage widget with no `type`, duplicate header nav links, invalid theme colors, missing translations for a supported locale, and layout/section-layout values outside the known enums (`PlatformLayoutType`, `SectionLayoutStrategy`).
|
||||
**Approach: metadata-augmented, not fully schema-driven.** Section templates stay hand-authored (`sections/*.component.html`); a field-schema registry sits alongside them as the single source of truth for field identity, labels, and validator wiring. This was chosen over a schema-driven renderer to preserve every existing template/UX pixel-for-pixel while still centralizing metadata and validation — the highest-value, lowest-regression-risk option given 11 mature section templates already built on the `shared/ui` primitives (see the primitives table above).
|
||||
|
||||
### Field-schema registry (`schema/`)
|
||||
|
||||
- `field-schema.model.ts` — `FieldSchema`: `{ key, section, type, labelKey, hintKey?, default?, required?, validators? }`. `key` is a dot path into `BootstrapConfig` (e.g. `theme.palette.primary`), unique per section. `validators` references reusable validator names (`hexColor`, `url`, `email`, `json`, `css`, `localeCompleteness`, `duplicateRoutes`, `widgetConfig`) rather than embedding logic.
|
||||
- `editor-schema.ts` — `SECTION_FIELD_SCHEMAS`: every editable field, one entry per section, sourced from what each template already renders. `ALL_FIELD_SCHEMAS` flattens it.
|
||||
- `editor-schema.service.ts` (`EditorSchemaService`, `providedIn: 'root'`) — `getFields(section)`, `getField(key)`, `all()`, `getByPath(source, key)` (safe dot-path resolver, never throws on a missing segment).
|
||||
|
||||
The schema is currently consumed by the facade (validation issue → field mapping, modified-field diffing, change-summary labels), not by the templates directly — templates keep calling `facade.updateBootstrap()` the same way they always did.
|
||||
|
||||
### Centralized validators (`schema/validators/`)
|
||||
|
||||
`primitives.ts` holds pure, framework-free functions — one per concern, reused everywhere that concern appears: `isValidHexColor`, `isValidHttpUrl`, `isValidEmail`, `validateJson`, `validateCss` (brace-balance check, comments stripped), `extractStyleBlocks` (pulls `<style>` bodies out of static-page HTML), `normalizeRoute` (trim/strip-slashes/lowercase for duplicate comparison).
|
||||
|
||||
`ProjectValidator` (`services/project-validator.service.ts`) composes these primitives into checks and tags every `ProjectValidationIssue` with `section`, `fieldKey`, and `severity` (`'error'` blocks Publish, `'warning'` is advisory). Checks: missing `branding.logoUrl`, no supported locales, invalid `tenant.websiteBaseUrl`, duplicate static-page slugs, **duplicate routes** across `pages`/`staticPages` (warning), empty homepage, a homepage widget with no `type`, **malformed widget config** — missing `id`/`type`/`version`/`props` (error), duplicate header nav links, invalid theme colors, **invalid CSS** inside static-page `<style>` blocks (warning), missing translations for a supported locale, and layout/section-layout values outside the known enums.
|
||||
|
||||
### Live inline feedback (facade)
|
||||
|
||||
`ProjectEditorFacade` exposes, on top of `validationIssues`: `blockingIssues` / `hasBlockingIssues` (severity-filtered), `issuesByField: Map<string, ProjectValidationIssue[]>`, `issuesBySection: Map<ProjectEditorSectionId, number>`, and `fieldError(key)` (first message for a field, or `null`). `publish()` gates on `hasBlockingIssues()`, not "any issue" — a duplicate-route or invalid-CSS warning no longer blocks publishing. Sections bind `[error]` on `app-form-field` for schema-backed fields (currently: theme palette, general name/domain, branding logo — the rest read `validationIssues`/`issuesBySection` via the save bar and nav badges rather than per-field, since not every section has been wired yet). `project-editor-nav` shows a red badge with the blocking-issue count per section.
|
||||
|
||||
### Undo / redo (`schema/history.util.ts` + facade)
|
||||
|
||||
A pure, framework-free reducer (`emptyHistory`, `commit`, `undo`, `redo`) over immutable `BootstrapConfig` snapshots, capped at 50 entries. The facade wraps it with **debounced commits** (~300ms): `updateBootstrap()` captures the pre-burst snapshot on the first call in a burst and only pushes it to history once edits settle, so a run of rapid typing collapses into one undo step instead of one per keystroke. `undo()`/`redo()` route through the same draft-save path as every other mutation, so the `localStorage` autosave never desyncs from the in-memory undo stack. History is cleared on `loadBootstrap()`, `publish()`, and `resetDraft()` (a fresh baseline invalidates old snapshots). UI: save-bar Undo/Redo buttons (`canUndo`/`canRedo`), `Ctrl/Cmd+Z` / `Ctrl/Cmd+Shift+Z` / `Ctrl/Cmd+Y` page-level shortcuts (skipped while a text field has focus, so native per-field text undo still works).
|
||||
|
||||
### Modified-field tracking
|
||||
|
||||
`modifiedFields` (facade, `computed<Set<string>>`) diffs every schema field's current value against `originalBootstrap`. `modifiedSections` rolls that up per section for an amber dot in the nav (shown only when a section has no blocking-issue badge). `changeSummary` builds the before/after rows (schema label + stringified value, truncated for objects) consumed by the Preview section below.
|
||||
|
||||
### Pre-publish preview
|
||||
|
||||
The Preview tab (`preview-section`) now opens with a "changes since last publish" card: the full validation issue list (warning/error styled) plus a before/after table from `changeSummary`, ahead of the existing export/import/live-preview card. Reuses the existing `ProjectEditorPreviewService.preview()` — no new preview mechanism, just more visibility before triggering it.
|
||||
|
||||
### What stayed the same
|
||||
|
||||
No changes to `ProjectEditorIoService` (export/import), `ProjectEditorDraftStorageService` (draft `localStorage` format), or the publish/draft/reset flow described above — draft/publish/import/export compatibility is fully preserved. Section templates are unchanged except for `[error]` bindings on already-existing `app-form-field` usages.
|
||||
|
||||
## Admin Authentication (QR reuse)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user