@qoretechnologies/reqraft 0.10.58 → 0.10.59-pr.130.ga79c828

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.
Files changed (37) hide show
  1. package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
  2. package/dist/components/form/engine/CompactRow.js +2 -0
  3. package/dist/components/form/engine/CompactRow.js.map +1 -1
  4. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  5. package/dist/components/form/engine/FormEngine.js +11 -5
  6. package/dist/components/form/engine/FormEngine.js.map +1 -1
  7. package/dist/components/form/engine/OptionFieldMessages.d.ts +10 -2
  8. package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
  9. package/dist/components/form/engine/OptionFieldMessages.js +21 -4
  10. package/dist/components/form/engine/OptionFieldMessages.js.map +1 -1
  11. package/dist/components/form/engine/compactRowContext.d.ts +2 -0
  12. package/dist/components/form/engine/compactRowContext.d.ts.map +1 -1
  13. package/dist/components/form/engine/compactRowContext.js.map +1 -1
  14. package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
  15. package/dist/components/form/expressions/builder/index.js +14 -4
  16. package/dist/components/form/expressions/builder/index.js.map +1 -1
  17. package/dist/components/smartEditor/useLspAutocomplete.d.ts.map +1 -1
  18. package/dist/components/smartEditor/useLspAutocomplete.js +60 -38
  19. package/dist/components/smartEditor/useLspAutocomplete.js.map +1 -1
  20. package/dist/helpers/allowedValues.d.ts +11 -1
  21. package/dist/helpers/allowedValues.d.ts.map +1 -1
  22. package/dist/helpers/allowedValues.js +25 -2
  23. package/dist/helpers/allowedValues.js.map +1 -1
  24. package/dist/helpers/expressions.d.ts +10 -1
  25. package/dist/helpers/expressions.d.ts.map +1 -1
  26. package/dist/helpers/expressions.js +20 -6
  27. package/dist/helpers/expressions.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/components/form/engine/CompactRow.tsx +2 -0
  30. package/src/components/form/engine/FormEngine.stories.tsx +68 -0
  31. package/src/components/form/engine/FormEngine.tsx +8 -0
  32. package/src/components/form/engine/OptionFieldMessages.tsx +29 -2
  33. package/src/components/form/engine/compactRowContext.ts +2 -0
  34. package/src/components/form/expressions/builder/index.tsx +18 -4
  35. package/src/components/smartEditor/useLspAutocomplete.ts +71 -44
  36. package/src/helpers/allowedValues.ts +35 -2
  37. package/src/helpers/expressions.ts +18 -5
@@ -3,9 +3,18 @@ import { IQorusTypeObject } from '../hooks/useQorusTypes';
3
3
  export declare const getArgumentType: (expressions: IExpressionSchema[], arg?: IExpression, schema?: IExpressionSchemaArg) => string;
4
4
  export declare const argumentMatchesType: (expressions: IExpressionSchema[], arg: IExpression | undefined, schema: IExpressionSchemaArg) => boolean;
5
5
  export declare const areQorusTypesCompatible: (mainType: string | string[], checkType: string | string[], fallbackTypes?: IQorusTypeObject[]) => boolean;
