openyida 2026.7.19 → 2026.7.21

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 (139) hide show
  1. package/README.md +14 -0
  2. package/bin/yida.js +107 -18
  3. package/lib/app/create-app.js +12 -1
  4. package/lib/app/create-form/args.js +13 -0
  5. package/lib/app/create-form.js +154 -67
  6. package/lib/app/create-page.js +45 -9
  7. package/lib/app/get-schema.js +65 -14
  8. package/lib/app/import-app.js +92 -41
  9. package/lib/app/publish.js +289 -556
  10. package/lib/app/schema-field-resolution.js +370 -0
  11. package/lib/app/services/app-reader.js +85 -0
  12. package/lib/app/services/app-service.js +235 -0
  13. package/lib/app/services/canvas-page-compiler.js +91 -0
  14. package/lib/app/services/canvas-page-schema-builder.js +149 -0
  15. package/lib/app/services/field-bindings.js +68 -0
  16. package/lib/app/services/form-compiler.js +1922 -0
  17. package/lib/app/services/form-mode-service.js +182 -0
  18. package/lib/app/services/form-schema-patcher.js +333 -0
  19. package/lib/app/services/form-schema-reader.js +59 -0
  20. package/lib/app/services/form-service.js +379 -0
  21. package/lib/app/services/form-validation.js +562 -0
  22. package/lib/app/services/native-page-compiler.js +92 -0
  23. package/lib/app/services/native-page-schema-builder.js +428 -0
  24. package/lib/app/services/page-resource-service.js +185 -0
  25. package/lib/app/update-app.js +18 -6
  26. package/lib/auth/token-auth.js +34 -3
  27. package/lib/auth/token-store.js +22 -4
  28. package/lib/bridge/bridge.js +37 -5
  29. package/lib/core/agent-capabilities.js +5 -3
  30. package/lib/core/command-manifest.js +40 -0
  31. package/lib/core/doctor.js +15 -4
  32. package/lib/core/env-cmd.js +1 -1
  33. package/lib/core/env.js +7 -6
  34. package/lib/core/legacy-schema-guard.js +408 -0
  35. package/lib/core/locales/en.js +8 -46
  36. package/lib/core/locales/zh.js +8 -46
  37. package/lib/core/sample.js +1 -1
  38. package/lib/core/utils.js +334 -66
  39. package/lib/core/yida-client.js +104 -3
  40. package/lib/formula/evaluate.js +8 -66
  41. package/lib/formula/field-refs.js +83 -0
  42. package/lib/process/configure-process.js +44 -1992
  43. package/lib/process/create-process.js +54 -87
  44. package/lib/process/services/process-compiler.js +2056 -0
  45. package/lib/process/services/process-reader.js +232 -0
  46. package/lib/process/services/process-resource-service.js +571 -0
  47. package/lib/process/services/process-service.js +122 -0
  48. package/lib/process/services/process-stage-checkpoint.js +246 -0
  49. package/lib/report/append.js +7 -26
  50. package/lib/report/http.js +31 -5
  51. package/lib/report/index.js +10 -2
  52. package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
  53. package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
  54. package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
  55. package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +204 -8
  56. package/lib/schema/adapters/app-adapter.js +210 -0
  57. package/lib/schema/adapters/form-adapter.js +1362 -0
  58. package/lib/schema/adapters/keys.js +19 -0
  59. package/lib/schema/adapters/page-adapter.js +566 -0
  60. package/lib/schema/adapters/process-adapter.js +519 -0
  61. package/lib/schema/applier.js +1878 -0
  62. package/lib/schema/apply-store.js +1083 -0
  63. package/lib/schema/command.js +645 -0
  64. package/lib/schema/dependency-graph.js +83 -0
  65. package/lib/schema/errors.js +481 -0
  66. package/lib/schema/hash.js +9 -0
  67. package/lib/schema/manifest-limits.js +177 -0
  68. package/lib/schema/manifest-loader.js +309 -0
  69. package/lib/schema/manifest-schema-v1.json +193 -0
  70. package/lib/schema/normalize-manifest.js +281 -0
  71. package/lib/schema/page-canvas-foundation.js +447 -0
  72. package/lib/schema/page-data-source-builder.js +561 -0
  73. package/lib/schema/page-foundation.js +556 -0
  74. package/lib/schema/page-source-loader.js +314 -0
  75. package/lib/schema/planner.js +691 -0
  76. package/lib/schema/remote-dispatch-boundary.js +39 -0
  77. package/lib/schema/remote-missing.js +21 -0
  78. package/lib/schema/remote-reader.js +189 -0
  79. package/lib/schema/resource-registry.js +157 -0
  80. package/lib/schema/server-revision.js +71 -0
  81. package/lib/schema/sort.js +28 -0
  82. package/lib/schema/state-store.js +589 -0
  83. package/package.json +2 -2
  84. package/scripts/postinstall.js +8 -12
  85. package/scripts/validate-package-size.js +1 -1
  86. package/yida-skills/SKILL.md +114 -29
  87. package/yida-skills/references/schema-as-code-phase1.md +99 -0
  88. package/yida-skills/references/setup-and-env.md +59 -59
  89. package/yida-skills/references/task-retrospective.md +6 -6
  90. package/yida-skills/skills/yida-app/SKILL.md +60 -14
  91. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
  92. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
  93. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +7 -1
  94. package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
  95. package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
  96. package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
  97. package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
  98. package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
  99. package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
  100. package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
  101. package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
  102. package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
  103. package/yida-skills/skills/yida-integration/SKILL.md +7 -1
  104. package/yida-skills/skills/yida-login/SKILL.md +50 -99
  105. package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
  106. package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
  107. package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
  108. package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
  109. package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
  110. package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
  111. package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
  112. package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
  113. package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
  114. package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
  115. package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
  116. package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
  117. package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
  118. package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
  119. package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
  120. package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
  121. package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
  122. package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
  123. package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
  124. package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
  125. package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
  126. package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
  127. package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
  128. package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
  129. package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
  130. package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
  131. package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
  132. package/yida-skills/skills/yida-report/SKILL.md +7 -1
  133. package/yida-skills/skills-index.json +14 -46
  134. package/yida-skills/skills/large-file-write/SKILL.md +0 -93
  135. package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
  136. package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
  137. package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
  138. package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
  139. package/yida-skills/skills/sls-log-workbench/sls-query.js +0 -295
