Overview
Category: Compound / form control. Selector: sense-automation-editor. The component is a pure editor β no HTTP, no router, no global state. The rule arrives as an immutable input and the composed rule leaves as a typed output; the host owns persistence and execution. Enterprise capabilities (templates, history, conflict detection, import/export, AI authoring) are all default-off, so adopting it never changes existing behaviour.
Feature catalogue
Compose a typed rule from a trigger, a condition tree, and an ordered action list.
Implements ControlValueAccessor + Validator β drop it into a FormGroup with formControlName.
Seed from rule templates and roll back through version history.
Pass sibling rules to surface automations that overlap the one being edited.
Round-trip a rule as portable JSON via opt-in import/export.
Opt-in prompt slot that emits a draft request β the host owns the model call.
Installation
npm install @artificialsenselabs/enterprise Peer dependencies: @angular/core, @angular/common, @angular/forms (^21.2.0) and rxjs ^7.8.0. Standalone β import the component directly.
Quick start
import { Component, signal } from '@angular/core';
import { SenseAutomationEditorComponent } from '@artificialsenselabs/enterprise';
import type {
SenseAutomationRule,
SenseAutomationSaveEvent,
} from '@artificialsenselabs/enterprise';
@Component({
standalone: true,
imports: [SenseAutomationEditorComponent],
template: `
<sense-automation-editor
[rule]="rule()"
colorScheme="auto"
(ruleSave)="onSave($event)"
(ruleCancel)="onCancel()"
/>
`,
})
export class RulePanel {
rule = signal<SenseAutomationRule | null>(null);
onSave(e: SenseAutomationSaveEvent) { /* persist e.rule */ }
onCancel() { /* close the editor */ }
}Forms integration
The editor implements ControlValueAccessor and Validator, so it behaves like a native form control. Bind it with formControlName (or [(ngModel)]) and the control's value is the composed SenseAutomationRule; its validity mirrors the editor's own rule validation.
<form [formGroup]="form">
<!-- The editor IS the control: ControlValueAccessor + Validator -->
<sense-automation-editor formControlName="rule" />
</form>
<!-- form.controls.rule.valid reflects the editor's own validation,
form.controls.rule.value is the composed SenseAutomationRule. -->Inputs 23
| Name | Type | Default | Description |
|---|---|---|---|
rule | SenseAutomationRule | null | null | The rule to edit. Treated as immutable; `null` authors a new rule. |
loading | boolean | false | Renders the skeleton/busy state while the host fetches a rule. |
readonly | boolean | false | Disables every control and hides the action footer β for audit and approval views. |
i18nOverrides | Partial<SenseAutomationEditorI18n> | null | null | Per-instance overrides for any UI string. |
styleClass | string | null | null | Extra CSS classes applied to the host element. |
colorScheme | 'light' | 'dark' | 'auto' | 'auto' | Selects the built-in light/dark theme or follows the OS preference. |
dir | 'ltr' | 'rtl' | null | null | Sets the `dir` HTML attribute on the host element for explicit RTL / LTR control. When `null` (the default) the attribute is absent and the host document's inherited direction applies. |
templates | readonly SenseAutomationRuleTemplate[] | [] | Pre-built rule skeletons shown as a picker when no rule is loaded. |
enableTestMode | boolean | false | When true, reveals a dry-run test panel below the actions section. Off by default β enabling it does not alter any existing behaviour. |
history | readonly SenseAutomationRuleVersion[] | [] | Prior saved snapshots of this rule, newest first. When non-empty, a compact history panel is shown. The host owns persistence β the component never fetches or stores versions itself (ADR-004). |
siblingRules | readonly SenseAutomationRule[] | [] | Other rules in the same tenant. When non-empty, `detectConflicts` runs against the current working state and non-blocking warnings are shown. Off by default (empty array = no conflict analysis). |
schema | SenseAutomationSchema | null | null | Optional data schema describing available entities and their fields. When provided, field and entity inputs gain `<datalist>`-backed autocomplete suggestions (assistive β the user can still type anything not in the list). The condition-value control type is also inferred from the matching schema field type. Absent by default: `null` preserves today's free-text behaviour exactly. |
enableImportExport | boolean | false | When true, reveals Copy JSON / Download JSON / Paste JSON / Upload JSON surfaces in the footer. Hidden by default to keep the UI clean for configurations that manage persistence entirely on the server side. |
conflictRules | readonly SenseAutomationRule[] | [] | Sibling rules to check the current rule against for conflicts. An empty array (default) disables conflict detection. |
conflictOptions | SenseConflictDetectionOptions | undefined | undefined | Options forwarded to `detectConflicts` (e.g. ignored conflict classes). When omitted the default `detectConflicts` options apply. |
maxConditions | number | 0 | Maximum number of conditions allowed. When the list reaches this count, the "Add Condition" button is disabled and a soft-limit warning is shown. `0` (default) disables the limit β all counts are allowed. |
maxActions | number | 0 | Maximum number of actions allowed. When the list reaches this count, the "Add Action" button is disabled and a soft-limit warning is shown. `0` (default) disables the limit β all counts are allowed. |
maxHistoryItems | number | 0 | Truncate the version history list to this many entries (newest first). When non-zero and the full history is longer, a "showing N of M" hint is shown below the list. `0` (default) shows all entries. |
variables | readonly SenseVariable[] | [] | Host-supplied variable list merged with the built-in variables. Used for `{{key}}` token interpolation in action text fields (12.1). |
view | 'form' | 'canvas' | 'form' | Editor presentation mode (4.1). `'form'` (default) renders the structured form; `'canvas'` renders the read-friendly trigger β conditions β actions flow diagram. The canvas reflects the same local signals β it is a pure alternate view, not a separate state model. |
telemetry | SenseAutomationRuleTelemetry | null | null | Recent execution telemetry supplied by the host (4.4). Purely presentational β the editor displays it but never polls or fetches. `null` (default) hides the telemetry overlay entirely. |
enableAiAuthoring | boolean | false | Opt-in seam for AI natural-language authoring (4.2). When `true`, a "describe in plain language" box appears; submitting it emits `aiDraftRequest`. The component itself makes NO model call β the host owns the AI integration (see README "AI authoring") and feeds a draft back via `aiDraft`, exactly like the emit-don't-act discipline of ADR-004. |
aiDraft | SenseAutomationRule | Omit<SenseAutomationRule, 'id'> | null | null | A host-supplied AI-generated draft (4.2). When set, the editor loads it into the form for the user to REVIEW and edit β it never auto-saves. Accepts a full rule or one without an `id` (a brand-new draft). |
Outputs 11
| Name | Payload | Description |
|---|---|---|
ruleSave | SenseAutomationSaveEvent | Emitted when the user saves; carries the composed rule. |
ruleRun | SenseAutomationRunEvent | Emitted by "Run now"; the host performs the run. |
ruleCancel | void | Emitted when the user cancels the edit. |
templateApply | { templateId: string } | Emitted when the user selects a template (for host analytics). |
ruleTest | SenseAutomationTestEvent | Emitted when the user clicks "Run test (emit to host)" so the host can optionally forward the rule+sample to a backend dry-run endpoint. The component itself NEVER calls any network (ADR-004). |
versionRollback | { versionId: string } | Emitted when the user requests a rollback to a historical version. The component does NOT mutate its own state β the host must load the chosen version back via `rule()` to complete the rollback. |
dirtyChange | boolean | Emitted whenever the dirty state changes. Fires `true` as soon as the local working state diverges from the loaded rule(); fires `false` when the user saves (and the host reloads the rule) or when the local state is programmatically reset to match the saved rule. |
exported | SenseAutomationRule | Emitted after a successful export action (copy or download). |
imported | SenseAutomationRule | Emitted after a successful import action (paste or upload). |
conflictClicked | SenseAutomationConflict | Emitted when the user clicks a conflict's "Conflicts withβ¦" button (15.1). |
aiDraftRequest | { prompt: string } | Emitted when the user submits the plain-language authoring box (4.2). The host owns the model call and should feed the result back via `aiDraft`. |
Content-projection slots 5
Pass a TemplateRef to override built-in rendering. With no slot supplied, output is identical to the default.
| Slot input | Context type | Description |
|---|---|---|
triggerConfigTemplate | SaeTriggerConfigContext | Optional `ng-template` that replaces the built-in trigger-config panel. Context: `{ $implicit: SenseAutomationTrigger, disabled: boolean }`. |
actionConfigTemplate | SaeActionConfigContext | Optional `ng-template` that replaces the built-in action config panel (the row header with type-select and reorder buttons is still rendered). Context: `SaeActionConfigContext`. |
conditionRowTemplate | SaeConditionRowContext | Optional `ng-template` that replaces each condition row inside the list. The host template is responsible for the `<li>` element if needed. Context: `SaeConditionRowContext`. |
emptyStateTemplate | SaeEmptyStateContext | Optional `ng-template` for empty sections (trigger not set, no conditions, no actions). Context: `{ $implicit: 'trigger' | 'conditions' | 'actions' }`. |
footerTemplate | SaeFooterContext | Optional `ng-template` that replaces the built-in footer bar (save / cancel / run buttons). Only rendered when `readonly()` is false. Context: `SaeFooterContext`. |
Theming
The component ships a self-contained --sae-* custom-property layer (its documented public theming API). Override any token from host CSS β no ::ng-deep required β or set colorScheme for the built-in light/dark themes.
/* Host CSS β override any --sae-* token, no ::ng-deep needed */
sense-automation-editor.brand {
--sae-color-surface: #ffffff;
--sae-color-border: #e5e7eb;
--sae-color-accent: #3b82f6;
--sae-color-primary: #1d4ed8;
}Token reference 23
| Token | Light | Dark |
|---|---|---|
--sae-color-surface | #ffffff | #1f2937 |
--sae-color-surface-subtle | #f9fafb | #111827 |
--sae-color-surface-disabled | #f3f4f6 | #374151 |
--sae-color-border | #e5e7eb | #374151 |
--sae-color-border-input | #d1d5db | #4b5563 |
--sae-color-accent | #3b82f6 | #60a5fa |
--sae-color-accent-subtle | #eff6ff | #1e3a5f |
--sae-color-accent-border | #bfdbfe | #3b82f6 |
--sae-color-primary | #1d4ed8 | #3b82f6 |
--sae-color-primary-hover | #1e40af | #2563eb |
--sae-color-run | #059669 | #10b981 |
--sae-color-run-hover | #047857 | #059669 |
--sae-color-error | #dc2626 | #f87171 |
--sae-color-danger | #dc2626 | #f87171 |
--sae-color-danger-hover | #b91c1c | #fca5a5 |
--sae-text-primary | #111827 | #f9fafb |
--sae-text-body | #374151 | #d1d5db |
--sae-text-muted | #4b5563 | #9ca3af |
--sae-text-subtle | #6b7280 | #9ca3af |
--sae-text-on-accent | #ffffff | #ffffff |
--sae-radius | 0.375rem | β |
--sae-gap | 1.5rem | β |
--sae-shadow-focus | 0 0 0 2px rgb(59 130 246 / 25%) | β |
Accessibility
- Every control is programmatically labelled; required fields expose
aria-required. - Validation issues are surfaced in an
aria-livesummary and associated with their fields. - Full keyboard operation β add, reorder, and remove conditions and actions without a pointer.
- Ships axe-clean across its empty, populated, invalid, and read-only states (WCAG 2.1 AA).