6
+ /**
7
+ * Whether a type is text, a date or a time: what a condition cannot be. A condition is evaluated by its
8
+ * truthiness, so a number (0 or not), a list or hash (empty or not), binary data and an untyped result
9
+ * all decide it; text, dates and times do not say plainly what is meant, and are compared explicitly
10
+ * instead (`@status == "open"`, `@note != ""`). The Qorus server applies the same rule.
11
+ */
12
+ export declare const isTextOrDateType: (type: string) => boolean;
13
+ /** Whether a place takes a condition: its result must be true or false (`bool`, or a list of only that). */
14
+ export declare const isConditionType: (returnType: string | string[] | undefined) => boolean;
6
15
  /**
7
16
  * Whether an operation is offered where its result must be of `returnType`: a condition (`bool`) is
8
- * built from the operations that give true or false, not from every operation the server has.
17
+ * built from the operations whose result decides it, which is any result but text, a date or a time.
9
18
  *
10
19
  * Only a condition narrows the list. Any other type keeps every operation: a value of one type is often
11
20
  * made from an operation that gives another (text from rich text, a number from an int), and the
@@ -1 +1 @@
1
- {"version":3,"file":"expressions.d.ts","sourceRoot":"","sources":["../../src/helpers/expressions.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAoC,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE5F,eAAO,MAAM,eAAe,gBACb,iBAAiB,EAAE,QAC1B,WAAW,WACR,oBAAoB,WAS9B,CAAC;AAEF,eAAO,MAAM,mBAAmB,gBACjB,iBAAiB,EAAE,OAC3B,WAAW,GAAG,SAAS,UACpB,oBAAoB,YAgB7B,CAAC;AAgBF,eAAO,MAAM,uBAAuB,aACxB,MAAM,GAAG,MAAM,EAAE,aAChB,MAAM,GAAG,MAAM,EAAE,kBACZ,gBAAgB,EAAE,KACjC,OAmCF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,eACvB,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,cAC7B;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAE,KACvE,OAWF,CAAC"}
1
+ {"version":3,"file":"expressions.d.ts","sourceRoot":"","sources":["../../src/helpers/expressions.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAoC,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE5F,eAAO,MAAM,eAAe,gBACb,iBAAiB,EAAE,QAC1B,WAAW,WACR,oBAAoB,WAS9B,CAAC;AAEF,eAAO,MAAM,mBAAmB,gBACjB,iBAAiB,EAAE,OAC3B,WAAW,GAAG,SAAS,UACpB,oBAAoB,YAgB7B,CAAC;AAgBF,eAAO,MAAM,uBAAuB,aACxB,MAAM,GAAG,MAAM,EAAE,aAChB,MAAM,GAAG,MAAM,EAAE,kBACZ,gBAAgB,EAAE,KACjC,OAmCF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,SAAU,MAAM,KAAG,OACoC,CAAC;AAErF,4GAA4G;AAC5G,eAAO,MAAM,eAAe,eAAgB,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,KAAG,OAG3E,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,eACvB,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,cAC7B;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAE,KACvE,OASF,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.expressionFitsReturnType = exports.areQorusTypesCompatible = exports.argumentMatchesType = exports.getArgumentType = void 0;
3
+ exports.expressionFitsReturnType = exports.isConditionType = exports.isTextOrDateType = exports.areQorusTypesCompatible = exports.argumentMatchesType = exports.getArgumentType = void 0;
4
4
  // Copyright 2026 Qore Technologies, s.r.o.
5
5
  // Expression / type-compatibility helpers — ported verbatim from qorus-ide
6
6
  // (`src/helpers/expressions.ts` + `areQorusTypesCompatible` from
@@ -89,9 +89,25 @@ var areQorusTypesCompatible = function (mainType, checkType, fallbackTypes) {
89
89
  return false;
90
90
  };
91
91
  exports.areQorusTypesCompatible = areQorusTypesCompatible;
92
+ /**
93
+ * Whether a type is text, a date or a time: what a condition cannot be. A condition is evaluated by its
94
+ * truthiness, so a number (0 or not), a list or hash (empty or not), binary data and an untyped result
95
+ * all decide it; text, dates and times do not say plainly what is meant, and are compared explicitly
96
+ * instead (`@status == "open"`, `@note != ""`). The Qorus server applies the same rule.
97
+ */
98
+ var isTextOrDateType = function (type) {
99
+ return /^(string|richtext|date|time)$/.test(type.replace(/^\*/, '').replace(/^soft/, ''));
100
+ };
101
+ exports.isTextOrDateType = isTextOrDateType;
102
+ /** Whether a place takes a condition: its result must be true or false (`bool`, or a list of only that). */
103
+ var isConditionType = function (returnType) {
104
+ var types = (0, lodash_1.isArray)(returnType) ? returnType : returnType ? [returnType] : [];
105
+ return types.length > 0 && types.every(function (type) { return /^\*?(bool|boolean)$/.test(type); });
106
+ };
107
+ exports.isConditionType = isConditionType;
92
108
  /**
93
109
  * Whether an operation is offered where its result must be of `returnType`: a condition (`bool`) is
94
- * built from the operations that give true or false, not from every operation the server has.
110
+ * built from the operations whose result decides it, which is any result but text, a date or a time.
95
111
  *
96
112
  * Only a condition narrows the list. Any other type keeps every operation: a value of one type is often
97
113
  * made from an operation that gives another (text from rich text, a number from an int), and the
@@ -99,16 +115,14 @@ exports.areQorusTypesCompatible = areQorusTypesCompatible;
99
115
  * only once it is built (`any`, or the type of its first argument) is offered everywhere.
100
116
  */
