@fieldia/core 0.28.0 → 0.29.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.
Files changed (233) hide show
  1. package/README.md +125 -1
  2. package/package.json +1 -1
  3. package/schema/page.schema.json +263 -6
  4. package/src/index.d.mts +8 -2
  5. package/src/index.d.ts +8 -2
  6. package/src/index.js +29 -2
  7. package/src/index.js.map +1 -1
  8. package/src/index.mjs +11 -2
  9. package/src/index.mjs.map +1 -1
  10. package/src/lib/format/approval-checks.d.mts +8 -0
  11. package/src/lib/format/approval-checks.d.ts +8 -0
  12. package/src/lib/format/approval-checks.js +29 -0
  13. package/src/lib/format/approval-checks.js.map +1 -0
  14. package/src/lib/format/approval-checks.mjs +26 -0
  15. package/src/lib/format/approval-checks.mjs.map +1 -0
  16. package/src/lib/format/approval.d.mts +20 -0
  17. package/src/lib/format/approval.d.ts +20 -0
  18. package/src/lib/format/approval.js +25 -0
  19. package/src/lib/format/approval.js.map +1 -0
  20. package/src/lib/format/approval.mjs +22 -0
  21. package/src/lib/format/approval.mjs.map +1 -0
  22. package/src/lib/format/assist-checks.d.mts +14 -0
  23. package/src/lib/format/assist-checks.d.ts +14 -0
  24. package/src/lib/format/assist-checks.js +68 -0
  25. package/src/lib/format/assist-checks.js.map +1 -0
  26. package/src/lib/format/assist-checks.mjs +64 -0
  27. package/src/lib/format/assist-checks.mjs.map +1 -0
  28. package/src/lib/format/assist.d.mts +41 -0
  29. package/src/lib/format/assist.d.ts +41 -0
  30. package/src/lib/format/assist.js +29 -0
  31. package/src/lib/format/assist.js.map +1 -0
  32. package/src/lib/format/assist.mjs +26 -0
  33. package/src/lib/format/assist.mjs.map +1 -0
  34. package/src/lib/format/editable-checks.d.mts +8 -0
  35. package/src/lib/format/editable-checks.d.ts +8 -0
  36. package/src/lib/format/editable-checks.js +38 -0
  37. package/src/lib/format/editable-checks.js.map +1 -0
  38. package/src/lib/format/editable-checks.mjs +35 -0
  39. package/src/lib/format/editable-checks.mjs.map +1 -0
  40. package/src/lib/format/field.d.mts +86 -0
  41. package/src/lib/format/field.d.ts +86 -0
  42. package/src/lib/format/field.js +56 -10
  43. package/src/lib/format/field.js.map +1 -1
  44. package/src/lib/format/field.mjs +55 -9
  45. package/src/lib/format/field.mjs.map +1 -1
  46. package/src/lib/format/languages-checks.d.mts +8 -0
  47. package/src/lib/format/languages-checks.d.ts +8 -0
  48. package/src/lib/format/languages-checks.js +40 -0
  49. package/src/lib/format/languages-checks.js.map +1 -0
  50. package/src/lib/format/languages-checks.mjs +37 -0
  51. package/src/lib/format/languages-checks.mjs.map +1 -0
  52. package/src/lib/format/later-checks.d.mts +4 -3
  53. package/src/lib/format/later-checks.d.ts +4 -3
  54. package/src/lib/format/later-checks.js +4 -3
  55. package/src/lib/format/later-checks.js.map +1 -1
  56. package/src/lib/format/later-checks.mjs +4 -3
  57. package/src/lib/format/later-checks.mjs.map +1 -1
  58. package/src/lib/format/layout.d.mts +100 -26
  59. package/src/lib/format/layout.d.ts +100 -26
  60. package/src/lib/format/layout.js +19 -3
  61. package/src/lib/format/layout.js.map +1 -1
  62. package/src/lib/format/layout.mjs +17 -1
  63. package/src/lib/format/layout.mjs.map +1 -1
  64. package/src/lib/format/layout.types.d.mts +2 -1
  65. package/src/lib/format/layout.types.d.ts +2 -1
  66. package/src/lib/format/list-editable.d.mts +20 -0
  67. package/src/lib/format/list-editable.d.ts +20 -0
  68. package/src/lib/format/list-editable.js +33 -0
  69. package/src/lib/format/list-editable.js.map +1 -0
  70. package/src/lib/format/list-editable.mjs +28 -0
  71. package/src/lib/format/list-editable.mjs.map +1 -0
  72. package/src/lib/format/page.d.mts +84 -9
  73. package/src/lib/format/page.d.ts +84 -9
  74. package/src/lib/format/page.js +4 -0
  75. package/src/lib/format/page.js.map +1 -1
  76. package/src/lib/format/page.mjs +4 -0
  77. package/src/lib/format/page.mjs.map +1 -1
  78. package/src/lib/format/photos-options.d.mts +29 -0
  79. package/src/lib/format/photos-options.d.ts +29 -0
  80. package/src/lib/format/photos-options.js +49 -0
  81. package/src/lib/format/photos-options.js.map +1 -0
  82. package/src/lib/format/photos-options.mjs +45 -0
  83. package/src/lib/format/photos-options.mjs.map +1 -0
  84. package/src/lib/format/references.js +31 -1
  85. package/src/lib/format/references.js.map +1 -1
  86. package/src/lib/format/references.mjs +31 -1
  87. package/src/lib/format/references.mjs.map +1 -1
  88. package/src/lib/format/scan-checks.d.mts +12 -0
  89. package/src/lib/format/scan-checks.d.ts +12 -0
  90. package/src/lib/format/scan-checks.js +89 -0
  91. package/src/lib/format/scan-checks.js.map +1 -0
  92. package/src/lib/format/scan-checks.mjs +86 -0
  93. package/src/lib/format/scan-checks.mjs.map +1 -0
  94. package/src/lib/format/scan-options.d.mts +84 -0
  95. package/src/lib/format/scan-options.d.ts +84 -0
  96. package/src/lib/format/scan-options.js +58 -0
  97. package/src/lib/format/scan-options.js.map +1 -0
  98. package/src/lib/format/scan-options.mjs +54 -0
  99. package/src/lib/format/scan-options.mjs.map +1 -0
  100. package/src/lib/format/upgrade.d.mts +4 -2
  101. package/src/lib/format/upgrade.d.ts +4 -2
  102. package/src/lib/format/upgrade.js +4 -2
  103. package/src/lib/format/upgrade.js.map +1 -1
  104. package/src/lib/format/upgrade.mjs +4 -2
  105. package/src/lib/format/upgrade.mjs.map +1 -1
  106. package/src/lib/format/version.d.mts +2 -2
  107. package/src/lib/format/version.d.ts +2 -2
  108. package/src/lib/format/version.js +2 -2
  109. package/src/lib/format/version.js.map +1 -1
  110. package/src/lib/format/version.mjs +2 -2
  111. package/src/lib/format/version.mjs.map +1 -1
  112. package/src/lib/record/approvals-part.d.mts +6 -0
  113. package/src/lib/record/approvals-part.d.ts +6 -0
  114. package/src/lib/record/approvals-part.js +14 -0
  115. package/src/lib/record/approvals-part.js.map +1 -0
  116. package/src/lib/record/approvals-part.mjs +7 -0
  117. package/src/lib/record/approvals-part.mjs.map +1 -0
  118. package/src/lib/record/approving.d.mts +45 -0
  119. package/src/lib/record/approving.d.ts +45 -0
  120. package/src/lib/record/approving.js +10 -0
  121. package/src/lib/record/approving.js.map +1 -0
  122. package/src/lib/record/approving.mjs +7 -0
  123. package/src/lib/record/approving.mjs.map +1 -0
  124. package/src/lib/record/check.js +4 -0
  125. package/src/lib/record/check.js.map +1 -1
  126. package/src/lib/record/check.mjs +4 -0
  127. package/src/lib/record/check.mjs.map +1 -1
  128. package/src/lib/record/data-source.d.mts +270 -0
  129. package/src/lib/record/data-source.d.ts +270 -0
  130. package/src/lib/record/data-source.js.map +1 -1
  131. package/src/lib/record/data-source.mjs.map +1 -1
  132. package/src/lib/record/files-part.d.mts +7 -0
  133. package/src/lib/record/files-part.d.ts +7 -0
  134. package/src/lib/record/files-part.js +167 -0
  135. package/src/lib/record/files-part.js.map +1 -0
  136. package/src/lib/record/files-part.mjs +163 -0
  137. package/src/lib/record/files-part.mjs.map +1 -0
  138. package/src/lib/record/filter.d.mts +5 -0
  139. package/src/lib/record/filter.d.ts +5 -0
  140. package/src/lib/record/filter.js +1 -0
  141. package/src/lib/record/filter.js.map +1 -1
  142. package/src/lib/record/filter.mjs +1 -1
  143. package/src/lib/record/filter.mjs.map +1 -1
  144. package/src/lib/record/form-approvals.d.mts +47 -0
  145. package/src/lib/record/form-approvals.d.ts +47 -0
  146. package/src/lib/record/form-approvals.js +186 -0
  147. package/src/lib/record/form-approvals.js.map +1 -0
  148. package/src/lib/record/form-approvals.mjs +180 -0
  149. package/src/lib/record/form-approvals.mjs.map +1 -0
  150. package/src/lib/record/form.d.mts +32 -1
  151. package/src/lib/record/form.d.ts +32 -1
  152. package/src/lib/record/form.js +49 -5
  153. package/src/lib/record/form.js.map +1 -1
  154. package/src/lib/record/form.mjs +49 -5
  155. package/src/lib/record/form.mjs.map +1 -1
  156. package/src/lib/record/languages-part.d.mts +32 -0
  157. package/src/lib/record/languages-part.d.ts +32 -0
  158. package/src/lib/record/languages-part.js +104 -0
  159. package/src/lib/record/languages-part.js.map +1 -0
  160. package/src/lib/record/languages-part.mjs +96 -0
  161. package/src/lib/record/languages-part.mjs.map +1 -0
  162. package/src/lib/record/languages.d.mts +26 -0
  163. package/src/lib/record/languages.d.ts +26 -0
  164. package/src/lib/record/languages.js +20 -0
  165. package/src/lib/record/languages.js.map +1 -0
  166. package/src/lib/record/languages.mjs +15 -0
  167. package/src/lib/record/languages.mjs.map +1 -0
  168. package/src/lib/record/locales/ar.js +5 -0
  169. package/src/lib/record/locales/ar.js.map +1 -1
  170. package/src/lib/record/locales/ar.mjs +5 -0
  171. package/src/lib/record/locales/ar.mjs.map +1 -1
  172. package/src/lib/record/locales/de.js +5 -0
  173. package/src/lib/record/locales/de.js.map +1 -1
  174. package/src/lib/record/locales/de.mjs +5 -0
  175. package/src/lib/record/locales/de.mjs.map +1 -1
  176. package/src/lib/record/locales/fr.js +5 -0
  177. package/src/lib/record/locales/fr.js.map +1 -1
  178. package/src/lib/record/locales/fr.mjs +5 -0
  179. package/src/lib/record/locales/fr.mjs.map +1 -1
  180. package/src/lib/record/memory-approvals.d.mts +47 -0
  181. package/src/lib/record/memory-approvals.d.ts +47 -0
  182. package/src/lib/record/memory-approvals.js +78 -0
  183. package/src/lib/record/memory-approvals.js.map +1 -0
  184. package/src/lib/record/memory-approvals.mjs +75 -0
  185. package/src/lib/record/memory-approvals.mjs.map +1 -0
  186. package/src/lib/record/memory-assist.d.mts +53 -0
  187. package/src/lib/record/memory-assist.d.ts +53 -0
  188. package/src/lib/record/memory-assist.js +519 -0
  189. package/src/lib/record/memory-assist.js.map +1 -0
  190. package/src/lib/record/memory-assist.mjs +513 -0
  191. package/src/lib/record/memory-assist.mjs.map +1 -0
  192. package/src/lib/record/memory-clusters.d.mts +18 -0
  193. package/src/lib/record/memory-clusters.d.ts +18 -0
  194. package/src/lib/record/memory-clusters.js +83 -0
  195. package/src/lib/record/memory-clusters.js.map +1 -0
  196. package/src/lib/record/memory-clusters.mjs +80 -0
  197. package/src/lib/record/memory-clusters.mjs.map +1 -0
  198. package/src/lib/record/memory-data-source.d.mts +43 -3
  199. package/src/lib/record/memory-data-source.d.ts +43 -3
  200. package/src/lib/record/memory-data-source.js +105 -19
  201. package/src/lib/record/memory-data-source.js.map +1 -1
  202. package/src/lib/record/memory-data-source.mjs +105 -19
  203. package/src/lib/record/memory-data-source.mjs.map +1 -1
  204. package/src/lib/record/memory-later.d.mts +1 -1
  205. package/src/lib/record/memory-later.d.ts +1 -1
  206. package/src/lib/record/memory-later.js +4 -1
  207. package/src/lib/record/memory-later.js.map +1 -1
  208. package/src/lib/record/memory-later.mjs +4 -1
  209. package/src/lib/record/memory-later.mjs.map +1 -1
  210. package/src/lib/record/messages.d.mts +14 -0
  211. package/src/lib/record/messages.d.ts +14 -0
  212. package/src/lib/record/messages.js +5 -0
  213. package/src/lib/record/messages.js.map +1 -1
  214. package/src/lib/record/messages.mjs +5 -0
  215. package/src/lib/record/messages.mjs.map +1 -1
  216. package/src/lib/record/uploads.d.mts +35 -9
  217. package/src/lib/record/uploads.d.ts +35 -9
  218. package/src/lib/record/uploads.js +49 -169
  219. package/src/lib/record/uploads.js.map +1 -1
  220. package/src/lib/record/uploads.mjs +42 -164
  221. package/src/lib/record/uploads.mjs.map +1 -1
  222. package/src/lib/record/values.d.mts +23 -0
  223. package/src/lib/record/values.d.ts +23 -0
  224. package/src/lib/record/values.js +4 -0
  225. package/src/lib/record/values.js.map +1 -1
  226. package/src/lib/record/values.mjs +4 -0
  227. package/src/lib/record/values.mjs.map +1 -1
  228. package/src/runtime.d.mts +12 -5
  229. package/src/runtime.d.ts +12 -5
  230. package/src/runtime.js +18 -3
  231. package/src/runtime.js.map +1 -1
  232. package/src/runtime.mjs +7 -1
  233. package/src/runtime.mjs.map +1 -1
