@nivaro/react 0.3.41 → 0.3.42

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 (96) hide show
  1. package/dist/components/LayoutForm.js +100 -98
  2. package/dist/full.css +1 -1
  3. package/dist/hooks/useSessionRecorder.js +1 -1
  4. package/dist/index.d.ts +644 -15
  5. package/dist/index.js +814 -775
  6. package/dist/shared/components/ActivityEntry.js +154 -0
  7. package/dist/shared/components/AudienceConfirmPanel.js +98 -0
  8. package/dist/shared/components/BackgroundRunOffer.js +43 -31
  9. package/dist/shared/components/BroadcastView.js +225 -204
  10. package/dist/shared/components/CollectionBrowserView.js +3924 -3584
  11. package/dist/shared/components/ConfigHistory.js +153 -0
  12. package/dist/shared/components/ConfigReadBack.js +65 -0
  13. package/dist/shared/components/DataTable.js +379 -346
  14. package/dist/shared/components/ForceReloadBanner.js +47 -40
  15. package/dist/shared/components/IdleReloadWatcher.js +69 -0
  16. package/dist/shared/components/ItemEditForm.js +4041 -3853
  17. package/dist/shared/components/MyWorkView.js +278 -213
  18. package/dist/shared/components/NotificationBell.js +130 -121
  19. package/dist/shared/components/ProfileView.js +776 -1003
  20. package/dist/shared/components/RecentRecordsRail.js +34 -16
  21. package/dist/shared/components/RecordReadView.js +500 -477
  22. package/dist/shared/components/Skeletons.js +199 -0
  23. package/dist/shared/components/SlowNotice.js +63 -0
  24. package/dist/shared/components/Term.js +23 -0
  25. package/dist/shared/components/WidgetSlot.js +350 -353
  26. package/dist/shared/components/access/PermissionSummary.js +106 -0
  27. package/dist/shared/components/bulk/BulkActionButtons.js +76 -73
  28. package/dist/shared/components/chat/ChatPanel.js +902 -886
  29. package/dist/shared/components/chat/MemberPaging.js +70 -0
  30. package/dist/shared/components/chat/RoomParts.js +147 -117
  31. package/dist/shared/components/chat/ScreenClip.js +73 -69
  32. package/dist/shared/components/chat/chat-core.js +449 -396
  33. package/dist/shared/components/chat/huddle/huddle-store.js +48 -43
  34. package/dist/shared/components/help-videos/api.js +2 -5
  35. package/dist/shared/components/help-videos/viewer/HelpVideoMatchChip.js +79 -0
  36. package/dist/shared/components/help-videos/viewer/matchChip.js +50 -0
  37. package/dist/shared/components/imports/CollectionImportPanel.js +139 -133
  38. package/dist/shared/components/imports/RunDetailSheet.js +222 -227
  39. package/dist/shared/components/integrations/console/ErrorExplanation.js +121 -0
  40. package/dist/shared/components/integrations/console/PartnerDetail.js +175 -174
  41. package/dist/shared/components/integrations/console/SubmissionRow.js +36 -34
  42. package/dist/shared/components/integrations/console/drill/SubmissionDrill.js +50 -48
  43. package/dist/shared/components/item-edit/FieldComments.js +212 -0
  44. package/dist/shared/components/item-edit/FieldRow.js +340 -336
  45. package/dist/shared/components/item-edit/RecordHeaderActions.js +23 -22
  46. package/dist/shared/components/item-edit/RelationCombobox.js +231 -222
  47. package/dist/shared/components/item-edit/RuleHiddenNotes.js +55 -0
  48. package/dist/shared/components/item-edit/field-comments-context.js +6 -0
  49. package/dist/shared/components/item-edit/field-sources.js +33 -0
  50. package/dist/shared/components/item-edit/rule-hidden.js +30 -0
  51. package/dist/shared/components/layout-editor/HistoryPanel.js +113 -96
  52. package/dist/shared/components/layout-editor/editors/cascade-filters.js +170 -145
  53. package/dist/shared/components/layout-editor/inspector/Breadcrumb.js +22 -18
  54. package/dist/shared/components/layout-editor/inspector/GroupInspector.js +177 -159
  55. package/dist/shared/components/layout-editor/inspector/PickerTab.js +35 -34
  56. package/dist/shared/components/layout-editor/inspector/RulesTab.js +124 -111
  57. package/dist/shared/components/layout-editor/inspector/kit.js +292 -277
  58. package/dist/shared/components/layout-editor/parity-keys.json.js +1 -1
  59. package/dist/shared/components/list/CopyListLinkButton.js +41 -0
  60. package/dist/shared/components/list/InlineEdit.js +363 -0
  61. package/dist/shared/components/notifications/NotificationCenterView.js +213 -181
  62. package/dist/shared/components/notifications/NotificationDetailBits.js +205 -82
  63. package/dist/shared/components/notifications/NotificationRulesEditor.js +515 -0
  64. package/dist/shared/components/notifications/TestMyRules.js +137 -0
  65. package/dist/shared/components/notifications/changes-lines.js +24 -0
  66. package/dist/shared/components/notifications/why-lines.js +64 -0
  67. package/dist/shared/components/panels/CommentPanel.js +316 -281
  68. package/dist/shared/components/panels/LegalHoldBanner.js +38 -0
  69. package/dist/shared/components/panels/PipelinePanel.js +201 -196
  70. package/dist/shared/components/panels/RevisionsPanel.js +496 -479
  71. package/dist/shared/components/pipeline/PipelineEditorView.js +1587 -1560
  72. package/dist/shared/components/pipeline/PipelineStateOwners.js +116 -103
  73. package/dist/shared/components/profile/PersonDidPanel.js +188 -0
  74. package/dist/shared/components/profile/PersonProfile.js +57 -52
  75. package/dist/shared/components/queue/QueueWorklist.js +1526 -1365
  76. package/dist/shared/hooks/useSlowReason.js +39 -0
  77. package/dist/shared/lib/activity-sentence.js +670 -0
  78. package/dist/shared/lib/config-sentences.js +418 -0
  79. package/dist/shared/lib/core-glossary.js +229 -0
  80. package/dist/shared/lib/error-hints.js +159 -0
  81. package/dist/shared/lib/error-message.js +105 -0
  82. package/dist/shared/lib/error-toast.js +22 -0
  83. package/dist/shared/lib/field-comments.js +25 -0
  84. package/dist/shared/lib/filter-sentence.js +261 -0
  85. package/dist/shared/lib/glossary.js +55 -48
  86. package/dist/shared/lib/idle-reload.js +138 -0
  87. package/dist/shared/lib/layout-resolve.js +42 -29
  88. package/dist/shared/lib/list-inline-edit.js +190 -0
  89. package/dist/shared/lib/list-link.js +147 -0
  90. package/dist/shared/lib/prefetch.js +259 -106
  91. package/dist/shared/lib/record-ref.js +72 -0
  92. package/dist/shared/lib/reload-holds.js +25 -0
  93. package/dist/shared/lib/safe-error.js +52 -24
  94. package/dist/shared/lib/skeleton-shapes.js +72 -0
  95. package/dist/shared/lib/slow-reason.js +63 -0
  96. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -120,6 +120,164 @@ declare interface ActiveLayoutData {
120
120
  ungrouped_sort?: number | null;
121
121
  }
122
122
 