101
117
  var expressionFitsReturnType = function (returnType, expression) {
102
- var types = (0, lodash_1.isArray)(returnType) ? returnType : returnType ? [returnType] : [];
103
- var condition = types.length > 0 && types.every(function (type) { return /^\*?(bool|boolean)$/.test(type); });
104
- if (!condition) {
118
+ if (!(0, exports.isConditionType)(returnType)) {
105
119
  return true;
106
120
  }
107
121
  var result = expression.ui_return_type;
108
122
  if (expression.return_type_first_arg || !result || result === 'any' || result === 'auto') {
109
123
  return true;
110
124
  }
111
- return /^\*?(bool|boolean)$/.test(result);
125
+ return !(0, exports.isTextOrDateType)(result);
112
126
  };
113
127
  exports.expressionFitsReturnType = expressionFitsReturnType;
114
128
  //# sourceMappingURL=expressions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"expressions.js","sourceRoot":"","sources":["../../src/helpers/expressions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"expressions.js","sourceRoot":"","sources":["../../src/helpers/expressions.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qoretechnologies/reqraft",
3
- "version": "0.10.58",
3
+ "version": "0.10.59-pr.130.ga79c828",
4
4
  "description": "ReQraft is a collection of React components and hooks that are used across Qore Technologies' products made using the ReQore component library from Qore.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -278,6 +278,7 @@ export const CompactRow = memo(
278
278
  (v) => v.removeSelectedOption
279
279
  );
280
280
  const getTypeForOption = useContextSelector(CompactRowContext, (v) => v.getTypeForOption);
281
+ const isRendererOnly = useContextSelector(CompactRowContext, (v) => v.isRendererOnly);
281
282
  const confirmAction = useContextSelector(CompactRowContext, (v) => v.confirmAction);
282
283
  const optionActions = useContextSelector(CompactRowContext, (v) => v.optionActions);
283
284
  const collapseOptionActions = useContextSelector(
@@ -1227,6 +1228,7 @@ export const CompactRow = memo(
1227
1228
  name: optionName,
1228
1229
  allOptions: availableOptions,
1229
1230
  getType: getTypeForOption,
1231
+ isRendererOnly,
1230
1232
  })
1231
1233
  // The empty required field's value slot already reads "Required — not
1232
1234
  // set", so the plain required message would duplicate it.
@@ -9213,3 +9213,71 @@ export const CompactReadOnlyDataProvider: Story = {
9213
9213
  expect(row('name').querySelector('.options-readfirst-structured')).toBeNull();
9214
9214
  },
9215
9215
  };
