@object-ui/plugin-detail 17.6.0 → 18.0.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 (102) hide show
  1. package/CHANGELOG.md +7755 -0
  2. package/README.md +557 -71
  3. package/dist/ActivityTimeline.d.ts.map +1 -1
  4. package/dist/CommentAttachment.d.ts.map +1 -1
  5. package/dist/CommentAuthorControls.d.ts +40 -0
  6. package/dist/CommentAuthorControls.d.ts.map +1 -0
  7. package/dist/ConcurrentUpdateDialog.d.ts +16 -1
  8. package/dist/ConcurrentUpdateDialog.d.ts.map +1 -1
  9. package/dist/DetailSection.d.ts.map +1 -1
  10. package/dist/DetailSectionNode.d.ts +97 -0
  11. package/dist/DetailSectionNode.d.ts.map +1 -0
  12. package/dist/DetailView.d.ts +13 -0
  13. package/dist/DetailView.d.ts.map +1 -1
  14. package/dist/HeaderHighlight.d.ts.map +1 -1
  15. package/dist/HistoryTimeline.d.ts +21 -4
  16. package/dist/HistoryTimeline.d.ts.map +1 -1
  17. package/dist/InlineEditSaveBar.d.ts +68 -0
  18. package/dist/InlineEditSaveBar.d.ts.map +1 -1
  19. package/dist/InlineFieldInput.d.ts +80 -7
  20. package/dist/InlineFieldInput.d.ts.map +1 -1
  21. package/dist/ReactionPicker.d.ts.map +1 -1
  22. package/dist/RecordActivityTimeline.d.ts +32 -4
  23. package/dist/RecordActivityTimeline.d.ts.map +1 -1
  24. package/dist/RecordChatterPanel.d.ts +10 -1
  25. package/dist/RecordChatterPanel.d.ts.map +1 -1
  26. package/dist/RecordComments.d.ts +1 -0
  27. package/dist/RecordComments.d.ts.map +1 -1
  28. package/dist/RecordDetailDrawer.d.ts +20 -6
  29. package/dist/RecordDetailDrawer.d.ts.map +1 -1
  30. package/dist/RecordDetailPanel.d.ts +101 -0
  31. package/dist/RecordDetailPanel.d.ts.map +1 -0
  32. package/dist/RecordMetaFooter.d.ts +1 -1
  33. package/dist/RecordMetaFooter.d.ts.map +1 -1
  34. package/dist/RelatedList.d.ts +83 -10
  35. package/dist/RelatedList.d.ts.map +1 -1
  36. package/dist/ThreadedReplies.d.ts +9 -1
  37. package/dist/ThreadedReplies.d.ts.map +1 -1
  38. package/dist/emptiness.d.ts +142 -0
  39. package/dist/emptiness.d.ts.map +1 -0
  40. package/dist/fieldEnrichment.d.ts +46 -5
  41. package/dist/fieldEnrichment.d.ts.map +1 -1
  42. package/dist/headerColor.d.ts +20 -0
  43. package/dist/headerColor.d.ts.map +1 -0
  44. package/dist/index.d.ts +94 -77
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +5693 -5487
  47. package/dist/index.umd.cjs +2 -5
  48. package/dist/recordPageProvenance.d.ts +39 -0
  49. package/dist/recordPageProvenance.d.ts.map +1 -0
  50. package/dist/renderers/record-activity.d.ts.map +1 -1
  51. package/dist/renderers/record-alert.d.ts +23 -3
  52. package/dist/renderers/record-alert.d.ts.map +1 -1
  53. package/dist/renderers/record-alert.readProps.d.ts +40 -0
  54. package/dist/renderers/record-alert.readProps.d.ts.map +1 -0
  55. package/dist/renderers/record-chatter.d.ts.map +1 -1
  56. package/dist/renderers/record-details.d.ts +12 -0
  57. package/dist/renderers/record-details.d.ts.map +1 -1
  58. package/dist/renderers/record-highlights.d.ts +10 -0
  59. package/dist/renderers/record-highlights.d.ts.map +1 -1
  60. package/dist/renderers/record-history.d.ts +1 -1
  61. package/dist/renderers/record-history.d.ts.map +1 -1
  62. package/dist/renderers/record-path.d.ts.map +1 -1
  63. package/dist/renderers/record-quick-actions.d.ts +26 -6
  64. package/dist/renderers/record-quick-actions.d.ts.map +1 -1
  65. package/dist/renderers/record-reference-rail.d.ts +40 -0
  66. package/dist/renderers/record-reference-rail.d.ts.map +1 -1
  67. package/dist/renderers/record-related-list.d.ts +51 -7
  68. package/dist/renderers/record-related-list.d.ts.map +1 -1
  69. package/dist/renderers/recordActivityFeed.d.ts +343 -33
  70. package/dist/renderers/recordActivityFeed.d.ts.map +1 -1
  71. package/dist/renderers/recordBinding.d.ts +180 -0
  72. package/dist/renderers/recordBinding.d.ts.map +1 -0
  73. package/dist/renderers/recordComponentAria.d.ts +44 -0
  74. package/dist/renderers/recordComponentAria.d.ts.map +1 -0
  75. package/dist/renderers/relatedListActions.d.ts +102 -0
  76. package/dist/renderers/relatedListActions.d.ts.map +1 -0
  77. package/dist/summaryChipPercent.d.ts +83 -0
  78. package/dist/summaryChipPercent.d.ts.map +1 -0
  79. package/dist/summaryChipRenderers.d.ts +83 -0
  80. package/dist/summaryChipRenderers.d.ts.map +1 -0
  81. package/dist/synth/buildDefaultPageSchema.d.ts +188 -21
  82. package/dist/synth/buildDefaultPageSchema.d.ts.map +1 -1
  83. package/dist/useDetailTranslation.d.ts.map +1 -1
  84. package/dist/useRecordEditable.d.ts +4 -1
  85. package/dist/useRecordEditable.d.ts.map +1 -1
  86. package/package.json +19 -18
  87. package/dist/CommentInput.d.ts +0 -24
  88. package/dist/CommentInput.d.ts.map +0 -1
  89. package/dist/DiffView.d.ts +0 -24
  90. package/dist/DiffView.d.ts.map +0 -1
  91. package/dist/InlineCreateRelated.d.ts +0 -32
  92. package/dist/InlineCreateRelated.d.ts.map +0 -1
  93. package/dist/MentionAutocomplete.d.ts +0 -43
  94. package/dist/MentionAutocomplete.d.ts.map +0 -1
  95. package/dist/PointInTimeRestore.d.ts +0 -28
  96. package/dist/PointInTimeRestore.d.ts.map +0 -1
  97. package/dist/RecordNavigationEnhanced.d.ts +0 -18
  98. package/dist/RecordNavigationEnhanced.d.ts.map +0 -1
  99. package/dist/RelationshipGraph.d.ts +0 -23
  100. package/dist/RelationshipGraph.d.ts.map +0 -1
  101. package/dist/renderers/PermissionFacetLink.d.ts +0 -38
  102. package/dist/renderers/PermissionFacetLink.d.ts.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { FeedItem, FeedItemType } from '@object-ui/types';
