@proteos/sdk 0.60.0 → 0.75.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 (40) hide show
  1. package/dist/{chunk-OOOTPEJK.js → chunk-3VQQEJV6.js} +71 -11
  2. package/dist/chunk-3VQQEJV6.js.map +1 -0
  3. package/dist/{chunk-25PD6TYH.cjs → chunk-C4CH6CEY.cjs} +75 -10
  4. package/dist/chunk-C4CH6CEY.cjs.map +1 -0
  5. package/dist/index.cjs +379 -130
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +813 -81
  8. package/dist/index.d.ts +813 -81
  9. package/dist/index.js +250 -13
  10. package/dist/index.js.map +1 -1
  11. package/dist/meta/index.cjs +109 -89
  12. package/dist/meta/index.d.cts +1 -1
  13. package/dist/meta/index.d.ts +1 -1
  14. package/dist/meta/index.js +1 -1
  15. package/dist/{types-DQmqR_G8.d.cts → types-Dp-AB2QZ.d.cts} +561 -48
  16. package/dist/{types-DQmqR_G8.d.ts → types-Dp-AB2QZ.d.ts} +561 -48
  17. package/package.json +1 -1
  18. package/src/agent/session-types.ts +8 -1
  19. package/src/agent/sessions.ts +6 -4
  20. package/src/agent/types.ts +9 -0
  21. package/src/auth/platform-entities.ts +6 -0
  22. package/src/client.ts +28 -4
  23. package/src/conversation/index.ts +327 -15
  24. package/src/conversation/types.ts +594 -5
  25. package/src/data/entity-indexes.ts +54 -0
  26. package/src/data/index.ts +12 -0
  27. package/src/data/types.ts +66 -0
  28. package/src/errors.ts +5 -0
  29. package/src/index.ts +57 -0
  30. package/src/meta/components.ts +23 -3
  31. package/src/meta/index.ts +8 -0
  32. package/src/meta/layout/common-props.ts +9 -0
  33. package/src/meta/layout/elements.ts +57 -0
  34. package/src/meta/layout/index.ts +5 -0
  35. package/src/meta/layout/page-layout.ts +15 -3
  36. package/src/meta/types.ts +206 -21
  37. package/src/scheduling/types.ts +5 -1
  38. package/src/workflow/types.ts +10 -2
  39. package/dist/chunk-25PD6TYH.cjs.map +0 -1
  40. package/dist/chunk-OOOTPEJK.js.map +0 -1
@@ -0,0 +1,54 @@
1
+ import type { ProteosClient } from '../client.js'
2
+ import type { CreateEntityIndexRequest, EntityIndex, IndexRecommendation } from './types.js'
3
+
4
+ const ENTITIES_BASE_PATH = '/data/v1/entities'
5
+
6
+ /**
7
+ * Per-entity indexes on the record table and the advisor's recommendations.
8
+ * Admin surface: gated on the `entities` permission, not on record access.
9
+ */
10
+ export interface EntityIndexService {
11
+ /** Every index of the entity (declared, automatic, applied) with live state. */
12
+ list(entitySlug: string): Promise<EntityIndex[]>
13
+ /** The advisor's current suggestions for the entity. */
14
+ recommendations(entitySlug: string): Promise<IndexRecommendation[]>
15
+ /** Applies one lookup/search index; returns it in `building` state. */
16
+ create(entitySlug: string, request: CreateEntityIndexRequest): Promise<EntityIndex>
17
+ /** Removes one APPLIED index (declared/automatic → 409 entity_index_not_deletable). */
18
+ delete(entitySlug: string, name: string): Promise<void>
19
+ }
20
+
21
+ export class EntityIndexServiceImpl implements EntityIndexService {
22
+ constructor(private readonly client: ProteosClient) {}
23
+
24
+ async list(entitySlug: string): Promise<EntityIndex[]> {
25
+ const response = await this.client.request<{ data: EntityIndex[] }>(
26
+ 'GET',
27
+ `${ENTITIES_BASE_PATH}/${encodeURIComponent(entitySlug)}/indexes`,
28
+ )
29
+ return response.data
30
+ }
31
+
32
+ async recommendations(entitySlug: string): Promise<IndexRecommendation[]> {
33
+ const response = await this.client.request<{ data: IndexRecommendation[] }>(
34
+ 'GET',
35
+ `${ENTITIES_BASE_PATH}/${encodeURIComponent(entitySlug)}/index-recommendations`,
36
+ )
37
+ return response.data
38
+ }
39
+
40
+ async create(entitySlug: string, request: CreateEntityIndexRequest): Promise<EntityIndex> {
41
+ return this.client.request<EntityIndex>(
42
+ 'POST',
43
+ `${ENTITIES_BASE_PATH}/${encodeURIComponent(entitySlug)}/indexes`,
44
+ request,
45
+ )
46
+ }
47
+
48
+ async delete(entitySlug: string, name: string): Promise<void> {
49
+ await this.client.request<void>(
50
+ 'DELETE',
51
+ `${ENTITIES_BASE_PATH}/${encodeURIComponent(entitySlug)}/indexes/${encodeURIComponent(name)}`,
52
+ )
53
+ }
54
+ }
package/src/data/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { ProteosClient } from '../client.js'
2
+ import { type EntityIndexService, EntityIndexServiceImpl } from './entity-indexes.js'
2
3
  import { type QueryService, QueryServiceImpl } from './queries.js'
