@proteos/sdk 0.46.0 → 0.48.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.
@@ -26,6 +26,7 @@ export type Channel =
26
26
  | 'teams-meeting'
27
27
  | 'webex-meeting'
28
28
  | 'adhoc'
29
+ | 'phone'
29
30
  | 'instagram'
30
31
  | 'messenger'
31
32
  | 'x'
@@ -52,6 +53,83 @@ export type ConnectorKey =
52
53
  | 'adhoc-meeting'
53
54
  | 'google-calendar-meeting'
54
55
  | 'microsoft-calendar-meeting'
56
+ | 'twilio-phone'
57
+ | 'aircall'
58
+ | 'webhook-cti'
59
+
60
+ /**
61
+ * Telephony lifecycle of one phone call, carried in the call conversation's
62
+ * `metadata.call_status` (a call is a conversation plus call facts — no
63
+ * dedicated resource). Status callbacks apply through a monotonic guard, so a
64
+ * terminal value never regresses.
65
+ */
66
+ /**
67
+ * Body of POST /conversations/v1/calls/token — the softphone's browser-calling
68
+ * grant for one connection. The client identity is derived server-side from
69
+ * the authenticated user.
70
+ */
71
+ export interface MintCallTokenRequest {
72
+ connection_id: string
73
+ }
74
+
75
+ /**
76
+ * Which system answers a provider phone number: connected to this platform,
77
+ * in use by another integration (connecting takes it over, disconnecting
78
+ * restores it), or configured nowhere (available).
79
+ */
80
+ export type PhoneNumberStatus = 'connected' | 'available' | 'in_use'
81
+
82
+ /** One dispatch target: a platform user (id) or a team (slug). */
83
+ export interface RoutingTarget {
84
+ type: 'user' | 'team'
85
+ id: string
86
+ }
87
+
88
+ /**
89
+ * A connected number's user-level dispatch: who a call rings. v1 rings every
90
+ * expanded target in parallel (teams expand to their direct members at ring
91
+ * time); the object shape leaves room for later strategy options. Empty
92
+ * targets fall back to the connection-wide routing, then busy.
93
+ */
94
+ export interface PhoneNumberRouting {
95
+ targets: RoutingTarget[]
96
+ }
97
+
98
+ /** One provider phone number on a phone connection. */
99
+ export interface PhoneNumber {
100
+ /** Provider-side number identity (vendor vocabulary stays in the adapter). */
101
+ external_id: string
102
+ phone_number: string
103
+ friendly_name: string
104
+ status: PhoneNumberStatus
105
+ /** Display-only description of what an in_use number is used by. */
106
+ used_by?: string
107
+ routing: PhoneNumberRouting
108
+ }
109
+
110
+ /** Routing update for one phone number (connect/disconnect are actions). */
111
+ export interface UpdatePhoneNumberRequest {
112
+ routing?: PhoneNumberRouting
113
+ }
114
+
115
+ /** The minted grant the Voice JS SDK consumes (`new Device(token, { edge })`). */
116
+ export interface CallTokenResponse {
117
+ token: string
118
+ identity: string
119
+ /** Vendor edge to connect through ('dublin' for the EU default). */
120
+ edge?: string
121
+ expires_at: string
122
+ }
123
+
124
+ export type CallStatus =
125
+ | 'queued'
126
+ | 'ringing'
127
+ | 'in_progress'
128
+ | 'completed'
129
+ | 'no_answer'
130
+ | 'busy'
131
+ | 'failed'
132
+ | 'canceled'
55
133
 
