ngx-t-workflow-typings 3.2.0 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -75,6 +75,100 @@ export interface BulkEmailConfig {
75
75
  * Removed in the next major.
76
76
  */
77
77
  export type LegacyBulkEmailConfig = BulkEmailConfig & Record<string, unknown>;
78
+ /**
79
+ * Who an email step sends to.
80
+ *
81
+ * The FIRST question the editor asks, because every other recipient control is
82
+ * gated on it. Replaces `BulkEmailType`, which asked about delivery mechanism —
83
+ * a question a workflow author has no basis to answer.
84
+ */
85
+ export declare enum EmailRecipientMode {
86
+ /** One address, held in a form control on the document. */
87
+ FormControl = "formControl",
88
+ /** One address, resolved from the assignee of another step in this workflow. */
89
+ Member = "member",
90
+ /** One fixed address, typed into the step. */
91
+ Literal = "literal",
92
+ /** Every row of a list held on the document. The old `bulkEmail` behaviour. */
93
+ List = "list"
94
+ }
95
+ /**
96
+ * Which template an email step starts from.
97
+ *
98
+ * Replaces `BulkEmailType.IServe` as the gate for the procurement-only fields:
99
+ * the author is picking a TEMPLATE, which they can reason about, instead of a
100
+ * delivery mode, which they cannot.
101
+ */
102
+ export declare enum EmailTemplatePreset {
103
+ /** Author writes the whole body. */
104
+ Blank = "blank",
105
+ /** Procurement supplier bid invite — reveals the tender and closing-date keys. */
106
+ SupplierBidInvite = "supplierBidInvite"
107
+ }
108
+ /**
109
+ * Serialised rich-text body, structurally typed.
110
+ *
111
+ * This is a Quill Delta op list, but the shape is declared locally rather than
112
+ * imported: `quill` is not a dependency of this package and must not become
113
+ * one. Stored as a plain object (never a `Delta` instance) so it round-trips
114
+ * through reactive-form state and the library's structural equality check —
115
+ * that check rejects values whose constructors differ.
116
+ */
117
+ export interface EmailBodyDocument {
118
+ readonly ops: readonly unknown[];
119
+ }
120
+ /**
121
+ * Configuration of an email (`bulkMail`) step.
122
+ *
123
+ * DELIBERATELY FLAT. Every member is one editor control and one `deepBind`
124
+ * path, so visibility gating, the required marker and error-to-control matching
125
+ * all address the same spelling with no mapping layer. Nesting would force the
126
+ * editor to bind `['email','recipients','listFormKey']` and buy nothing.
127
+ *
128
+ * Every member is optional because the editor writes them progressively; which
129
+ * ones are REQUIRED, and under which conditions, is stated by
130
+ * `STEP_TYPE_REQUIREMENTS[bulkMail]` rather than by the type.
131
+ */
132
+ export interface EmailStepConfig {
133
+ /** Recipient strategy. Gates every other recipient field. */
134
+ mode?: EmailRecipientMode;
135
+ /** {@link EmailRecipientMode.FormControl}: the control holding the address. */
136
+ addressFormKey?: string;
137
+ /** {@link EmailRecipientMode.Member}: `stepId` whose assignee is mailed. */
138
+ memberStepId?: string;
139
+ /** {@link EmailRecipientMode.Literal}: the address itself. */
140
+ address?: string;
141
+ /** {@link EmailRecipientMode.List}: the control holding the recipient array. */
142
+ listFormKey?: string;
143
+ /** {@link EmailRecipientMode.List}: the property of each ROW holding the address. */
144
+ listAddressProperty?: string;
145
+ /** Template to start from. Gates the procurement-only keys below. */
146
+ preset?: EmailTemplatePreset;
147
+ /**
148
+ * Subject line: plain text that may embed `{{token}}` references.
149
+ *
150
+ * A TEMPLATE STRING, not a form-control pointer — that is the whole point of
151
+ * the change. `{{contractType}}` reads a form control, `{{$org.name}}` a
152
+ * server-resolved namespace, matching the `$`-picker convention already used
153
+ * by the payload and header template editors.
154
+ */
155
+ subject?: string;
156
+ /** Editable rich-text source of the body. The source of record for re-editing. */
157
+ bodyDocument?: EmailBodyDocument;
158
+ /**
159
+ * The body compiled to sanitised, inline-styled, email-safe HTML with
160
+ * `<span data-token>` holes.
161
+ *
162
+ * DERIVED from {@link bodyDocument} at SAVE time, never at send time: a
163
+ * rich-text-editor upgrade must not be able to change the rendering of mail
164
+ * already configured. Produced by `quillToEmailHtml` in `ngx-t-workflows`.
165
+ */
166
+ bodyHtml?: string;
167
+ /** {@link EmailTemplatePreset.SupplierBidInvite} only: control holding the tender number. */
168
+ tenderNumberFormKey?: string;
169
+ /** {@link EmailTemplatePreset.SupplierBidInvite} only: control holding the closing date. */
170
+ closingDateFormKey?: string;
171
+ }
78
172
  export interface IProcessNodeBase extends ProcessStepTemplate {
79
173
  stepId: string;
80
174
  position: IPoint;
@@ -134,6 +228,19 @@ export type AdjudicationValue = AdjudicationConfig | string;
134
228
  export interface ProcessStep extends IProcessNodeBase {
135
229
  duration?: Duration;
136
230
  reference?: string;
231
+ /**
232
+ * Configuration of an email step. THE field the editor writes.
233
+ *
234
+ * @see EmailStepConfig for why this replaces {@link ProcessStep.bulkEmail}
235
+ * rather than extending it.
236
+ */
237
+ email?: EmailStepConfig;
238
+ /**
239
+ * @deprecated Superseded by {@link ProcessStep.email}. The builder no longer
240
+ * writes this field; it remains declared so existing readers compile and so a
241
+ * persisted document authored before the change still type-checks. The engine
242
+ * owns the read-time migration from these keys onto {@link EmailStepConfig}.
243
+ */
137
244
  bulkEmail?: BulkEmailConfig;
138
245
  /**
139
246
  * @deprecated LEGACY DUPLICATE of {@link BulkEmailConfig.documentValueWithEmail};
@@ -15,6 +15,69 @@ export var BulkEmailType;
15
15
  /** "Email to list" — plain email to a list resolved from a document property. */
16
16
  BulkEmailType["Default"] = "default";
17
17
  })(BulkEmailType || (BulkEmailType = {}));
18
+ /* -------------------------------------------------------------------------- */
19
+ /* THE EMAIL STEP — supersedes `bulkEmail` entirely */
20
+ /* -------------------------------------------------------------------------- */
21
+ /* */
22
+ /* ONE step type, configured several ways. `bulkMail` stops meaning "send a */
23
+ /* supplier bid invite to a list" and starts meaning "send an email", with the */
24
+ /* recipient strategy and the template chosen per step. */
25
+ /* */
26
+ /* WHY A NEW FIELD RATHER THAN MORE KEYS ON `bulkEmail` */
27
+ /* --------------------------------------------------- */
28
+ /* `bulkEmail` carries four kinds of redundancy that cannot be removed in */
29
+ /* place: */
30
+ /* */
31
+ /* 1. `documentValueWithEmail` / `listPropertyWithEmail` exist BOTH here and */
32
+ /* as top-level `ProcessStep` columns. The editor writes the nested pair; */
33
+ /* the top-level pair is dead but persisted. */
34
+ /* 2. `type` offers two values of which one (`default`) has no send path */
35
+ /* anywhere — a legal, persisted, permanently inert configuration. */
36
+ /* 3. `emailSubjectFormKey` / `emailBodyFormKey` can only POINT at a form */
37
+ /* control. There is no way to author content, so every email's wording */
38
+ /* has to be smuggled in through a form field. */
39
+ /* 4. `tenderNumberFormKey` / `closingDateFormKey` are procurement-specific */
40
+ /* keys sitting on the general-purpose config, gated by a delivery mode */
41
+ /* rather than by a template. */
42
+ /* */
43
+ /* {@link EmailStepConfig} is the optimised shape: flat (so every member maps */
44
+ /* 1:1 onto one editor control and one `deepBind` path), mode-discriminated, */
45
+ /* and free of the duplicate pair. The editor writes ONLY this field. */
46
+ /* `bulkEmail` and the two top-level duplicates remain DECLARED so existing */
47
+ /* readers keep compiling, and the engine owns the read-time migration. */
48
+ /* -------------------------------------------------------------------------- */
49
+ /**
50
+ * Who an email step sends to.
51
+ *
52
+ * The FIRST question the editor asks, because every other recipient control is
53
+ * gated on it. Replaces `BulkEmailType`, which asked about delivery mechanism —
54
+ * a question a workflow author has no basis to answer.
55
+ */
56
+ export var EmailRecipientMode;
57
+ (function (EmailRecipientMode) {
58
+ /** One address, held in a form control on the document. */
59
+ EmailRecipientMode["FormControl"] = "formControl";
60
+ /** One address, resolved from the assignee of another step in this workflow. */
61
+ EmailRecipientMode["Member"] = "member";
62
+ /** One fixed address, typed into the step. */
63
+ EmailRecipientMode["Literal"] = "literal";
64
+ /** Every row of a list held on the document. The old `bulkEmail` behaviour. */
65
+ EmailRecipientMode["List"] = "list";
66
+ })(EmailRecipientMode || (EmailRecipientMode = {}));
67
+ /**
68
+ * Which template an email step starts from.
69
+ *
70
+ * Replaces `BulkEmailType.IServe` as the gate for the procurement-only fields:
71
+ * the author is picking a TEMPLATE, which they can reason about, instead of a
72
+ * delivery mode, which they cannot.
73
+ */
74
+ export var EmailTemplatePreset;
75
+ (function (EmailTemplatePreset) {
76
+ /** Author writes the whole body. */
77
+ EmailTemplatePreset["Blank"] = "blank";
78
+ /** Procurement supplier bid invite — reveals the tender and closing-date keys. */
79
+ EmailTemplatePreset["SupplierBidInvite"] = "supplierBidInvite";
80
+ })(EmailTemplatePreset || (EmailTemplatePreset = {}));
18
81
  /**
19
82
  * The adjudication role a workflow step plays.
20
83
  *
@@ -1,5 +1,5 @@
1
1
  import { WorkflowStepTypeEnum } from '../ProcessStep/WorkflowStepType.interface.js';
2
- import { AdjudicationType, BulkEmailType } from '../ProcessStep/ProcessStep.interface.js';
2
+ import { AdjudicationType, EmailRecipientMode, EmailTemplatePreset } from '../ProcessStep/ProcessStep.interface.js';
3
3
  import { StepRequirementSeverity } from './StepTypeRequirements.interface.js';
4
4
  import { ReferenceCatalog, ReferenceTargetKind, } from './StepReferenceRules.interface.js';
5
5
  /** `ElementTypes.WorkflowAdjudication`. See the guard above. */
@@ -43,12 +43,10 @@ function formIdRule(stepType) {
43
43
  };
44
44
  }
