@object-ui/components 17.1.0 → 17.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,300 @@
1
1
  # @object-ui/components
2
2
 
3
+ ## 17.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4ae0ac4: One placement rule for action `locations` (objectui#3142).
8
+
9
+ **Breaking for metadata**: an action that declares no `locations` (missing key
10
+ or `[]`) no longer renders in a located surface. FROM: omitting `locations`
11
+ made an action appear on the list toolbar, the record header, and every
12
+ metadata-admin toolbar. TO: declare where it belongs —
13
+ `locations: ['record_header']` for the record header, `['list_toolbar']` for
14
+ the list toolbar, and so on. Nothing else changes; actions that already
15
+ declare a location are untouched.
16
+
17
+ Four renderers each answered "where does an action with no `locations` go?"
18
+ differently — `action:bar` and metadata-admin showed it EVERYWHERE,
19
+ `page:header` showed it on the header, `action:group` showed it for
20
+ `undefined` but hid it for `[]` — while `ActionEngine`, `RecordDetailView`,
21
+ `DeclaredActionsBar`, the related-list bridge and the environment toolbar all
22
+ showed it NOWHERE. The same action therefore appeared or vanished depending on
23
+ which component happened to draw it. All eight now go through one exported
24
+ predicate, `actionRendersAt(action, location)` from `@object-ui/types`: an
25
+ action renders at a location only if it declares that location.
26
+
27
+ The strict reading is the platform's own — ADR-0078 lists "an `action` with no
28
+ `locations`" as a verified inert shape, and the detail-page synthesizer already
29
+ documented "must include `locations: ['record_header']` to render". The
30
+ leniency contradicted both, and it is what let an aggregate-only bulk action
31
+ (objectui#3139) — one with no single-record placement by construction — mint a
32
+ list-toolbar button whose dispatch could only fail.
33
+
34
+ Two placements are declared elsewhere and need no `locations`, both unchanged:
35
+ host-injected chrome in the `systemActions` / `headerSystemActions` slot (now
36
+ consistently exempt on `page:header` too, where it used to be filtered), and an
37
+ action named in a view's `bulkActions` / `bulkActionDefs`.
38
+
39
+ Authoring side: Studio seeds `locations: ['record_header']` on a new action
40
+ instead of minting one that renders nowhere, and the action inspector says so
41
+ when no placement is ticked. The `ActionSchema.locations` JSDoc claimed a
42
+ `['record_header']` default that no renderer ever implemented — corrected.
43
+
44
+ - 09d30a4: Stop declaring 18 `@object-ui/auth` / `@object-ui/components` / `@object-ui/react`
45
+ symbols under names `@objectstack/spec` owns (objectui#3159, objectstack#4115
46
+ batch 5).
47
+
48
+ **Breaking for importers of all three packages** — six exported names changed,
49
+ because the spec exports the same name for a _different_ thing:
50
+
51
+ | package | was | now | what the spec's same-named export actually is |
52
+ | :----------- | :--------------------------- | :----------------------- | :----------------------------------------------------------------------------- |
53
+ | `auth` | `AuthSession` | `AuthClientSession` | the SERVER's session record (`{ id, userId, expiresAt: ISO string, token? }`) |
54
+ | `auth` | `AuthProviderConfig` | `AuthProviderOptions` | an OAuth/OIDC provider registration (`{ id, clientId, clientSecret, scope? }`) |
55
+ | `components` | `FilterCondition` | `FilterBuilderCondition` | the recursive ObjectQL predicate AST (`$and`/`$or`/`$not`) |
56
+ | `components` | `Field` | `FieldContainer` | an object FIELD's metadata and its builder namespace |
57
+ | `react` | `ConflictResolutionStrategy` | `ConflictResolution` | the metadata-MERGE policy (`error \| priority \| first-wins \| last-wins`) |
58
+
59
+ The `react` rename is the odd one out: the new name is the **spec's own** name
60
+ for the union that hook always used, so it is a re-export rather than a dialect.
61
+
62
+ Eleven more keep their names and are now **imported or derived from the spec**
63
+ instead of re-declared: `TenancyPosture`, `DelegableScope` (+`DelegableAdminScope`),
64
+ `AuthUser`, `ShareLinkPermission`, `ShareLinkAudience`, `ShareLink`, `SortItem`,
65
+ `OfflineStrategy`, `OfflineCacheConfig`, `OfflineSyncConfig`, `OfflineConfig`,
66
+ `NavigationConfig`.
67
+
68
+ **Three of the copies were losing information, not just duplicating it.**
69
+
70
+ - `AuthUser` never declared the spec's `positions` or `tenantId` — the
71
+ authorization inputs. Its `[key: string]: unknown` index signature meant the
72
+ omission was invisible at every call site _and_ to any structural comparison
73
+ (the objectstack#4075 mechanism). It now `extends` the spec principal, so the
74
+ display-only fields (`image`, `role`, `roles`, `emailVerified`) are the delta
75
+ and the spec's keys arrive on their own.
76
+ - `useNavigationOverlay`'s copy carried the note _"inline … to avoid importing
77
+ from `@object-ui/types` (which may not be a direct dependency of
78
+ `@object-ui/react`)"_. The vocabulary belongs to `@objectstack/spec`, which
79
+ **is** a direct dependency — the same expired "kept local to avoid a
80
+ dependency" comment objectui#3169 found in `@object-ui/app-shell`.
81
+ - `useOffline` and `usePerformance` both opened with _"Types aligned with
82
+ `@objectstack/spec` v2.0.7"_. The installed spec is 17.0.0-rc.1.
83
+
84
+ `ShareLink` derives from the spec row **minus `password_hash`** — omitted rather
85
+ than optional, because it is the credential itself and typing it in a browser
86
+ package is an invitation to render it. `password_protected` (the boolean the UI
87
+ needs in its place) is the one local addition.
88
+
89
+ The config types derive from each schema's **input** side, not `z.infer`.
90
+ `useOffline(config: OfflineConfig = {})` defaults to the empty object, which the
91
+ output type — every `.default()`ed key required — would reject outright.
92
+
93
+ `@objectstack/spec` moves from `devDependencies` to `dependencies` in
94
+ `@object-ui/components`: its public type surface now references the spec.
95
+
96
+ Scored `minor`, not `major`, per this repo's fixed-group rule — objectui's major
97
+ tracks `@objectstack`, so breaking changes of our own ship as minor with the
98
+ semantics spelled out above (see AGENTS.md §版本号策略). A `major` here would carry
99
+ all 39 packages of the fixed group to `18.0.0` and off objectstack's 17.x line.
100
+
101
+ - 726b89c: `@object-ui/types` stops declaring sixteen symbols under names `@objectstack/spec` owns (objectui#3156, objectstack#4115).
102
+
103
+ Seven are now **derived** from the spec, nine are **renamed** to the local
104
+ dialect they always were. Both halves remove the same hazard: a local
105
+ declaration under a spec export's name reads as the spec's own definition to
106
+ the next reader, so a copy that is merely _correct today_ is a planted premise
107
+ tomorrow.
108
+
109
+ **Derived** — the spec now supplies the keys, by reference:
110
+
111
+ | symbol | derivation |
112
+ | :----------------------- | :-------------------------------------------------------------------------------- |
113
+ | `ActionParam` | `z.input<typeof ActionParamSchema>`, `type` widened to the local legacy spellings |
114
+ | `CreateExportJobRequest` | `Omit<CreateExportJobInput, 'object'>` (`object` is the method argument) |
115
+ | `CreateExportJobResult` | re-export from `@objectstack/spec/contracts` |
116
+ | `ImportRowResult` | re-export from `@objectstack/spec/api` |
117
+ | `NavigationArea` | spec keys, with `navigation` / `visible` pinned locally |
118
+ | `NavigationAreaSchema` | `specFieldsExcept(NavigationAreaSchema.shape, …)` |
119
+ | `Theme` | re-export of the spec's `ThemeInput` (the authoring shape) |
120
+ | `ExportJobFormat` | re-export of the spec's `ExportFormat` |
121
+
122
+ Four of these close real gaps rather than tidy names. `ActionParam` never
123
+ declared `reference` — the key `resolveActionParams()` actually reads for an
124
+ inline lookup target — nor `defaultFromRow`, which the metadata designer's own
125
+ inspector writes; it also narrowed `visible` to a bare string although the
126
+ resolver has always accepted the `{ dialect, source }` envelope too.
127
+ `CreateExportJobResult.createdAt` and `ImportRowResult.action` were optional
128
+ here and required by the server, leaving every consumer a branch that could
129
+ never run. And `NavigationArea`'s `id` now carries the spec's own length rule
130
+ instead of accepting any string.
131
+
132
+ **Renamed** — same word, different concept:
133
+
134
+ | was | now | why |
135
+ | :----------------- | :----------------------- | :----------------------------------------------------------------------------------------------------------------------------- |
136
+ | `FileMetadata` | `UploadedFileMetadata` | field-VALUE payload (`url`, `original_name`), not the storage file record |
137
+ | `GestureType` | `TouchGestureType` | direction-fused (`swipe-left`), not the spec's type+direction pair |
138
+ | `GestureConfig` | `TouchGestureConfig` | gesture→`action` binding, not per-gesture tuning |
139
+ | `OfflineConfig` | `PWAOfflineConfig` | service-worker route caching, not the offline data/sync model |
140
+ | `PageRegion` | `PageNodeRegion` | region of the renderer page NODE, holding `SchemaNode`s |
141
+ | `PageRegionSchema` | `PageNodeRegionSchema` | zod twin of the above |
142
+ | `ResponsiveConfig` | `MobileResponsiveConfig` | mobile box config, not the spec's SDUI grid contract |
143
+ | `WidgetManifest` | `RuntimeWidgetManifest` | SDUI component manifest, not the field-widget plugin manifest |
144
+ | `WidgetSource` | `RuntimeWidgetSource` | `module`/`inline`/`registry` loader union — and its `inline` carries a resolved component where the spec's carries source code |
145
+
146
+ **Migration**: the old names are gone, not deprecated — an alias would preserve
147
+ exactly the ambiguity being removed. Import the new name; nothing about the
148
+ shapes changed. `@object-ui/types` already re-exports the spec's own
149
+ `SpecResponsiveConfig`, and `@object-ui/react`'s `useOffline` config remains the
150
+ spec-shaped `OfflineConfig`, so both concepts stay reachable under
151
+ distinguishable names.
152
+
153
+ Each rename carries a bidirectional tripwire
154
+ (`packages/types/src/__tests__/page-nav-misc-spec-parity.test.ts`): it fails if
155
+ the spec ever claims the new name, and also if the spec retires the old one —
156
+ at which point the natural name can be taken back rather than the workaround
157
+ outliving its reason.
158
+
159
+ ### Patch Changes
160
+
161
+ - cb82705: A standalone grid's search box searches the list, not the page you can see (objectui#3118).
162
+
163
+ Under server-side pagination a standalone `ObjectGrid` rendered `data-table`'s
164
+ built-in search box, and that box filtered the rows the table was holding —
165
+ which is one page. The user read "2 results for X in this list" while 3075 rows
166
+ never participated, with the pager beside it still reading `1 / 63`. Every piece
167
+ was individually correct: `searchable` defaults to true, `manualPagination` is
168
+ true, and the two are declared next to each other in the same object literal.
169
+
170
+ This is objectui#3106 one axis over — sort there, filter here — and it takes the
171
+ same shape. `DataTable` gains `manualSearch` + a controlled `search` +
172
+ `onSearchChange`. In that mode it filters nothing, reports the typed term, and
173
+ renders `search` as the box's value, holding **no** term of its own: a private
174
+ copy beside a controlled prop is the shape the defect had. `ObjectGrid` turns
175
+ that term into a `$search` on the refetch — the server picks the matching fields
176
+ from the object's metadata (ADR-0061), the same channel the ListView toolbar has
177
+ always used — and returns to page 1, since a new term makes the old page index a
178
+ different set of rows (usually no rows at all). `$searchFields` rides along only
179
+ when the view declared `searchableFields`, which can narrow the server-resolved
180
+ set and never widen it.
181
+
182
+ Two things worth naming:
183
+
184
+ - Both paths are never live at once. The server's answer is the answer; a client
185
+ pass left running underneath would silently re-narrow it to whichever returned
186
+ rows happen to contain the term as _rendered text_, overruling the server's
187
+ own notion of which fields are searchable.
188
+ - Under `manualSearch` a table with no `onSearchChange` renders **no** search
189
+ box. The sort axis could degrade to inert headers; here there is no honest
190
+ local behaviour to fall back to, because the rows to search are not in the
191
+ browser.
192
+
193
+ Client-paginated grids are untouched: inline, bound and grouped grids hold every
194
+ row they display, so their box keeps filtering in memory, where the count it
195
+ produces is true. The ListView path was never affected — it passes
196
+ `showSearch: false` and searches from its own toolbar.
197
+
198
+ - f6e8d78: Lookup search inside a create/edit modal is typeable again (objectui#3183).
199
+
200
+ In every production console build, the search input of a lookup field's
201
+ quick-select popover — and the nested Record Picker dialog — could not take
202
+ focus while the form modal was open: every click/focus was synchronously
203
+ yanked back to the field trigger, so a lookup could not be searched while
204
+ creating a record.
205
+
206
+ Root cause is a race in stock `@radix-ui/react-focus-scope@1.1.16`: the
207
+ focus-scopes stack effect's cleanup schedules `focusScopesStack.remove(scope)`
208
+ in a `setTimeout(0)`. When the effect re-runs for a still-mounted scope (a
209
+ `container` ref flicker), the re-run re-`add`s the scope and the stale timeout
210
+ then evicts it — the dialog's trap listeners stay active but its scope is no
211
+ longer in the stack, so an opening popover pauses nothing and the trap yanks
212
+ focus out of the popover forever.
213
+
214
+ Fixed via `patches/@radix-ui__react-focus-scope.patch`: an effect re-run for a
215
+ live scope cancels the pending eviction; a real unmount still runs the full
216
+ delayed cleanup (autofocus-on-unmount + stack removal). Regression-tested in
217
+ `packages/components` with a deterministic reproduction of the race.
218
+
219
+ - a8ad6c0: A required boolean must be savable in its UNCHECKED state — `false` and `0` are values.
220
+
221
+ Reported against an AI-built task tracker whose 任务 object has a required
222
+ `是否完成` boolean: the create form showed the switch OFF, answered "是否完成不能
223
+ 为空", and saved instantly once the switch was turned ON. The app could only ever
224
+ create ALREADY-DONE tasks — the one state the control shows by default was the
225
+ one value it refused to save (cloud#972).
226
+
227
+ Two defects stacked, and either alone is enough to break it:
228
+
229
+ **The `required` verdict read truthiness, not presence.** `@objectstack/spec`
230
+ FieldSchema.required (ADR-0113) is "an insert must provide a NON-NULL value",
231
+ and objectql's record validator implements exactly that. react-hook-form's
232
+ built-in rule instead fails whenever `isBoolean(value) && !value` — its
233
+ accept-the-terms checkbox heritage — silently redefining every required boolean
234
+ as "must be TRUE", including a select whose chosen option value is `false`. It
235
+ also disagreed the other way, letting a whitespace-only string through for the
236
+ server to reject with a 400. The form renderer no longer hands RHF its own
237
+ `required`: the check is now a `validate` entry keyed `required` (so the error
238
+ still surfaces as `type: 'required'`, which the conditional-required cleanup
239
+ keys on) backed by a new shared `isMissingForRequired` in `@object-ui/core`, a
240
+ deliberate mirror of objectql `record-validator.isMissing` — `undefined`,
241
+ `null`, blank-after-trim string, empty array. Deleting the inherited rule also
242
+ stops a `required` that rode in on `validation` from outliving a `requiredWhen`
243
+ that resolved to FALSE.
244
+
245
+ **A boolean field held `undefined` while displaying "off".** A two-state control
246
+ has no third state, but a field with no entry in `defaultValues` rendered an OFF
247
+ switch backed by nothing: the create payload omitted the column (it lands null,
248
+ which reads as unchecked but isn't) and the presence check above would still
249
+ refuse it. The form renderer now folds `false` into `defaultValues` for every
250
+ boolean-widget field the caller left unset — in `defaultValues` itself, not
251
+ per-Controller, because that object is also the dirty-check baseline and what
252
+ the defaults-reset window replays. Every surface gets it, including the
253
+ modal/drawer create dialogs that start from a bare `{}`. An authored default
254
+ (or a loaded record, `null` included) still wins.
255
+
256
+ `WizardForm`'s cross-step gate had its own copy of the empty-value predicate; it
257
+ now imports the shared one so it cannot drift from the per-field verdict. And
258
+ the field-demo renderer read `schema.defaultValue || schema.value`, throwing
259
+ away an authored default of `false` / `0` / `''` — same falsy-as-empty class,
260
+ now `??`.
261
+
262
+ Verified end to end on a local stack against the exact metadata shape
263
+ `apply_blueprint` materializes (`{ type: 'boolean', required: true }`, no
264
+ default): a 是否完成 = 否 task with 工时 = 0 now creates and persists as
265
+ `{ hours: 0, is_done: false }`, turning the switch on still stores `true`, and a
266
+ blank required text is still refused.
267
+
268
+ - Updated dependencies [4ae0ac4]
269
+ - Updated dependencies [696e3c1]
270
+ - Updated dependencies [bca45cc]
271
+ - Updated dependencies [a889e31]
272
+ - Updated dependencies [09d30a4]
273
+ - Updated dependencies [4bf612c]
274
+ - Updated dependencies [335041c]
275
+ - Updated dependencies [b414983]
276
+ - Updated dependencies [256f8cc]
277
+ - Updated dependencies [d9668a7]
278
+ - Updated dependencies [4b470b9]
279
+ - Updated dependencies [cb82705]
280
+ - Updated dependencies [f572849]
281
+ - Updated dependencies [4a51e77]
282
+ - Updated dependencies [ea96284]
283
+ - Updated dependencies [d3584c6]
284
+ - Updated dependencies [cc70b8f]
285
+ - Updated dependencies [a8ad6c0]
286
+ - Updated dependencies [444457c]
287
+ - Updated dependencies [850033c]
288
+ - Updated dependencies [022e4c3]
289
+ - Updated dependencies [009e25d]
290
+ - Updated dependencies [726b89c]
291
+ - @object-ui/types@17.2.0
292
+ - @object-ui/core@17.2.0
293
+ - @object-ui/react@17.2.0
294
+ - @object-ui/i18n@17.2.0
295
+ - @object-ui/sdui-parser@17.2.0
296
+ - @object-ui/react-runtime@17.2.0
297
+
3
298
  ## 17.1.0
4
299
 
5
300
  ### Minor Changes
@@ -8,6 +8,12 @@ import { VariantProps } from 'class-variance-authority';
8
8
  */
9
9
  import * as React from "react";
10
10
  declare const fieldVariants: (props?: import('class-variance-authority/types').ClassProp | undefined) => string;
11
+ /**
12
+ * Props for {@link FieldContainer}.
13
+ *
14
+ * Note this one keeps its natural name: the spec does not export `FieldProps`.
15
+ * Only the component itself had to move — see the note on `FieldContainer`.
16
+ */
11
17
  export interface FieldProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof fieldVariants> {
12
18
  label?: React.ReactNode;
13
19
  description?: React.ReactNode;
@@ -15,5 +21,17 @@ export interface FieldProps extends React.HTMLAttributes<HTMLDivElement>, Varian
15
21
  required?: boolean;
16
22
  htmlFor?: string;
17
23
  }
18
- declare const Field: React.ForwardRefExoticComponent<FieldProps & React.RefAttributes<HTMLDivElement>>;
19
- export { Field };
24
+ /**
25
+ * A labelled form-control wrapper: label, slotted control, description, error.
26
+ *
27
+ * Named `FieldContainer`, not `Field` (objectstack#4115): `@objectstack/spec/data`
28
+ * exports `Field` — an object FIELD's metadata (type, reference_to, options,
29
+ * permissions, …) and its builder namespace — which has nothing to do with this
30
+ * `<div>`. Two unrelated things under one name is the defect that issue exists
31
+ * to remove; `@object-ui/app-shell` already renamed the same kind of collision
32
+ * (`FieldInput` → `ScreenFieldInput`, objectui#3169) for the same reason.
33
+ * `__tests__/share-filter-sort-spec-parity.test.ts` pins that the spec does not
34
+ * own the new name.
35
+ */
36
+ declare const FieldContainer: React.ForwardRefExoticComponent<FieldProps & React.RefAttributes<HTMLDivElement>>;
37
+ export { FieldContainer };
@@ -6,7 +6,23 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  import * as React from "react";
9
- export interface FilterCondition {
9
+ /**
10
+ * One row of the FilterBuilder UI.
11
+ *
12
+ * Deliberately NOT `FilterCondition` (objectstack#4115): the spec owns that
13
+ * name for the RECURSIVE ObjectQL predicate AST — `{ [field]: FieldOperators }`
14
+ * with `$and` / `$or` / `$not` branches — which is a query, not a row. This is
15
+ * one flat `{ field, operator, value }` line plus the React key the list needs,
16
+ * and `operator` here is the builder's own dropdown vocabulary
17
+ * (`FILTER_BUILDER_OPERATORS`), not the spec's `$`-tokens.
18
+ *
19
+ * `@object-ui/types` already renamed its identical concept to
20
+ * `FilterBuilderCondition` (objectui#3068); this is the same thing, so it takes
21
+ * the same name rather than inventing a second dialect. Note the same name is
22
+ * NOT the right answer everywhere: `@object-ui/app-shell`'s `FilterCondition`
23
+ * really was the spec's ObjectQL AST and was re-exported (objectui#3169).
24
+ */
25
+ export interface FilterBuilderCondition {
10
26
  id: string;
11
27
  field: string;
12
28
  operator: string;
@@ -15,7 +31,7 @@ export interface FilterCondition {
15
31
  export interface FilterGroup {
16
32
  id: string;
17
33
  logic: "and" | "or";
18
- conditions: FilterCondition[];
34
+ conditions: FilterBuilderCondition[];
19
35
  }
20
36
  export interface FilterBuilderProps {
21
37
  fields?: Array<{
@@ -129,9 +145,9 @@ interface LookupValuePickerProps {
129
145
  displayField?: string;
130
146
  idField?: string;
131
147
  };
132
- value: FilterCondition["value"];
148
+ value: FilterBuilderCondition["value"];
133
149
  multiple: boolean;
134
- onChange: (value: FilterCondition["value"]) => void;
150
+ onChange: (value: FilterBuilderCondition["value"]) => void;
135
151
  }
136
152
  declare function LookupValuePicker({ field, value, multiple, onChange }: LookupValuePickerProps): React.JSX.Element;
137
153
  export { FilterBuilder, LookupValuePicker };
@@ -1,3 +1,4 @@
1
+ import { SortItem as SpecSortItem } from '@objectstack/spec/shared';
1
2
  /**
2
3
  * ObjectUI
3
4
  * Copyright (c) 2024-present ObjectStack Inc.
@@ -6,10 +7,20 @@
6
7
  * LICENSE file in the root directory of this source tree.
7
8
  */
8
9
  import * as React from "react";
9
- export interface SortItem {
10
+ /**
11
+ * One row of the SortBuilder UI: the spec's `SortItem` (`{ field, order }`)
12
+ * plus the React key this list needs.
13
+ *
14
+ * Derived rather than re-declared (objectstack#4115). The old hand copy agreed
15
+ * with the spec key-for-key, which is the state one spec release away from
16
+ * drifting — a new sort key (`nulls`, say) would have appeared in the spec and
17
+ * silently gone missing here. `id` is the ONE local addition and it is
18
+ * synthetic: `normalize()` mints it, and `SortBuilder`'s change comparison
19
+ * strips it before diffing precisely because it is not part of the contract.
20
+ */
21
+ export interface SortItem extends SpecSortItem {
22
+ /** React key for the row. Synthetic — never part of the emitted sort. */
10
23
  id: string;
11
- field: string;
12
- order: 'asc' | 'desc';
13
24
  }
14
25
  export interface SortBuilderProps {
15
26
  fields?: Array<{
package/dist/index.css CHANGED
@@ -2733,6 +2733,9 @@
2733
2733
  .fade-in-80 {
2734
2734
  --tw-enter-opacity: 0.8;
2735
2735
  }
2736
+ .paused {
2737
+ animation-play-state: paused;
2738
+ }
2736
2739
  .ring-inset {
2737
2740
  --tw-ring-inset: inset;
2738
2741
  }