@@ -43,7 +43,9 @@ function isUsableAuthData(authData) {
43
43
  if (authData.auth_mode === 'token' || authData.authMode === 'token') {
44
44
  return true;
45
45
  }
46
- return false;
46
+ const cookies = Array.isArray(authData.cookies) ? authData.cookies : [];
47
+ const csrfToken = authData.csrf_token || authData.csrfToken || authData._csrf_token;
48
+ return cookies.length > 0 && typeof csrfToken === 'string' && csrfToken.length > 0;
47
49
  }
48
50
 
49
51
  function createAuthRef(authData) {
@@ -53,7 +55,7 @@ function createAuthRef(authData) {
53
55
  : triggerLogin();
54
56
  const safeAuthData = loadedAuthData || {};
55
57
 
56
- return {
58
+ const authRef = {
57
59
  baseUrl: resolveBaseUrl(safeAuthData),
58
60
  authData: safeAuthData,
59
61
  authMode: safeAuthData.auth_mode || safeAuthData.authMode || '',
@@ -61,6 +63,12 @@ function createAuthRef(authData) {
61
63
  corpId: safeAuthData.corp_id || '',
62
64
  userId: safeAuthData.user_id || '',
63
65
  };
66
+ if (Array.isArray(safeAuthData.cookies)) {
67
+ authRef.cookieData = safeAuthData;
68
+ authRef.cookies = safeAuthData.cookies;
69
+ authRef.csrfToken = safeAuthData.csrf_token || safeAuthData.csrfToken || safeAuthData._csrf_token || '';
70
+ }
71
+ return authRef;
64
72
  }
65
73
 
66
74
  function isTokenAuthRef(authRef) {
@@ -80,6 +88,22 @@ function isTokenAuthRef(authRef) {
80
88
  return authMode === 'token' || authSource === 'token';
81
89
  }
82
90
 
91
+ function isCookieAuthRef(authRef) {
92
+ if (!authRef) {
93
+ return false;
94
+ }
95
+ const authData = authRef.authData || authRef.cookieData || {};
96
+ const authMode = authRef.authMode || authRef.auth_mode || authData.auth_mode || authData.authMode;
97
+ const authSource = authRef.authSource || authRef.auth_source || authData.auth_source || authData.authSource;
98
+ return (
99
+ authMode === 'env' ||
100
+ authMode === 'cookie' ||
101
+ authSource === 'env' ||
102
+ authSource === 'cookie' ||
103
+ Array.isArray(authRef.cookies)
104
+ );
105
+ }
106
+
83
107
  function isAuthRefReady(authRef) {
84
108
  if (!authRef) {
85
109
  return false;
@@ -87,7 +111,14 @@ function isAuthRefReady(authRef) {
87
111
  if (isTokenAuthRef(authRef)) {
88
112
  return true;
89
113
  }
90
- return false;
114
+ return (
115
+ isCookieAuthRef(authRef) &&
116
+ typeof authRef.baseUrl === 'string' &&
117
+ authRef.baseUrl.length > 0 &&
118
+ typeof authRef.csrfToken === 'string' &&
119
+ authRef.csrfToken.length > 0 &&
120
+ Array.isArray(authRef.cookies)
121
+ );
91
122
  }
92
123
 
93
124
  class YidaClient {
@@ -117,6 +148,20 @@ class YidaClient {
117
148
  );
118
149
  }
119
150
 
151
+ async getOnce(requestPath, queryParams, options) {
152
+ const ref = requireReadAuth(this.authRef);
153
+ const resolvedRequestPath = typeof requestPath === 'function'
154
+ ? requestPath(ref)
155
+ : requestPath;
156
+ const resolvedQueryParams = typeof queryParams === 'function'
157
+ ? queryParams(ref)
158
+ : queryParams;
159
+ if (options === undefined) {
160
+ return httpGet(ref.baseUrl, resolvedRequestPath, resolvedQueryParams || null, ref.cookies);
161
+ }
162
+ return httpGet(ref.baseUrl, resolvedRequestPath, resolvedQueryParams || null, ref.cookies, options);
163
+ }
164
+
120
165
  async postForm(requestPath, bodyParams, options) {
121
166
  return requestWithAutoLogin(
122
167
  (ref) => {
@@ -138,6 +183,23 @@ class YidaClient {
138
183
  );
139
184
  }
140
185
 
186
+ async postFormOnce(requestPath, bodyParams, options) {
187
+ const ref = requireOneShotAuth(this.authRef);
188
+ const resolvedRequestPath = typeof requestPath === 'function'
189
+ ? requestPath(ref)
190
+ : requestPath;
191
+ const resolvedBodyParams = typeof bodyParams === 'function'
192
+ ? bodyParams(ref)
193
+ : bodyParams;
194
+ const postData = typeof resolvedBodyParams === 'string'
195
+ ? resolvedBodyParams
196
+ : querystring.stringify(resolvedBodyParams || {});
197
+ if (options === undefined) {
198
+ return httpPost(ref.baseUrl, resolvedRequestPath, postData, ref.cookies);
199
+ }
200
+ return httpPost(ref.baseUrl, resolvedRequestPath, postData, ref.cookies, options);
201
+ }
202
+
141
203
  async postJson(requestPath, bodyParams, options) {
142
204
  return requestWithAutoLogin(
143
205
  (ref) => {
@@ -178,6 +240,44 @@ class YidaClient {
178
240
  }
179
241
  }
180
242
 
243
+ function requireOneShotAuth(authRef) {
244
+ if (!authRef || typeof authRef.baseUrl !== 'string' || authRef.baseUrl.length === 0) {
245
+ const error = new Error('Schema write authentication is not ready.');
246
+ error.code = 'YIDA_WRITE_AUTH_NOT_READY';
247
+ throw error;
248
+ }
249
+ if (isTokenAuthRef(authRef)) {
250
+ return authRef;
251
+ }
252
+ if (
253
+ typeof authRef.csrfToken !== 'string' ||
254
+ authRef.csrfToken.length === 0 ||
255
+ !Array.isArray(authRef.cookies)
256
+ ) {
257
+ const error = new Error('Schema write authentication is not ready.');
258
+ error.code = 'YIDA_WRITE_AUTH_NOT_READY';
259
+ throw error;
260
+ }
261
+ return authRef;
262
+ }
263
+
264
+ function requireReadAuth(authRef) {
265
+ if (!authRef || typeof authRef.baseUrl !== 'string' || authRef.baseUrl.length === 0) {
266
+ const error = new Error('Yida read authentication is not ready.');
267
+ error.code = 'YIDA_READ_AUTH_NOT_READY';
268
+ throw error;
269
+ }
270
+ if (isTokenAuthRef(authRef)) {
271
+ return authRef;
272
+ }
273
+ if (!Array.isArray(authRef.cookies)) {
274
+ const error = new Error('Yida read authentication is not ready.');
275
+ error.code = 'YIDA_READ_AUTH_NOT_READY';
276
+ throw error;
277
+ }
278
+ return authRef;
279
+ }
280
+
181
281
  function createYidaClient(options = {}) {
182
282
  return new YidaClient(options);
183
283
  }
@@ -188,6 +288,7 @@ module.exports = {
188
288
  createAuthRef,
189
289
  createYidaClient,
190
290
  isAuthRefReady,
291
+ isCookieAuthRef,
191
292
  isTokenAuthRef,
192
293
  unwrapYidaResponse,
193
294
  };
@@ -3,6 +3,11 @@
3
3
  const fs = require('fs');
4
4
  const { throwCommandError, throwUsage } = require('../core/command-errors');
5
5
  const path = require('path');
6
+ const {
7
+ normalizeFormulaFieldRefs,
8
+ normalizeSubtableFieldId,
9
+ stripSubtableFieldPrefix,
10
+ } = require('./field-refs');
6
11
 
7
12
  const KNOWN_FUNCTIONS = new Set([
8
13
  'ABS',
@@ -49,12 +54,6 @@ const KNOWN_FUNCTIONS = new Set([
49
54
  'YEAR',
50
55
  ]);
51
56
 
52
- // Subtable child fields are stored flat in the schema (get-schema / save never
53
- // emit a "tableField_xxx.xxx" form), and Yida formulas reference them by their
54
- // plain fieldId. A "tableField_xxx." prefix inside #{...} is therefore always
55
- // an invalid reference that the web platform cannot resolve.
56
- const SUBTABLE_FIELD_PREFIX_PATTERN = /^tableField_[A-Za-z0-9]+\./;
57
-
58
57
  const CHINESE_PUNCTUATION = {
59
58
  ',': ',',
60
59
  ';': ',',
@@ -164,64 +163,6 @@ function collectSchemaFieldIds(value) {
164
163
  return fieldIds;
165
164
  }
166
165
 
167
- // Rewrite every `#{tableField_xxx.field}` reference inside a string to the
168
- // canonical `#{field}` form. Returns the corrected string plus how many refs
169
- // were fixed. Only the exact `#{tableField_<alnum>.<rest>}` shape is touched,
170
- // so unrelated text (e.g. a JS regex like `#{1,3}`) is left untouched.
171
- function stripSubtableFieldPrefix(str) {
172
- if (typeof str !== 'string' || str.indexOf('#{') === -1) {
173
- return { value: str, count: 0 };
174
- }
175
-
176
- let count = 0;
177
- const value = str.replace(/#\{([^}]*)\}/g, (whole, inner) => {
178
- const trimmed = inner.trim();
179
- if (SUBTABLE_FIELD_PREFIX_PATTERN.test(trimmed)) {
180
- count += 1;
181
- return `#{${trimmed.replace(SUBTABLE_FIELD_PREFIX_PATTERN, '')}}`;
182
- }
183
- return whole;
184
- });
185
-
186
- return { value, count };
187
- }
188
-
189
- // Recursively walk any schema node (object/array) and normalize subtable
190
- // prefixes in every string value in place. Returns the total number of refs
191
- // fixed. Covers formula, complexValue.formula, expression, linkage, and any
192
- // other string-valued property uniformly.
193
- function normalizeFormulaFieldRefs(node) {
194
- let total = 0;
195
-
196
- function visit(parent, key) {
197
- const current = parent[key];
198
- if (typeof current === 'string') {
199
- const { value, count } = stripSubtableFieldPrefix(current);
200
- if (count > 0) {
201
- parent[key] = value;
202
- total += count;
203
- }
204
- return;
205
- }
206
- if (Array.isArray(current)) {
207
- for (let i = 0; i < current.length; i++) {
208
- visit(current, i);
209
- }
210
- return;
211
- }
212
- if (current && typeof current === 'object') {
213
- for (const childKey of Object.keys(current)) {
214
- visit(current, childKey);
215
- }
216
- }
217
- }
218
-
219
- if (node && typeof node === 'object') {
220
- visit({ root: node }, 'root');
221
- }
222
- return total;
223
- }
224
-
225
166
  function scanOutsideStrings(formula, callback) {
226
167
  let quote = '';
227
168
  let escaped = false;
@@ -355,8 +296,9 @@ function collectFieldRefs(formula, diagnostics) {
355
296
  { fieldId, index }
356
297
  ));
357
298
  }
358
- if (SUBTABLE_FIELD_PREFIX_PATTERN.test(fieldId)) {
359
- const suggestion = fieldId.replace(SUBTABLE_FIELD_PREFIX_PATTERN, '');
299
+ const normalizedFieldId = normalizeSubtableFieldId(fieldId);
300
+ if (normalizedFieldId.changed) {
301
+ const suggestion = normalizedFieldId.value;
360
302
  diagnostics.push(createDiagnostic(
361
303
  'error',
362
304
  'field_ref_subtable_prefix',
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ // Subtable child fields are stored flat in the schema (get-schema / save never
4
+ // emit a "tableField_xxx.xxx" form), and Yida formulas reference them by their
5
+ // plain fieldId. A "tableField_xxx." prefix inside #{...} is therefore always
6
+ // an invalid reference that the web platform cannot resolve.
7
+ const SUBTABLE_FIELD_PREFIX_PATTERN = /^tableField_[A-Za-z0-9]+\./;
8
+
9
+ function normalizeSubtableFieldId(fieldId) {
10
+ const value = String(fieldId || '');
11
+ if (!SUBTABLE_FIELD_PREFIX_PATTERN.test(value)) {
12
+ return { value, changed: false };
13
+ }
14
+ return {
15
+ value: value.replace(SUBTABLE_FIELD_PREFIX_PATTERN, ''),
16
+ changed: true,
17
+ };
18
+ }
19
+
20
+ // Rewrite every `#{tableField_xxx.field}` reference inside a string to the
21
+ // canonical `#{field}` form. Returns the corrected string plus how many refs
22
+ // were fixed. Only the exact `#{tableField_<alnum>.<rest>}` shape is touched,
23
+ // so unrelated text (e.g. a JS regex like `#{1,3}`) is left untouched.
24
+ function stripSubtableFieldPrefix(str) {
25
+ if (typeof str !== 'string' || str.indexOf('#{') === -1) {
26
+ return { value: str, count: 0 };
27
+ }
28
+
29
+ let count = 0;
30
+ const value = str.replace(/#\{([^}]*)\}/g, (whole, inner) => {
31
+ const trimmed = inner.trim();
32
+ const normalized = normalizeSubtableFieldId(trimmed);
33
+ if (normalized.changed) {
34
+ count += 1;
35
+ return `#{${normalized.value}}`;
36
+ }
37
+ return whole;
38
+ });
39
+
40
+ return { value, count };
41
+ }
42
+
43
+ // Recursively walk any schema node (object/array) and normalize subtable
44
+ // prefixes in every string value in place. Returns the total number of refs
45
+ // fixed. Covers formula, complexValue.formula, expression, linkage, and any
46
+ // other string-valued property uniformly.
47
+ function normalizeFormulaFieldRefs(node) {
48
+ let total = 0;
49
+
50
+ function visit(parent, key) {
51
+ const current = parent[key];
52
+ if (typeof current === 'string') {
53
+ const { value, count } = stripSubtableFieldPrefix(current);
54
+ if (count > 0) {
55
+ parent[key] = value;
56
+ total += count;
57
+ }
58
+ return;
59
+ }
60
+ if (Array.isArray(current)) {
61
+ for (let i = 0; i < current.length; i++) {
62
+ visit(current, i);
63
+ }
64
+ return;
65
+ }
66
+ if (current && typeof current === 'object') {
67
+ for (const childKey of Object.keys(current)) {
68
+ visit(current, childKey);
69
+ }
70
+ }
71
+ }
72
+
73
+ if (node && typeof node === 'object') {
74
+ visit({ root: node }, 'root');
75
+ }
76
+ return total;
77
+ }
78
+
79
+ module.exports = {
80
+ normalizeFormulaFieldRefs,
81
+ normalizeSubtableFieldId,
82
+ stripSubtableFieldPrefix,
83
+ };