3
4
  import {
4
5
  type RecordDuplicateService,
@@ -23,14 +24,18 @@ export class DataClient {
23
24
  readonly queries: QueryService
24
25
  /** Duplicate pairs of an entity's records (raised by contact binding). */
25
26
  readonly recordDuplicates: RecordDuplicateService
27
+ /** Per-entity indexes on the record table + the advisor's recommendations. */
28
+ readonly entityIndexes: EntityIndexService
26
29
 
27
30
  constructor(client: ProteosClient) {
28
31
  this.records = new RecordServiceImpl(client)
29
32
  this.queries = new QueryServiceImpl(client)
30
33
  this.recordDuplicates = new RecordDuplicateServiceImpl(client)
34
+ this.entityIndexes = new EntityIndexServiceImpl(client)
31
35
  }
32
36
  }
33
37
 
38
+ export type { EntityIndexService } from './entity-indexes.js'
34
39
  // Re-export service interfaces
35
40
  // Re-export types
36
41
  export type {
@@ -48,6 +53,13 @@ export type {
48
53
  BatchUpsertRecordsResponse,
49
54
  BatchUpsertTransaction,
50
55
  BatchUpsertTransactionResult,
56
+ CreateEntityIndexRequest,
57
+ EntityIndex,
58
+ EntityIndexSource,
59
+ EntityIndexState,
60
+ EntityIndexType,
61
+ IndexEvidence,
62
+ IndexRecommendation,
51
63
  ListRecordDuplicatesQuery,
52
64
  ListRecordsOptions,
53
65
  PublishContactObservationsResponse,
package/src/data/types.ts CHANGED
@@ -151,3 +151,69 @@ export interface PublishContactObservationsResponse {
151
151
  published: number
152
152
  skipped: number
153
153
  }
154
+
155
+ // ============================================================================
156
+ // Entity indexes (per-org indexes on an entity's record table)
157
+ // ============================================================================
158
+
159
+ /** Named by what it does: lookup (=, IN, ranges, sort), unique (lookup that
160
+ * refuses duplicates), search (substring match on one string attribute). */
161
+ export type EntityIndexType = 'lookup' | 'unique' | 'search'
162
+
163
+ /** declared = from the entity (is_unique / unique_constraints); automatic =
164
+ * the platform's relation/user indexes; applied = added per org by an admin. */
165
+ export type EntityIndexSource = 'declared' | 'automatic' | 'applied'
166
+
167
+ export type EntityIndexState = 'ready' | 'building' | 'invalid' | 'missing'
168
+
169
+ export interface EntityIndex {
170
+ name: string
171
+ type: EntityIndexType
172
+ attributes: string[]
173
+ source: EntityIndexSource
174
+ state?: EntityIndexState
175
+ size_bytes?: number
176
+ scans?: number
177
+ created_at?: string
178
+ }
179
+
180
+ export const EntityIndexSchema = z.object({
181
+ name: z.string(),
182
+ type: z.enum(['lookup', 'unique', 'search']),
183
+ attributes: z.array(z.string()),
184
+ source: z.enum(['declared', 'automatic', 'applied']),
185
+ state: z.enum(['ready', 'building', 'invalid', 'missing']).optional(),
186
+ size_bytes: z.number().optional(),
187
+ scans: z.number().optional(),
188
+ created_at: z.string().optional(),
189
+ })
190
+
191
+ /** Only slow runs (>= the advisor's slow threshold) are recorded, so
192
+ * `slow_queries` is the whole sample and `avg_ms` their average. */
193
+ export interface IndexEvidence {
194
+ slow_queries: number
195
+ avg_ms: number
196
+ rows: number
197
+ scans: number
198
+ reason: string
199
+ }
200
+
201
+ export interface IndexRecommendation {
202
+ action: 'add' | 'remove'
203
+ index: EntityIndex
204
+ evidence: IndexEvidence
205
+ /** On an `add`: applied lookups the new index supersedes. Pass through as
206
+ * `CreateEntityIndexRequest.replaces` so the create swaps them. */
207
+ replaces?: string[]
208
+ }
209
+
210
+ /** Only lookup and search can be applied; uniqueness is declared on the entity. */
211
+ export interface CreateEntityIndexRequest {
212
+ /** Derived from the attributes when omitted (by_<a>_<b>, search_<a>). */
213
+ name?: string
214
+ type: 'lookup' | 'search'
215
+ attributes: string[]
216
+ /** Applied lookups this index supersedes (each must be covered by it);
217
+ * removed in the same step. Required whenever one exists, else 400 index_redundant. */
218
+ replaces?: string[]
219
+ }
package/src/errors.ts CHANGED
@@ -10,6 +10,11 @@ export const ErrorCode = {
10
10
  INTERNAL_SERVER_ERROR: 'internal_server_error',
11
11
  INVALID_PAYLOAD: 'invalid_payload',
12
12
  INVALID_PERSONA: 'invalid_persona',
13
+ /** The server was never reached: DNS, connection refused, network down.
14
+ * Carried with httpStatus 0. */
15
+ NETWORK_ERROR: 'network_error',
16
+ /** The request timed out client-side. Carried with httpStatus 0. */
17
+ REQUEST_TIMEOUT: 'request_timeout',
13
18
  } as const
14
19
 
15
20
  export type ErrorCodeType = (typeof ErrorCode)[keyof typeof ErrorCode]
package/src/index.ts CHANGED
@@ -264,6 +264,8 @@ export type {
264
264
  BooleanTagCriteria,
265
265
  BounceClassification,
266
266
  BounceKind,
267
+ CallDialStatus,
268
+ CallMetadata,
267
269
  CallStatus,
268
270
  CallTokenResponse,
269
271
  Channel,
@@ -317,11 +319,31 @@ export type {
317
319
  ConversationFilterEvent,
318
320
  ConversationFilterService,
319
321
  ConversationFilterType,
322
+ ConversationNote,
323
+ ConversationNoteService,
324
+ ConversationObjective,
325
+ ConversationObjectiveAchievedWhen,
326
+ ConversationObjectiveCount,
327
+ ConversationObjectiveDefinition,
328
+ ConversationObjectiveDefinitionService,
329
+ ConversationObjectiveMeasure,
330
+ ConversationObjectiveMeasureKind,
331
+ ConversationObjectiveProgress,
332
+ ConversationObjectiveService,
333
+ ConversationObjectiveSource,
334
+ ConversationObjectiveStatus,
335
+ ConversationOutcome,
336
+ ConversationOutcomeOption,
337
+ ConversationOutcomeSource,
320
338
  ConversationParticipant,
339
+ ConversationPhase,
340
+ ConversationPhaseProgress,
341
+ ConversationPhaseStatus,
321
342
  ConversationService,
322
343
  ConversationStatus,
323
344
  ConversationSummaryStatus,
324
345
  ConversationTag,
346
+ ConversationTagBasis,
325
347
  ConversationTagCount,
326
348
  ConversationTagCriteria,
327
349
  ConversationTagDefinition,
@@ -333,6 +355,7 @@ export type {
333
355
  ConversationTaggedEvent,
334
356
  ConversationTagQuestion,
335
357
  ConversationTagQuestionType,
358
+ ConversationTagRole,
336
359
  ConversationTagScope,
337
360
  ConversationTagScreening,
338
361
  ConversationTagScreeningResult,
@@ -346,6 +369,8 @@ export type {
346
369
  ConversationType,
347
370
  ConversationTypeConfig,
348
371
  ConversationTypeService,
372
+ CountConversationObjectivesQuery,
373
+ CountConversationObjectivesResponse,
349
374
  CountConversationTagsQuery,
350
375
  CountConversationTagsResponse,
351
376
  CreateAgentListenerRequest,
@@ -355,6 +380,8 @@ export type {
355
380
  CreateContactRequest,
356
381
  CreateConversationBriefRequest,
357
382
  CreateConversationFilterRequest,
383
+ CreateConversationNoteRequest,
384
+ CreateConversationObjectiveDefinitionRequest,
358
385
  CreateConversationTagDefinitionRequest,
359
386
  CreateConversationTagRequest,
360
387
  CreateConversationTagSetRequest,
@@ -383,6 +410,8 @@ export type {
383
410
  FilterMatchOn,
384
411
  FrequencyCapRuleConfig,
385
412
  GetConnectionHealthQuery,
413
+ GetManyConversationObjectiveDefinitionsQuery,
414
+ GetManyConversationObjectivesQuery,
386
415
  GlossaryTerm,
387
416
  GlossaryTermService,
388
417
  InmailParams,
@@ -402,6 +431,7 @@ export type {
402
431
  ListConversationBriefsQuery,
403
432
  ListConversationFilterEventsQuery,
404
433
  ListConversationFiltersQuery,
434
+ ListConversationNotesQuery,
405
435
  ListConversationsQuery,
406
436
  ListConversationTagDefinitionsQuery,
407
437
  ListConversationTagEvaluationsQuery,
@@ -409,6 +439,7 @@ export type {
409
439
  ListConversationTagsQuery,
410
440
  ListConversationTypesQuery,
411
441
  ListGlossaryTermsQuery,
442
+ ListMeetingBotsQuery,
412
443
  ListMessagesQuery,
413
444
  ListMistranscribedTermsQuery,
414
445
  // Conversation-local page envelope ({meta, data}) — distinct from the
@@ -419,6 +450,10 @@ export type {
419
450
  ListSendingRulesQuery,
420
451
  ListToneProfileSetupsQuery,
421
452
  ListToneProfilesQuery,
453
+ ListTranscriptionsQuery,
454
+ MeetingBot,
455
+ MeetingBotService,
456
+ MeetingBotStatus,
422
457
  MeetingService,
423
458
  MergeContactsRequest,
424
459
  MergeProposalStatus,
@@ -464,6 +499,7 @@ export type {
464
499
  SendingRuleType,
465
500
  SendMessageRequest,
466
501
  SendRecipient,
502
+ SetCallRecordingRequest,
467
503
  SetDefaultSenderRequest,
468
504
  SyncConnectionRequest,
469
505
  ToneProfile,
@@ -478,6 +514,8 @@ export type {
478
514
  TranscriptionStatus,
479
515
  TranscriptResult,
480
516
  TranscriptTurn,
517
+ TwilioPhoneSettings,
518
+ TwilioPhoneVoicemailSettings,
481
519
  UnreadCounts,
482
520
  UpdateAgentListenerRequest,
483
521
  UpdateConnectionRequest,
@@ -485,6 +523,9 @@ export type {
485
523
  UpdateContactRequest,
486
524
  UpdateConversationBriefRequest,
487
525
  UpdateConversationFilterRequest,
526
+ UpdateConversationNoteRequest,
527
+ UpdateConversationObjectiveDefinitionRequest,
528
+ UpdateConversationRequest,
488
529
  UpdateConversationTagDefinitionRequest,
489
530
  UpdateConversationTagRequest,
490
531
  UpdateConversationTagSetRequest,
@@ -509,6 +550,14 @@ export type {
509
550
  BatchUpsertRecordsResponse,
510
551
  BatchUpsertTransaction,
511
552
  BatchUpsertTransactionResult,
553
+ CreateEntityIndexRequest,
554
+ EntityIndex,
555
+ EntityIndexService,
556
+ EntityIndexSource,
557
+ EntityIndexState,
558
+ EntityIndexType,
559
+ IndexEvidence,
560
+ IndexRecommendation,
512
561
  ListRecordDuplicatesQuery,
513
562
  ListRecordsOptions,
514
563
  PublishContactObservationsResponse,
@@ -752,20 +801,25 @@ export type {
752
801
  // Page types
753
802
  Page,
754
803
  PageAction,
804
+ PageActionIntent,
755
805
  PageActionKind,
756
806
  PageExtension,
757
807
  PageExtensionPlacement,
758
808
  PageExtensionService,
759
809
  PageLayout,
810
+ PageMode,
760
811
  PageService,
761
812
  PageType,
762
813
  PublicAccessOperation,
763
814
  PublicPageComponent,
764
815
  PublicPageResponse,
816
+ RecordPageBinding,
765
817
  // Relation attribute meta
766
818
  RelationAttributeMeta,
767
819
  SortConfig,
768
820
  SortDirection,
821
+ TriggerPageBinding,
822
+ TriggerPhase,
769
823
  UpdateAppConfigurationRequest,
770
824
  UpdateAppRequest,
771
825
  UpdateComponentRequest,
@@ -794,6 +848,7 @@ export {
794
848
  AttributeSchema,
795
849
  acceptsCurrentUserDefault,
796
850
  allCurrencyCodes,
851
+ bundleVersionOf,
797
852
  ColumnSchema,
798
853
  ComponentSchema,
799
854
  ContactAddressAttributeMetaSchema,
@@ -845,8 +900,10 @@ export {
845
900
  parseFileMeta,
846
901
  parseRelationMeta,
847
902
  platformAttributes,
903
+ RecordPageBindingSchema,
848
904
  RelationAttributeMetaSchema,
849
905
  SortConfigSchema,
906
+ TriggerPageBindingSchema,
850
907
  VariableSchema,
851
908
  } from './meta/index.js'
852
909
  // Scheduling types (scheduling-service: calendar connections, calendars,
@@ -10,6 +10,18 @@ import type {
10
10
 
11
11
  const COMPONENTS_BASE_PATH = '/meta/v1/components'
12
12
 
13
+ /**
14
+ * The hex digest of a `bundle_checksum` (`sha256:<hex>`), which is what the
15
+ * bundle endpoint compares its `v` query against. `undefined` for an absent
16
+ * or malformed checksum.
17
+ */
18
+ export function bundleVersionOf(bundleChecksum: string | undefined): string | undefined {
19
+ if (!bundleChecksum) return undefined
20
+ const separator = bundleChecksum.indexOf(':')
21
+ const hex = separator === -1 ? bundleChecksum : bundleChecksum.slice(separator + 1)
22
+ return /^[0-9a-f]{16,}$/i.test(hex) ? hex.toLowerCase() : undefined
23
+ }
24
+
13
25
  /**
14
26
  * Service for managing UI component definitions.
15
27
  * Components define reusable UI elements in the system.
@@ -81,7 +93,7 @@ export interface ComponentService {
81
93
  * @param slug - Component slug
82
94
  * @returns The relative bundle URL
83
95
  */
84
- bundleUrl(slug: string): string
96
+ bundleUrl(slug: string, bundleChecksum?: string): string
85
97
  }
86
98
 
87
99
  /**
@@ -118,7 +130,15 @@ export class ComponentServiceImpl implements ComponentService {
118
130
  await this.client.request<void>('DELETE', `${COMPONENTS_BASE_PATH}/${id}`)
119
131
  }
120
132
 
121
- bundleUrl(slug: string): string {
122
- return `${COMPONENTS_BASE_PATH}/${slug}/bundle`
133
+ /**
134
+ * Path of the component's compiled bundle. With the component's
135
+ * `bundle_checksum` the URL is content-addressed (`?v=<hex>`), which the
136
+ * server answers with an immutable cache policy; without it the bundle
137
+ * revalidates on every load.
138
+ */
139
+ bundleUrl(slug: string, bundleChecksum?: string): string {
140
+ const version = bundleVersionOf(bundleChecksum)
141
+ const base = `${COMPONENTS_BASE_PATH}/${slug}/bundle`
142
+ return version ? `${base}?v=${encodeURIComponent(version)}` : base
123
143
  }
124
144
  }
package/src/meta/index.ts CHANGED
@@ -149,6 +149,7 @@ export class MetaClient {
149
149
  export type { AppConfigurationService } from './app-configurations.js'
150
150
  export type { AppService } from './apps.js'
151
151
  export type { ComponentService } from './components.js'
152
+ export { bundleVersionOf } from './components.js'
152
153
  export * from './currency/index.js'
153
154
  export type { DesignReferenceService } from './design-references.js'
154
155
  // Re-export service interfaces
@@ -269,11 +270,16 @@ export type {
269
270
  // Page types
270
271
  Page,
271
272
  PageAction,
273
+ PageActionIntent,
272
274
  PageActionKind,
273
275
  // PageExtension types
274
276
  PageExtension,
275
277
  PageExtensionPlacement,
276
278
  PageType,
279
+ RecordPageBinding,
280
+ PageMode,
281
+ TriggerPageBinding,
282
+ TriggerPhase,
277
283
  // User attribute meta
278
284
  PrincipalAttributeMeta,
279
285
  PrincipalRef,
@@ -310,6 +316,8 @@ export type {
310
316
  export {
311
317
  AppConfigurationSchema,
312
318
  AppHomeSchema,
319
+ RecordPageBindingSchema,
320
+ TriggerPageBindingSchema,
313
321
  AppSchema,
314
322
  AttributeSchema,
315
323
  acceptsCurrentUserDefault,
@@ -31,6 +31,9 @@ export interface SizingProps {
31
31
  /** Floor under `height` (px or %). Under `height: 'fill'` in a column it
32
32
  * replaces the default 320px floor. */
33
33
  min_height?: LengthValue
34
+ /** Ceiling over `height` (px or %). A box that would grow past it scrolls
35
+ * its content instead; a field scrolls its value and keeps its label. */
36
+ max_height?: LengthValue
34
37
  grow?: number
35
38
  shrink?: number
36
39
  align?: LayoutAlign
@@ -40,6 +43,7 @@ const SizingPropsSchema = z.object({
40
43
  width: SizeValueSchema.optional(),
41
44
  height: SizeValueSchema.optional(),
42
45
  min_height: LengthValueSchema.optional(),
46
+ max_height: LengthValueSchema.optional(),
43
47
  grow: z.number().optional(),
44
48
  shrink: z.number().optional(),
45
49
  align: AlignSchema.optional(),
@@ -87,6 +91,10 @@ export interface CommonProps {
87
91
  height?: SizeValue
88
92
  /** Floor under `height`, px or %. */
89
93
  min_height?: LengthValue
94
+ /** Ceiling over `height`, px or %. Content past it scrolls inside the
95
+ * element (a field scrolls its value, the label stays). A fixed px / %
96
+ * `height` bounds the same way. */
97
+ max_height?: LengthValue
90
98
  grow?: number
91
99
  shrink?: number
92
100
  align?: LayoutAlign
@@ -105,6 +113,7 @@ export const commonPropsShape = {
105
113
  width: SizeValueSchema.optional(),
106
114
  height: SizeValueSchema.optional(),
107
115
  min_height: LengthValueSchema.optional(),
116
+ max_height: LengthValueSchema.optional(),
108
117
  grow: z.number().optional(),
109
118
  shrink: z.number().optional(),
110
119
  align: AlignSchema.optional(),
@@ -25,6 +25,7 @@ export const LayoutElementType = {
25
25
  WorkflowTrigger: 'workflow_trigger',
26
26
  SchedulingPicker: 'scheduling_picker',
27
27
  Calendar: 'calendar',
28
+ Stepper: 'stepper',
28
29
  } as const
29
30
  export type LayoutElementType = (typeof LayoutElementType)[keyof typeof LayoutElementType]
30
31
 
@@ -95,6 +96,41 @@ export type TabsElement = CommonProps & {
95
96
  default_tab_id?: string
96
97
  }
97
98
 
99
+ /**
100
+ * One stage inside a `stepper` element, bound to one value of the stepper's
101
+ * enum attribute. Steps are ordered: the step whose `value` the record holds
102
+ * is current, the ones before it completed, the ones after it upcoming.
103
+ * `visible_when` hides a step from the rail without changing the order;
104
+ * `continue_label` overrides the Continue button while this step is current.
105
+ * Step ids share the page-wide id namespace with element ids.
106
+ */
107
+ export type LayoutStep = {
108
+ id: string
109
+ value: string
110
+ label: string
111
+ icon?: string
112
+ continue_label?: string
113
+ visible_when?: FilterGroup
114
+ content: LayoutElement
115
+ }
116
+
117
+ /**
118
+ * Tabs for a record that walks through stages: ordered steps bound to one
119
+ * enum `attribute` of the page record, one content tree per step, and
120
+ * Continue / Back buttons that write the attribute through the normal record
121
+ * update — `before_update` hooks stay the authority on readiness and their
122
+ * refusal is shown inline. Record pages only.
123
+ */
124
+ export type StepperElement = CommonProps & {
125
+ type: 'stepper'
126
+ attribute: string
127
+ /** Continue button label (default "Continue"); a step's own `continue_label` wins. */
128
+ continue_label?: string
129
+ /** Back button label (default "Back"). */
130
+ back_label?: string
131
+ steps: LayoutStep[]
132
+ }
133
+
98
134
  export type FieldElement = CommonProps & {
99
135
  type: 'field'
100
136
  attribute: string
@@ -346,6 +382,7 @@ export type LayoutElement =
346
382
  | WorkflowTriggerElement
347
383
  | SchedulingPickerElement
348
384
  | CalendarElement
385
+ | StepperElement
349
386
 
350
387
  // ──────────────────────────────────────────────────────────── Schemas ──
351
388
 
@@ -361,6 +398,18 @@ export const LayoutTabSchema: z.ZodType<LayoutTab> = z.lazy(() =>
361
398
  }),
362
399
  )
363
400
 
401
+ export const LayoutStepSchema: z.ZodType<LayoutStep> = z.lazy(() =>
402
+ z.object({
403
+ id: z.string().min(1),
404
+ value: z.string().min(1),
405
+ label: z.string().min(1),
406
+ icon: z.string().optional(),
407
+ continue_label: z.string().optional(),
408
+ visible_when: FilterGroupSchema.optional(),
409
+ content: LayoutElementSchema,
410
+ }),
411
+ )
412
+
364
413
  export const LayoutElementSchema: z.ZodType<LayoutElement> = z.lazy(() =>
365
414
  z.discriminatedUnion('type', [
366
415
  z.object({
@@ -404,6 +453,14 @@ export const LayoutElementSchema: z.ZodType<LayoutElement> = z.lazy(() =>
404
453
  tabs: z.array(LayoutTabSchema),
405
454
  default_tab_id: z.string().optional(),
406
455
  }),
456
+ z.object({
457
+ type: z.literal('stepper'),
458
+ ...commonPropsShape,
459
+ attribute: z.string().min(1),
460
+ continue_label: z.string().optional(),
461
+ back_label: z.string().optional(),
462
+ steps: z.array(LayoutStepSchema).min(1),
463
+ }),
407
464
  z.object({
408
465
  type: z.literal('field'),
409
466
  ...commonPropsShape,
@@ -29,6 +29,8 @@ export {
29
29
  type LayoutElement,
30
30
  LayoutElementSchema,
31
31
  LayoutElementType,
32
+ type LayoutStep,
33
+ LayoutStepSchema,
32
34
  type LayoutTab,
33
35
  LayoutTabSchema,
34
36
  type ListElement,
@@ -38,6 +40,7 @@ export {
38
40
  type RowElement,
39
41
  type SchedulingPickerElement,
40
42
  type SectionElement,
43
+ type StepperElement,
41
44
  type TabsElement,
42
45
  type TextElement,
43
46
  type TextVariant,
@@ -47,7 +50,9 @@ export {
47
50
  isPageBackground,
48
51
  PAGE_BACKGROUND_COLOR_RE,
49
52
  PAGE_BACKGROUND_TOKENS,
53
+ PAGE_HEADERS,
50
54
  type PageBackgroundToken,
55
+ type PageHeader,
51
56
  type PageLayout,
52
57
  PageLayoutSchema,
53
58
  type PageLayoutSidePanel,
@@ -49,6 +49,14 @@ export const PAGE_BACKGROUND_TOKENS = [
49
49
 
50
50
  export type PageBackgroundToken = (typeof PAGE_BACKGROUND_TOKENS)[number]
51
51
 
52
+ /** Record toolbar shapes. `full` (default) = title strip + actions strip;
53
+ * `condensed` = title strip only: a (⋯) overflow with the page actions and
54
+ * the record's links (hidden when there are none), then Edit / Delete,
55
+ * beside the close button. */
56
+ export const PAGE_HEADERS = ['full', 'condensed'] as const
57
+
58
+ export type PageHeader = (typeof PAGE_HEADERS)[number]
59
+
52
60
  /**
53
61
  * A raw CSS color a page background may use instead of a token: a hex literal
54
62
  * or one of a few color functions with a restricted argument charset (no `;{}`,
@@ -76,9 +84,10 @@ export function isPageBackground(text: string): boolean {
76
84
  * bg / 1200px / 24px-32px look). `max_width: "fill"` (or `"auto"`) with zero
77
85
  * padding lets a component fill the page edge-to-edge.
78
86
  *
79
- * Honored on `platform`, `public` and `kiosk` pages; record pages reject it
80
- * (enforced server-side). On platform pages it styles the app shell's content
81
- * area rather than a standalone shell.
87
+ * Honored on `platform`, `record`, `public` and `kiosk` pages; on platform
88
+ * and record pages it styles the app shell's content area rather than a
89
+ * standalone shell. Trigger pages take `max_width` only; `header` is
90
+ * record-only (enforced server-side).
82
91
  */
83
92
  export interface PageStyle {
84
93
  /** Design-token key (see PAGE_BACKGROUND_TOKENS) or a raw CSS color
@@ -89,6 +98,8 @@ export interface PageStyle {
89
98
  /** Horizontal / vertical padding of the content container (Tailwind px/py). */
90
99
  padding_x?: StyleSizeValue
91
100
  padding_y?: StyleSizeValue
101
+ /** Record toolbar shape (record pages only); unset = `full`. */
102
+ header?: PageHeader
92
103
  }
93
104
 
94
105
  export const PageStyleSchema = z.object({
@@ -99,6 +110,7 @@ export const PageStyleSchema = z.object({
99
110
  max_width: StyleSizeValueSchema.optional(),
100
111
  padding_x: StyleSizeValueSchema.optional(),
101
112
  padding_y: StyleSizeValueSchema.optional(),
113
+ header: z.enum(PAGE_HEADERS).optional(),
102
114
  })
103
115
 
104
116
  /**