9216
+
9217
+ /** The AI endpoint's Tools field as the Qorus IDE hands it to the form: a
9218
+ * `tool-catalog` editor storing tool SELECTORS, with the tool catalogue's
9219
+ * SOURCES (fetched by its `get_message`) hung on `allowed_values`. */
9220
+ const AiEndpointToolsSchema = {
9221
+ tools: {
9222
+ type: 'list',
9223
+ display_name: 'Tools',
9224
+ short_desc: 'Which tools the AI can call during chat',
9225
+ ui_type: 'tool-catalog',
9226
+ element_type: 'string',
9227
+ default_value: { type: 'tool-catalog', value: ['*'] },
9228
+ preselected: true,
9229
+ required: true,
9230
+ get_message: { action: 'creator-get-objects', object_type: 'tool-catalog' },
9231
+ return_message: {
9232
+ action: 'creator-return-objects',
9233
+ object_type: 'tool-catalog',
9234
+ return_value: 'objects',
9235
+ },
9236
+ allowed_values: [
9237
+ { display_name: 'Qorus system tools', value: { type: 'string', value: 'system' } },
9238
+ { display_name: 'salesforce-prod', value: { type: 'string', value: 'salesforce-prod' } },
9239
+ ],
9240
+ },
9241
+ channel: {
9242
+ type: 'string',
9243
+ display_name: 'Channel',
9244
+ required: true,
9245
+ allowed_values: [
9246
+ { display_name: 'Ops', value: { type: 'string', value: 'ops' } },
9247
+ { display_name: 'Dev', value: { type: 'string', value: 'dev' } },
9248
+ ],
9249
+ },
9250
+ };
9251
+
9252
+ export const ToolSelectorsAreNotJudgedAsChoices: Story = {
9253
+ parameters: {
9254
+ docs: {
9255
+ description: {
9256
+ story:
9257
+ "An AI endpoint's Tools field holds the server's default, `*` (all tools), and needs no attention: its tool-catalog editor stores selectors, and the catalogue sources listed beside it are not choices the value must be one of. The Channel field below has fixed choices and a value that is none of them, so it alone needs attention.",
9258
+ },
9259
+ },
9260
+ },
9261
+ args: {
9262
+ compact: true,
9263
+ minColumnWidth: '360px',
9264
+ options: AiEndpointToolsSchema as IOptionsSchema,
9265
+ value: {
9266
+ tools: { type: 'tool-catalog', value: ['*'] },
9267
+ channel: { type: 'string', value: 'gone' },
9268
+ },
9269
+ },
9270
+ play: async ({ args }) => {
9271
+ await _testsWaitForText('Tools');
9272
+ // The closed-choice field still flags its value...
9273
+ await _testsWaitForText('"gone" is not one of the choices');
9274
+ // ...and the selector is never reported as a value that is not a choice.
9275
+ await _testsWaitForTextToNotExist('"*" is not one of the choices');
9276
+ await waitFor(() => {
9277
+ const calls = (args.onValidityChange as ReturnType<typeof fn>).mock.calls;
9278
+ expect(calls.length).toBeGreaterThan(0);
9279
+ const [, data] = calls[calls.length - 1] as [boolean, IFormValidityData];
9280
+ expect(data.invalidFields.map((field) => field.fieldName)).toEqual(['channel']);
9281
+ });
9282
+ },
9283
+ };
@@ -2326,6 +2326,9 @@ const FormEngineImpl = ({
2326
2326
  // The expression flag lives on the field value, not the schema — so an
2327
2327
  // expression value is validated as an expression, not the base type.
2328
2328
  isFunction: (availableOptions?.[optionName] as { is_expression?: boolean })?.is_expression,
2329
+ // A field drawn by a bespoke editor (the consumer's own included) is
2330
+ // not judged against `allowed_values` as fixed choices.
2331
+ hasOwnEditor: isRendererOnly(options?.[optionName]?.ui_type as TQorusType),
2329
2332
  } as any);
2330
2333
  },
2331
2334
  [
@@ -2333,6 +2336,7 @@ const FormEngineImpl = ({
2333
2336
  JSON.stringify(availableOptions),
2334
2337
  JSON.stringify(localValue.fields),
2335
2338
  dependencyLockedNames,
2339
+ isRendererOnly,
2336
2340
  ]
2337
2341
  );
2338
2342
 
@@ -2377,6 +2381,7 @@ const FormEngineImpl = ({
2377
2381
  ...options?.[optionName],
2378
2382
  // The expression flag lives on the field value, not the schema.
2379
2383
  isFunction: (option as { is_expression?: boolean }).is_expression,
2384
+ hasOwnEditor: isRendererOnly(options?.[optionName]?.ui_type as TQorusType),
2380
2385
  } as any);
2381
2386
  }
2382
2387
 
