jsfe 1.12.0 → 1.13.0
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.
- package/dist/forms/contracts.d.ts +1 -0
- package/dist/forms/contracts.d.ts.map +1 -1
- package/dist/forms/flows.d.ts +2 -0
- package/dist/forms/flows.d.ts.map +1 -1
- package/dist/forms/flows.js +6 -0
- package/dist/helpers/answer.js +2 -2
- package/dist/helpers/form-state.d.ts.map +1 -1
- package/dist/helpers/form-state.js +13 -7
- package/dist/helpers/match-inputs.d.ts.map +1 -1
- package/dist/helpers/match-inputs.js +18 -1
- package/dist/runtime-identity.js +1 -1
- package/docs/copilot-reference.md +7 -3
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../src/forms/contracts.js"],"names":[],"mappings":"AAgBA;;;IAGC;AACD;;;;;EAGC;AACD,qGAAqG;AACrG;;QAwCC;AAqBD;;;QAEC;AAgBD;;;;;;;;;QASC;AACD;;;;QAGC;AACD;;;;;;;;QA8BC;AAED,mHAAmH;AACnH,kDAgBC;AAED,+GAA+G;AAC/G
|
|
1
|
+
{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../src/forms/contracts.js"],"names":[],"mappings":"AAgBA;;;IAGC;AACD;;;;;EAGC;AACD,qGAAqG;AACrG;;QAwCC;AAqBD;;;QAEC;AAgBD;;;;;;;;;QASC;AACD;;;;QAGC;AACD;;;;;;;;QA8BC;AAED,mHAAmH;AACnH,kDAgBC;AAED,+GAA+G;AAC/G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2EC"}
|
package/dist/forms/flows.d.ts
CHANGED
|
@@ -365,6 +365,8 @@ export namespace FIELD_PRESETS {
|
|
|
365
365
|
export { digits_6 as digits };
|
|
366
366
|
let pii_2: boolean;
|
|
367
367
|
export { pii_2 as pii };
|
|
368
|
+
let validator_2: string;
|
|
369
|
+
export { validator_2 as validator };
|
|
368
370
|
let question_11: string;
|
|
369
371
|
export { question_11 as question };
|
|
370
372
|
let question_es_11: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"AA6HA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4DC;AACD;;;GAGG;AACH;;;;;;EAGC;AAocD,wHAAwH;AACxH,wEAaC;AACD,4FAA4F;AAC5F;;;EAYC;AACD,sGAAsG;AACtG;;;EAOC;AAhoBM,6CAAuG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyzB9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAOG"}
|
package/dist/forms/flows.js
CHANGED
|
@@ -82,6 +82,7 @@ export const FIELD_PRESETS = {
|
|
|
82
82
|
retry: 'Please say or enter the expiration as four digits, month then year, like 0 9 2 8.', retry_es: 'Por favor diga o ingrese el vencimiento en cuatro dígitos, mes y año, por ejemplo 0 9 2 8.',
|
|
83
83
|
retry_text: 'Please type the expiration as four digits, month then year, like 0928.', retry_text_es: 'Por favor escriba el vencimiento en cuatro dígitos, mes y año, por ejemplo 0928.' },
|
|
84
84
|
card_cvv: { label: 'security code', label_es: 'código de seguridad', profile: { kind: 'digits', min: 3, max: 4 }, digits: { min: 3, max: 4 }, pii: true,
|
|
85
|
+
validator: 'validate-card-cvv',
|
|
85
86
|
question: 'What is the security code on the back of the card?', question_es: '¿Cuál es el código de seguridad al reverso de la tarjeta?' },
|
|
86
87
|
card_zip: { label: 'billing ZIP code', label_es: 'código postal de facturación', profile: { kind: 'digits', min: 5, max: 5 }, digits: { min: 5, max: 5 },
|
|
87
88
|
question: 'What is the billing ZIP code for the card?', question_es: '¿Cuál es el código postal de facturación de la tarjeta?' },
|
|
@@ -602,6 +603,10 @@ const validators = [
|
|
|
602
603
|
description: 'Validator: month and year, a real month, not expired (validateExpiration). Normalizes to MMYY. Params: input, value.',
|
|
603
604
|
parameters: [{ name: 'input', type: 'string', description: 'input name' }, { name: 'value', type: 'string', description: 'the reading' }],
|
|
604
605
|
steps: [SIDE('vce_check', "(function (v) { var m = /^(0?[1-9]|1[0-2])\\D*(?:20)?(\\d{2})$/.exec(String(v).replace(/\\s+/g, '').replace(/de/gi, '')) || /^(\\d{2})(\\d{2})$/.exec(String(v).replace(/\\D/g, '')) || /^(0[1-9])0(\\d{2})$/.exec(String(v).replace(/\\D/g, '')); var mm = m ? ('0' + m[1]).slice(-2) : ''; var yy = m ? m[2] : ''; cargo.inputCheck = m && validateExpiration(mm, yy) ? { ok: true, value: mm + yy } : { ok: false, message: \"That expiration date doesn't look right.\", message_es: 'Esa fecha de vencimiento no parece correcta.' }; return true; })(value)")] },
|
|
606
|
+
{ id: 'validate-card-cvv', name: 'ValidateCardCvv', version: '1.0.0', scope: 'isolated',
|
|
607
|
+
description: 'Validator: the security code has the length of its card brand, 4 digits for American Express (34, 37) and 3 for every other brand; with no card number yet, 3 or 4. Params: input, value, values.',
|
|
608
|
+
parameters: [{ name: 'input', type: 'string', description: 'input name' }, { name: 'value', type: 'string', description: 'the reading' }, { name: 'values', type: 'object', description: "the form's values, for the card number" }],
|
|
609
|
+
steps: [SIDE('vcc_check', "(function (v, n) { var d = String(v).replace(/\\D/g, ''); var pan = String(n || '').replace(/\\D/g, ''); var want = pan ? (/^3[47]/.test(pan) ? 4 : 3) : 0; var ok = want ? d.length === want : d.length === 3 || d.length === 4; cargo.inputCheck = ok ? { ok: true, value: d } : want === 4 ? { ok: false, message: \"That security code doesn't look right. It is the 4 digits on the front of the card.\", message_es: 'Ese código de seguridad no parece correcto. Son los 4 dígitos al frente de la tarjeta.' } : { ok: false, message: \"That security code doesn't look right. It is the 3 digits on the back of the card.\", message_es: 'Ese código de seguridad no parece correcto. Son los 3 dígitos al reverso de la tarjeta.' }; return true; })(value, typeof values !== 'undefined' && values ? values.card_number : '')")] },
|
|
605
610
|
];
|
|
606
611
|
// ── field collectors and sub-forms ──────────────────────────────────────────────────────────────────────────────────
|
|
607
612
|
const fieldCollectors = Object.entries(FIELD_COLLECTORS).map(([id, field]) => formFlow({
|
|
@@ -857,6 +862,7 @@ const LIBRARY_CONTRACTS = {
|
|
|
857
862
|
'load-source': { role: 'worker' }, 'answer-facts': { role: 'worker' },
|
|
858
863
|
'validate-card-number': { role: 'worker', result: { path: 'cargo.inputCheck', type: 'object' } },
|
|
859
864
|
'validate-card-expiration': { role: 'worker', result: { path: 'cargo.inputCheck', type: 'object' } },
|
|
865
|
+
'validate-card-cvv': { role: 'worker', result: { path: 'cargo.inputCheck', type: 'object' } },
|
|
860
866
|
'collect-phone': { role: 'collector' }, 'collect-email': { role: 'collector' }, 'collect-zip': { role: 'collector' },
|
|
861
867
|
'collect-street-address': { role: 'collector' }, 'collect-city': { role: 'collector' }, 'collect-card-number': { role: 'collector' },
|
|
862
868
|
'collect-card-expiration': { role: 'collector' }, 'collect-card-cvv': { role: 'collector' }, 'collect-card-zip': { role: 'collector' },
|
package/dist/helpers/answer.js
CHANGED
|
@@ -347,8 +347,8 @@ export function numericAmountReading(input) {
|
|
|
347
347
|
// Mixed answers with an explicit monetary marker or payment verb still parse their independent amount.
|
|
348
348
|
if (/\b(?:cards?|tarjetas?|ending|termina(?:d[ao]s?)?|visa|mastercard|amex|discover)\b/i.test(raw) && !isAmountAnswer(raw) && !/\b(?:pay|paying|pagar|abonar)(?:\b|(?=\d))/i.test(raw))
|
|
349
349
|
return { value: NaN, candidates: 0 };
|
|
350
|
-
// 1.234,56 (European) → 1234.56; 1,234.56 → 1234.56; 12,50 → 12.50
|
|
351
|
-
let t = /\d{1,3}(\.\d{3})+,\d{1,2}\b/.test(raw) ? raw.replace(/\./g, '').replace(',', '.') : raw.replace(/,(?=\d{3}\b)/g, '').replace(/(\d),(\d{1,2})\b/g, '$1.$2');
|
|
350
|
+
// 1.234,56 (European) → 1234.56; 1,234.56 → 1234.56; 12,50 → 12.50; 1.500 → 1500 (three digits after a dot are never cents)
|
|
351
|
+
let t = /\d{1,3}(\.\d{3})+,\d{1,2}\b/.test(raw) ? raw.replace(/\./g, '').replace(',', '.') : raw.replace(/,(?=\d{3}\b)/g, '').replace(/\b\d{1,3}(?:\.\d{3})+\b(?![.,]\d)/g, (m) => m.replace(/\./g, '')).replace(/(\d),(\d{1,2})\b/g, '$1.$2');
|
|
352
352
|
const dc = t.match(DOLLARS_AND_CENTS);
|
|
353
353
|
if (dc) {
|
|
354
354
|
t = t.replace(DOLLARS_AND_CENTS, () => ` $${dc[1]}.${dc[2].padStart(2, '0')} `);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-state.d.ts","sourceRoot":"","sources":["../../src/helpers/form-state.js"],"names":[],"mappings":"AAQA,qDAKC;AAED,oDAEC;AAED,qFAAqF;AACrF,oEA8BC;
|
|
1
|
+
{"version":3,"file":"form-state.d.ts","sourceRoot":"","sources":["../../src/helpers/form-state.js"],"names":[],"mappings":"AAQA,qDAKC;AAED,oDAEC;AAED,qFAAqF;AACrF,oEA8BC;AA0CD,2FAEC;AACD,8FAcC;AACD,2DAOC;AACD,uEA6CC;AAED,kGAAkG;AAClG,2GAOC;AACD,0HAA0H;AAC1H,uEAwBC;AACD,8HAA8H;AAC9H;;;;;EAWC;AACD,uGAAuG;AACvG;;;;;;;;;;;;QAkCC;AAED,0FAA0F;AAC1F;;;SAIC;AACD,gGAAgG;AAChG;;;;;;;;;;;EAQC;AAED,sGAAsG;AACtG,2DAIC;AACD;;;GAGG;AACH;;;;;;;SAkBC;AAED,2FAA2F;AAC3F,oGAIC;AACD,gFA4BC;AAED,2GAA2G;AAC3G;;;EASC;AACD,mEAoBC;AACD,mEAGC;AACD,sEAUC;AACD,+EAEC;AAoCD,qFAYC;AACD,oGAqCC;AACD;;;SAKC;AACD,qEAGC;AACD,gEAgBC;AACD;;uGAEuG;AACvG,sEAGC;AACD,2DAQC;AACD,oGAUC;AAED,uEAAuE;AACvE,wHA+BC;AAED,uHAAuH;AACvH,6EAaC;AACD,mHAAmH;AACnH,iGAsDC;AACD;;;GAGG;AACH;;;SAcC;AACD,+EASC;AACD,8EAeC;AAnpBM,8CAAmE"}
|
|
@@ -70,10 +70,13 @@ export function resolveFormInputs(inputs, presets = {}) {
|
|
|
70
70
|
validateFormActivation(input, resolved);
|
|
71
71
|
return resolved;
|
|
72
72
|
}
|
|
73
|
-
/** Dormant entry is explicit: named descendant fields may propose local controllers, never ambient state.
|
|
73
|
+
/** Dormant entry is explicit: named descendant fields may propose local controllers, never ambient state. A subform
|
|
74
|
+
* names its entry fields (activate_on); a plain input is its own entry ("NEW CARD, LINK, or a different amount": an
|
|
75
|
+
* amount said there chooses the amount option), so it declares no activate_on. */
|
|
74
76
|
function validateFormActivation(input, specs) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
const plain = !input.form && !!input.profile && input.activate_on === undefined;
|
|
78
|
+
if (!input.activate || typeof input.activate !== 'object' || Array.isArray(input.activate) || !Object.keys(input.activate).length || !plain && (!input.form || !Array.isArray(input.activate_on) || !input.activate_on.length))
|
|
79
|
+
throw new Error(`FORM activation ${input.name} needs controllers, and a subform with activate_on fields or a plain input without them`);
|
|
77
80
|
for (const [name, value] of Object.entries(input.activate)) {
|
|
78
81
|
const controller = specs.flatMap(s => s.any_of || [s]).find(s => s.name === name);
|
|
79
82
|
if (!controller?.profile || controller.form || controller.bind || name === input.name || !hasFormValue(value) || typeof value === 'object')
|
|
@@ -82,7 +85,7 @@ function validateFormActivation(input, specs) {
|
|
|
82
85
|
if (!reading || !own(reading, 'value') || !hasFormValue(reading.value) || !formValuesEqual(reading.value, value))
|
|
83
86
|
throw new Error(`FORM activation ${input.name} has invalid controller value ${name}`);
|
|
84
87
|
}
|
|
85
|
-
for (const path of input.activate_on) {
|
|
88
|
+
for (const path of input.activate_on || []) {
|
|
86
89
|
if (typeof path !== 'string' || !path || path.split('.').some(name => !name || forbidden.has(name)))
|
|
87
90
|
throw new Error(`FORM activation ${input.name} has invalid entry field`);
|
|
88
91
|
if (input.schema) {
|
|
@@ -188,11 +191,14 @@ export function formInputChain(forms, id, cargo = {}) {
|
|
|
188
191
|
for (const input of spec.any_of || [spec]) {
|
|
189
192
|
if (entries && !entries.includes(input.name))
|
|
190
193
|
continue;
|
|
191
|
-
|
|
194
|
+
const inputCondition = formCondition(input, state.values, cargo, state);
|
|
195
|
+
// A dormant plain input that declares activate proposes its controllers when it is answered.
|
|
196
|
+
const inputDormant = inputCondition !== true && !activation && !input.form && input.activate !== undefined ? dormantActivation(input, state, cargo) : null;
|
|
197
|
+
if ((inputCondition !== false || inputDormant) && !input.form && input.profile)
|
|
192
198
|
out.push({ ...input,
|
|
193
199
|
...(prefix ? { label: `${prefix}: ${input.label || input.name}`, label_es: `${prefixEs || prefix}: ${input.label_es || input.label || input.name}` } : {}),
|
|
194
200
|
...(input.choices_from && Array.isArray(state.data?.[input.choices_from]) ? { profile: { ...input.profile, choices: state.data[input.choices_from] } } : {}),
|
|
195
|
-
...(activation ? { activation } : {}), form: target, scope, ...(spec.any_of ? { group: spec.name, exclusive: spec.exclusive === true } : {}), value: state.values[input.name],
|
|
201
|
+
...(activation || inputDormant ? { activation: activation || inputDormant } : {}), form: target, scope, ...(spec.any_of ? { group: spec.name, exclusive: spec.exclusive === true } : {}), value: state.values[input.name],
|
|
196
202
|
// its declared prerequisites hold accepted values: a field that cannot be validated yet is not a context for
|
|
197
203
|
// an unnamed out-of-order answer (contract, reference §5)
|
|
198
204
|
ready: (input.depends_on || spec.depends_on || []).every(name => hasFormValue(state.values[name])) });
|
|
@@ -211,7 +217,7 @@ export function enrichFormMatches(matches, chain, form, names = [], asked = '')
|
|
|
211
217
|
const enrich = h => {
|
|
212
218
|
const spec = chain.find(c => c.form === h.form && c.name === h.name) || {};
|
|
213
219
|
return { ...h, label: h.option_label || spec.label || h.name, label_es: h.option_label_es || spec.label_es || spec.label || h.name, ...(h.option_label ? { option: true } : {}),
|
|
214
|
-
validator: spec.validator || '', ...(spec.response ? { response: spec.response } : {}), after: spec.after || '', validate_always: spec.validate_always === true, ...(spec.activation ? { activation: spec.activation } : {}), pii: spec.pii === true, current: h.form === form && names.includes(h.name), asked };
|
|
220
|
+
validator: spec.validator || '', ...(spec.response ? { response: spec.response } : {}), after: spec.after || '', validate_always: spec.validate_always === true, ...(spec.activation ? { activation: spec.activation } : {}), ...(spec.silent === true ? { silent: true } : {}), pii: spec.pii === true, current: h.form === form && names.includes(h.name), asked };
|
|
215
221
|
};
|
|
216
222
|
return { ...matches, hits: matches.hits.map(enrich), ambiguous: matches.ambiguous.map(enrich), remaining: (matches.remaining || []).map(group => group.map(enrich)) };
|
|
217
223
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"match-inputs.d.ts","sourceRoot":"","sources":["../../src/helpers/match-inputs.js"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,sEAQC;AAqED;;;GAGG;AACH,sFAwGC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;mHACmH;AACnH;;;;;;;;;;;;;;;;;;;;;EAeC;AACD;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"match-inputs.d.ts","sourceRoot":"","sources":["../../src/helpers/match-inputs.js"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,sEAQC;AAqED;;;GAGG;AACH,sFAwGC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;mHACmH;AACnH;;;;;;;;;;;;;;;;;;;;;EAeC;AACD;;;;;;;;;;;;;;;;;;;;;;EAoMC"}
|
|
@@ -299,6 +299,9 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
299
299
|
const spanText = (reading) => (reading.spans || []).map(([a, b]) => raw.slice(a, b)).join(' ');
|
|
300
300
|
const refused = [];
|
|
301
301
|
let readings = [];
|
|
302
|
+
// An invited input is read as in order while it is asked (docs/copilot-reference.md §5): its asked signature, not
|
|
303
|
+
// its narrower out-of-order one ("40" at "NEW CARD, LINK, or a different amount" is the amount).
|
|
304
|
+
const invitedInput = new Set([...list.filter(isCur).flatMap((c) => Array.isArray(c.invites) ? c.invites : []), ...(Array.isArray(cur.invites) ? cur.invites : [])]);
|
|
302
305
|
for (const c of list) {
|
|
303
306
|
// Rule 2: a confirmation is answered only when asked, firm by CONFIRM_WORDS, soft by the wider YES vocabulary.
|
|
304
307
|
const confirmation = ['firm', 'soft'].includes(c.confirmation) ? c.confirmation : '';
|
|
@@ -313,7 +316,7 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
313
316
|
}
|
|
314
317
|
const vocabulary = confirmation ? { ...c.profile, choices: (c.profile.choices || []).map((x) => x.key === 'yes' ? { ...x, phrases: [...(x.phrases ?? x.words ?? []), ...(confirmation === 'firm' ? CONFIRM_WORDS : ANSWER_WORDS.yes)], exact: confirmation === 'firm' || x.exact === true } : x.key === 'no' ? { ...x, phrases: [...(x.phrases ?? x.words ?? []), ...ANSWER_WORDS.no] } : x) } : c.profile;
|
|
315
318
|
const profile = narrativeCurrent && vocabulary.kind === 'email' ? { ...vocabulary, literal: true } : vocabulary;
|
|
316
|
-
let r = readInputProfile(profile, raw, isCur(c));
|
|
319
|
+
let r = readInputProfile(profile, raw, isCur(c) || invitedInput.has(c.name) && c.confirmation === undefined);
|
|
317
320
|
// A semantic choice supplied by ask-choice (e.g. spoken keypad keys) is a reading, not an early return.
|
|
318
321
|
if (!r && c === own && choiceKey && c.profile.kind === 'choice' && (c.profile.choices || []).some(x => x.key === choiceKey)) {
|
|
319
322
|
r = { ...(r || {}), value: choiceKey, spans: r?.spans?.length ? r.spans : [[0, raw.length]] };
|
|
@@ -346,6 +349,20 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
346
349
|
const wholeRuns = (reading) => runs.every(([a, b]) => !(reading.spans || []).some(([s, e]) => s < b && a < e) || (reading.spans || []).some(([s, e]) => s <= a && e >= b));
|
|
347
350
|
// Rule 4: a bare number has no money mark and no word in its evidence.
|
|
348
351
|
const bare = (reading) => !isAmountAnswer(raw) && (reading.spans || []).length > 0 && (reading.spans || []).every(([a, b]) => /\d/.test(raw.slice(a, b)) && !/\p{L}/u.test(raw.slice(a, b)));
|
|
352
|
+
// Rule 1 by shape: a bare number whose length is the current question's own keypad length (`digits`) answers that
|
|
353
|
+
// question even when its reader rejects it (an expiration "1320" is month 13); its validation decides and the
|
|
354
|
+
// question is asked again. It is never read out of order as another field (that "1320" is not a security code).
|
|
355
|
+
// A choice is excluded (a digit it does not offer reaches the invited amount, rule 4), and so is an amount.
|
|
356
|
+
const bareDigits = raw.replace(/\D/g, '');
|
|
357
|
+
const shapeOf = own && own.digits && !['choice', 'amount', 'text'].includes(own.profile.kind) ? own.digits : null;
|
|
358
|
+
if (shapeOf && bareDigits && /^[\d\s./-]+$/.test(raw.trim()) && !isAmountAnswer(raw) && bareDigits.length >= Number(shapeOf.min || 1) && bareDigits.length <= Number(shapeOf.max || shapeOf.min || 1)
|
|
359
|
+
&& !readings.some((reading) => reading.current || invited(reading))) {
|
|
360
|
+
readings.push({ form: own.form, name: own.name, value: bareDigits, ...(own.validator ? {} : { invalid: true }), known: false, repeatedChoice: false, wholeChoice: false, choice: false, narrative: false, resetOn: [], spans: [[0, raw.length]], keypad: false, fallback: false,
|
|
361
|
+
group: own.group, exclusive: own.exclusive === true, level: forms.indexOf(own.scope || own.form), current: true, confirm: Array.isArray(own.confirm_against) ? own.confirm_against : [] });
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
// not a bare number of the current question's shape, or the current question read it
|
|
365
|
+
}
|
|
349
366
|
readings = readings.filter((reading) => {
|
|
350
367
|
if (inOrder(reading)) {
|
|
351
368
|
if (!reading.current)
|
package/dist/runtime-identity.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const JSFE_RUNTIME_FINGERPRINT = "sha256:
|
|
1
|
+
export const JSFE_RUNTIME_FINGERPRINT = "sha256:d1d531f3fad961b1d17d88b5fb63fffff2d5ddfaf837882fe0345bc496ce5dc2";
|
|
@@ -149,7 +149,7 @@ Use one of these input shapes:
|
|
|
149
149
|
|
|
150
150
|
| Shape | Fields |
|
|
151
151
|
| --- | --- |
|
|
152
|
-
| Preset field | `name`, `field`; optional overrides. Presets: `phone`, `email`, `zip`, `account_number`, `ssn_last4`, `amount`, `street_address`, `city`, `state`, `card_number`, `card_expiration`, `card_cvv`, `card_zip`. |
|
|
152
|
+
| Preset field | `name`, `field`; optional overrides. Presets: `phone`, `email`, `zip`, `account_number`, `ssn_last4`, `amount`, `street_address`, `city`, `state`, `card_number`, `card_expiration`, `card_cvv`, `card_zip`. `card_cvv` takes its card brand's length (`validate-card-cvv`: 4 digits for American Express, 3 for every other brand). |
|
|
153
153
|
| Inline field | `name`, `profile`, questions and business validation. |
|
|
154
154
|
| Child form | `name`, `form:"installed-child-id"`, optional `parameters`. JSFE links its schema; do not copy the derived `schema` into editable source. |
|
|
155
155
|
| Alternative group | `name`, `any_of:[leaf-or-child declarations]`; optional `exclusive:true`. At least one applicable member satisfies the group; exclusive competing answers require clarification. |
|
|
@@ -172,7 +172,8 @@ Field options:
|
|
|
172
172
|
| `choices_from`, `question_from` | Read prepared choices/question from the owner's `data`; corresponding localized question storage is handled by shared workers. |
|
|
173
173
|
| `digits`, `pii`, `as_spoken` | Collection hints. FORM preserves original speech by default; false explicitly opts into cleanup. |
|
|
174
174
|
| `confirm_against` | Declare overlapping field interpretations that require clarification even when normal current-field priority applies. |
|
|
175
|
-
| `activate`, `activate_on` | Narrow dormant
|
|
175
|
+
| `activate`, `activate_on` | Narrow dormant activation: proposed controller assignments plus, for a subform, nominated descendant entry paths (`activate_on`). A dormant plain input declares `activate` alone and is its own entry: an amount said at "NEW CARD, LINK, or a different amount" chooses the amount option (`revised_amount` with `when:{next:'amount'}`, `activate:{next:'amount'}`). Controller and entry validate atomically; a rejected entry must not flip a controller. |
|
|
176
|
+
| `silent` | An accepted answer is not acknowledged. For an input that hands its value on to another form's input (its `after` flow), whose own acceptance or rejection is what the caller hears. |
|
|
176
177
|
| `response` | `{description}` on a readable leaf; use existing contextual DISPATCH extraction before accepting an open-ended answer. |
|
|
177
178
|
| `proposal` | Optional approval of an inferred field candidate, independent of final action consent. |
|
|
178
179
|
| `max_misses`, `max_attempts`, `on_exhausted` | Bounds for unreadable responses, validator rejections and deliberate branch recovery. |
|
|
@@ -214,7 +215,10 @@ Declarations:
|
|
|
214
215
|
Rules, in order:
|
|
215
216
|
|
|
216
217
|
1. **The current question first.** The whole answer is read against the current input's signature and the inputs it
|
|
217
|
-
invites. A match is that answer.
|
|
218
|
+
invites (an invited input with its asked signature, not its out-of-order one). A match is that answer. A bare number
|
|
219
|
+
whose length is the current input's keypad length (`digits`) is its answer even when its reader rejects it: its
|
|
220
|
+
validation decides and the question is asked again, so an expiration "1320" (month 13) is never a security code.
|
|
221
|
+
A choice keeps rule 4, and an amount is read by its own profile.
|
|
218
222
|
2. **A confirmation is answered only when it is asked.** Out of order it never makes sense, so no words, YES, NO or the
|
|
219
223
|
option's own phrases, fill a confirmation that is not the current question.
|
|
220
224
|
3. **Out of order requires a ready field and the whole signature.** A bare number never fills, out of order, a field whose
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsfe",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "TypeScript workflow engine for conversational/automation apps: multi-flow orchestration, interrupt/resume, stack-based control, robust errors, and flexible tool/REST integration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ron Pinkas",
|