@object-ui/plugin-detail 17.6.0 → 17.7.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 (92) hide show
  1. package/CHANGELOG.md +6741 -0
  2. package/README.md +404 -71
  3. package/dist/ActivityTimeline.d.ts.map +1 -1
  4. package/dist/CommentAttachment.d.ts.map +1 -1
  5. package/dist/ConcurrentUpdateDialog.d.ts +17 -1
  6. package/dist/ConcurrentUpdateDialog.d.ts.map +1 -1
  7. package/dist/DetailSection.d.ts.map +1 -1
  8. package/dist/DetailSectionNode.d.ts +97 -0
  9. package/dist/DetailSectionNode.d.ts.map +1 -0
  10. package/dist/DetailView.d.ts.map +1 -1
  11. package/dist/HeaderHighlight.d.ts.map +1 -1
  12. package/dist/HistoryTimeline.d.ts +8 -1
  13. package/dist/HistoryTimeline.d.ts.map +1 -1
  14. package/dist/InlineEditSaveBar.d.ts +68 -0
  15. package/dist/InlineEditSaveBar.d.ts.map +1 -1
  16. package/dist/InlineFieldInput.d.ts +79 -6
  17. package/dist/InlineFieldInput.d.ts.map +1 -1
  18. package/dist/ReactionPicker.d.ts.map +1 -1
  19. package/dist/RecordActivityTimeline.d.ts +19 -4
  20. package/dist/RecordActivityTimeline.d.ts.map +1 -1
  21. package/dist/RecordChatterPanel.d.ts +6 -1
  22. package/dist/RecordChatterPanel.d.ts.map +1 -1
  23. package/dist/RecordComments.d.ts +1 -0
  24. package/dist/RecordComments.d.ts.map +1 -1
  25. package/dist/RecordDetailDrawer.d.ts +20 -6
  26. package/dist/RecordDetailDrawer.d.ts.map +1 -1
  27. package/dist/RecordDetailPanel.d.ts +101 -0
  28. package/dist/RecordDetailPanel.d.ts.map +1 -0
  29. package/dist/RecordMetaFooter.d.ts +1 -1
  30. package/dist/RecordMetaFooter.d.ts.map +1 -1
  31. package/dist/RelatedList.d.ts +83 -10
  32. package/dist/RelatedList.d.ts.map +1 -1
  33. package/dist/ThreadedReplies.d.ts +1 -1
  34. package/dist/ThreadedReplies.d.ts.map +1 -1
  35. package/dist/emptiness.d.ts +142 -0
  36. package/dist/emptiness.d.ts.map +1 -0
  37. package/dist/fieldEnrichment.d.ts +44 -4
  38. package/dist/fieldEnrichment.d.ts.map +1 -1
  39. package/dist/headerColor.d.ts +20 -0
  40. package/dist/headerColor.d.ts.map +1 -0
  41. package/dist/index.d.ts +94 -77
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +4883 -5255
  44. package/dist/index.umd.cjs +2 -5
  45. package/dist/renderers/record-activity.d.ts.map +1 -1
  46. package/dist/renderers/record-alert.d.ts +23 -3
  47. package/dist/renderers/record-alert.d.ts.map +1 -1
  48. package/dist/renderers/record-alert.readProps.d.ts +40 -0
  49. package/dist/renderers/record-alert.readProps.d.ts.map +1 -0
  50. package/dist/renderers/record-chatter.d.ts.map +1 -1
  51. package/dist/renderers/record-details.d.ts +2 -0
  52. package/dist/renderers/record-details.d.ts.map +1 -1
  53. package/dist/renderers/record-highlights.d.ts.map +1 -1
  54. package/dist/renderers/record-history.d.ts +1 -1
  55. package/dist/renderers/record-history.d.ts.map +1 -1
  56. package/dist/renderers/record-path.d.ts.map +1 -1
  57. package/dist/renderers/record-quick-actions.d.ts +16 -6
  58. package/dist/renderers/record-quick-actions.d.ts.map +1 -1
  59. package/dist/renderers/record-reference-rail.d.ts +40 -0
  60. package/dist/renderers/record-reference-rail.d.ts.map +1 -1
  61. package/dist/renderers/record-related-list.d.ts +37 -3
  62. package/dist/renderers/record-related-list.d.ts.map +1 -1
  63. package/dist/renderers/recordActivityFeed.d.ts +272 -20
  64. package/dist/renderers/recordActivityFeed.d.ts.map +1 -1
  65. package/dist/renderers/recordComponentAria.d.ts +44 -0
  66. package/dist/renderers/recordComponentAria.d.ts.map +1 -0
  67. package/dist/renderers/relatedListActions.d.ts +102 -0
  68. package/dist/renderers/relatedListActions.d.ts.map +1 -0
  69. package/dist/summaryChipPercent.d.ts +83 -0
  70. package/dist/summaryChipPercent.d.ts.map +1 -0
  71. package/dist/summaryChipRenderers.d.ts +83 -0
  72. package/dist/summaryChipRenderers.d.ts.map +1 -0
  73. package/dist/synth/buildDefaultPageSchema.d.ts +98 -17
  74. package/dist/synth/buildDefaultPageSchema.d.ts.map +1 -1
  75. package/dist/useDetailTranslation.d.ts.map +1 -1
  76. package/dist/useRecordEditable.d.ts +1 -1
  77. package/dist/useRecordEditable.d.ts.map +1 -1
  78. package/package.json +19 -18
  79. package/dist/CommentInput.d.ts +0 -24
  80. package/dist/CommentInput.d.ts.map +0 -1
  81. package/dist/DiffView.d.ts +0 -24
  82. package/dist/DiffView.d.ts.map +0 -1
  83. package/dist/InlineCreateRelated.d.ts +0 -32
  84. package/dist/InlineCreateRelated.d.ts.map +0 -1
  85. package/dist/MentionAutocomplete.d.ts +0 -43
  86. package/dist/MentionAutocomplete.d.ts.map +0 -1
  87. package/dist/PointInTimeRestore.d.ts +0 -28
  88. package/dist/PointInTimeRestore.d.ts.map +0 -1
  89. package/dist/RecordNavigationEnhanced.d.ts +0 -18
  90. package/dist/RecordNavigationEnhanced.d.ts.map +0 -1
  91. package/dist/RelationshipGraph.d.ts +0 -23
  92. package/dist/RelationshipGraph.d.ts.map +0 -1