2
- import { FeedFilterMode } from '../RecordActivityTimeline';
2
+ import { FeedFilterMode } from '../RecordActivityTimeline.js';
3
3
  /** Spec default for `RecordActivityProps.limit`. */
4
4
  export declare const DEFAULT_ACTIVITY_LIMIT = 20;
5
5
  /**
@@ -39,33 +39,182 @@ export declare const DEFAULT_ACTIVITY_LIMIT = 20;
39
39
  * ADR-0052 §5b.2 `activityMilestones[].type` is applied verbatim by
40
40
  * plugin-audit's `audit-writers.ts` (`if (milestone.type) activityType =
41
41
  * milestone.type`). That is how `completed` is produced, and it is a general
42
- * door, not a special case.
42
+ * door, not a special case. A row that door wrote also carries
43
+ * `metadata.kind: 'milestone'`, and the mapper overrides this table's
44
+ * `completed` entry for it: see {@link MILESTONE_ACTIVITY_FEED_TYPE}
45
+ * (objectui#12106).
43
46
  *
44
47
  * So `scheduled` is a value that is written, stored and queryable while being
45
48
  * undeclared upstream. Dropping it was not a decision this map made; it was the
46
- * absence of one. Its producer is HotCRM's `schedule_meeting` action
47
- * (`src/actions/global.actions.ts` — `type: EVENT_STATUS === 'held' ?
48
- * 'completed' : 'scheduled'`), registered for `crm_lead`, `crm_contact`,
49
- * `crm_account`, `crm_opportunity` and `crm_case`: the held branch reached the
50
- * timeline, the scheduled branch never did.
49
+ * absence of one. Its producer is HotCRM's activity-action factory
50
+ * (`src/sales/actions/activity-actions.ts`, once `src/actions/global.actions.ts`
51
+ * — `type: EVENT_STATUS === 'held' ? 'completed' : 'scheduled'`), which builds
52
+ * `log_call`, `log_meeting` and `schedule_meeting` for `crm_lead`,
53
+ * `crm_contact`, `crm_account`, `crm_opportunity` and `crm_case`: the held
54
+ * branch reached the timeline, the scheduled branch never did.
51
55
  *
52
56
  * `scheduled` → `event` is the semantic pairing and it is what makes the
53
- * declared `event` feed type reachable at all. Note the two branches of that
54
- * one producer now land at different DEFAULT visibility, which is the intended
55
- * reading: a held meeting is `completed` → `task`, hidden unless
56
- * `showCompleted`; a not-yet-held meeting is `scheduled` → `event`, shown,
57
- * because an upcoming meeting is the part of a timeline you still act on.
58
- *
59
- * Whether the upstream enum should GAIN `scheduled` is a platform ruling, not
60
- * this block's to make — filed as objectstack#11424. Until it is ruled, the
61
- * honest statement of this table is the one the test now pins: the upstream
62
- * declaration PLUS the values a shipped producer measurably writes.
63
- *
64
- * Values outside BOTH groups are still dropped — see
65
- * {@link activityRowToFeedItem}, which now says so out loud instead of
66
- * silently.
57
+ * declared `event` feed type reachable at all. Both branches of that producer
58
+ * show by default: a not-yet-held meeting is `scheduled` → `event`, because an
59
+ * upcoming meeting is the part of a timeline you still act on; a held one is
60
+ * `completed` → `task`, and shows because it is a LOGGED INTERACTION, a `task`
61
+ * item that points at the record it was logged as (objectui#12228, see
62
+ * {@link isLoggedInteraction}). Until objectui#12228 the held branch was hidden
63
+ * unless `showCompleted`.
64
+ *
65
+ * ## The vocabulary is OPEN — ruled. Do NOT restore set-equality.
66
+ *
67
+ * Maintainer ruling of 2026-08-24 (objectstack `88b9d749a`), **direction 4**:
68
+ * `sys_activity.type` is AUTHOR-EXTENSIBLE. The declared select options are the
69
+ * platform's BUILT-IN set, not the column's domain, and the two facts above are
70
+ * why — readonly fields are never validated on write, and ADR-0052 §5b.2
71
+ * forwards an author's `activityMilestones[].type` into the column verbatim.
72
+ *
73
+ * So the relation this table stands in is a SUPERSET, in one direction only:
74
+ *
75
+ * - **map ⊇ built-ins** — every value the platform declares has an entry here,
76
+ * either a feed type or a deliberate `undefined` exclusion. A new built-in
77
+ * with no entry is a gap, and objectui#5969's pin turns red for it.
78
+ * - **NOT built-ins ⊇ map**, and NOT set-equality in either spelling. An
79
+ * author-extended value legitimately exists outside the declaration, so a
80
+ * pin that required the two sets to match would be false by construction.
81
+ *
82
+ * ⛔ A reader who finds no equality check here is looking at a DECISION, not an
83
+ * omission: objectui#5840 removed the old set-equality pin on purpose, because
84
+ * pinning to the closed declaration meant dropping stored rows, and
85
+ * objectui#5969 replaced it with the two-directional pin under this ruling.
86
+ *
87
+ * (`scheduled` — the value #5840 was about — has since been declared upstream
88
+ * by objectstack#11522, superseding objectstack#11424. That is precisely the
89
+ * drift a hand-maintained equality pin cannot survive, and the reason the pin
90
+ * is a superset assertion plus a fallback rather than a census of two groups.)
91
+ *
92
+ * A value outside this table is NO LONGER DROPPED: it renders through
93
+ * {@link UNMAPPED_ACTIVITY_FEED_TYPE}. See {@link activityRowToFeedItem}.
67
94
  */