123
+ /** Who really did it (nivaro_activity.actor, migration 516; derived for older rows). */
124
+ declare interface ActivityActor {
125
+ kind: ActivityActorKind;
126
+ /** view_as: the admin; api_key: the key id; mcp: the connection; import: the definition
127
+ * key; flow: the flow id; cron: the job id. */
128
+ id?: string | null;
129
+ name?: string | null;
130
+ /** The person the actor acted for / as (view_as: the person being viewed as). */
131
+ on_behalf_of?: string | null;
132
+ }
133
+
134
+ /**
135
+ * Activity sentences — one readable line per audit-trail entry.
136
+ *
137
+ * "Pat moved PO 4471 from Draft to Approved"
138
+ * "Sam deleted 3 invoices"
139
+ * "Rob as Pat changed Vendor on Workflow 85607"
140
+ * "Pat updated 40 workflows: Status → On Hold"
141
+ *
142
+ * Pure (no imports, no I/O): the Activity page, the activity detail page, the
143
+ * record history panels and the email digest all render through it. Labels
144
+ * come from POST /api/activity/labels (records read AS THE READER — a record
145
+ * the reader cannot see is "a record you can't see"; people by name;
146
+ * collection / field / choice labels); until they arrive the sentence falls
147
+ * back to the collection name and the stored id.
148
+ *
149
+ * TWIN FILE: packages/shared/src/lib/activity-sentence.ts and
150
+ * api/src/services/activity-sentence.ts must stay byte-identical (the API
151
+ * cannot import packages/shared; an API test compares the two files).
152
+ */
153
+ declare type ActivityActorKind = 'person' | 'view_as' | 'api_key' | 'mcp' | 'import' | 'flow' | 'cron' | 'system';
154
+
155
+ /** A linked approval decision the read API joined onto the entry. */
156
+ declare interface ActivityApproval {
157
+ decision: string;
158
+ comment?: string | null;
159
+ chain?: string | null;
160
+ step?: string | number | null;
161
+ collection?: string | null;
162
+ item?: string | null;
163
+ }
164
+
165
+ /** A field change the read API summarised from the revision delta. */
166
+ declare interface ActivityChange {
167
+ field: string;
168
+ value?: unknown;
169
+ }
170
+
171
+ export declare function ActivityEntry({ entry, labels, group, onOpen, actions, showTime, className }: ActivityEntryProps): JSX.Element;
172
+
173
+ export declare interface ActivityEntryInput {
174
+ id?: number | string | null;
175
+ action: string | null;
176
+ /** The person (nivaro_activity.user); revisions call it user_id. */
177
+ user?: string | null;
178
+ user_id?: string | null;
179
+ first_name?: string | null;
180
+ last_name?: string | null;
181
+ user_email?: string | null;
182
+ collection?: string | null;
183
+ item?: string | number | null;
184
+ comment?: string | null;
185
+ timestamp?: string | Date | null;
186
+ origin?: string | null;
187
+ chain_parent?: string | null;
188
+ auth_method?: string | null;
189
+ api_key_id?: number | string | null;
190
+ api_key_name?: string | null;
191
+ mcp_conn?: string | null;
192
+ via_app?: string | null;
193
+ via_agent?: string | null;
194
+ group_key?: string | null;
195
+ /** Stored JSON (string) or the parsed object. */
196
+ actor?: ActivityActor | string | null;
197
+ changes?: ActivityChange[] | null;
198
+ changed_fields?: string[] | null;
199
+ delta?: Record<string, unknown> | null;
200
+ /** A pipeline event folded into a record's history (services/revisions.ts). */
201
+ event?: {
202
+ kind: 'transition' | 'start';
203
+ from_label?: string | null;
204
+ to_label?: string | null;
205
+ transition_label?: string | null;
206
+ source?: string | null;
207
+ on_behalf_of?: {
208
+ id: string;
209
+ name: string | null;
210
+ } | null;
211
+ addendum?: {
212
+ id: string;
213
+ title: string | null;
214
+ } | null;
215
+ } | null;
216
+ why?: ActivityWhy | null;
217
+ }
218
+
219
+ /**
220
+ * One audit-trail entry as a sentence ("Pat moved PO 4471 from Draft to Approved"), who
221
+ * really did it (View-as, API key, assistant, import, flow, scheduled job), why (change
222
+ * reason, transition note, linked approval) and when, in the reader's time zone. A burst
223
+ * (`group` with more than one entry: one bulk action, import run, flow run, MCP request…)
224
+ * reads as one line that expands to its entries.
225
+ *
226
+ * `actions` is a slot rendered at the row's end (Undo, Open…). Labels come from
227
+ * `useActivityLabels` (POST /api/activity/labels, read as the viewer).
228
+ */
229
+ export declare interface ActivityEntryProps {
230
+ entry: ActivityEntryInput;
231
+ labels?: ActivityLabels | null;
232
+ /** The entry's whole burst, newest first (the entry itself included). */
233
+ group?: ActivityEntryInput[];
234
+ onOpen?: (entry: ActivityEntryInput) => void;
235
+ actions?: ReactNode;
236
+ /** Show the time at the row's end (default true). */
237
+ showTime?: boolean;
238
+ className?: string;
239
+ }
240
+
241
+ /** A list of entries with bursts folded (groupActivity) — each group one ActivityEntry. */
242
+ export declare function ActivityFeed({ entries, labels, onOpen, actionsFor, empty, className }: {
243
+ entries: ActivityEntryInput[];
244
+ labels?: ActivityLabels | null;
245
+ onOpen?: (entry: ActivityEntryInput) => void;
246
+ /** The `actions` slot for a group's head entry. */
247
+ actionsFor?: (group: ActivityGroup) => ReactNode;
248
+ empty?: ReactNode;
249
+ className?: string;
250
+ }): JSX.Element;
251
+
252
+ declare interface ActivityGroup<T extends ActivityEntryInput = ActivityEntryInput> {
253
+ /** Stable React key. */
254
+ key: string;
255
+ /** Newest first, as given. */
256
+ entries: T[];
257
+ /** 'group_key' = written by one bulk action / import / flow / cron / batch / MCP request;
258
+ * 'time' = same actor + collection + action within the window (rows before 516). */
259
+ by: 'group_key' | 'time' | null;
260
+ }
261
+
262
+ /** What POST /api/activity/labels answers (plus the reader's time zone, if any). */
263
+ export declare interface ActivityLabels {
264
+ /** Person id (any case) → display name. */
265
+ users?: Record<string, string>;
266
+ /** `collection:id` → label; null = the reader cannot see that record. */
267
+ records?: Record<string, string | null>;
268
+ /** Collection → names (or a bare display name). */
269
+ collections?: Record<string, CollectionNames | string>;
270
+ /** `collection.field` → field label. */
271
+ fields?: Record<string, string>;
272
+ /** `collection.field` → stored value → choice label. */
273
+ choices?: Record<string, Record<string, string>>;
274
+ /** `collection.field` → the collection a link field points at. Link values are ids, so a
275
+ * sentence names the field, never the raw value. */
276
+ links?: Record<string, string>;
277
+ /** IANA zone dates in values render in; unset = the runtime's own zone. */
278
+ timeZone?: string | null;
279
+ }
280
+
123
281
  /** A stretch of the recording where the author was typing in a text field,
124
282
  * or touched nothing at all (#1518). Never what was typed. */