45
45
  /**
46
- * `bulkEmail.<key>` -> a control name in the document form.
46
+ * `email.<key>` -> a control name in the document form.
47
47
  *
48
- * ERROR, not warning. Every one of these is dereferenced as
49
- * `document.form[key]` by the WORKFLOW ENGINE on the supplier-bid-invite send
50
- * path (`E/services/workflow.ts:595-611`), which runs only when
51
- * `bulkEmail.type === 'i-serve'` — hence the shared `when` gate below.
48
+ * ERROR, not warning. Every one of these is dereferenced as `document.form[key]`
49
+ * by the WORKFLOW ENGINE on the send path (`E/services/workflow.ts:595-611`).
52
50
  *
53
51
  * The engine never guards these reads, so a stale key yields `undefined` and the
54
52
  * enclosing branch silently no-ops. Worse, the send happens on the DEFERRED side
@@ -56,27 +54,31 @@ function formIdRule(stepType) {
56
54
  * (`E/utils/workflowSideEffects.ts:25-27`): the API call succeeds, the step
57
55
  * completes, and no email is ever sent.
58
56
  *
59
- * `listPropertyWithEmail` is deliberately NOT one of these — it indexes into a
60
- * ROW of the recipient list, not into the document form. See
57
+ * `email.listAddressProperty` is deliberately NOT one of these — it indexes a
58
+ * ROW of the recipient list, not the document form. See
61
59
  * {@link EXCLUDED_STEP_REFERENCES}.
60
+ *
61
+ * @param key - The `EmailStepConfig` member, without the `email.` prefix.
62
+ * @param label - Display label for the offending control.
63
+ * @param consequence - One sentence on what breaks, for the author.
64
+ * @param when - The recipient mode or template preset that makes the key live.
65
+ * @param extraEvidence - Rule-specific `file:line` sites, read sites first.
62
66
  */
