Parent: #5
Depends on: #44, #45
Goal
Add validation feedback, accessibility, and loading/error states to @singleton-sd/post-kit-editor. The editor must validate the working template against its declared variable catalogue and preview data, surface problems clearly before save or test-send, and be operable by keyboard and screen reader.
Scope
Validation model
src/validation/validate.ts:
export type ValidationSeverity = 'error' | 'warning';
export interface ValidationIssue {
severity: ValidationSeverity;
/** Stable machine-readable identifier, e.g. `undeclared-variable`. */
code: string;
/** Human-readable message shown in the UI. */
message: string;
/** Which part of the editor the issue belongs to. */
field: 'metadata' | 'subject' | 'document' | 'previewData';
/** Variable name, when the issue concerns one. */
variable?: string;
}
export function validateTemplate(files: TemplateSourceFiles): ValidationIssue[];
Rules to implement
Errors:
missing-key / missing-name / missing-subject — required metadata fields empty.
invalid-key — key contains characters outside the accepted set.
undeclared-variable — a {{placeholder}} used in the subject or document is not present in metadata.variables.
missing-preview-value — a declared variable has no value in previewData.
render-failed — the compiler rejects the current source (reuse the failure already surfaced by the preview pane rather than compiling twice).
Warnings:
unused-variable — declared in metadata.variables but not referenced in subject or document.
extra-preview-value — present in previewData but not declared.
Presentation
- A validation summary region listing all current issues, grouped by severity, each linking focus to the responsible control.
- Inline indication on the metadata/subject fields and preview-data rows that own an issue, associated via
aria-describedby.
- The summary region is an ARIA live region so newly appearing errors are announced.
- Expose results to the host application via an optional prop:
onValidationChange?: (issues: ValidationIssue[]) => void;
Gating save and test-send
- Save and Send-test controls are disabled while any
error-severity issue exists, with an accessible explanation of why.
- Warnings never block.
- This is the issue that wires validation into the controls delivered by the save/send-test issue.
Loading and error states
loading?: boolean prop: when true, the editor renders a non-interactive loading state (used while the consumer fetches template files from its repository).
loadError?: string prop: when set, the editor renders an error state with that message instead of the editing surface.
- An internal render/parse failure renders an error boundary fallback with a retry affordance rather than unmounting the host application.
Accessibility
- All interactive controls reachable and operable by keyboard, in a logical tab order.
- Every control has an accessible name; icon-only affordances carry
aria-label.
- Visible focus indicators throughout.
- Panels use appropriate landmarks/headings so a screen-reader user can navigate between metadata, variables, canvas, and preview.
- Status changes (save in progress, validation errors, copy confirmation) are announced via live regions.
- Colour is never the sole carrier of validation meaning — pair it with text or an icon.
Constraints
- Do not duplicate the compiler's rules — where
@singleton-sd/post-kit-compiler already validates something, call it and translate its errors into ValidationIssue rather than reimplementing the check.
- Validation must be pure and synchronous apart from the compiler-backed render check; no network calls.
- Do not auto-fix the user's template. Report issues; never silently rewrite the document, metadata, or preview data.
- Do not add an accessibility testing framework that requires a real browser; assert accessible names, roles, and
aria-* wiring in the existing node --test setup.
- Warnings must not block save.
Acceptance criteria
Agent implementation notes
Read the metadata/variable-catalogue state from #44 and the preview/compiler integration from #45 first — this issue consumes both and must not fork their state. Read packages/post-kit-compiler/src/compiler-error.ts to see what the compiler already reports before writing any duplicate rule. Branch: feat/<issue-number>-editor-validation-accessibility.
Parent: #5
Depends on: #44, #45
Goal
Add validation feedback, accessibility, and loading/error states to
@singleton-sd/post-kit-editor. The editor must validate the working template against its declared variable catalogue and preview data, surface problems clearly before save or test-send, and be operable by keyboard and screen reader.Scope
Validation model
src/validation/validate.ts:Rules to implement
Errors:
missing-key/missing-name/missing-subject— required metadata fields empty.invalid-key—keycontains characters outside the accepted set.undeclared-variable— a{{placeholder}}used in the subject or document is not present inmetadata.variables.missing-preview-value— a declared variable has no value inpreviewData.render-failed— the compiler rejects the current source (reuse the failure already surfaced by the preview pane rather than compiling twice).Warnings:
unused-variable— declared inmetadata.variablesbut not referenced in subject or document.extra-preview-value— present inpreviewDatabut not declared.Presentation
aria-describedby.Gating save and test-send
error-severity issue exists, with an accessible explanation of why.Loading and error states
loading?: booleanprop: when true, the editor renders a non-interactive loading state (used while the consumer fetches template files from its repository).loadError?: stringprop: when set, the editor renders an error state with that message instead of the editing surface.Accessibility
aria-label.Constraints
@singleton-sd/post-kit-compileralready validates something, call it and translate its errors intoValidationIssuerather than reimplementing the check.aria-*wiring in the existingnode --testsetup.Acceptance criteria
validateTemplate()implements every error and warning rule listed above and is exported from the package root along withValidationIssue.aria-describedby.loadingandloadErrorprops render the documented non-interactive states.onValidationChangefires with the current issue list whenever it changes.aria-describedbywiring.pnpm -r --if-present run testpasses.Agent implementation notes
Read the metadata/variable-catalogue state from #44 and the preview/compiler integration from #45 first — this issue consumes both and must not fork their state. Read
packages/post-kit-compiler/src/compiler-error.tsto see what the compiler already reports before writing any duplicate rule. Branch:feat/<issue-number>-editor-validation-accessibility.