125
283
  declare type ActivitySpan = {
@@ -128,6 +286,25 @@ declare type ActivitySpan = {
128
286
  end_ms: number;
129
287
  };
130
288
 
289
+ /** The "why" the read API joins onto an entry (#5): reason, note, approval. */
290
+ declare interface ActivityWhy {
291
+ /** The change reason a person gave for a create / update / delete. */
292
+ reason?: string | null;
293
+ /** What a person wrote on a transition. */
294
+ note?: string | null;
295
+ approval?: ActivityApproval | null;
296
+ }
297
+
298
+ /** The entry's "why" as short lines: change reason, transition note, approval. */
299
+ export declare function activityWhy(entry: ActivityEntryInput): {
300
+ reason: string | null;
301
+ note: string | null;
302
+ approval: string | null;
303
+ };
304
+
305
+ /** "Pat", "Rob as Pat", "API key “Partner sync”", "Claude for Pat", "Import “Vendors”". */
306
+ export declare function actorName(entry: ActivityEntryInput, labels?: ActivityLabels | null): string;
307
+
131
308
  export declare function AddendumPanel({ collection, item, addendumLayoutId, canCreate, onActiveCountChange, onApplied, onCreated, defaultExpanded }: {
132
309
  collection: string;
133
310
  item: string;
@@ -786,6 +963,9 @@ export declare interface BackgroundRequest {
786
963
  * then offers to hand the read to the server. While the server runs it, the strip shows elapsed
787
964
  * time against the usual time; when the result is ready it fills the page's own query and the
788
965
  * strip goes away. Every state is inline — no dialog.
966
+ *
967
+ * #3231 — from ~3 s it also says what the read is waiting on ("Waiting for a free slot — busy
968
+ * right now", "Running PUB health — not cached yet"), before and beside the offer.
789
969
  */
790
970
  export declare function BackgroundRunOffer({ className, ...opts }: UseBackgroundRunOptions & {
791
971
  className?: string;
@@ -1052,6 +1232,20 @@ export declare function captureMaskedScreenshot(opts?: {
1052
1232
 
1053
1233
  export declare function captureReplayClip(kind?: ReplayClipKind, timeoutMs?: number): Promise<ReplayLink | null>;
1054
1234
 
1235
+ /**
1236
+ * A card placeholder: optional title row, a KPI figure, a chart block or text lines — the shape
1237
+ * of the card that is coming.
1238
+ */
1239
+ export declare function CardSkeleton({ title, kpi, chart, lines, className, framed }: {
1240
+ title?: boolean;
1241
+ kpi?: boolean;
1242
+ chart?: boolean;
1243
+ lines?: number;
1244
+ className?: string;
1245
+ /** false = no border/background (the host already draws the card). */
1246
+ framed?: boolean;
1247
+ }): JSX.Element;
1248
+
1055
1249
  export { CascadeFilterRule }
1056
1250
 
1057
1251
  declare type CascadeRule = {
@@ -1111,6 +1305,17 @@ declare interface CellFilterLite {
1111
1305
  id_value?: number | null;
1112
1306
  }
1113
1307
 
1308
+ /**
1309
+ * Shape-matched loading placeholders (#3229) — the pure half.
1310
+ *
1311
+ * A placeholder takes the shape of what is coming: a list's real columns (a date column gets a
1312
+ * date-sized bar, a status column a pill, a number is right-aligned), a record form's real
1313
+ * sections and field widths when its layout is known before the record, a card's header and
1314
+ * lines. Widths are deterministic per (row, column) so a skeleton never flickers between renders.
1315
+ * The components live in components/Skeletons.tsx.
1316
+ */
1317
+ declare type CellShape = 'text' | 'long' | 'short' | 'number' | 'date' | 'badge' | 'avatar' | 'bool';
1318
+
1114
1319
  /** Shape of the 422 CHANGE_REASON_REQUIRED challenge from the items service. */
1115
1320
  export declare interface ChangeReasonChallenge {
1116
1321
  fields_changed: string[];
@@ -1890,6 +2095,11 @@ export declare function CollectionImportPanel({ initialJobId, onJobOpen }: {
1890
2095
  onJobOpen?: (id: string | null) => void;
1891
2096
  }): JSX.Element;
1892
2097
 
2098
+ declare interface CollectionNames {
2099
+ singular?: string | null;
2100
+ plural?: string | null;
2101
+ }
2102
+
1893
2103
  /**
1894
2104
  * Realtime sprint client contract. A HOST (admin, a portal app) owns the actual
1895
2105
  * Socket.io connection and provides this to shared components; components
@@ -2044,6 +2254,9 @@ export declare function ConformanceView({ className }: {
2044
2254
  className?: string;
2045
2255
  }): JSX.Element;
2046
2256
 
2257
+ /** Pure: may this connection prefetch? Not when it asks to save data or is 2g-slow. */
2258
+ export declare function connectionAllowsPrefetch(c: NetInfo | null | undefined): boolean;
2259
+
2047
2260
  /**
2048
2261
  * "Copy as …" (#654): the request behind a list or a record, as a curl
2049
2262
  * command, an SDK call and a GraphQL document, each ready to paste. The
@@ -2782,6 +2995,8 @@ export declare interface ErpSubmission {
2782
2995
  * A record page hosting its OWN External requests dialog already IS that
2783
2996
  * record, so it never needs this badge and doesn't send it. */
2784
2997
  record_label?: string;
2998
+ /** #18 — a failed push in plain words + Fix it target (null when it did not fail). */
2999
+ explanation?: IntegrationErrorExplanation | null;
2785
3000
  }
2786
3001
 
2787
3002
  export declare interface ErrorReplayLink {
@@ -3248,8 +3463,14 @@ export declare const FORCE_RELOAD_EVENT = "nvr:force-refresh";
3248
3463
  * Unsaved work (lib/unsaved-work.ts) holds the reload: past the deadline the
3249
3464
  * banner says a reload is waiting and reloads the moment the edits are saved
3250
3465
  * or discarded — or when the person clicks Reload now.
3466
+ *
3467
+ * Reload old tabs when idle (#2937): the banner also mounts IdleReloadWatcher, so a tab left on an
3468
+ * older build after a release reloads by itself once idle (no countdown, no banner). Pass
3469
+ * `idleReload={false}` to leave that out.
3251
3470
  */
3252
- export declare function ForceReloadBanner(): JSX.Element | null;
3471
+ export declare function ForceReloadBanner({ idleReload }?: {
3472
+ idleReload?: boolean;
3473
+ }): JSX.Element;
3253
3474
 
3254
3475
  /**
3255
3476
  * Sticky force refresh — the client half of api/src/services/sticky-refresh.ts.
@@ -3386,6 +3607,45 @@ export declare type FormSchema = {
3386
3607
  slotAssignments: FormSlotAssignment[];
3387
3608
  };
3388
3609
 
3610
+ /**
3611
+ * A record form placeholder. With a plan (formSkeletonFromLayout) it paints the form's own tabs,
3612
+ * section titles and field grid; without one, two generic sections.
3613
+ */
3614
+ export declare function FormSkeleton({ plan, className }: {
3615
+ plan: FormSkeletonPlan | null;
3616
+ className?: string;
3617
+ }): JSX.Element;
3618
+
3619
+ declare interface FormSkeletonField {
3620
+ key: string;
3621
+ /** Grid span out of 12. */
3622
+ span: number;
3623
+ /** A grid, widget or slot block (taller than an input). */
3624
+ tall?: boolean;
3625
+ }
3626
+
3627
+ /**
3628
+ * The resolved layout (GET /collection-layouts/active) → the sections and field widths the form
3629
+ * will paint. Null when there is no layout (the caller shows a generic form placeholder).
3630
+ */
3631
+ export declare function formSkeletonFromLayout(layout: LayoutLike | null | undefined, opts?: {
3632
+ isNew?: boolean;
3633
+ maxSections?: number;
3634
+ maxFields?: number;
3635
+ }): FormSkeletonPlan | null;
3636
+
3637
+ export declare interface FormSkeletonPlan {
3638
+ /** Tab labels, when the form opens on tabs (the first is the one that shows). */
3639
+ tabs: string[];
3640
+ sections: FormSkeletonSection[];
3641
+ }
3642
+
3643
+ declare interface FormSkeletonSection {
3644
+ key: string;
3645
+ label: string | null;
3646
+ fields: FormSkeletonField[];
3647
+ }
3648
+
3389
3649
  export declare type FormSlotAssignment = {
3390
3650
  slot: string;
3391
3651
  groupKey: string | null;
@@ -3591,6 +3851,16 @@ declare interface GridSumCapConfig {
3591
3851
  message?: string;
3592
3852
  }
3593
3853
 
3854
+ /**
3855
+ * Folds consecutive entries of one burst into one group. Entries sharing a
3856
+ * group_key always fold; entries without one fold when the same actor did the
3857
+ * same action on the same collection, each within `windowMs` of the previous
3858
+ * (rows from before migration 516). Order is kept; singles are groups of one.
3859
+ */
3860
+ export declare function groupActivity<T extends ActivityEntryInput>(entries: T[], opts?: {
3861
+ windowMs?: number;
3862
+ }): ActivityGroup<T>[];
3863
+
3594
3864
  /**
3595
3865
  * Run one identity call through the gate: `blocked()` answers without a request while the tab
3596
3866
  * is signed out (outside its probe window); a success opens the gate, a signed-out 401 closes it.
@@ -3891,6 +4161,13 @@ export declare interface HelpVideoDto {
3891
4161
  * the page. Null when nothing was found or an author dismissed it;
3892
4162
  * publishing clears it. Optional: older servers. */
3893
4163
  stale?: HelpVideoStale | null;
4164
+ /** #18, GET /help-videos/for only: how the video matched the screen — tier
4165
+ * 0 step, 1 screen, 2 screens like it, 3 record type, 4 area, 5 a term on
4166
+ * the screen. Optional: older servers. */
4167
+ match?: {
4168
+ tier: number;
4169
+ reason: string;
4170
+ };
3894
4171
  }
3895
4172
 
3896
4173
  export declare function HelpVideoEditor({ videoId, onClose }: {
@@ -3935,6 +4212,28 @@ export declare function HelpVideoLibrary({ watchId, editId, onWatch, onEdit, hea
3935
4212
  headerExtra?: React.ReactNode;
3936
4213
  }): JSX.Element;
3937
4214
 
4215
+ /**
4216
+ * #18 — "Watch: How to submit an IR (2:10)": the screen's best help video,
4217
+ * when there is a strong match the person has not finished. Renders nothing
4218
+ * otherwise (no wrapper either), so a host can mount it in its header area
4219
+ * unconditionally. × hides that video's chip for good (this browser).
4220
+ *
4221
+ * Hosts pass the route (`path`) and, when they have one, their page key /
4222
+ * record context; the server ranks screen → screens like it → record type →
4223
+ * area → glossary terms (GET /help-videos/for).
4224
+ */
4225
+ export declare function HelpVideoMatchChip({ path, app, page, collection, item, terms, className }: {
4226
+ path: string;
4227
+ app?: string;
4228
+ page?: string;
4229
+ collection?: string;
4230
+ item?: string | number;
4231
+ /** Glossary terms the screen shows. */
4232
+ terms?: string[];
4233
+ /** Classes for the wrapper, which only renders when a chip shows. */
4234
+ className?: string;
4235
+ }): JSX.Element;
4236
+
3938
4237
  /** Keyed by video id: switching to another video starts a fresh player, so
3939
4238
  * progress, the resume point and the one-retry flag never carry over. */
3940
4239
  export declare function HelpVideoPlayer(props: HelpVideoPlayerProps): JSX.Element;
@@ -4147,6 +4446,16 @@ export declare function huddleHolds(prefs: Record<string, unknown> | null | unde
4147
4446
  /** Time without real input before someone counts as idle. */
4148
4447
  export declare const IDLE_AFTER_MS: number;
4149
4448
 
4449
+ /**
4450
+ * Reload old tabs when idle (#2937) — renders nothing. Mounted by ForceReloadBanner, so every
4451
+ * host that shows pushed reloads also gets this. The decision is lib/idle-reload.ts
4452
+ * `decideIdleReload` (pure, tested); this only feeds it and acts on it.
4453
+ *
4454
+ * Asks GET /realtime/build-status through the host's NivaroClient when there is one, else the
4455
+ * same-origin /api (admin mounts the banner outside its NivaroProvider).
4456
+ */
4457
+ export declare function IdleReloadWatcher(): null;
4458
+
4150
4459
  /** The fields a presence heartbeat should carry. */
4151
4460
  export declare function idleState(): {
4152
4461
  is_idle: boolean;
@@ -4634,8 +4943,9 @@ declare interface InputBinding {
4634
4943
  export declare function installClientChaosFetch(): void;
4635
4944
 
4636
4945
  /**
4637
- * Document-level intent listener: resting on (or keyboard-focusing) a same-origin link
4638
- * preloads its screen's code, and a record link also warms the record. Returns the uninstaller.
4946
+ * Document-level intent listener: resting on (or keyboard-focusing) a same-origin link preloads
4947
+ * its screen's code; a record link also warms the record, a list link the list's metadata.
4948
+ * Leaving the link before pressing it cancels what it started. Returns the uninstaller.
4639
4949
  */
4640
4950
  export declare function installIntentPrefetch(opts: IntentPrefetchOptions): () => void;
4641
4951
 
@@ -4661,6 +4971,17 @@ export declare interface IntegrationDotsProps {
4661
4971
  className?: string;
4662
4972
  }
4663
4973
 
4974
+ declare interface IntegrationErrorExplanation {
4975
+ sentence: string;
4976
+ cause: string;
4977
+ fix_label: string | null;
4978
+ fix: IntegrationFixTarget | null;
4979
+ fix_link: string | null;
4980
+ field: string | null;
4981
+ detail: string | null;
4982
+ rule: string;
4983
+ }
4984
+
4664
4985
  /**
4665
4986
  * One entry of the integration events feed. The cross-record feed carries
4666
4987
  * `provider`; the per-record feed (GET /integration-events/record/...) carries
@@ -4718,6 +5039,25 @@ export declare type IntegrationEventPathTarget = {
4718
5039
  chainId: string;
4719
5040
  };
4720
5041
 
5042
+ /** GET answers' `explanation` (api services/integration-error-explain.ts). */
5043
+ declare type IntegrationFixTarget = {
5044
+ kind: 'record';
5045
+ collection: string;
5046
+ item: string;
5047
+ field?: string | null;
5048
+ } | {
5049
+ kind: 'external_api';
5050
+ id: number;
5051
+ section?: 'auth' | 'endpoints' | 'connection' | 'response' | null;
5052
+ } | {
5053
+ kind: 'partner';
5054
+ id: number;
5055
+ } | {
5056
+ kind: 'payload_template';
5057
+ api_id: number;
5058
+ key: string;
5059
+ };
5060
+
4721
5061
  export declare function IntegrationObligationsView({ api, className }: IntegrationObligationsViewProps): JSX.Element;
4722
5062
 
4723
5063
  export declare interface IntegrationObligationsViewProps {
@@ -4915,10 +5255,6 @@ export declare interface IntegrationsConsoleTab {
4915
5255
  render: () => ReactNode;
4916
5256
  }
4917
5257
 
4918
- /**
4919
- * HTTP shapes served by GET /integration-signals and /integration-partners —
4920
- * copied from the core routes so the shared package never imports api code.
4921
- */
4922
5258
  export declare interface IntegrationSignalAction {
4923
5259
  kind: 'retry_submission' | 'resend' | 'open' | 'explain' | 'extension';
4924
5260
  label: string;
@@ -4984,12 +5320,29 @@ declare interface IntentPrefetchOptions {
4984
5320
  client: NivaroClient | null;
4985
5321
  /** A link's same-origin path → the record it opens, or null. */
4986
5322
  matchRecord?: (path: string) => RecordTarget | null;
5323
+ /** A link's same-origin path → the collection list it opens, or null (#3230). */
5324
+ matchList?: (path: string) => string | null;
4987
5325
  /** Load the code for the screen a same-origin path opens. */
4988
5326
  preloadRoute?: (path: string) => void;
4989
5327
  /** Only links inside this element (default: the whole document). */
4990
5328
  root?: HTMLElement | Document;
4991
5329
  }
4992
5330
 
5331
+ export declare interface IntentProps {
5332
+ onPointerEnter: () => void;
5333
+ onPointerLeave: () => void;
5334
+ onPointerDown: () => void;
5335
+ onFocus: () => void;
5336
+ onBlur: () => void;
5337
+ }
5338
+
5339
+ /**
5340
+ * Handlers that turn resting on (or focusing) an element into `act()` after INTENT_DWELL_MS,
5341
+ * and leaving it into a cancel of what `act` started (unless it was pressed). `group` names the
5342
+ * intent (e.g. `record:projects:7`); `act` must start its prefetches under that group.
5343
+ */
5344
+ export declare function intentProps(group: string, act: () => number | boolean | undefined): IntentProps;
5345
+
4993
5346
  /** A title card before the recording (real extra edited time). Blank
4994
5347
  * `title` / `subtitle` mean the video's title and the first line of its
4995
5348
  * description. */
@@ -5283,6 +5636,12 @@ declare interface JsonMapEditorConfig {
5283
5636
  };
5284
5637
  }
5285
5638
 
5639
+ /**
5640
+ * A total the person has already seen for this list, from any cached page of it (search, sort
5641
+ * and filters aside) — so "Reading 48,000 invoices" never counts rows they cannot see.
5642
+ */
5643
+ export declare function knownTotalFrom(entries: ReadonlyArray<readonly [unknown, unknown]>): number | null;
5644
+
5286
5645
  declare interface LadderTier {
5287
5646
  after_hours: number;
5288
5647
  notify: string;
@@ -5311,6 +5670,15 @@ declare interface LayoutAssignment {
5311
5670
  default_expanded?: boolean | number | null;
5312
5671
  }
5313
5672
 
5673
+ declare interface LayoutAssignmentLike {
5674
+ field: string;
5675
+ sort?: number | null;
5676
+ group_key?: string | null;
5677
+ col_span?: number | null;
5678
+ is_visible?: boolean | number | null;
5679
+ widget_id?: number | null;
5680
+ }
5681
+
5314
5682
  declare interface LayoutDesign {
5315
5683
  selected: DesignTarget | null;
5316
5684
  onSelect(t: DesignTarget, opts?: SelectOptions): void;
@@ -5382,10 +5750,15 @@ declare interface LayoutDesignProp {
5382
5750
  *
5383
5751
  * Zero CSS shipped — style via className props or data-* attributes.
5384
5752
  */
5385
- export declare function LayoutForm({ form, components, className, style, sectionClassName, tabStripClassName, activeTabClassName, inactiveTabClassName, gridClassName, slotRenderers, itemId, stepperClassName, stepNavClassName, prevButtonClassName, nextButtonClassName, lastStepActions, layoutSlug: _layoutSlug }: LayoutFormProps): default_2.JSX.Element | null;
5753
+ export declare function LayoutForm({ form, components, className, style, sectionClassName, tabStripClassName, activeTabClassName, inactiveTabClassName, gridClassName, slotRenderers, itemId, stepperClassName, stepNavClassName, prevButtonClassName, nextButtonClassName, lastStepActions, layoutSlug: _layoutSlug, loadingFallback }: LayoutFormProps): string | number | bigint | boolean | default_2.JSX.Element | Iterable<default_2.ReactNode> | Promise<string | number | bigint | boolean | default_2.ReactPortal | default_2.ReactElement<unknown, string | default_2.JSXElementConstructor<any>> | Iterable<default_2.ReactNode> | null | undefined> | null;
5386
5754
 
5387
5755
  declare type LayoutFormProps = {
5388
5756
  form: UseNivaroFormReturn;
5757
+ /**
5758
+ * #3229 — shown while the schema or the record is still loading (default: nothing, the form
5759
+ * stays headless). Pass a shape-matched placeholder, e.g. `<FormSkeleton plan={…} />`.
5760
+ */
5761
+ loadingFallback?: default_2.ReactNode;
5389
5762
  components?: ComponentOverrides;
5390
5763
  /** CSS class applied to the <form> element */
5391
5764
  className?: string;
@@ -5453,8 +5826,24 @@ declare interface LayoutGroup {
5453
5826
  summary_hide_empty?: boolean | number | null;
5454
5827
  }
5455
5828
 
5829
+ declare interface LayoutGroupLike {
5830
+ id?: number | null;
5831
+ key: string;
5832
+ label?: string | null;
5833
+ type?: string | null;
5834
+ sort?: number | null;
5835
+ container_id?: number | null;
5836
+ visibility_mode?: string | null;
5837
+ }
5838
+
5456
5839
  export declare type LayoutItem = FormGroupDescriptor | '__ungrouped__';
5457
5840
 
5841
+ declare interface LayoutLike {
5842
+ groups?: LayoutGroupLike[] | null;
5843
+ assignments?: LayoutAssignmentLike[] | null;
5844
+ ungrouped_sort?: number | null;
5845
+ }
5846
+
5458
5847
  declare interface LayoutMeta {
5459
5848
  id: number;
5460
5849
  name: string;
@@ -5588,6 +5977,11 @@ export declare function ListLoadError({ error, onRetry, className }: {
5588
5977
  */
5589
5978
  export declare function listLoadErrorText(err: unknown): string;
5590
5979
 
5980
+ /** The query key a collection list (CollectionBrowserView) reads its metadata under. */
5981
+ export declare function listQueryKeys(collection: string): {
5982
+ meta: QueryKey;
5983
+ };
5984
+
5591
5985
  /**
5592
5986
  * "Copy as …" (#654): the request a list or record view is making, rendered
5593
5987
  * as a curl command, an SDK call and a GraphQL document. Pure — no client,
@@ -5606,6 +6000,22 @@ declare interface ListRequest {
5606
6000
  page?: number;
5607
6001
  }
5608
6002
 
6003
+ /**
6004
+ * A whole list placeholder for surfaces without a real header yet: a header strip (real labels
6005
+ * when given) and rows at the real row height, one bar per column.
6006
+ */
6007
+ export declare function ListSkeleton({ columns, rows, header, rowClassName, className }: {
6008
+ columns: readonly (SkeletonColumn & {
6009
+ label?: ReactNode;
6010
+ grow?: number;
6011
+ })[];
6012
+ rows?: number;
6013
+ /** false = no header strip. */
6014
+ header?: boolean;
6015
+ rowClassName?: string;
6016
+ className?: string;
6017
+ }): JSX.Element;
6018
+
5609
6019
  /**
5610
6020
  * Query-key heads of surfaces that keep themselves live with their own socket handlers (record
5611
6021
  * pages — RECORD_QUERY_HEADS in item-edit/RecordLiveSync — the collection browser's row reads,
@@ -5977,6 +6387,11 @@ declare interface MatrixEditorConfig {
5977
6387
  };
5978
6388
  }
5979
6389
 
6390
+ /** Prefetch requests one tab runs at once; the rest wait their turn. */
6391
+ export declare const MAX_PREFETCH_IN_FLIGHT = 2;
6392
+
6393
+ declare type MaybeIntent = IntentProps | Record<string, never>;
6394
+
5980
6395
  /**
5981
6396
  * The offset from a few samples: the server stamped its answer somewhere inside the round
5982
6397
  * trip, so assume the midpoint, and trust the sample with the shortest round trip (the least
@@ -6137,6 +6552,11 @@ export declare type NavigationContextValue = {
6137
6552
  helpVideosPath?: string;
6138
6553
  };
6139
6554
 
6555
+ declare interface NetInfo {
6556
+ saveData?: boolean;
6557
+ effectiveType?: string;
6558
+ }
6559
+
6140
6560
  export declare function networkdaysBetween(a: Date, b: Date): number;
6141
6561
 
6142
6562
  export declare function NewImportDialog({ open, onOpenChange, definitions, runCounts, onQueued }: {
@@ -6284,7 +6704,7 @@ export declare function NotificationAnalyticsView({ className, compactHeader }:
6284
6704
  * click-through routing via the shared notification-target resolver. Needs
6285
6705
  * `<NivaroProvider>`.
6286
6706
  */
6287
- export declare function NotificationBell({ routes, onNavigate, app, subscribe, buttonClassName, panelClassName, beforeList, extraBadge, mailLogUrl, renderTrigger, allPath, subscriptionsPath, onActionError, panelPlacement, tabBadge }: NotificationBellProps): JSX.Element;
6707
+ export declare function NotificationBell({ routes, onNavigate, app, subscribe, buttonClassName, panelClassName, beforeList, extraBadge, mailLogUrl, renderTrigger, allPath, subscriptionsPath, rulesPath, onActionError, panelPlacement, tabBadge }: NotificationBellProps): JSX.Element;
6288
6708
 
6289
6709
  export declare interface NotificationBellProps {
6290
6710
  /** Where each notification kind lands in the host app. */
@@ -6322,6 +6742,8 @@ export declare interface NotificationBellProps {
6322
6742
  allPath?: string | null;
6323
6743
  /** Path of the subscriptions page — a watch's "Why me?" offers a Manage link. */
6324
6744
  subscriptionsPath?: string | null;
6745
+ /** Path of Profile → Notification rules — "Why?" offers a change link there (#3078). */
6746
+ rulesPath?: string | null;
6325
6747
  /** Called with an error message when an inline action fails. */
6326
6748
  onActionError?: (message: string) => void;
6327
6749
  /** Where the panel opens relative to the trigger. 'below' (default) keeps
@@ -6360,7 +6782,7 @@ export declare interface NotificationBundle<R = unknown> {
6360
6782
  rows: R[];
6361
6783
  }
6362
6784
 
6363
- export declare function NotificationCenterView({ routes, onNavigate, app, mailLogUrl, subscriptionsPath, onNotice, onError, className }: NotificationCenterViewProps): JSX.Element;
6785
+ export declare function NotificationCenterView({ routes, onNavigate, app, mailLogUrl, subscriptionsPath, rulesPath, changesId, onNotice, onError, className }: NotificationCenterViewProps): JSX.Element;
6364
6786
 
6365
6787
  export declare interface NotificationCenterViewProps {
6366
6788
  routes: NotificationRouteMap;
@@ -6369,6 +6791,11 @@ export declare interface NotificationCenterViewProps {
6369
6791
  mailLogUrl?: (mailLogId: number) => string | null;
6370
6792
  /** Host route of the subscriptions page (a watch's "Why me?" links there). */
6371
6793
  subscriptionsPath?: string | null;
6794
+ /** Host route of Profile → Notification rules — "Why?" offers a change link there (#3078). */
6795
+ rulesPath?: string | null;
6796
+ /** #3079 — open on one notification's changes (the email's "See what changed" link lands
6797
+ * with ?changes=<id>). Undefined = read ?changes= from the address bar. */
6798
+ changesId?: number | null;
6372
6799
  /** Toast hooks — hosts wire their own toaster. */
6373
6800
  onNotice?: (message: string) => void;
6374
6801
  onError?: (message: string) => void;
@@ -6837,6 +7264,8 @@ declare interface PartnerCall {
6837
7264
  triggered_by: string | null;
6838
7265
  has_body: boolean;
6839
7266
  user: PartnerCallUser | null;
7267
+ /** #18 — a failed call in plain words + Fix it target (null when it did not fail). */
7268
+ explanation?: IntegrationErrorExplanation | null;
6840
7269
  }
6841
7270
 
6842
7271
  /** One user recorded on a call — via nivaro_users, batch-resolved server-side
@@ -7062,7 +7491,7 @@ export declare interface PersonProfileData {
7062
7491
  } | null;
7063
7492
  }
7064
7493
 
7065
- export declare type PersonProfileTab = 'overview' | 'access' | 'activity' | 'tools';
7494
+ export declare type PersonProfileTab = 'overview' | 'access' | 'activity' | 'did' | 'tools';
7066
7495
 
7067
7496
  /** Mirror of the server's PersonProfile (services/user-profile.ts). */
7068
7497
  declare interface PersonRef {
@@ -7112,7 +7541,7 @@ declare type PipelineBinding = {
7112
7541
  dimensions?: PipelineOwnerDimension[];
7113
7542
  };
7114
7543
 
7115
- export declare type PipelineEditorSection = 'states' | 'transitions' | 'branches' | 'bindings' | 'matrix' | 'simulator' | 'canvas' | 'flow-map' | 'replay' | 'cumulative-flow' | 'migration' | 'coverage' | 'lint' | 'skip-report' | 'ai-review' | 'versions' | 'matrix-versions';
7544
+ export declare type PipelineEditorSection = 'states' | 'transitions' | 'branches' | 'bindings' | 'matrix' | 'simulator' | 'canvas' | 'flow-map' | 'replay' | 'cumulative-flow' | 'migration' | 'coverage' | 'lint' | 'skip-report' | 'history' | 'ai-review' | 'versions' | 'matrix-versions';
7116
7545
 
7117
7546
  export declare function PipelineEditorView({ templateId, onBack, onExport, hidden }: {
7118
7547
  templateId: string;
@@ -7282,12 +7711,22 @@ export declare function precisionOf(options: unknown, fallback?: number): number
7282
7711
  /** API prefetches one tab may make in any rolling minute. */
7283
7712
  export declare const PREFETCH_PER_MINUTE = 30;
7284
7713
 
7714
+ /**
7715
+ * Warm a collection list: its metadata (fields, relations, browse config) — what the list waits
7716
+ * on before it can ask for rows, and what shapes its loading skeleton. The rows themselves
7717
+ * depend on the person's saved view, sort and filters, so they are not guessed.
7718
+ */
7719
+ export declare function prefetchCollectionList(qc: QueryClient, client: NivaroClient, collection: string, opts?: {
7720
+ group?: string | null;
7721
+ }): number;
7722
+
7285
7723
  /**
7286
7724
  * Warm a record page: its readOne and the layout it will open with. Returns how many requests
7287
- * went out (0 when both were fresh or the budget was spent).
7725
+ * were queued (0 when both were fresh or the budget was spent).
7288
7726
  */
7289
7727
  export declare function prefetchRecord(qc: QueryClient, client: NivaroClient, collection: string, id: string | number, opts?: {
7290
7728
  layout?: boolean;
7729
+ group?: string | null;
7291
7730
  }): number;
7292
7731
 
7293
7732
  export declare interface PreparedReplayClip {
@@ -7396,7 +7835,7 @@ export declare function ProfileView({ userId, className, initialTab, extra, push
7396
7835
  userId?: string | null;
7397
7836
  className?: string;
7398
7837
  /** Which tab an admin lands on for someone else's page. */
7399
- initialTab?: 'overview' | 'access' | 'activity' | 'tools';
7838
+ initialTab?: 'overview' | 'access' | 'activity' | 'did' | 'tools';
7400
7839
  /** Host-only cards for the OWN profile (a browser-push card, say), rendered in the right column. */
7401
7840
  extra?: React.ReactNode;
7402
7841
  /** Service worker the Browser notifications card registers (same origin); false hides the card. */
@@ -8514,6 +8953,18 @@ export declare function registerHelpVideoPage(key: string): () => void;
8514
8953
 
8515
8954
  export declare function registerLayoutSlot(plugin: LayoutSlotPlugin): void;
8516
8955
 
8956
+ /**
8957
+ * "Is this tab in the middle of something live that a reload would cut off?" — a live huddle, a
8958
+ * screen clip being recorded. Not unsaved work (that is lib/unsaved-work.ts, the only source of
8959
+ * "unsaved changes"): nothing here is lost to a reload, it is interrupted. Read by the automatic
8960
+ * idle reload (lib/idle-reload.ts), which never reloads while any hold is on.
8961
+ *
8962
+ * Leaf module: the huddle store and the screen clip button report here, so the reload code never
8963
+ * imports the chat code.
8964
+ */
8965
+ /** Report something live; the returned function removes it. `active` is read at decision time. */
8966
+ export declare function registerReloadHold(label: string, active?: () => boolean): () => void;
8967
+
8517
8968
  /** Called by a host's recorder module. Returns an undo for tests / HMR. */
8518
8969
  export declare function registerReplayCapture(fn: Capture, snap?: Snapshot): () => void;
8519
8970
 
@@ -8533,8 +8984,12 @@ export declare function RelatedRecordsPanel({ collection, itemId, title, default
8533
8984
  defaultExpanded?: boolean;
8534
8985
  }): JSX.Element;
8535
8986
 
8536
- export declare function RelationCombobox({ collection, value, onChange, disabled, placeholder, extraFilter, autoSelectSingle, optionSort, requiredParent, facets, fieldKey, narrowedBy, pinned, missPlacement }: {
8987
+ export declare function RelationCombobox({ collection, value, onChange, disabled, placeholder, extraFilter, autoSelectSingle, optionSort, requiredParent, facets, fieldKey, narrowedBy, pinned, missPlacement, defaultOpen, onClose }: {
8537
8988
  collection: string;
8989
+ /** Open the options panel on mount (a list cell's inline editor, #3224). */
8990
+ defaultOpen?: boolean;
8991
+ /** Called when the options panel closes (picked, clicked away or Escape). */
8992
+ onClose?: () => void;
8538
8993
  /** The record-form placement this picker sits on: searches that find
8539
8994
  * nothing are counted there (#2726). Absent = not reported. */
8540
8995
  missPlacement?: PickerMissPlacement | null;
@@ -8622,6 +9077,12 @@ export declare function releaseGatedReadsOnSharedFetch(cache: QueryCacheLike): (
8622
9077
 
8623
9078
  export declare function releaseHeld(): HeldItem[];
8624
9079
 
9080
+ /** "Pat updated 40 workflows: Status → On Hold"; a group of one reads as its entry. */
9081
+ export declare function renderActivityGroupSentence(entries: ActivityEntryInput[], labels?: ActivityLabels | null): string;
9082
+
9083
+ /** One readable sentence for one activity entry. */
9084
+ export declare function renderActivitySentence(entry: ActivityEntryInput, labels?: ActivityLabels | null): string;
9085
+
8625
9086
  export declare type RenderFieldProps = {
8626
9087
  field: any;
8627
9088
  value: unknown;
@@ -9405,6 +9866,27 @@ declare type Size = {
9405
9866
  h: number;
9406
9867
  };
9407
9868
 
9869
+ export declare function SkeletonBar({ shape, width, className, style }: {
9870
+ shape?: CellShape;
9871
+ width?: string | number;
9872
+ className?: string;
9873
+ style?: CSSProperties;
9874
+ }): JSX.Element;
9875
+
9876
+ export declare interface SkeletonColumn {
9877
+ key: string;
9878
+ shape?: CellShape;
9879
+ /** Fixed column width in px (a resized column), when the list has one. */
9880
+ width?: number | null;
9881
+ }
9882
+
9883
+ /** Real list columns → skeleton columns (types decide the shapes). */
9884
+ export declare function skeletonColumnsFrom(keys: readonly string[], fields: ReadonlyArray<{
9885
+ field: string;
9886
+ type?: string | null;
9887
+ interface?: string | null;
9888
+ }>, widths?: Record<string, number | undefined>): SkeletonColumn[];
9889
+
9408
9890
  declare type SkipCondition = {
9409
9891
  type: 'no_owners';
9410
9892
  } | {
@@ -9488,6 +9970,80 @@ export declare type SlotRendererProps = {
9488
9970
  defaultExpanded: boolean;
9489
9971
  };
9490
9972
 
9973
+ /**
9974
+ * "This is taking a while" with a reason (#3231). Renders nothing until the read has been
9975
+ * loading ~3 s, then says what it is waiting on in plain words ("Reading 48,000 invoices",
9976
+ * "Waiting for Fusion", "Waiting for a free slot — busy right now") with the elapsed time.
9977
+ *
9978
+ * variant 'inline' — a line inside a loading area (widgets, query results, forms)
9979
+ * variant 'pill' — a floating chip; the caller positions it (absolute) so nothing moves
9980
+ *
9981
+ * `children` ride beside the text (e.g. a Run in background button). Reads that can run in the
9982
+ * background use BackgroundRunOffer, which shows the same reason.
9983
+ */
9984
+ export declare function SlowNotice({ variant, className, children, reason: given, ...opts }: UseSlowReasonOptions & {
9985
+ variant?: 'inline' | 'pill';
9986
+ className?: string;
9987
+ children?: ReactNode;
9988
+ /** A reason the host already computed with useSlowReason (skips the hook's own asking). */
9989
+ reason?: SlowReason;
9990
+ }): JSX.Element | null;
9991
+
9992
+ export declare function SlowNoticeView({ reason, variant, className, extra }: {
9993
+ reason: SlowReason;
9994
+ variant?: 'inline' | 'pill';
9995
+ className?: string;
9996
+ extra?: ReactNode;
9997
+ }): JSX.Element;
9998
+
9999
+ export declare interface SlowReadRequest {
10000
+ method: 'GET' | 'POST';
10001
+ /** API path (with or without /api); a query string is ignored. */
10002
+ url: string;
10003
+ }
10004
+
10005
+ /** The server's answer (api/src/services/wait-reasons.ts WaitAnswer). */
10006
+ export declare interface SlowReadWaitAnswer {
10007
+ found: boolean;
10008
+ kind?: WaitKind;
10009
+ subject?: string | null;
10010
+ shared?: boolean;
10011
+ position?: number | null;
10012
+ of?: number | null;
10013
+ age_ms?: number;
10014
+ statement_ms?: number;
10015
+ rows_so_far?: number;
10016
+ collection?: string | null;
10017
+ /** About how many rows the table holds — administrators only. */
10018
+ table_rows?: number | null;
10019
+ peer?: boolean;
10020
+ }
10021
+
10022
+ export declare interface SlowReason {
10023
+ /** Loading for at least `afterMs`. */
10024
+ slow: boolean;
10025
+ /** How long it has been loading, ms (0 while not slow). */
10026
+ elapsedMs: number;
10027
+ /** The plain sentence, or null when the server knows nothing useful. */
10028
+ text: string | null;
10029
+ answer: SlowReadWaitAnswer | null;
10030
+ }
10031
+
10032
+ declare interface SlowReasonContext {
10033
+ /** Plural noun for what a list reads, e.g. "invoices". */
10034
+ noun?: string | null;
10035
+ /** What the page calls this read, e.g. "PUB health" (a query or widget title). */
10036
+ label?: string | null;
10037
+ /** Rows the person can see, when the page already knows (a total from an earlier load). */
10038
+ knownRows?: number | null;
10039
+ }
10040
+
10041
+ /**
10042
+ * One plain sentence for what a slow read is waiting on, or null when the server knows nothing
10043
+ * useful (the request already finished, another replica not seen yet, cloud mode).
10044
+ */
10045
+ export declare function slowReasonText(answer: SlowReadWaitAnswer | null | undefined, ctx?: SlowReasonContext): string | null;
10046
+
9491
10047
  /**
9492
10048
  * Two-phase capture: take the moment NOW (the clip's events, or the live
9493
10049
  * recording's offset), upload later. A support dialog snapshots when it
@@ -9961,6 +10517,23 @@ export declare type SynonymGroups = ReadonlyArray<ReadonlyArray<string>>;
9961
10517
 
9962
10518
  declare type TabKey = 'catalog' | 'rules' | 'subscriptions' | 'history' | 'widget_alerts' | 'widget_alert_history' | 'anomaly_rules' | 'anomaly_history';
9963
10519
 
10520
+ /**
10521
+ * Body rows for a real `<table>` whose header is already on screen: one cell per real column
10522
+ * (plus the table's own leading/trailing cells), with the row height and cell padding of the
10523
+ * real rows. Render inside `<tbody>`.
10524
+ */
10525
+ export declare function TableSkeletonRows({ columns, rows, rowClassName, cellClassName, leadingCells, trailingCells, cellStyle, barClassName }: {
10526
+ columns: readonly SkeletonColumn[];
10527
+ rows?: number;
10528
+ rowClassName?: string;
10529
+ cellClassName?: string;
10530
+ leadingCells?: number;
10531
+ trailingCells?: number;
10532
+ /** Per-column style (pinned offsets, fixed widths) — the real cells' own. */
10533
+ cellStyle?: (key: string) => CSSProperties | undefined;
10534
+ barClassName?: string;
10535
+ }): JSX.Element;
10536
+
9964
10537
  export declare type TabState = {
9965
10538
  activeTab: string | null;
9966
10539
  setActiveTab: (key: string) => void;
@@ -10313,6 +10886,15 @@ declare type UploadRow = {
10313
10886
 
10314
10887
  export declare function useActiveGamedays(enabled?: boolean): ActiveGameday[];
10315
10888
 
10889
+ /**
10890
+ * Labels for a page of entries, read AS THE VIEWER (POST /api/activity/labels): records they
10891
+ * cannot see come back null ("a record you can't see"), people by display name, collection /
10892
+ * field / choice labels. Adds the viewer's time zone. One request per distinct page.
10893
+ */
10894
+ export declare function useActivityLabels(entries: ActivityEntryInput[], opts?: {
10895
+ enabled?: boolean;
10896
+ }): ActivityLabels | undefined;
10897
+
10316
10898
  /**
10317
10899
  * `useAfterIdle(ms)` — false on mount, true once the browser has had `ms` of
10318
10900
  * settle time after the first paint. Gate secondary reads on it (freshness
@@ -10645,10 +11227,19 @@ export declare function useHelpVideosFor(ctx: {
10645
11227
  item?: string | number;
10646
11228
  state?: string;
10647
11229
  page?: string;
11230
+ /** #18: the route (`/collections/purchase_orders/12`); the server derives
11231
+ * the screen, screens like it, the record type and the area from it. */
11232
+ path?: string;
11233
+ /** #18: which app `path` belongs to (default `admin`). */
11234
+ app?: string;
11235
+ /** #18: comma list of glossary terms the screen shows. */
11236
+ terms?: string;
10648
11237
  }, enabled?: boolean): UseQueryResult<NoInfer<{
10649
11238
  data: HelpVideoDto[];
10650
11239
  can_author: boolean;
10651
11240
  state: string | null;
11241
+ /** #18: the strong match this person has not finished. Older servers: absent. */
11242
+ best_id?: string | null;
10652
11243
  }>, Error>;
10653
11244
 
10654
11245
  /** "Show me on this page": the published version's labelled clicks. */
@@ -10911,6 +11502,16 @@ export declare function usePersonProfile(userId: string | null | undefined): Use
10911
11502
 
10912
11503
  export declare function usePlanGridHost(): PlanGridHost | null;
10913
11504
 
11505
+ /**
11506
+ * `const intent = usePrefetchIntent()` → spread `{...intent.record(collection, id)}` on a row or
11507
+ * button that opens a record, `{...intent.list(collection)}` on one that opens a list. Inert
11508
+ * without a client (nothing to fetch with).
11509
+ */
11510
+ export declare function usePrefetchIntent(): {
11511
+ record: (collection: string, id: string | number) => MaybeIntent;
11512
+ list: (collection: string) => MaybeIntent;
11513
+ };
11514
+
10914
11515
  /** Who else is viewing one record (joins its room; names only). */
10915
11516
  export declare function usePresence(collection: string | null | undefined, item: string | number | null | undefined): RecordPresence;
10916
11517
 
@@ -11121,6 +11722,23 @@ action: IntegrationSignalRow["actions"][number];
11121
11722
 
11122
11723
  export declare function useSignals(tab?: string): UseQueryResult<NoInfer<IntegrationSignalsSnapshot>, Error>;
11123
11724
 
11725
+ /**
11726
+ * #3231 — after ~3 s of loading, ask the server what this read is waiting on and keep asking
11727
+ * every few seconds while it still loads (hidden tabs skip a beat). Nothing is asked for a read
11728
+ * that finishes in time, so a fast page makes no extra call.
11729
+ */
11730
+ export declare function useSlowReason(opts: UseSlowReasonOptions): SlowReason;
11731
+
11732
+ declare interface UseSlowReasonOptions extends SlowReasonContext {
11733
+ /** True while the page's own read is still loading. */
11734
+ pending: boolean;
11735
+ /** The read the page is making; null = ask nothing (the notice still says it is slow). */
11736
+ request: SlowReadRequest | null;
11737
+ /** How long before the read counts as slow (default 3 s). */
11738
+ afterMs?: number;
11739
+ enabled?: boolean;
11740
+ }
11741
+
11124
11742
  export declare function useSnooze(): {
11125
11743
  add: UseMutationResult<unknown, Error, SnoozeInput, unknown>;
11126
11744
  remove: UseMutationResult<unknown, Error, number, unknown>;
@@ -11327,6 +11945,17 @@ declare type Visibility = {
11327
11945
  role_ids: string[];
11328
11946
  };
11329
11947
 
11948
+ /**
11949
+ * "This is taking a while" with a reason (#3231) — the client half of
11950
+ * GET /api/background-runs/waiting.
11951
+ *
11952
+ * The server tells a page what ITS OWN unfinished read is waiting on (a free heavy-read slot and
11953
+ * where it stands in line, an external partner, an uncached query or widget, a statement still
11954
+ * running); this file turns that answer into one plain sentence. Pure — the hook and the notice
11955
+ * live in hooks/useSlowReason.ts and components/SlowNotice.tsx.
11956
+ */
11957
+ declare type WaitKind = 'slot' | 'external' | 'query' | 'widget' | 'database' | 'auth' | 'working';
11958
+
11330
11959
  /** One step of "Show me on this page" (GET /help-videos/:id/walk). */
11331
11960
  export declare interface WalkStep {
11332
11961
  label: string;