63
- function bulkEmailKeyRule(key, label, consequence, extraEvidence) {
67
+ function emailKeyRule(key, label, consequence, when, extraEvidence) {
64
68
  return {
65
- id: `bulkMail.bulkEmail.${key}`,
69
+ id: `bulkMail.email.${key}`,
66
70
  stepType: WorkflowStepTypeEnum.BulkMail,
67
- field: `bulkEmail.${key}`,
71
+ field: `email.${key}`,
68
72
  label,
69
73
  targetKind: ReferenceTargetKind.FormControlInWorkflow,
70
- // The engine reads all of these ONLY inside the `i-serve` branch.
71
- when: [{ field: 'bulkEmail.type', equals: BulkEmailType.IServe }],
74
+ when,
72
75
  severity: StepRequirementSeverity.Error,
73
76
  message: consequence,
74
77
  evidence: [
75
78
  ...extraEvidence,
76
- 'E/services/workflow.ts:589-594 (the send gate; everything below runs only under `type == "i-serve"`)',
77
79
  'E/utils/workflowSideEffects.ts:25-27 (the send is deferred and its failures are logged and swallowed — nothing surfaces to the user)',
78
- "W/component/workflow-diagram/processStepEditorConfig.ts:415-478 (the `deepBind: ['bulkEmail', …]` writers; picker scope is the workflow-wide `allInputs` union, which is the right pool: the engine reads the accumulated `document.form`)",
79
- 'W/component/workflow-diagram/store/workflowDiagramSelectors.ts:284-298 (that union)',
80
+ 'W/component/workflow-diagram/processStepEditorConfig.ts (the writers; picker scope is the workflow-wide `allInputs` union, which is the right pool: the engine reads the accumulated `document.form`)',
81
+ 'W/component/workflow-diagram/store/workflowDiagramSelectors.ts (that union)',
80
82
  ],
81
83
  requires: [ReferenceCatalog.WorkflowControlNames],
82
84
  requiresExternalCatalog: true,
@@ -271,27 +273,20 @@ export const STEP_REFERENCE_RULES = {
271
273
  /* BULK MAIL */
272
274
  /* ====================================================================== */
273
275
  [WorkflowStepTypeEnum.BulkMail]: [
274
- bulkEmailKeyRule('documentValueWithEmail', 'Document property with the email list', 'This names the document field holding the recipient list. Pointing at a control this workflow does not have makes the list `undefined`, the send loop never runs, and NOT ONE supplier is emailed — while the step reports success.', [
276
+ emailKeyRule('addressFormKey', 'Field holding the email address', 'This names the document field holding the recipient address. Pointing at a control this workflow does not have leaves the address `undefined`, so nothing is sent — while the step reports success.', [{ field: 'email.mode', equals: EmailRecipientMode.FormControl }], ['E/services/workflow.ts:601 (`document.form[propkey]`)']),
277
+ emailKeyRule('listFormKey', 'Field holding the recipient list', 'This names the document field holding the recipient list. Pointing at a control this workflow does not have makes the list `undefined`, the send loop never runs, and NOT ONE recipient is emailed — while the step reports success.', [{ field: 'email.mode', equals: EmailRecipientMode.List }], [
275
278
  'E/services/workflow.ts:595,601 (`const propkey = ...; const arrValue = document.form[propkey]`)',
276
279
  'E/services/workflow.ts:603 (`if (arrValue?.length > 0)` — the silent no-op)',
277
280
  ]),
278
- bulkEmailKeyRule('emailSubjectFormKey', 'Email subject field', 'This names the document field holding the subject line. Pointing at a control this workflow does not have sends every supplier a bid invite with no subject and a blank headline.', [
279
- 'E/services/workflow.ts:597,606 (`document.form[emailSubjectKey]`)',
280
- 'E/services/mailer.ts:206,210 (`subject` and the `Rfqdescription` template variable)',
281
- ]),
282
- bulkEmailKeyRule('emailBodyFormKey', 'Email body field', 'This names the document field holding the invite body. Pointing at a control this workflow does not have mails every supplier an empty invite.', [
283
- 'E/services/workflow.ts:598,607 (`document.form[emailBodyKey]`)',
284
- 'E/services/mailer.ts:214 (`Insertbody` template variable)',
285
- ]),
286
- bulkEmailKeyRule('tenderNumberFormKey', 'Tender number field', "This names the document field holding the tender number, which goes into both the invite and the supplier's signed response token. Pointing at a control this workflow does not have produces an unidentifiable invite and a token that cannot be tied back to a tender.", [
281
+ emailKeyRule('tenderNumberFormKey', 'Field holding the tender number', "This names the document field holding the tender number, which goes into both the invite and the supplier's signed response token. Pointing at a control this workflow does not have produces an unidentifiable invite and a token that cannot be tied back to a tender.", [{ field: 'email.preset', equals: EmailTemplatePreset.SupplierBidInvite }], [
287
282
  'E/services/workflow.ts:599,608 (`document.form[tenderNumberKey]`)',
288
283
  'E/services/workflow.ts:617-620 (signed into the token payload)',
289
284
  'E/services/mailer.ts:212 (`TenderNumber` template variable)',
290
285
  ]),
291
- bulkEmailKeyRule('closingDateFormKey', 'Tender closing date field', "This is the worst of the set to get wrong. The closing date is the EXPIRY of the supplier's response token: an unresolvable control makes it `Invalid Date`, the token's `exp` claim becomes `NaN`, signing throws on the FIRST supplier, and the entire send loop aborts — no invites at all, and the failure is swallowed.", [
286
+ emailKeyRule('closingDateFormKey', 'Field holding the closing date', 'This is the worst of the set to get wrong. The closing date is the EXPIRY of the response token: an unresolvable control makes it `Invalid Date`, the token expiry becomes `NaN`, signing throws on the FIRST recipient, and the entire send loop aborts — no messages at all, and the failure is swallowed.', [{ field: 'email.preset', equals: EmailTemplatePreset.SupplierBidInvite }], [
292
287
  'E/services/workflow.ts:600,609-611 (`new Date(document.form[closingDateKey]).toDateString()` -> "Invalid Date")',
293
288
  "E/utils/tokenizer.ts:64-78 (`exp: Math.floor(new Date(date).getTime()/1000)` -> `NaN`; `jwt.sign` throws, rethrown as `Error('Session have Expired!')`)",
294
- 'E/services/workflow.ts:613-633 (the throw aborts the loop over ALL suppliers)',
289
+ 'E/services/workflow.ts:613-633 (the throw aborts the loop over ALL recipients)',
295
290
  ]),
296
291
  ],
297
292
  /* ====================================================================== */
@@ -826,9 +821,9 @@ export const EXCLUDED_STEP_REFERENCES = [
826
821
  ],
827
822
  },
828
823
  {
829
- field: 'bulkEmail.listPropertyWithEmail',
824
+ field: 'email.listAddressProperty',
830
825
  stepType: WorkflowStepTypeEnum.BulkMail,
831
- reason: "NOT A FORM CONTROL — it indexes into a ROW. The engine reads it as `element[emailColumnKey]` where `element` is an item of `document.form[documentValueWithEmail]`, so the valid names are the keys of the LIST ROWS, not the workflow's control names. Validating it against the control-name union would flag every correct configuration. Same class as the adjudication respondent keys. The field is still REQUIRED (at error) by the per-field layer, because the engine's send gate tests for its presence — only the cross-reference check is unsound.",
826
+ reason: "NOT A FORM CONTROL — it indexes into a ROW. The engine reads it as `element[emailColumnKey]` where `element` is an item of `document.form[listFormKey]`, so the valid names are the keys of the LIST ROWS, not the workflow's control names. Validating it against the control-name union would flag every correct configuration. Same class as the adjudication respondent keys. The field is still REQUIRED (at error) by the per-field layer, because the engine's send gate tests for its presence — only the cross-reference check is unsound.",
832
827
  evidence: [
833
828
  'E/services/workflow.ts:596,614-616 (`const emailColumnKey = ...; const element = arrValue[index]; const email = element[emailColumnKey]`)',
834
829
  'E/services/workflow.ts:592 (still a hard conjunct of the send gate, hence the per-field requirement)',
@@ -1,5 +1,5 @@
1
1
  import { WorkflowStepTypeEnum } from '../ProcessStep/WorkflowStepType.interface.js';
2
- import { AdjudicationType, BulkEmailType } from '../ProcessStep/ProcessStep.interface.js';
2
+ import { AdjudicationType, EmailRecipientMode, EmailTemplatePreset } from '../ProcessStep/ProcessStep.interface.js';
3
3
  import { StepFieldValueType, StepRequirementSeverity, StepTypeOrigin, } from './StepTypeRequirements.interface.js';
4
4
  /* -------------------------------------------------------------------------- */
5
5
  /* Evidence-file shorthands used throughout */
@@ -643,173 +643,210 @@ export const STEP_TYPE_REQUIREMENTS = {
643
643
  /* ====================================================================== */
644
644
  [WorkflowStepTypeEnum.BulkMail]: {
645
645
  stepType: WorkflowStepTypeEnum.BulkMail,
646
- label: 'Bulk Email',
646
+ label: 'Email',
647
647
  origin: StepTypeOrigin.Authored,
648
648
  systemStep: true,
649
649
  required: [
650
650
  LABEL_RULE,
651
651
  {
652
- field: 'bulkEmail',
653
- label: 'Bulk email configuration',
652
+ field: 'email',
653
+ label: 'Email configuration',
654
654
  type: StepFieldValueType.Object,
655
655
  severity: StepRequirementSeverity.Error,
656
- actionLabel: 'Select bulk email type',
657
- hint: 'Start by choosing the bulk email type; the recipient fields appear after that.',
658
- message: 'Set up the bulk email before saving — choose the type and the recipient fields. Left blank, the step completes but not a single email is sent.',
656
+ actionLabel: 'Who receives this email?',
657
+ hint: 'Start by choosing who receives the email; the remaining fields appear once you have.',
658
+ message: 'Set up the email before saving. Left blank, the step completes and not a single message is sent.',
659
659
  evidence: [
660
- 'E/services/workflow.ts:589-594 (the send gate reads `process.bulkEmail?.hasOwnProperty(...)` three times)',
661
- 'E/services/workflow.ts:595-600 (all six keys are dereferenced off it)',
662
- 'W/component/workflow-diagram/WorkflowStepDefault.ts:166-170 (requiredProperties)',
663
- 'W/component/workflow-diagram/processStepEditorConfig.ts:324-478 (seven `deepBind: [\'bulkEmail\', …]` write sites)',
664
- 'P/features/transactions/application/store/functions/workflow-document-utils.ts:352-356 (a bulk-mail step surfacing as ACTIVE means the automated transition never resolved -> `stuck`)',
660
+ 'E/services/workflow.ts:588-635 (the send path; every value below is dereferenced there)',
661
+ 'W/component/workflow-diagram/WorkflowStepDefault.ts (requiredProperties)',
662
+ 'P/features/transactions/application/store/functions/workflow-document-utils.ts:352-356,417 (an email step surfacing as ACTIVE means the automated transition never resolved -> `stuck`)',
665
663
  ],
666
664
  },
667
665
  {
668
- field: 'bulkEmail.type',
669
- label: 'Bulk email type',
666
+ field: 'email.mode',
667
+ label: 'Recipients',
670
668
  type: StepFieldValueType.String,
671
- allowedValues: [BulkEmailType.IServe, BulkEmailType.Default],
669
+ allowedValues: [
670
+ EmailRecipientMode.FormControl,
671
+ EmailRecipientMode.Member,
672
+ EmailRecipientMode.Literal,
673
+ EmailRecipientMode.List,
674
+ ],
672
675
  severity: StepRequirementSeverity.Error,
673
- actionLabel: 'Select bulk email type',
674
- hint: 'Choose the delivery type in the "Select bulk email type" dropdown.',
675
- message: 'Choose the bulk email type. Until you do, the step sends nothing.',
676
+ actionLabel: 'Who receives this email?',
677
+ hint: 'One person from a form field, a step assignee, a fixed address, or everyone in a list.',
678
+ message: 'Choose who receives this email. Until you do, none of the recipient fields can be filled in and the step sends nothing.',
676
679
  evidence: [
677
- 'E/services/workflow.ts:593 (`process.bulkEmail?.type == "i-serve"` — the third and final conjunct of the send gate)',
678
- 'W/component/workflow-diagram/processStepEditorConfig.ts:324-336 (ChipSelect, two options)',
679
- 'W/component/workflow-diagram/processStepEditorConfig.ts:456-461,469-475 (two `additionalTest` branches key off it)',
680
+ 'E/services/workflow.ts:589-594 (the send gate branches on the recipient strategy)',
681
+ 'W/component/workflow-diagram/processStepEditorConfig.ts (ChipSelect, four options)',
682
+ 'REPLACES `bulkEmail.type`, which asked about delivery mechanism rather than recipients and offered a value (`default`) with no send path at all.',
680
683
  ],
684
+ divergesFromDescriptor: 'REPLACES the `bulkEmail.type` rules. The old field forced the author to choose between "Procurement supplier bid invite" and "Email to list" before anything else, which is a question about the SENDER, not about the email. Recipient mode is the real discriminator and every other recipient control gates on it.',
681
685
  },
682
686
  {
683
- field: 'bulkEmail.documentValueWithEmail',
684
- label: 'Document property with email list',
687
+ field: 'email.subject',
688
+ label: 'Subject line',
685
689
  type: StepFieldValueType.String,
686
690
  severity: StepRequirementSeverity.Error,
687
- actionLabel: 'Select document property with email list',
688
- hint: 'Point to the form field that holds the list of recipients.',
689
- message: 'Choose the document field that holds the list of recipients. If it is blank or points at a field the document does not have, the recipient list is empty and nobody is emailed — though the step still reports as done.',
691
+ actionLabel: 'Subject',
692
+ hint: 'Type the subject. Press $ to insert a document value.',
693
+ message: 'Write the subject line. Without one every recipient gets a message with an empty subject, which most mail clients file as suspicious.',
690
694
  evidence: [
691
- 'E/services/workflow.ts:591 (`process.bulkEmail?.hasOwnProperty("documentValueWithEmail")` — gate conjunct 1)',
692
- 'E/services/workflow.ts:595,601 (`const propkey = ...; const arrValue = document.form[propkey]`)',
693
- 'E/services/workflow.ts:603 (`if (arrValue?.length > 0)` the silent no-op when the key misses)',
694
- 'W/component/workflow-diagram/processStepEditorConfig.ts:415-422',
695
- 'W/component/workflow-diagram/processStepEditorConfig.ts:429-433 (the list-property input is gated on this existing)',
695
+ 'E/services/workflow.ts:597,606 (`document.form[emailSubjectKey]` — the legacy pointer this template string replaces)',
696
+ 'E/services/mailer.ts:206,210 (`subject` and the `Rfqdescription` template variable)',
697
+ 'W/shared/functions/quill-email/tokens.ts (the `{{token}}` grammar the subject accepts)',
696
698
  ],
697
- divergesFromDescriptor: 'PROMOTED to error (corrected). Previously `warning` on the grounds that `bulkEmail` had zero read sites; the engine both gates on it and dereferences it.',
699
+ divergesFromDescriptor: 'The field is now a TEMPLATE STRING rather than a form-control pointer (`emailSubjectFormKey`). An author can type a subject and embed `{{token}}` references, instead of being forced to route the wording through a form field.',
698
700
  },
699
701
  {
700
- field: 'bulkEmail.listPropertyWithEmail',
701
- label: 'Email property within each list item',
702
+ field: 'email.bodyHtml',
703
+ label: 'Message body',
702
704
  type: StepFieldValueType.String,
703
705
  severity: StepRequirementSeverity.Error,
704
- actionLabel: 'select email property in list',
705
- hint: 'Name the column inside each list row that holds the email address.',
706
- message: 'Choose which column in each list row holds the email address. Without it the step either sends nothing or emails a blank address that every recipient bounces — one silent failure per recipient.',
706
+ actionLabel: 'Message',
707
+ hint: 'Write the message. Press $ to insert a document value.',
708
+ message: 'Write the message body. An empty body sends a blank email.',
707
709
  evidence: [
708
- 'E/services/workflow.ts:592 (`process.bulkEmail?.hasOwnProperty("listPropertyWithEmail")` — gate conjunct 2)',
709
- 'E/services/workflow.ts:596,616 (`const emailColumnKey = ...; const email = element[emailColumnKey]`)',
710
- 'E/services/mailer.ts:205,219-229 (`to: [email]`; a provider rejection is caught and returned as `{delivered: 0}`, never surfaced)',
711
- "W/component/workflow-diagram/processStepEditorConfig.ts:423-435 (editor gate: `{ testType: 'exists', deepBind: ['bulkEmail','documentValueWithEmail'] }`)",
710
+ 'E/services/workflow.ts:598,607 (`document.form[emailBodyKey]` — the legacy pointer this replaces)',
711
+ 'E/services/mailer.ts:214 (`Insertbody` template variable)',
712
+ 'W/shared/functions/quill-email/quill-to-email-html.ts (compiles the authored body to sanitised, email-safe HTML at SAVE time)',
712
713
  ],
713
- divergesFromDescriptor: 'PROMOTED to error AND MOVED from `conditional` to `required` (corrected). The editor only reveals the input once `documentValueWithEmail` is set, which is why it was previously conditional but the ENGINE requires the key unconditionally as gate conjunct 2, so a step missing it never sends regardless of authoring order.',
714
+ divergesFromDescriptor: 'Derived, not typed by hand: the author edits `email.bodyDocument` in a rich-text editor and the builder compiles this field from it. Validated rather than `bodyDocument` because this is the value that is actually sent.',
714
715
  },
715
716
  ],
716
717
  conditional: [
717
718
  {
718
- field: 'bulkEmail.emailSubjectFormKey',
719
- label: 'Email subject field',
719
+ field: 'email.addressFormKey',
720
+ label: 'Field holding the email address',
721
+ type: StepFieldValueType.String,
722
+ when: [{ field: 'email.mode', equals: EmailRecipientMode.FormControl }],
723
+ severity: StepRequirementSeverity.Error,
724
+ actionLabel: 'Which field holds the email address?',
725
+ hint: 'Point at the form field that captures the recipient address.',
726
+ message: 'Choose the field holding the recipient address. If it is blank or names a field this workflow does not have, there is no address to send to and the step still reports as done.',
727
+ evidence: [
728
+ 'E/services/workflow.ts:601 (`document.form[propkey]` — same dereference, single-address arm)',
729
+ 'W/component/workflow-diagram/store/workflowDiagramSelectors.ts (resolved against the workflow-wide control union)',
730
+ ],
731
+ },
732
+ {
733
+ field: 'email.memberStepId',
734
+ label: 'Step whose assignee is emailed',
735
+ type: StepFieldValueType.String,
736
+ when: [{ field: 'email.mode', equals: EmailRecipientMode.Member }],
737
+ severity: StepRequirementSeverity.Error,
738
+ actionLabel: 'Which step assignee receives it?',
739
+ hint: 'Pick the step whose assigned member should be emailed.',
740
+ message: 'Choose which step assignee receives this email. Without it there is no-one to resolve an address for.',
741
+ evidence: [
742
+ 'E/services/workflow.ts:724-733 (`nextProcess.members` -> `userModel.findOne` -> `nextUser` is the established member-to-address resolution)',
743
+ 'W/component/workflow-diagram/processStepEditorConfig.ts (WorkflowStepPicker supplies `step.stepId`, a uuid string)',
744
+ ],
745
+ },
746
+ {
747
+ field: 'email.address',
748
+ label: 'Email address',
749
+ type: StepFieldValueType.String,
750
+ when: [{ field: 'email.mode', equals: EmailRecipientMode.Literal }],
751
+ severity: StepRequirementSeverity.Error,
752
+ actionLabel: 'Email address',
753
+ hint: 'Type the fixed address this step always mails.',
754
+ message: 'Type the address this step sends to. A fixed address outlives the person who holds it, so prefer a shared mailbox over an individual.',
755
+ evidence: [
756
+ 'E/services/mailer.ts:205 (`to: [email]` accepts a literal address unchanged)',
757
+ ],
758
+ },
759
+ {
760
+ field: 'email.listFormKey',
761
+ label: 'Field holding the recipient list',
720
762
  type: StepFieldValueType.String,
721
- when: [{ field: 'bulkEmail.type', equals: BulkEmailType.IServe }],
763
+ when: [{ field: 'email.mode', equals: EmailRecipientMode.List }],
722
764
  severity: StepRequirementSeverity.Error,
723
- actionLabel: 'Select document property with email subject',
724
- hint: 'Point to the form field that holds the subject line.',
725
- message: 'Choose the document field that holds the email subject line. Leave it blank and every supplier gets a bid invite with no subject and a blank headline.',
765
+ actionLabel: 'Which field holds the list?',
766
+ hint: 'Point at the form field that holds the rows of recipients.',
767
+ message: 'Choose the field holding the recipient list. If it is blank or names a field this workflow does not have, the list is empty and nobody is emailed — though the step still reports as done.',
726
768
  evidence: [
727
- 'E/services/workflow.ts:597,606 (`const emailSubjectKey = ...; const emailSubject = document.form[emailSubjectKey]`)',
728
- 'E/services/mailer.ts:206,210 (`subject: emailSubject` and the `Rfqdescription` template variable)',
729
- 'W/component/workflow-diagram/processStepEditorConfig.ts:436-442',
769
+ 'E/services/workflow.ts:591 (gate conjunct 1), :595,601 (`const arrValue = document.form[propkey]`)',
770
+ 'E/services/workflow.ts:603 (`if (arrValue?.length > 0)` the silent no-op when the key misses)',
771
+ 'MIGRATION: same value as the legacy `bulkEmail.documentValueWithEmail`, under a name that says what it is.',
730
772
  ],
731
- divergesFromDescriptor: 'PROMOTED to error and gated on `i-serve` (corrected). Previously an unconditional `warning`. The engine reads it only inside the `i-serve` branch, so the gate is now explicit, and it feeds an outgoing email — the coordinator\'s "sends mail" criterion.',
732
773
  },
733
774
  {
734
- field: 'bulkEmail.emailBodyFormKey',
735
- label: 'Email body field',
775
+ field: 'email.listAddressProperty',
776
+ label: 'Column holding the address',
736
777
  type: StepFieldValueType.String,
737
- when: [{ field: 'bulkEmail.type', equals: BulkEmailType.IServe }],
778
+ when: [{ field: 'email.mode', equals: EmailRecipientMode.List }],
738
779
  severity: StepRequirementSeverity.Error,
739
- actionLabel: 'Select document property with email body',
740
- hint: 'Point to the form field that holds the invite body text.',
741
- message: 'Choose the document field that holds the invite body. Leave it blank and every supplier gets an empty invite.',
780
+ actionLabel: 'Which column holds the address?',
781
+ hint: 'Name the column inside each row that holds the email address.',
782
+ message: 'Choose which column of each row holds the email address. Without it the step either sends nothing or mails a blank address that every recipient bounces one silent failure per recipient.',
742
783
  evidence: [
743
- 'E/services/workflow.ts:598,607 (`const emailBodyKey = ...; const emailContent = document.form[emailBodyKey]`)',
744
- 'E/services/mailer.ts:214 (`Insertbody: emailContent` template variable)',
745
- 'W/component/workflow-diagram/processStepEditorConfig.ts:443-449',
784
+ 'E/services/workflow.ts:592 (gate conjunct 2), :596,616 (`const email = element[emailColumnKey]`)',
785
+ 'E/services/mailer.ts:205,219-229 (`to: [email]`; a provider rejection is caught and returned as `{delivered: 0}`, never surfaced)',
786
+ 'NOT a form-control reference — it indexes a ROW. See EXCLUDED_STEP_REFERENCES.',
787
+ 'MIGRATION: same value as the legacy `bulkEmail.listPropertyWithEmail`.',
746
788
  ],
747
- divergesFromDescriptor: 'PROMOTED to error and gated on `i-serve` (corrected). Same reasoning as `emailSubjectFormKey`.',
748
789
  },
749
790
  {
750
- field: 'bulkEmail.tenderNumberFormKey',
751
- label: 'Tender number field',
791
+ field: 'email.tenderNumberFormKey',
792
+ label: 'Field holding the tender number',
752
793
  type: StepFieldValueType.String,
753
- when: [{ field: 'bulkEmail.type', equals: BulkEmailType.IServe }],
794
+ when: [{ field: 'email.preset', equals: EmailTemplatePreset.SupplierBidInvite }],
754
795
  severity: StepRequirementSeverity.Error,
755
- actionLabel: 'Select document property with tender number',
756
- hint: 'Point to the form field that holds the tender number.',
757
- message: "Choose the document field that holds the tender number — it identifies the bid the supplier is invited to and ties their response back to it. Without it the invite is unidentifiable and responses cannot be matched to a tender.",
796
+ actionLabel: 'Field holding the tender number',
797
+ hint: 'Point at the form field that holds the tender number.',
798
+ message: 'Choose the field holding the tender number — it identifies the bid the supplier is invited to and ties their response back to it. Without it the invite is unidentifiable and responses cannot be matched to a tender.',
758
799
  evidence: [
759
- 'E/services/workflow.ts:599,608 (`const tenderNumberKey = ...; const tenderNumber = document.form[tenderNumberKey]`)',
800
+ 'E/services/workflow.ts:599,608 (`document.form[tenderNumberKey]`)',
760
801
  'E/services/workflow.ts:617-620 (signed into the response token payload)',
761
802
  'E/services/mailer.ts:212 (`TenderNumber` template variable)',
762
- "W/component/workflow-diagram/processStepEditorConfig.ts:450-463 (editor gate: `expression: 'type === i-serve'` on `deepBind: ['bulkEmail','type']`)",
763
803
  ],
764
- divergesFromDescriptor: 'PROMOTED to error (corrected). The editor gate was already right; the severity was not.',
804
+ divergesFromDescriptor: 'Gate changed from `bulkEmail.type === i-serve` to `email.preset === supplierBidInvite`. Same set of steps, but the author is picking a template rather than a delivery mode.',
765
805
  },
766
806
  {
767
- field: 'bulkEmail.closingDateFormKey',
768
- label: 'Tender closing date field',
807
+ field: 'email.closingDateFormKey',
808
+ label: 'Field holding the closing date',
769
809
  type: StepFieldValueType.String,
770
- when: [{ field: 'bulkEmail.type', equals: BulkEmailType.IServe }],
810
+ when: [{ field: 'email.preset', equals: EmailTemplatePreset.SupplierBidInvite }],
771
811
  severity: StepRequirementSeverity.Error,
772
- actionLabel: 'Select document property with tender closing date',
773
- hint: 'Point to the form field that holds the tender closing date — it is also the response deadline.',
774
- message: "Choose the document field that holds the tender closing date — it doubles as the deadline on each supplier's response link. Leave it blank and the whole send fails on the first supplier, so not one invite goes out, with no error shown.",
812
+ actionLabel: 'Field holding the closing date',
813
+ hint: 'Point at a DATE field — it is also the deadline on each response link.',
814
+ message: 'Choose the field holding the tender closing date — it doubles as the deadline on each supplier response link. Leave it blank and the whole send fails on the first supplier, so not one invite goes out, with no error shown.',
775
815
  evidence: [
776
816
  'E/services/workflow.ts:600,609-611 (`new Date(document.form[closingDateKey]).toDateString()` -> "Invalid Date")',
777
- 'E/services/workflow.ts:617-620 (passed to `generateTimeObjToken(..., closingDate)` as the expiry)',
778
- "E/utils/tokenizer.ts:64-78 (`exp: Math.floor(new Date(date).getTime() / 1000)` -> `NaN`; `jwt.sign` throws and is rethrown as `Error('Session have Expired!')`)",
817
+ "E/utils/tokenizer.ts:64-78 (`exp: Math.floor(new Date(date).getTime() / 1000)` -> `NaN`; `jwt.sign` throws, rethrown as `Error('Session have Expired!')`)",
779
818
  'E/services/workflow.ts:613-633 (the throw aborts the `for` loop over ALL suppliers)',
780
- 'E/utils/workflowSideEffects.ts:25-27 (`void effect().catch(err => Logger.error(...))` — the abort is logged and swallowed, never surfaced to the user)',
781
- "W/component/workflow-diagram/processStepEditorConfig.ts:464-478 (editor gate: `expression: 'type === i-serve'`)",
819
+ 'E/utils/workflowSideEffects.ts:25-27 (`void effect().catch(...)` — the abort is logged and swallowed, never surfaced)',
782
820
  ],
783
- divergesFromDescriptor: 'PROMOTED to error (corrected). Previously `warning` with the message "the invite goes out without one" the opposite of the truth: the invite does not go out at all.',
821
+ divergesFromDescriptor: 'Gate changed to `email.preset === supplierBidInvite`, as for the tender number. Severity stays `error`: this is the worst field in the set to get wrong, because it fails the ENTIRE batch rather than one recipient.',
784
822
  },
785
823
  ],
786
824
  optional: [
787
825
  {
788
- field: 'bulkEmail.type',
789
- label: 'Bulk email type (implemented modes)',
826
+ field: 'email.preset',
827
+ label: 'Template',
790
828
  type: StepFieldValueType.String,
791
- allowedValues: [BulkEmailType.IServe],
829
+ allowedValues: [EmailTemplatePreset.Blank, EmailTemplatePreset.SupplierBidInvite],
792
830
  severity: StepRequirementSeverity.Warning,
793
- actionLabel: 'Select bulk email type',
794
- hint: 'Prefer "Procurement supplier bid invite" "Email to list" does not send yet.',
795
- message: 'Only "Procurement supplier bid invite" actually sends today. If you pick "Email to list" the step saves and runs but no email ever goes out.',
831
+ actionLabel: 'Template',
832
+ hint: 'Start from a blank message, or from the procurement supplier bid invite.',
833
+ message: 'Pick a template to start from. Leaving it unset is the same as starting blank.',
796
834
  evidence: [
797
- 'E/services/workflow.ts:589-594 (the ONLY send gate; `type == "i-serve"` is a hard conjunct)',
798
- 'E/services/workflow.ts:613-633 (the only sender, `SendSupplierEmail`, reachable only through that gate)',
799
- 'E/services/mailer.ts:183-231 (`SendSupplierEmail` uses the "bid invite" template exclusively)',
800
- 'W/component/workflow-diagram/processStepEditorConfig.ts:324-336 (the editor nonetheless offers both options)',
835
+ 'E/services/mailer.ts:183-231 (`SendSupplierEmail` the "bid invite" template the preset reproduces)',
836
+ 'W/component/workflow-diagram/processStepEditorConfig.ts (ChipSelect, two options)',
801
837
  ],
802
- divergesFromDescriptor: 'ADDED. A second rule on the same field, at `warning`, recording that one of the two values the editor offers is not implemented server-side. It is deliberately NOT folded into the `allowedValues` of the required rule above: `default` is a legal persisted value that the editor writes, so rejecting it outright would flag existing data as malformed. The warning states the functional gap without lying about the value set.',
838
+ divergesFromDescriptor: 'NOT required: an unset preset is a well-defined state (blank), unlike the old `bulkEmail.type` where unset meant the step could never send.',
803
839
  },
804
840
  ],
805
841
  sockets: LINEAR_SOCKETS,
806
842
  integrityNotes: [
807
- 'CORRECTED (was AUDIT (c), PARTIAL). The previous revision recorded `bulkEmail` as having "ZERO read sites the sender is presumed to live outside the four audited repositories" and graded every leaf key `warning`. The sender does exist and IS auditable: `osproc-be` implements the whole path at `E/services/workflow.ts:588-635`, sending through `E/services/mailer.ts:183-231` (Mailgun, "bid invite" template) with `E/emails/ms365Graph.ts` / `E/emails/office365.ts` as the alternate transports. All six leaf keys are dereferenced there and are now graded `error`.',
808
- 'ENGINE SEND GATE, in full (`E/services/workflow.ts:589-594`): `!!process && process.bulkEmail?.hasOwnProperty("documentValueWithEmail") && process.bulkEmail?.hasOwnProperty("listPropertyWithEmail") && process.bulkEmail?.type == "i-serve"`. Note it tests KEY PRESENCE, not truthiness `documentValueWithEmail: ""` passes the gate and then resolves `document.form[""]` to `undefined`, so the step reports success and sends nothing. This map requires non-empty values precisely because the engine does not.',
809
- 'MODE ASYMMETRY: the editor offers two delivery modes but the engine implements one. `default` ("Email to list") has no send path anywhere in `osproc-be`. Recorded as the second, `warning`-severity `bulkEmail.type` rule above rather than as a value-set restriction.',
810
- "`documentValueWithEmail` and `listPropertyWithEmail` appear in `properties` (`WorkflowStepDefault.ts:173-174`) as TOP-LEVEL step fields, but the elements with those `name`s bind to the NESTED paths (`processStepEditorConfig.ts:420` -> `['bulkEmail','documentValueWithEmail']`; `:427` -> `['bulkEmail','listPropertyWithEmail']`). The `properties` entries therefore gate the right controls only by name coincidence; the top-level fields themselves are never written and are `@deprecated` legacy duplicates (`ProcessStep.interface.ts:149-168`). This map requires the nested paths only. The engine reads ONLY the nested keys, confirming the top-level pair is dead.",
811
- "`documentValueWithEmail` and `listPropertyWithEmail` appear in `properties` (`WorkflowStepDefault.ts:173-174`) as TOP-LEVEL step fields, but the elements with those `name`s bind to the NESTED paths (`processStepEditorConfig.ts:420` -> `['bulkEmail','documentValueWithEmail']`; `:427` -> `['bulkEmail','listPropertyWithEmail']`). The `properties` entries therefore gate the right controls only by name coincidence; the top-level fields themselves are never written and are `@deprecated` legacy duplicates (`ProcessStep.interface.ts:149-168`). This map requires the nested paths only.",
812
- "Four separate elements share `name: 'bulkEmail'` (`processStepEditorConfig.ts:325,437,444,451,464`) while binding to five different nested keys. `processElement`'s `required` computation (`getSectionElementsForWorkflowStep.ts:57`) keys off `name`, so all five would be marked required together the moment `bulkEmail` is in `requiredProperties` which it is. The editor cannot express per-key requiredness for this step type.",
843
+ 'REWRITTEN against `ProcessStep.email`, which supersedes `bulkEmail` entirely. One step type, configured several ways: the recipient strategy is now an explicit four-value mode instead of being implied by which keys happen to be present, and the procurement-only fields gate on a TEMPLATE rather than on a delivery mode.',
844
+ 'EVIDENCE PROVENANCE. The `file:line` sites cited for the `email.*` rules are the engine reads of the LEGACY key carrying the same value (`email.listFormKey` <- `bulkEmail.documentValueWithEmail` at `E/services/workflow.ts:595,601`, and so on). They are cited because they prove the field is load-bearing, not because the engine reads the new spelling today — the read-time migration from `bulkEmail` onto `EmailStepConfig` is the engines own half of this change and is tracked separately. No rule here claims a read site that does not exist.',
845
+ 'ENGINE GATE, for reference (`E/services/workflow.ts:589-594`): `process.bulkEmail?.hasOwnProperty("documentValueWithEmail") && ...hasOwnProperty("listPropertyWithEmail") && process.bulkEmail?.type == "i-serve"`. It tests KEY PRESENCE, not truthiness — `documentValueWithEmail: ""` passes and then resolves `document.form[""]` to `undefined`, so the step reports success and sends nothing. The validator is deliberately STRICTER than the runtime: `hasValue` treats `""` as absent (`W/component/workflow-diagram/function/nodeValidator.ts:175-177`), so the canvas blocks exactly the configuration the engine would accept and silently no-op.',
846
+ 'NO MEMBERS RULE, AND THAT IS CORRECT. An email step reached as the NEXT step advances only through `E/services/workflow.ts:843-848`, where `updateR = true` is set exclusively when the next step has no members; nothing else in the engine completes one, so members on an email step would stall the document permanently (`P/.../workflow-document-utils.ts:417` already reports that state as `system-fault`). A `maxItems: 0` rule was drafted for this and then REMOVED: the builder makes the state unreachable. `bulkMail` is `systemStep: true`, the context menu hard-disables the members picker for an automated step, and `W/component/workflow-diagram/store/workflowDiagramActions.ts:305,378` force `members: []` on save whenever `systemStep === true`. `W/component/workflow-diagram/function/nodeValidator.ts:156,330-331` consequently skips `members` and `formId` on system steps altogether, so the rule could never fire — and if it somehow did, it would be an error with no reachable control to clear it, which is the specific trap this validator exists to avoid. The hazard is real but the builder already forecloses it; the only way back in is flipping the step off `systemStep`, which is what the flag is for.',
847
+ 'THE `default` MODE IS GONE. `bulkEmail.type` offered "Email to list", which has no send path anywhere in `osproc-be` a legal, persisted, permanently inert configuration the editor actively recommended. It is not carried into `EmailRecipientMode`; list sending is `EmailRecipientMode.List`, which is the arm that actually works.',
848
+ 'THE REDUNDANT PAIR IS GONE. `documentValueWithEmail` / `listPropertyWithEmail` existed BOTH as nested `bulkEmail` keys and as top-level `ProcessStep` columns, with the editor writing the nested pair and the top-level pair persisted but dead. `EmailStepConfig` has one spelling for each.',
849
+ 'ELEMENT NAMES ARE NOW DISTINCT. Five editor elements previously shared `name: "bulkEmail"` across five different bind paths. Visibility is keyed on `name` (`W/component/workflow-diagram/function/getSectionElementsForWorkflowStep.ts:41`) and the required marker is a union of name and bind path (`:145-153`), so all five appeared and were marked required together — the editor could not express per-key requiredness for this step type. Each control now has its own name, which is what makes the conditional rules above renderable.',
813
850
  ],
814
851
  },
815
852
  /* ====================================================================== */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-t-workflow-typings",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
4
4
  "description": "Typings and interfaces for the ngx-t-workflows library.",
5
5
  "keywords": [
6
6
  "typings",
@@ -42,14 +42,14 @@
42
42
  "test": "echo \"Error: no test specified\" && exit 1"
43
43
  },
44
44
  "peerDependencies": {
45
- "ngx-t-forms-types": ">=0.0.27",
45
+ "ngx-t-forms-types": ">=0.0.29",
46
46
  "rxjs": ">=7.8.0"
47
47
  },
48
48
  "dependencies": {
49
49
  "joi": "^18.0.2"
50
50
  },
51
51
  "devDependencies": {
52
- "ngx-t-forms-types": "^0.0.27",
52
+ "ngx-t-forms-types": "^0.0.29",
53
53
  "rimraf": "^6.1.3",
54
54
  "typescript": "^5.9.3"
55
55
  }