@@ -21,6 +21,8 @@ import { recordSentence } from './record-words.mjs';
21
21
  import { askKey, cellOf, listedColumns, notOffered } from './line-choices.mjs';
22
22
  import { loadedRecordOf, saveProblemOf, } from './data-source.mjs';
23
23
  import { formLimitsPart, hasLimits } from './taking.mjs';
24
+ import { formLanguagesPart, hasLanguages } from './languages.mjs';
25
+ import { formApprovalsPart } from './approving.mjs';
24
26
  import { draftRead, resumeStep } from './drafts.mjs';
25
27
  import { waitingOn, workedOutFrom } from './waits.mjs';
26
28
  import { createUploads, fileBytesIn, uploadGoing, withoutFileBytes } from './uploads.mjs';
@@ -361,6 +363,7 @@ function innerForm(options, within, place, outer) {
361
363
  // Worked out once the form has its state, as the limits part comes (`limitsCode`).
362
364
  limits: null,
363
365
  resume: null,
366
+ approval: null,
364
367
  };
365
368
  started = true;
366
369
  /**
@@ -957,6 +960,10 @@ function innerForm(options, within, place, outer) {
957
960
  }
958
961
  if (hasOptionRules(page))
959
962
  void track(optionsCode());
963
+ // A text in several languages is checked by the languages part: asked for at once, a save waiting for it.
964
+ const languagesCode = () => formLanguagesPart.load().then(() => undefined, () => undefined);
965
+ if (hasLanguages(page))
966
+ void track(languagesCode());
960
967
  /** A table line's choice's options as they are offered on that line: its list's once loaded, or its own, by their conditions once they came. */