@@ -3154,6 +3159,7 @@ const FormEngineImpl = ({
3154
3159
  name={optionName}
3155
3160
  option={{ type: resolvedType, ...other }}
3156
3161
  getType={getTypeForOption}
3162
+ isRendererOnly={isRendererOnly}
3157
3163
  untouched={!touchedOptionsRef.current.has(optionName)}
3158
3164
  />
3159
3165
  {operators && size(operators) && size(other.op) ?
@@ -3266,6 +3272,7 @@ const FormEngineImpl = ({
3266
3272
  handleOptionLabelClick,
3267
3273
  removeSelectedOption,
3268
3274
  getTypeForOption,
3275
+ isRendererOnly,
3269
3276
  isOptionValid,
3270
3277
  confirmAction,
3271
3278
  optionActions,
@@ -3313,6 +3320,7 @@ const FormEngineImpl = ({
3313
3320
  handleOptionLabelClick,
3314
3321
  removeSelectedOption,
3315
3322
  getTypeForOption,
3323
+ isRendererOnly,
3316
3324
  isOptionValid,
3317
3325
  confirmAction,
3318
3326
  optionActions,
@@ -326,6 +326,14 @@ export interface IOptionFieldMessagesProps {
326
326
  allOptions?: TQorusForm;
327
327
  name: string;
328
328
  getType: (type: string) => string;
329
+ /**
330
+ * Whether a `ui_type` names a bespoke editor, the consumer's own included.
331
+ *
332
+ * Such a field's `allowed_values` are not fixed choices (the editor decides
333
+ * what a value means), so a value is not judged against them. Without it only
334
+ * reqraft's built-in editors are known.
335
+ */
336
+ isRendererOnly?: (type?: any) => boolean;
329
337
  /**
330
338
  * The reader has not edited this field in this session.
331
339
  *
@@ -354,6 +362,7 @@ export const getOptionFieldMessages = ({
354
362
  name,
355
363
  allOptions,
356
364
  getType,
365
+ isRendererOnly,
357
366
  untouched,
358
367
  }: IOptionFieldMessagesProps): IReqoreTagProps[] => {
359
368
  const optionSchema = schema[name];
@@ -386,6 +395,7 @@ export const getOptionFieldMessages = ({
386
395
  // config never reached the validator.
387
396
  ...(optionSchema as object),
388
397
  optionSchema: schema,
398
+ hasOwnEditor: !!isRendererOnly?.(optionSchema?.ui_type),
389
399
  }
390
400
  );
391
401
  if (!validationData.isValid) {
@@ -442,11 +452,28 @@ export const OptionFieldMessages = ({
442
452
  name,
443
453
  allOptions,
444
454
  getType,
455
+ isRendererOnly,
445
456
  untouched,
446
457
  }: IOptionFieldMessagesProps) => {
447
458
  const messages: IReqoreTagProps[] = useMemo(
448
- () => getOptionFieldMessages({ schema, option, name, allOptions, getType, untouched }),
449
- [JSON.stringify(schema), JSON.stringify(option), JSON.stringify(allOptions), name, untouched]
459
+ () =>
460
+ getOptionFieldMessages({
461
+ schema,
462
+ option,
463
+ name,
464
+ allOptions,
465
+ getType,
466
+ isRendererOnly,
467
+ untouched,
468
+ }),
469
+ [
470
+ JSON.stringify(schema),
471
+ JSON.stringify(option),
472
+ JSON.stringify(allOptions),
473
+ name,
474
+ isRendererOnly,
475
+ untouched,
476
+ ]
450
477
  );
451
478
 
452
479
  if (!size(messages)) {
@@ -98,6 +98,8 @@ export interface ICompactRowContext {
98
98
  handleOptionLabelClick: (optionName: string) => void;
99
99
  removeSelectedOption: (optionName: string) => void;
100
100
  getTypeForOption: (type: string) => string;
101
+ /** Whether a `ui_type` names a bespoke editor, reqraft's or the consumer's own. */
102
+ isRendererOnly?: (type?: any) => boolean;
101
103
  isOptionValid: (optionName: string, type: any, optionValue: any) => boolean;
102
104
  confirmAction: ReturnType<typeof useReqoreProperty<'confirmAction'>>;
103
105
 
@@ -28,6 +28,8 @@ import {
28
28
  areQorusTypesCompatible,
29
29
  expressionFitsReturnType,
30
30
  getArgumentType,
31
+ isConditionType,
32
+ isTextOrDateType,
31
33
  } from '../../../../helpers/expressions';
32
34
  import { addMissingExpressionArgs } from '../argumentPresence';
33
35
  import { findTemplate } from '../../../../helpers/templates';
@@ -595,13 +597,18 @@ export const Expression = ({
595
597
  expressionReturnType === 'any' ||
596
598
  areQorusTypesCompatible(returnType, expressionReturnType) ||
597
599
  returnType === expressionReturnType ||
598
- !returnType);
600
+ !returnType) ||
601
+ // a condition is decided by any result but text, a date or a time
602
+ (isConditionType(returnType as string | string[] | undefined) &&
603
+ !!expressionReturnType &&
604
+ !isTextOrDateType(expressionReturnType));
599
605
 
600
606
  const defaultItems = useMemo(
601
607
  () =>
602
608
  expressions.value
603
- // a condition (a field or an operand that must be true or false) offers the operations that
604
- // give true or false; a group's members are its own business, and the operation already chosen
609
+ // a condition (a field or an operand that must be true or false) offers the operations whose
610
+ // result decides it - any but text, a date or a time, which are compared explicitly; a group's members
611
+ // are its own business, and the operation already chosen
605
612
  // stays, so a stored mismatch can be seen and fixed
606
613
  .filter(
607
614
  (exp) =>
@@ -1283,7 +1290,14 @@ export const Expression = ({
1283
1290
  intent='danger'
1284
1291
  size='tiny'
1285
1292
  wrap
1286
- label={`This expression returns ${types.getTypeDisplayName(expressionReturnType) || 'nothing'} but the expected return type is ${expectedReturnTypeText}. Please select an expression that returns ${expectedReturnTypeText} or wrap this expression inside another one that does.`}
1293
+ label={
1294
+ isConditionType(returnType as string | string[] | undefined) &&
1295
+ !!expressionReturnType &&
1296
+ isTextOrDateType(expressionReturnType)
1297
+ ? // a condition that gives text, a date or a time: say how to make it one
1298
+ `A condition can't give ${types.getTypeDisplayName(expressionReturnType)}: compare it explicitly, for example @status == "open" or @note != "".`
1299
+ : `This expression returns ${types.getTypeDisplayName(expressionReturnType) || 'nothing'} but the expected return type is ${expectedReturnTypeText}. Please select an expression that returns ${expectedReturnTypeText} or wrap this expression inside another one that does.`
1300
+ }
1287
1301
  />
1288
1302
  </>
1289
1303
  )}
@@ -496,43 +496,17 @@ export function useLspAutocomplete(
496
496
  return () => document.removeEventListener('keydown', handleKeyDown, true);
497
497
  }, [isOpen, filteredItems, close, selectItem]);
498
498
 
499
- const onSlateChangeImpl = useCallback(
500
- (
501
- editor: BaseEditor & ReactEditor & HistoryEditor,
502
- nodes: ISlateElement[]
503
- ) => {
504
- editorRef.current = editor;
505
- if (!editor.selection || !isReady) {
506
- // Don't tear down replace mode just because the click that
507
- // opened it momentarily left the selection null — clicking a
508
- // void inline can do that.
509
- if (replacingChipPathRef.current) return;
510
- close();
511
- return;
512
- }
513
- // `onChange` fires for content edits AND selection-only changes
514
- // (cursor moves, click-to-focus). Only OPEN on content edits — else
515
- // clicking after a trigger char (e.g. the space in `/list services `)
516
- // pops the dropdown without the user typing. An all-`set_selection`
517
- // op batch is selection-only.
518
- const isContentChange = editor.operations.some(
519
- (op) => op.type !== 'set_selection'
520
- );
521
- if (!isContentChange) {
522
- // Replace mode anchors on a chip; the selection echo from the
523
- // opening click must not close it (outside-click handler does).
524
- if (isOpen && !replacingChipPathRef.current) {
525
- close();
526
- }
527
- return;
528
- }
529
- // A content edit in replace mode means the user typed instead of
530
- // picking a replacement — fall back to normal flow.
531
- if (replacingChipPathRef.current) {
499
+ /**
500
+ * Opens, narrows or closes the completions for what the editor holds at its caret, after a content
501
+ * edit: a trigger character just typed, typing inside a sigil token, narrowing an open list, or
502
+ * autosuggest inside a bare identifier.
503
+ */
504
+ const completeAtCaret = useCallback(
505
+ (editor: BaseEditor & ReactEditor & HistoryEditor, nodes: ISlateElement[]) => {
506
+ if (!editor.selection) {
532
507
  close();
533
508
  return;
534
509
  }
535
-
536
510
  const { anchor } = editor.selection;
537
511
  const plainText = converter.fromSlateNodes(nodes);
538
512
  const cursorOffset = converter.selectionToOffset(
@@ -604,17 +578,70 @@ export function useLspAutocomplete(
604
578
 
605
579
  close();
606
580
  },
607
- [
608
- isReady,
609
- isOpen,
610
- close,
611
- findTokenStart,
612
- positionTrigger,
613
- requestCompletions,
614
- triggerCharacters,
615
- converter,
616
- ]
581
+ [isOpen, close, findTokenStart, positionTrigger, requestCompletions, triggerCharacters, converter]
582
+ );
583
+
584
+ /* An edit made before the session is ready - typing `$` or `@` while the connection is still being
585
+ made, or its context bound - would otherwise be dropped: the list is closed and nothing asks again,
586
+ so the trigger the author typed never opens anything. The editor is kept, and once the session is
587
+ ready the completions are asked for what it holds then, as for a chip click (see
588
+ `pendingChipRequestRef`). */
589
+ const pendingTypingRef = useRef<(BaseEditor & ReactEditor & HistoryEditor) | null>(null);
590
+
591
+ const onSlateChangeImpl = useCallback(
592
+ (
593
+ editor: BaseEditor & ReactEditor & HistoryEditor,
594
+ nodes: ISlateElement[]
595
+ ) => {
596
+ editorRef.current = editor;
597
+ if (!editor.selection || !isReady) {
598
+ // Don't tear down replace mode just because the click that
599
+ // opened it momentarily left the selection null — clicking a
600
+ // void inline can do that.
601
+ if (replacingChipPathRef.current) return;
602
+ // a content edit before the session is ready is answered once it is (see `pendingTypingRef`)
603
+ if (editor.selection && editor.operations.some((op) => op.type !== 'set_selection')) {
604
+ pendingTypingRef.current = editor;
605
+ }
606
+ close();
607
+ return;
608
+ }
609
+ pendingTypingRef.current = null;
610
+ // `onChange` fires for content edits AND selection-only changes
611
+ // (cursor moves, click-to-focus). Only OPEN on content edits — else
612
+ // clicking after a trigger char (e.g. the space in `/list services `)
613
+ // pops the dropdown without the user typing. An all-`set_selection`
614
+ // op batch is selection-only.
615
+ const isContentChange = editor.operations.some(
616
+ (op) => op.type !== 'set_selection'
617
+ );
618
+ if (!isContentChange) {
619
+ // Replace mode anchors on a chip; the selection echo from the
620
+ // opening click must not close it (outside-click handler does).
621
+ if (isOpen && !replacingChipPathRef.current) {
622
+ close();
623
+ }
624
+ return;
625
+ }
626
+ // A content edit in replace mode means the user typed instead of
627
+ // picking a replacement — fall back to normal flow.
628
+ if (replacingChipPathRef.current) {
629
+ close();
630
+ return;
631
+ }
632
+
633
+ completeAtCaret(editor, nodes);
634
+ },
635
+ [isReady, isOpen, close, completeAtCaret]
617
636
  );
637
+
638
+ // Answer an edit made before the session was ready, for what the editor holds now.
639
+ useEffect(() => {
640
+ const editor = pendingTypingRef.current;
641
+ if (!isReady || !editor) return;
642
+ pendingTypingRef.current = null;
643
+ completeAtCaret(editor, editor.children as ISlateElement[]);
644
+ }, [isReady, completeAtCaret]);
618
645
  onSlateChangeImplRef.current = onSlateChangeImpl;
619
646
 
620
647
  const onItemSelect = useCallback(
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { TQorusFormFieldSchema } from '@qoretechnologies/ts-toolkit';
11
11
  import isEqual from 'lodash/isEqual';
12
+ import { isRendererOnlyUiType } from '../components/form/engine/rendererTypes';
12
13
  import { richtextHasTag, richtextToString } from './common';
13
14
  import { getListElementValue } from './options';
14
15
  import { isValueTemplate } from './templates';
@@ -109,8 +110,32 @@ export interface IChoiceFieldSchema {
109
110
  /** Set when the value is an expression, which is resolved at run time. */
110
111
  isFunction?: boolean;
111
112
  is_expression?: boolean;
113
+ /** The editor the field is drawn with; see {@link hasOwnEditor}. */
114
+ ui_type?: string | readonly string[];
115
+ /**
116
+ * Set by a form that knows the field is drawn by a bespoke editor of the
117
+ * consumer's own (one it declared in `rendererOnlyUiTypes`), which reqraft's
118
+ * built-in list cannot name.
119
+ */
120
+ hasOwnEditor?: boolean;
112
121
  }
113
122
 
123
+ /**
124
+ * Does a bespoke editor, rather than reqraft's choice picker, draw this field?
125
+ *
126
+ * Then the editor alone decides what a value means, and `allowed_values` is not
127
+ * a closed set of values the field may hold. The AI endpoint's Tools field is
128
+ * the case that proved it: its `tool-catalog` editor stores SELECTORS -- `*` for
129
+ * every tool, `system:*`, a connection name, `connection/tool` -- and the host
130
+ * hangs the tool catalogue's SOURCES on `allowed_values` for the editor to
131
+ * browse. Judging the selectors against the sources flagged the server's own
132
+ * default, `["*"]`, as "not one of the choices", so no chat endpoint could be
133
+ * published with it. The server says the same: a custom `ui_type` "defines its
134
+ * own value shape".
135
+ */
136
+ const hasOwnEditor = (field: IChoiceFieldSchema): boolean =>
137
+ !!field.hasOwnEditor || isRendererOnlyUiType(field.ui_type);
138
+
114
139
  /**
115
140
  * The parts of a stored value that are not among the field's fixed choices.
116
141
  *
@@ -121,14 +146,22 @@ export interface IChoiceFieldSchema {
121
146
  * all of which are resolved when the interface runs;
122
147
  * - `allowed_values: []` — the server has no choices to offer right now, which
123
148
  * is "unknown", not "nothing is allowed";
124
- * - creatable choices, where any value may be added.
149
+ * - creatable choices, where any value may be added;
150
+ * - a field drawn by a bespoke editor (a renderer-only `ui_type` such as
151
+ * `tool-catalog`), whose values are whatever that editor says they are.
125
152
  *
126
153
  * A single-valued field is judged as a whole. A multi-select field, and a list
127
154
  * whose elements are constrained by `element_allowed_values`, are judged per
128
155
  * element, so the answer names exactly the elements that are not choices.
129
156
  */
130
157
  export const getUnlistedChoices = (value: unknown, field?: IChoiceFieldSchema): unknown[] => {
131
- if (!field || isEmptyChoiceValue(value) || field.isFunction || field.is_expression) {
158
+ if (
159
+ !field ||
160
+ isEmptyChoiceValue(value) ||
161
+ field.isFunction ||
162
+ field.is_expression ||
163
+ hasOwnEditor(field)
164
+ ) {
132
165
  return [];
133
166
  }
134
167
 
@@ -101,9 +101,24 @@ export const areQorusTypesCompatible = (
101
101
  return false;
102
102
  };
103
103
 
104
+ /**
105
+ * Whether a type is text, a date or a time: what a condition cannot be. A condition is evaluated by its
106
+ * truthiness, so a number (0 or not), a list or hash (empty or not), binary data and an untyped result
107
+ * all decide it; text, dates and times do not say plainly what is meant, and are compared explicitly
108
+ * instead (`@status == "open"`, `@note != ""`). The Qorus server applies the same rule.
109
+ */
110
+ export const isTextOrDateType = (type: string): boolean =>
111
+ /^(string|richtext|date|time)$/.test(type.replace(/^\*/, '').replace(/^soft/, ''));
112
+
113
+ /** Whether a place takes a condition: its result must be true or false (`bool`, or a list of only that). */
114
+ export const isConditionType = (returnType: string | string[] | undefined): boolean => {
115
+ const types = isArray(returnType) ? returnType : returnType ? [returnType] : [];
116
+ return types.length > 0 && types.every((type) => /^\*?(bool|boolean)$/.test(type));
117
+ };
118
+
104
119
  /**
105
120
  * Whether an operation is offered where its result must be of `returnType`: a condition (`bool`) is
106
- * built from the operations that give true or false, not from every operation the server has.
121
+ * built from the operations whose result decides it, which is any result but text, a date or a time.
107
122
  *
108
123
  * Only a condition narrows the list. Any other type keeps every operation: a value of one type is often
109
124
  * made from an operation that gives another (text from rich text, a number from an int), and the
@@ -114,14 +129,12 @@ export const expressionFitsReturnType = (
114
129
  returnType: string | string[] | undefined,
115
130
  expression: { ui_return_type?: string; return_type_first_arg?: boolean }
116
131
  ): boolean => {
117
- const types = isArray(returnType) ? returnType : returnType ? [returnType] : [];
118
- const condition = types.length > 0 && types.every((type) => /^\*?(bool|boolean)$/.test(type));
119
- if (!condition) {
132
+ if (!isConditionType(returnType)) {
120
133
  return true;
121
134
  }
122
135
  const result = expression.ui_return_type;
123
136
  if (expression.return_type_first_arg || !result || result === 'any' || result === 'auto') {
124
137
  return true;
125
138
  }
126
- return /^\*?(bool|boolean)$/.test(result);
139
+ return !isTextOrDateType(result);
127
140
  };