@object-ui/plugin-detail 17.5.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 +7216 -0
  2. package/README.md +442 -74
  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 +94 -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 +181 -76
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +5259 -5567
  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 +83 -13
  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 +20 -3
  58. package/dist/renderers/record-quick-actions.d.ts.map +1 -1
  59. package/dist/renderers/record-reference-rail.d.ts +42 -17
  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 +352 -13
  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 +20 -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
@@ -1,25 +1,189 @@
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
  /**
6
6
  * `sys_activity.type` → `FeedItem.type`.
7
7
  *
8
- * Identical to the map `RecordDetailView` uses when it merges `sys_activity`
9
- * into the discussion feed, deliberately: one table, one reading. Two renderers
10
- * that disagree about what a `created` row IS would put the same record's
11
- * history under two different icons depending on which block an author reached
12
- * for, so the richer `record_create` / `record_delete` / `sharing` feed types
13
- * the spec offers are NOT used here — adopting them is a change to that shared
14
- * map, not to this block.
8
+ * Meant to be one reading with the map `RecordDetailView` uses when it merges
9
+ * `sys_activity` into the discussion feed. Two renderers that disagree about what a `created` row IS would put
10
+ * the same record's history under two different icons depending on which block
11
+ * an author reached for, so the richer `record_create` / `record_delete` /
12
+ * `sharing` feed types the spec offers are NOT used here — adopting them is a
13
+ * change to that shared map, not to this block.
14
+ *
15
+ * ⚠️ That copy is a hand-written literal in `app-shell`, not an import of this
16
+ * one, and nothing fails when the two disagree — so the `scheduled` entry below
17
+ * is currently present here and absent there. Tracked as objectui#5878; fixing
18
+ * it means the second copy reading this export, which is a different package's
19
+ * surface than this card's.
15
20
  *
16
21
  * `commented` / `mentioned` map to nothing because their content lives in
17
22
  * `sys_comment` (with reactions and threading attached) — a host that has both
18
23
  * merges the comment rows, and the block on its own has no comment write path
19
24
  * to pair them with. `login` / `logout` are account events, not record
20
25
  * activity.
26
+ *
27
+ * ## Two vocabularies, not one (objectui#5840)
28
+ *
29
+ * The keys were originally set-equal to plugin-audit's declared
30
+ * `sys_activity.type` select options, and the test pinned exactly that. They no
31
+ * longer are, because those options are **not** what the column stores:
32
+ *
33
+ * - Every field on `sys_activity` is `readonly: true`, and objectql's
34
+ * `validateRecord` skips readonly fields on both the insert and the update
35
+ * branch. The eleven-value enum is therefore documentation, not a contract —
36
+ * an undeclared value is written silently (measured upstream by
37
+ * plugin-audit's own `sys-activity-type-vocabulary.test.ts`).
38
+ * - The platform itself forwards author-declared values into the column:
39
+ * ADR-0052 §5b.2 `activityMilestones[].type` is applied verbatim by
40
+ * plugin-audit's `audit-writers.ts` (`if (milestone.type) activityType =
41
+ * milestone.type`). That is how `completed` is produced, and it is a general
42
+ * door, not a special case.
43
+ *
44
+ * So `scheduled` is a value that is written, stored and queryable while being
45
+ * 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.
51
+ *
52
+ * `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
+ * ## The vocabulary is OPEN — ruled. Do NOT restore set-equality.
60
+ *
61
+ * Maintainer ruling of 2026-08-24 (objectstack `88b9d749a`), **direction 4**:
62
+ * `sys_activity.type` is AUTHOR-EXTENSIBLE. The declared select options are the
63
+ * platform's BUILT-IN set, not the column's domain, and the two facts above are
64
+ * why — readonly fields are never validated on write, and ADR-0052 §5b.2
65
+ * forwards an author's `activityMilestones[].type` into the column verbatim.
66
+ *
67
+ * So the relation this table stands in is a SUPERSET, in one direction only:
68
+ *
69
+ * - **map ⊇ built-ins** — every value the platform declares has an entry here,
70
+ * either a feed type or a deliberate `undefined` exclusion. A new built-in
71
+ * with no entry is a gap, and objectui#5969's pin turns red for it.
72
+ * - **NOT built-ins ⊇ map**, and NOT set-equality in either spelling. An
73
+ * author-extended value legitimately exists outside the declaration, so a
74
+ * pin that required the two sets to match would be false by construction.
75
+ *
76
+ * ⛔ A reader who finds no equality check here is looking at a DECISION, not an
77
+ * omission: objectui#5840 removed the old set-equality pin on purpose, because
78
+ * pinning to the closed declaration meant dropping stored rows, and
79
+ * objectui#5969 replaced it with the two-directional pin under this ruling.
80
+ *
81
+ * (`scheduled` — the value #5840 was about — has since been declared upstream
82
+ * by objectstack#11522, superseding objectstack#11424. That is precisely the
83
+ * drift a hand-maintained equality pin cannot survive, and the reason the pin
84
+ * is a superset assertion plus a fallback rather than a census of two groups.)
85
+ *
86
+ * A value outside this table is NO LONGER DROPPED: it renders through
87
+ * {@link UNMAPPED_ACTIVITY_FEED_TYPE}. See {@link activityRowToFeedItem}.
21
88
  */