68
95
  export declare const ACTIVITY_TYPE_TO_FEED_TYPE: Readonly<Record<string, FeedItemType | undefined>>;
96
+ /**
97
+ * The presentation an `sys_activity.type` value outside
98
+ * {@link ACTIVITY_TYPE_TO_FEED_TYPE} renders through.
99
+ *
100
+ * The second half of the direction-4 ruling (2026-08-24, objectstack `88b9d749a`): if
101
+ * the column is author-extensible, then a value this map has never heard of is
102
+ * REAL RECORD ACTIVITY that an author extended the platform with — not a
103
+ * mistake — and dropping it is the objectui#5840 failure mode reappearing for
104
+ * every author who ever writes one. Stored, queryable, invisible.
105
+ *
106
+ * `system` is the generic bucket rather than a new feed type because
107
+ * `FeedItemType` is a CLOSED spec enum owned by `@objectstack/spec` — minting a
108
+ * kind for "we don't know" is a platform change, not this block's.
109
+ *
110
+ * ⚠️ This is a FLOOR under the map, never a substitute for it. objectui#5840
111
+ * rejected a catch-all *offered as the fix* — an unmeasured type rendering as
112
+ * `system` is not the same data as a type someone read and mapped. What makes
113
+ * the catch-all safe here is that it does not stand alone: the superset pin
114
+ * above forces every built-in to keep its OWN presentation, so the fallback can
115
+ * only ever receive values nobody has ruled on yet, and the diagnostic below
116
+ * names each one so somebody can.
117
+ */
118
+ export declare const UNMAPPED_ACTIVITY_FEED_TYPE: FeedItemType;
119
+ /**
120
+ * The feed type a milestone row renders as when its `type` would otherwise land
121
+ * on a feed type `showCompleted` hides (objectui#12106).
122
+ *
123
+ * Today that is a `completed` row that a milestone fired, such as "Case
124
+ * resolved", or a task's own "Task completed" on that task's page. Triage
125
+ * ruled route B: a milestone is not a completed activity, so it shows whatever
126
+ * `showCompleted` says. `showCompleted` keeps its spec meaning ("Include
127
+ * completed activities") for every row without the marker.
128
+ *
129
+ * `system`, not `task` with an exemption, because every other reader of the
130
+ * feed type then stays right with no new member on `FeedItem`:
131
+ *
132
+ * - `filterMode: 'tasks_only'` and `types: ['task']` keep meaning tasks. A
133
+ * case milestone is not one.
134
+ * - {@link COMPLETED_FEED_TYPES} stays exact: `task` is still produced only by
135
+ * completed activities.
136
+ * - The icon and colour are the ones every other author-typed milestone
137
+ * already gets (`escalated` reaches `system` through
138
+ * {@link UNMAPPED_ACTIVITY_FEED_TYPE}), and the ones app-shell's
139
+ * `ActivityItem` surface gives `completed`.
140
+ * - A host feed arrives already mapped, so the rule needs nothing carried
141
+ * past this module.
142
+ */
143
+ export declare const MILESTONE_ACTIVITY_FEED_TYPE: FeedItemType;
144
+ /**
145
+ * Feed kinds an ObjectUI surface produces WITHOUT going through
146
+ * {@link ACTIVITY_TYPE_TO_FEED_TYPE}.
147
+ *
148
+ * Exactly one today: `comment`, built from `sys_comment` rows by app-shell's
149
+ * `RecordDetailView` (the `sys_comment` read, and the two optimistic rows the
150
+ * composer writes) and handed to this block through `DiscussionContext`. It
151
+ * cannot be derived here — app-shell depends on this package, not the other way
152
+ * round — so it is DECLARED, and `feedTypeProducerCensus-5877.test.ts` re-runs
153
+ * the census over the repository so the declaration cannot go stale silently.
154
+ *
155
+ * ## Census, and the pathspec it was taken over (objectui#5877)
156
+ *
157
+ * Every `.ts` / `.tsx` file in the repository that is not a test, at every top
158
+ * level (`packages/`, `apps/`, `examples/`, `scripts/`, `e2e/`), narrowed to the
159
+ * files that mention `FeedItem` at all — you cannot construct one without
160
+ * naming the type — then read for `FeedItemType` literals. 1622 files scanned,
161
+ * 13 mention `FeedItem`, one produces a kind off this map. A census is only as
162
+ * wide as its pathspec, so the pathspec is stated rather than implied, and the
163
+ * test above holds it.
164
+ *
165
+ * ⛔ NOT counted, deliberately: app-shell's `sharedUserFeeds.ts` /
166
+ * `activityItemType.ts` read the same `sys_activity` rows but produce
167
+ * `ActivityItemType` (`create` / `update` / `delete` / `comment` / `system`), a
168
+ * DIFFERENT five-value vocabulary that is not a projection of this one in either
169
+ * direction (objectui#6730 records why converging them costs rows). A row it
170
+ * turns into an `ActivityItem` never becomes a `FeedItem`, so it produces
171
+ * nothing for this feed. `ActivityEntry` in `@object-ui/types` is a third
172
+ * vocabulary again, and has no producer at all.
173
+ *
174
+ * ⚠️ Also NOT counted, and the reason the diagnostic below names it: a HOST can
175
+ * hand this block a feed of its own (`items` on the node, or a
176
+ * `DiscussionContext` it mounts itself). Those items are produced outside this
177
+ * repository, so no census taken here can bound their kinds.
178
+ */
179
+ export declare const FEED_TYPES_PRODUCED_OFF_MAP: readonly FeedItemType[];
180
+ /**
181
+ * Feed kinds this repository has decided NOT to adopt — as opposed to kinds
182
+ * nothing happens to produce.
183
+ *
184
+ * The distinction matters more than the diagnostic that uses it. Reporting a
185
+ * decision as a defect is how a warning channel gets trained out of an author's
186
+ * attention, and these three are decisions: `created` / `deleted` / `shared`
187
+ * rows are mapped to `field_change` ON PURPOSE, because `RecordDetailView` and
188
+ * this block must agree about what a `created` row IS before either can move to
189
+ * the richer kind. Adopting them is a change to that shared map, not to this
190
+ * block. The reasoning is written out at {@link ACTIVITY_TYPE_TO_FEED_TYPE}.
191
+ *
192
+ * ⚠️ This list is a TRANSCRIPTION of that prose, not a derivation, and there is
193
+ * no way to make it one: the decision exists as a comment, and a comment is not
194
+ * machine-readable. Two consequences, both load-bearing:
195
+ *
196
+ * 1. it is only as true as the prose it was copied from, so it is edited
197
+ * together with that prose and never on its own;
198
+ * 2. a kind that is NOT on this list is not thereby known to be an oversight.
199
+ * It is only known to have no producer. Whether somebody decided against it
200
+ * and did not write it down is not recorded anywhere this code can read, and
201
+ * the message below says so rather than guessing.
202
+ *
203
+ * ⛔ Do not extend it by inference. A kind belongs here when a decision about it
204
+ * is written down, not when its absence looks deliberate.
205
+ */
206
+ export declare const DELIBERATELY_UNADOPTED_FEED_TYPES: readonly FeedItemType[];
207
+ /**
208
+ * Every feed kind some ObjectUI surface can put on a record feed.
209
+ *
210
+ * DERIVED from the producers themselves — the map's range, its fallback, and
211
+ * the declared off-map producer above — never a second hand-written list of
212
+ * kinds. Giving an unproduced kind a producer (one new entry in
213
+ * {@link ACTIVITY_TYPE_TO_FEED_TYPE}) therefore retires its diagnostic in the
214
+ * same edit, with nothing else to remember. A hand-kept copy would not, and
215
+ * that copy is exactly what this file's header records the cost of.
216
+ */
217
+ export declare const PRODUCED_FEED_TYPES: ReadonlySet<FeedItemType>;
69
218
  /** A row as `sys_activity` returns it. Loose on purpose: the block reads a
70
219
  * system table it does not own, and an unknown column is not an error. */