package/README.md CHANGED
@@ -7,7 +7,9 @@ DetailView plugin for ObjectUI - A comprehensive detail page component with fiel
7
7
  - **Field Grouping/Sections**: Organize fields into logical sections with titles
8
8
  - **Collapsible Sections**: Make sections collapsible to save space
9
9
  - **Tab Navigation**: Organize content into tabs for better UX
10
- - **Related Lists**: Display related records (e.g., contacts for an account)
10
+ - **Related Lists**: Display related records (e.g., contacts for an account) —
11
+ authored as a `record:related_list` block, ⛔ not as a `detail-view` `related`
12
+ array, which is retired (objectui#7997; see **RelatedList** below)
11
13
  - **Action Buttons**: Edit, Delete, and custom action buttons
12
14
  - **Readonly/Edit Mode**: Toggle between view and edit modes
13
15
  - **Back Navigation**: Built-in back button with customizable behavior
@@ -30,16 +32,20 @@ present:
30
32
 
31
33
  ```tsx
32
34
  import { SchemaRenderer, SchemaRendererProvider } from '@object-ui/react';
35
+ import { DetailView } from '@object-ui/plugin-detail';
36
+ import type { DataSource } from '@object-ui/types';
37
+
38
+ declare const dataSource: DataSource;
33
39
 
34
40
  // 1. Injected by an ancestor — the pattern the rest of the family uses, and the
35
41
  // one to reach for when a page renders several data-bound blocks.
36
- <SchemaRendererProvider dataSource={dataSource}>
42
+ const viaProvider = <SchemaRendererProvider dataSource={dataSource}>
37
43
  <SchemaRenderer schema={{ type: 'detail-view', objectName: 'account', resourceId: '42' }} />
38
- </SchemaRendererProvider>
44
+ </SchemaRendererProvider>;
39
45
 
40
46
  // 2. Handed to one placement — what `<DetailView dataSource={…} />` has always
41
47
  // taken, and what the examples below use.
42
- <DetailView schema={{ type: 'detail-view', objectName: 'account', resourceId: '42' }} dataSource={dataSource} />
48
+ const viaProp = <DetailView schema={{ type: 'detail-view', objectName: 'account', resourceId: '42' }} dataSource={dataSource} />;
43
49
  ```
44
50
 
45
51
  Route 1 is new as of objectui#5378: this block used to read the adapter from its
@@ -87,7 +93,11 @@ function ContactDetail() {
87
93
  ### With Sections
88
94
 
89
95
  ```tsx
90
- <DetailView
96
+ import { DetailView } from '@object-ui/plugin-detail';
97
+
98
+ declare const accountData: Record<string, unknown>;
99
+
100
+ const accountDetail = <DetailView
91
101
  schema={{
92
102
  type: 'detail-view',
93
103
  title: 'Account Details',
@@ -117,13 +127,18 @@ function ContactDetail() {
117
127
  ],
118
128
  data: accountData,
119
129
  }}
120
- />
130
+ />;
121
131
  ```
122
132
 
123
- ### With Tabs and Related Lists
133
+ ### With Tabs
124
134
 
125
135
  ```tsx
126
- <DetailView
136
+ import { DetailView } from '@object-ui/plugin-detail';
137
+
138
+ declare const navigate: (url: string) => void;
139
+ declare const deleteAccount: (id: string) => void;
140
+
141
+ const accountDetail = <DetailView
127
142
  schema={{
128
143
  type: 'detail-view',
129
144
  title: 'Account: Acme Corp',
@@ -150,66 +165,220 @@ function ContactDetail() {
150
165
  key: 'activity',
151
166
  label: 'Activity',
152
167
  badge: '12',
168
+ // `record:activity` — the registered Activity Timeline block. Reachable
169
+ // under that exact key and no other; see the note below the block.
170
+ // Its inputs go in `properties`, as the spec declares them.
153
171
  content: {
154
- type: 'activity-timeline',
155
- data: activityData,
172
+ type: 'record:activity',
173
+ properties: { limit: 20, showCompleted: false },
156
174
  },
157
175
  },
158
176
  ],
159
- related: [
160
- {
161
- title: 'Contacts',
162
- type: 'table',
163
- api: '/api/accounts/12345/contacts',
164
- columns: ['name', 'email', 'phone', 'title'],
165
- },
166
- {
167
- title: 'Opportunities',
168
- type: 'table',
169
- api: '/api/accounts/12345/opportunities',
170
- columns: ['name', 'amount', 'stage', 'close_date'],
171
- },
172
- ],
173
177
  showEdit: true,
174
178
  showDelete: true,
175
179
  }}
176
180
  onEdit={() => navigate('/accounts/12345/edit')}
177
181
  onDelete={() => deleteAccount('12345')}
178
182
  onBack={() => navigate('/accounts')}
179
- />
183
+ />;
180
184
  ```
181
185
 
186
+ A tab's `content` is an **SDUI node**, not a private vocabulary: `DetailTabs`
187
+ renders it with `<SchemaRenderer schema={toRenderableSchema(tab.content)} />`,
188
+ so `content.type` is resolved by the component registry and an unregistered
189
+ name paints the `Unknown component type` panel (**OBJUI-001**) instead of a
190
+ component. Author only types this repository registers.
191
+
192
+ Two things about the Activity tab above are worth copying rather than guessing
193
+ (objectui#8114 — it previously taught `type: 'activity-timeline'`, which
194
+ nothing registers):
195
+
196
+ - **The key is `record:activity`, with the namespace spelled out.** The
197
+ registration in `src/index.tsx` passes the bare name under
198
+ `{ namespace: 'record', skipFallback: true }`, and `skipFallback` is what
199
+ stops the bare name from also being claimed globally — so `record:activity`
200
+ resolves and `activity` resolves to nothing. `activity` is additionally a tab
201
+ **key** in the example above; the two are unrelated.
202
+ - **The node declares the feed's filters, not the feed.** Its inputs (`limit`,
203
+ `showCompleted`, `types`, `filterMode`, …) go in its `properties` bag, and
204
+ the block finds its own feed: a mounted discussion context, or a self-fetch
205
+ from `sys_activity` scoped to the record `useRecordContext` binds. Both need
206
+ a record host; a bare `<DetailView>` like the one above mounts neither, so
207
+ there the tab draws the block's empty state ("No activity recorded"). Under
208
+ a record host, such as the console's record page, the same node draws that
209
+ record's activity. `items` (with its `loading` flag) is the host's feed
210
+ slot, not a key of this node: `@object-ui/types` refuses it by name, and
211
+ `data` is not a key this block reads.
212
+
213
+ See **The `record:activity` block** in the plugin-detail guide for its declared
214
+ inputs (`types`, `limit`, `filterMode`, `showCompleted`, …).
215
+
182
216
  ## Schema
183
217
 
184
- The DetailView component accepts a `DetailViewSchema`:
218
+ The DetailView component accepts a `DetailViewSchema`, declared in
219
+ `@object-ui/types`. Import it rather than re-typing it — every key below is
220
+ checked against that declaration, so this example cannot drift from the package:
185
221
 
186
222
  ```typescript
187
- interface DetailViewSchema {
188
- type: 'detail-view';
189
- title?: string;
190
- objectName?: string;
191
- resourceId?: string | number;
192
- api?: string;
193
- data?: any;
194
- sections?: DetailViewSection[];
195
- fields?: DetailViewField[];
196
- tabs?: DetailViewTab[];
197
- related?: RelatedList[];
198
- actions?: ActionSchema[];
199
- showBack?: boolean;
200
- showEdit?: boolean;
201
- showDelete?: boolean;
202
- backUrl?: string;
203
- editUrl?: string;
204
- deleteConfirmation?: string;
205
- loading?: boolean;
206
- header?: SchemaNode;
207
- footer?: SchemaNode;
208
- }
223
+ import type { DetailViewSchema } from '@object-ui/types';
224
+
225
+ const schema: DetailViewSchema = {
226
+ type: 'detail-view',
227
+ title: 'Account Details',
228
+ objectName: 'accounts',
229
+ resourceId: '12345',
230
+ api: '/api/accounts/12345',
231
+ data: undefined,
232
+ // Field name whose value becomes the header title; `title` is the fallback.
233
+ primaryField: 'name',
234
+ // Field values rendered as badges beside the header title.
235
+ summaryFields: ['industry', 'website'],
236
+ layout: 'vertical',
237
+ columns: 2,
238
+ // See the examples above for the shapes these three carry.
239
+ sections: [],
240
+ fields: [],
241
+ tabs: [],
242
+ actions: [],
243
+ // The view's own heading (title, follow-star, copy-id chip); drawn unless
244
+ // `false`. `record:details` sets it from its own `showHeader`.
245
+ showHeader: true,
246
+ showBack: true,
247
+ backUrl: '/accounts',
248
+ showEdit: true,
249
+ editUrl: '/accounts/12345/edit',
250
+ showDelete: true,
251
+ deleteConfirmation: 'Delete this account?',
252
+ loading: false,
253
+ header: { type: 'text', label: 'Custom header' },
254
+ footer: { type: 'text', label: 'Custom footer' },
255
+ };
209
256
  ```
210
257
 
258
+ ### `summaryFields` and non-scalar values
259
+
260
+ A summary chip is a single-line pill, and it formats `currency`, `date`,
261
+ `datetime`, `percent` and the option families itself. Any other value it can
262
+ render as text it renders as text.
263
+
264
+ A **`percent`** chip is formatted by `@object-ui/fields`' `formatPercent` — the
265
+ same call the list cell makes — so one stored value reads the same beside the H1
266
+ as it does in a list: scaled at the storage the field declares (the spec's
267
+ `percentScaleOf`: a fraction unless the field declares a `max` above 1, read
268
+ through `percentCellScale` in `@object-ui/fields`, never guessed from the
269
+ value), rounded to the width `resolveFieldScale` (`@objectstack/spec/data`)
270
+ resolves for the field — its declared `scale`, or the protocol's own width for
271
+ a percent that declares none, which every percent face reads (objectui#9843) —
272
+ and rendered through the display locale's own percent affix rather than an
273
+ appended sign. A stored `1234.5` on a field declaring `max: 100` therefore
274
+ reads `1,235%` in an `en` session, and a stored `1` on a field declaring no
275
+ `max` reads `100%`; both carry the locale's own affix and marks elsewhere. Which stored values that convention moves, and
276
+ what each reads in both places, is re-derived by
277
+ `src/__tests__/summaryChip.percentConvention-9167.test.tsx` rather than listed
278
+ here. The chip's bar keeps the unrounded magnitude, as the list cell's bar does.
279
+
280
+ An **object** value — an expanded lookup payload, an address, a location, a
281
+ file — is drawn by that field's own cell renderer, so the chip shows what the
282
+ same value shows everywhere else on the page: the referenced record's name, the
283
+ formatted postal line, the coordinates, the file name.
284
+
285
+ Fifteen field kinds are the exception, because their renderer does not fit a
286
+ pill: the option families draw a badge (which would nest inside the chip's own),
287
+ `user` draws an avatar, `image` / `avatar` / `signature` draw an image and no
288
+ text at all, and `boolean` / `toggle` / `date` / `datetime` / `repeater` draw a
289
+ "No value" face for a value the chip only exists because the page called
290
+ filled. Those keep a text chip, carrying `@object-ui/fields`' shared value
291
+ coercion — the record's name where the object has one, and `[Object]` where it
292
+ does not. The set and the measurement behind it are
293
+ `src/summaryChipRenderers.ts`.
294
+
211
295
  ## Components
212
296
 
297
+ ### RecordDetailPanel
298
+
299
+ The record overlay's **payload**, with no shell of its own: one
300
+ `InlineEditProvider` session wrapping `DetailView` plus the record-level
301
+ `InlineEditSaveBar`, and the `objectSchema` → typed-field derivation that feeds
302
+ them.
303
+
304
+ All five list-type renderers (`ObjectGrid`, `ObjectTree`, `ObjectGantt`,
305
+ `ObjectKanban`, `ObjectCalendar`) mount this component through
306
+ `NavigationOverlay` from `@object-ui/components`, so an authored
307
+ `navigation.mode` of `drawer` / `modal` / `split` / `popover` means the same
308
+ thing on every view type (objectui#9299). ⛔ Do not re-wrap it in a shell of
309
+ your own — that is exactly the fork this component exists to remove.
310
+
311
+ ```tsx
312
+ import type { ReactNode } from 'react';
313
+ import { NavigationOverlay, useOverlayAnchor } from '@object-ui/components';
314
+ import { RecordDetailPanel } from '@object-ui/plugin-detail';
315
+ import { useNavigationOverlay } from '@object-ui/react';
316
+ import type { DataSource } from '@object-ui/types';
317
+
318
+ declare const dataSource: DataSource;
319
+ declare const objectSchema: { fields?: Record<string, unknown> };
320
+ declare const myView: ReactNode;
321
+ declare function saveField(field: string, value: unknown): Promise<void>;
322
+ declare function deleteRecord(): Promise<void>;
323
+
324
+ function ContactsOverlay() {
325
+ const navigation = useNavigationOverlay({
326
+ navigation: { mode: 'drawer' },
327
+ objectName: 'contacts',
328
+ });
329
+ // `popover` anchors to the element the click landed on; spread
330
+ // `anchorCaptureProps` on your own container, or call `captureAnchor(event)`
331
+ // at a click site that already has one.
332
+ const { anchorRef } = useOverlayAnchor();
333
+
334
+ return (
335
+ <NavigationOverlay
336
+ {...navigation}
337
+ title="Record Detail"
338
+ mainContent={myView}
339
+ popoverAnchorRef={anchorRef}
340
+ >
341
+ {(record) => (
342
+ <RecordDetailPanel
343
+ record={record}
344
+ objectName="contacts"
345
+ recordId={String(record.id)}
346
+ dataSource={dataSource}
347
+ objectSchema={objectSchema}
348
+ onFieldSave={saveField}
349
+ onDelete={deleteRecord}
350
+ onClose={navigation.close}
351
+ />
352
+ )}
353
+ </NavigationOverlay>
354
+ );
355
+ }
356
+ ```
357
+
358
+ Omit `onFieldSave` for a strictly read-only panel; omit `onDelete` for no
359
+ delete action.
360
+
361
+ **Capability is handler presence, not a boolean.** A caller that omits
362
+ `onFieldSave` gets a read-only panel and one that omits `onDelete` gets no
363
+ delete action — which is what lets a row locked through a renderer's `lockField`
364
+ open read-only without the panel knowing what a lock is.
365
+
366
+ `RECORD_OVERLAY_DEFAULT_WIDTH` is exported alongside it and is the single code
367
+ home of the default overlay width repo-wide. ⛔ Do not re-spell the literal at a
368
+ call site.
369
+
370
+ ### RecordDetailDrawer
371
+
372
+ The same payload in the drawer shell — a published convenience wrapper that
373
+ delegates to `NavigationOverlay` in `mode="drawer"`. It no longer owns a
374
+ `Sheet`, a drag-resize implementation, or a chrome header of its own
375
+ (objectui#9299).
376
+
377
+ A width a user had already dragged is persisted by the shell under
378
+ `ov:drawer-width:<objectName>`; a width left by the retired implementation under
379
+ `objectui.drawerWidth.<objectName>` is read once, moved forward and removed, so
380
+ existing widths carry over rather than reset.
381
+
213
382
  ### DetailSection
214
383
 
215
384
  Renders a group of fields with optional collapsing.
@@ -222,6 +391,54 @@ Tab navigation for organizing content into different views.
222
391
 
223
392
  Displays related records in list, grid, or table format.
224
393
 
394
+ > **Retired: `DetailViewSchema.related`** (objectui#7997, ADR-0049
395
+ > enforce-or-remove). Author a `record:related_list` block instead.
396
+ >
397
+ > Until objectui#7997 a `detail-view` node could carry its own `related` array,
398
+ > and this README taught it. That array is retired under ADR-0049
399
+ > enforce-or-remove (maintainer ruling 2026-09-10). It was a second entry to a
400
+ > capability `@objectstack/spec` already governs — the protocol declares no
401
+ > `DetailView` schema at all — so it mirrored nothing and drifted from the
402
+ > renderer it fed: it typed `columns` as `TableColumn` objects while the
403
+ > renderer also accepted bare field names. Authoring it is now **refused by
404
+ > name** on both the TypeScript and the JSON face, ⛔ not silently ignored.
405
+ >
406
+ > Nothing about the rendered result changed: both entries always went through
407
+ > the `RelatedList` component documented here. Only the second door closed.
408
+ >
409
+ > ```tsx
410
+ > import { SchemaRenderer } from '@object-ui/react';
411
+ >
412
+ > const contacts = (
413
+ > <SchemaRenderer
414
+ > schema={{
415
+ > type: 'record:related_list',
416
+ > properties: {
417
+ > objectName: 'contact',
418
+ > relationshipField: 'account_id',
419
+ > title: 'Contacts',
420
+ > columns: ['name', 'email', 'phone'],
421
+ > },
422
+ > }}
423
+ > />
424
+ > );
425
+ > ```
426
+ >
427
+ > ⚠️ `columns` is an array of **field-name strings**, which is what the protocol
428
+ > declares (`RecordRelatedListProps.columns`). The header comes from the related
429
+ > object's field `label` and the cell from the field's type, so a label rename
430
+ > reaches the list for free — the hand-spelled `{ accessorKey, header }` form the
431
+ > retired array taught froze both. `relationshipField` names the field on the
432
+ > related object pointing back at this record, and replaces the retired form's
433
+ > `api` endpoint.
434
+ >
435
+ > ⚠️ The block reads the parent record from the record page's `RecordContext`,
436
+ > so author it on a record page. Placed anywhere it cannot resolve a parent id
437
+ > it scopes to nothing and renders an empty list — which is also what the
438
+ > retired `related` form did whenever it declared `api` without a
439
+ > `referenceField`.
440
+
441
+
225
442
  Related lists are **paged by default**: the `record:related_list` renderer
226
443
  applies the spec default `limit` of **5** when the node doesn't declare one
227
444
  (`@objectstack/spec` `RecordRelatedListProps.limit`, "Number of records to
@@ -245,18 +462,92 @@ value the cell shows — the label this list already resolved, the formula resul
245
462
  the server hydrated — so the affordance stays and orders by that.
246
463
  Passing `data` directly keeps the historical client-side slicing, and typing
247
464
  in the opt-in filter box temporarily falls back to the full-fetch client
248
- pipeline (the contains-filter sweeps every field, which no generic server
249
- filter can express).
465
+ pipeline (the contains-filter sweeps the columns the list shows, which no
466
+ generic server filter can express).
467
+
468
+ A `password` / `secret` column draws `••••••`, and the list flags it
469
+ `masked` on the table it renders (objectui#10657), so the table never copies it
470
+ on Ctrl+C / Cmd+C, shows it as a tooltip, sorts by it from its header, or sizes
471
+ it by its value. The rule is `isMaskedFieldType()` from `@object-ui/fields`,
472
+ read over the column's authored `type` and the object's field type, so an
473
+ authored `type: 'text'` over a `secret` field keeps the flag. While the object
474
+ definition is still loading, or after its read failed, the list cannot tell
475
+ which columns are masked, so it flags every one and draws every cell it would
476
+ draw from that definition as the mask, never as text; it keeps doing so when
477
+ the read failed (objectui#10657). A column with a `cell` of your own draws what
478
+ your `cell` returns.
479
+
480
+ The list's own filter box and sort read the same flag (objectui#10728). The
481
+ opt-in filter box matches a term against the columns the list shows that are
482
+ not masked, so a masked value, and a field no column shows, never keeps a row.
483
+ A masked column gets no sort button on a `list` card, and a sort by it is
484
+ refused (including one the embedded table asks for, which a server-paged list
485
+ would send as `$orderby`). In a list that sorts in the browser, a sort set on a
486
+ column before it was flagged also stops ordering the rows. While the definition
487
+ is loading, or after its read failed, every column is flagged: the filter box
488
+ keeps no row and no sort button is offered. Not covered: on a server-paged
489
+ list, an authored `sort` naming a masked field is still sent to the server as
490
+ `$orderby`, and so is a sort the user set on a column before that column was
491
+ flagged, with each page the list fetches, until the user sorts by another
492
+ column. The column is flagged after the sort only when the `columns` or the
493
+ object of a list already on screen change; `record:related_list` sets both
494
+ once per block.
495
+
496
+ A list that authors `columns` sends a **`$select` projection** on its
497
+ auto-fetch (`objectui#10186`), the one `ListView` and `ObjectGrid` already send
498
+ (`objectui#6898`). It asks for the authored columns that pass the same gates the
499
+ column layer draws through (redaction, the parent key, field-level security),
500
+ plus `id`, the `$expand` roots, and, once the child schema has loaded, the
501
+ fields the row predicates read (the child object's `userActions` Edit/Delete
502
+ overrides, its actions, and the host's row actions), plus the mobile card
503
+ gallery's cover field when declared and readable. A column the principal
504
+ cannot read is dropped once the permission answer has loaded; a request sent
505
+ before that answer can still carry it, the same deferral `ObjectGrid` has.
506
+ If every authored column is denied, the list asks for `id` and its predicate
507
+ operands, not for everything. On a backend that honours `$select`, rows then
508
+ carry only the projected fields, so the opt-in filter box above sweeps those.
509
+ A list **without** authored `columns` derives them from `highlightFields` or
510
+ the field walk, and both choose by the emptiness of the rows fetched. That list sends no projection, and its column layer alone keeps a
511
+ denied field off screen. Against ObjectStack's server the projection is defence
512
+ in depth, because the server already strips denied fields from every row.
250
513
 
251
514
  The node's `filter` (spec `RecordRelatedListProps.filter`, "additional filter
252
515
  criteria") narrows the list beyond the parent relationship: it is
253
- **AND-combined** with `{ [relationshipField]: parentId }`, never substituted for
516
+ **AND-combined** with the parent-relationship condition, never substituted for
254
517
  it, so a related list stays scoped to the record it appears on and an additional
255
- criterion can only ever narrow that set. Authors write it in the spec's own
518
+ criterion can only ever narrow that set. That condition is **not one fixed
519
+ spelling** — it is compiled to match the relationship field's ARITY on the
520
+ child object (`objectui#7299`). A single-valued relationship gets the equality
521
+ form `{ [relationshipField]: parentId }`; a multi-valued one gets the membership
522
+ form `{ [relationshipField]: { $contains: parentId } }`, because the stored
523
+ value is an array of ids and equality would ask whether the whole array *is* one
524
+ id. The arity verdict is `@objectstack/spec/data`'s own `isMultiValueField`,
525
+ never a rule this package keeps locally. Authors write `filter` in the spec's own
256
526
  vocabulary (`[{ field, operator, value }]`); a `dataSource` binding's composed
257
527
  filter (component AND saved view AND binding) lands on the same key. Both are
258
528
  lowered to ObjectQL through the repo's single filter sink, so no second dialect
259
- appears. On the legacy raw-URL fallback path (no `dataSource` adapter, where the
529
+ appears.
530
+
531
+ **A third producer writes that same key: the relationship itself.** A `lookup` /
532
+ `master_detail` field may declare `relatedListFilter` (added to `@objectstack/spec`
533
+ in objectstack#8704 / PR #8955 — see this package's own `@objectstack/spec`
534
+ dependency in `package.json` for the version in use), a canonical Query-DSL
535
+ `FilterCondition` such as
536
+ `{ status: { $ne: 'deleted' } }`. `RecordDetailView` derives the record page's
537
+ related lists from the relationship graph and carries that value onto this node's
538
+ `filter`, so an auto-derived page honours it without anyone authoring a page
539
+ (objectui#4664). It is an authored constraint, not a user-editable suggestion —
540
+ it never becomes a filter-bar row — and it lands on the existing key rather than
541
+ a new one, so all three producers share one read site and one lowering.
542
+
543
+ The **tab badge honours the same composed filter**. `page:tabs` auto-derives a
544
+ Related tab's count from the `record:related_list` nodes underneath it, and that
545
+ probe reads this node's `filter` and composes it with the parent scope through
546
+ the same `mergeFilterNodes` sink the row query uses — one value, sent twice. That
547
+ is what keeps the badge from saying 7 above a list showing 3; it is normative in
548
+ the spec key's own contract text, not a nicety. Counts are cached per
549
+ (object, relationship, parent, **scope**), so a filtered and an unfiltered probe
550
+ over the same relationship never badge each other. On the legacy raw-URL fallback path (no `dataSource` adapter, where the
260
551
  query language is `filter[<field>]=<value>` and cannot carry an operator) a
261
552
  declared filter is refused with a console explanation rather than dropped —
262
553
  answering with more rows than the metadata asked for is the failure this key's
@@ -294,6 +585,16 @@ shared draft (`InlineEditProvider` from `@object-ui/react`), committed by
294
585
  - **Approval lock**: hosts pass `locked` / `lockedHint` to the save bar and
295
586
  gate `InlineEditProvider.canEdit` when the record is approval-locked, so a
296
587
  locked record hides its edit affordances instead of rejecting at Save.
588
+ - **Approval band & recall** (#6464): the band reads `approvalPending` /
589
+ `approvalProgress` from the same provider, and offers its **Recall** button
590
+ only where the click can succeed. Recall is the submitter's lever — the
591
+ server authorizes it on submitter identity and refuses everyone else — so
592
+ hosts that resolve approvals pass `InlineEditProvider.approvalIsSubmitter`.
593
+ It is tri-state on purpose: `false` withdraws the button from a resolved
594
+ non-submitter, `true` keeps it, and **omitting it leaves the button exactly
595
+ as it was** — a host that resolves no approval identity is unchanged rather
596
+ than losing its submitter's only unlock lever. It gates the affordance only;
597
+ the recall endpoint remains the authority on who may actually recall.
297
598
 
298
599
  ## Links
299
600
 
@@ -309,32 +610,63 @@ shared draft (`InlineEditProvider` from `@object-ui/react`), committed by
309
610
  The "Reference Rail" is the right-hand column on the record detail page
310
611
  that surfaces summary cards for related collections (similar to
311
612
  Salesforce's **Related** rail and HubSpot's **About this record**
312
- sidebar). It is rendered by the `record:reference_rail` component and
313
- emits automatically when:
613
+ sidebar). It is rendered by the `record:reference_rail` component. The
614
+ rail is **off by default** — it fans out a collection query per related
615
+ list — so the synth emits it only when the `buildDefaultPageSchema` call
616
+ opts in with `showReferenceRail: true`, and then only when:
314
617
 
315
618
  1. The page is generated by the synth (`buildDefaultPageSchema`) — i.e.
316
619
  no explicit `Page` overrides the object's detail view.
317
- 2. The objectDef declares **≥2 related collections** (lookup/master-detail
318
- inbound fields).
620
+ 2. That same synth call passes **≥2 entries** in its `related` option —
621
+ the count is read off the caller's option, **not** off the objectDef.
319
622
  3. The viewport is **≥ xl (1280 px)** — below that the rail collapses and
320
623
  the **Related** tab keeps full coverage.
321
- 4. The objectDef does **not** opt out via `detail.hideReferenceRail`.
624
+ 4. The synth call does **not** opt out via `hideReferenceRail`.
625
+
626
+ Conditions 1, 2 and 4 are one conjunction inside `buildDefaultPageSchema`,
627
+ quoted here verbatim so that a later change to it shows up as a diff
628
+ against this page: `options.showReferenceRail === true`,
629
+ `!options.hideReferenceRail`, `Array.isArray(options.related)`,
630
+ `options.related.length >= 2`. Condition 3 is not part of that
631
+ expression — it is the `hidden xl:flex` class the synth puts on the
632
+ `aside` region.
633
+
634
+ ### Opting in
635
+
636
+ The opt-in flag and the ≥2 count are both `buildDefaultPageSchema`
637
+ **options**, never objectDef keys:
638
+
639
+ ```ts
640
+ import { buildDefaultPageSchema } from '@object-ui/plugin-detail';
641
+ import type { ObjectDefLike } from '@object-ui/plugin-detail';
642
+
643
+ declare const opportunityDef: ObjectDefLike;
644
+
645
+ const page = buildDefaultPageSchema(opportunityDef, {
646
+ showReferenceRail: true, // required — omit it and no rail is emitted
647
+ related: [
648
+ { objectName: 'quote', relationshipField: 'opportunity' },
649
+ { objectName: 'task', relationshipField: 'related_to_opportunity' },
650
+ ],
651
+ });
652
+ ```
322
653
 
323
654
  When the rail emits, the synth automatically suppresses the **Related**
324
655
  tab so the same information isn't shown twice.
325
656
 
326
- ### Per-object opt-out
657
+ ### Opting out
327
658
 
328
- Add a `detail` block to the objectDef:
659
+ The opt-out is a `buildDefaultPageSchema` **option**, not an objectDef key:
329
660
 
330
661
  ```ts
331
- ObjectSchema.create({
332
- name: 'product',
333
- // …
334
- detail: {
335
- hideReferenceRail: true, // hide the rail; restore the Related tab
336
- hideRelatedTab: true, // (optional) force-hide the Related tab too
337
- },
662
+ import { buildDefaultPageSchema } from '@object-ui/plugin-detail';
663
+ import type { ObjectDefLike } from '@object-ui/plugin-detail';
664
+
665
+ declare const productDef: ObjectDefLike;
666
+
667
+ const page = buildDefaultPageSchema(productDef, {
668
+ hideReferenceRail: true, // hide the rail; restore the Related tab
669
+ hideRelatedTab: true, // (optional) force-hide the Related tab too
338
670
  });
339
671
  ```
340
672
 
@@ -355,7 +687,7 @@ For explicit (non-synth) Pages, add an `aside` region after the `main`
355
687
  region:
356
688
 
357
689
  ```ts
358
- {
690
+ const asideRegion = {
359
691
  name: 'aside',
360
692
  width: 'small',
361
693
  className: 'hidden xl:flex flex-col gap-4',
@@ -372,11 +704,12 @@ region:
372
704
  },
373
705
  },
374
706
  ],
375
- },
707
+ };
376
708
  ```
377
709
 
378
- The renderer reads `entries` from both `schema.entries` and
379
- `schema.properties.entries` so either spec-style or flat authoring works.
710
+ The `entries` go in the node's `properties` bag, as the example shows: that
711
+ bag is the contract, and `objectui validate` refuses a flat `entries` written
712
+ on the node itself, naming `properties.entries`.
380
713
 
381
714
  ## License
382
715
 
@@ -1 +1 @@
1
- {"version":3,"file":"ActivityTimeline.d.ts","sourceRoot":"","sources":["../src/ActivityTimeline.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,MAAM,kBAAkB,GAAG,aAAa,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;AAE/D,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,8CAA8C;IAC9C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yCAAyC;IACzC,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CA6F5D,CAAC"}
1
+ {"version":3,"file":"ActivityTimeline.d.ts","sourceRoot":"","sources":["../src/ActivityTimeline.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAItD,MAAM,MAAM,kBAAkB,GAAG,aAAa,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;AAE/D,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,8CAA8C;IAC9C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yCAAyC;IACzC,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAuHD,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAmG5D,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"CommentAttachment.d.ts","sourceRoot":"","sources":["../src/CommentAttachment.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAqBD,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAgJ9D,CAAC"}
1
+ {"version":3,"file":"CommentAttachment.d.ts","sourceRoot":"","sources":["../src/CommentAttachment.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAqBD,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAkJ9D,CAAC"}
@@ -33,9 +33,25 @@ export default ConcurrentUpdateDialog;
33
33
  * raise the same kind of error) while still recognising the canonical
34
34
  * shape: `code === 'CONCURRENT_UPDATE'` carrying optional
35
35
  * `currentVersion` / `currentRecord` fields.
36
+ *
37
+ * The second limb — `name === 'ConcurrentUpdateError'` — is the deliberate
38
+ * cross-realm discriminator, and it stays: a host that bundles the adapter
39
+ * twice makes `instanceof` fail, leaving the class name as the only thing
40
+ * left to match on. The rationale is recorded above `isConcurrentUpdateError`
41
+ * in `packages/data-objectstack/src/index.ts` and, in full, above
42
+ * `isViewConfigPermissionDeniedError` in the same file.
43
+ *
44
+ * That limb is also why `code` is declared OPTIONAL below (objectui#6421). An
45
+ * error matched by `name` alone carries no `code` at all, so promising
46
+ * `code: 'CONCURRENT_UPDATE'` as a REQUIRED literal handed the caller a
47
+ * property the value does not have: `err.code === 'CONCURRENT_UPDATE'` would
48
+ * type-check and be `undefined` at runtime, with no compiler help. Declared =
49
+ * enforced — the narrowed type states only what BOTH limbs guarantee, which
50
+ * is the two optional payload fields its one consumer already reads. Pinned
51
+ * by `__tests__/ConcurrentUpdateDialog.narrowedCode-6421.test.tsx`.
36
52
  */
37
53
  export declare function isConcurrentUpdateError(err: unknown): err is {
38
- code: 'CONCURRENT_UPDATE';
54
+ code?: 'CONCURRENT_UPDATE';
39
55
  currentVersion?: string;
40
56
  currentRecord?: Record<string, unknown> | null;
41
57
  message?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ConcurrentUpdateDialog.d.ts","sourceRoot":"","sources":["../src/ConcurrentUpdateDialog.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAc/B,MAAM,WAAW,wBAAwB;IACvC,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,YAAY,EAAE,OAAO,CAAC;IACtB,qEAAqE;IACrE,YAAY,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAChD;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAC1C,qEAAqE;IACrE,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,4EAA4E;IAC5E,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,yDAAyD;IACzD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA0BD,eAAO,MAAM,sBAAsB,EAAE,KAAK,CAAC,EAAE,CAAC,2BAA2B,CA4HxE,CAAC;AAEF,eAAe,sBAAsB,CAAC;AAEtC;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI;IAC5D,IAAI,EAAE,mBAAmB,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAIA"}
1
+ {"version":3,"file":"ConcurrentUpdateDialog.d.ts","sourceRoot":"","sources":["../src/ConcurrentUpdateDialog.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAe/B,MAAM,WAAW,wBAAwB;IACvC,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,YAAY,EAAE,OAAO,CAAC;IACtB,qEAAqE;IACrE,YAAY,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAChD;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAC1C,qEAAqE;IACrE,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,4EAA4E;IAC5E,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,yDAAyD;IACzD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA4BD,eAAO,MAAM,sBAAsB,EAAE,KAAK,CAAC,EAAE,CAAC,2BAA2B,CA8HxE,CAAC;AAEF,eAAe,sBAAsB,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI;IAC5D,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAIA"}
@@ -1 +1 @@
1
- {"version":3,"file":"DetailSection.d.ts","sourceRoot":"","sources":["../src/DetailSection.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAqB/B,OAAO,KAAK,EAAE,iBAAiB,IAAI,qBAAqB,EAAkC,MAAM,kBAAkB,CAAC;AA2BnH;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAmBxF;AAED,MAAM,WAAW,oBAAoB;IACnC,oDAAoD;IACpD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,qBAAqB,CAAC;IAC/B,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gEAAgE;IAChE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IACpD;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,6FAA6F;IAC7F,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,oEAAoE;IACpE,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACtC;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA4btD,CAAC"}
1
+ {"version":3,"file":"DetailSection.d.ts","sourceRoot":"","sources":["../src/DetailSection.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAsB/B,OAAO,KAAK,EAAE,iBAAiB,IAAI,qBAAqB,EAAkC,MAAM,kBAAkB,CAAC;AA8BnH;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAmBxF;AAED,MAAM,WAAW,oBAAoB;IACnC,oDAAoD;IACpD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,qBAAqB,CAAC;IAC/B,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gEAAgE;IAChE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IACpD;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,6FAA6F;IAC7F,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,oEAAoE;IACpE,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACtC;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAonBtD,CAAC"}