56
134
  /**
57
135
  * Who operates the integration mechanics behind a connector: Proteos' own
@@ -486,8 +564,27 @@ export interface UpdateConnectionRequest {
486
564
  settings?: Record<string, unknown>
487
565
  }
488
566
 
567
+ /**
568
+ * Optional body of POST /connections/:id/install. `input` carries
569
+ * user-supplied install credentials for direct-install connectors
570
+ * (twilio-phone, aircall, webhook-cti — connector-documented snake_case
571
+ * keys); omit it for OAuth/hosted connectors.
572
+ */
573
+ export interface InstallConnectionRequest {
574
+ input?: Record<string, string>
575
+ }
576
+
489
577
  export interface InstallConnectionResponse {
490
- authorization_url: string
578
+ /** OAuth/hosted installs: open this in a popup. Absent on direct installs. */
579
+ authorization_url?: string
580
+ /** Direct installs complete server-side in this request. */
581
+ is_completed?: boolean
582
+ /**
583
+ * A direct-install connector's one-time setup handout (callback URLs to
584
+ * paste provider-side; a minted token shown exactly once — only its hash is
585
+ * stored server-side). Never retrievable again.
586
+ */
587
+ setup?: Record<string, string>
491
588
  }
492
589
 
493
590
  export interface SendMessageRequest {
@@ -852,6 +949,11 @@ export interface ListConnectionsQuery extends PaginationQuery {
852
949
  connector_key?: string
853
950
  channel?: string
854
951
  scope?: string
952
+ /**
953
+ * Narrows to the user-scoped connections one person owns. Org-scoped
954
+ * connections have no owner and never match.
955
+ */
956
+ owner_id?: string
855
957
  status?: string
856
958
  }
857
959
 
package/src/data/types.ts CHANGED
@@ -18,6 +18,15 @@ export type RecordData = Record<string, any>
18
18
  * (via bracket syntax) include `[eq]`, `[ne]`, `[gt]`, `[gte]`, `[lt]`,
19
19
  * `[lte]`, `[in]` (pipe-separated), `[not_in]`, `[contains]`, `[starts_with]`,
20
20
  * `[ends_with]`, `[empty]`, `[not_empty]`. Missing operator defaults to `[eq]`.
21
+ * A filter key may reach one hop through a relation attribute with a dotted
22
+ * path (`"company_id.name[contains]": "acme"`).
23
+ *
24
+ * For nested AND/OR trees, pass a filter group (the same wire dialect as
25
+ * `List.filters` / `visible_when`: `{logical_operator, elements: [{field,
26
+ * operator, value}], groups}`, string values, pipe-joined for in/not_in)
27
+ * JSON-encoded under the reserved `_filter` key:
28
+ * `{ _filter: JSON.stringify(group) }`. The server ANDs it with any flat
29
+ * filter params.
21
30
  */
22
31
  export interface ListRecordsOptions {
23
32
  /** Page number (0-indexed). First page is 0. */
@@ -61,6 +61,19 @@ export interface ActionService {
61
61
  slug: string,
62
62
  params: Record<string, unknown>,
63
63
  ): Promise<unknown>
64
+
65
+ /**
66
+ * Invokes an `entity_batch` action against a SET of records. The whole id
67
+ * array reaches the action in ONE invocation, so the result is a single
68
+ * aggregate payload, not one per record. `recordIds` must be non-empty;
69
+ * the server dedupes it, preserving order. Returns the unwrapped `result`.
70
+ */
71
+ invokeBatch(
72
+ entitySlug: string,
73
+ slug: string,
74
+ recordIds: string[],
75
+ params: Record<string, unknown>,
76
+ ): Promise<unknown>
64
77
  }
65
78
 
66
79
  /**
@@ -117,4 +130,20 @@ export class ActionServiceImpl implements ActionService {
117
130
  )
118
131
  return response.result
119
132
  }
133
+
134
+ async invokeBatch(
135
+ entitySlug: string,
136
+ slug: string,
137
+ recordIds: string[],
138
+ params: Record<string, unknown>,
139
+ ): Promise<unknown> {
140
+ // The one invoke route whose body is an envelope rather than the params
141
+ // object itself — an id array has nowhere to live in the path.
142
+ const response = await this.client.request<InvokeActionResponse>(
143
+ 'POST',
144
+ `/functions/v1/entities/${entitySlug}/actions/${slug}/invoke`,
145
+ { record_ids: recordIds, params },
146
+ )
147
+ return response.result
148
+ }
120
149
  }
@@ -5,11 +5,13 @@ import { type AuditFields, AuditFieldsSchema } from '../types/common.js'
5
5
 
6
6
  /**
7
7
  * Scope of an action. `entity`-scoped actions are invoked against a
8
- * specific record; `global` actions take no record context.
8
+ * specific record; `entity_batch` actions against a SET of records of one
9
+ * entity (one invocation carrying every id, never one per record — they back
10
+ * a list's toolbar buttons); `global` actions take no record context.
9
11
  */
10
- export type ActionScope = 'entity' | 'global'
12
+ export type ActionScope = 'entity' | 'entity_batch' | 'global'
11
13
 
12
- export const ActionScopeSchema = z.enum(['entity', 'global'])
14
+ export const ActionScopeSchema = z.enum(['entity', 'entity_batch', 'global'])
13
15
 
14
16
  /**
15
17
  * Deployable, user-invokable operation. Mirrors `functionsmodel.Action`
@@ -21,7 +23,7 @@ export interface Action extends AuditFields {
21
23
  org_id: string
22
24
  module_slug: string
23
25
  scope: ActionScope
24
- /** Present only when `scope === 'entity'`. */
26
+ /** Present only when `scope` is `entity` or `entity_batch`. */
25
27
  entity?: string
26
28
  name: string
27
29
  is_active: boolean
package/src/index.ts CHANGED
@@ -227,6 +227,8 @@ export type {
227
227
  AutomatedFilterConfig,
228
228
  AutomatedSignal,
229
229
  BlockContactRequest,
230
+ CallStatus,
231
+ CallTokenResponse,
230
232
  Channel,
231
233
  Connection,
232
234
  ConnectionCredentials,
@@ -310,7 +312,12 @@ export type {
310
312
  MessageRecipient,
311
313
  MessageService,
312
314
  MessageStatus,
315
+ MintCallTokenRequest,
313
316
  MistranscribedTerm,
317
+ PhoneNumber,
318
+ PhoneNumberRouting,
319
+ PhoneNumberStatus,
320
+ RoutingTarget,
314
321
  MistranscribedTermService,
315
322
  MistranscribedTermStatus,
316
323
  MistranscriptionSuggestionSource,
@@ -349,6 +356,7 @@ export type {
349
356
  UpdateConversationTypeRequest,
350
357
  UpdateDraftRequest,
351
358
  UpdateGlossaryTermRequest,
359
+ UpdatePhoneNumberRequest,
352
360
  VoiceService,
353
361
  VoiceTranscriptionStream,
354
362
  } from './conversation/index.js'
@@ -25,7 +25,8 @@
25
25
  "currency",
26
26
  "knowledge-text",
27
27
  "file",
28
- "file-viewer"
28
+ "file-viewer",
29
+ "record-filter"
29
30
  ],
30
31
  "controls": {
31
32
  "string": {
@@ -33,7 +34,8 @@
33
34
  "compatible": [
34
35
  "text",
35
36
  "textarea",
36
- "password"
37
+ "password",
38
+ "record-filter"
37
39
  ],
38
40
  "byFormat": {
39
41
  "email": {
@@ -20,6 +20,9 @@ export const LayoutElementType = {
20
20
  Component: 'component',
21
21
  Divider: 'divider',
22
22
  Text: 'text',
23
+ RecordFilter: 'record_filter',
24
+ List: 'list',
25
+ WorkflowTrigger: 'workflow_trigger',
23
26
  } as const
24
27
  export type LayoutElementType = (typeof LayoutElementType)[keyof typeof LayoutElementType]
25
28
 
@@ -66,11 +69,19 @@ export type CardElement = CommonProps & {
66
69
  content: LayoutElement
67
70
  }
68
71
 
72
+ /**
73
+ * One switchable view inside a `tabs` element. `visible_when` hides the tab
74
+ * when it does not match the record; `default_when` makes it the initially
75
+ * shown tab when it matches. Both are evaluated live against the record being
76
+ * viewed or edited. Resolution order: first visible tab (document order) whose
77
+ * `default_when` matches → `TabsElement.default_tab_id` → first visible tab.
78
+ */
69
79
  export type LayoutTab = {
70
80
  id: string
71
81
  label: string
72
82
  icon?: string
73
83
  visible_when?: FilterGroup
84
+ default_when?: FilterGroup
74
85
  content: LayoutElement
75
86
  }
76
87
 
@@ -151,10 +162,99 @@ export type ComponentElement = CommonProps & {
151
162
  reserved_height?: number
152
163
  }
153
164
 
165
+ /**
166
+ * A filter builder placed directly on a page. It owns no data of its own: it
167
+ * publishes the filter (and the chosen subject entity) under its element id,
168
+ * and every `list` element naming that id in `filter_element_id` renders the
169
+ * filtered rows.
170
+ *
171
+ * `subject_entity` pins which entity the filter is authored against. When it is
172
+ * absent the element renders a subject picker over the entities its bound lists
173
+ * offer — one entry per bound list, since a list carries exactly one entity.
174
+ *
175
+ * The filter is in-memory: it resets on reload rather than persisting per user.
176
+ */
177
+ export type RecordFilterElement = CommonProps & {
178
+ type: 'record_filter'
179
+ subject_entity?: string
180
+ /** `toolbar` (default) is the Filter button + active chips, matching every
181
+ * list view. `panel` is the always-open AND/OR editor, for a page whose
182
+ * point IS the filter. */
183
+ variant?: 'toolbar' | 'panel'
184
+ /** Nested AND/OR groups. Defaults to true — the records query carries the
185
+ * whole tree, so there is no reason to hide the affordance. */
186
+ is_complex_enabled?: boolean
187
+ }
188
+
189
+ /**
190
+ * Renders records of a configured List — the record-agnostic sibling of
191
+ * `related_list`, and the only way to show records on a page that has no
192
+ * record of its own.
193
+ *
194
+ * The List supplies everything about presentation and behaviour: columns,
195
+ * sorting, base filters, toolbar actions, selection mode, and which page a row
196
+ * opens. Naming more than one list makes the element switchable; the active one
197
+ * is chosen by the bound filter's subject picker, or by the element's own
198
+ * switcher when it is unbound.
199
+ *
200
+ * `filter_element_id` binds the element to a `record_filter` on the same page.
201
+ * The bound filter is ANDed with the list's own saved filters — it narrows the
202
+ * list, it never replaces what the list declared.
203
+ */
204
+ export type ListElement = CommonProps & {
205
+ type: 'list'
206
+ /** Slugs of the lists this element can render. At least one. */
207
+ list_slugs: string[]
208
+ /** Element id of the `record_filter` driving this list. Unbound renders the
209
+ * list with its own filters only. */
210
+ filter_element_id?: string
211
+ /**
212
+ * Record-page alternative to `filter_element_id`: the attribute on THIS
213
+ * page's record holding a saved filter (as written by the `record-filter`
214
+ * control). The list then renders what the record's own filter selects —
215
+ * a saved-segment record showing its matches.
216
+ *
217
+ * Mutually exclusive with `filter_element_id`: two filters driving one list
218
+ * has no defined precedence, so the layout validator rejects both at once.
219
+ * Record pages only — a standalone page has no record to read.
220
+ */
221
+ filter_attribute?: string
222
+ /**
223
+ * Attribute on this page's record naming the subject entity slug. The
224
+ * element renders whichever of `list_slugs` targets that entity. Without
225
+ * it the first configured list wins. Pairs with `filter_attribute`.
226
+ */
227
+ subject_entity_attribute?: string
228
+ page_size?: number
229
+ }
230
+
154
231
  export type DividerElement = CommonProps & {
155
232
  type: 'divider'
156
233
  }
157
234
 
235
+ /**
236
+ * An always-visible button that starts a manual run of `workflow` (by key) —
237
+ * the in-layout counterpart of a `kind: workflow` toolbar action. `inputs`
238
+ * maps the manual trigger's input_schema field names to Liquid templates
239
+ * rendered against the page scope; `skip_confirmation` fires immediately when
240
+ * every required input resolves. While the run is in flight the element shows
241
+ * the step progress inline in place of the button.
242
+ */
243
+ export type WorkflowTriggerElement = CommonProps & {
244
+ type: 'workflow_trigger'
245
+ workflow: string
246
+ label: string
247
+ icon?: string
248
+ inputs?: Record<string, string>
249
+ skip_confirmation?: boolean
250
+ /**
251
+ * Optional string attribute on the page's record where the element persists
252
+ * the id of the execution it starts — and reads it back, so progress
253
+ * survives reloads and is shared by every viewer. Record pages only.
254
+ */
255
+ execution_attribute?: string
256
+ }
257
+
158
258
  export type TextVariant = 'heading' | 'subheading' | 'body' | 'caption' | 'callout'
159
259
 
160
260
  export type TextElement = CommonProps & {
@@ -175,6 +275,9 @@ export type LayoutElement =
175
275
  | ComponentElement
176
276
  | DividerElement
177
277
  | TextElement
278
+ | RecordFilterElement
279
+ | ListElement
280
+ | WorkflowTriggerElement
178
281
 
179
282
  // ──────────────────────────────────────────────────────────── Schemas ──
180
283
 
@@ -224,6 +327,7 @@ export const LayoutElementSchema: z.ZodType<LayoutElement> = z.lazy(() =>
224
327
  label: z.string().min(1),
225
328
  icon: z.string().optional(),
226
329
  visible_when: FilterGroupSchema.optional(),
330
+ default_when: FilterGroupSchema.optional(),
227
331
  content: LayoutElementSchema,
228
332
  }),
229
333
  ),
@@ -265,10 +369,36 @@ export const LayoutElementSchema: z.ZodType<LayoutElement> = z.lazy(() =>
265
369
  props: z.record(z.unknown()).optional(),
266
370
  reserved_height: z.number().positive().optional(),
267
371
  }),
372
+ z.object({
373
+ type: z.literal('record_filter'),
374
+ ...commonPropsShape,
375
+ subject_entity: z.string().min(1).optional(),
376
+ variant: z.enum(['toolbar', 'panel']).optional(),
377
+ is_complex_enabled: z.boolean().optional(),
378
+ }),
379
+ z.object({
380
+ type: z.literal('list'),
381
+ ...commonPropsShape,
382
+ list_slugs: z.array(z.string().min(1)).min(1),
383
+ filter_element_id: z.string().min(1).optional(),
384
+ filter_attribute: z.string().min(1).optional(),
385
+ subject_entity_attribute: z.string().min(1).optional(),
386
+ page_size: z.number().int().positive().optional(),
387
+ }),
268
388
  z.object({
269
389
  type: z.literal('divider'),
270
390
  ...commonPropsShape,
271
391
  }),
392
+ z.object({
393
+ type: z.literal('workflow_trigger'),
394
+ ...commonPropsShape,
395
+ workflow: z.string().min(1),
396
+ label: z.string().min(1),
397
+ icon: z.string().optional(),
398
+ inputs: z.record(z.string()).optional(),
399
+ skip_confirmation: z.boolean().optional(),
400
+ execution_attribute: z.string().min(1).optional(),
401
+ }),
272
402
  z.object({
273
403
  type: z.literal('text'),
274
404
  ...commonPropsShape,
@@ -26,6 +26,9 @@ export {
26
26
  LayoutElementSchema,
27
27
  LayoutElementType,
28
28
  type LayoutTab,
29
+ type ListElement,
30
+ type RecordFilterElement,
31
+ type WorkflowTriggerElement,
29
32
  type RelatedListElement,
30
33
  type RelatedRecordElement,
31
34
  type RowElement,
package/src/meta/types.ts CHANGED
@@ -864,6 +864,19 @@ export interface UpdateComponentRequest {
864
864
 
865
865
  /**
866
866
  * List column definition.
867
+ *
868
+ * `attribute` is an attribute name or a dot path, told apart by the type of
869
+ * the first segment:
870
+ *
871
+ * - `name` — an attribute on the list's own entity.
872
+ * - `address.city` — a leaf inside one of its `object` attributes.
873
+ * - `company_id.name` — a field of the RELATED record, reached through a
874
+ * relation attribute (the first segment is the FK).
875
+ *
876
+ * A bare `object` attribute is not a valid column — it carries no value of
877
+ * its own, only leaves. Sorting follows the same grammar minus the relation
878
+ * hop: an attribute or an object path can be ordered by, a related field
879
+ * cannot (it would need a join).
867
880
  */
868
881
  export interface Column {
869
882
  attribute: string
@@ -877,7 +890,9 @@ export interface Column {
877
890
  export type SortDirection = 'asc' | 'desc'
878
891
 
879
892
  /**
880
- * Sort configuration.
893
+ * Sort configuration. `attribute` is an attribute name or an object path
894
+ * (`address.city`); relation paths are not sortable — ordering by a related
895
+ * field would need a join the records query doesn't do.
881
896
  */
882
897
  export interface SortConfig {
883
898
  attribute: string
@@ -904,6 +919,60 @@ export {
904
919
  type LogicalOperator,
905
920
  }
906
921
 
922
+ // ============================================================================
923
+ // Toolbar action buttons (shared by pages and lists)
924
+ // ============================================================================
925
+
926
+ /**
927
+ * What a page toolbar button invokes. Absent normalizes to `action` (pages
928
+ * persisted before `kind` existed).
929
+ */
930
+ export type PageActionKind = 'action' | 'workflow'
931
+
932
+ /**
933
+ * Page action definition — one toolbar button. `kind: action` invokes a
934
+ * function-service Action by slug (`action`) and may prefill its params;
935
+ * `kind: workflow` starts a manual run of a workflow by key (`workflow`) and
936
+ * may prefill its manual-trigger inputs. `params` / `inputs` map target field
937
+ * names to Liquid templates rendered against the page scope
938
+ * `{ record, entity, params, user }`; a resolved field is locked in the invoke
939
+ * dialog. `skip_confirmation` fires the target immediately when every required
940
+ * field resolved from the templates.
941
+ */
942
+ export interface PageAction {
943
+ label: string
944
+ icon: string
945
+ kind?: PageActionKind
946
+ action?: string
947
+ workflow?: string
948
+ params?: Record<string, string>
949
+ inputs?: Record<string, string>
950
+ skip_confirmation?: boolean
951
+ }
952
+
953
+ export const PageActionSchema = z.object({
954
+ label: z.string(),
955
+ icon: z.string(),
956
+ kind: z.enum(['action', 'workflow']).optional(),
957
+ action: z.string().optional(),
958
+ workflow: z.string().optional(),
959
+ params: z.record(z.string()).optional(),
960
+ inputs: z.record(z.string()).optional(),
961
+ skip_confirmation: z.boolean().optional(),
962
+ })
963
+
964
+ /**
965
+ * Whether a list's rows can be checked, and whether the checkboxes show from
966
+ * the start:
967
+ *
968
+ * - `on_demand` (default) — a Select toggle in the toolbar reveals them.
969
+ * - `always` — checkboxes are showing from the start.
970
+ * - `off` — rows can never be checked, even when the list carries actions.
971
+ */
972
+ export type SelectionMode = 'on_demand' | 'always' | 'off'
973
+
974
+ export const SelectionModeSchema = z.enum(['on_demand', 'always', 'off'])
975
+
907
976
  /**
908
977
  * List configuration.
909
978
  * Note: List uses `slug` as its primary identifier, not `id`.
@@ -915,6 +984,15 @@ export interface List extends AuditFields {
915
984
  name: string
916
985
  entity_slug: string
917
986
  columns: Column[]
987
+ /**
988
+ * Toolbar buttons on the list, same shape a page carries. They act on the
989
+ * rows SELECTED in the list, so an `action` button names an `entity_batch`
990
+ * action (invoked once with every selected record id) and prefill templates
991
+ * resolve against the list scope `{ selection, entity, user }`.
992
+ */
993
+ actions?: PageAction[]
994
+ /** Row-selection affordance; absent normalizes to `on_demand`. */
995
+ selection_mode?: SelectionMode
918
996
  /** Record page to open from this list; empty/absent = org default for the entity. */
919
997
  default_page_slug?: string
920
998
  sorting: SortConfig[]
@@ -938,6 +1016,8 @@ export const ListSchema = AuditFieldsSchema.extend({
938
1016
  name: z.string(),
939
1017
  entity_slug: z.string(),
940
1018
  columns: z.array(ColumnSchema),
1019
+ actions: z.array(PageActionSchema).optional(),
1020
+ selection_mode: SelectionModeSchema.optional(),
941
1021
  default_page_slug: z.string().optional(),
942
1022
  sorting: z.array(SortConfigSchema),
943
1023
  filters: z.array(FilterGroupSchema),
@@ -962,6 +1042,8 @@ export interface CreateListRequest {
962
1042
  entity_slug: string
963
1043
  name: string
964
1044
  columns: Column[]
1045
+ actions?: PageAction[]
1046
+ selection_mode?: SelectionMode
965
1047
  default_page_slug?: string
966
1048
  sorting: SortConfig[]
967
1049
  filters: FilterGroup[]
@@ -974,6 +1056,8 @@ export interface UpdateListRequest {
974
1056
  name?: string
975
1057
  module_slug?: string
976
1058
  columns?: Column[]
1059
+ actions?: PageAction[]
1060
+ selection_mode?: SelectionMode
977
1061
  /** Set to '' to clear back to the org default. */
978
1062
  default_page_slug?: string
979
1063
  sorting?: SortConfig[]
@@ -1047,44 +1131,6 @@ export interface UpdateListViewRequest {
1047
1131
  // Page Types
1048
1132
  // ============================================================================
1049
1133
 
1050
- /**
1051
- * What a page toolbar button invokes. Absent normalizes to `action` (pages
1052
- * persisted before `kind` existed).
1053
- */
1054
- export type PageActionKind = 'action' | 'workflow'
1055
-
1056
- /**
1057
- * Page action definition — one toolbar button. `kind: action` invokes a
1058
- * function-service Action by slug (`action`) and may prefill its params;
1059
- * `kind: workflow` starts a manual run of a workflow by key (`workflow`) and
1060
- * may prefill its manual-trigger inputs. `params` / `inputs` map target field
1061
- * names to Liquid templates rendered against the page scope
1062
- * `{ record, entity, params, user }`; a resolved field is locked in the invoke
1063
- * dialog. `skip_confirmation` fires the target immediately when every required
1064
- * field resolved from the templates.
1065
- */
1066
- export interface PageAction {
1067
- label: string
1068
- icon: string
1069
- kind?: PageActionKind
1070
- action?: string
1071
- workflow?: string
1072
- params?: Record<string, string>
1073
- inputs?: Record<string, string>
1074
- skip_confirmation?: boolean
1075
- }
1076
-
1077
- export const PageActionSchema = z.object({
1078
- label: z.string(),
1079
- icon: z.string(),
1080
- kind: z.enum(['action', 'workflow']).optional(),
1081
- action: z.string().optional(),
1082
- workflow: z.string().optional(),
1083
- params: z.record(z.string()).optional(),
1084
- inputs: z.record(z.string()).optional(),
1085
- skip_confirmation: z.boolean().optional(),
1086
- })
1087
-
1088
1134
  /**
1089
1135
  * Page type — encodes what the page binds to and how it is served (chrome +
1090
1136
  * auth posture both follow from it):