961
968
  function lineOffered(lines, line, column, record) {
962
969
  if (optionKit)
@@ -1124,6 +1131,9 @@ function innerForm(options, within, place, outer) {
1124
1131
  const answer = answerOf(node);
1125
1132
  if (answer !== undefined && signedNow().has(answer))
1126
1133
  readonly = true;
1134
+ // A record waiting for approval: locked but for the fields the page leaves open (the approvals part).
1135
+ if (answer !== undefined && approvalKit?.locks(answer))
1136
+ readonly = true;
1127
1137
  return {
1128
1138
  invisible,
1129
1139
  readonly: readonly || (def !== undefined && lockedField(def)),
@@ -1762,6 +1772,15 @@ function innerForm(options, within, place, outer) {
1762
1772
  }
1763
1773
  /** A choice of a full option, picked by the person, refused under its field: the field keeps what it held. */
1764
1774
  const refusedFull = (name, value) => !!limitsKit?.refusedFull(name, value);
1775
+ // ---- a record's approvals (the approvals part: form-approvals.ts) ---------------------------------------------------
1776
+ /** The approvals made for this form from the approvals part, once it came: none before, nor on a page without them. */
1777
+ let approvalKit = null;
1778
+ /** Whether the form asks for approvals: a page with `approval` (a record's own, as its check says), its source telling them. */
1779
+ const keepsApprovals = !outer && !!page.approval && !!options.dataSource?.approvals;
1780
+ /** The approvals, made from their part once it came; null without them, or where it could not come. */
1781
+ const approvalsCode = () => keepsApprovals ? formApprovalsPart.load().then((part) => (approvalKit ??= part.formApprovals({ form, dataSource: options.dataSource, messages, set, reload: () => load(true) })), () => null) : Promise.resolve(null);
1782
+ /** Ask the record shown's approvals, once their part came; nothing on a page without them. */
1783
+ const askApprovals = () => approvalsCode().then((kit) => kit?.ask());
1765
1784
  function scheduleAutosave() {
1766
1785
  const autosave = options.autosave;
1767
1786
  // A record the person may not change is never saved unasked: it would only be refused.
@@ -1908,6 +1927,12 @@ function innerForm(options, within, place, outer) {
1908
1927
  if (asked !== generation)
1909
1928
  return elsewhere();
1910
1929
  }
1930
+ // A text in several languages, checked in each once its part has come.
1931
+ if (!formLanguagesPart.loaded && hasLanguages(page)) {
1932
+ await languagesCode();
1933
+ if (asked !== generation)
1934
+ return elsewhere();
1935
+ }
1911
1936
  // Values the app's functions are asked for are waited for — those that failed asked again — so what is saved is worked out.
1912
1937
  if (asking.size || Object.values(state.asked).some((found) => found.failed !== undefined)) {
1913
1938
  set({ status: 'saving', error: null, saveProblem: null });
@@ -2031,6 +2056,9 @@ function innerForm(options, within, place, outer) {
2031
2056
  scheduleDraft();
2032
2057
  scheduleAutosave();
2033
2058
  }
2059
+ // A save may have sent it for approval, or moved it on: its approvals asked again.
2060
+ if (keepsApprovals)
2061
+ void track(askApprovals());
2034
2062
  // The record as this save left it: a change made while it was on its way is no value of it.
2035
2063
  emit('save', { recordId: state.recordId, values: baseline });
2036
2064
  void track(moment('on.afterSave', page.on?.afterSave, chain, false));
@@ -2237,7 +2265,7 @@ function innerForm(options, within, place, outer) {
2237
2265
  throw new Error(`"${name}" cannot make records: its data source has no create, or it is not a link`);
2238
2266
  return track(source.create({ model, name: typed.trim(), ...(Object.keys(values).length ? { values } : {}) }));
2239
2267
  }
2240
- async function runSearch(def, name, ctx, query, limit, { model, ids, exact } = {}) {
2268
+ async function runSearch(def, name, ctx, query, limit, { model, ids, exact, field } = {}) {
2241
2269
  // Formatted text names people and records in place (@mention, #4127): searched by the model they are of.
2242
2270
  const byModel = def.type === 'reference' || def.type === 'json' || def.type === 'html';
2243
2271
  if (def.type !== 'many2one' && def.type !== 'many2many' && !byModel) {
@@ -2250,9 +2278,11 @@ function innerForm(options, within, place, outer) {
2250
2278
  return [];
2251
2279
  // Every valueFrom, in groups too, becomes the value it names before the search goes out.
2252
2280
  const own = byModel || !def.filter ? undefined : resolveFilter(def.filter, ctx);
2253
- const filter = ids ? [...(own ?? []), { field: 'id', op: 'in', value: ids }] : own;
2281
+ const kept = ids ? [...(own ?? []), { field: 'id', op: 'in', value: ids }] : own;
2282
+ // A code read by a scanner: the records whose field holds it, whatever their names.
2283
+ const filter = field ? [...(kept ?? []), { field, op: '=', value: query }] : kept;
2254
2284
  const relation = byModel ? model : def.relation;
2255
- return track(source.search({ model: relation, query, ...(filter ? { filter } : {}), limit, ...(exact ? { exact: true } : {}) }));
2285
+ return track(source.search({ model: relation, query: field ? '' : query, ...(filter ? { filter } : {}), limit, ...(exact ? { exact: true } : {}) }));
2256
2286
  }
2257
2287
  function lineField(field) {
2258
2288
  const def = fieldDef(field);
@@ -2293,6 +2323,9 @@ function innerForm(options, within, place, outer) {
2293
2323
  loadedOnce = true;
2294
2324
  if (asksLimits())
2295
2325
  void track(askLimits());
2326
+ // Its approvals: where it stands, what was decided, what this person may decide — its fields locked while it waits.
2327
+ if (keepsApprovals)
2328
+ void track(askApprovals());
2296
2329
  if (again)
2297
2330
  return;
2298
2331
  offerDraft();
@@ -2325,7 +2358,7 @@ function innerForm(options, within, place, outer) {
2325
2358
  found = null;
2326
2359
  serverKey = null;
2327
2360
  draftEpoch++;
2328
- const fresh = { dirty: [], errors: {}, warning: null, warningField: null, error: null, draft: null, step: steps[0] ?? null, skipped: [], saveProblem: null, version: null, can: null, conflict: null, setAside: null, unsent: [], unread: {}, takenOut: {}, resume: null };
2361
+ const fresh = { dirty: [], errors: {}, warning: null, warningField: null, error: null, draft: null, step: steps[0] ?? null, skipped: [], saveProblem: null, version: null, can: null, conflict: null, setAside: null, unsent: [], unread: {}, takenOut: {}, resume: null, approval: null };
2329
2362
  if (id != null) {
2330
2363
  set({ ...fresh, recordId: id, status: 'idle' });
2331
2364
  await load();
@@ -2744,7 +2777,7 @@ function innerForm(options, within, place, outer) {
2744
2777
  // Nothing of the record changed; what shows of it may have.
2745
2778
  set({});
2746
2779
  },
2747
- fieldReadonly: (name) => lockedField(fieldDef(name)) || state.can?.edit === false || state.can?.readonlyFields?.includes(name) === true || signedNow().has(name),
2780
+ fieldReadonly: (name) => lockedField(fieldDef(name)) || state.can?.edit === false || state.can?.readonlyFields?.includes(name) === true || signedNow().has(name) || !!approvalKit?.locks(name),
2748
2781
  loadChoices: (field) => loadChoices(field),
2749
2782
  loadDefinitions,
2750
2783
  addDefinition,
@@ -3242,6 +3275,14 @@ function innerForm(options, within, place, outer) {
3242
3275
  return saved;
3243
3276
  },
3244
3277
  refreshLimits: () => askLimits(),
3278
+ refreshApprovals: askApprovals,
3279
+ async decide(level, decision, reason) {
3280
+ const kit = await approvalsCode();
3281
+ if (!kit)
3282
+ throw new Error('No approvals on this page');
3283
+ return track(kit.decide(level, decision, reason));
3284
+ },
3285
+ approvalLock: (name) => approvalKit?.lockWords(name) ?? null,
3245
3286
  async reloadTheirs() {
3246
3287
  // A save on its way first: its answer is not dropped by the load.
3247
3288
  const asked = shownNow();
@@ -3426,6 +3467,9 @@ function innerForm(options, within, place, outer) {
3426
3467
  // choice's places as the source tells them: a record's own once it is loaded, its own choices not counted against it.
3427
3468
  if (keepsLimits())
3428
3469
  void track(limitsCode());
3470
+ // A record's approvals: their part asked for as the form is made, ready by the time its record is loaded.
3471
+ if (keepsApprovals)
3472
+ void track(approvalsCode());
3429
3473
  // A new form has its values now — a draft continued from its key once it is laid — and opens once whoever made it
3430
3474
  // has had the chance to listen.
3431
3475
  if (state.recordId == null) {