71
220
  export interface SysActivityRow {
@@ -80,6 +229,12 @@ export interface SysActivityRow {
80
229
  source_id?: string | number | null;
81
230
  [k: string]: unknown;
82
231
  }
232
+ /** Test seam: forget which `types` entries have already been named. */
233
+ export declare function resetUnrecognisedFeedTypeWarnings(): void;
234
+ /** Test seam: forget which unproduced `types` entries have already been named. */
235
+ export declare function resetUnproducedFeedTypeWarnings(): void;
236
+ /** Test seam: forget which `filterMode` values have already been named. */
237
+ export declare function resetUnrecognisedFilterModeWarnings(): void;
83
238
  /**
84
239
  * Coerce an authored `filterMode` to a value the timeline understands.
85
240
  *
@@ -87,10 +242,40 @@ export interface SysActivityRow {
87
242
  * `<Select>` handed a value with no matching item renders blank and the
88
243
  * dropdown reads as broken. Same posture as objectui#3151 — an unrecognised
89
244
  * filter token is skipped, never turned into a filter that can't match.
245
+ *
246
+ * ## Why that fallback needs a diagnostic (objectui#5891)
247
+ *
248
+ * `'all'` is the WIDEST of the four declared modes — `filterItems` in
249
+ * `RecordActivityTimeline` narrows to ONE feed type for each of the other three
250
+ * and returns the feed untouched for `'all'` — so every unrecognised value
251
+ * lands on the one option that shows the author MORE than they asked for. A
252
+ * near-miss like `comments-only` or `commentsOnly` therefore opens the panel on
253
+ * the unfiltered stream, and the tell is a PLAUSIBLE result (a populated
254
+ * timeline) rather than an empty one that gets investigated. That is the same
255
+ * shape objectui#5841 removed from the sibling `types` sanitiser.
256
+ *
257
+ * The ruling on this card (triage, 2026-08-25) is that the defect is the
258
+ * INVISIBILITY, not the fallback: `filterMode` seeds a control the user can
259
+ * change, and there is no defensible narrower default to fall back to instead.
260
+ * So nothing that renders changes here — what changes is that the fold is now
261
+ * SAID OUT LOUD, once per distinct offending value on each block (see
262
+ * {@link warnOnce}).
263
+ *
264
+ * `block` is the registered name of the block the value was authored on
265
+ * (`record:activity`, `record:chatter`, `record:discussion`). It is REQUIRED,
266
+ * not defaulted: the message is addressed to that block's author, and a default
267
+ * would hand every caller that forgot it some other block's name — the defect
268
+ * objectui#9557 removed.
269
+ *
270
+ * Absent (`undefined` / `null`) is silent: no `filterMode` was authored, which
271
+ * is not a mistake, and a warning about a decision teaches authors to ignore
272
+ * the channel. Only a value the author actually wrote is reported.
273
+ *
274
+ * The declared modes named in the message are read from `@objectstack/spec`'s
275
+ * `FeedFilterMode` at runtime, never re-typed here — see the file header for
276
+ * what a hand copy of a spec enum cost.
90
277
  */
91
- export declare function normalizeFilterMode(value: unknown): FeedFilterMode;
92
- /** Test seam: forget which `types` entries have already been named. */
93
- export declare function resetUnrecognisedFeedTypeWarnings(): void;
278
+ export declare function normalizeFilterMode(value: unknown, block: string): FeedFilterMode;
94
279
  /**
95
280
  * Sanitise an authored `types` allow-list. Narrowing or refusal, never widening.
96
281
  *
@@ -103,6 +288,7 @@ export declare function resetUnrecognisedFeedTypeWarnings(): void;
103
288
  * | `[]` | `[]` | nothing — the author said "no kinds" |
104
289
  * | `['crm_task']` (none known) | `[]` | nothing, plus one diagnostic |
105
290
  * | `['task', 'crm_task']` | `['task']` | the recognised members, plus one diagnostic |
291
+ * | `['approval']` (no producer) | `['approval']` | nothing, plus one diagnostic — the kind is declared and honoured, but nothing emits it (objectui#5877) |
106
292
  *
107
293
  * `undefined` now means "no `types` key was authored" and ONLY that. It used to
108
294
  * mean that OR "everything you authored was dropped", and {@link applyFeedConfig}
@@ -121,13 +307,57 @@ export declare function resetUnrecognisedFeedTypeWarnings(): void;
121
307
  * Membership is read from `@objectstack/spec`'s `FeedItemType` at runtime, never
122
308
  * re-typed here — see the file header for what a hand copy of a spec enum cost.
123
309
  *
124
- * NOT pure: an entry it cannot recognise logs once (see {@link warnOnce}). The
125
- * failure being repaired is invisibility, so the diagnostic is the other half of
126
- * the fix rather than a nicety.
310
+ * NOT pure, on TWO independent channels (see {@link warnOnce}) — the failure
311
+ * being repaired is invisibility in both cases, so the diagnostics are the other
312
+ * half of the fix rather than a nicety:
313
+ *
314
+ * - an entry it cannot recognise logs once (no declared kind matches it);
315
+ * - a RECOGNISED entry that no producer emits logs once as well, on its own
316
+ * channel — see {@link warnUnproducedFeedTypes}. That one changes nothing
317
+ * about the return value: the kind is declared, so it is kept and honoured.
318
+ *
319
+ * Both channels name, and dedupe per, the REQUIRED `block` the list was
320
+ * authored on — see {@link normalizeFilterMode} for why it has no default.
321
+ */
322
+ export declare function normalizeFeedTypes(value: unknown, block: string): FeedItemType[] | undefined;
323
+ /**
324
+ * The ONE resolver for this block's row cap (objectui#10096, objectui#10145).
325
+ *
326
+ * A refused value is dropped for this block's own default.
327
+ *
328
+ * ⛔ Not `Math.floor`, which objectui#10096 replaced: flooring REPAIRED an
329
+ * authored `2.5` into a two-row window and handed it back as a result, so the
330
+ * author got a silently different number than the one they wrote. Refusing and
331
+ * defaulting is the answer the sibling `record:history` gives through
332
+ * `normalizeHistoryLimit`, and the answer objectui#9925 landed at three further
333
+ * read points (all three test `typeof value === 'number'` first, as this does).
334
+ *
335
+ * `NaN` and both infinities are refused (not integers); `0` and negatives are
336
+ * refused (not positive); `undefined` is the unauthored case and resolves to
337
+ * the default without being a refusal.
338
+ *
339
+ * ⚠️ FAIL-SOFT on purpose: throwing would take out a record page over one
340
+ * declaration. It is NOT silent — the renderers that call this state every
341
+ * refusal through `describeRefusedFeedLimit` (objectui#10097 ruled "always
342
+ * warn" for the family; objectui#10145 extends the refused set to non-numbers).
343
+ * This function stays pure so it can run on every render.
344
+ *
345
+ * ⭐ Shared with `record:chatter` / `record:discussion`, which resolve
346
+ * `feed.limit` through this same function — `RecordChatterProps.feed` is
347
+ * `RecordActivityProps`, so it is the same declared member and the same
348
+ * refusal.
127
349
  */
128
- export declare function normalizeFeedTypes(value: unknown): FeedItemType[] | undefined;
129
- /** Coerce `limit` to a positive integer, falling back to the spec default. */
130
350
  export declare function normalizeLimit(value: unknown): number;
351
+ /**
352
+ * The diagnostic half of the row-cap refusal for the blocks that resolve
353
+ * through `normalizeLimit` (`record:activity`, `record:chatter`,
354
+ * `record:discussion`). `null` means "nothing to say": an absent `limit`
355
+ * (`undefined` / `null`) is not a mistake, and a usable one is not either.
356
+ *
357
+ * Callers fire it from an effect keyed on the declaration — the channel
358
+ * objectui#9925 uses at its three read points — never from render.
359
+ */
360
+ export declare function describeRefusedFeedLimit(block: string, authored: unknown): string | null;
131
361
  /**
132
362
  * Timestamp of a `sys_activity` row.
133
363
  *
@@ -139,13 +369,49 @@ export declare function normalizeLimit(value: unknown): number;
139
369
  export declare function activityTimestamp(row: SysActivityRow): string;
140
370
  /** Test seam: forget which unknown types have already been warned about. */
141
371
  export declare function resetUnknownActivityTypeWarnings(): void;
372
+ /**
373
+ * The name of whoever acted on a `sys_activity` row, or `null` when the row
374
+ * names nobody this viewer can name (objectui#12067). One reading for every
375
+ * surface that shows these rows: `record:history`, and through
376
+ * {@link activityRowToFeedItem} the `record:activity` block and the console
377
+ * record page's merged feed.
378
+ *
379
+ * 1. `actor_name`, when it is non-blank. It is the snapshot the writer took at
380
+ * the time of the action, so it wins even over the user's current name.
381
+ * 2. `actor_id`, a lookup to `sys_user`. A read that passes
382
+ * `$expand: ['actor_id']` gets the user's record in place of the id, and
383
+ * that record's `name` (`sys_user`'s declared name field) names the actor.
384
+ * The engine expands every row of a page in one batched read. This is the
385
+ * row the `@objectstack/*` 17.7.0 writer leaves: `actor_id` set and no
386
+ * `actor_name` (objectstack#22510 fills `actor_name` from then on).
387
+ * 3. `null`, so the caller shows its existing fallback. That covers a row
388
+ * with neither, and an `actor_id` the engine kept as the bare id because
389
+ * this viewer may not read that user or the user is gone. A raw id is
390
+ * never shown as a name.
391
+ */
392
+ export declare function activityActorName(row: SysActivityRow): string | null;
142
393
  /**
143
394
  * One `sys_activity` row → one {@link FeedItem}, or `null` when the row is not
144
395
  * record activity (see {@link ACTIVITY_TYPE_TO_FEED_TYPE}).
145
396
  *
146
- * Two different `null`s, and the difference is the point: a type the table
147
- * maps to `undefined` is a deliberate exclusion and returns quietly; a type the
148
- * table does not contain at all is an unmapped producer and says so once.
397
+ * Three outcomes, and the difference between the last two is the whole of the
398
+ * direction-4 ruling (2026-08-24, objectstack `88b9d749a`):
399
+ *
400
+ * - a type mapped to a feed type renders with THAT presentation;
401
+ * - a type the table maps to `undefined` is a DELIBERATE exclusion — `null`,
402
+ * quietly, because a warning about a decision teaches authors to ignore the
403
+ * channel;
404
+ * - a type the table does not contain at all is an AUTHOR-EXTENDED value under
405
+ * the ruled open vocabulary. It renders through
406
+ * {@link UNMAPPED_ACTIVITY_FEED_TYPE} and says so once. It used to return
407
+ * `null` here, which made every extended value invisible — the same outcome
408
+ * objectui#5840 was filed for, reached by a different route.
409
+ *
410
+ * One override sits on top of the first outcome (objectui#12106): a row whose
411
+ * type maps to a feed type `showCompleted` hides, but which carries the
412
+ * milestone marker, renders as {@link MILESTONE_ACTIVITY_FEED_TYPE}. A
413
+ * milestone is not a completed activity. A row without the marker is mapped
414
+ * exactly as before.
149
415
  */
150
416
  export declare function activityRowToFeedItem(row: SysActivityRow, systemActorLabel: string): FeedItem | null;
151
417
  /** The subset of `RecordActivityProps` that decides which items survive. */
@@ -173,8 +439,52 @@ export interface AppliedFeed {
173
439
  * `pageSize` is the effective window (`limit` on first render, grown by
174
440
  * "Load more"); it is separate from `config.limit` so paging does not have to
175
441
  * rewrite the authored config.
442
+ *
443
+ * `block` is the registered name of the block whose config this is; the
444
+ * `types` diagnostics are addressed to it (objectui#9557, see
445
+ * {@link normalizeFilterMode}).
176
446
  */
177
- export declare function applyFeedConfig(items: readonly FeedItem[], config: FeedConfigFilters, pageSize: number): AppliedFeed;
447
+ export declare function applyFeedConfig(items: readonly FeedItem[], config: FeedConfigFilters, pageSize: number, block: string): AppliedFeed;
178
448
  /** Sort a feed chronologically and de-duplicate by id (feeds are append-only). */
179
449
  export declare function mergeFeedItems(...groups: readonly FeedItem[][]): FeedItem[];
450
+ /**
451
+ * Whether a rejected feed read — `sys_activity` or `sys_comment` — was REFUSED
452
+ * (objectui#11195): the server answered, and the answer was "not for you".
453
+ *
454
+ * A refused read and an empty read are different answers about the record, and
455
+ * the panel used to give both the same one. Every failure on the feed landed on
456
+ * `No activity recorded` / `No comments yet`, so a member the server refused
457
+ * read a permission wall as a record where nothing had happened. The panel now
458
+ * shows a no-permission state for a refused read instead, and keeps the empty
459
+ * state for a read that answered with zero rows.
460
+ *
461
+ * The verdict is `classifyLoadError`'s (`@object-ui/react`), the read
462
+ * classifier `ListView`'s error panel and `RecordAttachmentsPanel` already
463
+ * share. Nothing here reads a status or a code of its own. Refused means the
464
+ * two kinds that are about the caller:
465
+ *
466
+ * - `forbidden`: 403, or a `PERMISSION_DENIED` / `FORBIDDEN` envelope;
467
+ * - `unauthorized`: 401, or `UNAUTHORIZED` / `UNAUTHENTICATED`.
468
+ *
469
+ * ⚠️ The 401 half is a deliberate difference from `RecordAttachmentsPanel`,
470
+ * whose `denied` state does not claim a 401. The triage on objectui#11195 ruled
471
+ * it for this panel: any refused read (401 / 403, or a permission envelope)
472
+ * shows the no-permission state.
473
+ *
474
+ * NOT refused, so these keep today's empty state:
475
+ *
476
+ * - `api-disabled` (`OBJECT_API_DISABLED` 404 / `OBJECT_API_METHOD_NOT_ALLOWED`
477
+ * 405): a property of the object's metadata, not of the caller, and no
478
+ * permission grant changes it;
479
+ * - a bare 404 (`sys_activity` on a deployment without the audit plugin).
480
+ * The ObjectStack adapter already answers that as zero rows; another
481
+ * adapter's rejection classifies as `network` and lands here;
482
+ * - `rejected` (400) and `network` (a 5xx, an unreachable server). Those are
483
+ * failures, not refusals. The panel has no error state of its own, so they
484
+ * still render the empty state.
485
+ *
486
+ * ⛔ A refusal is not retried. A refused read is the same answer on every
487
+ * retry, so the callers write it into state and do not re-issue the read.
488
+ */
489
+ export declare function isRefusedFeedRead(err: unknown): boolean;
180
490
  //# sourceMappingURL=recordActivityFeed.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"recordActivityFeed.d.ts","sourceRoot":"","sources":["../../src/renderers/recordActivityFeed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAKhE,oDAAoD;AACpD,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AACH,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,CAAC,CAazF,CAAC;AAgBF;2EAC2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACnC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAIlE;AAkCD,uEAAuE;AACvE,wBAAgB,iCAAiC,IAAI,IAAI,CAExD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,EAAE,GAAG,SAAS,CAuC7E;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGrD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,CAM7D;AAiCD,4EAA4E;AAC5E,wBAAgB,gCAAgC,IAAI,IAAI,CAEvD;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,cAAc,EACnB,gBAAgB,EAAE,MAAM,GACvB,QAAQ,GAAG,IAAI,CAoBjB;AAED,4EAA4E;AAC5E,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,2EAA2E;IAC3E,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,MAAM,EAAE,iBAAiB,EACzB,QAAQ,EAAE,MAAM,GACf,WAAW,CAyCb;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,GAAG,MAAM,EAAE,SAAS,QAAQ,EAAE,EAAE,GAAG,QAAQ,EAAE,CAU3E"}
1
+ {"version":3,"file":"recordActivityFeed.d.ts","sourceRoot":"","sources":["../../src/renderers/recordActivityFeed.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,180 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * The ONE-RECORD binding of the record blocks (objectui#12294).
9
+ *
10
+ * `PageComponentSchema.dataSource` is the spec's per-element data binding, and
11
+ * the spec declares it on every page component ("Overrides the page-level
12
+ * object context for this block"). `ElementDataSourceGate` (`@object-ui/react`)
13
+ * honours it for the LIST-shaped blocks by mapping the binding onto keys the
14
+ * block then queries with. The record blocks query nothing: they read the row
15
+ * the record page put in `RecordContextProvider`. So on an `app` page, with no
16
+ * record page around them, `record:details`, `record:highlights` and
17
+ * `record:quick_actions` drew an empty box or "no actions configured" even
18
+ * with a binding that returns one row.
19
+ *
20
+ * This module is the one-record consumer those three share, and
21
+ * {@link recordBindingBlock} is the one way a record block takes it. The gate
22
+ * still resolves the binding (the named view, the AND-combined filter, the
23
+ * sort, and every configuration panel the family draws), so a record block's
24
+ * binding means exactly what it means on a list block. What this adds is the
25
+ * last hop: read the bound set, take its FIRST row, and provide that row
26
+ * through the existing `RecordContextProvider` — so the block bodies are
27
+ * unchanged and there is no second rendering path inside a block.
28
+ *
29
+ * ## Semantics of the bound set (the card's, verbatim in intent)
30
+ *
31
+ * - 0 rows → the block's authored `emptyState` when it declares one (see
32
+ * "The authored empty state" below). Without one, the block is mounted under
33
+ * a record context that names the bound object and NO record, so each block
34
+ * draws what it draws for a record that is not there: `record:details` its
35
+ * "record not found" state, `record:highlights` nothing,
36
+ * `record:quick_actions` its empty bar. A row the caller may not read never
37
+ * comes back from the server, and a read the server refuses outright
38
+ * (`classifyLoadError` → `forbidden`) is treated the same, so "absent" and
39
+ * "not yours to read" are indistinguishable — and the authored empty state
40
+ * is drawn for both.
41
+ * - 1 row → that record.
42
+ * - more than one row → the FIRST by the binding's `sort`. Only one row is
43
+ * ever asked for (`$top: 1`): the first row of the authored `limit` and the
44
+ * first row of a one-row read are the same row under the same `sort`.
45
+ * `objectui validate` warns on a record block whose binding has no
46
+ * `limit: 1`, so an author says which one they mean.
47
+ *
48
+ * The row is read with the adapter the page already uses, so under the
49
+ * caller's identity: nothing here widens what the server serves.
50
+ *
51
+ * ## The authored empty state (objectui#12932)
52
+ *
53
+ * The spec declares `emptyState` on the three bound record blocks
54
+ * (`RecordDetailsProps`, `RecordHighlightsProps`, `RecordQuickActionsProps`;
55
+ * objectstack#24266), the list view's own `EmptyStateSchema` by reference:
56
+ * `{ title, message, icon, link }`. It is drawn on ONE condition, the spec's:
57
+ * the block carries a `dataSource` binding and the settled read answered no
58
+ * row. {@link BoundRecordEmptyState} draws it IN PLACE of the block body, with
59
+ * the pieces `ObjectGrid` draws its authored empty state with —
60
+ * `DataEmptyState`, `resolveIcon` for the `icon`, the spec's own
61
+ * `resolveI18nLabel` for `title` and `message` against the display locale,
62
+ * and the one shared `EmptyStateLink` for `link` — so one key draws one
63
+ * picture on every surface that takes it.
64
+ *
65
+ * It is NOT drawn while the read is in flight (the loading state), for a read
66
+ * that failed (the error panel stays the error panel), for a bound row that is
67
+ * there but leaves the block nothing to show, or on an unbound block: an
68
+ * unbound block reads the record page's record and never reaches this module's
69
+ * read. A block that declares none keeps its own no-record state above.
70
+ *
71
+ * ## The record page path is untouched
72
+ *
73
+ * {@link BoundRecordScope} renders its children AS THEY ARE when the block
74
+ * carries no binding: no provider, no element, no fetch. A record page's own
75
+ * `RecordContextProvider` therefore still reaches the block, byte-identically.
76
+ * With a binding, the binding wins, as the spec says it does: the bound record
77
+ * shadows the page record for this block only.
78
+ *
79
+ * ## Freshness: the data-invalidation bus (objectstack#4444's bar)
80
+ *
81
+ * A fetching record context that does not listen to the bus shows a STALE
82
+ * record that looks correct after a write elsewhere on the page — harder to
83
+ * notice than an empty one. So the read is keyed on `useDataInvalidation` for
84
+ * the bound OBJECT, the same bus `RecordDetailView` keys its record read on.
85
+ * Object-scoped, not row-scoped, because the binding is a query: a write to any
86
+ * row of the object can change which row is first. A refetch replaces the row
87
+ * in place (no remount), and an equal answer is not republished as a new object
88
+ * (AGENTS.md #10).
89
+ *
90
+ * ## What a bound block does NOT join
91
+ *
92
+ * A bound block sits inside the record page's providers when it is placed on a
93
+ * record page, and two of them are about THAT page's record:
94
+ *
95
+ * - the page's inline-edit session (`InlineEditProvider`): a bound
96
+ * `record:details` or `record:highlights` joining it would put the bound
97
+ * record's fields into the page record's draft, and the page's Save bar
98
+ * would write them to the page record. The bound scope mounts a read-only
99
+ * session instead (`canEdit: false`), which is also what an `app` page,
100
+ * with no session at all, already gives these blocks;
101
+ * - the page's highlight-field registry (`HighlightFieldsProvider`): a bound
102
+ * `record:highlights` would otherwise hide fields from the PAGE record's
103
+ * `record:details`. The bound scope gets its own registry.
104
+ *
105
+ * ## The PARENT binding of `record:related_list` ({@link ParentRecordBinding})
106
+ *
107
+ * `record:related_list` is the one record block with two bindings (ruling B on
108
+ * objectui#12296): its node-level `dataSource` is the CHILD rows the list
109
+ * draws (objectstack#6953), and the spec row's `parentDataSource` is the record
110
+ * the list hangs off. The parent binding is read here, by the same one-record
111
+ * read: the same gate, the same first row by `sort`, the same invalidation bus,
112
+ * provided through the same `RecordContextProvider` the related-list body
113
+ * already reads. It differs from the three blocks above in ONE place, and the
114
+ * spec's describe is why: no row is a LOUD ABSENCE drawn in the block's place,
115
+ * never an empty record context — under one the list would query nothing and
116
+ * draw an empty list, which reads as "this record has no related records", a
117
+ * wrong fact in the silent direction.
118
+ */
119
+ import * as React from 'react';
120
+ /**
121
+ * Wrap a record block's body so its `dataSource` binding supplies its record,
122
+ * and declare the key through the seam.
123
+ *
124
+ * The one way a record block takes the binding, so a fourth one cannot take it
125
+ * differently: `ElementDataSourceGate` resolves the binding (and draws the
126
+ * family's panels for an unresolvable view, a refused filter, or no adapter),
127
+ * {@link BoundRecordScope} reads and provides the first row, and
128
+ * `elementDataSourceBlock` makes the registration publish the injected
129
+ * `dataSource` input — from `@object-ui/core`, the import source
130
+ * `check:element-data-source-declaration` holds every call site to.
131
+ *
132
+ * `requiresDataSource` is set for a BOUND block only: a bound block with no
133
+ * adapter has nothing to read its row with, and says so through the family's
134
+ * no-data-source panel. An unbound block is the record page path, left as it was.
135
+ *
136
+ * @param Body The block's renderer, unchanged: it reads `useRecordContext()`.
137
+ * @param blockKey The registry key (`record:details`, …); the panel test ids are
138
+ * derived from it the way `record-related-list` spells its own.
139
+ */
140
+ export declare function recordBindingBlock<RendererProps extends {
141
+ schema?: unknown;
142
+ }>(Body: React.ComponentType<RendererProps>, blockKey: string): React.FC<RendererProps>;
143
+ export interface ParentRecordBindingProps {
144
+ /**
145
+ * The PARENT binding as authored — `record:related_list`'s
146
+ * `parentDataSource`, the spec's `ElementDataSourceSchema` by reference.
147
+ * Anything that is not a binding the gate would resolve (absent included)
148
+ * leaves the children exactly as they are.
149
+ */
150
+ binding: unknown;
151
+ /** The block's registry key, named in the no-adapter panel. */
152
+ blockKey: string;
153
+ /** `data-testid` stem for this scope's states (`<stem>-loading-record`, …). */
154
+ testId: string;
155
+ /** Heading of the panels a binding that cannot be read draws. */
156
+ errorTitle?: string;
157
+ /** What the block draws in its own place when no row answers: the loud absence. */
158
+ absent: React.ReactNode;
159
+ children: React.ReactNode;
160
+ }
161
+ /**
162
+ * The parent record of a block, from a binding of its own (ruling B on
163
+ * objectui#12296, the spec's `RecordRelatedListProps.parentDataSource`).
164
+ *
165
+ * Unbound → the children as they are, so the record page's own context still
166
+ * reaches the block, byte-identically. Bound → the binding is resolved by
167
+ * `ElementDataSourceGate` (a named view, the AND-combined filter, the sort, and
168
+ * the family's panels for a view that does not resolve, a refused filter, or no
169
+ * adapter), its FIRST row is read by {@link BoundRecordProvider} — the one
170
+ * read the three one-record blocks take, keyed on the same invalidation bus —
171
+ * and provided through `RecordContextProvider` as the children's record. No row
172
+ * draws `absent` instead of the children.
173
+ *
174
+ * The gate is handed the parent binding ALONE, as a node's `dataSource`, and
175
+ * never the block's own schema: with the schema, the gate's filter and sort
176
+ * mapping would compose the block's own `filter` and `sort` — the CHILD list's
177
+ * — into the parent's query.
178
+ */
179
+ export declare function ParentRecordBinding({ binding, blockKey, testId, errorTitle, absent, children, }: ParentRecordBindingProps): React.ReactElement;
180
+ //# sourceMappingURL=recordBinding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordBinding.d.ts","sourceRoot":"","sources":["../../src/renderers/recordBinding.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqHG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA+C/B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,SAAS;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAC3E,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,aAAa,CAAC,EACxC,QAAQ,EAAE,MAAM,GACf,KAAK,CAAC,EAAE,CAAC,aAAa,CAAC,CA0BzB;AA6FD,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,QAAQ,EACR,MAAM,EACN,UAAU,EACV,MAAM,EACN,QAAQ,GACT,EAAE,wBAAwB,GAAG,KAAK,CAAC,YAAY,CAgC/C"}