jsfe 1.40.0 → 1.40.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.
@@ -1 +1 @@
1
- {"version":3,"file":"form-client.d.ts","sourceRoot":"","sources":["../../src/forms/form-client.js"],"names":[],"mappings":"AA2BA,iGAAiG;AACjG;;;;;SAmEC;AAED,+GAA+G;AAC/G;;;;SAgCC;AAED;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4BC;AA9JD,qCAAsC,eAAe,CAAC"}
1
+ {"version":3,"file":"form-client.d.ts","sourceRoot":"","sources":["../../src/forms/form-client.js"],"names":[],"mappings":"AA2BA,iGAAiG;AACjG;;;;;SA6EC;AAED,+GAA+G;AAC/G;;;;SAgCC;AAED;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4BC;AAxKD,qCAAsC,eAAe,CAAC"}
@@ -40,12 +40,13 @@ export function projectForm(session, flows) {
40
40
  const { state: root, context } = pending, cargo = session.cargo, language = session.language || 'en';
41
41
  // Both the code field and its free-text fallback can contain the one-time code.
42
42
  const collectingOtp = session.flowStacks.flat().some(frame => frame.flowId === 'get-and-validate-otp-code');
43
+ let askedShown = false;
43
44
  const fields = (specs, state, prefix = '', depth = 0) => {
44
45
  if (depth > 16)
45
46
  return [];
46
47
  const out = [];
47
48
  for (const input of specs || []) {
48
- if (!safe(input.name) || input.form_client === false || input.internal || input.bind || input.confirmation || !formInputApplies(input, state.values, cargo, state))
49
+ if (!safe(input.name) || input.form_client === false || input.bind || input.confirmation || !formInputApplies(input, state.values, cargo, state))
49
50
  continue;
50
51
  const path = prefix + input.name;
51
52
  const required = input.required_when === undefined || formInputApplies({ when: input.required_when }, state.values, cargo, state);
@@ -57,6 +58,8 @@ export function projectForm(session, flows) {
57
58
  // An alternative is a group constraint, not several individually required fields.
58
59
  const members = fields(input.any_of, state, prefix, depth + 1).map(field => ({ ...field, required: false }));
59
60
  const groupLabel = language === 'es' ? input.label_es || input.label : input.label;
61
+ if (members.length && isCurrent)
62
+ askedShown = true;
60
63
  if (members.length)
61
64
  out.push({ name: input.name, path, kind: 'alternatives', label: groupLabel || '', required, exclusive: input.exclusive === true, ...(isCurrent ? { current: true } : {}), fields: members });
62
65
  }
@@ -67,6 +70,8 @@ export function projectForm(session, flows) {
67
70
  const snapshot = state.childResults?.[input.name];
68
71
  const virtual = child || { values: state.values?.[input.name] || {}, data: snapshot?.data || {}, prepared: snapshot?.prepared || {}, errors: {} };
69
72
  const nested = fields(child?.specs || input.schema.inputs, virtual, path + '.', depth + 1);
73
+ if (nested.length && isCurrent)
74
+ askedShown = true;
70
75
  if (nested.length)
71
76
  out.push({ name: input.name, path, kind: 'group', label: label(input, language), required, ...(isCurrent ? { current: true } : {}), fields: nested });
72
77
  }
@@ -101,17 +106,27 @@ export function projectForm(session, flows) {
101
106
  field.constraints = Object.fromEntries(['min', 'max'].filter(key => typeof profile[key] === 'number').map(key => [key, profile[key]]));
102
107
  if (!field.sensitive && ['string', 'number', 'boolean'].includes(typeof state.values?.[input.name]))
103
108
  field.value = state.values[input.name];
104
- if (state.errors?.[input.name])
105
- field.error = String(state.errors[input.name]);
109
+ // A worded refusal is shown with its field; the bare 'invalid' flag (a refusal with no message, or a whole-form
110
+ // validator asking for this field) is not caller text: the reply above says what is needed and `current` points here.
111
+ const error = state.errors?.[input.name];
112
+ if (error && error !== 'invalid')
113
+ field.error = String(error);
106
114
  // The field the conversation is asking for now, so a client can point at it on a redrawn form.
107
- if (isCurrent)
115
+ if (isCurrent) {
108
116
  field.current = true;
117
+ askedShown = true;
118
+ }
109
119
  out.push(field);
110
120
  }
111
121
  }
112
122
  return out;
113
123
  };
114
124
  const projected = fields(root.specs, root);
125
+ // A card that cannot take the answer being asked for (a chat-only or not yet prepared field) is not drawn: the
126
+ // question is answered in chat, never beside a form that has no place for it.
127
+ const asked = context.names?.length || context.current;
128
+ if (asked && !askedShown)
129
+ return null;
115
130
  return projected.length ? { version: 1, form: root.id, language, fields: projected } : null;
116
131
  }
117
132
  /** Host preflight: call before updateActivity. Unknown paths and malformed envelopes never enter execution. */
@@ -1 +1 @@
1
- export const JSFE_RUNTIME_FINGERPRINT = "sha256:66fc7813afb500bc0554e498473cb92a02100e37f3283ccfd6d84471fbd304ee";
1
+ export const JSFE_RUNTIME_FINGERPRINT = "sha256:046178eae674f9484c9ef39ac003f3ebbf6bba8eabe8b51a0b088da24c80eed3";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsfe",
3
- "version": "1.40.0",
3
+ "version": "1.40.2",
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",