22
89
  export declare const ACTIVITY_TYPE_TO_FEED_TYPE: Readonly<Record<string, FeedItemType | undefined>>;
90
+ /**
91
+ * The presentation an `sys_activity.type` value outside
92
+ * {@link ACTIVITY_TYPE_TO_FEED_TYPE} renders through.
93
+ *
94
+ * The second half of the direction-4 ruling (2026-08-24, objectstack `88b9d749a`): if
95
+ * the column is author-extensible, then a value this map has never heard of is
96
+ * REAL RECORD ACTIVITY that an author extended the platform with — not a
97
+ * mistake — and dropping it is the objectui#5840 failure mode reappearing for
98
+ * every author who ever writes one. Stored, queryable, invisible.
99
+ *
100
+ * `system` is the generic bucket rather than a new feed type because
101
+ * `FeedItemType` is a CLOSED spec enum owned by `@objectstack/spec` — minting a
102
+ * kind for "we don't know" is a platform change, not this block's.
103
+ *
104
+ * ⚠️ This is a FLOOR under the map, never a substitute for it. objectui#5840
105
+ * rejected a catch-all *offered as the fix* — an unmeasured type rendering as
106
+ * `system` is not the same data as a type someone read and mapped. What makes
107
+ * the catch-all safe here is that it does not stand alone: the superset pin
108
+ * above forces every built-in to keep its OWN presentation, so the fallback can
109
+ * only ever receive values nobody has ruled on yet, and the diagnostic below
110
+ * names each one so somebody can.
111
+ */
112
+ export declare const UNMAPPED_ACTIVITY_FEED_TYPE: FeedItemType;
113
+ /**
114
+ * Feed kinds an ObjectUI surface produces WITHOUT going through
115
+ * {@link ACTIVITY_TYPE_TO_FEED_TYPE}.
116
+ *
117
+ * Exactly one today: `comment`, built from `sys_comment` rows by app-shell's
118
+ * `RecordDetailView` (the `sys_comment` read, and the two optimistic rows the
119
+ * composer writes) and handed to this block through `DiscussionContext`. It
120
+ * cannot be derived here — app-shell depends on this package, not the other way
121
+ * round — so it is DECLARED, and `feedTypeProducerCensus-5877.test.ts` re-runs
122
+ * the census over the repository so the declaration cannot go stale silently.
123
+ *
124
+ * ## Census, and the pathspec it was taken over (objectui#5877)
125
+ *
126
+ * Every `.ts` / `.tsx` file in the repository that is not a test, at every top
127
+ * level (`packages/`, `apps/`, `examples/`, `scripts/`, `e2e/`), narrowed to the
128
+ * files that mention `FeedItem` at all — you cannot construct one without
129
+ * naming the type — then read for `FeedItemType` literals. 1622 files scanned,
130
+ * 13 mention `FeedItem`, one produces a kind off this map. A census is only as
131
+ * wide as its pathspec, so the pathspec is stated rather than implied, and the
132
+ * test above holds it.
133
+ *
134
+ * ⛔ NOT counted, deliberately: app-shell's `sharedUserFeeds.ts` /
135
+ * `activityItemType.ts` read the same `sys_activity` rows but produce
136
+ * `ActivityItemType` (`create` / `update` / `delete` / `comment` / `system`), a
137
+ * DIFFERENT five-value vocabulary that is not a projection of this one in either
138
+ * direction (objectui#6730 records why converging them costs rows). A row it
139
+ * turns into an `ActivityItem` never becomes a `FeedItem`, so it produces
140
+ * nothing for this feed. `ActivityEntry` in `@object-ui/types` is a third
141
+ * vocabulary again, and has no producer at all.
142
+ *
143
+ * ⚠️ Also NOT counted, and the reason the diagnostic below names it: a HOST can
144
+ * hand this block a feed of its own (`items` on the node, or a
145
+ * `DiscussionContext` it mounts itself). Those items are produced outside this
146
+ * repository, so no census taken here can bound their kinds.
147
+ */
148
+ export declare const FEED_TYPES_PRODUCED_OFF_MAP: readonly FeedItemType[];
149
+ /**
150
+ * Feed kinds this repository has decided NOT to adopt — as opposed to kinds
151
+ * nothing happens to produce.
152
+ *
153
+ * The distinction matters more than the diagnostic that uses it. Reporting a
154
+ * decision as a defect is how a warning channel gets trained out of an author's
155
+ * attention, and these three are decisions: `created` / `deleted` / `shared`
156
+ * rows are mapped to `field_change` ON PURPOSE, because `RecordDetailView` and
157
+ * this block must agree about what a `created` row IS before either can move to
158
+ * the richer kind. Adopting them is a change to that shared map, not to this
159
+ * block. The reasoning is written out at {@link ACTIVITY_TYPE_TO_FEED_TYPE}.
160
+ *
161
+ * ⚠️ This list is a TRANSCRIPTION of that prose, not a derivation, and there is
162
+ * no way to make it one: the decision exists as a comment, and a comment is not
163
+ * machine-readable. Two consequences, both load-bearing:
164
+ *
165
+ * 1. it is only as true as the prose it was copied from, so it is edited
166
+ * together with that prose and never on its own;
167
+ * 2. a kind that is NOT on this list is not thereby known to be an oversight.
168
+ * It is only known to have no producer. Whether somebody decided against it
169
+ * and did not write it down is not recorded anywhere this code can read, and
170
+ * the message below says so rather than guessing.
171
+ *
172
+ * ⛔ Do not extend it by inference. A kind belongs here when a decision about it
173
+ * is written down, not when its absence looks deliberate.
174
+ */
175
+ export declare const DELIBERATELY_UNADOPTED_FEED_TYPES: readonly FeedItemType[];
176
+ /**
177
+ * Every feed kind some ObjectUI surface can put on a record feed.
178
+ *
179
+ * DERIVED from the producers themselves — the map's range, its fallback, and
180
+ * the declared off-map producer above — never a second hand-written list of
181
+ * kinds. Giving an unproduced kind a producer (one new entry in
182
+ * {@link ACTIVITY_TYPE_TO_FEED_TYPE}) therefore retires its diagnostic in the
183
+ * same edit, with nothing else to remember. A hand-kept copy would not, and
184
+ * that copy is exactly what this file's header records the cost of.
185
+ */
186
+ export declare const PRODUCED_FEED_TYPES: ReadonlySet<FeedItemType>;
23
187
  /** A row as `sys_activity` returns it. Loose on purpose: the block reads a
24
188
  * system table it does not own, and an unknown column is not an error. */
25
189
  export interface SysActivityRow {
@@ -34,6 +198,12 @@ export interface SysActivityRow {
34
198
  source_id?: string | number | null;
35
199
  [k: string]: unknown;
36
200
  }
201
+ /** Test seam: forget which `types` entries have already been named. */
202
+ export declare function resetUnrecognisedFeedTypeWarnings(): void;
203
+ /** Test seam: forget which unproduced `types` entries have already been named. */
204
+ export declare function resetUnproducedFeedTypeWarnings(): void;
205
+ /** Test seam: forget which `filterMode` values have already been named. */
206
+ export declare function resetUnrecognisedFilterModeWarnings(): void;
37
207
  /**
38
208
  * Coerce an authored `filterMode` to a value the timeline understands.
39
209
  *
@@ -41,12 +211,122 @@ export interface SysActivityRow {
41
211
  * `<Select>` handed a value with no matching item renders blank and the
42
212
  * dropdown reads as broken. Same posture as objectui#3151 — an unrecognised
43
213
  * filter token is skipped, never turned into a filter that can't match.
214
+ *
215
+ * ## Why that fallback needs a diagnostic (objectui#5891)
216
+ *
217
+ * `'all'` is the WIDEST of the four declared modes — `filterItems` in
218
+ * `RecordActivityTimeline` narrows to ONE feed type for each of the other three
219
+ * and returns the feed untouched for `'all'` — so every unrecognised value
220
+ * lands on the one option that shows the author MORE than they asked for. A
221
+ * near-miss like `comments-only` or `commentsOnly` therefore opens the panel on
222
+ * the unfiltered stream, and the tell is a PLAUSIBLE result (a populated
223
+ * timeline) rather than an empty one that gets investigated. That is the same
224
+ * shape objectui#5841 removed from the sibling `types` sanitiser.
225
+ *
226
+ * The ruling on this card (triage, 2026-08-25) is that the defect is the
227
+ * INVISIBILITY, not the fallback: `filterMode` seeds a control the user can
228
+ * change, and there is no defensible narrower default to fall back to instead.
229
+ * So nothing that renders changes here — what changes is that the fold is now
230
+ * SAID OUT LOUD, once per distinct offending value on each block (see
231
+ * {@link warnOnce}).
232
+ *
233
+ * `block` is the registered name of the block the value was authored on
234
+ * (`record:activity`, `record:chatter`, `record:discussion`). It is REQUIRED,
235
+ * not defaulted: the message is addressed to that block's author, and a default
236
+ * would hand every caller that forgot it some other block's name — the defect
237
+ * objectui#9557 removed.
238
+ *
239
+ * Absent (`undefined` / `null`) is silent: no `filterMode` was authored, which
240
+ * is not a mistake, and a warning about a decision teaches authors to ignore
241
+ * the channel. Only a value the author actually wrote is reported.
242
+ *
243
+ * The declared modes named in the message are read from `@objectstack/spec`'s
244
+ * `FeedFilterMode` at runtime, never re-typed here — see the file header for
245
+ * what a hand copy of a spec enum cost.
246
+ */
247
+ export declare function normalizeFilterMode(value: unknown, block: string): FeedFilterMode;
248
+ /**
249
+ * Sanitise an authored `types` allow-list. Narrowing or refusal, never widening.
250
+ *
251
+ * Three distinct authored intents used to collapse into one rendering
252
+ * (objectui#5841). They are three again:
253
+ *
254
+ * | authored | returns | rendered |
255
+ * | ----------------------------- | ----------- | ------------------------------ |
256
+ * | absent (`undefined`/`null`) | `undefined` | every kind — no filter authored |
257
+ * | `[]` | `[]` | nothing — the author said "no kinds" |
258
+ * | `['crm_task']` (none known) | `[]` | nothing, plus one diagnostic |
259
+ * | `['task', 'crm_task']` | `['task']` | the recognised members, plus one diagnostic |
260
+ * | `['approval']` (no producer) | `['approval']` | nothing, plus one diagnostic — the kind is declared and honoured, but nothing emits it (objectui#5877) |
261
+ *
262
+ * `undefined` now means "no `types` key was authored" and ONLY that. It used to
263
+ * mean that OR "everything you authored was dropped", and {@link applyFeedConfig}
264
+ * reads it as "apply no filter" — so a single typo served the author every
265
+ * activity on the record with nothing said anywhere, and the tell was a
266
+ * PLAUSIBLE result (a populated timeline) rather than an empty one that gets
267
+ * investigated. That is the principle this function now holds: a sanitiser may
268
+ * narrow an author's request or refuse it, but it must never silently widen it,
269
+ * because widening turns a typo into "show the user everything" — the one
270
+ * outcome no author asked for.
271
+ *
272
+ * `[]` is honoured rather than reinterpreted, and silently: rendering every kind
273
+ * is the maximally wrong answer to "no kinds", and there is nothing to report
274
+ * about a request that was carried out exactly.
275
+ *
276
+ * Membership is read from `@objectstack/spec`'s `FeedItemType` at runtime, never
277
+ * re-typed here — see the file header for what a hand copy of a spec enum cost.
278
+ *
279
+ * NOT pure, on TWO independent channels (see {@link warnOnce}) — the failure
280
+ * being repaired is invisibility in both cases, so the diagnostics are the other
281
+ * half of the fix rather than a nicety:
282
+ *
283
+ * - an entry it cannot recognise logs once (no declared kind matches it);
284
+ * - a RECOGNISED entry that no producer emits logs once as well, on its own
285
+ * channel — see {@link warnUnproducedFeedTypes}. That one changes nothing
286
+ * about the return value: the kind is declared, so it is kept and honoured.
287
+ *
288
+ * Both channels name, and dedupe per, the REQUIRED `block` the list was
289
+ * authored on — see {@link normalizeFilterMode} for why it has no default.
290
+ */
291
+ export declare function normalizeFeedTypes(value: unknown, block: string): FeedItemType[] | undefined;
292
+ /**
293
+ * The ONE resolver for this block's row cap (objectui#10096, objectui#10145).
294
+ *
295
+ * A refused value is dropped for this block's own default.
296
+ *
297
+ * ⛔ Not `Math.floor`, which objectui#10096 replaced: flooring REPAIRED an
298
+ * authored `2.5` into a two-row window and handed it back as a result, so the
299
+ * author got a silently different number than the one they wrote. Refusing and
300
+ * defaulting is the answer the sibling `record:history` gives through
301
+ * `normalizeHistoryLimit`, and the answer objectui#9925 landed at three further
302
+ * read points (all three test `typeof value === 'number'` first, as this does).
303
+ *
304
+ * `NaN` and both infinities are refused (not integers); `0` and negatives are
305
+ * refused (not positive); `undefined` is the unauthored case and resolves to
306
+ * the default without being a refusal.
307
+ *
308
+ * ⚠️ FAIL-SOFT on purpose: throwing would take out a record page over one
309
+ * declaration. It is NOT silent — the renderers that call this state every
310
+ * refusal through `describeRefusedFeedLimit` (objectui#10097 ruled "always
311
+ * warn" for the family; objectui#10145 extends the refused set to non-numbers).
312
+ * This function stays pure so it can run on every render.
313
+ *
314
+ * ⭐ Shared with `record:chatter` / `record:discussion`, which resolve
315
+ * `feed.limit` through this same function — `RecordChatterProps.feed` is
316
+ * `RecordActivityProps`, so it is the same declared member and the same
317
+ * refusal.
44
318
  */
45
- export declare function normalizeFilterMode(value: unknown): FeedFilterMode;
46
- /** Keep only the `types` entries the spec actually defines. */
47
- export declare function normalizeFeedTypes(value: unknown): FeedItemType[] | undefined;
48
- /** Coerce `limit` to a positive integer, falling back to the spec default. */
49
319
  export declare function normalizeLimit(value: unknown): number;
320
+ /**
321
+ * The diagnostic half of the row-cap refusal for the blocks that resolve
322
+ * through `normalizeLimit` (`record:activity`, `record:chatter`,
323
+ * `record:discussion`). `null` means "nothing to say": an absent `limit`
324
+ * (`undefined` / `null`) is not a mistake, and a usable one is not either.
325
+ *
326
+ * Callers fire it from an effect keyed on the declaration — the channel
327
+ * objectui#9925 uses at its three read points — never from render.
328
+ */
329
+ export declare function describeRefusedFeedLimit(block: string, authored: unknown): string | null;
50
330
  /**
51
331
  * Timestamp of a `sys_activity` row.
52
332
  *
@@ -56,9 +336,24 @@ export declare function normalizeLimit(value: unknown): number;
56
336
  * type map is: same rows, same quirk.
57
337
  */
58
338
  export declare function activityTimestamp(row: SysActivityRow): string;
339
+ /** Test seam: forget which unknown types have already been warned about. */
340
+ export declare function resetUnknownActivityTypeWarnings(): void;
59
341
  /**
60
342
  * One `sys_activity` row → one {@link FeedItem}, or `null` when the row is not
61
343
  * record activity (see {@link ACTIVITY_TYPE_TO_FEED_TYPE}).
344
+ *
345
+ * Three outcomes, and the difference between the last two is the whole of the
346
+ * direction-4 ruling (2026-08-24, objectstack `88b9d749a`):
347
+ *
348
+ * - a type mapped to a feed type renders with THAT presentation;
349
+ * - a type the table maps to `undefined` is a DELIBERATE exclusion — `null`,
350
+ * quietly, because a warning about a decision teaches authors to ignore the
351
+ * channel;
352
+ * - a type the table does not contain at all is an AUTHOR-EXTENDED value under
353
+ * the ruled open vocabulary. It renders through
354
+ * {@link UNMAPPED_ACTIVITY_FEED_TYPE} and says so once. It used to return
355
+ * `null` here, which made every extended value invisible — the same outcome
356
+ * objectui#5840 was filed for, reached by a different route.
62
357
  */
63
358
  export declare function activityRowToFeedItem(row: SysActivityRow, systemActorLabel: string): FeedItem | null;
64
359
  /** The subset of `RecordActivityProps` that decides which items survive. */
@@ -86,8 +381,52 @@ export interface AppliedFeed {
86
381
  * `pageSize` is the effective window (`limit` on first render, grown by
87
382
  * "Load more"); it is separate from `config.limit` so paging does not have to
88
383
  * rewrite the authored config.
384
+ *
385
+ * `block` is the registered name of the block whose config this is; the
386
+ * `types` diagnostics are addressed to it (objectui#9557, see
387
+ * {@link normalizeFilterMode}).
89
388
  */
90
- export declare function applyFeedConfig(items: readonly FeedItem[], config: FeedConfigFilters, pageSize: number): AppliedFeed;
389
+ export declare function applyFeedConfig(items: readonly FeedItem[], config: FeedConfigFilters, pageSize: number, block: string): AppliedFeed;
91
390
  /** Sort a feed chronologically and de-duplicate by id (feeds are append-only). */
92
391
  export declare function mergeFeedItems(...groups: readonly FeedItem[][]): FeedItem[];
392
+ /**
393
+ * Whether a rejected feed read — `sys_activity` or `sys_comment` — was REFUSED
394
+ * (objectui#11195): the server answered, and the answer was "not for you".
395
+ *
396
+ * A refused read and an empty read are different answers about the record, and
397
+ * the panel used to give both the same one. Every failure on the feed landed on
398
+ * `No activity recorded` / `No comments yet`, so a member the server refused
399
+ * read a permission wall as a record where nothing had happened. The panel now
400
+ * shows a no-permission state for a refused read instead, and keeps the empty
401
+ * state for a read that answered with zero rows.
402
+ *
403
+ * The verdict is `classifyLoadError`'s (`@object-ui/react`), the read
404
+ * classifier `ListView`'s error panel and `RecordAttachmentsPanel` already
405
+ * share. Nothing here reads a status or a code of its own. Refused means the
406
+ * two kinds that are about the caller:
407
+ *
408
+ * - `forbidden`: 403, or a `PERMISSION_DENIED` / `FORBIDDEN` envelope;
409
+ * - `unauthorized`: 401, or `UNAUTHORIZED` / `UNAUTHENTICATED`.
410
+ *
411
+ * ⚠️ The 401 half is a deliberate difference from `RecordAttachmentsPanel`,
412
+ * whose `denied` state does not claim a 401. The triage on objectui#11195 ruled
413
+ * it for this panel: any refused read (401 / 403, or a permission envelope)
414
+ * shows the no-permission state.
415
+ *
416
+ * NOT refused, so these keep today's empty state:
417
+ *
418
+ * - `api-disabled` (`OBJECT_API_DISABLED` 404 / `OBJECT_API_METHOD_NOT_ALLOWED`
419
+ * 405): a property of the object's metadata, not of the caller, and no
420
+ * permission grant changes it;
421
+ * - a bare 404 (`sys_activity` on a deployment without the audit plugin).
422
+ * The ObjectStack adapter already answers that as zero rows; another
423
+ * adapter's rejection classifies as `network` and lands here;
424
+ * - `rejected` (400) and `network` (a 5xx, an unreachable server). Those are
425
+ * failures, not refusals. The panel has no error state of its own, so they
426
+ * still render the empty state.
427
+ *
428
+ * ⛔ A refusal is not retried. A refused read is the same answer on every
429
+ * retry, so the callers write it into state and do not re-issue the read.
430
+ */
431
+ export declare function isRefusedFeedRead(err: unknown): boolean;
93
432
  //# 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;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,CAAC,CAYzF,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;AAED,+DAA+D;AAC/D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,EAAE,GAAG,SAAS,CAM7E;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGrD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,CAM7D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,cAAc,EACnB,gBAAgB,EAAE,MAAM,GACvB,QAAQ,GAAG,IAAI,CAejB;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,CAoCb;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,44 @@
1
+ import { RecordComponentAriaProps } from '@object-ui/types';
2
+ /**
3
+ * The authored `aria` bag as a renderer actually receives it: the declared
4
+ * face, plus the undeclared `label` spelling a stored document may still carry.
5
+ */
6
+ export type AuthoredRecordAria = RecordComponentAriaProps & {
7
+ /**
8
+ * ⛔ Not a member of the contract. It is the shared ARIA shape's alias entry
9
+ * and is refused on parse. Declared on this type ONLY so the diagnostic can
10
+ * see it without an `as any` cast (a cast is how `record:path` once came to
11
+ * read this spelling and no other).
12
+ *
13
+ * ⚠️ It is never read as a name. objectui#9945 retired the fold that did;
14
+ * a served value is reported, and only for a caller that names its `block`.
15
+ * See this module's header.
16
+ */
17
+ label?: string;
18
+ };
19
+ /** What {@link useRecordAriaProps} spreads onto a block's container element. */
20
+ export interface RecordAriaDomProps {
21
+ 'aria-label'?: string;
22
+ 'aria-describedby'?: string;
23
+ role?: string;
24
+ }
25
+ /**
26
+ * The ARIA DOM props for a `record:*` block's own container.
27
+ *
28
+ * @param aria - the authored bag, straight off the node's `aria` key.
29
+ * @param defaultRole - the role this block renders when the author declares
30
+ * none. Omit it and the container stays role-less until an author declares a
31
+ * name, a description or a role — at which point `'region'` carries the name,
32
+ * because an attribute on a `generic` element carries nothing.
33
+ * @param defaultLabel - the block's built-in accessible name, used when the
34
+ * author declared none (or declared an empty one).
35
+ * @param block - the block's registered type (`record:path`), named in the
36
+ * report of a served `aria.label`. Omit it and that spelling is still not
37
+ * read, just not reported. See this module's header for which blocks pass it.
38
+ */
39
+ export declare function useRecordAriaProps(aria: AuthoredRecordAria | undefined, { defaultRole, defaultLabel, block, }?: {
40
+ defaultRole?: string;
41
+ defaultLabel?: string;
42
+ block?: string;
43
+ }): RecordAriaDomProps;
44
+ //# sourceMappingURL=recordComponentAria.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordComponentAria.d.ts","sourceRoot":"","sources":["../../src/renderers/recordComponentAria.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AAKH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAEjE;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,wBAAwB,GAAG;IAC1D;;;;;;;;;OASG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,gFAAgF;AAChF,MAAM,WAAW,kBAAkB;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAwCD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,kBAAkB,GAAG,SAAS,EACpC,EACE,WAAW,EACX,YAAY,EACZ,KAAK,GACN,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACtE,kBAAkB,CAapB"}
@@ -0,0 +1,102 @@
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
+ * `record:related_list.actions` — where an authored action id lands on the list,
9
+ * or why it is refused (objectui#11163; the maintainer's ENFORCE ruling on
10
+ * objectstack-ai/objectstack#20665).
11
+ *
12
+ * The contract declares the key as `z.array(z.string())`, "Action IDs available
13
+ * for related records". This module is the whole of its meaning, kept apart
14
+ * from the renderer so the file that draws the block keeps exporting
15
+ * components only:
16
+ *
17
+ * - RESOLUTION is `resolveDeclaredActionIds` — the one function
18
+ * `page:header`, `record:quick_actions` and `record:alert` resolve an
19
+ * authored action id through (objectui#7182, objectui#7382) — against the
20
+ * RELATED object's registered `actions`. ⛔ No second lookup: the caller
21
+ * reads that registry through `useMetadataItem('object', …)`, the same
22
+ * entry `record:quick_actions.actionNames` uses.
23
+ * - Only the IDS arm is this key's. The inline-object arm of the shared
24
+ * rule is `page:header`'s transition tolerance (objectstack#11592) and is
25
+ * not inherited here, the choice `record:alert` made for its CTA: an
26
+ * object in this array would widen what a related list can run past what
27
+ * the object declares.
28
+ * - PLACEMENT is each action's own `locations`, read by `actionRendersAt`
29
+ * — the platform's one placement rule, and the rule the host bridge
30
+ * (`RelatedRecordActionsBridge.deriveActions`) already applies to this
31
+ * list: `list_toolbar` draws a header button; `list_item` and
32
+ * `record_related` draw a row-menu item (objectui#11270). This block only
33
+ * ever renders inside a parent record, which is the scope `record_related`
34
+ * names (objectstack-ai/objectstack#20937, triage `5919625056`: row
35
+ * placement, inside a record only). Naming an action here does not bypass
36
+ * location filtering (only a list view's selection bar is placed by
37
+ * naming), so an id whose action declares none of the three is REFUSED
38
+ * rather than drawn somewhere or dropped.
39
+ * - ORDER is the author's, within each surface.
40
+ */
41
+ /**
42
+ * An action definition as the registry holds it — the two members read here,
43
+ * typed `unknown` because the definitions arrive straight off the metadata
44
+ * wire (the reason `actionRendersAt` gives for its own parameter).
45
+ */
46
+ export interface RegisteredRelatedAction {
47
+ readonly name?: unknown;
48
+ readonly locations?: unknown;
49
+ }
50
+ /** One reason an authored entry does not render, stated for the author. */
51
+ export type RelatedListActionRefusal =
52
+ /** The array itself is refused: not an array, or not all action ids. */
53
+ {
54
+ readonly kind: 'array';
55
+ readonly message: string;
56
+ }
57
+ /** An id that names no registered action of the related object. */
58
+ | {
59
+ readonly kind: 'unresolved';
60
+ readonly id: string;
61
+ }
62
+ /** An id that resolves, to an action declaring no location this list renders. */
63
+ | {
64
+ readonly kind: 'unplaced';
65
+ readonly id: string;
66
+ };
67
+ export interface PlacedRelatedListActions<T> {
68
+ /** `list_toolbar` actions, in authored order — the list's header buttons. */
69
+ readonly toolbar: T[];
70
+ /** `list_item` and `record_related` actions, in authored order — each row's menu. */
71
+ readonly row: T[];
72
+ /** Every authored entry that renders nowhere, and why. */
73
+ readonly refused: RelatedListActionRefusal[];
74
+ }
75
+ /**
76
+ * Whether an authored value has any id to look up — the registry-independent
77
+ * half of the shared rule, so the caller requests the metadata read only when
78
+ * it can change the answer (a `[]`, an object array or a refused array is
79
+ * decided without it).
80
+ */
81
+ export declare function relatedListActionsNeedLookup(authored: unknown): boolean;
82
+ /**
83
+ * Resolve and place an authored `record:related_list.actions` value.
84
+ *
85
+ * `registered` is the related object's `actions` once the metadata lookup has
86
+ * ANSWERED. The caller does not call this while the lookup is in flight: every
87
+ * id would come back unresolved and the list would refuse an id it is about to
88
+ * resolve.
89
+ *
90
+ * An id authored twice renders once, at its first position — it is drawn, so
91
+ * nothing the author asked for is dropped.
92
+ */
93
+ export declare function placeAuthoredRelatedListActions<T extends RegisteredRelatedAction>(authored: unknown, registered: readonly T[] | null | undefined): PlacedRelatedListActions<T>;
94
+ /**
95
+ * The refusal notice's text — one sentence per refused entry, naming it and
96
+ * the related object, so the fix is a one-entry edit.
97
+ *
98
+ * `objectKnown` is whether the lookup found the related object at all: an id
99
+ * cannot be "not an action of" an object whose metadata never arrived.
100
+ */
101
+ export declare function describeRelatedListActionRefusals(refused: readonly RelatedListActionRefusal[], objectName: string, objectKnown: boolean): string;
102
+ //# sourceMappingURL=relatedListActions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relatedListActions.d.ts","sourceRoot":"","sources":["../../src/renderers/relatedListActions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAIH;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,2EAA2E;AAC3E,MAAM,MAAM,wBAAwB;AAClC,wEAAwE;AACtE;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AACtD,mEAAmE;GACjE;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE;AACtD,iFAAiF;GAC/E;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvD,MAAM,WAAW,wBAAwB,CAAC,CAAC;IACzC,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;IACtB,qFAAqF;IACrF,QAAQ,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;IAClB,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,wBAAwB,EAAE,CAAC;CAC9C;AAQD;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAIvE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,+BAA+B,CAAC,CAAC,SAAS,uBAAuB,EAC/E,QAAQ,EAAE,OAAO,EACjB,UAAU,EAAE,SAAS,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,GAC1C,wBAAwB,CAAC,CAAC,CAAC,CA6C7B;AAED;;;;;;GAMG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,SAAS,wBAAwB,EAAE,EAC5C,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,OAAO,GACnB,MAAM,CAeR"}