@diagonal-hq/cli 0.5.0 → 0.6.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.
package/dist/cli.mjs CHANGED
@@ -238764,13 +238764,13 @@ var require_ReactHotLoaderTransformer = __commonJS({
238764
238764
  })();`.replace(/\s+/g, " ").trim();
238765
238765
  }
238766
238766
  getSuffixCode() {
238767
- const topLevelNames = /* @__PURE__ */ new Set();
238767
+ const topLevelNames2 = /* @__PURE__ */ new Set();
238768
238768
  for (const token of this.tokens.tokens) {
238769
238769
  if (!token.isType && _tokenizer.isTopLevelDeclaration.call(void 0, token) && token.identifierRole !== _tokenizer.IdentifierRole.ImportDeclaration) {
238770
- topLevelNames.add(this.tokens.identifierNameForToken(token));
238770
+ topLevelNames2.add(this.tokens.identifierNameForToken(token));
238771
238771
  }
238772
238772
  }
238773
- const namesToRegister = Array.from(topLevelNames).map((name) => ({
238773
+ const namesToRegister = Array.from(topLevelNames2).map((name) => ({
238774
238774
  variableName: name,
238775
238775
  uniqueLocalName: name
238776
238776
  }));
@@ -264101,6 +264101,44 @@ function childNodes(node) {
264101
264101
  }
264102
264102
  return children;
264103
264103
  }
264104
+ function patternNames(pattern) {
264105
+ switch (pattern?.type) {
264106
+ case "Identifier":
264107
+ return [pattern.name];
264108
+ case "ObjectPattern":
264109
+ return pattern.properties.flatMap(
264110
+ (property) => patternNames(property.type === "RestElement" ? property.argument : property.value)
264111
+ );
264112
+ case "ArrayPattern":
264113
+ return pattern.elements.flatMap((element) => patternNames(element));
264114
+ case "AssignmentPattern":
264115
+ return patternNames(pattern.left);
264116
+ case "RestElement":
264117
+ return patternNames(pattern.argument);
264118
+ default:
264119
+ return [];
264120
+ }
264121
+ }
264122
+ function topLevelNames(ast) {
264123
+ const names2 = /* @__PURE__ */ new Set();
264124
+ for (const statement2 of ast.program.body) {
264125
+ const declaration = statement2.type === "ExportNamedDeclaration" ? statement2.declaration : statement2;
264126
+ if (declaration?.type === "VariableDeclaration") {
264127
+ for (const declarator of declaration.declarations) {
264128
+ for (const name of patternNames(declarator.id)) {
264129
+ names2.add(name);
264130
+ }
264131
+ }
264132
+ } else if (declaration?.id?.type === "Identifier") {
264133
+ names2.add(declaration.id.name);
264134
+ }
264135
+ }
264136
+ return names2;
264137
+ }
264138
+ function usesImplicitName(ast, source, name) {
264139
+ const imported = ast.program.body.filter((statement2) => statement2.type === "ImportDeclaration").some((statement2) => statement2.specifiers.some((specifier) => specifier.local.name === name));
264140
+ return !imported && !topLevelNames(ast).has(name) && new RegExp(`\\b${name}\\b`).test(source);
264141
+ }
264104
264142
 
264105
264143
  // scripts/lib/runtime-build/static-value.mjs
264106
264144
  init_define_globalThis_DIAGONAL_SDK_RUNTIME();
@@ -292190,7 +292228,7 @@ function transformJsx(code2, options2 = {}) {
292190
292228
 
292191
292229
  // scripts/generated/typeCheckLibs.mjs
292192
292230
  init_define_globalThis_DIAGONAL_SDK_RUNTIME();
292193
- var sdkLib = "// AUTO-GENERATED by scripts/generate-page-sdk-types.mjs - do not edit by hand.\n// Ambient type surface for the page `Sdk` global, loaded into the Monaco\n// editor via addExtraLib for hover + IntelliSense.\n\n// Stubs for global namespaces (React/JSX) referenced by component prop types but\n// not resolvable on disk. Kept loose so the file stays self-contained.\ndeclare namespace React {\n type AnchorHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type AriaAttributes<A = any, B = any, C = any, D = any> = any;\n type AriaRole<A = any, B = any, C = any, D = any> = any;\n type ButtonHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type DOMAttributes<A = any, B = any, C = any, D = any> = any;\n type ElementType<A = any, B = any, C = any, D = any> = any;\n type HTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type IframeHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type ImgHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type InputHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type LiHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type MouseEventHandler<A = any, B = any, C = any, D = any> = any;\n type TextareaHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type VideoHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n}\n\ndeclare module \"diagonal:sdk-internal\" {\n// A page component: anything callable with its props object. Component prop\n// types reference this instead of `@types/react` so the file stays self-contained.\ntype SdkComponent<P = any> = (props: P) => any;\ntype SdkCustomComponent<T> = T extends (props: infer P, ...args: any[]) => any\n ? unknown extends P\n ? SdkComponent\n : T\n : SdkComponent;\n\ntype SdkResourceFieldSettingsUi = {\n field_type?: DynamicFieldType;\n display_type?: DynamicDisplayType;\n /** Display-specific format. Number fields accept `grouped` or `plain`; year-like\n * field keys default to `plain`. */\n display_format?: string;\n options?: any[];\n placeholder?: string;\n language?: string;\n show_on_creating?: boolean;\n show_on_updating?: boolean;\n show_formatting_toolbar?: boolean;\n toolbar_placement?: 'floating' | 'fixed';\n allow_inline_create_on_creating?: boolean;\n allow_inline_attach_on_creating?: boolean;\n allow_inline_create_on_updating?: boolean;\n allow_inline_attach_on_updating?: boolean;\n /**\n * Runtime-only: resolved inline-create/attach booleans injected client-side by\n * `resolveRecordFormField` in the record-form hooks. Not part of the backend\n * `PageFieldSettingsUiData` wire shape \u2014 populated after the catalog is built and\n * read by `FieldInput` as `resolvedValues` for relation fields.\n */\n injections?: Record<string, any>;\n};\n\ntype DynamicFieldType = 'boolean' | 'boolean_filter' | 'checkbox_group' | 'code' | 'color' | 'combobox' | 'component' | 'content' | 'content_type_settings' | 'css_classes' | 'data_grid_column_definitions' | 'date' | 'datetime' | 'dictionary' | 'dynamic' | 'email_layout_fields_input' | 'email_layout_select' | 'event_handler' | 'event_schedule' | 'file' | 'filter' | 'font' | 'guard_rules' | 'icon' | 'inline_select' | 'interval' | 'json' | 'list' | 'location' | 'markdown' | 'money' | 'navigation_menu' | 'none' | 'number' | 'operation_options' | 'page' | 'password' | 'phone' | 'plain_text' | 'point' | 'rating' | 'record' | 'recurrence' | 'regular_hours' | 'relation' | 'resource' | 'resource_field' | 'resource_field_key' | 'resource_field_value' | 'resource_field_value_entries' | 'resource_field_value_mapper' | 'resource_relation' | 'rich_text' | 'role' | 'schema' | 'schema_key' | 'select' | 'select_options' | 'success_redirect' | 'task' | 'text' | 'textarea' | 'time' | 'timezone' | 'unknown' | 'url' | 'wallet_accounting_account' | 'wallet_accounting_entity' | 'workflow' | 'workflow_fields_input' | 'workflow_select' | 'workflow_variable';\n\ntype DynamicDisplayType = 'boolean' | 'color' | 'content' | 'date' | 'datetime' | 'dictionary' | 'email' | 'event_schedule' | 'file' | 'list' | 'location' | 'markdown' | 'money' | 'number' | 'password' | 'phone' | 'point' | 'recurrence' | 'regular_hours' | 'relation' | 'resource' | 'select' | 'text' | 'time' | 'timezone' | 'wallet_accounting_account' | 'wallet_accounting_entity';\n\ntype SdkResourceFieldSettings = {\n currency?: string;\n integer?: boolean;\n minimum?: number | null;\n maximum?: number | null;\n multi_line?: boolean;\n placeholder?: string;\n yes_label?: string;\n no_label?: string;\n accept?: string[];\n visibility?: string;\n options?: Record<string, any>[];\n sort_order?: number | null;\n /** Item definition for list fields. */\n schema?: SdkListItemField | SdkListItemField[];\n /** Marks a Markdown field accepts. Absent allows every mark. */\n allowed_marks?: string[];\n /** Blocks a Markdown field accepts. Absent allows every block. */\n allowed_blocks?: string[];\n /** Relation pointer \u2014 the SDK dereferences `resource_id` against the flat catalog. */\n resource_id?: string;\n type?: string;\n foreign_key?: string;\n max_connections?: number | null;\n /** Field id of the child's back-reference that a has_many or has_one stores under. */\n inverse_of?: string;\n ui?: SdkResourceFieldSettingsUi;\n};\n\ntype SdkListItemField = {\n key: string;\n type: string;\n label?: string | null;\n description?: string | null;\n guard?: any[] | null;\n settings?: SdkResourceFieldSettings;\n};\n\ntype SdkResourceField = {\n id: string | null;\n key: string;\n label: string | null;\n type: string;\n resource_id: string;\n policy_scope: number | null;\n is_system_field: boolean;\n is_system_managed: boolean;\n /** Whether the field is the resource's configured roles or permissions relation. */\n is_authorization_relation: boolean;\n is_virtual_field: boolean;\n is_exportable: boolean;\n is_required: boolean;\n guard: Record<string, any> | null;\n /** The field's configured description \u2014 render it as help text under inputs. */\n description: string | null;\n settings: SdkResourceFieldSettings;\n /** Dotted accessor path; equals `key` at the top level, `parent.key` once nested. */\n path: string;\n /** A Boolean field's configured default; form checkboxes start from it. */\n default_value?: boolean | null;\n /** Relation-only decoration, filled lazily by the SDK builder from the flat catalog. */\n display_field_key?: string | null;\n column_label?: string | null;\n fields?: Record<string, SdkResourceField> | null;\n};\n\ntype SdkResourceCapabilityFieldMappings = Record<string, SdkResourceField>;\n\ntype ResourceSortDescriptor = { key: string; direction: 'asc' | 'desc' };\n\ntype ResourceSortFieldDescriptor = {\n field: string;\n direction: 'asc' | 'desc';\n};\n\ntype ResourceSortDirection = 1 | -1 | 'asc' | 'desc';\n\ntype ResourceSortInput =\n | string\n | string[]\n | ResourceSortDescriptor\n | ResourceSortFieldDescriptor\n | (ResourceSortDescriptor | ResourceSortFieldDescriptor)[]\n | Record<string, ResourceSortDirection>;\n\ntype ResourceListParams<TRelations extends readonly string[] = readonly string[]> = {\n page?: number;\n perPage?: number;\n per_page?: number;\n relations?: TRelations;\n /** Soft-delete subset to read from. Defaults to active records only. */\n $scope?: RecordScope;\n [key: string]: any;\n};\n\ntype RecordScope = 'active' | 'activeOrArchived' | 'archived';\n\ntype ResourceSearchBody<TRelations extends readonly string[] = readonly string[]> = {\n /** Exact field/value matches sent as `$match`. */\n $match?: Record<string, any>;\n /** Full-text search term sent as `$search`. */\n $search?: string;\n /** Structured `$filter` tree (renamed, not translated \u2014 flat maps are rejected). */\n filter?: Record<string, any>;\n /** Pre-built structured filter tree, sent as-is. */\n $filter?: Record<string, any>;\n /** Convenience sort input, normalized to `$sort` \u2014 see {@link ResourceSortInput}. */\n sort?: ResourceSortInput;\n /** Explicit `$sort` direction map, sent as-is. */\n $sort?: Record<string, string | number>;\n /** Soft-delete subset to read from; renamed to `$scope`. */\n scope?: RecordScope;\n /** Explicit backend record scope, sent as-is. */\n $scope?: RecordScope;\n /** One record for each repeating event; renamed to `$collapse`. See {@link RecordCollapse}. */\n collapse?: RecordCollapse;\n /** Explicit backend collapse, sent as-is. */\n $collapse?: RecordCollapse;\n /** Page number to return. Defaults to 1. */\n page?: number;\n /** Records per page (maximum 500, defaults to 15); renamed to `per_page`. */\n perPage?: number;\n /** Records per page (maximum 500, defaults to 15). */\n per_page?: number;\n /** Relation paths to load alongside each record. */\n relations?: TRelations;\n [key: string]: any;\n};\n\ntype RecordCollapse = 'series';\n\ntype ResourceCountOptions = {\n /** Structured filter tree, JSON-encoded so boolean and null values reach the server unchanged. */\n filter?: Record<string, any>;\n /** Pre-built structured filter tree, sent as-is. */\n $filter?: Record<string, any>;\n /** Soft-delete subset to count. Defaults to active records only. */\n scope?: RecordScope;\n /** Explicit backend scope alias, sent as-is. */\n $scope?: RecordScope;\n};\n\ntype ResourceSearchAllOptions<TRelations extends readonly string[] = readonly string[]> = {\n $match?: Record<string, any>;\n $search?: string;\n filter?: Record<string, any>;\n $filter?: Record<string, any>;\n sort?: ResourceSortInput;\n $sort?: Record<string, string | number>;\n relations?: TRelations;\n scope?: RecordScope;\n $scope?: RecordScope;\n collapse?: RecordCollapse;\n $collapse?: RecordCollapse;\n perPage?: number;\n per_page?: number;\n /** Maximum records to return (1\u20135,000). A warning is logged when matches exceed it. */\n maxRecords: number;\n};\n\ntype ResourcePagination = {\n current_page: number;\n last_page: number;\n per_page: number;\n total: number;\n from: number;\n to: number;\n first_page_url: string;\n last_page_url: string;\n next_page_url: string | null;\n prev_page_url: string | null;\n};\n\ntype ResourceCollectionResult<TRecord extends ResourceRecord = ResourceRecord> = {\n data: TRecord[];\n pagination: ResourcePagination;\n files?: FileMetadataMap;\n};\n\ntype ResourceRecord = { id: string; [key: string]: any };\n\ntype FileMetadataMap = Record<string, FileMetadata>;\n\ntype FileMetadata = {\nreadonly id: string,\nreadonly url: string,\nreadonly is_public: boolean,\nreadonly mime_type: string,\nreadonly original_filename: string,\nreadonly bytes: number,\nreadonly width: number | null,\nreadonly height: number | null,\nreadonly keywords: string[] | null,\nreadonly created_at: string | null,\nreadonly sizes: number[],\n};\n\ntype Diagonal = any;\n\ntype ResourceRecordsResult<TRecord extends ResourceRecord = ResourceRecord> = {\n data: TRecord[];\n};\n\ntype ResourceRecordResult<TRecord extends ResourceRecord = ResourceRecord> = {\n data: TRecord;\n /**\n * `series` is set on a save that changed a repeating series, and `booking` on a create on a\n * Booking resource that returned the first booking of a retry. Both are absent otherwise.\n */\n meta?: { series?: SdkSeriesChangeResult; booking?: SdkBookingCreateResult };\n};\n\ntype SdkSeriesChangeResult = {\n series_id: string;\n /** The counts cover only what ran before the save returned. */\n counts: {\n created: number;\n moved: number;\n updated: number;\n removed: number;\n kept: number;\n failed: number;\n /** Booking dates that wait for rescheduling, the queued remainder included. */\n waiting: number;\n };\n items: Array<{\n id: string;\n status: 'error';\n http_status: number;\n errors: string[];\n code:\n | 'series_kept_referenced'\n | 'series_kept_policy'\n | 'series_kept_locked'\n | 'series_kept_hook'\n | 'series_kept_override'\n | 'series_kept_capacity'\n | 'series_failed';\n }>;\n /**\n * On a Booking resource: every date of the planned range that waits for rescheduling. Each\n * booking is written with the waiting status, to reschedule by hand. `reason` is `full` for a\n * date with no capacity left and `closed` for a date outside open hours on a resource that\n * requires them. `record_id` is null for a date the queued remainder has not written yet.\n */\n waiting: Array<{\n date: string;\n record_id: string | null;\n reason: 'full' | 'closed';\n }>;\n /**\n * What part of the change still runs on the queue after the save returned, or null when it ran\n * whole. `from` is the first date of the queued remainder within the horizon, or null when only\n * the past part waits. `past` is true while the past occurrences of an \"All occurrences\" change\n * still wait. The counts cover only what ran before the save returned, so a save whose run was\n * queued whole has zero counts.\n */\n queued: { from: string | null; past: boolean } | null;\n};\n\ntype SdkBookingCreateResult = {\n replayed: true;\n};\n\ntype ResourceRecordPayload<TRecord extends ResourceRecord = ResourceRecord> =\n Partial<TRecord> & Record<string, any>;\n\ntype ResourceBulkUpdateRecord<TRecord extends ResourceRecord = ResourceRecord> =\n ResourceRecordPayload<TRecord> & { id: string };\n\ntype ResourceDeletePayload = {\n /**\n * Capability-specific operation options. Recurring eventable deletes accept\n * `{ eventable: { scope: 'this' | 'this_and_future' | 'all' } }`.\n */\n $operation?: Record<string, Record<string, any>>;\n};\n\ntype ResourceOperationRecord<TRecord extends ResourceRecord = ResourceRecord> = TRecord;\n\ntype ResourceUrls = {\n /** `GET` list records \u2014 `/api/data/resources/{id}/records`. */\n index: string;\n /** `POST` search records \u2014 `/api/data/resources/{id}/record/search`. */\n search: string;\n /** `GET` count records \u2014 `/api/data/resources/{id}/records/count`. */\n count: string;\n /** `POST` create a record \u2014 `/api/data/resources/{id}/records`. */\n store: string;\n /** `PUT` update multiple records \u2014 `/api/data/resources/{id}/records/bulk`. */\n bulk: string;\n /** `GET` one record \u2014 `/api/data/resources/{id}/records/{record}`. */\n show(recordId: string): string;\n /** `PUT` update a record \u2014 `/api/data/resources/{id}/records/{record}`. */\n update(recordId: string): string;\n /** `DELETE` soft-delete a record \u2014 `/api/data/resources/{id}/records/{record}`. */\n delete(recordId: string): string;\n /** `POST` restore a soft-deleted record \u2014 `/api/data/resources/{id}/records/{record}/restore`. */\n restore(recordId: string): string;\n /**\n * `PUT` update a field's schema \u2014 `/api/data/resources/{id}/fields/{field}`.\n * Requires the `manage_fields` permission; prefer\n * {@link SdkResourceOperations.updateField} over calling this directly.\n */\n field(fieldId: string): string;\n};\n\ntype ResourceFieldUpdatePayload = {\n type?: string;\n label?: string | null;\n description?: string | null;\n guard?: any;\n settings?: Record<string, any>;\n} & Record<string, any>;\n\ntype SdkDndIdentifier = string | number;\n\ntype SdkDndSortableItem = {\n id: SdkDndIdentifier;\n};\n\ntype SdkDndItemIdentifier<TItem> = TItem extends {\n id: infer TIdentifier;\n}\n ? Extract<TIdentifier, SdkDndIdentifier>\n : SdkDndIdentifier;\n\ntype SdkDndSortableOptions<TItem> = {\n /** Return the stable, unique drag id for an item. Items with an `id` do not need this option. */\n getItemId: (item: TItem) => SdkDndIdentifier;\n};\n\ntype SdkDndReorderChange<TItem> = {\n id: SdkDndItemIdentifier<TItem>;\n item: TItem;\n previousIndex: number;\n index: number;\n};\n\ntype SdkDndReorderResult<TItem> = {\n /** The complete array after the move. */\n items: TItem[];\n /** Only items whose array index changed. */\n changes: SdkDndReorderChange<TItem>[];\n};\n\ntype SdkDndSortableRenderState = {\n index: number;\n isDragging: boolean;\n};\n\ntype SdkDndLocation = {\n index: number;\n};\n\ntype SdkDndGroupedLocation = SdkDndLocation & {\n groupId: SdkDndIdentifier;\n};\n\ntype SdkDndMoveContext<TItem, TLocation extends SdkDndLocation = SdkDndLocation> = {\n item: TItem;\n source: TLocation;\n destination: TLocation;\n /** Items in the source collection when the drag began, including the active item. */\n sourceItems: readonly TItem[];\n /** Items currently in the destination collection, excluding the active item. */\n destinationItems: readonly TItem[];\n};\n\ntype SdkDndCanDrag<TItem, TLocation extends SdkDndLocation = SdkDndLocation> = (\n item: TItem,\n source: TLocation\n) => boolean;\n\ntype SdkDndCanDrop<TItem, TLocation extends SdkDndLocation = SdkDndLocation> = (\n context: SdkDndMoveContext<TItem, TLocation>\n) => boolean;\n\ntype SdkDndSortableListCommonProps<TItem> = {\n items: readonly TItem[];\n onError?: (error: any) => void;\n renderItem: (item: TItem, state: SdkDndSortableRenderState) => React.ReactNode;\n /** Optional for objects with an `id`; required at runtime for every other item shape. */\n getItemId?: (item: TItem) => SdkDndIdentifier;\n /** Return a nonempty accessible label for each item. Omit the callback for positional labels. */\n getItemLabel?: (item: TItem) => string;\n /** Return false to prevent an item from being picked up. */\n canDrag?: SdkDndCanDrag<TItem>;\n /** Return false to reject a proposed destination index. */\n canDrop?: SdkDndCanDrop<TItem>;\n ariaLabel?: string;\n layout?: 'vertical' | 'horizontal' | 'grid';\n className?: string;\n itemClassName?: string;\n handleClassName?: string;\n disabled?: boolean;\n};\n\ntype SdkDndCustomPersistence<TItem> = {\n /** Receives the complete reordered list immediately after a successful drop. */\n onItemsChange: (items: TItem[]) => void;\n resource?: never;\n sortField?: never;\n /**\n * Receives only changed positions plus the full result. Returning a rejected promise\n * rolls the optimistic list back and calls `onError`.\n */\n onReorder?: (\n changes: SdkDndReorderChange<TItem>[],\n result: SdkDndReorderResult<TItem>\n ) => any | Promise<any>;\n};\n\ntype SdkDndResourcePersistence<TItem> = {\n /**\n * Optional observer for the complete reordered list. Resource mode owns its optimistic\n * order and reconciles refreshed resource records whether this callback is present or not.\n */\n onItemsChange?: (items: TItem[]) => void;\n /** Resource handle whose existing bulk-update endpoint persists changed positions. */\n resource: Pick<SdkResourceOperations, 'updateMany'>;\n /** Stored Number field key or field handle used for ordering this list. */\n sortField: SdkFieldSelector;\n onReorder?: never;\n};\n\ntype SdkResourceOperations<TResource extends SdkResourceType = SdkResourceType> = {\n /** Capability field mappings resolved to fields \u2014 see {@link SdkResourceCapabilityFieldMappings}. */\n capabilities?: Record<string, SdkResourceCapabilityFieldMappings>;\n /** Record API endpoint URLs for this resource \u2014 see {@link ResourceUrls}. */\n urls: ResourceUrls;\n /**\n * Check whether the acting identity can perform an operation on this resource.\n * This reads the resource's server-resolved `policy_scope`; it is intended for\n * conditionally rendering page controls, while the API remains authoritative.\n *\n * @example\n * ```tsx\n * const invoices = Sdk.resource('...');\n * return invoices.can('create') ? <Sdk.Components.Button>New invoice</Sdk.Components.Button> : null;\n * ```\n */\n can(operation: SdkResourceOperation): boolean;\n /**\n * Page through every record the acting identity can read. Pagination, `relations`,\n * and `$scope` only \u2014 the list endpoint has no filtering or sorting, so passing\n * `filter`/`$filter`/`sort`/`$sort` throws instead of silently returning an\n * unfiltered page. Use {@link SdkResourceOperations.search} for those.\n */\n list<const TOptions extends ResourceListParams = ResourceListParams<readonly []>>(\n params?: TOptions & ValidateSdkResourceRelationOptions<TResource, NoInfer<TOptions>>\n ): Promise<\n ResourceCollectionResult<\n SdkResourceRecord<TResource, SdkResourceRelationsFromOptions<TResource, TOptions>>\n >\n >;\n search<const TOptions extends ResourceSearchBody>(\n body: TOptions & ValidateSdkResourceRelationOptions<TResource, NoInfer<TOptions>>\n ): Promise<\n ResourceCollectionResult<\n SdkResourceRecord<TResource, SdkResourceRelationsFromOptions<TResource, TOptions>>\n >\n >;\n /**\n * Fetch a bounded filtered slice across all of its pages. `maxRecords` is required\n * and capped at 5,000; the SDK logs a warning when more rows match than it returns.\n * Use this only when a client-side aggregate needs actual row values. Prefer\n * {@link SdkResourceOperations.count} for totals and `analytics` for server-side\n * numeric aggregates. Use `useInfiniteRecords` for scrolling collection UIs.\n */\n searchAll<\n const TRelations extends readonly SdkResourceRelationPath<NoInfer<TResource>>[] = readonly []\n >(\n options: ResourceSearchAllOptions<TRelations>\n ): Promise<SdkResourceRecord<TResource, TRelations>[]>;\n /**\n * Return the exact server-computed number of readable matching records without\n * fetching record rows. Accepts the same structured `filter` tree and `scope` as\n * the record hooks.\n */\n count(options?: ResourceCountOptions): Promise<number>;\n /**\n * Fetch one record. Rejects with {@link SdkApiError}; classify policy-denied and\n * missing reads with `Sdk.utils.error.isForbidden` / `isNotFound`. This matters\n * on public pages, where a guest denied by policy must not be shown a false\n * \"record not found\" state.\n */\n get(\n recordId: string\n ): Promise<ResourceRecordResult<ResourceOperationRecord<ResourceRecordFrom<TResource>>>>;\n /**\n * Create a record. On success, cached record and analytics queries are invalidated\n * across resources so reactive hooks (`useResourceDataTable`, `useRecord`,\n * `useAnalytics`, \u2026) refresh automatically \u2014 no manual remount or reload is needed.\n *\n * On a Booking resource, a booking that does not fit rejects with a 422 whose errors hold\n * the message under `$capacity`, such as \"This time is outside open hours.\" Show it to\n * the visitor. The first date of a repeating booking must fit like any booking. When it fits,\n * the create resolves, and `meta.series.waiting` lists each later date that waits for\n * rescheduling, with its `record_id` and `reason`, so the page can offer an action for each\n * date.\n *\n * @example\n * ```ts\n * try {\n * const { meta } = await lessons.create(values);\n * const waiting = meta?.series?.waiting ?? [];\n * if (waiting.length > 0) {\n * setNotice(`${waiting.length} dates need rescheduling`);\n * }\n * } catch (error) {\n * const capacity = Sdk.utils.error.extractApiErrorBody(error)?.errors?.$capacity?.[0];\n * setError(capacity ?? Sdk.utils.error.apiErrorMessage(error, 'The booking was not saved.'));\n * }\n * ```\n */\n create(\n payload: ResourceRecordPayload<ResourceRecordFrom<TResource>>\n ): Promise<ResourceRecordResult<ResourceOperationRecord<ResourceRecordFrom<TResource>>>>;\n /**\n * Update a record. On success, cached record and analytics queries are invalidated\n * across resources so reactive hooks (`useResourceDataTable`, `useRecord`,\n * `useAnalytics`, \u2026) refresh automatically \u2014 no manual remount or reload is needed.\n * On a Booking resource, read a refusal and `meta.series` as `create` describes.\n */\n update(\n recordId: string,\n payload: ResourceRecordPayload<ResourceRecordFrom<TResource>>\n ): Promise<ResourceRecordResult<ResourceOperationRecord<ResourceRecordFrom<TResource>>>>;\n /**\n * Update multiple records with the existing bulk-update endpoint. Empty arrays are a\n * client-side no-op. On success, cached record and analytics queries are invalidated.\n */\n updateMany(\n records: readonly ResourceBulkUpdateRecord<ResourceRecordFrom<TResource>>[]\n ): Promise<ResourceRecordsResult<ResourceOperationRecord<ResourceRecordFrom<TResource>>>>;\n /**\n * Delete a record. On success, cached record and analytics queries are invalidated\n * across resources so reactive hooks refresh automatically (the deleted record's\n * own per-record queries are left alone \u2014 refetching them would 404).\n */\n delete(\n recordId: string,\n payload?: ResourceDeletePayload\n ): Promise<ResourceRecordResult<ResourceOperationRecord<ResourceRecordFrom<TResource>>>>;\n /**\n * Restore a soft-deleted record. On success, cached record and analytics data is\n * refreshed across resources the same way `delete` refreshes it, and the restored\n * record is written straight into the subject's own cache entries.\n *\n * @example\n * ```ts\n * await Sdk.resource('...').restore(recordId);\n * ```\n */\n restore(\n recordId: string\n ): Promise<ResourceRecordResult<ResourceOperationRecord<ResourceRecordFrom<TResource>>>>;\n /**\n * Update a field's schema on this resource \u2014 its label, guard, or `settings`\n * (for a select field, `settings.options` is the editable option list). Accepts\n * the field's key or its handle from `resource.fields`. The acting identity must\n * hold the `manage_fields` permission; without it the endpoint responds 403 \u2014\n * this is a schema operation, so `can(...)` (which reads record policy scope)\n * says nothing about it. `settings` replaces the stored settings \u2014 send the\n * complete object, not a diff. On success the handle's own `fields` metadata is\n * refreshed in place, so UI built from it reflects the change without a reload.\n *\n * @example\n * ```ts\n * const businesses = Sdk.resource('<businesses-resource-id>');\n * const stage = businesses.fields.pipeline_stage;\n * await businesses.updateField(stage, {\n * settings: {\n * ...stage.settings,\n * options: [\n * ...(stage.settings.options ?? []),\n * { value: 'proposal', label: 'Proposal', color: 'blue' }\n * ]\n * }\n * });\n * ```\n */\n updateField(\n field: string | SdkResourceField,\n payload: ResourceFieldUpdatePayload\n ): Promise<SdkResourceField>;\n /**\n * Refresh every cached record and analytics query on the page across resources \u2014\n * lists, single records, relation pickers, calendars, and dashboard aggregations.\n * Cross-resource invalidation is intentional because relation-backed displays,\n * filters, and aggregates can depend on records from another resource. Reach for it\n * after a write the SDK didn't make itself: a workflow run, a websocket event, or a\n * raw `Sdk.api` call against a custom endpoint. Records mutated through\n * `create`/`update`/`delete`/`restore` already refresh on their own.\n *\n * @example\n * ```ts\n * const invoices = Sdk.resource('...');\n * await Sdk.api.post('/api/custom/invoices/reconcile');\n * await invoices.invalidate();\n * ```\n */\n invalidate(): Promise<void>;\n\n /**\n * Aggregate analytics for this resource \u2014 `kpi`, `timeSeries`, `leaderboard`, and\n * `distribution`; see {@link SdkResourceAnalytics}. Always present, and scoped by the resource's\n * read policies for the acting identity.\n *\n * @example\n * ```ts\n * const revenue = await Sdk.resource('...').analytics.kpi({\n * metrics: [{ name: 'total', operation: 'sum', field: 'amount' }]\n * });\n * ```\n */\n analytics: SdkResourceAnalytics;\n\n /**\n * Build calendar invite links for a record on this resource. Returns relative\n * URLs for Google Calendar, Apple/ICS download, and Outlook \u2014 use as `<a href>`\n * targets, not with `Sdk.api`.\n *\n * Only present at runtime on resources with the `eventable` capability configured.\n * On non-eventable resources this property is `undefined`.\n *\n * @example\n * ```tsx\n * const lessons = Sdk.resource('<lessons-resource-id>');\n * const links = lessons.calendarLinks(record.id);\n * <a href={links.google}>Add to Google Calendar</a>\n * <a href={links.apple}>Download .ics</a>\n * <a href={links.outlook}>Add to Outlook</a>\n * ```\n */\n calendarLinks?(recordId: string): CalendarLinks;\n};\n\ntype SdkResourceType<\n TRecord extends ResourceRecord = ResourceRecord,\n TRelations extends ResourceRelationCatalog = ResourceRelationCatalog\n> = {\n readonly [sdkResourceRecordCatalog]?: TRecord;\n readonly [sdkResourceRelationCatalog]?: TRelations;\n};\n\ntype ResourceRelationCatalog = Record<string, Record<string, any>>;\n\ntype SdkResourceOperation =\n | 'list'\n | 'search'\n | 'get'\n | 'create'\n | 'createMany'\n | 'update'\n | 'delete'\n | 'restore'\n | 'count'\n | 'analytics'\n | 'validate';\n\ntype ValidateSdkResourceRelationOptions<\n TResource extends SdkResourceType,\n TOptions extends { relations?: readonly string[] }\n> = TOptions extends { relations: infer TRelations extends readonly string[] }\n ? string extends TRelations[number]\n ? any\n : TRelations extends readonly SdkResourceRelationPath<TResource>[]\n ? any\n : never\n : any;\n\ntype SdkResourceRelationPath<TResource extends SdkResourceType> = Extract<\n keyof ResourceRelationsFrom<TResource>,\n string\n>;\n\ntype ResourceRelationsFrom<TResource extends SdkResourceType> = TResource extends {\n readonly [sdkResourceRelationCatalog]?: infer TRelations extends ResourceRelationCatalog;\n}\n ? TRelations\n : ResourceRelationCatalog;\n\ntype SdkResourceRecord<\n TResource extends SdkResourceType | null | undefined,\n TRelations extends readonly SdkResourceRelationPath<NonNullable<TResource>>[] = readonly []\n> = SdkSelectedResourceRecord<\n ResourceRecordFrom<NonNullable<TResource>>,\n ResourceRelationsFrom<NonNullable<TResource>>,\n TRelations\n>;\n\ntype SdkSelectedResourceRecord<\n TRecord extends ResourceRecord,\n TRelationCatalog extends ResourceRelationCatalog,\n TRelations extends readonly Extract<keyof TRelationCatalog, string>[] = readonly []\n> = TRecord & SelectedResourceRelations<TRelationCatalog, TRelations>;\n\ntype SelectedResourceRelations<\n TRelationCatalog extends ResourceRelationCatalog,\n TRelations extends readonly Extract<keyof TRelationCatalog, string>[]\n> = [TRelations[number]] extends [never]\n ? object\n : string extends keyof TRelationCatalog\n ? UnionToIntersection<TRelationCatalog[TRelations[number]]>\n : IsRelationConstraintFallback<TRelationCatalog, TRelations> extends true\n ? object\n : MergeResourceRelationFragments<TRelationCatalog[TRelations[number]]>;\n\ntype UnionToIntersection<TValue> = (\n TValue extends any ? (value: TValue) => void : never\n) extends (value: infer TIntersection) => void\n ? TIntersection\n : never;\n\ntype IsRelationConstraintFallback<\n TRelationCatalog extends ResourceRelationCatalog,\n TRelations extends readonly string[]\n> = number extends TRelations['length']\n ? [Exclude<keyof TRelationCatalog, TRelations[number]>] extends [never]\n ? true\n : false\n : false;\n\ntype MergeResourceRelationFragments<TUnion> = [RelationKeysRequiringMerge<TUnion>] extends [never]\n ? UnionToIntersection<TUnion>\n : // Generated relation keys are always required and mutable, so rebuilding them does not drop modifiers.\n Omit<UnionToIntersection<TUnion>, RelationKeysRequiringMerge<TUnion>> & {\n [TKey in RelationKeysRequiringMerge<TUnion>]: MergeResourceRelationValue<\n ValueOfUnionKey<TUnion, TKey>\n >;\n };\n\ntype RelationKeysRequiringMerge<TUnion> = {\n [TKey in KeysOfUnion<TUnion>]: IsAny<ValueOfUnionKey<TUnion, TKey>> extends true\n ? never\n : [ValueOfUnionKey<TUnion, TKey>] extends [readonly any[]]\n ? TKey\n : null extends ValueOfUnionKey<TUnion, TKey>\n ? [Exclude<ValueOfUnionKey<TUnion, TKey>, null | undefined>] extends [object]\n ? [\n RelationKeysRequiringMerge<Exclude<ValueOfUnionKey<TUnion, TKey>, null | undefined>>\n ] extends [never]\n ? never\n : TKey\n : never\n : never;\n}[KeysOfUnion<TUnion>];\n\ntype KeysOfUnion<TValue> = TValue extends any ? keyof TValue : never;\n\ntype IsAny<TValue> = 0 extends 1 & TValue ? true : false;\n\ntype ValueOfUnionKey<TUnion, TKey extends PropertyKey> = TUnion extends any\n ? TKey extends keyof TUnion\n ? TUnion[TKey]\n : never\n : never;\n\ntype PropertyKey = any;\n\ntype MergeResourceRelationValue<TValue> =\n IsAny<TValue> extends true\n ? TValue\n : [TValue] extends [readonly any[]]\n ? MergeResourceRelationFragments<ArrayElementOfUnion<TValue>>[]\n :\n | MergeResourceRelationFragments<Exclude<TValue, null | undefined>>\n | (null extends TValue ? null : never)\n | (undefined extends TValue ? undefined : never);\n\ntype ArrayElementOfUnion<TValue> = TValue extends readonly (infer TElement)[] ? TElement : never;\n\ntype ResourceRecordFrom<TResource extends SdkResourceType> = TResource extends {\n readonly [sdkResourceRecordCatalog]?: infer TRecord extends ResourceRecord;\n}\n ? TRecord\n : ResourceRecord;\n\ntype SdkResourceRelationsFromOptions<\n TResource extends SdkResourceType,\n TOptions extends { relations?: readonly string[] }\n> = TOptions extends { relations: infer TRelations extends readonly string[] }\n ? TRelations extends readonly SdkResourceRelationPath<TResource>[]\n ? TRelations\n : readonly []\n : readonly [];\n\ntype SdkResourceAnalytics = {\n /**\n * Compute one or more aggregate metrics across matching records, returning a single\n * {@link AnalyticsKpiResult} keyed by your metric `name`s. Use for headline numbers \u2014 total\n * revenue, active count, average score. Supports `distinct`, unlike the other analytics methods.\n *\n * @example\n * ```ts\n * const stats = await Sdk.resource('...').analytics.kpi({\n * metrics: [\n * { name: 'revenue', operation: 'sum', field: 'amount' },\n * { name: 'customers', operation: 'distinct', field: 'client_id' }\n * ]\n * });\n * ```\n */\n kpi<const TMetrics extends readonly AnalyticsMetric[]>(\n params: AnalyticsKpiParams<TMetrics>\n ): Promise<AnalyticsKpiResult<TMetrics>>;\n\n /**\n * Bucket records into a time series by `date_field` at the chosen `granularity`, computing each\n * metric per bucket. Buckets are returned oldest-first as an array of {@link AnalyticsRow}s, each\n * carrying the `date_field` key (`2026-07` for month, `2026-W28` for week, `2026-07-09` for day,\n * \u2026) plus your metric names. Ideal for line/bar charts over time.\n *\n * @example\n * ```ts\n * const monthly = await Sdk.resource('...').analytics.timeSeries({\n * date_field: 'created_at',\n * granularity: 'month',\n * metrics: [{ name: 'signups', operation: 'count' }]\n * });\n * // [{ created_at: '2026-05', signups: 12 }, { created_at: '2026-06', signups: 31 }, ...]\n * ```\n */\n timeSeries(params: AnalyticsTimeSeriesParams): Promise<AnalyticsRow[]>;\n\n /**\n * Rank entities by a single aggregation, highest first. Each {@link AnalyticsRow} carries the\n * `entity_field` key, the score under `metric_name` (default `value`), and a `count` companion\n * when `operation` is not `count`. Great for \"top N\" lists.\n *\n * @example\n * ```ts\n * const topReps = await Sdk.resource('...').analytics.leaderboard({\n * entity_field: 'sales_rep',\n * operation: 'sum',\n * value_field: 'amount',\n * metric_name: 'revenue',\n * limit: 5\n * });\n * // [{ sales_rep: 'Ada', revenue: { amount: 90000, currency: 'usd' }, count: 9 }, ...]\n * ```\n */\n leaderboard(params: AnalyticsLeaderboardParams): Promise<AnalyticsRow[]>;\n\n /**\n * Group records by `category_field` and return a `count` per category (largest first), plus a\n * summed `total` when `value_field` is given. Each row is an {@link AnalyticsRow} with the\n * `category_field` key, `count`, and optional `total`. Use for pie/donut breakdowns and category\n * tables.\n *\n * @example\n * ```ts\n * const byStatus = await Sdk.resource('...').analytics.distribution({\n * category_field: 'status',\n * value_field: 'amount'\n * });\n * // [{ status: 'paid', count: 120, total: {...} }, { status: 'open', count: 45, total: {...} }]\n * ```\n */\n distribution(params: AnalyticsDistributionParams): Promise<AnalyticsRow[]>;\n};\n\ntype AnalyticsMetric = {\n /** Response key for this metric's value. `alpha_dash` (letters, numbers, `-`, `_`). */\n name: string;\n /** Aggregation to apply. `count` takes no `field`; every other operation requires one. */\n operation: 'count' | 'sum' | 'avg' | 'min' | 'max' | 'distinct';\n /** Field key to aggregate. Omit for `count`; required for every other operation. */\n field?: string;\n};\n\ntype AnalyticsKpiParams<TMetrics extends readonly AnalyticsMetric[] = AnalyticsMetric[]> = {\n /** 1\u201325 metrics to compute across matching records. */\n metrics: TMetrics;\n /** Structured filter tree scoping which records are aggregated \u2014 same `$filter` shape as {@link ResourceSearchBody}. */\n $filter?: Record<string, any>;\n};\n\ntype AnalyticsKpiResult<TMetrics extends readonly AnalyticsMetric[] = AnalyticsMetric[]> = {\n [TMetric in TMetrics[number] as TMetric['name']]?: AnalyticsValue;\n};\n\ntype AnalyticsValue = number | { amount: number; currency: string };\n\ntype AnalyticsTimeSeriesParams = {\n /** Date/datetime field key (or `created_at` / `updated_at`) to bucket rows by. */\n date_field: string;\n /** Bucket size \u2014 see {@link AnalyticsGranularity}. */\n granularity: AnalyticsGranularity;\n /** 1\u201325 metrics to compute per bucket. */\n metrics: AnalyticsTimeSeriesMetric[];\n /** Max buckets to return (1\u20131000, default 100), oldest first. */\n limit?: number;\n /** Structured filter tree \u2014 same `$filter` shape as {@link ResourceSearchBody}. */\n $filter?: Record<string, any>;\n};\n\ntype AnalyticsGranularity = 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';\n\ntype AnalyticsTimeSeriesMetric = {\n /** Response key for this metric's value. `alpha_dash` (letters, numbers, `-`, `_`). */\n name: string;\n /** Aggregation to apply. `count` takes no `field`; every other operation requires one. */\n operation: 'count' | 'sum' | 'avg' | 'min' | 'max';\n /** Field key to aggregate. Omit for `count`; required for every other operation. */\n field?: string;\n};\n\ntype AnalyticsRow = Record<string, any>;\n\ntype AnalyticsLeaderboardParams = {\n /** Field key that identifies the entity each ranked row represents. */\n entity_field: string;\n /** How to score each entity. `count` ranks by record count and takes no `value_field`. */\n operation: 'count' | 'sum' | 'avg';\n /** Numeric/money field to `sum`/`avg`; required for those, omit for `count`. */\n value_field?: string;\n /** Response key for the score (`alpha_dash`, default `value`). */\n metric_name?: string;\n /** Top-N rows to return (1\u2013100, default 10). */\n limit?: number;\n /** Structured filter tree \u2014 same `$filter` shape as {@link ResourceSearchBody}. */\n $filter?: Record<string, any>;\n};\n\ntype AnalyticsDistributionParams = {\n /** Field key to group the distribution by. */\n category_field: string;\n /** Optional numeric/money field summed into a `total` per category. */\n value_field?: string;\n /** Max categories to return (1\u201350, default 10), largest first. */\n limit?: number;\n /** Structured filter tree \u2014 same `$filter` shape as {@link ResourceSearchBody}. */\n $filter?: Record<string, any>;\n};\n\ntype CalendarLinks = {\n /** Redirects to Google Calendar's \"add event\" URL. */\n google: string;\n /** Downloads an `.ics` calendar file. */\n apple: string;\n /** Redirects to Outlook's \"add event\" URL. */\n outlook: string;\n};\n\ntype SdkFieldSelector<TKey extends string = string> = TKey | { readonly key: TKey };\n\ntype SdkDndSortableListProps<TItem> = SdkDndSortableListCommonProps<TItem> &\n (SdkDndCustomPersistence<TItem> | SdkDndResourcePersistence<TItem>);\n\ntype SdkDndSortableListResourceProps<\n TResource extends SdkResourceType & Pick<SdkResource, 'fields' | 'updateMany'>,\n TItem extends SdkDndResourceItem<NoInfer<TResource>> = SdkDndResourceItem<TResource>\n> = SdkDndSortableListCommonProps<TItem> & {\n onItemsChange?: (items: TItem[]) => void;\n resource: TResource;\n sortField: SdkFieldSelector<\n Extract<\n SdkDndNumberFieldKey<ResourceRecordFrom<NoInfer<TResource>>>,\n keyof NoInfer<TResource>['fields']\n >\n >;\n onReorder?: never;\n};\n\ntype SdkResource<\n TFields extends Record<string, SdkResourceField> = Record<string, SdkResourceField>,\n TRecord extends ResourceRecord = ResourceRecord,\n TRelations extends ResourceRelationCatalog = ResourceRelationCatalog,\n TWorkflowInputs extends SdkResourceWorkflowInputs = SdkResourceWorkflowInputs\n> = {\n id: string;\n name: string;\n singular_name: string;\n display_field_id: string | null;\n display_field_key: string | null;\n policy_scope: number | null;\n disabled_data_api_endpoints?: string[];\n fields: TFields;\n workflow_inputs: TWorkflowInputs;\n readonly [sdkResourceRecordCatalog]?: TRecord;\n readonly [sdkResourceRelationCatalog]?: TRelations;\n} & SdkResourceOperations<SdkResourceType<TRecord, TRelations>>;\n\ntype SdkResourceWorkflowInputs = Partial<\n Record<SdkWorkflowEvent, Record<string, SdkWorkflowInput>>\n>;\n\ntype SdkWorkflowEvent = 'created' | 'deleted' | 'restored' | 'saved' | 'updated';\n\ntype SdkWorkflowInput = {\n workflow_id: string;\n workflow_resource_id?: string | null;\n workflow_event?: string | null;\n id: string | null;\n workspace_id?: string | null;\n configurable_id: string | null;\n configurable_type: string | null;\n type: string;\n scope: string | null;\n key: string;\n value: any;\n label: string | null;\n description: string | null;\n guard: any[] | Record<string, any> | null;\n settings: SdkWorkflowInputSettings | null;\n resolver: Record<string, any> | null;\n sort_order?: number | null;\n created_at?: string;\n updated_at?: string;\n deleted_at?: string | null;\n};\n\ntype SdkWorkflowInputSettings = {\n ui?: SdkResourceFieldSettingsUi;\n [key: string]: any;\n};\n\ntype SdkDndResourceItem<TResource extends SdkResourceType = SdkResourceType> =\n ResourceRecordFrom<TResource> & { id: string };\n\ntype SdkDndNumberFieldKey<TItem extends ResourceRecord> = string extends keyof TItem\n ? string\n : Extract<\n {\n [TKey in keyof TItem]-?: NonNullable<TItem[TKey]> extends number ? TKey : never;\n }[keyof TItem],\n string\n >;\n\ntype SdkDndSortableListComponent = {\n <\n TResource extends SdkResourceType & Pick<SdkResource, 'fields' | 'updateMany'>,\n TItem extends SdkDndResourceItem<NoInfer<TResource>>\n >(\n props: SdkDndSortableListResourceProps<TResource, TItem>\n ): React.ReactNode;\n <TItem>(\n props: SdkDndSortableListCommonProps<TItem> & SdkDndCustomPersistence<TItem>\n ): React.ReactNode;\n};\n\ntype SdkDndSortableGroupRenderState<TItem> = {\n id: SdkDndIdentifier;\n items: readonly TItem[];\n isOver: boolean;\n children: React.ReactNode;\n};\n\ntype SdkDndGroupMoveChange<TItem> = {\n id: SdkDndItemIdentifier<TItem>;\n item: TItem;\n source: SdkDndGroupedLocation;\n destination: SdkDndGroupedLocation;\n};\n\ntype SdkDndGroupMoveResult<TItem> = {\n /** All items flattened in group order after the move. */\n items: TItem[];\n /** Only items whose group or index changed. */\n changes: SdkDndGroupMoveChange<TItem>[];\n};\n\ntype SdkDndSortableGroupsCommonProps<TItem, TGroup> = {\n items: readonly TItem[];\n groups: readonly TGroup[];\n getGroupId: (group: TGroup) => SdkDndIdentifier;\n renderGroup: (group: TGroup, state: SdkDndSortableGroupRenderState<TItem>) => React.ReactNode;\n renderItem: (item: TItem, state: SdkDndSortableRenderState) => React.ReactNode;\n /** Optional for objects with an `id`; required at runtime for every other item shape. */\n getItemId?: (item: TItem) => SdkDndIdentifier;\n /** Return a nonempty accessible label for each item. Omit the callback for positional labels. */\n getItemLabel?: (item: TItem) => string;\n getGroupLabel?: (group: TGroup) => string;\n canDrag?: SdkDndCanDrag<TItem, SdkDndGroupedLocation>;\n canDrop?: SdkDndCanDrop<TItem, SdkDndGroupedLocation>;\n onError?: (error: any) => void;\n ariaLabel?: string;\n className?: string;\n groupClassName?: string;\n itemsClassName?: string;\n itemClassName?: string;\n handleClassName?: string;\n disabled?: boolean;\n};\n\ntype SdkDndSortableGroupsCustomPersistence<TItem, TGroup> = {\n getItemGroupId: (item: TItem) => SdkDndIdentifier;\n setItemGroupId: (item: TItem, groupId: SdkDndIdentifier, group: TGroup) => TItem;\n onItemsChange: (items: TItem[]) => void;\n resource?: never;\n groupField?: never;\n sortField?: never;\n /**\n * Receives only changed group positions plus the complete result. A rejected promise\n * rolls the optimistic groups back and calls `onError`.\n */\n onMove?: (\n changes: SdkDndGroupMoveChange<TItem>[],\n result: SdkDndGroupMoveResult<TItem>\n ) => any | Promise<any>;\n};\n\ntype SdkDndSortableGroupsResourcePersistence<TItem, TGroup> = {\n /** Override how a stored group-field value maps to a group id. */\n getItemGroupId?: (item: TItem) => SdkDndIdentifier;\n /**\n * Optionally build the optimistic item value for a destination group. Use this when\n * presentation groups are not the stored relation records. The SDK still persists the\n * destination group id and owns the bulk update, rollback, and cache refresh.\n */\n setItemGroupId?: (item: TItem, groupId: SdkDndIdentifier, group: TGroup) => TItem;\n onItemsChange?: (items: TItem[]) => void;\n resource: Pick<SdkResourceOperations, 'updateMany'>;\n /** Stored field key or handle that assigns an item to a group. */\n groupField: SdkFieldSelector;\n /** Stored Number field key or handle used for ordering within each group. */\n sortField: SdkFieldSelector;\n onMove?: never;\n};\n\ntype SdkDndSortableGroupsProps<TItem, TGroup> = SdkDndSortableGroupsCommonProps<\n TItem,\n TGroup\n> &\n (\n | SdkDndSortableGroupsCustomPersistence<TItem, TGroup>\n | SdkDndSortableGroupsResourcePersistence<TItem, TGroup>\n );\n\ntype SdkDndSortableGroupsResourceProps<\n TResource extends SdkResourceType & Pick<SdkResource, 'fields' | 'updateMany'>,\n TItem extends SdkDndResourceItem<NoInfer<TResource>>,\n TGroup\n> = SdkDndSortableGroupsCommonProps<TItem, TGroup> & {\n getItemGroupId?: (item: TItem) => SdkDndIdentifier;\n setItemGroupId?: (item: TItem, groupId: SdkDndIdentifier, group: TGroup) => TItem;\n onItemsChange?: (items: TItem[]) => void;\n resource: TResource;\n groupField: SdkFieldSelector<Extract<keyof NoInfer<TResource>['fields'], string>>;\n sortField: SdkFieldSelector<\n Extract<\n SdkDndNumberFieldKey<ResourceRecordFrom<NoInfer<TResource>>>,\n keyof NoInfer<TResource>['fields']\n >\n >;\n onMove?: never;\n};\n\ntype SdkDndSortableGroupsComponent = {\n <\n TResource extends SdkResourceType & Pick<SdkResource, 'fields' | 'updateMany'>,\n TItem extends SdkDndResourceItem<NoInfer<TResource>>,\n TGroup\n >(\n props: SdkDndSortableGroupsResourceProps<TResource, TItem, TGroup>\n ): React.ReactNode;\n <TItem, TGroup>(\n props: SdkDndSortableGroupsCommonProps<TItem, TGroup> &\n SdkDndSortableGroupsCustomPersistence<TItem, TGroup>\n ): React.ReactNode;\n};\n\ntype SdkDndReorder = {\n <TItem extends SdkDndSortableItem>(\n items: readonly TItem[],\n activeId: SdkDndIdentifier,\n overId: SdkDndIdentifier | null,\n options?: SdkDndSortableOptions<TItem>\n ): SdkDndReorderResult<TItem>;\n <TItem>(\n items: readonly TItem[],\n activeId: SdkDndIdentifier,\n overId: SdkDndIdentifier | null,\n options: SdkDndSortableOptions<TItem>\n ): SdkDndReorderResult<TItem>;\n};\n\ntype SdkDnd = {\n /**\n * Accessible sortable list with drag handles and keyboard controls. For resource records,\n * pass `resource` and `sortField`; the list owns optimistic ordering, calls `updateMany`,\n * refreshes record queries, and rolls back failures without page-authored state or effects.\n * `onItemsChange` is required only for non-resource lists and optional when a page needs\n * to observe a resource list's new order.\n *\n * @remarks Resource mode keeps `onItemsChange?: (items) => void` optional. Both modes\n * accept `canDrag?: (item, source) => boolean` and\n * `canDrop?: (context) => boolean` constraints.\n *\n * @example\n * ```tsx\n * const students = Sdk.resource('students');\n *\n * return function StudentRanking() {\n * const { data, isLoading, isError } = Sdk.hooks.useRecords(students, {\n * sort: 'rank',\n * perPage: 100\n * });\n *\n * if (isLoading) return <Sdk.Components.Skeleton />;\n * if (isError) return <Sdk.Components.Alert>Could not load students.</Sdk.Components.Alert>;\n *\n * return (\n * <Sdk.Dnd.SortableList\n * items={data}\n * resource={students}\n * sortField={students.fields.rank}\n * renderItem={(student) => <span>{student.name}</span>}\n * getItemLabel={(student) => student.name ?? 'Unnamed student'}\n * ariaLabel=\"Students by rank\"\n * />\n * );\n * }\n * ```\n */\n SortableList: SdkDndSortableListComponent;\n\n /**\n * Accessible sorting across multiple ordered groups. Groups are presentation-agnostic:\n * use them for statuses, positions, categories, lanes, or a Kanban layout. Resource mode\n * persists the grouping field and per-group Number order in one `updateMany` request.\n *\n * @remarks Resource mode keeps `onItemsChange?: (items) => void` optional. Both modes\n * accept `canDrag?: (item, source) => boolean` and\n * `canDrop?: (context) => boolean` for movement constraints between any group shapes.\n * When presentation groups differ from stored relation records, use\n * `setItemGroupId?: (item, groupId, group) => item` to shape the optimistic item; the\n * SDK still owns persistence and sends the destination group id.\n *\n * @example\n * ```tsx\n * const tasks = Sdk.resource('tasks');\n * const statuses = [\n * { id: 'todo', label: 'To do' },\n * { id: 'in_progress', label: 'In progress' },\n * { id: 'done', label: 'Done' }\n * ];\n *\n * return function TaskBoard() {\n * const { data } = Sdk.hooks.useRecords(tasks, { sort: ['status', 'rank'], perPage: 100 });\n *\n * return (\n * <Sdk.Dnd.SortableGroups\n * items={data}\n * groups={statuses}\n * getGroupId={(status) => status.id}\n * getGroupLabel={(status) => status.label}\n * resource={tasks}\n * groupField={tasks.fields.status}\n * sortField={tasks.fields.rank}\n * renderGroup={(status, { children }) => (\n * <section><h2>{status.label}</h2>{children}</section>\n * )}\n * renderItem={(task) => <span>{task.title}</span>}\n * />\n * );\n * }\n * ```\n */\n SortableGroups: SdkDndSortableGroupsComponent;\n\n /**\n * Pure move calculation for custom drag-and-drop UI. Returns the full reordered array and\n * only items whose zero-based indices changed. `SortableList` already calls this internally.\n *\n * @example\n * ```ts\n * const result = Sdk.Dnd.reorder(todos, active.id, over?.id ?? null);\n * setTodos(result.items);\n * await saveChangedTodos(result.changes);\n * ```\n */\n reorder: SdkDndReorder;\n} & Pick<\n any,\n | 'applyModifiers'\n | 'AutoScrollActivator'\n | 'closestCenter'\n | 'closestCorners'\n | 'defaultAnnouncements'\n | 'defaultCoordinates'\n | 'defaultDropAnimation'\n | 'defaultDropAnimationSideEffects'\n | 'defaultKeyboardCoordinateGetter'\n | 'defaultScreenReaderInstructions'\n | 'DndContext'\n | 'DragOverlay'\n | 'getClientRect'\n | 'getFirstCollision'\n | 'getScrollableAncestors'\n | 'KeyboardCode'\n | 'KeyboardSensor'\n | 'MeasuringFrequency'\n | 'MeasuringStrategy'\n | 'MouseSensor'\n | 'PointerSensor'\n | 'pointerWithin'\n | 'rectIntersection'\n | 'TouchSensor'\n | 'TraversalOrder'\n | 'useDndContext'\n | 'useDndMonitor'\n | 'useDraggable'\n | 'useDroppable'\n | 'useSensor'\n | 'useSensors'\n> &\n Pick<\n any,\n | 'arrayMove'\n | 'arraySwap'\n | 'defaultAnimateLayoutChanges'\n | 'defaultNewIndexGetter'\n | 'hasSortableData'\n | 'horizontalListSortingStrategy'\n | 'rectSortingStrategy'\n | 'rectSwappingStrategy'\n | 'sortableKeyboardCoordinates'\n | 'SortableContext'\n | 'useSortable'\n | 'verticalListSortingStrategy'\n > &\n Pick<\n any,\n | 'createSnapModifier'\n | 'restrictToFirstScrollableAncestor'\n | 'restrictToHorizontalAxis'\n | 'restrictToParentElement'\n | 'restrictToVerticalAxis'\n | 'restrictToWindowEdges'\n | 'snapCenterToCursor'\n > &\n Pick<any, 'CSS'>;\n\ntype SdkUseAnalyticsOptions = {\n /** Set `false` to hold the request until the page has what the aggregation needs. */\n enabled?: boolean;\n};\n\ntype SdkAnalyticsHandle<TResult> = {\n data: TResult | undefined;\n isLoading: boolean;\n isError: boolean;\n invalidate: () => Promise<void>;\n};\n\ninterface ResourceRecordCatalog {}\n\ntype SdkRecordOf<TKey extends keyof ResourceRecordCatalog> = ResourceRecordCatalog[TKey];\n\ntype WorkflowReference =\n | Extract<keyof WorkflowCatalog, string>\n | (string & Record<never, never>);\n\ninterface WorkflowCatalog {}\n\ninterface PageAliasCatalog {}\n\ntype PageReference =\n | Extract<keyof PageAliasCatalog, string>\n | (string & Record<never, never>);\n\ntype SdkPageHandle = {\n /** The page's stable id. */\n readonly id: string;\n /** The published route pattern, e.g. `/invoices/{invoice}`, or null when the page is not published. */\n readonly route: string | null;\n /**\n * The page's URL with `params` filled into the route, or null when the page\n * is not published or a route parameter is missing.\n *\n * @example\n * const href = Sdk.page(INVOICE_DETAIL_PAGE_ID).url({ invoice: row.id });\n * return href ? <a href={href}>View</a> : null;\n */\n url(params?: Record<string, any>): string | null;\n /**\n * Navigate to the page with `params` filled into the route. Unpublished\n * pages and missing route params are ignored after a warning.\n *\n * @example\n * Sdk.page(STUDENTS_PAGE_ID).navigate();\n * @example\n * Sdk.page(INVOICE_DETAIL_PAGE_ID).navigate({ invoice: row.id }, { openInNewTab: true });\n */\n navigate(params?: Record<string, any>, options?: TaskNavigateOptions): void;\n};\n\ntype TaskNavigateOptions = {\n openInNewTab?: boolean;\n preserveScroll?: boolean;\n preserveState?: boolean;\n};\n\ntype SdkResourceReference = SdkResourceHandle | string | null | undefined;\n\ntype SdkResourceHandle = Omit<SdkResource, keyof SdkResourceOperations>;\n\ntype SdkResourceQueryReference<TUrl extends keyof ResourceUrls = 'show' | 'search'> =\n | SdkResourceQueryHandle<TUrl>\n | SdkResourceReference;\n\ntype SdkResourceQueryHandle<TUrl extends keyof ResourceUrls = 'show' | 'search'> =\n SdkResourceType &\n Pick<SdkResource, 'id' | 'name' | 'disabled_data_api_endpoints'> & {\n urls: Pick<ResourceUrls, TUrl>;\n };\n\ntype SdkAnalyticsResource<TKind extends keyof SdkResourceAnalytics> = Pick<\n SdkResource,\n 'id'\n> & {\n analytics: Pick<SdkResourceAnalytics, TKind>;\n};\n\ntype SdkCatalogRelationPath<TResourceId extends string> =\n TResourceId extends keyof ResourceCatalog\n ? SdkResourceRelationPath<ResourceCatalog[TResourceId]>\n : string;\n\ninterface ResourceCatalog {}\n\ntype SdkCatalogRecord<\n TResourceId extends string,\n TRelations extends readonly SdkCatalogRelationPath<TResourceId>[] = readonly []\n> = TResourceId extends keyof ResourceCatalog\n ? SdkResourceRecord<\n ResourceCatalog[TResourceId],\n Extract<TRelations, readonly SdkResourceRelationPath<ResourceCatalog[TResourceId]>[]>\n >\n : ResourceRecord;\n\ntype SdkReferenceRelationPath<TReference extends ResourceTypeReference> = [\n NonNullable<TReference>\n] extends [never]\n ? string\n : NonNullable<TReference> extends SdkResourceType\n ? SdkResourceRelationPath<NonNullable<TReference>>\n : TReference extends string\n ? SdkCatalogRelationPath<TReference>\n : string;\n\ntype ResourceTypeReference =\n | (SdkResourceType & Pick<SdkResource, 'id'>)\n | string\n | null\n | undefined;\n\ntype SdkReferenceRecord<\n TReference extends ResourceTypeReference,\n TRelations extends readonly SdkReferenceRelationPath<TReference>[] = readonly []\n> = [NonNullable<TReference>] extends [never]\n ? ResourceRecord\n : NonNullable<TReference> extends SdkResourceType\n ? TRelations extends readonly SdkResourceRelationPath<NonNullable<TReference>>[]\n ? SdkResourceRecord<NonNullable<TReference>, TRelations>\n : never\n : TReference extends string\n ? TRelations extends readonly SdkCatalogRelationPath<TReference>[]\n ? SdkCatalogRecord<TReference, TRelations>\n : never\n : ResourceRecord;\n\ntype SdkPollInterval<TData> = number | false | ((data: TData) => number | false);\n\ntype SdkUsePollOptions = {\n /**\n * Start polling on mount. Defaults to `true`. Pass `false` to wait for a manual\n * `start()`, for example until the visitor submits something the page then waits on.\n */\n autoStart?: boolean;\n /**\n * Keep polling at full rate while the browser tab is hidden. Defaults to `false`, which\n * slows a hidden tab to one tick in ten rather than stopping it.\n */\n keepAlive?: boolean;\n};\n\ntype SdkPollControls = {\n start: () => void;\n stop: () => void;\n /**\n * When the latest tick finished loading the page data, or `null` before the first\n * one. It changes on every successful tick, even when the data itself did not.\n */\n lastPolledAt: Date | null;\n};\n\ntype SdkUseRecordOptions<\n TRelations extends readonly string[] = readonly string[],\n TRecord extends ResourceRecord = ResourceRecord\n> = {\n relations?: TRelations;\n enabled?: boolean;\n /** Soft-delete subset to fetch from. Defaults to active records only; pass\n * `RecordScope.ActiveOrArchived`/`Archived` to fetch (or restore) archived records. */\n scope?: RecordScope;\n /**\n * Refetch this record on an interval. The function form receives the current record\n * (`undefined` before the first fetch) and returns `false` to stop.\n *\n * @example\n * ```tsx\n * Sdk.hooks.useRecord(orders, orderId, {\n * relations: ['tickets'],\n * pollInterval: (order) => (order?.tickets?.length ? false : 3000)\n * });\n * ```\n */\n pollInterval?: SdkPollInterval<TRecord | undefined>;\n};\n\ntype SdkRecordHandle<TRecord extends ResourceRecord = ResourceRecord> = {\n data: TRecord | undefined;\n isLoading: boolean;\n isError: boolean;\n /**\n * The failed request, or `null`. On public pages, a guest denied by resource\n * policy receives a forbidden error, while an absent record receives not-found;\n * branch with `Sdk.utils.error` instead of treating missing `data` as not-found.\n */\n error: SdkApiError | null;\n /**\n * Whole seconds until the next automatic `pollInterval` request, rounded up, for\n * rendering a refresh countdown. `0` means a refresh is in flight right now. `null`\n * means no refresh is scheduled: before the first request settles, without a\n * `pollInterval`, once a function-form `pollInterval` returns `false`, or while the\n * browser tab is hidden and the poll is paused.\n */\n refreshIn: number | null;\n invalidate: () => Promise<void>;\n};\n\ntype SdkApiError = {\n /** HTTP status code, or `0` when no response was received. */\n readonly status: number;\n /** Parsed response body, or `null` when no response body was received. */\n readonly body: unknown;\n /** Human-readable request failure message. */\n message: string;\n};\n\ntype SdkUseRecordsOptions<\n TRelations extends readonly string[] = readonly string[],\n TRecord extends ResourceRecord = ResourceRecord\n> = {\n /**\n * Relation paths to load alongside each record, e.g. `['family']`. Filtering on a\n * nested path (`family.id`) does not load the relation: it still comes back as its\n * `{ id }` reference, so list it here to read any other related field.\n */\n relations?: TRelations;\n /**\n * Structured `$filter` tree \u2014 the same contract as {@link ResourceSearchBody}'s\n * `filter`: `{ operator: 'and' | 'or' | 'nor', conditions: [...] }`, never a flat\n * map like `{ status: 'paid' }`. `null` means unfiltered, same as omitting it, so\n * saved view state (e.g. `useSharedFilters().filter`) can be passed straight through.\n */\n filter?: Record<string, any> | null;\n /** Sort input in any {@link ResourceSortInput} form; normalized to `$sort`. `null` = default order. */\n sort?: ResourceSortInput | null;\n /** Free-text search across the resource's searchable fields, sent as `$search`. `null` or `''` = no search. */\n search?: string | null;\n /** Page number to fetch. Defaults to 1. */\n page?: number;\n /** Records per page (maximum 500, defaults to 15). `null` = the default page size. */\n perPage?: number | null;\n /** Soft-delete subset to read from. Defaults to active records only. */\n scope?: RecordScope;\n /**\n * `'series'` returns one record for each repeating event of an Eventable resource, sent as\n * `$collapse`: its first date under `sort` (default: the Event Schedule start) among the\n * records that pass every filter. A one-time record is its own entry, and\n * `pagination.total` counts events. `null` = every record. List the dates of one series\n * with a filter on `__meta.capabilities.eventable.series.series_id`.\n *\n * @example\n * ```tsx\n * const upcoming = Sdk.hooks.useInfiniteRecords(events, {\n * collapse: 'series',\n * sort: 'schedule.start',\n * filter: { operator: 'and', conditions: [{ field: 'schedule.start', operator: '>=', value: now }] }\n * });\n *\n * const seriesId = event.__meta?.capabilities?.eventable?.series?.series_id;\n * const dates = Sdk.hooks.useRecords(events, {\n * enabled: seriesId !== undefined,\n * sort: 'schedule.start',\n * filter: {\n * operator: 'and',\n * conditions: [\n * { field: '__meta.capabilities.eventable.series.series_id', operator: '=', value: seriesId },\n * { field: 'schedule.start', operator: '>=', value: now }\n * ]\n * }\n * });\n * ```\n */\n collapse?: RecordCollapse | null;\n /** Set `false` to hold the request until the page has what the query needs. */\n enabled?: boolean;\n /**\n * Refetch this page of records on an interval. The function form receives the current\n * records array (empty before the first fetch) and returns `false` to stop.\n *\n * @example\n * ```tsx\n * Sdk.hooks.useRecords(tickets, {\n * filter: { operator: 'and', conditions: [{ field: 'order_id', operator: '=', value: orderId }] },\n * pollInterval: (records) => (records.length > 0 ? false : 3000)\n * });\n * ```\n */\n pollInterval?: SdkPollInterval<TRecord[]>;\n};\n\ntype SdkRecordsHandle<TRecord extends ResourceRecord = ResourceRecord> = {\n /** Authorized file metadata for the records currently retained by this hook. */\n files: FileMetadataMap;\n /** The records on the current page \u2014 an empty array until the first fetch resolves. */\n data: TRecord[];\n /** Pagination metadata for the current page, `undefined` until the first fetch resolves. */\n pagination: ResourcePagination | undefined;\n isLoading: boolean;\n isError: boolean;\n /** The failed request, or `null`; classify it with `Sdk.utils.error`. */\n error: SdkApiError | null;\n /**\n * Whole seconds until the next automatic `pollInterval` request, rounded up. `0`\n * means a refresh is in flight. `null` means no refresh is scheduled.\n */\n refreshIn: number | null;\n invalidate: () => Promise<void>;\n};\n\ntype SdkUseInfiniteRecordsOptions<TRelations extends readonly string[] = readonly string[]> =\n Omit<SdkUseRecordsOptions<TRelations>, 'page' | 'pollInterval'>;\n\ntype SdkInfiniteRecordsHandle<TRecord extends ResourceRecord = ResourceRecord> = {\n /** Authorized file metadata for the records currently retained by this hook. */\n files: FileMetadataMap;\n /** Up to the ten most recently loaded pages flattened into one record array. */\n data: TRecord[];\n /** Pagination metadata from the most recently loaded page. */\n pagination: ResourcePagination | undefined;\n isLoading: boolean;\n isError: boolean;\n isFetchingNextPage: boolean;\n hasNextPage: boolean;\n fetchNextPage: () => Promise<void>;\n invalidate: () => Promise<void>;\n};\n\ntype SdkRecordMutationState<TRecord extends ResourceRecord = ResourceRecord> = {\n /** The record returned by the most recent successful call. */\n data: TRecord | undefined;\n /** `true` while a request is in flight. */\n isPending: boolean;\n isError: boolean;\n /** The failed request's {@link SdkApiError} (with `status` and `body`), or `null`. */\n error: SdkApiError | null;\n /** Clears `data`, `error`, and `isError` back to their initial state. */\n reset: () => void;\n};\n\ntype SdkUseCreateRecordOptions<TRecord extends ResourceRecord = ResourceRecord> = {\n onSuccess?: (record: TRecord) => void;\n onError?: (error: SdkApiError) => void;\n /**\n * Make a retried booking create return the booking that the first attempt made, instead of a\n * second booking. Each create writes a key into the field that the resource's booking\n * capability names as its idempotency field. A retry with the same payload sends the same key;\n * a changed payload gets a new key. `create` throws when the resource has no idempotency field.\n * Defaults to `false`.\n */\n idempotent?: boolean;\n};\n\ntype SdkCreateRecordHandle<TRecord extends ResourceRecord = ResourceRecord> =\n SdkRecordMutationState<TRecord> & {\n /**\n * Create a record. Resolves with the created record, or `null` when the request\n * failed \u2014 it never rejects, so an un-awaited call from an event handler cannot\n * produce an unhandled rejection. Read `error` for the failure.\n */\n create: (payload: ResourceRecordPayload<TRecord>) => Promise<TRecord | null>;\n };\n\ntype SdkUseUpdateRecordOptions<TRecord extends ResourceRecord = ResourceRecord> = {\n onSuccess?: (record: TRecord) => void;\n onError?: (error: SdkApiError) => void;\n};\n\ntype SdkUpdateRecordHandle<TRecord extends ResourceRecord = ResourceRecord> =\n SdkRecordMutationState<TRecord> & {\n /**\n * Update a record. Resolves with the updated record, or `null` when the request\n * failed \u2014 it never rejects, so an un-awaited call from an event handler cannot\n * produce an unhandled rejection. Read `error` for the failure.\n */\n update: (recordId: string, payload: ResourceRecordPayload<TRecord>) => Promise<TRecord | null>;\n };\n\ntype SdkEmailRecipient = {\n email: string;\n data?: Record<string, any>;\n} & (\n | { resource_id?: never; record_id?: never }\n /** Supply both IDs to link this recipient's record to their email. */\n | { resource_id: string; record_id: string }\n);\n\ntype SdkSendEmailInput = {\n /** Recipients. With `bulk`, each entry receives its own email. */\n to: SdkEmailRecipient[];\n /** Liquid subject template. */\n subject: string;\n /**\n * Layout variables keyed by name. Without `email_layout_id`, `content` is the whole\n * email body (HTML string or TipTap JSON document).\n */\n layout_data: Record<string, any>;\n /** Published email layout that wraps the body. */\n email_layout_id?: string | null;\n /** Not allowed with `bulk`. */\n cc?: string[];\n /** Not allowed with `bulk`. */\n bcc?: string[];\n reply_to?: string[];\n /** Records the email is about; they are linked to the stored message. */\n records?: Array<{ id: string; resource_id: string }>;\n /** File ids the caller can read. */\n attachments?: string[];\n /** Eventable records to attach as calendar invites; at most 5. An archived or cancelled record sends a cancellation. */\n calendar_events?: Array<{ resource_id: string; record_id: string }>;\n /** Send every recipient its own email instead of one email addressed to all. */\n bulk?: boolean;\n};\n\ntype SdkSendEmailResponse = {\n success: boolean;\n status_message: string;\n recipients: {\n to: string[];\n cc: string[];\n bcc: string[];\n reply_to: string[];\n };\n subject: string | null;\n content: string | null;\n attachments: Array<{ id: string; name: string; mime: string }>;\n bulk: boolean;\n};\n\ntype SdkUseSendEmailOptions = {\n onSuccess?: (response: SdkSendEmailResponse) => void;\n onError?: (error: SdkApiError) => void;\n};\n\ntype SdkSendEmailHandle = {\n /**\n * Queue the email. Resolves with the response, or `null` when the request failed \u2014\n * it never rejects, so an un-awaited call from an event handler cannot produce an\n * unhandled rejection. Read `error` for the failure.\n */\n send: (input: SdkSendEmailInput) => Promise<SdkSendEmailResponse | null>;\n /** The response of the most recent successful call. */\n data: SdkSendEmailResponse | undefined;\n /** `true` while a request is in flight. */\n isPending: boolean;\n isError: boolean;\n /** The failed request's {@link SdkApiError} (with `status` and `body`), or `null`. */\n error: SdkApiError | null;\n /** Clears `data`, `error`, and `isError` back to their initial state. */\n reset: () => void;\n};\n\ntype SdkBulkNotificationRun = {\n id: string;\n status: 'queued' | 'processing' | 'completed' | 'failed' | 'interrupted';\n total_count: number;\n processed_count: number;\n queued_count: number;\n failed_count: number;\n uncertain_count: number;\n /** Source records skipped because an earlier notification already used their idempotency key. */\n duplicate_count: number;\n failures: Array<{ record_id: string; errors: Record<string, string[]> }>;\n failures_truncated: boolean;\n error: string | null;\n};\n\ntype SdkBulkNotificationVariable =\n | { literal: any; source_path?: never; recipient_path?: never }\n | { source_path: string; literal?: never; recipient_path?: never }\n | { recipient_path: string; literal?: never; source_path?: never };\n\ntype SdkSendBulkNotificationsInput = {\n resource_id: string;\n notification_type_id: string;\n notification_template_id?: string | null;\n /** Empty string selects the source record; otherwise use a singular relation path. */\n recipient_path: string;\n variables: Record<string, SdkBulkNotificationVariable>;\n attachments?: string[];\n /**\n * Must contain `{source_record_id}`, for example `subscription-period:{source_record_id}:initial-notice`.\n * Each source record gets its own key, so it matches a single send that uses the same key.\n */\n idempotency_key?: string;\n /** Maximum 50,000 source records. Explicit empty filters select all accessible active records. */\n audience:\n | { mode: 'records'; record_ids: string[]; filters?: never }\n | {\n mode: 'filters';\n filters: Record<string, any> | any[];\n record_ids?: never;\n };\n};\n\ntype SdkSendBulkNotificationsHandle = {\n /** Save the audience and start sending. Returns null on request failure. Never automatically retries. */\n send: (input: SdkSendBulkNotificationsInput) => Promise<SdkBulkNotificationRun | null>;\n data: SdkBulkNotificationRun | undefined;\n isPending: boolean;\n isError: boolean;\n error: SdkApiError | null;\n reset: () => void;\n};\n\ntype SdkNotificationRecipient = ResourceRecord & {\n id: string;\n resource_id: string;\n};\n\ntype SdkNotificationDeliveryChannel = 'email' | 'sms' | 'email_and_sms';\n\ntype SdkSendNotificationInput = {\n notification_type_id: string;\n /** Omit to use the type's compatible current default template. */\n notification_template_id?: string | null;\n variables: Record<string, any>;\n /**\n * At most 200 characters, not blank. A send whose key an earlier notification in the workspace\n * used sends nothing and resolves with `replayed: true`. The response carries that notification's\n * IDs only when the viewer can read it; otherwise they are null and empty. Not allowed with `bulk`.\n */\n idempotency_key?: string;\n} & (\n | (Omit<SdkSendEmailInput, 'subject' | 'layout_data' | 'email_layout_id'> & {\n delivery_channel?: 'email';\n recipient?: never;\n })\n | (Pick<SdkSendEmailInput, 'records' | 'attachments' | 'reply_to' | 'calendar_events'> & {\n delivery_channel?: 'email' | 'email_and_sms';\n recipient: SdkNotificationRecipient;\n to?: never;\n cc?: never;\n bcc?: never;\n bulk?: never;\n })\n | (Pick<SdkSendEmailInput, 'records'> & {\n delivery_channel: 'sms';\n recipient: SdkNotificationRecipient;\n to?: never;\n cc?: never;\n bcc?: never;\n bulk?: never;\n attachments?: never;\n reply_to?: never;\n calendar_events?: never;\n })\n);\n\ntype SdkSendNotificationResponse = SdkSendEmailResponse & {\n notification_history_id: string | null;\n /** Email attempt IDs available at response time. Queued workers can add attempts later. */\n email_message_ids: string[];\n sms_messages: { sms_message_id: string; status: string }[];\n /**\n * True when an earlier notification already used the idempotency key, so nothing was sent.\n * A replay names the earlier notification only when the viewer can read it.\n */\n replayed: boolean;\n};\n\ntype SdkNotificationConsentChannels = {\n email?: boolean;\n sms?: boolean;\n};\n\ntype SdkNotificationConsent = {\n resource_id: string;\n record_id: string;\n channels: { email: boolean; sms: boolean };\n /** When consent last changed, or `null` when the person has never been asked. */\n changed_at: string | null;\n};\n\ntype SdkUseNotificationConsentOptions = {\n /** The notifiable resource. Omit both IDs to manage the signed-in identity's own consent. */\n resourceId?: string | null;\n /** The record in that resource. */\n recordId?: string | null;\n /** Set `false` to skip the read, for example until a record is selected. */\n enabled?: boolean;\n onSuccess?: (consent: SdkNotificationConsent) => void;\n onError?: (error: SdkApiError) => void;\n};\n\ntype SdkNotificationConsentHandle = {\n /** The stored consent. A record that was never asked has email on and SMS off. */\n data: SdkNotificationConsent | undefined;\n isLoading: boolean;\n isError: boolean;\n /** The read failure, or `null`. */\n error: ApiError | null;\n /**\n * Save the given channels. An omitted channel keeps its stored value. Resolves with the\n * stored consent, or `null` when the request failed \u2014 it never rejects, so calling it\n * from an `onChange` is safe; read `updateError` for the failure.\n */\n update: (channels: SdkNotificationConsentChannels) => Promise<SdkNotificationConsent | null>;\n /** `true` while a save is in flight. */\n isPending: boolean;\n /** The save failure, or `null`. */\n updateError: ApiError | null;\n reset: () => void;\n};\n\ntype ApiError = Error & {\n readonly status: number;\n readonly body: any;\n};\n\ntype SdkCalendarUnavailableReason = 'signed_out' | 'flag_off' | 'not_schedulable';\n\ntype SdkCalendarLinkState = 'active' | 'waiting_for_write_access' | 'broken';\n\ntype SdkCalendarAccessRole = 'freeBusyReader' | 'reader' | 'writer' | 'owner';\n\ntype SdkGoogleCalendarLoginStatus =\n | 'connected'\n | 'reconnect_required'\n | 'disconnecting'\n | 'disconnected';\n\ntype SdkCalendarEventsChoice = 'keep' | 'remove';\n\ntype SdkCalendarStaff = {\n resource_id: string;\n record_id: string;\n name: string;\n /** The staff record is the signed-in identity. */\n is_viewer: boolean;\n};\n\ntype SdkCalendarLink = {\n id: string;\n calendar_id: string;\n calendar_name: string;\n /** The Google account that the link uses. Only its owner can reconnect it. */\n login_id: string;\n account: string | null;\n login_status: SdkGoogleCalendarLoginStatus | null;\n owner_name: string;\n viewer_owns_login: boolean;\n connected_by_name: string;\n receives_events: boolean;\n blocks_availability: boolean;\n state: SdkCalendarLinkState;\n access_role: SdkCalendarAccessRole | null;\n /** The explanation of the state for this viewer, the same text as the admin page. Null while active. */\n message: string | null;\n /** A reconnect queued an access check that has not run yet. */\n checking: boolean;\n /** When Diagonal last read the calendar's busy time. `null` when it blocks no availability or was not read yet. */\n busy_refreshed_at: string | null;\n /** The calendar blocks availability, but its busy time was not read for 6 hours. Bookings can overlap busy time. */\n busy_stale: boolean;\n};\n\ntype SdkCalendarLogin = {\n id: string;\n account: string;\n status: SdkGoogleCalendarLoginStatus;\n connected_at: string;\n};\n\ntype SdkCalendarChoice = {\n login_id: string;\n account: string;\n calendar_id: string;\n name: string;\n access_role: SdkCalendarAccessRole;\n /** The account's own calendar, which also holds the person's personal events. */\n primary: boolean;\n /** Its share lets Diagonal add events. */\n can_receive_events: boolean;\n /** It is already linked to the staff record. */\n linked: boolean;\n /** Diagonal created it for this workspace with `addLink({ newCalendar: true })`. Suggest it first. */\n created_by_diagonal: boolean;\n};\n\ntype SdkCalendarChoices = {\n calendars: SdkCalendarChoice[];\n /** One line for each Google account that could not list its calendars. */\n errors: string[];\n /**\n * The name of a new calendar, on the signed-in identity's own record. `null` on another\n * person's record, where `newCalendar` is refused.\n */\n new_calendar_name: string | null;\n};\n\ntype SdkCalendarResult = {\n status: 'success' | 'warning' | 'error';\n message: string;\n};\n\ntype SdkCalendarSignInResult = SdkCalendarResult & {\n /** The sign-in added a Google account. A reconnect renews one, so offer its calendars only after this. */\n new_login: boolean;\n};\n\ntype SdkCalendarLinkFlags = {\n /** Diagonal adds the person's events to this calendar. Needs a \"Make changes\" share. */\n receivesEvents: boolean;\n /** Busy time in this calendar blocks the person's availability. */\n blocksAvailability: boolean;\n};\n\ntype SdkAddCalendarLinkInput = SdkCalendarLinkFlags & {\n /** One of the viewer's own Google accounts. */\n loginId: string;\n} & (\n | { calendarId: string; email?: never; newCalendar?: never }\n | { email: string; calendarId?: never; newCalendar?: never }\n | { newCalendar: true; calendarId?: never; email?: never }\n );\n\ntype SdkUseCalendarConnectionsOptions = {\n /** A Schedulable staff resource. Omit both IDs for the signed-in identity's own record. */\n resourceId?: string | null;\n /** A record of that resource. Another person's record needs \"Manage calendar connections\" and Show access. */\n recordId?: string | null;\n};\n\ntype SdkCalendarConnectionsHandle = {\n /** False while loading, after a failure, and for each `reason`. Render nothing then. */\n available: boolean;\n reason: SdkCalendarUnavailableReason | null;\n /** False when this environment has no Google Calendar client. Connect and Add then fail. */\n configured: boolean;\n staff: SdkCalendarStaff | null;\n links: SdkCalendarLink[];\n /** The viewer's own Google accounts. A viewer adds calendars only through these. */\n logins: SdkCalendarLogin[];\n /** The viewer holds \"Manage calendar connections\". */\n canManageOthers: boolean;\n /** The outcome of the Google sign-in that just returned to this page. Show it once. */\n result: SdkCalendarSignInResult | null;\n isLoading: boolean;\n /** The read failure, or `null`. A 403 means the viewer may not manage this staff record. */\n error: ApiError | null;\n /**\n * Leave the page for Google sign-in. The browser returns to this page afterwards and\n * `result` carries the outcome. Rejects with the refusal, for example from an editor\n * preview, which can't open Google sign-in.\n */\n connect: () => Promise<void>;\n /** Renew one of the viewer's own Google accounts. Leaves the page like `connect`. */\n reconnect: (loginId: string) => Promise<void>;\n /**\n * End one of the viewer's own Google accounts. Every calendar that uses it stops working.\n * `events` defaults to `keep`.\n */\n disconnect: (\n loginId: string,\n options?: { events?: SdkCalendarEventsChoice }\n ) => Promise<SdkCalendarResult>;\n /** The calendars that the viewer's Google accounts can link, or one account's with `loginId`. */\n listChoices: (loginId?: string) => Promise<SdkCalendarChoices>;\n addLink: (input: SdkAddCalendarLinkInput) => Promise<SdkCalendarResult>;\n /** `events` applies when `receivesEvents` turns off, and defaults to `keep`. */\n updateLink: (\n linkId: string,\n flags: SdkCalendarLinkFlags,\n options?: { events?: SdkCalendarEventsChoice }\n ) => Promise<SdkCalendarResult>;\n /** Stop using a calendar. `events` defaults to `keep`. */\n removeLink: (\n linkId: string,\n options?: { events?: SdkCalendarEventsChoice }\n ) => Promise<SdkCalendarResult>;\n};\n\ntype SdkUseSendNotificationOptions = Omit<SdkUseSendEmailOptions, 'onSuccess'> & {\n onSuccess?: (response: SdkSendNotificationResponse) => void;\n};\n\ntype SdkSendNotificationHandle = Omit<SdkSendEmailHandle, 'send' | 'data'> & {\n send: (input: SdkSendNotificationInput) => Promise<SdkSendNotificationResponse | null>;\n data: SdkSendNotificationResponse | undefined;\n};\n\ntype SdkPreviewNotificationInput = {\n notification_type_id: string;\n /** Omit to use the type's current default template. */\n notification_template_id?: string | null;\n recipient?: SdkNotificationRecipient;\n variables: Record<string, any>;\n};\n\ntype SdkNotificationChannelUnavailableReason =\n | 'template_off'\n | 'no_permission'\n | 'no_address'\n | 'no_phone'\n | 'opted_out';\n\ntype SdkNotificationChannelAvailability = {\n available: boolean;\n /** Null when the channel is available. */\n reason: SdkNotificationChannelUnavailableReason | null;\n /**\n * The mapped email address or E.164 phone number the channel sends to. Null when the\n * recipient has none, or when the type takes email addresses at send time.\n */\n destination: string | null;\n};\n\ntype SdkNotificationChannel = 'email' | 'sms';\n\ntype SdkNotificationChannels = {\n /** `reason` is `template_off`, `no_permission`, or `no_address`. */\n email: SdkNotificationChannelAvailability;\n /** `reason` is `template_off`, `no_permission`, `no_phone`, or `opted_out`. */\n sms: SdkNotificationChannelAvailability;\n};\n\ntype SdkPreviewNotificationResponse = {\n /** The template that was resolved, including a default chosen by the server. */\n notification_template_id: string;\n /** Null when the template has no email. */\n subject: string | null;\n email_layout_id: string | null;\n /** Null when the template has no email. */\n layout_data: Record<string, any> | null;\n /** The rendered SMS text, ready to edit. Null when the template has no SMS. */\n sms_body: string | null;\n channels: SdkNotificationChannels;\n resolved_variables: Record<string, any>;\n};\n\ntype SdkResolvedNotificationEmail = {\n subject: string;\n email_layout_id: string | null;\n layout_data: Record<string, any>;\n};\n\ntype SdkResolvedNotification = {\n notification_template_id: string;\n resolved_variables: Record<string, any>;\n} & (\n | (SdkResolvedNotificationEmail & {\n delivery_channel: 'email';\n sms_body?: never;\n })\n | (SdkResolvedNotificationEmail & {\n delivery_channel: 'email_and_sms';\n sms_body: string;\n })\n | {\n delivery_channel: 'sms';\n sms_body: string;\n subject?: never;\n email_layout_id?: never;\n layout_data?: never;\n }\n);\n\ntype SdkSendResolvedNotificationInput = Omit<\n SdkPreviewNotificationInput,\n 'variables' | 'notification_template_id' | 'recipient'\n> &\n Pick<SdkResolvedNotification, 'notification_template_id'> & {\n resolved_variables?: Record<string, any>;\n /** The same key that `SdkSendNotificationInput` accepts. */\n idempotency_key?: string;\n } & (\n | (SdkResolvedNotificationEmail &\n Pick<SdkSendEmailInput, 'records' | 'attachments' | 'reply_to' | 'calendar_events'> & {\n delivery_channel?: 'email';\n sms_body?: never;\n } & (\n | { recipient: SdkNotificationRecipient; to?: never }\n | {\n recipient?: never;\n to: Array<SdkEmailRecipient & { data?: never }>;\n }\n ))\n | (SdkResolvedNotificationEmail &\n Pick<SdkSendEmailInput, 'records' | 'attachments' | 'reply_to' | 'calendar_events'> & {\n delivery_channel: 'email_and_sms';\n sms_body: string;\n recipient: SdkNotificationRecipient;\n to?: never;\n })\n | (Pick<SdkSendEmailInput, 'records'> & {\n delivery_channel: 'sms';\n sms_body: string;\n recipient: SdkNotificationRecipient;\n subject?: never;\n email_layout_id?: never;\n layout_data?: never;\n to?: never;\n attachments?: never;\n reply_to?: never;\n calendar_events?: never;\n })\n );\n\ntype SdkSendResolvedNotificationHandle = Omit<SdkSendEmailHandle, 'send' | 'data'> & {\n send: (input: SdkSendResolvedNotificationInput) => Promise<SdkSendNotificationResponse | null>;\n /** The response of the most recent successful call. */\n data: SdkSendNotificationResponse | undefined;\n};\n\ntype SdkUpcomingNotification = {\n id: string;\n revision: number;\n idempotency_key: string | null;\n computed_at: string | null;\n scheduled_at_override: string | null;\n changed_by_resource_id: string | null;\n changed_by_record_id: string | null;\n notification_type_name: string;\n scheduled_at: string | null;\n status: 'draft' | 'scheduled' | 'failed';\n is_one_off: boolean;\n is_paused: boolean;\n /** Held occurrences do not send until they are resumed. */\n is_held: boolean;\n can_cancel: boolean;\n can_edit: boolean;\n can_reschedule: boolean;\n can_save: boolean;\n can_schedule: boolean;\n can_send_now: boolean;\n can_hold: boolean;\n can_resume: boolean;\n};\n\ntype SdkScheduledNotificationContent = {\n email_layout_id: string | null;\n email_subject: string;\n email_layout_data: Record<string, any>;\n};\n\ntype SdkScheduledNotificationDetail = Omit<SdkUpcomingNotification, 'status'> & {\n status: 'draft' | 'scheduled' | 'failed' | 'queued' | 'cancelled' | 'expired';\n notification_type_id: string;\n notification_template_id: string;\n recipient: SdkNotificationRecipient;\n /** Saved override, or the current shared template when no override exists. */\n content: SdkScheduledNotificationContent;\n has_content_override: boolean;\n};\n\ntype SdkScheduledNotificationEdits = {\n expected_revision: number;\n recipient?: SdkNotificationRecipient;\n /** Omit to retain content. Null restores the shared template. Use raw content from ScheduledNotificationComposer, not resolved NotificationComposer output. */\n content_override?: SdkScheduledNotificationContent | null;\n};\n\ntype SdkUpdateScheduledNotificationInput = SdkScheduledNotificationEdits & {\n /** Required with scheduled_at to release a saved draft. */\n transition?: 'schedule';\n /** Explicit send-time override. Future ISO datetime with Z or an explicit offset. */\n scheduled_at?: string;\n};\n\ntype SdkUpdateScheduledNotificationHandle = {\n /** Rejects with ApiError on permission, validation, or dispatch conflict. Never retries automatically. */\n update: (\n id: string,\n input: SdkUpdateScheduledNotificationInput\n ) => Promise<SdkScheduledNotificationResult>;\n isPending: boolean;\n error: ApiError | null;\n reset: () => void;\n};\n\ntype SdkScheduledNotificationResult = {\n id: string;\n revision: number;\n status: 'draft' | 'scheduled' | 'failed' | 'cancelled' | 'queued' | 'expired';\n};\n\ntype SdkSendScheduledNotificationNowHandle = {\n /** Applies edits and queues this occurrence once. Queued does not mean delivered. Never retries automatically. */\n sendNow: (\n id: string,\n input: SdkScheduledNotificationEdits\n ) => Promise<{ id: string; status: 'queued' }>;\n isPending: boolean;\n error: ApiError | null;\n reset: () => void;\n};\n\ntype SdkNotificationHistoryStatus = 'upcoming' | 'sent' | 'failed' | 'cancelled' | 'expired';\n\ntype SdkNotificationHistoryKind = 'scheduled' | 'sent';\n\ntype SdkNotificationSourceKind = 'api' | 'one_off' | 'schedule' | 'workflow';\n\ntype SdkEmailAttemptStatus =\n | 'bounced'\n | 'deferred'\n | 'delivered'\n | 'dropped'\n | 'failed'\n | 'sending'\n | 'sent'\n | 'spam_reported'\n | 'suppressed';\n\ntype SdkEmailRecipientRole = 'to' | 'cc' | 'bcc';\n\ntype SdkEmailMessagesRecord = {\n /** Resource the record belongs to. */\n resourceId: string;\n /** Record whose linked emails are included. */\n recordId: string;\n};\n\ntype SdkEmailMessagesFilters = {\n /** Only emails linked to a record of this resource. */\n resourceId?: string | null;\n /** Case-insensitive subject substring. */\n subject?: string | null;\n /** Exact recipient addresses; an email matches when any of them received it. */\n emails?: string[];\n /** Recipient roles the `emails` filter checks. Defaults to To, CC, and BCC. */\n recipientRoles?: SdkEmailRecipientRole[];\n /** Exact sender address. */\n fromAddress?: string | null;\n /** Delivery status. */\n status?: SdkEmailAttemptStatus | null;\n /** Inclusive first recorded day, `YYYY-MM-DD` in `timezone`. */\n dateFrom?: string | null;\n /** Exclusive end day, `YYYY-MM-DD` in `timezone`. */\n dateBefore?: string | null;\n /** IANA time zone the date range is read in. Defaults to the viewer's browser time zone. */\n timezone?: string;\n /**\n * Also match unlinked emails sent to the selected records' current email address.\n * Requires the `manage_notifications` permission; the option is dropped silently for\n * other viewers so their linked history still loads.\n */\n includeCurrentAddresses?: boolean;\n /** Email field to read the current address from. Defaults to the Notifiable mapping. */\n emailFieldId?: string | null;\n /** Extra addresses to match alongside the resolved current addresses. */\n additionalAddresses?: string[];\n /** Resolved current addresses to leave out. */\n excludedAddresses?: string[];\n};\n\ntype SdkEmailMessagesOptions = {\n /** Pair with recordId for one record's history. */\n resourceId?: string;\n recordId?: string;\n /** Up to 1,000 records. Replaces the single record pair. Each requires Show access. */\n records?: SdkEmailMessagesRecord[];\n /** Explicit workspace scope. Requires manage_notifications. Do not combine with records. */\n workspace?: boolean;\n filters?: SdkEmailMessagesFilters;\n /** Defaults to newest first. Equal sort values are ordered by message ID. */\n sort?: 'created_at' | '-created_at' | 'subject' | '-subject' | 'status' | '-status';\n /** One-based page number. Defaults to 1. */\n page?: number;\n /** Rows per page, 1 to 100. Defaults to 50. */\n perPage?: number;\n /** False pauses the query. Defaults to true. Empty records also leave it idle. */\n enabled?: boolean;\n /** Single-record searches default to 30 seconds. Multi-record and workspace searches do not poll by default. Minimum 1,000 ms. */\n pollInterval?: number | false;\n};\n\ntype SdkEmailMessage = SdkEmailAttempt['message'] & {\n /** Workflow links are present only when the viewer can open them. */\n context: {\n workflow_title: string | null;\n workflow_url: string | null;\n execution_url: string | null;\n } | null;\n};\n\ntype SdkEmailAttempt = {\n message: {\n id: string;\n from_address: string;\n from_name: string | null;\n to_summary: string;\n subject: string;\n status: SdkEmailAttemptStatus;\n has_html_body: boolean;\n created_at: string;\n /** The files the email carried, without its inline images. */\n attachments: SdkEmailAttemptAttachment[];\n };\n /** Provider delivery events, oldest first. */\n events: SdkEmailAttemptEvent[];\n /**\n * Every To, CC, and BCC address. Null without the `manage_notifications` permission;\n * `message.to_summary` still names the first To address.\n */\n recipients: SdkEmailAttemptRecipients | null;\n};\n\ntype SdkEmailAttemptAttachment = {\n name: string;\n mime: string | null;\n /** Size in bytes. */\n size: number | null;\n /** The stored File id. Null when the email did not send a stored file. */\n id: string | null;\n /** A link that opens the file for a viewer who can read the email. Null without a stored file. */\n url: string | null;\n};\n\ntype SdkEmailAttemptEvent = {\n id: string;\n event_type:\n | 'bounce'\n | 'click'\n | 'deferred'\n | 'delivered'\n | 'dropped'\n | 'group_resubscribe'\n | 'group_unsubscribe'\n | 'open'\n | 'processed'\n | 'spamreport'\n | 'unsubscribe';\n recipient_email: string;\n reason: string | null;\n timestamp: string;\n};\n\ntype SdkEmailAttemptRecipients = {\n to: SdkEmailAddress[];\n cc: SdkEmailAddress[];\n bcc: SdkEmailAddress[];\n};\n\ntype SdkEmailAddress = { address: string; name: string | null };\n\ntype SdkEmailMessages = {\n data: SdkEmailMessage[];\n pagination: ResourcePagination;\n};\n\ntype SdkNotificationHistoryItem = {\n id: string;\n kind: SdkNotificationHistoryKind;\n status: SdkNotificationHistoryStatus;\n /** Saved name for sent rows; the current name, or a deleted placeholder, otherwise. */\n notification_type_name: string;\n notification_template_name: string;\n /** Send time for scheduled rows; preparation time for sent rows. ISO 8601, or null for unscheduled drafts. */\n occurred_at: string | null;\n source_kind: SdkNotificationSourceKind;\n /** Schedule or workflow name. Null without the `manage_notifications` permission. */\n source_name: string | null;\n is_paused: boolean;\n is_held: boolean;\n /** Email attempts the notification produced. Always 0 for scheduled rows. */\n email_count: number;\n /** The attempt furthest along the delivery lifecycle. Null without an attempt. */\n email_status: SdkEmailAttemptStatus | null;\n /** Attempts that have `email_status`. Show this count with the status, not `email_count`. */\n email_status_count: number;\n sms_status_counts?: Record<string, number>;\n};\n\ntype SdkNotificationHistory = {\n data: SdkNotificationHistoryItem[];\n pagination: ResourcePagination;\n};\n\ntype SdkNotificationHistoryOptions = {\n /** Lifecycle status to list. Defaults to `upcoming`. */\n status?: SdkNotificationHistoryStatus;\n /** Notifications linked to any of these records. The viewer needs Show access to each. */\n records?: { resourceId: string; recordId: string }[];\n /**\n * List the whole workspace. Requires `manage_notifications`. Ignored when `records`\n * is set. Without `records` or `workspace`, the hook waits.\n */\n workspace?: boolean;\n /** Only notifications linked to a record of this resource. */\n resourceId?: string | null;\n /** Case-insensitive match on type, template, ID, or email subject. */\n search?: string | null;\n /** Exact type name. Sent rows match the saved name. */\n typeName?: string | null;\n /** Exact template name. Sent rows match the saved name. */\n templateName?: string | null;\n /** `none` for one-offs, `workflow`, `api`, or a schedule ID. */\n source?: string | null;\n /** Sent only. An attempt status, or `no_attempt` for notifications without an email. */\n emailStatus?: SdkEmailAttemptStatus | 'no_attempt' | null;\n /** Inclusive first day, `YYYY-MM-DD` in `timezone`. */\n dateFrom?: string | null;\n /** Exclusive end day, `YYYY-MM-DD` in `timezone`. */\n dateBefore?: string | null;\n /** IANA time zone for the date range. Defaults to the workspace time zone. */\n timezone?: string;\n /** `occurred_at` (oldest first) or `-occurred_at` (newest first). */\n sort?: 'occurred_at' | '-occurred_at';\n page?: number;\n /** 1 to 100. Defaults to 25. */\n perPage?: number;\n enabled?: boolean;\n};\n\ntype SdkNotificationHistoryRecord = {\n id: string;\n resource_id: string;\n label: string;\n resource_name: string;\n is_archived: boolean;\n};\n\ntype SdkNotificationHistoryDetail = Omit<\n SdkNotificationHistoryItem,\n 'email_count' | 'email_status' | 'email_status_count' | 'sms_status_counts'\n> & {\n /** Preparation error. Null without the `manage_notifications` permission. */\n error: string | null;\n /** Saved variable values. Empty without the `manage_notifications` permission. */\n variables: Record<string, any>;\n /** Workflow context of a sent notification. Links appear only for viewers who can open them. */\n context: {\n workflow_title: string | null;\n workflow_url: string | null;\n execution_url: string | null;\n } | null;\n /** Linked records the viewer can read. Other links are left out. */\n records: SdkNotificationHistoryRecord[];\n /** For a queued scheduled row, the saved notification to open with kind `sent`. */\n sent_notification_id: string | null;\n /** Readable email attempts in send order. Empty for scheduled rows and prepared notifications without an email. */\n attempts: SdkEmailAttempt[];\n /** Readable SMS messages in send order. A record-scoped viewer sees the first destination only. */\n sms_messages?: SdkSmsMessage[];\n};\n\ntype SdkSmsMessage = {\n id: string;\n from_number: string;\n to_number: string;\n /** The text as sent. */\n body: string;\n status: SdkSmsMessageStatus;\n /** Why a failed or suppressed message did not reach the carrier. */\n status_reason: string | null;\n /** `gsm7` or `ucs2`. */\n encoding: string;\n segments: number;\n sent_at: string | null;\n finalized_at: string | null;\n};\n\ntype SdkSmsMessageStatus =\n | 'queued'\n | 'sending'\n | 'sent'\n | 'delivered'\n | 'failed'\n | 'suppressed';\n\ntype SdkUpcomingNotifications = {\n data: SdkUpcomingNotification[];\n pagination: ResourcePagination;\n};\n\ntype SdkUpcomingNotificationsOptions = {\n resourceId: string | null | undefined;\n recordId: string | null | undefined;\n page?: number;\n perPage?: number;\n enabled?: boolean;\n};\n\ntype SdkScheduleNotificationInput =\n | ({\n notification_type_id: string;\n notification_template_id: string;\n recipient: SdkNotificationRecipient;\n variables: Record<string, any>;\n content_override?: SdkScheduledNotificationContent | null;\n idempotency_key?: string;\n } & (\n | { status: 'draft'; idempotency_key: string; scheduled_at?: never }\n | { status?: 'scheduled'; scheduled_at: string }\n ))\n | {\n notification_schedule_id: string;\n source_record_id: string;\n stage_id: string;\n status: 'draft';\n content_override?: SdkScheduledNotificationContent | null;\n };\n\ntype SdkCancelledNotificationResult = {\n id: string;\n revision: number;\n status: 'cancelled';\n};\n\ntype SdkScheduleNotificationHandle = {\n /** Rejects with ApiError on failure. */\n schedule: (input: SdkScheduleNotificationInput) => Promise<SdkScheduledNotificationResult>;\n isPending: boolean;\n error: ApiError | null;\n reset: () => void;\n};\n\ntype SdkCancelScheduledNotificationHandle = {\n /** Rejects with ApiError on failure, including 409 once delivery has started. */\n cancel: (id: string, expectedRevision: number) => Promise<SdkCancelledNotificationResult>;\n isPending: boolean;\n error: ApiError | null;\n reset: () => void;\n};\n\ntype SdkHeldNotificationResult = {\n id: string;\n revision: number;\n status: 'scheduled' | 'failed';\n is_held: boolean;\n scheduled_at: string;\n};\n\ntype SdkHoldScheduledNotificationHandle = {\n /** Rejects with ApiError on failure, including 409 once delivery has started. */\n hold: (id: string, expectedRevision: number) => Promise<SdkHeldNotificationResult>;\n isPending: boolean;\n error: ApiError | null;\n reset: () => void;\n};\n\ntype SdkResumeScheduledNotificationHandle = {\n /**\n * Pass a future ISO `scheduledAt` with Z or an offset to send at a new time. It is required\n * when the saved time passed during the hold. Rejects with ApiError on failure.\n */\n resume: (\n id: string,\n expectedRevision: number,\n scheduledAt?: string\n ) => Promise<SdkHeldNotificationResult>;\n isPending: boolean;\n error: ApiError | null;\n reset: () => void;\n};\n\ntype SdkNotificationListOptions = {\n page?: number;\n /** Between 1 and 100. Defaults to 50. */\n perPage?: number;\n search?: string;\n /** Only templates that support every requested channel, and only types with such a template. */\n deliveryChannel?: SdkNotificationDeliveryChannel;\n enabled?: boolean;\n};\n\ntype SdkNotificationTypeListOptions = SdkNotificationListOptions & {\n /** notification selects record recipients; email selects address recipients. */\n recipientMode?: 'notification' | 'email';\n};\n\ntype SdkNotificationVariableSummary = {\n key: string;\n /** Falls back to the key in title case. */\n label: string;\n /** The variable type key, such as `text`, `date`, `select`, or `record`. */\n type: string;\n description: string | null;\n required: boolean;\n};\n\ntype SdkNotificationTypeSummary = {\n id: string;\n name: string;\n /** Every variable key, including the derived `record` of a type with a recipient resource. */\n variableKeys: string[];\n /** The values a sender supplies, in order. Never includes the derived `record`. */\n variables: SdkNotificationVariableSummary[];\n notifiable_resource_id: string | null;\n};\n\ntype SdkNotificationTemplateSummary = {\n id: string;\n name: string;\n notification_type_id: string;\n notification_type_name: string;\n email_layout_name: string | null;\n is_default: boolean;\n is_email_enabled: boolean;\n is_sms_enabled: boolean;\n};\n\ntype SdkNotificationTypeList = {\n data: SdkNotificationTypeSummary[];\n pagination: ResourcePagination;\n};\n\ntype SdkNotificationTemplateList = {\n data: SdkNotificationTemplateSummary[];\n pagination: ResourcePagination;\n};\n\ntype SdkNavigationMenu = {\n id: string;\n name: string;\n items: any[];\n};\n\ntype SdkNavigationMenuCatalog = Record<string, SdkNavigationMenu>;\n\ntype SdkResourceFormWorkflowInputs<\n TResource extends Pick<SdkResource, 'workflow_inputs'>,\n TEvents extends SdkWorkflowEvent\n> = [Extract<keyof TResource['workflow_inputs'], TEvents>] extends [never]\n ? Record<never, never>\n : UnionToIntersection<\n NonNullable<\n TResource['workflow_inputs'][Extract<keyof TResource['workflow_inputs'], TEvents>]\n >\n >;\n\ntype SdkIdentity =\n | ({\n id: string;\n workspace_id: string;\n resource_id: string;\n created_at: string;\n updated_at: string;\n $display_name: string;\n $permission_ids: string[];\n $permission_slugs: string[];\n } & PageIdentityRecord)\n | null;\n\ntype PageIdentityRecord = keyof PageIdentityCatalog extends never\n ? Record<string, any>\n : PageIdentityCatalog;\n\ninterface PageIdentityCatalog {}\n\ntype SdkPermissions = {\n /** Return whether the current identity has a permission id or slug. */\n has(permissionIdOrSlug: string): boolean;\n /** Return whether the current identity has at least one of the supplied permission ids or slugs. */\n hasAny(permissionIdsOrSlugs: readonly string[]): boolean;\n /** Return whether the current identity has the system-administrator permission. */\n isSystemAdmin(): boolean;\n};\n\ntype SdkWorkspaceField = {\n id: string | null;\n key: string;\n type: string;\n label: string | null;\n /** Rendered as help text under inputs. */\n description: string | null;\n guard: string[] | Record<string, string[]> | null;\n settings: Record<string, any>;\n /** Derived on the server from `required` guard rules. */\n is_required: boolean;\n /** Read-only system fields and package-locked values. `WorkspaceFieldsForm` never renders these. */\n is_read_only: boolean;\n /** Hidden from the admin field list because it has its own management surface. */\n is_hidden: boolean;\n};\n\ntype SdkFormField = SdkResourceField | SdkWorkspaceField;\n\ntype SdkWorkspaceValues = Record<string, any>;\n\ntype SdkWorkspace = {\n id: string;\n name: string;\n slug: string;\n primary_domain_id: string | null;\n /** The workspace's default navigation menu, resolvable via `Sdk.navigationMenu()`. */\n default_navigation_menu_id: string | null;\n /** Workspace field definitions keyed by field key. Values live in `Sdk.hooks.useWorkspaceValues`. */\n fields: WorkspaceFieldDefinitionCatalog;\n /** @deprecated Load-time snapshot. Read live values with `Sdk.hooks.useWorkspaceValues`. */\n values: WorkspaceFieldCatalog | null;\n} | null;\n\ntype SdkWorkspaceRecord = Omit<NonNullable<SdkWorkspace>, 'fields'>;\n\ntype SdkApi = {\n /** Parsed body of a 2xx GET; throws {@link SdkApiError} on non-2xx. */\n get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;\n /** Parsed body of a 2xx POST; throws {@link SdkApiError} on non-2xx. */\n post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;\n /** Parsed body of a 2xx PUT; throws {@link SdkApiError} on non-2xx. */\n put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;\n /** Parsed body of a 2xx PATCH; throws {@link SdkApiError} on non-2xx. */\n patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;\n /** Parsed body of a 2xx DELETE; throws {@link SdkApiError} on non-2xx. */\n delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;\n};\n\ntype AxiosRequestConfig = any;\n\ntype SdkApiQueryKey = readonly [string, ...any[]];\n\ntype SdkApiFetcher<TData> = (api: SdkApi) => Promise<TData>;\n\ntype SdkSubscriptionPreview = {\nreadonly currency: string,\nreadonly billing_cycle_mode: ('anniversary' | 'calendar_aligned'),\nreadonly interval: ('monthly' | 'quarterly' | 'semi_annual' | 'yearly'),\nreadonly timezone: string,\nreadonly initial: ({\nreadonly lines: ({\nreadonly price_record_id: string,\nreadonly description: string,\nreadonly quantity: number,\nreadonly is_one_time: boolean,\nreadonly full_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly line_total: {\namount: number,\ncurrency: string,\n},\n})[],\nreadonly total: {\namount: number,\ncurrency: string,\n},\nreadonly covered_start: string,\nreadonly covered_end: string,\nreadonly reference_start: string,\nreadonly reference_end: string,\nreadonly days_charged: number,\nreadonly days_in_period: number,\nreadonly is_prorated: boolean,\nreadonly subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly discount_total: {\namount: number,\ncurrency: string,\n},\nreadonly discounts: ({\nreadonly description: string,\nreadonly duration: ('every_invoice' | 'first_invoice' | 'specific_period'),\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly period_start: string | null,\n})[],\n}),\nreadonly additional_invoices: ({\nreadonly lines: ({\nreadonly price_record_id: string,\nreadonly description: string,\nreadonly quantity: number,\nreadonly is_one_time: boolean,\nreadonly full_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly line_total: {\namount: number,\ncurrency: string,\n},\n})[],\nreadonly total: {\namount: number,\ncurrency: string,\n},\nreadonly covered_start: string,\nreadonly covered_end: string,\nreadonly reference_start: string,\nreadonly reference_end: string,\nreadonly days_charged: number,\nreadonly days_in_period: number,\nreadonly is_prorated: boolean,\nreadonly subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly discount_total: {\namount: number,\ncurrency: string,\n},\nreadonly discounts: ({\nreadonly description: string,\nreadonly duration: ('every_invoice' | 'first_invoice' | 'specific_period'),\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly period_start: string | null,\n})[],\n})[],\nreadonly renewal: ({\nreadonly next_renewal_date: string,\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly discount_total: {\namount: number,\ncurrency: string,\n},\n}),\nreadonly trial_ends_at: string | null,\nreadonly quote: string,\nreadonly discounts: ({\nreadonly calculation_type: ('fixed_amount' | 'percentage'),\nreadonly duration: ('every_invoice' | 'first_invoice' | 'specific_period'),\nreadonly percent: number | null,\nreadonly fixed_amount: {\namount: number,\ncurrency: string,\n} | null,\nreadonly description: string,\nreadonly period_start: string | null,\n})[],\nreadonly first_paid_invoice: ({\nreadonly lines: ({\nreadonly price_record_id: string,\nreadonly description: string,\nreadonly quantity: number,\nreadonly is_one_time: boolean,\nreadonly full_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly line_total: {\namount: number,\ncurrency: string,\n},\n})[],\nreadonly total: {\namount: number,\ncurrency: string,\n},\nreadonly covered_start: string,\nreadonly covered_end: string,\nreadonly reference_start: string,\nreadonly reference_end: string,\nreadonly days_charged: number,\nreadonly days_in_period: number,\nreadonly is_prorated: boolean,\nreadonly subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly discount_total: {\namount: number,\ncurrency: string,\n},\nreadonly discounts: ({\nreadonly description: string,\nreadonly duration: ('every_invoice' | 'first_invoice' | 'specific_period'),\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly period_start: string | null,\n})[],\n}) | null,\n};\n\ntype SdkSubscriptionPreviewOptions = {\n /** Subscription resource ID. No payer or business record is needed. */\n resourceId: string;\n /** Eligible price IDs and integer quantities of at least one. */\n items: { price_record_id: string; quantity: number }[];\n /**\n * Up to ten discounts. Requires Store access to the discount resource. A specific-period\n * discount applies to the invoice whose billing period starts on `period_start`.\n */\n discounts?: {\n calculation_type: 'percentage' | 'fixed_amount';\n percent?: number;\n fixed_amount?: { amount: number; currency: string };\n duration: 'first_invoice' | 'every_invoice' | 'specific_period';\n period_start?: string;\n description?: string;\n }[];\n /** Proposed service start, in the subscription's configured timezone. */\n startedAt?: string;\n /** Permitted calendar anchor month. Monthly billing validates only the day. */\n billingAnchorMonth?: number;\n /** Permitted calendar anchor day. */\n billingAnchorDay?: number;\n /** Calendar mode uses billingAnchorMonth and billingAnchorDay. Omitted uses the Subscription capability default. */\n billingCycleMode?: 'anniversary' | 'calendar_aligned';\n /** Activation policy override. Omitted uses the Subscription capability default. */\n activationPolicy?: 'immediate' | 'after_first_payment';\n /** With first_payment_at, dates assume payment on the preview day and move to the payment date. */\n initialTermAnchor?: 'subscription_created_at' | 'first_payment_at';\n /** Days allowed for the first payment. Omitted uses the Subscription capability default. */\n initialPaymentWindowDays?: number;\n /** Trial duration in days. Zero disables a default trial. Use either this or trialEndsAt. */\n trialDays?: number;\n /** ISO trial-end date in the billing timezone. Use either this or trialDays. */\n trialEndsAt?: string;\n /** Saved payment method requirement for a trial. Omitted uses the Subscription capability default. */\n trialPaymentMethodPolicy?: 'optional' | 'required';\n /** Days past due before lapse. Omitted uses the Subscription capability default. */\n gracePeriodDays?: number;\n /** Billing behavior after lapse. Omitted uses the Subscription capability default. */\n lapseBillingBehavior?: 'continue_billing' | 'suspend_renewals' | 'cancel';\n /** Hold requests until the selection is ready. Defaults to true. */\n enabled?: boolean;\n};\n\ntype SdkSubscriptionPreviewHandle = {\n /** Hidden during loading, after failure, and while disabled. */\n preview: SdkSubscriptionPreview | undefined;\n /** Current quote, or null while no terms can be reviewed. */\n quote: string | null;\n isLoading: boolean;\n error: ApiError | null;\n /** Refresh terms after a failed creation or before renewed confirmation. */\n refetch: () => Promise<void>;\n};\n\ntype SdkSubscriptionChangePreview = {\nreadonly mode: ('current_revision' | 'initial_revision' | 'prorated' | 'selection_only' | 'upcoming_revision'),\nreadonly changed: boolean,\nreadonly currency: string,\nreadonly timezone: string,\nreadonly effective_date: string,\nreadonly period_start: string | null,\nreadonly period_end: string | null,\nreadonly days_in_period: number | null,\nreadonly days_remaining: number | null,\nreadonly charges: ({\nreadonly price_record_id: string,\nreadonly description: string,\nreadonly quantity: number,\nreadonly is_one_time: boolean,\nreadonly full_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly line_total: {\namount: number,\ncurrency: string,\n},\n})[],\nreadonly charge_total: {\namount: number,\ncurrency: string,\n},\nreadonly discounts: ({\nreadonly description: string,\nreadonly duration: ('every_invoice' | 'first_invoice' | 'specific_period'),\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly period_start: string | null,\n})[],\nreadonly discount_total: {\namount: number,\ncurrency: string,\n},\nreadonly credits: ({\nreadonly description: string,\nreadonly quantity: number,\nreadonly full_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly gross_amount: {\namount: number,\ncurrency: string,\n},\nreadonly source_invoice_record_id: string,\nreadonly source_line_record_id: string,\nreadonly covered_start: string,\nreadonly covered_end: string,\n})[],\nreadonly credit_total: {\namount: number,\ncurrency: string,\n},\nreadonly amount_due: {\namount: number,\ncurrency: string,\n},\nreadonly net_credit: {\namount: number,\ncurrency: string,\n},\nreadonly applied_to_invoices: {\ninvoice_record_id: string,\namount: {\namount: number,\ncurrency: string,\n},\n}[],\nreadonly applied_to_invoices_total: {\namount: number,\ncurrency: string,\n},\nreadonly credit_to_balance: {\namount: number,\ncurrency: string,\n},\nreadonly outstanding_before: {\namount: number,\ncurrency: string,\n},\nreadonly outstanding_after: {\namount: number,\ncurrency: string,\n},\nreadonly renewal: ({\nreadonly next_renewal_date: string,\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly discount_total: {\namount: number,\ncurrency: string,\n},\n}) | null,\nreadonly replaces_upcoming_items: boolean,\nreadonly quote: string,\nreadonly effective: string,\nreadonly proration_behavior: string,\nreadonly revision: ({\nreadonly scope: string,\nreadonly period: string,\nreadonly invoice_record_id: string,\nreadonly period_start: string,\nreadonly period_end: string,\nreadonly before_lines: ({\nreadonly price_record_id: string,\nreadonly description: string,\nreadonly quantity: number,\nreadonly is_one_time: boolean,\nreadonly full_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly line_total: {\namount: number,\ncurrency: string,\n},\n})[],\nreadonly after_lines: ({\nreadonly price_record_id: string,\nreadonly description: string,\nreadonly quantity: number,\nreadonly is_one_time: boolean,\nreadonly full_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly line_total: {\namount: number,\ncurrency: string,\n},\n})[],\nreadonly before_subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly after_subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly before_discount_total: {\namount: number,\ncurrency: string,\n},\nreadonly after_discount_total: {\namount: number,\ncurrency: string,\n},\nreadonly before_total: {\namount: number,\ncurrency: string,\n},\nreadonly after_total: {\namount: number,\ncurrency: string,\n},\nreadonly conflicts: {\nline_record_id: string,\nreason: string,\n}[],\nreadonly following_renewal: ({\nreadonly next_renewal_date: string,\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly discount_total: {\namount: number,\ncurrency: string,\n},\n}),\nreadonly starts_on_payment: boolean,\nreadonly activates: boolean,\n}) | null,\nreadonly schedule: ({\nreadonly period_start: string,\nreadonly period_end: string,\nreadonly preserved_invoices: ({\nreadonly invoice_record_id: string,\nreadonly period_start: string,\nreadonly period_end: string,\nreadonly status: string,\nreadonly amount_total: {\namount: number,\ncurrency: string,\n},\n})[],\nreadonly replaced_selections: ({\nreadonly effective_date: string,\nreadonly items: {\nprice_record_id: string,\nquantity: number,\nunit_price: {\namount: number,\ncurrency: string,\n} | null,\n}[],\n})[],\nreadonly recurring_subtotal: {\namount: number,\ncurrency: string,\n},\n}) | null,\nreadonly items: ({\nreadonly price_record_id: string,\nreadonly quantity: number,\nreadonly pricing: string,\nreadonly catalog_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly current_unit_price: {\namount: number,\ncurrency: string,\n} | null,\nreadonly effective_unit_price: {\namount: number,\ncurrency: string,\n},\n})[],\n};\n\ntype SdkSubscriptionChangePreviewOptions = {\n /** Subscription resource ID. */\n resourceId: string;\n /** Subscription record ID. The visitor needs Show access to this record. */\n recordId: string;\n /** The complete desired selection: price IDs and integer quantities of at least one. */\n items: {\n price_record_id: string;\n quantity: number;\n pricing?: {\n mode: 'catalog' | 'custom';\n unit_price?: { amount: number; currency: string };\n };\n }[];\n /** Apply now, keep current items until renewal, or start at a later billing period. Defaults to now. */\n effective?: 'now' | 'next_renewal' | 'period';\n /**\n * Start date (`YYYY-MM-DD`, in the subscription's preview timezone) of the later billing period\n * for `effective: 'period'`. It must be a future billing boundary that has no invoice. It is\n * ignored for other timings. The hook holds the request until it is set.\n */\n periodStart?: string;\n /** Ordinary current-period billing. Defaults to prorate. Initial and renewal invoice revisions ignore this option and use their invoice terms. */\n prorationBehavior?: 'prorate' | 'none';\n /**\n * Revise an unpaid renewal invoice that was already issued. Use `effective: 'now'` for the\n * current-period invoice and `effective: 'next_renewal'` for the upcoming renewal invoice.\n * `this_renewal` changes only that invoice; `this_and_following` also carries recurring\n * items forward. Omit for an ordinary item change.\n */\n revisionScope?: 'this_renewal' | 'this_and_following';\n /** Hold requests until the visitor is signed in and the selection is ready. Defaults to true. */\n enabled?: boolean;\n};\n\ntype SdkSubscriptionChangePreviewHandle = {\n /** Hidden during loading, after failure, and while disabled. */\n preview: SdkSubscriptionChangePreview | undefined;\n /** Current quote, or null while no terms can be reviewed. */\n quote: string | null;\n isLoading: boolean;\n error: ApiError | null;\n /** Refresh terms after a failed change or before renewed confirmation. */\n refetch: () => Promise<void>;\n};\n\ntype SdkSchedulableRecordRef = { resourceId: string; id: string };\n\ntype SdkSlotSearchGroup = {\n /** Defaults to `'all'`. */\n mode?: 'all' | 'any';\n /** Only for `mode: 'any'`. Defaults to `'first_available'`. */\n strategy?: 'first_available' | 'least_booked' | 'round_robin';\n records: SdkSchedulableRecordRef[];\n};\n\ntype SdkAvailableSlotsOptions = {\n /** Booking resource ID. Its booking settings decide what a slot occupies. */\n resourceId: string;\n /** At least one group, each with at least one record. */\n groups: SdkSlotSearchGroup[];\n /** Start of the search window: a `Date` or an ISO 8601 date-time. */\n from: Date | string;\n /** End of the search window: a `Date` or an ISO 8601 date-time. */\n to: Date | string;\n /** Length of each slot in minutes. */\n durationMinutes: number;\n /** Minutes between slot starts. Defaults to 15. */\n stepMinutes?: number;\n /** The first slot starts at least this many minutes from now. */\n noticeMinutes?: number;\n /** The last slot starts at most this many days from now. */\n maxAdvanceDays?: number;\n /** Units each slot needs, for records with a capacity. Defaults to 1. */\n quantity?: number;\n /** Most slots to return. Defaults to 50; at most 500. */\n maxSlots?: number;\n /** IANA time zone for each slot's `date`. When omitted, read the zone the search used from `timezone`. */\n timezone?: string;\n /** Makes the `round_robin` choice repeatable for the same seed. */\n seed?: string;\n /** Hold the search until its inputs are ready. Defaults to `true`. */\n enabled?: boolean;\n};\n\ntype SdkAvailableSlot = {\n /** Stable for the same start, end, and assignments. */\n id: string;\n start: string;\n end: string;\n /** The local date of `start` in the search time zone, `YYYY-MM-DD`. */\n date: string;\n assignments: {\n group: number;\n records: { resource_id: string; id: string }[];\n }[];\n};\n\ntype SdkAvailableSlotsHandle = {\n /** Slots in time order. Empty while loading, after a failure, and while disabled. */\n slots: SdkAvailableSlot[];\n /** The time zone of each slot's `date`, or null until a search succeeds. */\n timezone: string | null;\n /** True when the search stopped before the end of the window, for example at `maxSlots`. */\n stoppedEarly: boolean;\n isLoading: boolean;\n /** A 422 names the invalid option; a 429 means a guest searched too often. */\n error: ApiError | null;\n refetch: () => Promise<void>;\n};\n\ntype SdkAvailabilityOptions = {\n /** Schedulable resource ID. */\n resourceId: string;\n /** Record IDs of that resource. At most 50. */\n ids: string[];\n /** Start of the window: a `Date` or an ISO 8601 date-time. */\n from: Date | string;\n /** End of the window: a `Date` or an ISO 8601 date-time. At most 62 days after `from`. */\n to: Date | string;\n /** Hold the read until its inputs are ready. Defaults to `true`. */\n enabled?: boolean;\n /**\n * Words on the closed time of `backgroundTimes`. `busy` defaults to \"Busy\" and `closed` to\n * \"Closed\". `false` keeps the closed shading without words. Changing them sends no request.\n */\n labels?: { busy?: string; closed?: string } | false;\n};\n\ntype SdkAvailabilityPiece = {\n /** `external` is busy time on the record's blocking connected calendars. It never names the calendar. */\n source: 'hours' | 'exception' | 'limit' | 'closure' | 'external';\n effect: 'open' | 'closed' | 'replace';\n start: string;\n end: string;\n /** The record that the piece came from, such as an exception record, or null for hours and busy time. */\n record_id: string | null;\n};\n\ntype SdkRecordAvailability = {\n id: string;\n /** The time zone that the record's hours are read in. */\n timezone: string;\n /** Free time in the window after hours, exceptions, limits, closures, and calendar busy time. */\n available: { start: string; end: string }[];\n pieces: SdkAvailabilityPiece[];\n /**\n * Times for `Sdk.Components.Calendar`: free time with the open tone, then closed time inside\n * the record's hours, labelled \"Busy\" for busy time on connected calendars and \"Closed\" for\n * exceptions, closures, and limits. Time outside the record's hours is absent.\n */\n backgroundTimes: CalendarBackgroundTime[];\n};\n\ntype CalendarBackgroundTime = {\n /** A `Date` or an ISO 8601 date-time. */\n start: Date | string;\n /** A `Date` or an ISO 8601 date-time. */\n end: Date | string;\n /** `'open'` shades free time and `'closed'` shades time that is not free. Defaults to `'open'`. */\n tone?: 'open' | 'closed';\n /** Short text shown at the top of the shaded time. */\n label?: string;\n};\n\ntype SdkAvailabilityHandle = {\n /** One entry for each requested record. Empty while loading, after a failure, and while disabled. */\n records: SdkRecordAvailability[];\n isLoading: boolean;\n error: ApiError | null;\n refetch: () => Promise<void>;\n};\n\ntype SdkScheduleConflictsOptions = {\n /** Resource ID of the records that hold time: a Booking resource or an Eventable resource with a Calendar Owner. */\n resourceId: string;\n /** Only conflicts of these records. At most 50. */\n ids?: string[];\n /** Only conflicts on these Schedulable records. At most 50 IDs. */\n bookable?: { resourceId: string; ids: string[] };\n /** Hold the read until its inputs are ready. Defaults to `true`. */\n enabled?: boolean;\n};\n\ntype SdkScheduleConflict = {\n id: number;\n /** `booking` for a booking's claim, `owned_event` for an event on its calendar owner's time. */\n kind: 'booking' | 'owned_event';\n /** The record that holds the time. */\n record_id: string;\n /** The field of that record that names the Schedulable record. */\n relation: string;\n bookable_resource_id: string;\n bookable_id: string;\n /** The record's own start, without booking buffers. */\n start: string;\n /** The record's own end, without booking buffers. */\n end: string;\n /** The start of the time detection checks: a booking's claim with the buffers of its resource. Equals `start` for an owned event. */\n claim_start: string;\n /** The end of the time detection checks. Equals `end` for an owned event. */\n claim_end: string;\n /** What closes the time. `external` is busy time on a blocking connected calendar. It never names the calendar. */\n sources: ('exception' | 'closure' | 'external' | 'limit' | 'hours')[];\n detected_at: string;\n};\n\ntype SdkScheduleConflictsHandle = {\n /** Conflicts whose time has not ended, by start. Empty while loading, after a failure, while disabled, and for a guest. */\n conflicts: SdkScheduleConflict[];\n /** More conflicts remain than the read returned (at most 500). Narrow the read with `ids` or `bookable` to see them. */\n truncated: boolean;\n isLoading: boolean;\n error: ApiError | null;\n refetch: () => Promise<void>;\n};\n\ntype SdkPaymentRefundPreview = {\nreadonly payment_remaining: {\namount: number,\ncurrency: string,\n},\nreadonly change_credit: {\namount: number,\ncurrency: string,\n} | null,\nreadonly invoice_refundable: {\namount: number,\ncurrency: string,\n} | null,\nreadonly refundable: {\namount: number,\ncurrency: string,\n},\n};\n\ntype SdkPaymentRefundPreviewOptions = {\n /** Payment resource ID. */\n resourceId: string;\n /** Payment record ID. The visitor needs Show access to this record. */\n recordId: string;\n /** Hold requests until the visitor is signed in and the payment is known. Defaults to true. */\n enabled?: boolean;\n};\n\ntype SdkPaymentRefundPreviewHandle = {\n /** Hidden during loading, after failure, and while disabled. */\n preview: SdkPaymentRefundPreview | undefined;\n isLoading: boolean;\n error: ApiError | null;\n /** Refresh the limits after a refund or a rejected refund. */\n refetch: () => Promise<void>;\n};\n\ntype SdkAccountingEntity = {\nreadonly id: string,\nreadonly name: string,\nreadonly active: boolean,\nreadonly is_default: boolean,\n};\n\ntype SdkAccountingEntities = {\n /** Ordered by name. Empty while loading, after failure, in a branch, and for a signed-out visitor. */\n entities: SdkAccountingEntity[];\n isLoading: boolean;\n error: ApiError | null;\n};\n\ntype SdkSeriesOccurrenceMeta = {\n series_id: string;\n /** The occurrence key, today its local date (`YYYY-MM-DD`). Treat it as opaque. */\n key: string;\n /** `restored`: a person restored it after the series removed it. `kept`: the series could not change it, or a lock rule, a referrer, a hook, or the editor's policy kept it from removal. */\n state: 'regular' | 'restored' | 'kept';\n /** Why the series kept it. Present only while `state` is `kept`. */\n reason?:\n | 'series_kept_referenced'\n | 'series_kept_policy'\n | 'series_kept_locked'\n | 'series_kept_hook'\n | 'series_kept_override';\n /** Fields this occurrence changed on its own, which series changes leave alone. */\n overrides: string[];\n};\n\ntype SdkPayerEntityBalance = {\nreadonly entity: ({\nreadonly id: string,\nreadonly name: string,\nreadonly active: boolean,\nreadonly is_default: boolean,\n}),\nreadonly available: {\namount: number,\ncurrency: string,\n},\n};\n\ntype SdkPayerBalancesOptions = {\n /** Payer resource ID. */\n resourceId: string;\n /** Payer record ID. The visitor needs Show access to this record and its balance field. */\n recordId: string;\n /** Hold requests until the visitor is signed in and the payer is known. Defaults to true. */\n enabled?: boolean;\n};\n\ntype SdkPayerBalances = {\n /** Each active entity, and each inactive entity with an available balance above zero. Empty while loading, after failure, and while disabled. */\n balances: SdkPayerEntityBalance[];\n /** The payer's balance field. Null without a balance, while loading, after failure, and while disabled. */\n total: { amount: number; currency: string } | null;\n isLoading: boolean;\n error: ApiError | null;\n /** Refresh the amounts after a ledger entry or a payment. */\n refetch: () => Promise<void>;\n};\n\ntype SdkPackageReleaseHistory = any;\n\ntype SdkPackageReleaseNotesOptions = {\n /** Only this installed package. Omit it for every installed package. */\n packageId?: string;\n /** How many releases at or below the installed one to list per package. Newer releases are always listed. Defaults to 20; at most 100. */\n limit?: number;\n /** Hold the request until its inputs are ready. Defaults to true. */\n enabled?: boolean;\n};\n\ntype SdkPackageReleaseNotes = {\n /** Each installed package, by name. Empty while loading, after failure, and while disabled. */\n packages: SdkPackageReleaseHistory[];\n isLoading: boolean;\n error: ApiError | null;\n refetch: () => Promise<void>;\n};\n\ntype SdkPackageReleaseNotesProps = {\n /** Only this installed package. Omit it to list every installed package. */\n packageId?: string;\n /** Show only the installed release and the newer ones, for a dashboard. */\n compact?: boolean;\n /** How many releases at or below the installed one to list per package. */\n limit?: number;\n /** Shown when no package is installed. */\n emptyMessage?: string;\n className?: string;\n};\n\ntype SdkUseApiOptions<TData = any> = {\n /** Hold the request until its inputs are ready. Defaults to `true`. */\n enabled?: boolean;\n /** Keep the last successful value visible while a changed key fetches. */\n keepPreviousData?: boolean;\n /**\n * Refetch this endpoint on an interval. The function form receives the current `data`\n * (`undefined` before the first fetch) and returns `false` to stop.\n *\n * @example\n * ```tsx\n * Sdk.hooks.useApi(\n * ['records', order.id, 'fulfillment'],\n * (api) => api.get(Sdk.utils.workflow.url('...', { order_id: order.id })),\n * { pollInterval: (data) => (data?.shipped ? false : 5000) }\n * );\n * ```\n */\n pollInterval?: SdkPollInterval<TData | undefined>;\n};\n\ntype SdkApiHandle<TData> = {\n data: TData | undefined;\n isLoading: boolean;\n isError: boolean;\n /** The normalized request failure, or `null` before/after a successful request. */\n error: ApiError | null;\n /**\n * Whole seconds until the next automatic `pollInterval` request, rounded up. `0`\n * means a refresh is in flight. `null` means no refresh is scheduled.\n */\n refreshIn: number | null;\n /** Mark this exact key stale and refetch it when active. */\n invalidate: () => Promise<void>;\n};\n\ntype SdkRouterVisitOptions = TaskRouterVisitOptions;\n\ntype TaskRouterVisitOptions = Record<string, any>;\n\ntype SdkRouter = TaskRouter;\n\ntype TaskRouter = {\n /** Visit a URL (defaults to a GET). Re-renders the page with the new props. */\n visit(url: string, options?: TaskRouterVisitOptions): void;\n /** Make a GET visit, encoding `data` into the query string. */\n get(url: string, data?: Record<string, any>, options?: TaskRouterVisitOptions): void;\n /** Make a POST visit with `data` as the request body. */\n post(url: string, data?: Record<string, any>, options?: TaskRouterVisitOptions): void;\n /** Make a PUT visit with `data` as the request body. */\n put(url: string, data?: Record<string, any>, options?: TaskRouterVisitOptions): void;\n /** Make a PATCH visit with `data` as the request body. */\n patch(url: string, data?: Record<string, any>, options?: TaskRouterVisitOptions): void;\n /** Make a DELETE visit to the URL. */\n delete(url: string, options?: TaskRouterVisitOptions): void;\n /** Reload the current page, re-fetching its props without changing the URL. */\n reload(options?: TaskRouterVisitOptions): void;\n};\n\ntype SdkAuthResource = {\n id: string;\n name: string;\n\n /** Whether the resource has a password field, and so can offer a reset link. */\n passwordResetEnabled: boolean;\n\n /**\n * Whether the resource opted in to emailed sign-in links. When false,\n * {@link SdkAuth.sendMagicLink} is rejected server-side for this resource.\n */\n magicLinkEnabled: boolean;\n\n /**\n * Whether the resource requires an SMS code after the password or emailed link. A\n * sign-in then continues with {@link SdkAuth.twoFactor}.\n */\n twoFactorEnabled: boolean;\n};\n\ntype SdkTwoFactorMode = 'hosted' | 'inline';\n\ntype SdkTwoFactorPending = {\n /** The authenticatable resource that the sign-in belongs to. */\n resourceId: string;\n\n /**\n * `'challenge'`: the visitor enters a code sent to the account's MFA number, or a backup\n * code. `'setup'`: the account has no MFA number yet, so the visitor sets one up first.\n */\n stage: 'challenge' | 'setup';\n};\n\ntype SdkAuthLoginInput = {\n identifier: string;\n password: string;\n resourceId?: string;\n redirectPage?: string;\n returnTo?: 'current';\n /** See {@link SdkTwoFactorMode}. Defaults to `'hosted'`. */\n twoFactor?: SdkTwoFactorMode;\n};\n\ntype SdkAuthMagicLinkInput = {\n identifier: string;\n resourceId?: string;\n redirectPage?: string;\n returnTo?: 'current';\n /**\n * See {@link SdkTwoFactorMode}. With `'inline'`, a link that needs two-factor returns\n * to this page, path parameters and query string included, to show the challenge.\n */\n twoFactor?: SdkTwoFactorMode;\n};\n\ntype SdkAuthPasswordResetInput = {\n identifier: string;\n resourceId?: string;\n /** With `'current'`, the visitor returns to this page after the reset signs them in. */\n returnTo?: 'current';\n /**\n * See {@link SdkTwoFactorMode}. With `'inline'`, a reset that needs two-factor returns\n * to this page to show the challenge.\n */\n twoFactor?: SdkTwoFactorMode;\n};\n\ntype SdkAuth = {\n /**\n * The default authenticatable resource id used when {@link SdkAuth.login} is called\n * without a `resourceId`. `null` when the workspace has zero or multiple authenticatable\n * resources \u2014 pass an explicit `resourceId` in that case.\n */\n resourceId: string | null;\n\n /** Every authenticatable resource in the workspace \u2014 see {@link SdkAuthResource}. */\n resources: SdkAuthResource[];\n\n /**\n * The pending two-factor sign-in of this browser, or `null`. It is set after an\n * `'inline'` sign-in whose resource enables two-factor; render the challenge or the\n * setup for it. Always `null` in previews.\n */\n twoFactor: SdkTwoFactorPending | null;\n\n /**\n * Sign in with an identifier (email) and password. Resolves the target resource from\n * `credentials.resourceId`, falling back to {@link SdkAuth.resourceId}; throws if neither\n * is set. Pass `options` (`onError`, `onSuccess`, `preserveState`, `preserveScroll`, \u2026)\n * straight through to the router \u2014 read validation errors (bad credentials, rate limiting)\n * from `onError`. A successful login to a same-origin workspace page follows the 302\n * redirect as an Inertia visit, so `onSuccess` runs and `preserveState` can keep\n * surrounding form data intact; cross-origin destinations still trigger a full-page\n * `X-Inertia-Location` redirect, so `onSuccess` does not run. Control the destination\n * with `credentials.redirectPage` or use `credentials.returnTo: 'current'` for the\n * exact served page URL.\n */\n login(credentials: SdkAuthLoginInput, options?: SdkRouterVisitOptions): void;\n\n /**\n * Email a one-time sign-in link to `identifier`, for resources that enabled magic link\n * login (see {@link SdkAuthResource.magicLinkEnabled} \u2014 the endpoint 404s otherwise).\n * The response is deliberately identical whether or not an account exists, so treat\n * `onSuccess` as \"the request was accepted\", never as \"an account was found\". Unlike\n * `login` this stays on the current page, so `onSuccess` does run. `returnTo: 'current'`\n * also carries that exact served page URL through the emailed link for confirmation in\n * the same tab, another tab, or another browser.\n */\n sendMagicLink(input: SdkAuthMagicLinkInput, options?: SdkRouterVisitOptions): void;\n\n /**\n * Email a password reset link to `identifier`. Like `sendMagicLink`, the response is\n * the same whether or not an account exists, and the visitor stays on the current\n * page so `onSuccess` runs. `Sdk.Components.LoginForm` already offers this inline.\n */\n sendPasswordResetLink(input: SdkAuthPasswordResetInput, options?: SdkRouterVisitOptions): void;\n\n /** Sign the current identity out. Logout also clears this origin's local and session storage. */\n logout(options?: SdkRouterVisitOptions): void;\n};\n\ntype SdkAuthConfig = {\n resourceId: string | null;\n resources: SdkAuthResource[];\n /** The stable id of the currently served Page. Draft and authoring previews omit it. */\n currentPageId?: string | null;\n /** The pending two-factor sign-in of this browser. Previews omit it. */\n twoFactor?: SdkTwoFactorPending | null;\n};\n\ntype SdkTwoFactorFlow = 'challenge' | 'setup' | 'backupCodes';\n\ntype SdkUseTwoFactorOptions = {\n /**\n * The authenticatable resource. Defaults to the resource of the pending sign-in, then of\n * the signed-in identity, then the workspace's sole authenticatable resource.\n */\n resourceId?: string;\n};\n\ntype SdkTwoFactorChallengeState = {\n /**\n * `'consent'`: no code was sent yet; render `Sdk.Components.TwoFactorSmsConsent` with\n * `flow=\"challenge\"`. `'code'`: a code was sent to `maskedPhone`.\n */\n step: 'consent' | 'code';\n\n /** The last four digits of the MFA number, such as `\u2022\u2022\u2022\u2022 0123`. */\n maskedPhone: string;\n\n /** Seconds until `Sdk.Components.TwoFactorSendCodeButton` can send a new code. */\n resendAvailableIn: number;\n\n /** A confirmation from the last send, such as \"We sent you a new code.\" */\n status: string | null;\n\n /** Why the last code could not be sent. A backup code still works. */\n deliveryError: string | null;\n};\n\ntype SdkTwoFactorRequestState = {\n /** The first load is in flight. */\n isLoading: boolean;\n\n /** The flow did not load. */\n error: SdkApiError | null;\n\n /** A step is being submitted. */\n isPending: boolean;\n\n /** The last submitted step failed. Read field messages with `Sdk.utils.error`. */\n actionError: SdkApiError | null;\n\n /**\n * The pending sign-in ended: it expired, or too many codes were wrong. Call `cancel()`\n * to start again from the email step.\n */\n restartRequired: boolean;\n};\n\ntype SdkTwoFactorChallengeHandle = SdkTwoFactorRequestState & {\n /** Whether a pending sign-in waits for this challenge. Nothing loads otherwise. */\n isActive: boolean;\n\n data: SdkTwoFactorChallengeState | undefined;\n\n /**\n * Set when the code passed and the account has no unused backup codes: render\n * `Sdk.Components.TwoFactorBackupCodes` before the visitor continues.\n */\n needsBackupCodes: boolean;\n\n /** Check a 6-digit SMS code. On success the page navigates to the sign-in destination. */\n verifyCode(code: string): Promise<boolean>;\n\n /** Check a backup code, such as `ABCDE-12345`. Each code works once. */\n verifyBackupCode(code: string): Promise<boolean>;\n\n /** End the pending sign-in and return to the email step. */\n cancel(): Promise<void>;\n};\n\ntype SdkTwoFactorSetupStep = 'authorize' | 'phone' | 'consent' | 'verify' | 'complete';\n\ntype SdkTwoFactorSetupState = {\n /**\n * `'authorize'`: a replacement first proves the current number or a backup code.\n * `'phone'`: enter the new MFA number. `'consent'`: render\n * `Sdk.Components.TwoFactorSmsConsent` with `flow=\"setup\"`. `'verify'`: enter the code\n * sent to the new number. `'complete'`: the number is verified; call `finish()`.\n */\n step: SdkTwoFactorSetupStep;\n\n /** Whether this changes an existing MFA number of the signed-in member. */\n isReplacement: boolean;\n\n /** The last four digits of the number this step texts, or `null` before one is entered. */\n maskedPhone: string | null;\n\n /** A confirmation from the last send. */\n status: string | null;\n};\n\ntype SdkTwoFactorSetupHandle = SdkTwoFactorRequestState & {\n /**\n * Whether a setup applies: a pending sign-in in the setup stage, or a signed-in member\n * of a resource that enables two-factor. Nothing loads otherwise.\n */\n isActive: boolean;\n\n data: SdkTwoFactorSetupState | undefined;\n\n /**\n * The new backup codes, once, after the new number is verified. Show them with\n * `Sdk.Components.TwoFactorBackupCodeList`. Never store them.\n */\n codes: string[] | null;\n\n /** Prove the current factor before a replacement, with an SMS code or a backup code. */\n verifyExistingFactor(factor: { code: string } | { recoveryCode: string }): Promise<boolean>;\n\n /** Set the proposed MFA number, with its country code. */\n submitPhone(phone: string): Promise<boolean>;\n\n /** Check the 6-digit code sent to the proposed number. */\n verifyNewNumber(code: string): Promise<boolean>;\n\n /**\n * Finish after `'complete'`. A pending sign-in navigates to its destination; a\n * replacement resolves `true` and the page stays.\n */\n finish(): Promise<boolean>;\n\n /** Stop the setup. A pending sign-in also ends. */\n cancel(): Promise<void>;\n};\n\ntype SdkTwoFactorBackupCodesState = {\n /** How many unused backup codes remain. */\n remainingCount: number;\n\n /**\n * Whether the first set can be generated without a code, right after a sign-in that\n * passed the second factor.\n */\n setupAvailable: boolean;\n\n /** The last four digits of the MFA number. */\n maskedPhone: string;\n\n /** Whether proving the factor by SMS needs consent first. */\n smsConsentRequired: boolean;\n\n /** A confirmation from the last send. */\n status: string | null;\n};\n\ntype SdkTwoFactorBackupCodesHandle = SdkTwoFactorRequestState & {\n /** Whether the signed-in member's resource enables two-factor. Nothing loads otherwise. */\n isActive: boolean;\n\n data: SdkTwoFactorBackupCodesState | undefined;\n\n /**\n * The new codes, once, after `generate()`. Show them with\n * `Sdk.Components.TwoFactorBackupCodeList`. Never store them.\n */\n codes: string[] | null;\n\n /**\n * Generate a new set. Every earlier code stops working. Pass a fresh SMS code or an\n * unused backup code; pass nothing only when `setupAvailable` is true.\n */\n generate(factor?: { code: string } | { recoveryCode: string }): Promise<boolean>;\n\n /** Leave after the first set: go on to the sign-in destination. */\n continue(): void;\n};\n\ntype SdkUseForm = <TForm extends FormDataType<TForm>>(\n defaults: TForm\n) => InertiaFormProps<TForm>;\n\ntype FormDataType = any;\n\ntype InertiaFormProps = any;\n\ntype WorkflowRouteSettings = CoreWorkflowRouteSettings;\n\ntype CoreWorkflowRouteSettings = any;\n\ntype PageRouteSettings = CorePageRouteSettings;\n\ntype CorePageRouteSettings = any;\n\ntype SdkWorkspaceFields = TaskWorkspaceFields;\n\ntype TaskWorkspaceFields = Record<string, any>;\n\ntype SdkContentValue = {\n type?: string;\n text?: string;\n attrs?: Record<string, any> | null;\n marks?: any[] | null;\n content?: SdkContentValue[] | null;\n};\n\ntype SdkMoney = TaskMoney;\n\ntype TaskMoney = { amount: number; currency: string };\n\ntype SdkMoneyInput = TaskMoneyInput;\n\ntype TaskMoneyInput = TaskMoney | null | undefined;\n\ntype SdkPointValue = { x: number; y: number };\n\ntype SdkRecurrenceDefinition = {\n rule: string;\n exceptions: string[];\n /**\n * On a series occurrence, read only: the date a pending series change takes effect. The\n * `rule` of an occurrence is read from its series, and sending it back unchanged changes\n * nothing.\n */\n changes_on?: string | null;\n};\n\ntype SdkRecurrenceValue = SdkRecurrenceDefinition | string;\n\ntype SdkEventScheduleValue = {\n start: string;\n end: string;\n all_day: boolean;\n recurrence: SdkRecurrenceDefinition | null;\n};\n\ntype SdkRegularHoursDay =\n | 'monday'\n | 'tuesday'\n | 'wednesday'\n | 'thursday'\n | 'friday'\n | 'saturday'\n | 'sunday';\n\ntype SdkRegularHoursValue = {\n periods: {\n from: string | null;\n rules: { days: SdkRegularHoursDay[]; start: string; end: string }[];\n }[];\n};\n\ntype SdkLocalStoragePrimitive = boolean | null | number | string;\n\ninterface SdkLocalStorageObject {\n readonly [key: string]: SdkLocalStorageValue;\n}\n\ntype SdkLocalStorageValue =\n | SdkLocalStoragePrimitive\n | SdkLocalStorageObject\n | SdkLocalStorageArray;\n\ninterface SdkLocalStorageArray extends ReadonlyArray<SdkLocalStorageValue> {}\n\ntype SdkLocalStorageSetter<TValue> = (\n value: TValue | null | ((current: TValue | null) => TValue | null)\n) => void;\n\ntype SdkLocalStorageResult<TValue> = readonly [\n value: TValue | null,\n setter: SdkLocalStorageSetter<TValue>\n];\n\ntype SdkMoneyFormatOptions = TaskMoneyFormatOptions;\n\ntype TaskMoneyFormatOptions = {\n showSymbol?: boolean;\n showCode?: boolean;\n};\n\ntype SdkDateAddUnit = TaskDateAddUnit;\n\ntype TaskDateAddUnit = 'days' | 'weeks' | 'months' | 'years';\n\ntype SdkDateBoundaryUnit = TaskDateBoundaryUnit;\n\ntype TaskDateBoundaryUnit = 'week' | 'month' | 'quarter' | 'year';\n\ntype SdkTimeAddUnit = TaskTimeAddUnit;\n\ntype TaskTimeAddUnit =\n | 'seconds'\n | 'minutes'\n | 'hours'\n | 'days'\n | 'weeks'\n | 'months'\n | 'years';\n\ntype SdkTimeBoundaryUnit = TaskTimeBoundaryUnit;\n\ntype TaskTimeBoundaryUnit = 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';\n\ntype SdkWorkflowTarget = TaskWorkflowTarget<WorkflowReference>;\n\ntype TaskWorkflowTarget<TReference extends string = string> =\n | TReference\n | { id: TReference; params?: Record<string, any> }\n | null\n | undefined;\n\ntype SdkBrandLogoOptions = TaskBrandLogoOptions;\n\ntype TaskBrandLogoOptions = {\n variants?: TaskBrandLogoVariant[];\n mode?: 'light' | 'dark';\n};\n\ntype TaskBrandLogoVariant = 'horizontal' | 'icon' | 'wordmark';\n\ntype SdkClassValue = TaskClassValue;\n\ntype TaskClassValue =\n | string\n | number\n | bigint\n | boolean\n | null\n | undefined\n | { [className: string]: any }\n | TaskClassValue[];\n\ntype SdkNavigateOptions = TaskNavigateOptions;\n\ntype SdkTiptapParseInput = TaskTiptapParseInput;\n\ntype TaskTiptapParseInput = {\n content?: any;\n html?: boolean;\n oneLine?: boolean;\n placeholder?: string | null;\n};\n\ntype SdkTextGenerateOptions = TaskTextGenerateOptions;\n\ntype TaskTextGenerateOptions = {\n length?: number;\n includeNumbers?: boolean;\n includeSpecial?: boolean;\n};\n\ntype SdkLocationValue = TaskLocationValue;\n\ntype TaskLocationValue = {\n point: { x: number | null; y: number | null } | null;\n display_name: string | null;\n line1: string | null;\n line2: string | null;\n city: string | null;\n county: string | null;\n state: string | null;\n country: string | null;\n country_code: string | null;\n postal_code: string | null;\n place_id: string | null;\n};\n\ntype SdkLocationFormatOptions = TaskLocationFormatOptions;\n\ntype TaskLocationFormatOptions = {\n /** Use `address` to skip `display_name` and always assemble the postal address. */\n format?: 'label' | 'address';\n};\n\ntype SdkPhoneFormatOptions = TaskPhoneFormatOptions;\n\ntype TaskPhoneFormatOptions = {\n format?: 'national' | 'international' | 'e164' | 'rfc3966';\n};\n\ntype SdkNumberFormatOptions = TaskNumberFormatOptions;\n\ntype TaskNumberFormatOptions = {\n decimals?: number;\n};\n\ntype SdkConfettiOptions = TaskConfettiOptions;\n\ntype TaskConfettiOptions = Record<string, any>;\n\ntype SdkClipboardCopyOptions = TaskClipboardCopyOptions;\n\ntype TaskClipboardCopyOptions = {\n message?: string;\n silent?: boolean;\n};\n\ntype SdkNotifyToastOptions = TaskNotifyToastOptions;\n\ntype TaskNotifyToastOptions = {\n variant?: TaskNotifyVariant;\n description?: string;\n};\n\ntype TaskNotifyVariant = 'default' | 'success' | 'error' | 'warning' | 'info';\n\ntype SdkNotifyVariant = TaskNotifyVariant;\n\ntype SdkBadgeVariant = 'default' | 'secondary' | 'success' | 'warning' | 'destructive';\n\ntype SdkDisplayFormatOptions = {\n /** Forwarded to the existing Page SDK phone formatter. Defaults to `national`. */\n phoneFormat?: 'national' | 'international' | 'e164' | 'rfc3966';\n /** Treat a numeric field value as a duration measured in this unit. */\n durationUnit?: 'milliseconds' | 'seconds' | 'minutes' | 'hours';\n};\n\ntype SdkRecordInput<TRecord extends ResourceRecord = ResourceRecord> =\n | TRecord\n | { fields?: TRecord | null }\n | null\n | undefined;\n\ntype SdkFieldValue<TRecord extends ResourceRecord, TSelector extends SdkFieldSelector> =\n TSelector extends SdkFieldSelector<infer TKey>\n ? TKey extends keyof TRecord\n ? TRecord[TKey]\n : any\n : any;\n\ntype SdkDisplayUtils = {\n /**\n * Format a resource field value for plain-text display. The field handle supplies\n * its type, configured date/time pattern, select options, and boolean labels.\n * Date, time, datetime, money, phone, location, select, number, boolean, content,\n * markdown, and scalar text values are supported; pass `durationUnit` to format a\n * numeric duration. A content value formats to its plain text, with one line for each\n * paragraph. A markdown value formats to its plain text with the syntax removed: one\n * line for each block, link text kept, and link URLs and images dropped. Existing SDK\n * date, time, money, number, phone, and location formatters remain the source of truth.\n *\n * @remarks\n * Configured date/time display patterns are forwarded to those formatters. Their\n * current locale and timezone behavior is preserved; use the dedicated formatter\n * utilities directly when you need their lower-level options. Nullish or malformed\n * values return `null` and report the standard SDK diagnostic.\n *\n * @example\n * const startsAt = Sdk.utils.display.format(events.fields.starts_at, event.starts_at);\n * const summary = Sdk.utils.display.format(events.fields.description, event.description);\n * const duration = Sdk.utils.display.format(events.fields.duration_minutes, event.duration_minutes, {\n * durationUnit: 'minutes'\n * });\n */\n format: (\n field: SdkResourceField | null | undefined,\n value: any,\n options?: SdkDisplayFormatOptions\n ) => string | null;\n\n /**\n * Build initials from the first `count` whitespace-delimited name parts.\n * `count` defaults to `2`. Nullish/blank names or an invalid count return `''`\n * and report the standard SDK diagnostic.\n *\n * @example\n * const fallback = Sdk.utils.display.initials('Ada Lovelace'); // 'AL'\n */\n initials: (name: string | null | undefined, count?: number) => string;\n\n /**\n * Map a select option's configured color to an `Sdk.Components.Badge` variant.\n * `green`/`emerald` \u2192 `success`, `yellow`/`orange` \u2192 `warning`, `red` \u2192\n * `destructive`, `blue`/`pink`/`purple` \u2192 `default`, and missing, custom,\n * `gray`, or `slate` colors \u2192 `secondary`.\n *\n * @example\n * const status = invoices.fields.status;\n * <Sdk.Components.Badge variant={Sdk.utils.display.badgeVariant(status, invoice.status)}>\n * {Sdk.utils.display.format(status, invoice.status)}\n * </Sdk.Components.Badge>\n */\n badgeVariant: (field: SdkResourceField | null | undefined, value: any) => SdkBadgeVariant;\n\n /**\n * Read a field from a flat SDK record or a legacy/external `{ fields: record }`\n * shape. Prefer the nested value when both shapes contain the key. Missing,\n * nullish, or malformed inputs return `null`; pass a field handle to retain the\n * workspace-generated record value type.\n *\n * @example\n * const name = Sdk.utils.display.fieldValue(student, students.fields.name);\n */\n fieldValue: <TRecord extends ResourceRecord, TSelector extends SdkFieldSelector>(\n record: SdkRecordInput<TRecord>,\n field: TSelector\n ) => SdkFieldValue<TRecord, TSelector> | null;\n};\n\ntype SdkApiErrorBody = TaskApiErrorBody;\n\ntype TaskApiErrorBody = {\n message?: string;\n errors?: Record<string, string[]>;\n [key: string]: any;\n};\n\ntype SdkUtils = Omit<TaskUtils, 'workspace' | 'workflow' | 'tiptap'> & {\n /**\n * Render TipTap JSON to plain text, or to sanitized HTML with `html: true`.\n *\n * @deprecated Render a content field with `Sdk.Components.FormFieldDisplay` or\n * `Sdk.Components.RecordFields`. Read its plain text with `Sdk.utils.display.format`.\n * @aiHidden Kept on the runtime SDK for existing pages, but omitted from the generated\n * SDK reference so agents stop using it.\n *\n * @example\n * const text = Sdk.utils.tiptap.parse({ content: record.notes });\n */\n tiptap: TaskUtils['tiptap'];\n /**\n * Format a count with the matching English singular or plural word.\n *\n * @example\n * Sdk.utils.pluralize(1, 'item', 'items'); // '1 item'\n * Sdk.utils.pluralize(2, 'item', 'items'); // '2 items'\n */\n pluralize: (count: number, one: string, other: string) => string;\n /**\n * Build URLs for published workflows by stable slug or ID.\n *\n * @example\n * const action = Sdk.utils.workflow.url('archive-student', { id: student.id });\n * if (action) await Sdk.api.post(action);\n */\n workflow: {\n /**\n * Build a workflow URL. Pass the target by stable slug or ID, or as\n * `{ id, params }`.\n *\n * @returns The published URL with route params interpolated, or `null` when\n * the target cannot be resolved.\n * @remarks Prefer the workflow slug from the workspace workflow catalog. The\n * workflow ID remains valid. Null/undefined targets, unpublished workflows,\n * and missing route params return `null` and warn in development/editor mode.\n */\n url: (target: SdkWorkflowTarget, params?: Record<string, any>) => string | null;\n };\n /**\n * Field-aware plain-text display helpers for formatting values, deriving avatar\n * initials and select badge variants, and reading flat or legacy nested records.\n *\n * @remarks\n * `format` delegates date, time, money, number, phone, and location rendering to the\n * existing SDK formatters so their current locale and timezone behavior remains consistent.\n * Nullish or malformed values return `null` with the standard SDK diagnostic;\n * `fieldValue` returns `null`.\n *\n * @example\n * const status = Sdk.utils.display.format(tasks.fields.status, task.status);\n */\n display: SdkDisplayUtils;\n};\n\ntype TaskUtils = {\n /**\n * Compose class names conditionally and resolve conflicting Tailwind utilities\n * with last-in-list precedence. Accepts strings, nested arrays, conditional\n * class maps, and falsy values.\n *\n * @remarks\n * Write every Tailwind utility as a complete static token in source, including\n * conditional alternatives. Use `{ 'bg-success': isActive }`, not\n * `` `bg-${color}` ``. Complete static tokens keep `tailwind-merge` conflict\n * behavior predictable, remain visible to Page design/source analysis, and avoid\n * relying on runtime CDN compilation for generated utilities.\n *\n * @example\n * const className = Sdk.utils.cn(\n * 'rounded-md px-2 text-sm',\n * isSelected && 'bg-primary text-primary-foreground',\n * ['transition-opacity', { 'opacity-50': isDisabled }]\n * );\n *\n * @example\n * Sdk.utils.cn('px-2', 'px-4'); // 'px-4'\n */\n cn: (...inputs: TaskClassValue[]) => string;\n /**\n * Build the request URL for a published workflow from the page's injected\n * `workflowRouteSettings`, keyed by the workflow's stable ID and indexed by slug.\n *\n * @remarks\n * Workflows are request targets: resolve the URL, guard its documented `null`\n * fallback, then pass the string to `Sdk.api`; or use\n * `Sdk.hooks.useWorkflowForm` for forms. To navigate the user somewhere, target a\n * page with `Sdk.utils.page.url()` / `Sdk.utils.page.navigate()`; the SDK has\n * no workflow-navigation helper, so to send the user *through* a workflow\n * (one that ends in a redirect), assign its URL to `window.location.href`.\n * Prefer the stable workflow slug from the workspace catalog. The stable ID\n * remains valid. Never hand-write `/api/workflows/...` endpoint strings; a\n * hardcoded path silently breaks when the workflow route changes.\n *\n * @example\n * // Call a workflow imperatively by slug (no form):\n * const action = Sdk.utils.workflow.url('approve-invoice');\n * if (action) await Sdk.api.post(action, { invoice_id: row.id });\n *\n * @example\n * // Send the user through a workflow that redirects:\n * const action = Sdk.utils.workflow.url('open-record', { id: row.id });\n * if (action) window.location.href = action;\n */\n workflow: {\n /**\n * Build a workflow URL. Pass the target by stable slug or ID (string) or\n * `{ id, params }`.\n *\n * @returns The published URL with route params interpolated, or `null` when\n * the target cannot be resolved.\n * @remarks Null/undefined targets, unpublished workflows, and missing route\n * params return `null` and warn in development/editor mode.\n * @example\n * const action = Sdk.utils.workflow.url({ id: 'apply-for-loan', params: { applicant_id: '123' } });\n */\n url: (target: TaskWorkflowTarget, params?: Record<string, any>) => string | null;\n };\n /**\n * Build the URL of a published page from the page's injected\n * `pageRouteSettings`, keyed by the page's stable id.\n *\n * @remarks\n * Pages are navigation targets: use the result as a link `href`, or navigate\n * to it directly with `Sdk.utils.page.navigate()`. You cannot submit a form\n * to a page \u2014 to submit data, target a workflow with `Sdk.utils.workflow.url()`\n * or `Sdk.hooks.useWorkflowForm`. **Always** address the page by its stable id\n * through this helper \u2014 never hand-write route strings; the id is\n * rename-safe and a hardcoded path silently breaks when the page's route\n * changes.\n *\n * @example\n * // Link to a page by id:\n * const href = Sdk.utils.page.url(INVOICE_DETAIL_PAGE_ID, { invoice: row.id });\n * return href ? <a href={href}>View invoice</a> : null;\n */\n page: {\n /**\n * Build an absolute URL on the page's selected domain, or the workspace's\n * primary domain when none is selected. Pass the target by stable id (string) or\n * `{ id, params }`.\n *\n * @returns The published URL with route params interpolated, or `null` when\n * the target or its domain cannot be resolved.\n * @remarks Null/undefined targets, unpublished pages, and missing route\n * params return `null` and warn in development/editor mode.\n * @example\n * const href = Sdk.utils.page.url({ id: INVOICE_DETAIL_PAGE_ID, params: { invoice: row.id } });\n */\n url: (target: TaskPageTarget, params?: Record<string, any>) => string | null;\n /**\n * Navigate to a published page by stable id. Builds the page URL from the\n * injected `pageRouteSettings` (like `Sdk.utils.page.url`) and then performs\n * an Inertia visit on the same origin. Other origins use normal browser\n * navigation in the current tab; `openInNewTab` opens a new tab. Pass the\n * target by stable id (string) or `{ id, params }`.\n *\n * @remarks Null/undefined targets, unpublished pages, and missing route\n * params are ignored after a warning in development/editor mode.\n * @example\n * Sdk.utils.page.navigate(STUDENTS_PAGE_ID);\n * @example\n * Sdk.utils.page.navigate({ id: INVOICE_DETAIL_PAGE_ID, params: { invoice: row.id } });\n */\n navigate: (target: TaskPageTarget, options?: TaskNavigateOptions) => void;\n };\n /**\n * Resolve a workspace file id to the URL that serves it, so authors never\n * hardcode absolute file URLs (e.g. `https://portal.example.com/files/<id>`).\n * The returned path is host-relative and hits the `files.proxy` route, which\n * redirects to the file's real storage/CDN location.\n *\n * @remarks\n * Pass the file by id (string) or `{ id }`. A `file_reference` record value is\n * always the File uuid string when set and `null` when unset \u2014 pass it straight in;\n * never check for URLs or `{ url }` objects, those shapes never occur.\n * Null/undefined/malformed input returns `null` and warns in\n * development/editor mode. Pair a non-null result with any element that takes\n * a URL \u2014 an `<img src>`, a download link, etc.\n *\n * Without options, the URL serves the original stored file. Use it for\n * downloads and links. In a page, `Sdk.Components.Image` and a plain `<img>`\n * with this URL select a size automatically: when the page has authorized\n * metadata for the file, the runtime adds `srcset` candidates from the file's\n * available widths and `sizes` from the rendered box. A file without that\n * metadata, or a private file, loads the original.\n *\n * `{ width }` requests one explicit output width in image pixels. The browser\n * does not apply device pixel ratio to it, and the runtime does not replace it\n * with automatic candidates. The server rounds up to an available width, and\n * returns the original when the file cannot be resized. An invalid width\n * returns the unsized URL and warns.\n *\n * `{ quality }` overrides the default transformation quality of 85 with an\n * integer from 1 to 100. It preserves automatic sizing unless `width` is also\n * set. Invalid quality values use the default and warn. Quality does not\n * transform a file without a width request and does not affect original delivery.\n *\n * @example\n * return <Sdk.Components.Image src={Sdk.utils.file(LOGO_FILE_ID)} alt=\"Logo\" />;\n *\n * @example\n * const logoUrl = Sdk.utils.file(LOGO_FILE_ID);\n * return logoUrl ? <img src={logoUrl} alt=\"Logo\" /> : null;\n *\n * @example\n * return <Sdk.Components.Image src={Sdk.utils.file(record.avatar)} alt={record.name} />;\n *\n * @example\n * const href = Sdk.utils.file({ id: attachment.id });\n *\n * @example\n * const thumbnailUrl = Sdk.utils.file(record.photo, { width: 320 });\n *\n * @example\n * return <Sdk.Components.Image src={Sdk.utils.file(record.photo, { quality: 90 })} alt=\"Photo\" />;\n */\n file: (target: TaskFileTarget, options?: TaskFileUrlOptions) => string | null;\n /**\n * Resolve the workspace Brand Kit logo to a servable URL. Walks the\n * brand-kit `logos.<variant>.<light|dark>` slots \u2014 each slot stores a File\n * uuid string or `null` \u2014 and returns the URL of the first uploaded logo,\n * so layouts never re-derive this walk by hand.\n *\n * @remarks\n * Default variant order is `horizontal` \u2192 `wordmark` \u2192 `icon` (widest\n * first); pass `variants` to prefer a different shape (e.g. `['icon']` for\n * a compact sidebar mark). The requested `mode` (default `light`) is\n * preferred, with the other mode as a fallback so a workspace that uploaded\n * only one asset still renders. Returns `null` when no logo is uploaded at\n * all \u2014 render your fallback mark in that case. Unsupported entries in a\n * mixed `variants` list are warned about and skipped. Null or malformed\n * options return `null` and warn in development/editor mode.\n *\n * @example\n * const logoUrl = Sdk.utils.brandLogo();\n * return logoUrl ? <img src={logoUrl} alt=\"Logo\" /> : <FallbackMark />;\n *\n * @example\n * // Compact icon mark for a collapsed sidebar, dark theme first:\n * const iconUrl = Sdk.utils.brandLogo({ variants: ['icon', 'horizontal'], mode: 'dark' });\n */\n brandLogo: (options?: TaskBrandLogoOptions | null) => string | null;\n /**\n * Non-throwing accessors for workspace field values, tolerant of missing keys\n * (returns a default instead of throwing).\n *\n * @example\n * const fromName = utils.workspace.value('outgoing_email_sender_name', 'Diagonal');\n */\n workspace: {\n /**\n * Read a single workspace field value by stable key, returning\n * `defaultValue` (default `null`) when the key is absent or nullish.\n *\n * @example\n * const currency = utils.workspace.value('default_currency', 'usd');\n */\n value: (key: string, defaultValue?: any) => any;\n /**\n * Return a shallow copy of all workspace field values keyed by their stable\n * keys.\n *\n * @example\n * const allFields = utils.workspace.values();\n */\n values: () => TaskWorkspaceFields;\n };\n /**\n * Currency-safe money math. Money values are `{ amount: number; currency: string }`\n * with `amount` in **minor units** (e.g. cents). Every operation preserves the\n * currency; mixing currencies in `add`/`subtract`/`sum` throws.\n *\n * @example\n * const subtotal = Sdk.utils.money.sum(items.map((i) => i.total));\n * const tax = Sdk.utils.money.percentage(subtotal, 8.875);\n * const grand = Sdk.utils.money.add(subtotal, tax);\n * return <div>{Sdk.utils.money.format(grand)}</div>;\n *\n * @remarks Math operations propagate nullish money inputs by returning `null`;\n * they never fabricate a zero in a fallback currency. A currency mismatch\n * between two real values remains a programmer error and throws.\n */\n money: {\n /**\n * Add two money values. Nullish operands return `null`; two real values with\n * different currencies throw.\n * @example\n * const total = Sdk.utils.money.add({ amount: 1000, currency: 'usd' }, { amount: 250, currency: 'usd' });\n */\n add: (a: TaskMoneyInput, b: TaskMoneyInput) => TaskMoney | null;\n /**\n * Subtract `b` from `a`. Nullish operands return `null`; two real values with\n * different currencies throw.\n * @example\n * const change = Sdk.utils.money.subtract({ amount: 2000, currency: 'usd' }, { amount: 1500, currency: 'usd' });\n */\n subtract: (a: TaskMoneyInput, b: TaskMoneyInput) => TaskMoney | null;\n /**\n * Multiply a money value by a plain number, rounding to whole minor units.\n * A nullish money value returns `null`.\n * @example\n * const lineTotal = Sdk.utils.money.multiply({ amount: 1299, currency: 'usd' }, 3);\n */\n multiply: (value: TaskMoneyInput, factor: number) => TaskMoney | null;\n /**\n * Divide a money value by a plain number, rounding to whole minor units.\n * Dividing by `0` yields a zero amount in the same currency with a diagnostic\n * warning. A nullish money value returns `null`.\n * @example\n * const perPerson = Sdk.utils.money.divide({ amount: 10000, currency: 'usd' }, 4);\n */\n divide: (value: TaskMoneyInput, divisor: number) => TaskMoney | null;\n /**\n * Take a percentage of a money value (e.g. `8.875` for 8.875%), rounding to\n * whole minor units. A nullish money value returns `null`.\n * @example\n * const tax = Sdk.utils.money.percentage({ amount: 10000, currency: 'usd' }, 8.875);\n */\n percentage: (value: TaskMoneyInput, percent: number) => TaskMoney | null;\n /**\n * Sum a list of money values. Nullish/non-money entries are skipped with a\n * diagnostic warning; an empty list falls back to the workspace\n * `default_currency` (or `usd`) with a zero amount. Throws on mixed\n * currencies.\n * @example\n * const subtotal = Sdk.utils.money.sum(rows.map((r) => r.amount));\n */\n sum: (values: TaskMoneyInput[]) => TaskMoney;\n /**\n * Format a money value for display via `Intl.NumberFormat` currency style.\n * Nullish or malformed input returns `null` and warns in\n * development/editor mode.\n * @returns A localized currency string, e.g. `\"$1,234.50\"`.\n * @example\n * const label = Sdk.utils.money.format({ amount: 123450, currency: 'usd' });\n */\n format: (value: TaskMoneyInput, options?: TaskMoneyFormatOptions) => string | null;\n };\n /**\n * Coerce raw values into Diagonal-shaped objects.\n *\n * @example\n * const price = Sdk.utils.cast.toMoney(19.99, 'usd');\n */\n cast: {\n /**\n * Convert a number/string into a money value `{ amount, currency }` in minor\n * units (currency defaults to `usd`). Already-money values pass through\n * unchanged; empty/boolean/non-numeric input yields a zero amount.\n * @example\n * const amount = Sdk.utils.cast.toMoney('19.99'); // { amount: 1999, currency: 'usd' }\n */\n toMoney: (value: any, currency?: string) => TaskMoney;\n };\n /**\n * Format location field values as display text.\n *\n * @example\n * const address = Sdk.utils.location.format(business.address); // '123 Main St, Austin, TX 78701'\n */\n location: {\n /**\n * Format a location value as a single-line address. The establishment name\n * (`display_name`) wins when the place has one; otherwise the street, city,\n * state, postal code, and country parts are joined with `', '`. Pass\n * `{ format: 'address' }` to skip `display_name` and always get the assembled\n * postal address. Nullish or malformed values return `null` and warn\n * in development/editor mode.\n * @example\n * const venue = Sdk.utils.location.format(event.venue);\n * const postal = Sdk.utils.location.format(event.venue, { format: 'address' });\n */\n format: (value: any, options?: TaskLocationFormatOptions) => string | null;\n };\n /**\n * Format phone numbers for display.\n *\n * @example\n * const display = Sdk.utils.phone.format('+14155552671'); // '+1 415 555 2671'\n */\n phone: {\n /**\n * Format a phone number string. Default format is `international`. Nullish,\n * invalid, or unparseable numbers return `null` and warn in\n * development/editor mode.\n * @example\n * const national = Sdk.utils.phone.format('+14155552671', { format: 'national' });\n */\n format: (value: any, options?: TaskPhoneFormatOptions) => string | null;\n };\n /**\n * Format plain numbers for display using the browser locale.\n *\n * @example\n * const display = Sdk.utils.number.format(1234.5); // '1,234.50'\n */\n number: {\n /**\n * Format a number (or numeric string) with `Intl.NumberFormat` and the\n * browser locale. `decimals` defaults to `2`. Nullish, non-finite, or\n * otherwise malformed input returns `null` and warns in\n * development/editor mode.\n * @example\n * const whole = Sdk.utils.number.format(1234.5, { decimals: 0 }); // '1,235'\n */\n format: (value: any, options?: TaskNumberFormatOptions) => string | null;\n };\n /**\n * Date math on ISO date strings (`yyyy-MM-dd`), powered by `date-fns`. Use\n * this for calendar-day operations; for full datetimes use `Sdk.utils.time`.\n *\n * @example\n * const dueOn = Sdk.utils.date.add(Sdk.utils.date.current(), 30, 'days');\n */\n date: {\n /**\n * Add `amount` of `unit` to a date and return a `yyyy-MM-dd` string. Nullish\n * or empty input returns `null`.\n * @example\n * const next = Sdk.utils.date.add('2026-01-01', 2, 'weeks'); // '2026-01-15'\n */\n add: (value: string | null | undefined, amount: number, unit: TaskDateAddUnit) => string | null;\n /**\n * Subtract `amount` of `unit` from a date and return a `yyyy-MM-dd` string.\n * Nullish or empty input returns `null`.\n * @example\n * const prior = Sdk.utils.date.subtract('2026-01-15', 1, 'months'); // '2025-12-15'\n */\n subtract: (\n value: string | null | undefined,\n amount: number,\n unit: TaskDateAddUnit\n ) => string | null;\n /**\n * Difference between two dates (`b - a`) in `unit` (default `days`). Nullish\n * or empty input returns `null`.\n * @returns A signed whole number; negative when `b` is before `a`.\n * @example\n * const days = Sdk.utils.date.diff('2026-01-01', '2026-01-31'); // 30\n */\n diff: (\n a: string | null | undefined,\n b: string | null | undefined,\n unit?: TaskDateAddUnit\n ) => number | null;\n /**\n * Start of the given calendar boundary as `yyyy-MM-dd`. Weeks start on\n * Monday. Nullish or empty input returns `null`.\n * @example\n * const monday = Sdk.utils.date.startOf('2026-01-15', 'week');\n */\n startOf: (value: string | null | undefined, unit: TaskDateBoundaryUnit) => string | null;\n /**\n * End of the given calendar boundary as `yyyy-MM-dd`. Weeks end on Sunday.\n * Nullish or empty input returns `null`.\n * @example\n * const monthEnd = Sdk.utils.date.endOf('2026-01-15', 'month'); // '2026-01-31'\n */\n endOf: (value: string | null | undefined, unit: TaskDateBoundaryUnit) => string | null;\n /**\n * Format a date with a `date-fns` pattern (default `yyyy-MM-dd`). Nullish,\n * empty, or malformed input returns `null` and warns in\n * development/editor mode.\n * @example\n * const label = Sdk.utils.date.format('2026-01-15', 'MMM d, yyyy'); // 'Jan 15, 2026'\n */\n format: (value: string | null | undefined, format?: string) => string | null;\n /**\n * Today's date as a `yyyy-MM-dd` string.\n * @example\n * const today = Sdk.utils.date.current();\n */\n current: () => string;\n };\n /**\n * Datetime math on ISO 8601 strings, powered by `date-fns`. Mirrors\n * `Sdk.utils.date` but adds sub-day units (seconds/minutes/hours) and outputs\n * full datetimes.\n *\n * Stored datetimes are always UTC, and `format` renders them in the workspace\n * timezone by default. Pass an IANA timezone to `format` to render in a\n * different one. Never add or subtract fixed offsets to shift displayed times.\n *\n * @example\n * const reminder = Sdk.utils.time.add(Sdk.utils.time.current(), 90, 'minutes');\n */\n time: {\n /**\n * Add `amount` of `unit` to a datetime and return an ISO 8601 string. Nullish\n * or empty input returns `null`.\n * @example\n * const later = Sdk.utils.time.add('2026-01-01T09:00:00Z', 3, 'hours');\n */\n add: (value: string | null | undefined, amount: number, unit: TaskTimeAddUnit) => string | null;\n /**\n * Subtract `amount` of `unit` from a datetime and return an ISO 8601 string.\n * Nullish or empty input returns `null`.\n * @example\n * const earlier = Sdk.utils.time.subtract('2026-01-01T09:00:00Z', 30, 'minutes');\n */\n subtract: (\n value: string | null | undefined,\n amount: number,\n unit: TaskTimeAddUnit\n ) => string | null;\n /**\n * Difference between two datetimes (`b - a`) in `unit` (default `minutes`).\n * Nullish or empty input returns `null`.\n * @returns A signed whole number; negative when `b` is before `a`.\n * @example\n * const minutes = Sdk.utils.time.diff('2026-01-01T09:00:00Z', '2026-01-01T10:30:00Z'); // 90\n */\n diff: (\n a: string | null | undefined,\n b: string | null | undefined,\n unit?: TaskTimeAddUnit\n ) => number | null;\n /**\n * Start of the given datetime boundary as an ISO 8601 string. Weeks start on\n * Monday. Nullish or empty input returns `null`.\n * @example\n * const dayStart = Sdk.utils.time.startOf('2026-01-15T13:45:00Z', 'day');\n */\n startOf: (value: string | null | undefined, unit: TaskTimeBoundaryUnit) => string | null;\n /**\n * End of the given datetime boundary as an ISO 8601 string. Weeks end on\n * Sunday. Nullish or empty input returns `null`.\n * @example\n * const hourEnd = Sdk.utils.time.endOf('2026-01-15T13:45:00Z', 'hour');\n */\n endOf: (value: string | null | undefined, unit: TaskTimeBoundaryUnit) => string | null;\n /**\n * Format a datetime with a `date-fns` pattern (default\n * `yyyy-MM-dd'T'HH:mm:ssXXX`). In pages the default rendering timezone is\n * the workspace timezone; pass `timezone` to render in a different one.\n * Never add or subtract fixed offsets for display. Nullish or malformed\n * input returns `null` and warns in development/editor mode.\n * @example\n * const label = Sdk.utils.time.format('2026-01-15T13:45:00Z', 'MMM d, h:mm a', Sdk.utils.timezone.get());\n */\n format: (value: string | null | undefined, format?: string, timezone?: string) => string | null;\n /**\n * The current datetime as an ISO 8601 string.\n * @example\n * const now = Sdk.utils.time.current();\n */\n current: () => string;\n };\n /**\n * Timezone display + interpretation helpers. Timezone names use IANA values\n * such as `America/New_York`.\n *\n * Use `Sdk.utils.timezone.get()` in pages to read the workspace timezone.\n * `Sdk.utils.time.format` already defaults to it; pass it explicitly to keep\n * the intent visible at the call site, and pass a different IANA zone only\n * when the page genuinely renders one. Use `interpret` to convert\n * user-entered wall-clock datetimes into UTC for storage.\n *\n * @example\n * const label = Sdk.utils.timezone.format('America/New_York', 'abbreviation'); // 'EST' / 'EDT'\n * const tz = Sdk.utils.timezone.get();\n * const utc = Sdk.utils.timezone.interpret('2026-01-15T09:00:00', tz);\n */\n timezone: {\n /**\n * Produce a display label for a timezone. `full` (default) gives a name +\n * offset, `offset` gives `UTC\xB1HH:mm`, `abbreviation` gives e.g. `EDT`, and\n * `city` gives the trailing city name. Nullish or invalid input returns\n * `null` and warns in development/editor mode.\n * @example\n * const city = Sdk.utils.timezone.format('America/New_York', 'city'); // 'New York'\n */\n format: (\n timezone: string | null | undefined,\n format?: 'full' | 'offset' | 'abbreviation' | 'city'\n ) => string | null;\n /**\n * Interpret a wall-clock local date/datetime as occurring in `timezone` and\n * return the equivalent UTC ISO 8601 string. A nullish or empty value\n * propagates as `null`; a nullish/empty timezone returns `null` with a\n * warning. Malformed non-null values or non-empty invalid timezone names\n * still throw.\n * @example\n * const utc = Sdk.utils.timezone.interpret('2026-01-15T09:00:00', 'America/New_York');\n */\n interpret: (value: any, timezone: string | null | undefined) => string | null;\n /**\n * Return the workspace's default IANA timezone. In non-page contexts with no\n * workspace, this falls back to UTC.\n * @example\n * const tz = Sdk.utils.timezone.get();\n */\n get: () => string;\n };\n /**\n * Render rich-text fields stored as TipTap JSON to a string.\n *\n * @example\n * const text = Sdk.utils.tiptap.parse({ content: record.notes });\n */\n tiptap: {\n /**\n * Convert TipTap JSON to plain text (default) or HTML (`html: true`).\n * `oneLine` collapses to a single line; `placeholder` is substituted when\n * empty. Empty/falsy content returns an empty string.\n * @example\n * const html = Sdk.utils.tiptap.parse({ content: record.body, html: true });\n */\n parse: (value: TaskTiptapParseInput) => string;\n };\n /**\n * Generate identifiers and random text.\n *\n * @example\n * const id = Sdk.utils.text.randomUuid();\n */\n text: {\n /**\n * Generate a time-ordered UUID (v7).\n * @example\n * const id = Sdk.utils.text.randomUuid();\n */\n randomUuid: () => string;\n /**\n * Generate a random string. Always includes letters; numbers and special\n * characters are opt-in. Defaults: `length=16`, `includeNumbers=false`,\n * `includeSpecial=false`.\n * @example\n * const token = Sdk.utils.text.generate({ length: 24, includeNumbers: true });\n */\n generate: (options?: TaskTextGenerateOptions) => string;\n };\n /**\n * Copy text to the system clipboard.\n *\n * @example\n * <Sdk.Components.Button onClick={() => Sdk.utils.clipboard.copy(record.id)}>\n * Copy ID\n * </Sdk.Components.Button>\n */\n clipboard: {\n /**\n * Write a string to the system clipboard and show a success/failure toast.\n *\n * Pass `options.message` to customize the success toast, or `options.silent`\n * to suppress it. The failure toast always fires \u2014 a denied or blocked write\n * is otherwise invisible to the caller.\n *\n * @example\n * Sdk.utils.clipboard.copy('https://example.com/share/abc');\n * @example\n * Sdk.utils.clipboard.copy(record.id, { message: 'Record ID copied' });\n */\n copy: (value: string, options?: TaskClipboardCopyOptions) => Promise<void> | void;\n };\n /**\n * Play a celebratory confetti animation \u2014 handy as an `onSuccess` callback.\n *\n * @example\n * const { form } = Sdk.hooks.useWorkflowForm({\n * workflowId: SEND_INVITE_WORKFLOW_ID,\n * initialValues: { email: '' },\n * onSuccess: () => Sdk.utils.confetti.trigger()\n * });\n */\n confetti: {\n /**\n * Trigger the confetti animation.\n * @example\n * Sdk.utils.confetti.trigger();\n */\n trigger: (options?: TaskConfettiOptions) => void;\n };\n /**\n * Show a toast notification \u2014 general-purpose UI feedback for any event\n * (a saved form, a validation error, a background action completing).\n *\n * @example\n * Sdk.utils.notify.toast('Settings saved');\n */\n notify: {\n /**\n * Display a toast. Pass `options.variant` to color it\n * (`success` | `error` | `warning` | `info`) and `options.description` for a\n * second line of supporting text.\n * @example\n * Sdk.utils.notify.toast('Invite sent', { variant: 'success' });\n */\n toast: (message: string, options?: TaskNotifyToastOptions) => void;\n };\n /**\n * Evaluate a saved-filter predicate against a record in the browser \u2014 the same\n * `{ operator, conditions: [{ left, operator, right }, ...] }` shape produced\n * by the filter builder UI. Useful for conditionally showing/hiding UI.\n *\n * @example\n * const shouldShow = Sdk.utils.guard.filterCheck(record, savedFilter);\n * if (!shouldShow) return null;\n */\n guard: {\n /**\n * Run a filter predicate against `input` and return whether it matches. When\n * a condition's `left` is a string it is resolved as a (dot-notation) field\n * path on `input`; other `left` values are used as-is.\n * @example\n * const isActive = Sdk.utils.guard.filterCheck(record, {\n * operator: 'and',\n * conditions: [{ left: 'status', operator: 'eq', right: 'active' }]\n * });\n */\n filterCheck: (input: any, predicate: any) => boolean;\n };\n /**\n * Interpret failures from `Sdk.api`, resource operations, and SDK data hooks\n * without comparing raw HTTP status codes.\n *\n * @example\n * try {\n * await Sdk.resource(INVOICES_RESOURCE_ID).get(recordId);\n * } catch (error) {\n * if (Sdk.utils.error.isForbidden(error)) return showAccessDenied();\n * if (Sdk.utils.error.isNotFound(error)) return showMissingRecord();\n * }\n */\n error: {\n /** Return the parsed API error body, or `null` when none is available. */\n extractApiErrorBody: (error: any) => TaskApiErrorBody | null;\n /** Return the API body's message, or the supplied fallback. */\n apiErrorMessage: (error: any, fallback?: string) => string;\n /** Return whether the request failed field validation (HTTP 422). */\n isValidationError: (error: any) => boolean;\n /** Return whether the request failed because of a domain conflict (HTTP 409). */\n isConflict: (error: any) => boolean;\n /** Return whether the acting identity is not permitted to access the target (HTTP 403). */\n isForbidden: (error: any) => boolean;\n /** Return whether the requested target does not exist (HTTP 404). */\n isNotFound: (error: any) => boolean;\n };\n};\n\ntype TaskPageTarget =\n | string\n | { id: string; params?: Record<string, any> }\n | null\n | undefined;\n\ntype TaskFileTarget = string | { id: string } | null | undefined;\n\ntype TaskFileUrlOptions = {\n /**\n * Requested output width in image pixels, not CSS pixels. Must be a positive\n * integer. The server returns the narrowest available width that covers it,\n * or the original file when the file cannot be resized.\n */\n width?: number;\n\n /**\n * Quality for transformed public images, from 1 to 100. Defaults to 85.\n * Without width, page images keep automatic responsive sizing. Original\n * delivery ignores quality. Invalid values use the default and warn.\n */\n quality?: number;\n};\n\ntype SdkHelpers = {\n /**\n * Read a value from the page's input data by key. Dot paths reach nested\n * values (`'record.id'`). Returns `defaultValue` (default `null`) when the\n * key is absent. Optional page inputs require an explicit, type-compatible\n * default value.\n */\n getInput<TKey extends string>(\n key: PageContextRequiredKey<PageInputCatalog, TKey>\n ): PageContextValue<PageInputCatalog, TKey>;\n getInput<TKey extends string>(\n key: PageContextKey<PageInputCatalog, TKey>,\n defaultValue: PageContextPresentValue<PageInputCatalog, TKey>\n ): PageContextPresentValue<PageInputCatalog, TKey>;\n\n /**\n * Alias of {@link SdkHelpers.getInput} for reads inside the rendered\n * component. The page re-renders with fresh input data, so component reads\n * stay current.\n */\n useInput<TKey extends string>(\n key: PageContextRequiredKey<PageInputCatalog, TKey>\n ): PageContextValue<PageInputCatalog, TKey>;\n useInput<TKey extends string>(\n key: PageContextKey<PageInputCatalog, TKey>,\n defaultValue: PageContextPresentValue<PageInputCatalog, TKey>\n ): PageContextPresentValue<PageInputCatalog, TKey>;\n\n /**\n * Every raw route parameter from the current request URL, keyed by param\n * name. Always an object \u2014 `{}` when the page's route has no params.\n */\n getRouteParams: () => keyof PageRouteParamCatalog extends never\n ? Record<string, any>\n : PageRouteParamCatalog;\n\n /**\n * A single raw route parameter value \u2014 e.g. the `{member}` URL segment as a\n * string. Available even when the param has no route binding configured.\n */\n getRouteParam<TKey extends string>(\n key: PageContextKey<PageRouteParamCatalog, TKey>,\n defaultValue?: any\n ): PageContextValue<PageRouteParamCatalog, TKey>;\n\n /**\n * The hydrated record bound to a route param via a route binding, or\n * `undefined` when that param has no binding configured. This is the\n * resolved resource that {@link SdkHelpers.getRouteParam} only exposes the\n * raw value of.\n */\n getRouteBinding<TKey extends string>(\n key: PageContextKey<PageRouteBindingCatalog, TKey>,\n defaultValue?: any\n ): PageContextValue<PageRouteBindingCatalog, TKey>;\n};\n\ntype PageContextRequiredKey<TCatalog, TKey extends string> = keyof TCatalog extends never\n ? TKey\n : string extends TKey\n ? TKey\n : TKey extends Extract<keyof TCatalog, string>\n ? {} extends Pick<TCatalog, Extract<TKey, keyof TCatalog>>\n ? never\n : TKey\n : never;\n\ninterface PageInputCatalog {}\n\ntype PageContextValue<TCatalog, TKey extends string> = keyof TCatalog extends never\n ? any\n : TKey extends keyof TCatalog\n ? TCatalog[TKey]\n : any;\n\ntype PageContextKey<TCatalog, TKey extends string> = keyof TCatalog extends never\n ? TKey\n : TKey extends Extract<keyof TCatalog, string>\n ? TKey\n : string extends TKey\n ? TKey\n : never;\n\ntype PageContextPresentValue<TCatalog, TKey extends string> = Exclude<\n PageContextValue<TCatalog, TKey>,\n null | undefined\n>;\n\ninterface PageRouteParamCatalog {}\n\ninterface PageRouteBindingCatalog {}\n\ntype SdkContract = {\n /** Namespace of built-in Diagonal SDK components (`Sdk.Components.Button`, `Card`, \u2026). */\n Components: ComponentRegistry;\n\n /**\n * Drag-and-drop primitives and high-level sortable collections. Use `SortableList` for one\n * ordered collection and `SortableGroups` for movement within and between groups. For\n * sortable resource records, pass the query's records, resource handle, and stored Number\n * field. The high-level components own the\n * sensors, keyboard controls, drag handles, item transforms, changed-index calculation,\n * optimistic update, persistence state, and rollback. For resource records, pass the\n * resource handle and stored Number field; the component sorts nulls last, breaks ties\n * by record id, and bulk-updates only records whose positions changed. No resource\n * capability or dedicated reorder endpoint is required.\n *\n * `reorder(items, activeId, overId)` is the same pure move calculation without UI. It\n * returns both the complete `items` array and only the `changes` whose indices moved.\n * Positions are zero-based. Every other member exposes the underlying dnd-kit primitive\n * for custom drag-and-drop experiences outside sortable resource records.\n *\n * @example\n * ```tsx\n * const students = Sdk.resource('students');\n *\n * return function StudentRanking() {\n * const { data } = Sdk.hooks.useRecords(students, { sort: 'rank', perPage: 100 });\n *\n * return (\n * <Sdk.Dnd.SortableList\n * items={data}\n * resource={students}\n * sortField={students.fields.rank}\n * renderItem={(student) => <span>{student.name}</span>}\n * />\n * );\n * }\n * ```\n */\n Dnd: SdkDnd;\n\n /**\n * Namespace of reactive data hooks for analytics, API requests, URL query parameters,\n * reading and writing records, building tables and calendars, and reading app chrome\n * state (sidebar, theme). See {@link SdkHooks} for the full interface.\n *\n * **Convention:** hooks belong here, not at the top level of `SdkContract`.\n * `useForm` and the deprecated top-level calendar aliases remain for backwards compatibility.\n *\n * @example\n * ```tsx\n * const students = Sdk.resource('...');\n * const { data } = Sdk.hooks.useRecord(students, recordId);\n * ```\n *\n * @example\n * ```tsx\n * function PaidInvoicesTile() {\n * const invoices = Sdk.resource('...');\n * const { data, isLoading, isError } = Sdk.hooks.useAnalytics(invoices, 'kpi', {\n * metrics: [{ name: 'paid_count', operation: 'count' }]\n * });\n *\n * if (isLoading) return <Sdk.Components.Skeleton />;\n * if (isError) return <Sdk.Components.Alert>Could not load invoices.</Sdk.Components.Alert>;\n * return <Sdk.Components.Card>{Sdk.utils.number.format(data?.paid_count ?? 0, { decimals: 0 })} paid</Sdk.Components.Card>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * const [params, setParams] = Sdk.hooks.useQueryParams({\n * search: { default: '' },\n * page: { type: 'number', default: 1 },\n * active: { type: 'boolean', default: true },\n * tags: { type: 'array', default: [] }\n * });\n * ```\n */\n hooks: SdkHooks;\n\n /**\n * Namespace of page input + route parameter readers \u2014 `getInput`, `useInput`,\n * `getRouteParams`, `getRouteParam`, and `getRouteBinding`. See {@link SdkHelpers}.\n *\n * @remarks\n * `getInput(key, defaultValue?)` reads a value from the page's input data\n * (workflow-provided values, route params, and route-binding records), supporting dot\n * paths such as `'record.id'`; `useInput` is its alias for reads inside the rendered\n * component. For a page routed at `/members/{member}`, `getRouteParam('member')`\n * returns the raw URL segment, `getRouteParams()` every raw param keyed by name, and\n * `getRouteBinding('member')` the hydrated record when a route binding is configured\n * (`undefined` otherwise). Raw params also remain readable via\n * `getInput('params.member')`, but prefer the dedicated route helpers. Never derive\n * route segments from `window.location` \u2014 the editor preview serves drafts at\n * `/preview/pages/{id}` with params in the query string, so pathname parsing renders the\n * page's not-found/empty state in preview even though the params were delivered. `params`\n * is a reserved key in page output; a page output variable named `params` takes precedence.\n * In the page editor and save-time gate, literal input/param/binding keys and bound-record\n * fields are inferred from the selected page. Runtime-computed string keys retain the\n * permissive dynamic fallback.\n *\n * @example\n * ```tsx\n * return function Page() {\n * const recordId = Sdk.helpers.useInput('record_id');\n * const member = Sdk.helpers.getRouteBinding('member');\n *\n * return <p>{member ? `Editing ${member.name}` : `Record ${recordId}`}</p>;\n * };\n * ```\n */\n helpers: SdkHelpers;\n\n /** Namespace of workspace-authored shared components (`Sdk.Custom.StatusBadge`, \u2026). */\n Custom: CustomComponentRegistry;\n\n /**\n * Look up a workspace resource by its portable slug or UUID and return a\n * {@link SdkResource} bundling metadata (`fields`, `display_field_key`, \u2026) with the\n * record operations `list`, `search`, bounded `searchAll`, `count`, `get`, `create`,\n * `update`, `updateMany`, `delete`, and `restore` (plus `invalidate` to refresh cached record data),\n * the record API endpoint URLs under `urls` (`index`, `search`, `count`, `store`,\n * `bulk`, `show`, `update`, `delete`, `restore`) \u2014 see {@link ResourceUrls} \u2014 and aggregate\n * analytics under `analytics` (`kpi`, `timeSeries`, `leaderboard`, `distribution`) \u2014\n * see {@link SdkResourceAnalytics}.\n * Use `can(operation)` to check the resource's server-resolved policy scope before\n * rendering controls for those operations.\n *\n * @remarks\n * `fields` is a keyed object (`resource.fields.<key>`), not an array \u2014 `.map()`/`.filter()`\n * on it fails to compile, so name each field key you use.\n * Prefer the resource slug shown in the workspace SDK catalog (for example, `students`).\n * Slugs make page source portable across workspaces and packages with matching resource\n * names. UUIDs remain supported as a fallback. Display names are not accepted. Throws if\n * the slug or UUID is unknown; the error lists the available slugs and ids.\n * The SDK renames `perPage` \u2192 `per_page`, `sort` \u2192 `$sort` (accepting `'name'`,\n * `'-created_at'`, `['a', '-b']`, or `{ created_at: -1 }`), and `filter` \u2192 `$filter`\n * before calling the records API. The rename does NOT restructure `filter`: pass a\n * structured filter tree (see {@link ResourceSearchBody}) \u2014 flat maps like\n * `{ status: 'paid' }` are rejected with a 422. Filtering and sorting are `search`-only;\n * `list` throws if given them, because the list endpoint would ignore them. Use\n * `count` for exact totals without rows and `analytics` for server-side numeric\n * aggregates. `searchAll` is only for a bounded client-side aggregate that needs row\n * values; its required `maxRecords` cap warns when the result is incomplete. Resource\n * ordering is explicit: query the desired sort field and pass that same field plus the\n * resource handle to `Sdk.Dnd.SortableList`. Use the bundled handle methods directly;\n * do not monkey-patch or extend handles with your own helpers.\n * `can(operation)` checks resource-policy access, not permission membership; use\n * {@link SdkContract.permissions} for permission ids and slugs. Both client-side\n * helpers are UI conveniences and never replace server-side enforcement.\n * On public pages, `resource.get(recordId)` rejects with forbidden when the guest\n * lacks record policy access and not-found only when the record is absent. Classify\n * the caught error with `Sdk.utils.error.isForbidden` / `isNotFound`; do not infer\n * not-found from missing data.\n * On a Booking resource, `create` and `update` reject a booking that does not fit with a\n * 422 whose message is under `$capacity`: read it with\n * `Sdk.utils.error.extractApiErrorBody(error)?.errors?.$capacity?.[0]` and show it. A\n * repeating booking whose first date fits resolves, and its `meta.series.waiting` lists each\n * later date that waits for rescheduling, so the page can show \"4 dates need rescheduling\"\n * with an action for each `record_id`. `meta.booking.replayed` marks a retried create that returned the first booking.\n *\n * @returns A {@link SdkResource} \u2014 resource metadata plus the record CRUD methods.\n *\n * @example\n * ```tsx\n * return function Page() {\n * const students = Sdk.resource('students');\n * const [rows, setRows] = React.useState([]);\n *\n * React.useEffect(() => {\n * students.list({ perPage: 50 }).then((r) => setRows(r.data ?? []));\n * }, []);\n *\n * return (\n * <Sdk.Components.List\n * items={rows.map((r) => ({ id: r.id, title: r[students.display_field_key] }))}\n * />\n * );\n * };\n * ```\n *\n * @example\n * ```ts\n * const result = await Sdk.resource('invoices').create({\n * amount: 12500,\n * currency: 'usd',\n * client_id: '...'\n * });\n * ```\n *\n * @example\n * ```ts\n * // Filtered search \u2014 `filter` must be a structured tree\n * // `{ operator: 'and' | 'or', conditions: [...] }` of leaf conditions\n * // (`{ field, operator, value }`) and/or nested groups. Operators are the\n * // symbol or snake_case enum values (`=`, `!=`, `>=`, `contains`, `in`,\n * // `between`, `is_null`, \u2026); flat maps like `{ status: 'paid' }` and word\n * // operators like `eq` are rejected with a 422.\n * const invoices = Sdk.resource('invoices');\n * const paid = await invoices.search({\n * filter: {\n * operator: 'and',\n * conditions: [\n * { field: 'status', operator: '=', value: 'paid' },\n * { field: 'total', operator: '>=', value: 100 }\n * ]\n * },\n * sort: '-created_at',\n * perPage: 25\n * });\n * ```\n *\n * @example\n * ```ts\n * const invoices = Sdk.resource('019286e1-7c0a-7a3e-8f21-3b9d5e6a1c4d');\n * const filter = {\n * operator: 'and',\n * conditions: [{ field: 'status', operator: '=', value: 'paid' }]\n * };\n * const paidCount = await invoices.count({ filter });\n * const paidRows = await invoices.searchAll({ filter, maxRecords: 500 });\n * ```\n *\n * @example\n * ```ts\n * // Restore a soft-deleted record.\n * const invoices = Sdk.resource('invoices');\n * await invoices.restore('019286e1-9f2c-7b10-bc44-1a2b3c4d5e6f');\n * ```\n *\n * @example\n * ```ts\n * // Aggregate analytics \u2014 headline KPIs plus a monthly time series, without\n * // building a workflow per chart. See {@link SdkResourceAnalytics}.\n * const invoices = Sdk.resource('invoices');\n * const kpis = await invoices.analytics.kpi({\n * metrics: [{ name: 'revenue', operation: 'sum', field: 'amount' }]\n * });\n * const monthly = await invoices.analytics.timeSeries({\n * date_field: 'created_at',\n * granularity: 'month',\n * metrics: [{ name: 'revenue', operation: 'sum', field: 'amount' }]\n * });\n * ```\n */\n resource<TId extends string>(id: DynamicResourceId<TId>): SdkResource;\n resource<K extends keyof ResourceCatalog>(id: K): ResourceCatalog[K];\n resource(id: string): SdkResource;\n\n /**\n * A page of this workspace by its stable id. The code editor shows and\n * completes the page's name in this position and stores the id, so a renamed\n * page keeps every reference. Use the handle where a page id or URL is\n * expected.\n *\n * @example\n * const invoices = Sdk.page(INVOICES_PAGE_ID);\n * return <a href={invoices.url() ?? '#'}>Invoices</a>;\n * @example\n * Sdk.page(INVOICE_DETAIL_PAGE_ID).navigate({ invoice: row.id });\n * @example\n * <Sdk.Components.PageLink pageId={Sdk.page(INVOICES_PAGE_ID).id}>Invoices</Sdk.Components.PageLink>\n */\n page(id: PageReference): SdkPageHandle;\n\n /**\n * Access a workspace resource by id or slug, or `null` when the current viewer cannot\n * access it.\n *\n * @remarks\n * The nullable twin of {@link SdkContract.resource}. Reach for it only when access is\n * genuinely viewer-dependent (a public page that also serves logged-in members) and the\n * code path is imperative. For data hooks, pass the id or slug straight to\n * `Sdk.hooks.useRecords` / `useRecord` / `useInfiniteRecords` instead; they stay idle\n * for viewers without access and need no null check.\n *\n * @example\n * ```ts\n * const contacts = Sdk.resourceOrNull('business-contacts');\n * if (contacts) {\n * await contacts.create({ status: 'pending' });\n * }\n * ```\n */\n resourceOrNull<TId extends string>(id: DynamicResourceId<TId>): SdkResource | null;\n resourceOrNull<K extends keyof ResourceCatalog>(id: K): ResourceCatalog[K] | null;\n resourceOrNull(id: string): SdkResource | null;\n\n /**\n * Look up a workspace resource's stable id by its slug.\n *\n * @remarks\n * This lookup is policy-independent and returns identification only. It can identify a\n * resource that the current viewer cannot access. It does not expose resource metadata,\n * fields, capabilities, policies, records, or record operations. Continue to use\n * {@link SdkContract.resource} for policy-checked data access. Static slug references are\n * validated when page code is saved.\n *\n * @example\n * ```tsx\n * const isContact = Sdk.identity?.resource_id === Sdk.resourceId('contacts');\n * return isContact ? <ContactHome /> : <DefaultHome />;\n * ```\n */\n resourceId(resourceSlug: string): string;\n\n /**\n * List every resource id available to the current user through {@link SdkContract.resource}.\n *\n * @returns The array of resource ids available to the current user.\n *\n * @example\n * ```ts\n * const ids = Sdk.resourceIds();\n * ```\n */\n resourceIds(): string[];\n\n /**\n * The current workspace record, its field definitions under `fields`, and the\n * load-time value snapshot under `values`.\n *\n * @remarks\n * `workspace.fields` is the definition catalog keyed by stable workspace field key;\n * pass an entry to `Sdk.Components.FormFieldInput` or `Sdk.Components.WorkspaceFieldsForm`.\n * `workspace.values` is a snapshot taken when the page loaded and is deprecated for\n * reads: use `Sdk.hooks.useWorkspaceValues([...keys])`, which updates after a save on\n * this page. `null` when no workspace is resolved, so guard for null/missing\n * (`Sdk.workspace?.fields.support_email`).\n *\n * @example\n * ```tsx\n * const name = Sdk.workspace?.name;\n * const { support_email } = Sdk.hooks.useWorkspaceValues(['support_email']);\n * const supportEmailField = Sdk.workspace?.fields.support_email;\n * ```\n */\n workspace: SdkWorkspace;\n\n /**\n * Look up a navigation menu by its id and return its metadata\n * plus resolved items.\n *\n * @remarks\n * Throws if the id is unknown \u2014 the error lists available ids (also via\n * {@link SdkContract.navigationMenuIds}). Each `items` entry exposes `title`, a resolved\n * `url` (`null` for `dropdown` containers), `type.value`\n * (`'page' | 'url' | 'external_link' | 'dropdown' | 'system_config'`), optional `icon`,\n * `sort_order`, and nested `children` (for `dropdown` items). Prefer\n * `Sdk.Components.Link` / `ButtonLink` for individual nav items and\n * `Sdk.Components.DropdownMenu` for dropdown containers.\n *\n * @returns A {@link SdkNavigationMenu} \u2014 `{ id, name, items }`.\n *\n * @example\n * ```tsx\n * const menu = Sdk.navigationMenu(MAIN_NAVIGATION_MENU_ID);\n * return (\n * <Sdk.Components.List\n * items={menu.items.map((item) => ({\n * id: item.id,\n * title: item.title,\n * href: item.url ?? '#'\n * }))}\n * />\n * );\n * ```\n */\n navigationMenu(id: string): SdkNavigationMenu;\n\n /**\n * List every available navigation menu id for {@link SdkContract.navigationMenu}.\n *\n * @returns The array of navigation menu ids.\n *\n * @example\n * ```ts\n * const ids = Sdk.navigationMenuIds();\n * ```\n */\n navigationMenuIds(): string[];\n\n /**\n * Hook wrapping Inertia's `useForm` for plain Inertia\n * form submissions to your own endpoints.\n *\n * @remarks\n * Same return shape as Inertia's `useForm` \u2014 `.data`, `.errors`, `.processing`,\n * `.setData(...)`, `.submit(...)`, `.post(...)`, etc. For submitting a Diagonal workflow,\n * use `Sdk.Components.WorkflowForm` or `Sdk.hooks.useWorkflowForm` instead.\n *\n * @returns Inertia `InertiaFormProps<TForm>`.\n *\n * @example\n * ```tsx\n * return function Page() {\n * const form = Sdk.useForm({ name: '', email: '' });\n *\n * return (\n * <form\n * onSubmit={(e) => {\n * e.preventDefault();\n * form.post('/students');\n * }}\n * >\n * <Sdk.Components.FormTextInput\n * label=\"Name\"\n * value={form.data.name}\n * onChange={(v) => form.setData('name', v)}\n * />\n * <Sdk.Components.FormTextInput\n * label=\"Email\"\n * value={form.data.email}\n * onChange={(v) => form.setData('email', v)}\n * />\n * <Sdk.Components.Button disabled={form.processing} type=\"submit\">\n * Save\n * </Sdk.Components.Button>\n * </form>\n * );\n * };\n * ```\n */\n useForm: SdkUseForm;\n\n /**\n * Hook that manages calendar view mode, focal date, derived window, title string, and\n * navigation helpers. Pair with `Sdk.hooks.useCalendarEvents` and `Sdk.Components.Calendar`.\n *\n * @remarks\n * Returns `view`, `setView`, `date`, `setDate`, `windowStart`, `windowEnd`, `title`,\n * `goToToday`, `goToPrevious`, and `goToNext`. Pass `windowStart`/`windowEnd` directly to\n * `Sdk.hooks.useCalendarEvents({ from, to })`. Pass `view` and `date` directly to\n * `<Sdk.Components.Calendar view={view} date={date} />`.\n *\n * Use the pre-built header subcomponents (`CalendarTitle`, `CalendarViewSwitcher`,\n * `CalendarPrevButton`, `CalendarTodayButton`, `CalendarNextButton`) to compose a\n * standard calendar header, or build your own from the raw state values.\n *\n * @example\n * ```tsx\n * function Page() {\n * const cal = Sdk.hooks.useCalendarView({ initialView: 'month' });\n * const { events, isLoading } = Sdk.hooks.useCalendarEvents({\n * resourceId: 'YOUR_RESOURCE_ID',\n * from: cal.windowStart,\n * to: cal.windowEnd\n * });\n *\n * return (\n * <div>\n * <div className=\"flex flex-wrap items-center justify-between gap-3 mb-4\">\n * <Sdk.Components.CalendarTitle title={cal.title} isLoading={isLoading} />\n * <div className=\"flex items-center gap-2\">\n * <Sdk.Components.CalendarViewSwitcher view={cal.view} onViewChange={cal.setView} />\n * <Sdk.Components.CalendarPrevButton view={cal.view} onClick={cal.goToPrevious} />\n * <Sdk.Components.CalendarTodayButton onClick={cal.goToToday} />\n * <Sdk.Components.CalendarNextButton view={cal.view} onClick={cal.goToNext} />\n * </div>\n * </div>\n * <Sdk.Components.Calendar events={events} view={cal.view} date={cal.date} />\n * </div>\n * );\n * }\n * ```\n *\n * @deprecated Use `Sdk.hooks.useCalendarView`.\n * @aiHidden\n */\n useCalendarView: SdkHooks['useCalendarView'];\n\n /**\n * Hook that fetches calendar events for a resource and normalises them to the canonical\n * `CalendarEvent = { id, start: Date, end: Date, allDay: boolean, record }` shape.\n *\n * @remarks\n * The `record` field preserves every raw field value (including loaded relations) so your\n * `renderEvent` callback can read e.g. `event.record.title` or `event.record.status`.\n * Pass `from`/`to` directly from `Sdk.hooks.useCalendarView({ \u2026 }).windowStart` / `windowEnd`.\n *\n * @example\n * ```tsx\n * const { events, isLoading } = Sdk.hooks.useCalendarEvents({\n * resourceId: 'YOUR_RESOURCE_ID',\n * from: cal.windowStart,\n * to: cal.windowEnd,\n * relations: ['assigned_to']\n * });\n * ```\n *\n * @deprecated Use `Sdk.hooks.useCalendarEvents`.\n * @aiHidden\n */\n useCalendarEvents: SdkHooks['useCalendarEvents'];\n\n /**\n * Hook that manages an optimistic placeholder event for a calendar create flow.\n *\n * @remarks\n * Call `setPlaceholder(payload)` inside your `onSlotClick` handler to immediately\n * reflect the selected slot on the calendar while a create modal is open. Pass\n * `newEvent.mergeEvents(events)` to the `events` prop and `newEvent.placeholderTitle`\n * to the `placeholderTitle` prop on `<Sdk.Components.Calendar>`. Call\n * `clearPlaceholder()` when the modal is cancelled or after the record is saved\n * (and optionally after refetching events).\n *\n * The hook owns only the intermediate placeholder state; persisting the calendar\n * event is handled by the parent page.\n *\n * @example\n * ```tsx\n * function LessonsCalendar() {\n * const cal = Sdk.hooks.useCalendarView({ initialView: 'week' });\n * const { events, refetch } = Sdk.hooks.useCalendarEvents({\n * resourceId: 'LESSONS_RESOURCE_ID',\n * from: cal.windowStart,\n * to: cal.windowEnd\n * });\n * const newEvent = Sdk.hooks.useNewCalendarEvent({ placeholderTitle: 'New Lesson' });\n * const [isModalOpen, setIsModalOpen] = React.useState(false);\n * const [slotPayload, setSlotPayload] = React.useState(null);\n *\n * function handleSlotClick(payload) {\n * newEvent.setPlaceholder(payload);\n * setSlotPayload(payload);\n * setIsModalOpen(true);\n * }\n *\n * function handleModalCancel() {\n * newEvent.clearPlaceholder();\n * setIsModalOpen(false);\n * }\n *\n * async function handleModalSave(data) {\n * await Sdk.resource('LESSONS_RESOURCE_ID').create(data);\n * newEvent.clearPlaceholder();\n * await refetch();\n * setIsModalOpen(false);\n * }\n *\n * return (\n * <Sdk.Components.Calendar\n * events={newEvent.mergeEvents(events)}\n * placeholderTitle={newEvent.placeholderTitle}\n * view={cal.view}\n * date={cal.date}\n * onSlotClick={handleSlotClick}\n * />\n * );\n * }\n * ```\n *\n * @deprecated Use `Sdk.hooks.useNewCalendarEvent`.\n * @aiHidden\n */\n useNewCalendarEvent: SdkHooks['useNewCalendarEvent'];\n\n /**\n * Inertia router operations using the application's shared request handling\n * (`visit`, `get`, `post`, `put`, `patch`, `delete`, `reload`).\n *\n * @remarks\n * Use for SPA-style navigation and write operations that should trigger Inertia visits.\n * Prefer this over the bare Inertia `router`. Use `reload` to re-fetch the current\n * page's props in place after a mutation.\n *\n * @example\n * ```tsx\n * function SaveButton() {\n * return (\n * <Sdk.Components.Button onClick={() => Sdk.router.post('/students', { name: 'Ada' })}>\n * Save\n * </Sdk.Components.Button>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * function RefreshButton() {\n * return (\n * <Sdk.Components.Button onClick={() => Sdk.router.reload()}>\n * Refresh\n * </Sdk.Components.Button>\n * );\n * }\n * ```\n */\n router: SdkRouter;\n\n /**\n * HTTP helper wrapping the application's shared Axios client \u2014 see {@link SdkApi}.\n *\n * @remarks\n * Use `Sdk.api` for consistent response parsing and error handling.\n *\n * @remarks\n * For the current logged-in identity, read {@link SdkContract.identity} directly \u2014 there is\n * no `/api/me` endpoint to fetch.\n *\n * @remarks\n * To call a Diagonal workflow, do not hand-write its `/api/workflows/...` path. Address the\n * workflow by its stable slug or ID: resolve the URL with `Sdk.utils.workflow.url(reference, params)`,\n * check its documented `null` fallback, and pass the URL here. Alternatively, use\n * `Sdk.Components.WorkflowForm` / `Sdk.hooks.useWorkflowForm` for form submissions. To\n * send the user through a workflow that redirects, assign a resolved non-null URL to\n * `window.location.href`.\n * Id-based references survive route changes and keep page\u2192workflow calls trackable.\n *\n * @example\n * ```tsx\n * const action = Sdk.utils.workflow.url(APPROVE_INVOICE_WORKFLOW_ID);\n * if (action) await Sdk.api.post(action, { invoice_id: 'invoice-id' });\n *\n * await Sdk.api.post('/api/notifications/mark-read', {\n * ids: [Sdk.identity?.id]\n * });\n * ```\n */\n api: SdkApi;\n\n /**\n * The current logged-in identity, or `null` for anonymous requests.\n *\n * @remarks\n * The identity's underlying resource record, verbatim \u2014 the same plain shape as records\n * read via `Sdk.resource(...)` \u2014 plus `$`-prefixed platform-computed keys: `$display_name`\n * (the resource's configured display value) and `$permission_ids` / `$permission_slugs`.\n * Workspace-defined fields such as `name`/`email` are returned as-is and exist only if the\n * auth resource defines them; use `$display_name` when labeling the current user. NOT a\n * workspace user model or bespoke DTO. Secret fields (password, two-factor phone, remember\n * token) are stripped server-side. Read it directly; there is no `/api/me` endpoint to\n * fetch. Always guard for `null` to handle anonymous (unauthenticated) page views.\n * When the workspace has one authenticatable resource, its field catalog is included in\n * editor and save-time checking; multi-resource or otherwise dynamic identity contexts stay\n * permissive.\n *\n * @example\n * ```tsx\n * return function Page() {\n * const name = Sdk.identity?.$display_name ?? 'Guest';\n * return <p>Welcome, {name}</p>;\n * };\n * ```\n */\n identity: SdkIdentity;\n\n /**\n * Sign the current visitor in or out via the existing Dynamic Auth flow \u2014 see\n * {@link SdkAuth}. Use this to build fully custom login pages on public code pages;\n * `Sdk.auth.login` posts to the workspace's authenticatable resource and follows the\n * server redirect on success.\n *\n * @example\n * ```tsx\n * function LoginPage() {\n * const [identifier, setIdentifier] = React.useState('');\n * const [password, setPassword] = React.useState('');\n * const [error, setError] = React.useState(null);\n *\n * return (\n * <Sdk.Components.Button\n * onClick={() =>\n * Sdk.auth.login(\n * { identifier, password },\n * { onError: (errors) => setError(errors.identifier ?? errors.password ?? null) }\n * )\n * }\n * >\n * Sign in\n * </Sdk.Components.Button>\n * );\n * }\n * ```\n */\n auth: SdkAuth;\n\n /**\n * Permission checks for the current identity. Both permission ids and slugs are\n * accepted. Anonymous requests return `false` for every check.\n *\n * @remarks\n * These helpers are for conditionally rendering page UI. Server-side route and\n * policy enforcement remains authoritative. The raw permission arrays remain\n * available on {@link SdkContract.identity}.\n * `Sdk.Components.HasPermission` and `Sdk.Components.HasAnyPermission` gate a\n * section of JSX on the same checks.\n *\n * @example\n * ```tsx\n * if (Sdk.permissions.has('manage-users')) {\n * return <AdminTools />;\n * }\n *\n * return null;\n * ```\n */\n permissions: SdkPermissions;\n\n /**\n * Namespace of SDK utilities (`Sdk.utils.money`, `date`, `page`, \u2026); see\n * {@link SdkUtils}.\n *\n * @remarks\n * Null-safety convention: display formatters and value/URL resolvers never\n * throw for nullish or malformed field data. They return their documented\n * empty fallback (`''` or `null`) and report a diagnostic through the app\n * logger, with a console warning in development or the page editor.\n * Computation helpers propagate nullish inputs by returning `null`; genuine\n * programmer errors such as unsupported units or currency mismatches between\n * two real values may still throw with an actionable message. The collection\n * helper `money.sum` retains valid entries but reports any skipped nullish or\n * malformed entries rather than silently lowering a total.\n *\n * @example\n * ```tsx\n * const href = Sdk.utils.page.url(DETAIL_PAGE_ID, { record: row.id });\n * return href ? <a href={href}>View</a> : null;\n * ```\n */\n utils: SdkUtils;\n};\n\ntype ComponentRegistry = {\n \"Accordion\": SdkComponent<AccordionProps>;\n \"AdminNavButtons\": SdkComponent<AdminNavButtonsProps>;\n \"AiAgentButton\": SdkComponent<AiAgentButtonProps>;\n \"Alert\": SdkComponent<AlertProps>;\n \"AnalyticsEmbed\": SdkComponent<AnalyticsEmbedProps>;\n \"Avatar\": SdkComponent<AvatarProps>;\n \"Badge\": SdkComponent<BadgeProps>;\n \"BarChart\": <TRow extends object>(props: CartesianChartProps<TRow>) => any;\n \"Breadcrumb\": SdkComponent<BreadcrumbProps>;\n \"Button\": SdkComponent<PrimitiveButtonProps>;\n \"ButtonLink\": SdkComponent<ButtonLinkProps>;\n \"Calendar\": <TRecord extends object = Record<string, unknown>>(props: CalendarProps<TRecord>) => any;\n \"CalendarConnections\": SdkComponent<CalendarConnectionsProps>;\n \"CalendarNextButton\": SdkComponent<CalendarNextButtonProps>;\n \"CalendarPrevButton\": SdkComponent<CalendarPrevButtonProps>;\n \"CalendarSelector\": SdkComponent<CalendarSelectorProps>;\n \"CalendarTitle\": SdkComponent<CalendarTitleProps>;\n \"CalendarTodayButton\": SdkComponent<CalendarTodayButtonProps>;\n \"CalendarViewSwitcher\": SdkComponent<CalendarViewSwitcherProps>;\n \"Card\": SdkComponent<CardProps>;\n \"Collapsible\": SdkComponent<CollapsibleProps>;\n \"ConfirmDialog\": SdkComponent<ConfirmDialogProps>;\n \"CreateResourceRecordForm\": <TResource extends CreateFormResource = CreateFormResource>(props: CreateResourceRecordFormProps<TResource>) => any;\n \"DataTable\": <TData extends RowData>(props: DataTableProps<TData>) => any;\n \"DataTableAddFilter\": SdkComponent<DataTableAddFilterProps>;\n \"DataTableBulkActions\": SdkComponent<DataTableBulkActionsProps>;\n \"DataTableColumnToggle\": SdkComponent<Pick<DataTableViewOptionsProps, 'className' | 'label'>>;\n \"DataTableContent\": SdkComponent<DataTableContentProps>;\n \"DataTableFilterChips\": SdkComponent<DataTableFilterChipsProps>;\n \"DataTableFilters\": SdkComponent<DataTableFiltersProps>;\n \"DataTablePagination\": SdkComponent<DataTablePaginationProps>;\n \"DataTableRowActions\": SdkComponent<DataTableRowActionsProps>;\n \"DataTableSearch\": SdkComponent<DataTableSearchProps>;\n \"DataTableToolbar\": SdkComponent<DataTableToolbarProps>;\n \"DeleteResourceRecord\": SdkComponent<DeleteResourceRecordProps>;\n \"DropdownMenu\": SdkComponent<DropdownMenuProps>;\n \"EmailComposer\": SdkComponent<EmailComposerProps>;\n \"EmailMessages\": SdkComponent<EmailMessagesProps>;\n \"EmptyState\": SdkComponent<EmptyStateProps>;\n \"ErrorBoundary\": SdkComponent<ErrorBoundaryProps>;\n \"ForgotPasswordForm\": SdkComponent<ForgotPasswordFormProps>;\n \"FormFieldDisplay\": SdkComponent<FormFieldDisplayProps>;\n \"FormFieldInput\": SdkComponent<FormFieldInputProps>;\n \"FormCheckbox\": SdkComponent<FormCheckboxProps>;\n \"FormCheckboxGroupInput\": SdkComponent<FormCheckboxGroupInputProps>;\n \"FormCodeInput\": SdkComponent<FormCodeInputProps>;\n \"FormColorInput\": SdkComponent<FormColorInputProps>;\n \"FormComboboxInput\": SdkComponent<FormComboboxInputProps>;\n \"FormContentInput\": SdkComponent<FormContentInputProps>;\n \"FormDateInput\": SdkComponent<FormDateInputProps>;\n \"FormDatetimeInput\": SdkComponent<FormDatetimeInputProps>;\n \"FormFileUpload\": SdkComponent<FormFileUploadProps>;\n \"FormLocationInput\": SdkComponent<FormLocationInputProps>;\n \"FormMarkdownInput\": SdkComponent<FormMarkdownInputProps>;\n \"FormMoneyInput\": SdkComponent<FormMoneyInputProps>;\n \"FormMultiComboboxInput\": SdkComponent<FormMultiComboboxInputProps>;\n \"FormNumberInput\": SdkComponent<FormNumberInputProps>;\n \"FormPasswordInput\": SdkComponent<FormPasswordInputProps>;\n \"FormPhoneInput\": SdkComponent<FormPhoneInputProps>;\n \"FormRadio\": SdkComponent<FormRadioProps>;\n \"FormSelectInput\": SdkComponent<FormSelectInputProps>;\n \"FormSlider\": SdkComponent<FormSliderProps>;\n \"FormSwitch\": SdkComponent<FormSwitchProps>;\n \"FormTextArea\": SdkComponent<FormTextAreaProps>;\n \"FormTextInput\": SdkComponent<FormTextInputProps>;\n \"FormTimeInput\": SdkComponent<FormTimeInputProps>;\n \"FormTimezoneInput\": SdkComponent<FormTimezoneInputProps>;\n \"HasAnyPermission\": SdkComponent<HasAnyPermissionProps>;\n \"HasPermission\": SdkComponent<HasPermissionProps>;\n \"Icon\": SdkComponent<IconProps>;\n \"Iframe\": SdkComponent<IframeProps>;\n \"Image\": SdkComponent<ImageProps>;\n \"KeyValueList\": SdkComponent<KeyValueListProps>;\n \"Link\": SdkComponent<LinkProps>;\n \"LineChart\": <TRow extends object>(props: CartesianChartProps<TRow>) => any;\n \"List\": SdkComponent<ListProps>;\n \"LoginForm\": SdkComponent<LoginFormProps>;\n \"LogoutButton\": SdkComponent<UserControlProps>;\n \"Map\": SdkComponent<MapProps>;\n \"Modal\": SdkComponent<ModalProps>;\n \"NavMain\": SdkComponent<NavMainProps>;\n \"NotificationComposer\": SdkComponent<NotificationComposerProps>;\n \"NotificationConsentForm\": SdkComponent<NotificationConsentFormProps>;\n \"NotificationHistory\": SdkComponent<NotificationHistoryProps>;\n \"NotificationVariablesForm\": SdkComponent<NotificationVariablesFormProps>;\n \"PackageReleaseNotes\": SdkComponent<SdkPackageReleaseNotesProps>;\n \"PageHeader\": SdkComponent<PageHeaderProps>;\n \"PaymentForm\": SdkComponent<PaymentFormProps>;\n \"PaymentMethods\": SdkComponent<PaymentMethodsProps>;\n \"PieChart\": <TRow extends object>(props: PieChartProps<TRow>) => any;\n \"Popover\": SdkComponent<PopoverProps>;\n \"QrCode\": SdkComponent<QrCodeProps>;\n \"RecordFields\": SdkComponent<RecordFieldsProps>;\n \"RecordScheduleDisplay\": SdkComponent<RecordScheduleDisplayProps>;\n \"ResourceTable\": <TResource extends ResourceDataTableResource, const TRelations extends readonly SdkResourceRelationPath<NoInfer<TResource>>[] = readonly []>(props: ResourceTableProps<TResource, TRelations>) => any;\n \"RestoreResourceRecord\": SdkComponent<RestoreResourceRecordProps>;\n \"RichText\": SdkComponent<RichTextProps>;\n \"ScheduledNotificationComposer\": SdkComponent<ScheduledNotificationComposerProps>;\n \"SearchBar\": SdkComponent<SearchBarProps>;\n \"Separator\": SdkComponent<SeparatorProps>;\n \"SharedFilters\": SdkComponent<SharedFiltersProps>;\n \"ShowResourceRecord\": <TResource extends ShowResourceRecordResource & Pick<SdkResource, 'policy_scope'>, const TRelations extends readonly SdkResourceRelationPath<NoInfer<TResource>>[] = readonly []>(props: ShowResourceRecordProps<TResource, TRelations>) => any;\n \"Sidebar\": SdkComponent<SidebarProps>;\n \"SidebarCollapseButton\": SdkComponent<SidebarTriggerProps>;\n \"SidebarContent\": SdkComponent<SidebarSectionProps>;\n \"SidebarFooter\": SdkComponent<SidebarSectionProps>;\n \"SidebarHeader\": SdkComponent<SidebarSectionProps>;\n \"SidebarInset\": SdkComponent<SidebarSectionProps>;\n \"SidebarMenu\": SdkComponent<SidebarSectionProps>;\n \"SidebarMenuButton\": SdkComponent<SidebarMenuButtonProps>;\n \"SidebarMenuItem\": SdkComponent<PrimitiveListItemNativeProps>;\n \"SidebarProvider\": SdkComponent<SidebarProviderProps>;\n \"SidebarTrigger\": SdkComponent<PrimitiveSidebarTriggerProps>;\n \"SimpleTable\": <TRow extends object>(props: SimpleTableProps<TRow>) => any;\n \"Skeleton\": SdkComponent<SkeletonProps>;\n \"Slideover\": SdkComponent<SlideoverProps>;\n \"SlotPicker\": SdkComponent<SlotPickerProps>;\n \"Spinner\": SdkComponent<SpinnerProps>;\n \"StatCard\": SdkComponent<StatCardProps>;\n \"Step\": SdkComponent<StepProps>;\n \"Stepper\": SdkComponent<StepperProps>;\n \"SubscriptionBillingPreview\": SdkComponent<SubscriptionBillingPreviewProps>;\n \"SubscriptionChangePreview\": SdkComponent<SubscriptionChangePreviewProps>;\n \"SupportChatButton\": SdkComponent<SupportChatButtonProps>;\n \"Tabs\": SdkComponent<TabsProps>;\n \"ThemeToggle\": SdkComponent<ThemeToggleProps>;\n \"Timer\": SdkComponent<TimerProps>;\n \"Tooltip\": SdkComponent<TooltipProps>;\n \"TwoFactorBackupCodeList\": SdkComponent<TwoFactorBackupCodeListProps>;\n \"TwoFactorBackupCodes\": SdkComponent<TwoFactorBackupCodesProps>;\n \"TwoFactorChallenge\": SdkComponent<TwoFactorChallengeProps>;\n \"TwoFactorSendCodeButton\": SdkComponent<TwoFactorSendCodeButtonProps>;\n \"TwoFactorSetup\": SdkComponent<TwoFactorSetupProps>;\n \"TwoFactorSmsConsent\": SdkComponent<TwoFactorSmsConsentProps>;\n \"UpdateResourceRecordForm\": <TResource extends UpdateFormResource = UpdateFormResource>(props: UpdateResourceRecordFormProps<TResource>) => any;\n \"UserMenu\": SdkComponent<UserControlProps>;\n \"Video\": SdkComponent<VideoProps>;\n \"WorkflowForm\": SdkComponent<WorkflowFormProps>;\n \"WorkspaceFieldsForm\": SdkComponent<WorkspaceFieldsFormProps>;\n};\n\ntype SdkHooks = {\n /**\n * React Query wrapper around a resource analytics aggregation. Choose `kpi`,\n * `timeSeries`, `leaderboard`, or `distribution`; each kind requires the same params\n * and returns the same result as its matching `resource.analytics` method. Returns\n * `{ data, isLoading, isError, invalidate }`. Record mutations refresh analytics\n * across resources because relation-backed filters and aggregates may depend on the\n * changed record.\n *\n * Each metric holds an {@link AnalyticsValue} \u2014 a `number`, or `{ amount, currency }`\n * when the aggregated field is money-typed. Render it through\n * `Sdk.utils.number.format` or `Sdk.utils.money.format`; a money object thrown\n * straight into JSX is not a renderable child.\n *\n * `params` is part of the query key. Keep it the same between renders: a date range\n * built from `Sdk.utils.time.current()` or `new Date()` during render changes on every\n * render and makes the hook request without end. See `useRecords`.\n *\n * @example\n * ```tsx\n * function PaidInvoicesTile() {\n * const invoices = Sdk.resource('...');\n * const { data, isLoading, isError } = Sdk.hooks.useAnalytics(invoices, 'kpi', {\n * metrics: [{ name: 'paid_count', operation: 'count' }],\n * $filter: {\n * operator: 'and',\n * conditions: [{ field: 'status', operator: '=', value: 'paid' }]\n * }\n * });\n *\n * if (isLoading) return <Sdk.Components.Skeleton />;\n * if (isError) return <Sdk.Components.Alert>Could not load invoices.</Sdk.Components.Alert>;\n *\n * return <Sdk.Components.Card>{Sdk.utils.number.format(data?.paid_count ?? 0, { decimals: 0 })} paid</Sdk.Components.Card>;\n * }\n * ```\n */\n useAnalytics<const TMetrics extends readonly AnalyticsMetric[]>(\n resource: SdkAnalyticsResource<'kpi'>,\n kind: 'kpi',\n params: AnalyticsKpiParams<TMetrics>,\n options?: SdkUseAnalyticsOptions\n ): SdkAnalyticsHandle<AnalyticsKpiResult<TMetrics>>;\n useAnalytics(\n resource: SdkAnalyticsResource<'timeSeries'>,\n kind: 'timeSeries',\n params: AnalyticsTimeSeriesParams,\n options?: SdkUseAnalyticsOptions\n ): SdkAnalyticsHandle<AnalyticsRow[]>;\n useAnalytics(\n resource: SdkAnalyticsResource<'leaderboard'>,\n kind: 'leaderboard',\n params: AnalyticsLeaderboardParams,\n options?: SdkUseAnalyticsOptions\n ): SdkAnalyticsHandle<AnalyticsRow[]>;\n useAnalytics(\n resource: SdkAnalyticsResource<'distribution'>,\n kind: 'distribution',\n params: AnalyticsDistributionParams,\n options?: SdkUseAnalyticsOptions\n ): SdkAnalyticsHandle<AnalyticsRow[]>;\n\n /**\n * Reactive escape hatch for custom HTTP endpoints. The key controls caching and\n * deduplication; the fetcher receives the same scoped `get`/`post`/`put`/`patch`/`delete`\n * helpers as {@link SdkContract.api}. Returns\n * `{ data, isLoading, isError, error, refreshIn, invalidate }`, with every failure normalized to\n * {@link ApiError}.\n *\n * Keys are isolated from the app's other React Query caches. Begin a key with\n * `'records'` when the endpoint's result depends on resource records, for example\n * `['records', resource.id, 'summary']`; successful resource create/update/delete/restore\n * operations then invalidate it automatically. Other keys refresh only through their\n * returned `invalidate` function.\n *\n * Keep the key the same between renders. A clock read such as `Date.now()` in the key\n * makes a new query on every render, and the hook requests without end. See\n * `useRecords`.\n *\n * With `options.pollInterval`, `refreshIn` counts the whole seconds until the next\n * automatic request, rounded up. It is `0` while a refresh is in flight and `null` when\n * no refresh is scheduled.\n *\n * @example\n * ```tsx\n * function NotificationSummary() {\n * const summary = Sdk.hooks.useApi(\n * ['notifications', 'summary'],\n * (api) => api.get('/api/notifications/summary')\n * );\n *\n * if (summary.isLoading) return <Sdk.Components.Skeleton />;\n * if (summary.error) return <Sdk.Components.Alert>{summary.error.message}</Sdk.Components.Alert>;\n * return <p>{summary.data?.unread_count ?? 0} unread</p>;\n * }\n * ```\n */\n useApi<TData>(\n key: SdkApiQueryKey,\n fetcher: SdkApiFetcher<TData>,\n options?: SdkUseApiOptions<TData>\n ): SdkApiHandle<TData>;\n\n /**\n * Preview subscription charges without creating records or collecting payment.\n * All amounts and dates come from the backend. Changing inputs invalidates the\n * displayed quote; responses for older inputs cannot replace the current result.\n * Pass the reviewed quote as `confirmed_quote` to Subscription: Create.\n * Pass the same settings to Subscription: Create as the preview, or the confirmed quote does not match.\n * Discounts apply after proration. First-invoice discounts apply to the first paid\n * invoice after a trial and do not reduce the renewal amount.\n *\n * @example\n * const billing = Sdk.hooks.useSubscriptionPreview({\n * resourceId: Sdk.resource('subscriptions').id,\n * items: [{ price_record_id: selectedPriceId, quantity: 1 }],\n * discounts: [{ calculation_type: 'percentage', percent: 10, duration: 'first_invoice' }]\n * });\n * // On a creation failure, await billing.refetch() and confirm the refreshed terms.\n */\n useSubscriptionPreview(options: SdkSubscriptionPreviewOptions): SdkSubscriptionPreviewHandle;\n\n /**\n * Preview a change to the items of an existing subscription without applying it.\n * Pass the complete desired selection. All amounts and dates come from the backend.\n * The preview includes discount rows, discount_total, and each credit's gross_amount\n * before source discounts or credits. The credit amount is net of those reductions.\n * Renewal includes discounts on the next invoice. Discount term changes require\n * a new confirmed_quote.\n * An incomplete subscription uses `effective: 'now'` without `revisionScope`. Its\n * `initial_revision` preview revises the first invoice in place. `revision.starts_on_payment`\n * means its term dates move when payment activates the subscription. `revision.activates`\n * means applying the revision activates the subscription. A settled draft first invoice\n * activates it only after staff open the invoice.\n * For initial revisions, `credit_to_balance` is potential excess. Settlement moves only\n * eligible amounts; excess stays on the overpaid invoice when transfer mappings are missing.\n * With `revisionScope`, the preview revises an unpaid renewal invoice: `mode` is\n * `current_revision` or `upcoming_revision`, and `preview.revision` holds the invoice ID,\n * before and after lines and totals, conflicts, and the following renewal. Pass the same\n * selection, `effective`, `revision_scope`, confirmed quote, and a reason to\n * Subscription: Change Items.\n * With `effective: 'period'` and `periodStart`, `preview.schedule` holds the period dates,\n * invoices kept unchanged (`preserved_invoices`), replaced scheduled selections, and the\n * recurring subtotal. Pass the same items, `effective: 'period'`, `period_start`, and confirmed\n * quote to Subscription: Change Items.\n * Changing inputs invalidates the displayed quote; responses for older inputs cannot\n * replace the current result. `preview.changed` is false when the selection matches\n * the current subscription. The endpoint requires a signed-in visitor. `Sdk.resource`\n * throws for a visitor who cannot see the resource, so resolve the ID with\n * `Sdk.resourceOrNull` on a page that signed-out visitors can open.\n *\n * @example\n * const change = Sdk.hooks.useSubscriptionChangePreview({\n * resourceId: Sdk.resourceOrNull('subscriptions')?.id ?? '',\n * recordId: subscriptionId,\n * items: [{ price_record_id: selectedPriceId, quantity: 1 }],\n * enabled: Sdk.identity !== null\n * });\n * const canConfirm = change.quote !== null && change.preview?.changed === true;\n */\n useSubscriptionChangePreview(\n options: SdkSubscriptionChangePreviewOptions\n ): SdkSubscriptionChangePreviewHandle;\n\n /**\n * Search free slots for a booking across groups of schedulable records. Each group needs all\n * of its records (`mode: 'all'`) or one of them (`mode: 'any'`). A slot is advice: the booking\n * create decides. Changing an option starts a new search, and the slots of the old search are\n * hidden at once. Show `error` as an error state; an empty `slots` list is not a failure.\n * Render the result with `Sdk.Components.SlotPicker`.\n *\n * @example\n * const search = Sdk.hooks.useAvailableSlots({\n * resourceId: Sdk.resource('appointments').id,\n * groups: [{ mode: 'any', records: tutors.map((tutor) => ({ resourceId: tutorsResourceId, id: tutor.id })) }],\n * from: weekStart,\n * to: weekEnd,\n * durationMinutes: 60\n * });\n */\n useAvailableSlots(options: SdkAvailableSlotsOptions): SdkAvailableSlotsHandle;\n\n /**\n * Read the free time of schedulable records in a window of at most 62 days, with the pieces\n * that made it: regular hours, exceptions, limits, closures, and busy time from staff members'\n * blocking connected calendars. Pass a record's `backgroundTimes` to `Sdk.Components.Calendar`\n * to shade open time and label busy and closed time in the week and day views.\n *\n * @example\n * const availability = Sdk.hooks.useAvailability({\n * resourceId: Sdk.resource('tutors').id,\n * ids: [tutorId],\n * from: cal.windowStart,\n * to: cal.windowEnd,\n * labels: { busy: 'Away' }\n * });\n * <Sdk.Components.Calendar\n * events={[]}\n * view={cal.view}\n * date={cal.date}\n * backgroundTimes={availability.records[0]?.backgroundTimes ?? []}\n * />\n */\n useAvailability(options: SdkAvailabilityOptions): SdkAvailabilityHandle;\n\n /**\n * Read the held times of a resource's records that closed time conflicts with: bookings outside\n * open time, and events over a closed exception, a closure, or busy time on the calendar owner's\n * blocking connected calendars. Only a signed-in visitor reads conflicts, and only of records\n * whose time fields and relation the visitor can read. Show `error` as an error state.\n *\n * @example\n * const { conflicts } = Sdk.hooks.useScheduleConflicts({\n * resourceId: Sdk.resource('lessons').id,\n * bookable: { resourceId: Sdk.resource('tutors').id, ids: [tutorId] },\n * enabled: Sdk.identity !== null\n * });\n */\n useScheduleConflicts(options: SdkScheduleConflictsOptions): SdkScheduleConflictsHandle;\n\n /**\n * Preview the refund amount limits of one payment without refunding it.\n * `refundable` is the largest amount that the refund amount checks of Payment: Refund\n * allow, and the amount that Payment: Refund refunds when the amount is omitted.\n * Payment: Refund can still reject a refund for another `payment_refund_rejected`\n * reason. `payment_remaining` is the payment amount less its succeeded refunds.\n * When the invoice supplied unused-service credit to a subscription change,\n * `change_credit` is that credit and `invoice_refundable` is what the invoice can\n * still refund, so `refundable` can be lower than `payment_remaining`. Both are null\n * when no change credit limits the refund, or when the visitor cannot see every\n * payment and succeeded refund on the invoice with its amount. `refundable` still\n * applies the cap. Call `refetch` after a refund.\n * The endpoint requires a signed-in visitor with Show access to the payment.\n *\n * @example\n * const limits = Sdk.hooks.usePaymentRefundPreview({\n * resourceId: Sdk.resourceOrNull('payments')?.id ?? '',\n * recordId: paymentId,\n * enabled: Sdk.identity !== null\n * });\n * const refundable = limits.preview?.refundable;\n * const isFullRefund = refundable?.amount === limits.preview?.payment_remaining.amount;\n * const label = isFullRefund ? 'Refund full payment' : 'Refund available amount';\n * // After the refund request completes, await limits.refetch().\n */\n usePaymentRefundPreview(options: SdkPaymentRefundPreviewOptions): SdkPaymentRefundPreviewHandle;\n\n /**\n * Read the available payer balance of each accounting entity. The amounts are the\n * amounts that checkout uses when it pays an invoice of that entity from the payer\n * balance. The default entity holds ledger entries with no entity, and any part of the\n * total that no ledger entry explains. Each amount is clamped to zero and to the total,\n * so the amounts need not add up to `total`. The list holds each active entity, and each\n * inactive entity whose amount is above zero, ordered by name. A workspace with no\n * entity returns the total and an empty list.\n * The endpoint requires a signed-in visitor with Show access to the payer and its\n * balance field. Call `refetch` after a ledger entry or a payment.\n *\n * @example\n * const payerBalances = Sdk.hooks.usePayerBalances({\n * resourceId: Sdk.resourceOrNull('businesses')?.id ?? '',\n * recordId: businessId,\n * enabled: Sdk.identity !== null\n * });\n * const rows = payerBalances.balances.map((balance) => ({\n * name: balance.entity.name,\n * available: Sdk.utils.money.format(balance.available)\n * }));\n */\n usePayerBalances(options: SdkPayerBalancesOptions): SdkPayerBalances;\n\n /**\n * The release history of the packages this workspace installed: the installed and the\n * newest version of each, and the notes of each published release, newest first. Every\n * release newer than the installed one is listed, so a skipped upgrade shows each\n * version's notes. The endpoint requires a visitor with the Manage Packages permission.\n *\n * @example\n * const releaseNotes = Sdk.hooks.usePackageReleaseNotes({ limit: 5 });\n * const updates = releaseNotes.packages.filter((item) => item.has_newer_release);\n */\n usePackageReleaseNotes(options?: SdkPackageReleaseNotesOptions): SdkPackageReleaseNotes;\n\n /**\n * List the accounting entities of the workspace with their `active` and `is_default`\n * flags, ordered by name. Inactive entities are included. A branch returns the copies\n * of its production workspace's entities. A signed-out visitor gets an empty list and\n * no request is made,\n * so a public page reads the entity of a record from `record.__meta?.accounting_entity`\n * instead. ShowRecord and IndexRecords add it to each invoice, subscription, and payer\n * balance entry record whose entity field the visitor can read. ShowRecord (`useRecord`)\n * uses the field visibility of the Show policy, and IndexRecords (`useRecords`) uses the\n * field visibility of the Index policy. A Show or Index policy with a field list must list\n * the entity field, or the name is absent. Add the entity field to each policy scope\n * that the page uses.\n *\n * @example\n * const { entities } = Sdk.hooks.useAccountingEntities();\n * const options = entities\n * .filter((entity) => entity.active)\n * .map((entity) => ({ value: entity.id, label: entity.name }));\n * // On a public invoice page:\n * const payee = invoice.__meta?.accounting_entity?.name ?? 'Our organization';\n */\n useAccountingEntities(): SdkAccountingEntities;\n\n /**\n * Refresh the page's route bindings and inputs on an interval.\n *\n * Each tick reloads only the page's own data. Reach for it when a backend change\n * arrives from outside the page \u2014 a webhook, a queued job, another user \u2014 and the page\n * must notice it without the visitor pressing anything. Returns `{ start, stop,\n * lastPolledAt }`; polling begins on mount unless `autoStart: false`, and slows down\n * while the tab is hidden. `lastPolledAt` is the time the latest tick finished, or\n * `null` before the first one.\n *\n * This is the route-binding layer. `Sdk.helpers.getRouteBinding(...)` and the other\n * `Sdk.helpers` data only refresh this way, and this is the only refresh mechanism an\n * anonymous visitor on a public page has, because the record API needs authentication.\n * Records fetched with `useRecord`/`useRecords`/`useApi` live in a separate cache that\n * a reload does not touch \u2014 poll those with their own `pollInterval` option.\n *\n * Every tick costs a full server-side page render, so keep the interval at 3000 ms or\n * more and call `stop()` as soon as the awaited change arrives. Intervals below 1000 ms\n * are raised to 1000 ms. A hidden tab slows to one tick in ten; it does not stop.\n *\n * The interval is read once on mount, so changing it later has no effect \u2014 use `stop()`\n * to end polling. When a tick returns changed data, the page source recompiles.\n * Only the returned page function preserves its local React state. Helper components\n * declared in page source and `Sdk.Custom` components can remount and reset their state.\n * Prefer `pollInterval` on `useRecord`/`useRecords`/`useApi` when a page holds state and\n * the record API is available. State initialized from old data does not automatically\n * track new data, so read route bindings during render.\n *\n * @example\n * ```tsx\n * function TicketList() {\n * const { stop } = Sdk.hooks.usePoll(3000);\n * const order = Sdk.helpers.getRouteBinding('event_order');\n * const tickets = order?.tickets ?? [];\n *\n * React.useEffect(() => {\n * if (tickets.length > 0) stop();\n * }, [tickets.length, stop]);\n *\n * if (tickets.length === 0) return <Sdk.Components.Skeleton />;\n * return <ul>{tickets.map((ticket) => <li key={ticket.id}>{ticket.code}</li>)}</ul>;\n * }\n * ```\n */\n usePoll(interval: number, options?: SdkUsePollOptions): SdkPollControls;\n\n /**\n * Bind page-owned filter, search, and pagination state to the current URL query string.\n * Returns `[params, setParams]`; updates merge the supplied keys into the current URL with\n * `history.replaceState`, so they do not reload the page or trigger an Inertia visit.\n * Pass `null` or `undefined` to remove a key. Browser back/forward and `Sdk.router`\n * navigations update `params` reactively. Without a schema, values are strings (or string\n * arrays for repeated keys). A schema can coerce numbers and booleans, read arrays from\n * repeated query keys, and supply defaults for missing or invalid values. Arrays are written\n * as repeated keys (`tag=a&tag=b`). Editor preview plumbing is hidden and reserved, so page\n * code behaves identically on published routes and in preview.\n *\n * @example\n * ```tsx\n * function StudentList() {\n * const [params, setParams] = Sdk.hooks.useQueryParams({\n * search: { default: '' },\n * page: { type: 'number', default: 1 },\n * active: { type: 'boolean', default: true },\n * tags: { type: 'array', default: [] }\n * });\n *\n * return (\n * <Sdk.Components.Button onClick={() => setParams({ page: params.page + 1 })}>\n * Next page\n * </Sdk.Components.Button>\n * );\n * }\n * ```\n */\n useQueryParams(): [QueryParams, SetQueryParams<QueryParams>];\n useQueryParams<const TSchema extends QueryParamSchema>(\n schema: TSchema\n ): [QueryParamsFromSchema<TSchema>, SetQueryParams<QueryParamsFromSchema<TSchema>>];\n\n /**\n * Reactive wrapper around `resource.urls.show(recordId)` for fetching a single\n * record. Returns `{ data, isLoading, isError, error, refreshIn }`. Pass relation paths through\n * `options.relations`, for example `{ relations: ['family'] }`; known resource handles add only\n * selected relations to `data`, so accessing an omitted relation or unknown field is a\n * save-time type error. Runtime-dynamic resource ids retain the generic record fallback.\n *\n * On a public page, `data === undefined` does not prove that the record is absent:\n * guest policy denial is a 403 and a missing record is a 404. Check `error` with\n * `Sdk.utils.error.isForbidden(error)` / `isNotFound(error)` before choosing the UI state.\n *\n * The resource may be a handle from `Sdk.resource()` or a resource id / slug string. The\n * string form is typed from the workspace catalog and stays idle (`data` is `undefined`,\n * no error) for a viewer who cannot access the resource, so it suits pages that serve\n * both guests and members.\n *\n * With `options.pollInterval`, `refreshIn` counts the whole seconds until the next\n * automatic request, rounded up, so a page can show a countdown. It is `0` while a\n * refresh is in flight, and `null` whenever no refresh is scheduled: before the first\n * request settles, without a `pollInterval`, once a function-form `pollInterval` returns\n * `false`, or while the browser tab is hidden and the poll is paused.\n *\n * @example\n * ```tsx\n * function StudentCard({ recordId }) {\n * const students = Sdk.resource('...');\n * const { data: student, isLoading, error } = Sdk.hooks.useRecord(students, recordId, {\n * relations: ['family']\n * });\n *\n * if (isLoading) return <Sdk.Components.Skeleton />;\n * if (Sdk.utils.error.isForbidden(error)) return <AccessDenied />;\n * if (Sdk.utils.error.isNotFound(error)) return <NotFound />;\n * return <div>{student?.name} \u2014 {student?.family?.name}</div>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * function OrderStatus({ recordId }) {\n * const orders = Sdk.resource('...');\n * const { data: order, refreshIn } = Sdk.hooks.useRecord(orders, recordId, {\n * relations: ['tickets'],\n * pollInterval: (record) => (record?.tickets?.length ? false : 3000)\n * });\n *\n * return (\n * <div>\n * {order?.status}\n * {refreshIn === null ? null : <span>Refreshing in {refreshIn}s</span>}\n * </div>\n * );\n * }\n * ```\n */\n useRecord<\n TReference extends SdkResourceQueryReference<'show'>,\n const TRelations extends readonly SdkReferenceRelationPath<NoInfer<TReference>>[] = readonly []\n >(\n resource: TReference,\n recordId: string | null | undefined,\n options?: SdkUseRecordOptions<TRelations, NoInfer<SdkReferenceRecord<TReference, TRelations>>>\n ): SdkRecordHandle<SdkReferenceRecord<TReference, TRelations>>;\n\n /**\n * Reactive wrapper around the record search endpoint for fetching a page of records \u2014\n * the collection twin of `useRecord`, and the right tool for any collection UI that\n * isn't a table (card grids, charts, select options, kanban boards, dashboard tiles).\n * Returns `{ data, pagination, isLoading, isError, refreshIn, invalidate }`, where `data` is the\n * current page of records (`[]` until the first fetch resolves). The previous page\n * stays rendered while a new page, filter, or sort loads, and records mutated through\n * `resource.create/update/delete/restore` refresh it automatically.\n *\n * Pass relation paths through `options.relations`; known resource handles add only\n * selected relations to `data`, so accessing an omitted relation or unknown field is\n * a save-time type error. Runtime-dynamic resource ids retain the generic record\n * fallback. `filter` must be a structured tree (see {@link ResourceSearchBody}) \u2014\n * flat maps like `{ status: 'paid' }` are rejected with a 422. Filtering on a nested\n * relation path (`family.id`) does not load that relation: it still comes back as its\n * `{ id }` reference, so list it in `relations` to read any other related field.\n * With `options.pollInterval`, `refreshIn` counts the whole seconds until the next\n * automatic request, rounded up. It is `0` while a refresh is in flight and `null` when\n * no refresh is scheduled.\n *\n * Every option except `enabled` and `pollInterval` is part of the query key, so keep\n * the values the same between renders. Do not read the clock in them:\n * `Sdk.utils.time.current()`, `Date.now()`, and `new Date()` return a new value on every\n * render, so each response builds a new query and the page requests without end. Read\n * the time once with `React.useState(() => Sdk.utils.time.current())` or at the top of\n * the page factory. `Sdk.utils.time.startOf(...)`/`endOf(...)` of the current time stay\n * the same between renders and are safe. Saving a page rejects a clock read that\n * reaches a query hook's arguments.\n *\n * Reach for `useResourceDataTable` instead when the UI really is a table.\n *\n * The resource may be a handle from `Sdk.resource()` or a resource id / slug string.\n * A string reference is typed from the workspace catalog at authoring time and is\n * resolved against the viewer's accessible resources at render time, so a viewer\n * without access keeps the hook idle (`data` stays `[]`) instead of throwing. Prefer\n * the string form on pages that serve both guests and members.\n *\n * @example\n * ```tsx\n * function BusinessContacts({ contactId }: { contactId: string }) {\n * const { data } = Sdk.hooks.useRecords('business-contacts', {\n * enabled: Boolean(contactId),\n * relations: ['business'],\n * filter: {\n * operator: 'and',\n * conditions: [{ field: 'contact.id', operator: '=', value: contactId }]\n * }\n * });\n *\n * return <ul>{data.map((link) => <li key={link.id}>{link.business?.name}</li>)}</ul>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * function StudentCards() {\n * const students = Sdk.resource('...');\n * const { data, pagination, isLoading } = Sdk.hooks.useRecords(students, {\n * relations: ['family'],\n * filter: {\n * operator: 'and',\n * conditions: [{ field: 'status', operator: '=', value: 'active' }]\n * },\n * sort: '-created_at',\n * perPage: 12\n * });\n *\n * if (isLoading) return <Sdk.Components.Skeleton />;\n *\n * return (\n * <div className=\"grid grid-cols-3 gap-4\">\n * {data.map((student) => (\n * <Sdk.Components.Card key={student.id}>\n * {student.name} \u2014 {student.family?.name}\n * </Sdk.Components.Card>\n * ))}\n * <p>{pagination?.total} students</p>\n * </div>\n * );\n * }\n * ```\n */\n useRecords<\n TReference extends SdkResourceQueryReference<'search'>,\n const TRelations extends readonly SdkReferenceRelationPath<NoInfer<TReference>>[] = readonly []\n >(\n resource: TReference,\n options?: SdkUseRecordsOptions<TRelations, NoInfer<SdkReferenceRecord<TReference, TRelations>>>\n ): SdkRecordsHandle<SdkReferenceRecord<TReference, TRelations>>;\n\n /**\n * Saved views for a resource: the views the viewer may use or\n * manage (policy-gated server-side), the current selection,\n * and the live view state a selection hydrates \u2014 `filter`, `sorts`, `search`,\n * `perPage`, and `columns`, each `null` (`''` for search, `[]` for sorts) when not\n * configured. `sorts` is the ordered sort, primary first; `sort` is its primary entry.\n * The query values spread straight into `useRecords`. `columns` contains the visible\n * table column identifiers, including custom column IDs, in display order.\n * `useResourceDataTable` applies this layout automatically. Selecting applies the\n * saved state; the setters only dirty the local state until `reset`, `create` (save\n * as a new private view), or `update`.\n *\n * `useResourceDataTable` wires this up automatically \u2014 reach for it directly when\n * building a non-table view (lists, card grids, custom layouts). Pair it with\n * `Sdk.Components.SharedFilters` for a ready-made selector dropdown, or build your\n * own from `available` + `select`.\n *\n * @example\n * ```tsx\n * function StudentList() {\n * const students = Sdk.resource('...');\n * const sharedFilters = Sdk.hooks.useSharedFilters(students);\n * const { data } = Sdk.hooks.useRecords(students, {\n * filter: sharedFilters.filter,\n * sort: sharedFilters.sorts,\n * search: sharedFilters.search,\n * perPage: sharedFilters.perPage\n * });\n *\n * return (\n * <div className=\"grid gap-2\">\n * <Sdk.Components.SharedFilters sharedFilters={sharedFilters} />\n * {data.map((student) => (\n * <Sdk.Components.Card key={student.id}>{student.name}</Sdk.Components.Card>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\n useSharedFilters(\n resource: Pick<SdkResource, 'id' | 'policy_scope'>,\n options?: SdkUseSharedFiltersOptions\n ): SharedFiltersApi;\n\n /**\n * Infinite-scroll counterpart to `useRecords`. It accepts the same `relations`,\n * `filter`, `sort`, `perPage`, `scope`, and `enabled` options, then returns up to\n * the ten most recently loaded pages flattened in `data` with `fetchNextPage`,\n * `hasNextPage`, and `isFetchingNextPage`. Older pages are discarded so cache memory\n * and mutation-triggered refetch work remain bounded. Mutations through the resource\n * handle refresh the retained pages. Like `useRecords`, it accepts a resource id / slug\n * string typed from the workspace catalog, which stays idle for a viewer without access.\n * Keep the options the same between renders; see `useRecords` for why clock reads are\n * rejected.\n *\n * @example\n * ```tsx\n * const students = Sdk.resource('...');\n * const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =\n * Sdk.hooks.useInfiniteRecords(students, {\n * filter: {\n * operator: 'and',\n * conditions: [{ field: 'status', operator: '=', value: 'active' }]\n * },\n * sort: '-created_at',\n * perPage: 25\n * });\n * ```\n */\n useInfiniteRecords<\n TReference extends SdkResourceQueryReference<'search'>,\n const TRelations extends readonly SdkReferenceRelationPath<NoInfer<TReference>>[] = readonly []\n >(\n resource: TReference,\n options?: SdkUseInfiniteRecordsOptions<TRelations>\n ): SdkInfiniteRecordsHandle<SdkReferenceRecord<TReference, TRelations>>;\n\n /**\n * Reactive `resource.create` for pages that write records without a form hook.\n * Returns `{ create, data, isPending, isError, error, reset }`. `create` resolves\n * with the created record, or `null` when the request failed \u2014 it never rejects, so\n * calling it straight from an `onClick` is safe; read `error` for the failure.\n * Cached record data refreshes on success.\n *\n * Prefer `Sdk.Components.CreateResourceRecordForm` when a schema-driven form fits.\n * On a Booking resource, a refusal lands on `error`; read its message with\n * `Sdk.utils.error.extractApiErrorBody(error)?.errors?.$capacity?.[0]`. `create` returns the record only, so call `resource.create`\n * when the page must read `meta.series.waiting` of a repeating booking.\n *\n * @example\n * ```tsx\n * function QuickAddTask() {\n * const tasks = Sdk.resource('...');\n * const { create, isPending, error } = Sdk.hooks.useCreateRecord(tasks);\n * const [title, setTitle] = React.useState('');\n *\n * return (\n * <div className=\"flex gap-2\">\n * <Sdk.Components.FormTextInput value={title} onChange={setTitle} />\n * <Sdk.Components.Button\n * disabled={isPending}\n * onClick={async () => {\n * const task = await create({ title });\n * if (task) setTitle('');\n * }}\n * >\n * Add\n * </Sdk.Components.Button>\n * {error ? <Sdk.Components.Alert>{error.message}</Sdk.Components.Alert> : null}\n * </div>\n * );\n * }\n * ```\n */\n useCreateRecord<TResource extends SdkResourceType & Pick<SdkResource, 'create' | 'capabilities'>>(\n resource: TResource,\n options?: SdkUseCreateRecordOptions<SdkResourceRecord<TResource>>\n ): SdkCreateRecordHandle<SdkResourceRecord<TResource>>;\n\n /**\n * Reactive `resource.update` for pages that write records without a form hook \u2014\n * inline status toggles, kanban drag-and-drop, quick edits. Returns\n * `{ update, data, isPending, isError, error, reset }`. `update` resolves with the\n * updated record, or `null` when the request failed \u2014 it never rejects; read `error`\n * for the failure. Cached record data refreshes on success.\n *\n * Prefer `Sdk.Components.UpdateResourceRecordForm` when a schema-driven form fits.\n *\n * @example\n * ```tsx\n * function CompleteButton({ task }) {\n * const tasks = Sdk.resource('...');\n * const { update, isPending } = Sdk.hooks.useUpdateRecord(tasks);\n *\n * return (\n * <Sdk.Components.Button\n * disabled={isPending}\n * onClick={() => update(task.id, { status: 'complete' })}\n * >\n * Complete\n * </Sdk.Components.Button>\n * );\n * }\n * ```\n */\n useUpdateRecord<TResource extends SdkResourceType & Pick<SdkResource, 'update'>>(\n resource: TResource,\n options?: SdkUseUpdateRecordOptions<SdkResourceRecord<TResource>>\n ): SdkUpdateRecordHandle<SdkResourceRecord<TResource>>;\n\n /**\n * Delete flow for a single record, with the impact preview and blocked-delete error\n * body the built-in delete dialog uses. Returns `{ preview, isPreviewLoading,\n * isPreviewError, confirm, isProcessing, deleteError, error, reset }`. `error` is\n * the normalized {@link SdkApiError}; classify it with `Sdk.utils.error`. Open `fetchPreview`\n * when your confirmation UI opens so the preview reflects the moment of decision,\n * then call `confirm()`. Cached record data refreshes on success.\n *\n * Prefer `Sdk.Components.DeleteResourceRecord` when the built-in dialog fits.\n *\n * @example\n * ```tsx\n * function DeleteStudent({ recordId }) {\n * const students = Sdk.resource('...');\n * const [open, setOpen] = React.useState(false);\n * const { preview, confirm, isProcessing, deleteError } = Sdk.hooks.useDeleteResourceRecord({\n * resource: students,\n * recordId,\n * fetchPreview: open,\n * onSuccess: () => setOpen(false)\n * });\n *\n * if (!open) return <Sdk.Components.Button onClick={() => setOpen(true)}>Delete</Sdk.Components.Button>;\n *\n * return (\n * <div>\n * <p>{preview?.summary}</p>\n * {deleteError ? <Sdk.Components.Alert>{deleteError.message}</Sdk.Components.Alert> : null}\n * <Sdk.Components.Button disabled={isProcessing} onClick={() => confirm()}>\n * Confirm delete\n * </Sdk.Components.Button>\n * </div>\n * );\n * }\n * ```\n */\n useDeleteResourceRecord(options: {\n resource: DeleteResourceRecordResource;\n recordId: string | undefined;\n /** Open this gate when the confirmation UI opens so the preview is fetched then. */\n fetchPreview?: boolean;\n onSuccess?: () => void;\n }): DeleteResourceRecordState;\n\n /**\n * Restore flow for a single soft-deleted record, mirroring\n * `useDeleteResourceRecord`. Returns `{ preview, isPreviewLoading, isPreviewError,\n * confirm, isProcessing, restoreError, reset }`. Pair it with\n * `Sdk.hooks.useRecords(resource, { scope: 'archived' })` to list what can be\n * restored. Cached record data refreshes on success.\n *\n * Prefer `Sdk.Components.RestoreResourceRecord` when the built-in dialog fits.\n *\n * @example\n * ```tsx\n * function RestoreStudent({ recordId }) {\n * const students = Sdk.resource('...');\n * const { confirm, isProcessing } = Sdk.hooks.useRestoreResourceRecord({\n * resource: students,\n * recordId\n * });\n *\n * return (\n * <Sdk.Components.Button disabled={isProcessing} onClick={confirm}>\n * Restore\n * </Sdk.Components.Button>\n * );\n * }\n * ```\n */\n useRestoreResourceRecord(options: {\n resource: RestoreResourceRecordResource;\n recordId: string | undefined;\n /** Open this gate when the confirmation UI opens so the preview is fetched then. */\n fetchPreview?: boolean;\n onSuccess?: () => void;\n }): RestoreResourceRecordState;\n\n /**\n * Queue an ad hoc email through `POST /api/email-messages`. Returns\n * `{ send, data, isPending, isError, error, reset }`. `send` resolves with the\n * response, or `null` when the request failed \u2014 it never rejects, so calling it\n * straight from an `onClick` is safe; read `error` for the failure. The viewer's\n * role must hold the `send_emails` permission, or the request fails with a 403.\n *\n * Prefer `Sdk.Components.EmailComposer` when a person writes the email in the page.\n *\n * @example\n * ```tsx\n * function ResendInvoice({ invoice }: { invoice: Sdk.RecordOf<'invoices'> }) {\n * const invoices = Sdk.resource('invoices');\n * const { send, isPending, error } = Sdk.hooks.useSendEmail();\n *\n * return (\n * <div className=\"space-y-2\">\n * <Sdk.Components.Button\n * disabled={isPending}\n * onClick={() =>\n * send({\n * to: [{ email: invoice.email }],\n * subject: 'Invoice {{ number }}',\n * email_layout_id: '...',\n * layout_data: { number: invoice.number, content: '<p>Your invoice is attached.</p>' },\n * records: [{ id: invoice.id, resource_id: invoices.id }]\n * })\n * }\n * >\n * Resend invoice\n * </Sdk.Components.Button>\n * {error ? <Sdk.Components.Alert variant=\"destructive\">{error.message}</Sdk.Components.Alert> : null}\n * </div>\n * );\n * }\n * ```\n */\n useSendEmail(options?: SdkUseSendEmailOptions): SdkSendEmailHandle;\n\n /**\n * Send a notification template through `POST /api/email-messages/send-notification`.\n * Requires notification templates and the send permissions for every selected channel.\n * An omitted delivery_channel means email. SMS goes to the recipient record\u2019s mapped\n * phone number and only when that record opted in; otherwise history shows it as\n * suppressed. Email and SMS requires one template with both channels enabled. Uses the\n * compatible current default when no template is selected. Refreshes email and\n * notification history after success. Returns null on failure and exposes the API\n * error on the handle. Add `calendar_events: [{ resource_id, record_id }]` to attach an\n * Eventable record as a calendar invite that a later send updates or cancels.\n * Pass `idempotency_key` (for example `renewal-notice:${record.id}:initial`) when a notice\n * must reach a recipient at most once. The first send with a key in the workspace claims it;\n * every later send with that key, from a page, workflow, bulk run, or scheduled notification,\n * sends nothing and resolves with `replayed: true`. The response names the first notification\n * (`notification_history_id`, its email and SMS IDs) only when the viewer can read it:\n * with `manage_notifications` or Show access to one of its records. Build the key from the record and the notice, never from a random value or the time.\n *\n * @example\n * ```tsx\n * function WelcomeButton() {\n * const email = Sdk.hooks.useSendNotification();\n * return <Sdk.Components.Button disabled={email.isPending} onClick={() => email.send({\n * notification_type_id: '01990000-0000-7000-8000-000000000001',\n * variables: { name: 'Morgan' },\n * to: [{ email: 'morgan@example.com' }]\n * })}>Send welcome</Sdk.Components.Button>;\n * }\n * ```\n */\n useSendNotification(options?: SdkUseSendNotificationOptions): SdkSendNotificationHandle;\n\n /**\n * Read and change a notifiable record\u2019s consent for email and SMS through\n * `GET` and `PUT /api/notification-consents`. With no `resourceId` and `recordId` it\n * manages the signed-in identity\u2019s own consent and needs no permission. Another record\n * follows the resource\u2019s record policies. A record that was never asked has email on and\n * SMS off, and SMS notifications to it are suppressed until it opts in.\n *\n * @example\n * ```tsx\n * function SmsOptIn() {\n * const consent = Sdk.hooks.useNotificationConsent();\n * if (!consent.data) return null;\n * return (\n * <label className=\"flex items-center gap-2\">\n * <input\n * type=\"checkbox\"\n * checked={consent.data.channels.sms}\n * disabled={consent.isPending}\n * onChange={(event) => consent.update({ sms: event.target.checked })}\n * />\n * Text me event reminders\n * </label>\n * );\n * }\n * ```\n */\n useNotificationConsent(options?: SdkUseNotificationConsentOptions): SdkNotificationConsentHandle;\n\n /**\n * Read and change the Google calendars of one Schedulable staff record through\n * `/api/calendar-connections`. With no `resourceId` and `recordId` it manages the\n * signed-in identity's own calendars and needs no permission. Another staff record needs\n * \"Manage calendar connections\" and Show access to the record. A viewer connects Google\n * accounts and adds calendars only through their own accounts. Nothing is available\n * while the workspace's `calendar_sync` flag is off, for a signed-out visitor (no request\n * is made), and for an identity that is not a Schedulable record: check `available` and\n * render nothing. Every write refreshes the data and rejects with an `ApiError` on a\n * refusal: read its text with `Sdk.utils.error.apiErrorMessage(error)`.\n * `Sdk.Components.CalendarConnections` renders all of it.\n *\n * @example\n * ```tsx\n * function CalendarStatus() {\n * const calendars = Sdk.hooks.useCalendarConnections();\n * if (!calendars.available) return null;\n * if (calendars.links.length === 0) {\n * return <Sdk.Components.Button onClick={() => calendars.connect()}>Connect Google Calendar</Sdk.Components.Button>;\n * }\n * return (\n * <ul>\n * {calendars.links.map((link) => (\n * <li key={link.id}>{link.calendar_name}: {link.message ?? 'Active'}</li>\n * ))}\n * </ul>\n * );\n * }\n * ```\n */\n useCalendarConnections(options?: SdkUseCalendarConnectionsOptions): SdkCalendarConnectionsHandle;\n\n /**\n * Drive the two-factor challenge of a pending sign-in on this page, for a custom UI.\n * `Sdk.Components.TwoFactorChallenge` already renders it. Codes are sent only by\n * `Sdk.Components.TwoFactorSmsConsent` and `Sdk.Components.TwoFactorSendCodeButton` with\n * `flow=\"challenge\"`, which always show the SMS terms.\n *\n * @example\n * ```tsx\n * function Challenge() {\n * const challenge = Sdk.hooks.useTwoFactorChallenge();\n * const [code, setCode] = React.useState('');\n * if (!challenge.isActive || !challenge.data) return null;\n * if (challenge.data.step === 'consent') return <Sdk.Components.TwoFactorSmsConsent flow=\"challenge\" />;\n * return (\n * <>\n * <Sdk.Components.FormCodeInput label=\"Code\" value={code} onChange={setCode} onComplete={challenge.verifyCode} />\n * <Sdk.Components.TwoFactorSendCodeButton flow=\"challenge\" />\n * </>\n * );\n * }\n * ```\n */\n useTwoFactorChallenge(options?: SdkUseTwoFactorOptions): SdkTwoFactorChallengeHandle;\n\n /**\n * Drive MFA setup for a custom UI: the first setup of a pending sign-in, or a signed-in\n * member's change of MFA number. `Sdk.Components.TwoFactorSetup` already renders it.\n * Codes are sent only by `Sdk.Components.TwoFactorSmsConsent` and\n * `Sdk.Components.TwoFactorSendCodeButton` with `flow=\"setup\"`.\n *\n * @example\n * ```tsx\n * function ChangeMfaNumber() {\n * const setup = Sdk.hooks.useTwoFactorSetup();\n * const [phone, setPhone] = React.useState('');\n * if (!setup.isActive || !setup.data) return null;\n * if (setup.data.step === 'phone') {\n * return (\n * <>\n * <Sdk.Components.FormPhoneInput label=\"New MFA number\" value={phone} onChange={setPhone} />\n * <Sdk.Components.Button onClick={() => setup.submitPhone(phone)}>Continue</Sdk.Components.Button>\n * </>\n * );\n * }\n * return <Sdk.Components.TwoFactorSetup />;\n * }\n * ```\n */\n useTwoFactorSetup(options?: SdkUseTwoFactorOptions): SdkTwoFactorSetupHandle;\n\n /**\n * Read and regenerate the signed-in member's backup codes for a custom UI.\n * `Sdk.Components.TwoFactorBackupCodes` already renders it. Codes for the proof are sent\n * only by `Sdk.Components.TwoFactorSmsConsent` and `Sdk.Components.TwoFactorSendCodeButton`\n * with `flow=\"backupCodes\"`.\n *\n * @example\n * ```tsx\n * function BackupCodeCount() {\n * const backupCodes = Sdk.hooks.useTwoFactorBackupCodes();\n * if (!backupCodes.data) return null;\n * return <p>{backupCodes.data.remainingCount} unused backup codes</p>;\n * }\n * ```\n */\n useTwoFactorBackupCodes(options?: SdkUseTwoFactorOptions): SdkTwoFactorBackupCodesHandle;\n\n /**\n * Start background notifications for selected records or a filtered audience. The server\n * saves the audience, template, and published layout before sending. Duplicate recipients\n * on different source records receive separate emails. Save the returned ID in page state\n * or a route parameter to reopen progress with useBulkNotificationRun after navigation.\n * Each new send call creates a separate run. Do not automatically retry a failed request.\n * To make a repeated run safe, pass `idempotency_key` containing `{source_record_id}`, for\n * example `renewal-notice:{source_record_id}:initial`. Each source record gets its own key,\n * which matches a single send with the same expanded key; a source record whose key any\n * earlier notification used sends nothing and counts in `duplicate_count`.\n * @example\n * ```tsx\n * const sender = Sdk.hooks.useSendBulkNotifications();\n * const progress = Sdk.hooks.useBulkNotificationRun(sender.data?.id);\n * // From a user action:\n * await sender.send({\n * resource_id: attendeeResourceId,\n * notification_type_id: notificationTypeId,\n * recipient_path: '',\n * audience: { mode: 'records', record_ids: selectedAttendeeIds },\n * variables: {}\n * });\n * ```\n */\n useSendBulkNotifications(): SdkSendBulkNotificationsHandle;\n\n /**\n * Read your saved bulk send and poll every two seconds while it is queued or processing.\n * A nullish ID keeps the query idle. Polling stops for completed, failed, or interrupted runs.\n * A completed run can contain item failures. Read queued_count, failed_count,\n * uncertain_count, and duplicate_count separately. Only the initiating identity can read this run.\n * @example\n * ```tsx\n * const progress = Sdk.hooks.useBulkNotificationRun(savedRunId);\n * return <p>{progress.data?.queued_count ?? 0} emails queued</p>;\n * ```\n */\n useBulkNotificationRun(runId: string | null | undefined): SdkApiHandle<SdkBulkNotificationRun>;\n\n /**\n * Queue the complete edited NotificationComposer draft without resolving its copy again.\n * The draft carries the template that preview resolved, including the type's default.\n * delivery_channel selects email (default), sms, or email_and_sms; an SMS send carries\n * the edited sms_body as written and needs the send_sms permission. The response\n * includes the notification history ID and the queued or suppressed SMS messages.\n * For a type without a recipient resource, omit recipient and supply to email objects.\n * Preview that type without recipient or to. Accepts the same `idempotency_key` as\n * useSendNotification; a repeated key sends nothing and returns `replayed: true`.\n * @example\n * const notification = Sdk.hooks.useSendResolvedNotification();\n * await notification.send({ notification_type_id: typeId, recipient: member, ...draft });\n */\n useSendResolvedNotification(options?: SdkUseSendEmailOptions): SdkSendResolvedNotificationHandle;\n /**\n * List notification types using the send_emails or send_sms permission. Results are\n * paginated. deliveryChannel keeps the types with a template for every requested channel.\n * Each type lists the `variables` a sender supplies with their label, type, and\n * required flag. Render their inputs with `Sdk.Components.NotificationVariablesForm`.\n * Call invalidate() to refresh after an administrator changes the catalog.\n * @example\n * const types = Sdk.hooks.useNotificationTypes({ recipientMode: 'notification' });\n * const choices = types.data?.data ?? [];\n */\n useNotificationTypes(\n options?: SdkNotificationTypeListOptions\n ): SdkApiHandle<SdkNotificationTypeList>;\n /**\n * List a type's templates, with is_default, is_email_enabled, and is_sms_enabled, using\n * the send_emails or send_sms permission. deliveryChannel keeps the templates that\n * support every requested channel. No request runs until notificationTypeId is set.\n * Results are paginated.\n * @example\n * const templates = Sdk.hooks.useNotificationTemplates(selectedTypeId);\n * const choices = templates.data?.data ?? [];\n */\n useNotificationTemplates(\n notificationTypeId: string | null | undefined,\n options?: SdkNotificationListOptions\n ): SdkApiHandle<SdkNotificationTemplateList>;\n\n /**\n * List pending and failed notifications associated with one source or recipient record.\n * Requires Show on that record; does not require send_emails. Returns scheduling metadata\n * only. can_cancel reflects current permissions; cancellation rechecks them on the server.\n * Waits until both IDs are present. Results are ordered by send time and ID.\n * @example\n * const upcoming = Sdk.hooks.useUpcomingNotifications({ resourceId, recordId, perPage: 10 });\n */\n useUpcomingNotifications(\n options: SdkUpcomingNotificationsOptions\n ): SdkApiHandle<SdkUpcomingNotifications>;\n\n /**\n * Schedule one notification for a notifiable record. Requires send_emails and Show on\n * the recipient and context records. Supply literal variables and an explicit template.\n * scheduled_at must be a future ISO timestamp with Z or an offset; convert local input\n * with new Date(value).toISOString(). Template values resolve again at send preparation.\n * Success refreshes upcoming-notification queries. The promise rejects with ApiError.\n * Use status: \"draft\" without scheduled_at to prepare an unscheduled draft. One-off drafts\n * require idempotency_key. Generated drafts use notification_schedule_id, source_record_id,\n * and stage_id. Saving a draft never releases it; use Send or transition: \"schedule\".\n * An `idempotency_key` makes a repeated schedule call return the first scheduled\n * notification unchanged. At send time the key also stops delivery when a send, bulk item,\n * or workflow already used it, so a manual send and a scheduled send of one notice reach the\n * recipient once.\n * @example\n * const notification = Sdk.hooks.useScheduleNotification();\n * await notification.schedule({ notification_type_id: typeId, notification_template_id: templateId, recipient, variables: {}, scheduled_at: new Date(value).toISOString() });\n */\n useScheduleNotification(): SdkScheduleNotificationHandle;\n\n /**\n * Cancel one pending occurrence. Requires send_emails, recipient Show, and Update on\n * the recipient for one-offs or Show and Update on the source for generated occurrences.\n * Does not change the shared schedule or opt out of future occurrences. Already queued\n * or expired items return 409. Success refreshes upcoming lists. Rejects with ApiError.\n * @example\n * const cancellation = Sdk.hooks.useCancelScheduledNotification();\n * await cancellation.cancel(notification.id, notification.revision);\n */\n useCancelScheduledNotification(): SdkCancelScheduledNotificationHandle;\n\n /**\n * Hold one pending occurrence. It keeps its ID, content, and time but does not send until\n * it is resumed. Send now and cancel still work while it is held. Requires the same access\n * as cancel. Repeated holds succeed. Success refreshes upcoming lists. Rejects with ApiError.\n * @example\n * const hold = Sdk.hooks.useHoldScheduledNotification();\n * await hold.hold(notification.id, notification.revision);\n */\n useHoldScheduledNotification(): SdkHoldScheduledNotificationHandle;\n\n /**\n * Resume a held occurrence so it sends once at its time. When that time passed during the\n * hold, pass a new future time; the old time never sends late. A generated occurrence keeps\n * the explicit time across schedule date changes. Rejects with ApiError.\n * @example\n * const resume = Sdk.hooks.useResumeScheduledNotification();\n * await resume.resume(notification.id, notification.revision, new Date(value).toISOString());\n */\n useResumeScheduledNotification(): SdkResumeScheduledNotificationHandle;\n\n /**\n * Load editable raw content for one occurrence. Waits when id is nullish.\n * Requires send_emails and Show/Update on the one-off recipient, or Show/Update\n * on the generated source and Show on its recipient. Context records are not returned.\n * @example\n * const detail = Sdk.hooks.useScheduledNotification(selectedId);\n */\n useScheduledNotification(\n scheduledNotificationId: string | null | undefined\n ): SdkApiHandle<SdkScheduledNotificationDetail>;\n\n /**\n * Edit a draft, scheduled, or failed occurrence. Pass its expected_revision.\n * A null content_override restores the template. Failed preparation recovers only\n * after successful resolution. Refreshes detail and all upcoming record lists.\n * @example\n * const editing = Sdk.hooks.useUpdateScheduledNotification();\n * await editing.update(id, { expected_revision: notification.revision, content_override: null });\n */\n useUpdateScheduledNotification(): SdkUpdateScheduledNotificationHandle;\n\n /**\n * Apply edits and send the same draft, scheduled, or failed occurrence now, including\n * paused occurrences. The original time cannot send it again. Rejects terminal\n * items with 409. Refreshes detail, upcoming lists, and email history.\n * @example\n * const sending = Sdk.hooks.useSendScheduledNotificationNow();\n * await sending.sendNow(id, { expected_revision: notification.revision });\n */\n useSendScheduledNotificationNow(): SdkSendScheduledNotificationNowHandle;\n\n /**\n * Read tracked email rows, including ad hoc and template emails, for custom tables.\n * Uses the same scope, filters, permissions, and send invalidation as EmailMessages.\n * Each email appears once even when linked to several scoped records. Empty records\n * leave the query idle. Render isError before treating missing data as an empty result.\n * Changing scope, filters, or page clears the previous rows while the query loads.\n * Pagination covers emails only: merge and paginate other activity sources explicitly.\n * @example\n * const emails = Sdk.hooks.useEmailMessages({\n * records: [{ resourceId: businessResource.id, recordId: business.id }],\n * page: 1,\n * perPage: 20\n * });\n */\n useEmailMessages(options: SdkEmailMessagesOptions): SdkApiHandle<SdkEmailMessages>;\n\n /**\n * List notifications by lifecycle status: upcoming, sent, failed, cancelled, or expired.\n * Pass `records` for record-scoped history (Show access to each record) or `workspace`\n * for every notification (`manage_notifications`). Filters narrow the scope and follow\n * the admin notification list. Sent rows keep saved names after template edits.\n * @example\n * const history = Sdk.hooks.useNotificationHistory({ records: [{ resourceId, recordId }], status: 'sent' });\n */\n useNotificationHistory(\n options: SdkNotificationHistoryOptions\n ): SdkApiHandle<SdkNotificationHistory>;\n\n /**\n * Load one notification and its email attempts with delivery events. Pass the list row's\n * `kind` and `id`. Waits when either is nullish. Reads require `manage_notifications` or\n * Show access to a linked record. Load an attempt body with `Sdk.Components.NotificationHistory`\n * or `Sdk.Components.EmailMessages`.\n * @example\n * const detail = Sdk.hooks.useNotificationHistoryDetail(selected?.kind, selected?.id);\n */\n useNotificationHistoryDetail(\n kind: SdkNotificationHistoryKind | null | undefined,\n notificationId: string | null | undefined\n ): SdkApiHandle<SdkNotificationHistoryDetail>;\n\n /**\n * Form hook for creating a resource record via the JSON API.\n * Returns `{ form, FieldInput, Form }` \u2014 a handle with form state, a context-wired field\n * input component, and a submit-pre-wired form wrapper.\n *\n * @aiHidden Kept on the runtime SDK for existing pages, but omitted from the generated\n * SDK reference so the code-builder agent is steered to `Sdk.Components.CreateResourceRecordForm`.\n *\n * @example\n * ```tsx\n * function CreateStudent() {\n * const students = Sdk.resource('...');\n * const { form, FieldInput, Form } = Sdk.hooks.useCreateResourceRecordForm({\n * resource: students,\n * onSuccess: (record) => console.log('Created', record)\n * });\n *\n * return (\n * <Form className=\"grid gap-4\">\n * <FieldInput field={students.fields.first_name} />\n * <FieldInput field={students.fields.email} />\n * <button type=\"submit\" disabled={form.isSubmitting}>Create</button>\n * </Form>\n * );\n * }\n * ```\n */\n useCreateResourceRecordForm(options: {\n resource: CreateResourceRecordFormResource;\n fields?: SdkResourceField[];\n /** Editable seed values \u2014 pre-fill fields the user can still change. */\n initialValues?: Record<string, any>;\n /** Locked writable Resource field values \u2014 rendered disabled and submitted even when\n * omitted from `fields`; unknown, system, virtual, or inaccessible keys are ignored. */\n fixedValues?: Record<string, any>;\n /** Include every creatable field, ignoring per-field `show_on_creating` form\n * visibility. Still excludes system/virtual fields and respects field access. */\n includeAllFields?: boolean;\n /** How relation fields resolve inline attach/create. Defaults to `'settings'`;\n * pass `'always'` to force them on. */\n relationVisibility?: RelationVisibility;\n /** Send a booking idempotency key, kept across retries of the same values. Defaults to\n * `false`. Submitting throws when the resource has no booking idempotency field. */\n idempotent?: boolean;\n onSuccess?: (record: ResourceRecord) => void;\n /** Called when a submit fails, after the form shows its errors. */\n onError?: (error: any) => void;\n }): UseCreateResourceRecordFormResult;\n\n /**\n * Form hook for updating an existing resource record via the JSON API.\n * Loads the record and seeds the form before rendering fields.\n * Returns `{ form, FieldInput, Form, interaction }` \u2014 same shape as the create hook,\n * with an additional `form.isLoading` flag while the record is being fetched and a\n * ready-to-render `interaction` node for capability-aware pre-submit prompts.\n * Direct hook consumers must render `interaction` next to `Form`; otherwise recurring\n * eventable updates can pause at the scope prompt without showing it.\n *\n * @aiHidden Kept on the runtime SDK for existing pages, but omitted from the generated\n * SDK reference so the code-builder agent is steered to `Sdk.Components.UpdateResourceRecordForm`.\n *\n * @example\n * ```tsx\n * function EditStudent({ recordId }) {\n * const students = Sdk.resource('...');\n * const { form, FieldInput, Form, interaction } = Sdk.hooks.useUpdateResourceRecordForm({\n * resource: students,\n * recordId,\n * onSuccess: (record) => console.log('Updated', record)\n * });\n *\n * if (form.isLoading) return <Sdk.Components.Skeleton />;\n *\n * return (\n * <>\n * <Form className=\"grid gap-4\">\n * <FieldInput field={students.fields.first_name} />\n * <FieldInput field={students.fields.email} />\n * <button type=\"submit\" disabled={form.isSubmitting}>Save</button>\n * </Form>\n * {interaction}\n * </>\n * );\n * }\n * ```\n */\n useUpdateResourceRecordForm(options: {\n resource: UpdateResourceRecordFormResource;\n recordId: string | null | undefined;\n fields?: SdkResourceField[];\n /** Editable seed values applied before the record loads (or when no `recordId`). */\n initialValues?: Record<string, any>;\n /** Locked writable Resource field values \u2014 rendered disabled and submitted even when\n * omitted from `fields`; unknown, system, virtual, or inaccessible keys are ignored. */\n fixedValues?: Record<string, any>;\n /** Include every updatable field, ignoring per-field `show_on_updating` form\n * visibility. Still excludes system/virtual fields and respects field access. */\n includeAllFields?: boolean;\n /** How relation fields resolve inline attach/create. Defaults to `'settings'`;\n * pass `'always'` to force them on. */\n relationVisibility?: RelationVisibility;\n onSuccess?: (record: ResourceRecord) => void;\n }): UseUpdateResourceRecordFormResult;\n\n /**\n * Schema-aware workflow form hook \u2014 the headless counterpart of\n * `Sdk.Components.WorkflowForm`. Resolves a\n * published Request workflow's input schema (query fields for GET, body fields for\n * POST/PUT/PATCH, none for DELETE) and returns `{ form, FieldInput, Form, fields,\n * method, isAvailable }`. Reach for it when the form UI must escape the component's\n * managed shell \u2014 e.g. a submit button rendered outside the `<form>`, a bespoke\n * multi-step wizard, or hand-wired inputs bound through `form.data`/`form.setData`.\n * Prefer `Sdk.Components.WorkflowForm` when the default managed form fits.\n * `form.errors` contains only 422 field validation. Other failures are available\n * as `form.failure`; for example, branch on\n * `Sdk.utils.error.isConflict(form.failure)` without inspecting status codes.\n * Success callbacks receive the parsed workflow body directly. Synchronous\n * output can be a scalar, list, record, or object; asynchronous workflows return\n * `{ execution_id }`. Narrow the value before reading authored output fields.\n * Match `initialValues` keys to the workflow's expected `request.body` keys;\n * `initialValues` is captured once on mount, so when seeding from fetched data gate\n * rendering until it has loaded. Rich text inputs use `Sdk.Components.FormContentInput`\n * initialized as TipTap JSON. Markdown inputs use `Sdk.Components.FormMarkdownInput`;\n * disable submit while `form.isSubmitting` or `!form.isReady`.\n *\n * @example\n * ```tsx\n * function InvitePanel() {\n * const { form, FieldInput, Form, fields, isAvailable } = Sdk.hooks.useWorkflowForm({\n * workflowId: 'send-invite'\n * });\n *\n * if (!isAvailable) return null;\n *\n * return (\n * <div className=\"grid gap-4\">\n * <Form className=\"grid gap-4\">\n * {fields.map((field) => <FieldInput key={field.key} field={field} />)}\n * </Form>\n * <Sdk.Components.Button onClick={form.submit} disabled={form.isSubmitting}>\n * Send invite\n * </Sdk.Components.Button>\n * </div>\n * );\n * }\n * ```\n */\n useWorkflowForm(options: {\n /** The workflow's slug or ID. Both values are stable across title renames. */\n workflowId: WorkflowReference;\n /** Route parameter values for parameterized published routes\n * (e.g. `/invites/{id}` needs `params: { id: recordId }`). */\n params?: Record<string, any>;\n /** Input-field keys controlling which schema fields are exposed and their order.\n * Omit for every field from the workflow's input schema; unknown keys are ignored. */\n fields?: string[];\n /** Editable seed values \u2014 captured once on mount; they form the `reset()` baseline. */\n initialValues?: Record<string, any>;\n /** Locked values \u2014 rendered as disabled fields and always submitted. */\n fixedValues?: Record<string, any>;\n /** Defaults to `true` for mutation workflows and `false` for GET workflows, so a\n * search form keeps its filters. */\n resetOnSuccess?: boolean;\n onSuccess?: (response: SdkWorkflowResponse) => void;\n onError?: (context: { status: number; data: any; error: any }) => void;\n }): UseWorkflowFormResult;\n\n /**\n * Form state for editing workspace field values, for layouts that must escape the\n * managed `Sdk.Components.WorkspaceFieldsForm` shell. Submits to\n * `PUT /api/data/workspace-fields/values`; requires `update_workspace_fields` or `manage_workspace_fields`\n * permission.\n *\n * @example\n * ```tsx\n * const { fields } = Sdk.workspace!;\n * const { form, canSubmit, FieldInput, Form } = Sdk.hooks.useWorkspaceFieldsForm({ fields: [fields.support_email] });\n * return (\n * <Form>\n * <FieldInput field={fields.support_email} />\n * <Sdk.Components.Button type=\"submit\" disabled={form.isSubmitting || !canSubmit}>Save</Sdk.Components.Button>\n * </Form>\n * );\n * ```\n */\n useWorkspaceFieldsForm(options: SdkUseWorkspaceFieldsFormOptions): UseWorkspaceFieldsFormResult;\n\n /**\n * Imperatively runs a published Request workflow while managing reactive\n * `{ isPending, errorMessage, failure, result }` state. Use this for button-triggered\n * actions that do not need schema-driven form state. `run(inputs)` resolves with\n * the parsed workflow body on success and `null` on failure; it never rejects.\n * Only the latest concurrent run may update the returned state.\n *\n * Set `idempotent: true` to add a generated UUID under the conventional\n * `idempotency_key` input. Identical retries keep the key, while changed inputs or\n * route parameters rotate it, including the conflict/failure -> edit -> retry flow.\n * `reset()` also starts a fresh key lifecycle. The hook does not poll asynchronous\n * executions.\n *\n * @example\n * ```tsx\n * function ReserveButton({ slotId }) {\n * const action = Sdk.hooks.useWorkflowAction({\n * workflowId: 'reserve-slot',\n * idempotent: true\n * });\n *\n * return (\n * <div>\n * <Sdk.Components.Button\n * disabled={action.isPending}\n * onClick={() => action.run({ slot_id: slotId })}\n * >\n * Reserve\n * </Sdk.Components.Button>\n * {Sdk.utils.error.isConflict(action.failure) ? (\n * <Sdk.Components.Alert>That slot was just taken. Choose another.</Sdk.Components.Alert>\n * ) : action.errorMessage ? (\n * <Sdk.Components.Alert>{action.errorMessage}</Sdk.Components.Alert>\n * ) : null}\n * </div>\n * );\n * }\n * ```\n */\n useWorkflowAction(options: {\n /** The workflow's slug or ID. Both values are stable across title renames. */\n workflowId: WorkflowReference;\n /** Reactive route parameter values for parameterized published routes\n * (e.g. `/invites/{id}` needs `params: { id: recordId }`). */\n params?: Record<string, any>;\n /** Generate and manage the conventional `idempotency_key` input. */\n idempotent?: boolean;\n }): UseWorkflowActionResult;\n\n /**\n * Reactive timelines of a record's schedule workflow runs, the data behind\n * `Sdk.Components.RecordScheduleDisplay`, for pages that render a custom\n * schedule UI. For each published record-schedule workflow the record participates\n * in, returns the anchor date and every offset in chronological order with its\n * state (`pending`, `sent`, `skipped`, or `manually_skipped`) and an `is_next`\n * flag on the upcoming run. Show `manually_skipped` offsets distinctly from\n * automatic skips. Call `skipOffset({ workflowId, offset, scheduledRunAt })` to\n * skip one upcoming pending occurrence. Recomputing the same date preserves its\n * skip; a changed reminder date does not inherit the old skip.\n * Skipping requires Show and Update access to the record. Viewers only see the\n * schedule of records they are allowed to see. Prefer the component when the\n * default timeline rendering fits.\n *\n * @example\n * ```tsx\n * function NextReminder({ invoiceId }) {\n * const { timelines, isLoading } = Sdk.hooks.useRecordScheduleTimelines({\n * resourceId: '0192a1b2-c3d4-7e5f-8a6b-9c0d1e2f3a4b',\n * recordId: invoiceId\n * });\n *\n * if (isLoading) return <Sdk.Components.Skeleton />;\n *\n * const next = timelines\n * .flatMap((timeline) => timeline.offsets)\n * .find((offset) => offset.is_next);\n *\n * return <p>{next ? `Next reminder: ${next.scheduled_run_at}` : 'Nothing scheduled'}</p>;\n * }\n * ```\n */\n useRecordScheduleTimelines(\n options: SdkUseRecordScheduleTimelinesOptions\n ): SdkRecordScheduleTimelinesHandle;\n\n /**\n * Builds a client-side table from seeded, in-memory data (no resource). Sorting,\n * search, and pagination run in the browser. Pair the returned `table` with\n * `Sdk.Components.DataTable`.\n *\n * @example\n * ```tsx\n * const { table } = Sdk.hooks.useDataTable({\n * data: rows,\n * columns: [{ accessor: 'name', header: 'Name' }, { accessor: 'total', header: 'Total' }]\n * });\n * return <Sdk.Components.DataTable table={table} searchable />;\n * ```\n */\n useDataTable<TData extends Record<string, any>>(\n options: UseDataTableOptions<TData>\n ): UseDataTableResult<TData>;\n\n /**\n * Batteries-included resource-backed table. Creates columns for every Index-scope field\n * the viewer can access, uses `initialFields` to select which start visible, wires\n * server-side search / sort / pagination / filters, fetches records, and returns a\n * TanStack `table` for `Sdk.Components.DataTable`. Use `fieldOverrides` for the uncommon\n * field that needs a custom cell. Each cell override receives the row plus row-bound\n * `FieldValue` / `FieldLabel` helpers. Relation field overrides receive the preloaded relation\n * value on the typed row. An override for a field that policy removes does not run.\n *\n * The `columns` option provides complete column control. It accepts field-handle shorthand,\n * an explicit field column (`{ field, header?, enableSorting?, width?, className? }`), or a\n * custom column. Cells wrap at 240px unless the column sets `width`, so one long name or email\n * cannot push the other columns out of view. A `max-w-*` class in `className` replaces the limit.\n * Nested relation field handles (e.g. `students.fields.family.fields.name`) load their\n * relations automatically, but cannot sort.\n * Custom columns that read relation properties from `row` should also list those paths in\n * `relations`; this keeps the callback's record type aligned with its requested data.\n * Resource columns never take react-table `{ accessor, header }` objects \u2014 that shape is\n * `useDataTable`-only (seeded in-memory data) and fails the save-time type check here.\n *\n * Authored `filters` narrow every query invisibly; filter relation fields by nested\n * scalar paths such as `family.id`, not the relation object path (`family`). The\n * returned `filters` object powers the end-user advanced-filters dialog \u2014 pass it to\n * `Sdk.Components.DataTable`'s `filters` prop to show the \"Filters\" toolbar button.\n *\n * @example\n * ```tsx\n * const students = Sdk.resource('students');\n * const { table, isLoading, isError, refetch, filters } = Sdk.hooks.useResourceDataTable({\n * resource: students,\n * initialFields: [students.fields.email, students.fields.family, students.fields.status],\n * fieldOverrides: {\n * family: {\n * cell: (row) => <strong>{row.family.name}</strong>\n * }\n * },\n * filters: { operator: 'and', conditions: [{ field: 'status', operator: '=', value: 'active' }] }\n * });\n *\n * return (\n * <Sdk.Components.DataTable\n * table={table}\n * isLoading={isLoading}\n * isError={isError}\n * onRetry={refetch}\n * searchable\n * filters={filters}\n * onRowClick={(row) => Sdk.router.visit(students.urls.show(row.id))}\n * />\n * );\n * ```\n */\n useResourceDataTable<\n TResource extends ResourceDataTableResource,\n const TRelations extends readonly SdkResourceRelationPath<NoInfer<TResource>>[] = readonly []\n >(\n options: UseResourceDataTableOptions<TResource, TRelations>\n ): UseResourceDataTableResult<SdkResourceRecord<TResource, TRelations>>;\n\n /**\n * Manages calendar view mode, focal date, the visible window, title, and navigation.\n * Pair its `windowStart` and `windowEnd` with `useCalendarEvents`.\n *\n * @example\n * ```tsx\n * const cal = Sdk.hooks.useCalendarView({ initialView: 'month' });\n * return <Sdk.Components.Calendar events={events} view={cal.view} date={cal.date} />;\n * ```\n */\n useCalendarView(options?: {\n initialView?: 'month' | 'week' | 'day';\n initialDate?: Date;\n }): UseCalendarViewResult;\n\n /**\n * Fetches calendar events for a resource and normalises them to the canonical event shape.\n * Pass a stable `from`/`to` window, such as `useCalendarView`'s `windowStart`/`windowEnd`.\n * A window built from `new Date()` during render changes on every render and makes the\n * hook request without end.\n *\n * A resource whose relations point at Schedulable records returns one event for each\n * related Schedulable record, with the same `id`. A booking of a therapist, a room, and a\n * client is three events, one for each calendar, and `event.record.$calendar.id` names the\n * calendar. Show one calendar at a time with a selector, or keep the first event of each\n * `id`.\n *\n * @example\n * ```tsx\n * const { events, isLoading } = Sdk.hooks.useCalendarEvents({\n * resourceId: 'YOUR_RESOURCE_ID',\n * from: cal.windowStart,\n * to: cal.windowEnd\n * });\n * const seen = new Set<string>();\n * const oneEventPerBooking = events.filter((event) => {\n * if (seen.has(event.id)) return false;\n * seen.add(event.id);\n * return true;\n * });\n * ```\n */\n useCalendarEvents<\n TResourceId extends string,\n const TRelations extends readonly SdkCatalogRelationPath<TResourceId>[] = readonly []\n >(\n options: Omit<UseCalendarEventsOptions, 'relations' | 'resourceId'> & {\n resourceId: TResourceId;\n relations?: TRelations;\n }\n ): UseCalendarEventsResult<SdkCatalogRecord<TResourceId, TRelations>>;\n\n /**\n * Manages an optimistic placeholder event while a calendar create flow is open.\n *\n * @example\n * ```tsx\n * const newEvent = Sdk.hooks.useNewCalendarEvent({ placeholderTitle: 'New Lesson' });\n * return <Sdk.Components.Calendar events={newEvent.mergeEvents(events)} />;\n * ```\n */\n useNewCalendarEvent(options?: UseNewCalendarEventOptions): UseNewCalendarEventResult;\n\n /**\n * Hook that loads the schedulable resources (\"calendars\" \u2014 staff, rooms, users) tied to an\n * eventable resource, owns the multi-select state, and composes the selection into a `filters`\n * object ready to hand to `Sdk.hooks.useCalendarEvents`.\n *\n * @remarks\n * Returns `groups` (the selectable calendars, grouped by schedulable resource), `isLoading`,\n * `selectedIds`, `setSelectedIds`, and `filters`. By default every calendar is selected.\n * Pass `groups`/`isLoading`/`selectedIds`/`setSelectedIds` to `Sdk.Components.CalendarSelector`\n * and pass `filters` straight to `Sdk.hooks.useCalendarEvents({ filters })` \u2014 the OR-group fan-out\n * across each calendar's relation paths is handled for you. Selection is in-memory and resets\n * on reload. Provide your own base `filters` to AND additional constraints onto the query.\n *\n * @example\n * ```tsx\n * const cal = Sdk.hooks.useCalendarView();\n * const calendars = Sdk.hooks.useCalendarSelection({ resourceId: 'SESSIONS_ID' });\n * const { events } = Sdk.hooks.useCalendarEvents({\n * resourceId: 'SESSIONS_ID',\n * from: cal.windowStart,\n * to: cal.windowEnd,\n * filters: calendars.filters\n * });\n *\n * return (\n * <div>\n * <Sdk.Components.CalendarSelector\n * groups={calendars.groups}\n * isLoading={calendars.isLoading}\n * selectedIds={calendars.selectedIds}\n * onChange={calendars.setSelectedIds}\n * />\n * <Sdk.Components.Calendar events={events} view={cal.view} date={cal.date} />\n * </div>\n * );\n * ```\n */\n useCalendarSelection(options: UseCalendarSelectionOptions): UseCalendarSelectionResult;\n\n /**\n * Reactive workspace field values for the given keys. Returns one object keyed by the\n * requested keys; an unknown key is `undefined`. Values update after a\n * `WorkspaceFieldsForm` save on this page. Prefer this over `Sdk.workspace.values`,\n * which is a load-time snapshot.\n *\n * The page type check gives each requested value its workspace field's type from the\n * workspace field catalog (`index_workspace_fields` lists the keys and types): for\n * example, a text or email field reads as a `string`. Use the value as that type. Do not\n * add `typeof` checks; the `unknown` in the signature applies only to keys outside the\n * catalog.\n *\n * @example\n * ```tsx\n * const { registration_mode, admin_notification_email } = Sdk.hooks.useWorkspaceValues([\n * 'registration_mode',\n * 'admin_notification_email'\n * ]);\n * ```\n */\n useWorkspaceValues<K extends keyof WorkspaceFieldCatalog & string>(keys: readonly K[]): Pick<WorkspaceFieldCatalog, K>;\n\n /**\n * Reads the sidebar shell state from the nearest `Sdk.Components.SidebarProvider`.\n * Returns the expanded/collapsed `state`, mobile flags, and `toggleSidebar` for\n * building collapse controls. Throws when called outside a SidebarProvider.\n *\n * @example\n * ```tsx\n * function CollapseButton() {\n * const { state, toggleSidebar } = Sdk.hooks.useSidebar();\n * const collapsed = state === 'collapsed';\n * return (\n * <Sdk.Components.SidebarMenuButton onClick={toggleSidebar}>\n * <Sdk.Components.Icon name={collapsed ? 'PanelLeftOpen' : 'PanelLeftClose'} className=\"size-4\" />\n * <span>{collapsed ? 'Expand' : 'Collapse'}</span>\n * </Sdk.Components.SidebarMenuButton>\n * );\n * }\n * ```\n */\n useSidebar(): {\n state: 'expanded' | 'collapsed';\n open: boolean;\n setOpen: (open: boolean) => void;\n openMobile: boolean;\n setOpenMobile: (open: boolean) => void;\n isMobile: boolean;\n toggleSidebar: () => void;\n };\n\n /**\n * Headless state machine for multi-step / wizard forms. Tracks the active step and exposes\n * free navigation (`goTo`/`next`/`back`) \u2014 no validation or gating. Use this for custom step\n * chrome; for the default indicator + Previous/Next UI, use `Sdk.Components.Stepper` instead.\n *\n * @aiHidden Kept on the runtime SDK for custom-chrome pages, but omitted from the generated\n * SDK reference \u2014 the code-builder agent should use the `steps` prop on\n * `Sdk.Components.CreateResourceRecordForm` / `UpdateResourceRecordForm` for multi-step\n * record forms, or `Sdk.Components.Stepper` for wizards that are not record forms.\n *\n * @example\n * ```tsx\n * const { step, steps, label, next, back, isFirst, isLast } = Sdk.hooks.useStepper([\n * 'General', 'Education', 'Review'\n * ]);\n * ```\n */\n useStepper(steps: readonly string[], options?: UseStepperOptions): UseStepperResult;\n\n /**\n * Persist JSON-safe UI state in the browser. Returns `[value, setter]`. The setter\n * accepts a value or functional update; setting `null` removes the stored value.\n * Persistence and synchronization follow the repository's standard local-storage hook.\n *\n * The supplied key is used directly. Choose a stable, page-specific key to avoid\n * collisions with other pages or application features.\n *\n * Use only for non-sensitive UI preferences such as dismissed notices, selected tabs,\n * filters, and display density. Never store secrets, authentication data or tokens,\n * PII, resource records, or sensitive drafts in local storage. Logout clears all stored\n * values.\n *\n * @example\n * ```tsx\n * const [density, setDensity] = Sdk.hooks.useLocalStorage('table-density', 'comfortable');\n *\n * setDensity('compact');\n * setDensity((current) => current === 'compact' ? 'comfortable' : 'compact');\n * setDensity(null); // Remove the stored value.\n * ```\n */\n useLocalStorage(\n key: string,\n initial: string\n ): readonly [value: string | null, setter: SdkLocalStorageSetter<string>];\n useLocalStorage(\n key: string,\n initial: boolean\n ): readonly [value: boolean | null, setter: SdkLocalStorageSetter<boolean>];\n useLocalStorage(\n key: string,\n initial: number\n ): readonly [value: number | null, setter: SdkLocalStorageSetter<number>];\n useLocalStorage<TValue extends SdkLocalStorageValue>(\n key: string,\n initial: TValue\n ): SdkLocalStorageResult<TValue>;\n\n /**\n * Read and control the app theme (system / light / dark). Returns the resolved `theme`\n * currently applied \u2014 reactive, so the component re-renders when it changes \u2014 plus\n * the saved `mode` preference (`'system'` follows the device setting) and the\n * `setTheme` / `toggleTheme` setters. `toggleTheme` cycles system \u2192 dark \u2192 light.\n * The choice persists across visits and applies to the whole app, not just the current\n * page. For the standard toggle button, use `Sdk.Components.ThemeToggle` \u2014 reach for\n * this hook when the page needs custom theme UI.\n *\n * @example\n * ```tsx\n * function ThemeToggle() {\n * const { mode, toggleTheme } = Sdk.hooks.useTheme();\n *\n * return (\n * <Sdk.Components.Button variant=\"ghost\" onClick={toggleTheme}>\n * Theme: {mode}\n * </Sdk.Components.Button>\n * );\n * }\n * ```\n */\n useTheme(): UseThemeResult;\n};\n\ntype QueryParams = Record<string, string | string[]>;\n\ntype SetQueryParams<TParams extends Record<string, any>> = (\n next: QueryParamsPatch<TParams> | ((current: TParams) => QueryParamsPatch<TParams>)\n) => void;\n\ntype QueryParamsPatch<TParams extends Record<string, any>> = Partial<{\n [TKey in keyof TParams]: QueryParamSetValue<TParams[TKey]>;\n}>;\n\ntype QueryParamSetValue<TValue> = DefinedQueryParamSetValue<NonNullable<TValue>>;\n\ntype DefinedQueryParamSetValue<TValue> = TValue extends number\n ? number | null | undefined\n : TValue extends boolean\n ? boolean | null | undefined\n : TValue extends readonly string[]\n ? readonly string[] | null | undefined\n : string | null | undefined;\n\ntype QueryParamSchema = Record<string, QueryParamSchemaEntry>;\n\ntype QueryParamSchemaEntry =\n | { type?: 'string'; default?: string }\n | { type: 'number'; default?: number }\n | { type: 'boolean'; default?: boolean }\n | { type: 'array'; default?: readonly string[] };\n\ntype QueryParamsFromSchema<TSchema extends QueryParamSchema> = {\n [TKey in keyof TSchema]: TSchema[TKey] extends { default: any }\n ? QueryParamValue<TSchema[TKey]>\n : QueryParamValue<TSchema[TKey]> | undefined;\n};\n\ntype QueryParamValue<TEntry extends QueryParamSchemaEntry> = TEntry extends { type: 'number' }\n ? number\n : TEntry extends { type: 'boolean' }\n ? boolean\n : TEntry extends { type: 'array' }\n ? string[]\n : string;\n\ntype SdkUseSharedFiltersOptions = any;\n\ntype SharedFiltersApi = SharedFilterViewState & {\n /** Views the viewer may use or manage, subject to resource policy visibility. */\n available: SharedFilter[];\n isLoading: boolean;\n selectedId: string | null;\n selected: SharedFilter | null;\n /** Applies the stored view state; `null` resets everything to defaults. */\n select: (sharedFilterId: string | null) => void;\n setFilter: (filter: FilterGroup | null) => void;\n /** Replaces the ordered sort list. An empty list clears the sort. */\n setSorts: (sorts: ResourceSortDescriptor[]) => void;\n /** Replaces the whole sort list with this one sort, or clears it when `null`. */\n setSort: (sort: ResourceSortDescriptor | null) => void;\n setSearch: (search: string) => void;\n setPerPage: (perPage: number | null) => void;\n setColumns: (columns: string[] | null) => void;\n /**\n * The view state has been edited away from the selected saved view, or from the\n * defaults when no saved view is selected. A selection that has not loaded yet is\n * never dirty.\n */\n isDirty: boolean;\n /** Re-applies the stored view state, discarding local edits. */\n reset: () => void;\n /** Whether the viewer may save the current view state as a new private view. */\n canCreate: boolean;\n /** Whether the viewer may choose a saved view's sharing scope (the Manage Shared Filters permission). */\n canShare: boolean;\n /**\n * Saves the current view state as a new saved view and selects it. Private by\n * default; `isPrivate: false` shares it with everyone (honored only when `canShare`).\n */\n create: (input: { name: string; isPrivate?: boolean }) => Promise<SharedFilter>;\n isCreating: boolean;\n /** Overwrites the selected view state with the current one. */\n update: () => Promise<SharedFilter>;\n isUpdating: boolean;\n /** Deletes a saved view the viewer manages; the selection falls back to defaults. */\n remove: (sharedFilterId: string) => Promise<void>;\n /**\n * Renames a saved view the viewer manages \u2014 and, when `canShare`, optionally\n * changes its sharing scope. Never sends the view state.\n */\n rename: (\n sharedFilterId: string,\n input: { name: string; isPrivate?: boolean }\n ) => Promise<SharedFilter>;\n};\n\ntype SharedFilterViewState = {\n /** Filter conditions, in the records API's `$filter` shape. */\n filter: FilterGroup | null;\n /** Ordered sort, primary first, directly usable as a `useRecords` sort input. Empty = not configured. */\n sorts: ResourceSortDescriptor[];\n /** The primary sort (`sorts[0]`), or `null` when no sort is configured. */\n sort: ResourceSortDescriptor | null;\n /** Free-text search. Empty string = no search. */\n search: string;\n /** Page size, or `null` for the consumer's default. */\n perPage: number | null;\n /** Visible table column identifiers, including custom IDs, in display order; `null` uses defaults. */\n columns: string[] | null;\n};\n\ntype FilterGroup = {\n operator: LogicalOperator;\n conditions: FilterNode[];\n when?: boolean | Resolver | null;\n};\n\ntype LogicalOperator = 'and' | 'or' | 'nor';\n\ntype FilterNode = FilterGroup | FilterCondition;\n\ntype FilterCondition = {\n field: any;\n operator: string;\n value: any;\n when?: boolean | Resolver | null;\n};\n\ntype Resolver = {\n $type: string;\n $value: any;\n};\n\ntype SharedFilter = SharedFiltersResponse['data'][number];\n\ntype SharedFiltersResponse = any;\n\ntype DeleteResourceRecordResource = Pick<\n SdkResource,\n 'id' | 'name' | 'disabled_data_api_endpoints'\n> & {\n urls: Pick<ResourceUrls, 'delete'>;\n};\n\ninterface DeleteResourceRecordState {\n preview: PreviewData | undefined;\n isPreviewLoading: boolean;\n isPreviewError: boolean;\n confirm: (operation?: OperationMetadata) => void;\n isProcessing: boolean;\n /**\n * Structured error body returned by the API on a blocked delete response.\n * `null` for successful deletes and generic transport errors.\n */\n deleteError: DeleteResourceRecordErrorBody | null;\n /** Normalized failed delete request, or `null`. */\n error: SdkApiError | null;\n reset: () => void;\n}\n\ntype PreviewData = any;\n\ntype OperationMetadata = Record<string, Record<string, any>>;\n\ntype DeleteResourceRecordErrorBody = PreviewData & {\n title: string;\n status: 409;\n detail: string;\n};\n\ntype RestoreResourceRecordResource = Pick<\n SdkResource,\n 'id' | 'name' | 'disabled_data_api_endpoints'\n> & {\n urls: Pick<ResourceUrls, 'restore'>;\n};\n\ninterface RestoreResourceRecordState {\n preview: PreviewData | undefined;\n isPreviewLoading: boolean;\n isPreviewError: boolean;\n confirm: () => void;\n isProcessing: boolean;\n /**\n * Transport error from the restore request, surfaced so the dialog can show\n * a 403 (no policy) or 422 (validation) message. `null` on success.\n */\n restoreError: AxiosError | null;\n reset: () => void;\n}\n\ntype AxiosError = any;\n\ntype CreateResourceRecordFormResource = Pick<\n SdkResource,\n 'id' | 'fields' | 'workflow_inputs' | 'name' | 'disabled_data_api_endpoints' | 'capabilities'\n> & {\n urls: Pick<ResourceUrls, 'store'>;\n};\n\ntype RelationVisibility = 'settings' | 'always' | 'attach-only';\n\ntype UseCreateResourceRecordFormResult = {\n /** The form handle \u2014 `data`, `errors`, `fields`, `isSubmitting`, `submit`, `reset`, `setData`. */\n form: SdkResourceForm;\n /**\n * Context-wired field input. Renders the correct editor for a resource field handle;\n * returns `null` for handles that are not part of the active form.\n * Must be rendered inside `Form`.\n */\n FieldInput: (props: FormFieldProps) => React.ReactNode;\n /** Context-wired RecordEvent workflow input. Must be rendered inside `Form`. */\n WorkflowInput: (props: WorkflowInputProps) => React.ReactNode;\n /** Renders every applicable RecordEvent workflow input, stacked in order.\n * Must be rendered inside `Form`. */\n WorkflowInputs: (props: WorkflowInputsProps) => React.ReactNode;\n /** The applicable RecordEvent workflow inputs, keyed by input key. */\n workflowInputs: Record<string, SdkWorkflowInput>;\n /**\n * Form wrapper with `onSubmit` pre-wired to `form.submit`. Provides the context that\n * `FieldInput` reads from. Renders a `<form>` element; accepts `className` and `children`.\n */\n Form: (props: FormWrapperProps) => React.ReactNode;\n};\n\ntype SdkResourceForm = {\n /**\n * Current form data keyed by resource field. `$operation` is reserved for transient\n * mutation metadata: it is validated and delivered to record-event workflows, but\n * is never persisted on or returned as part of the record. Treat it as untrusted\n * trigger input in downstream workflows.\n */\n data: Record<string, any>;\n errors: Record<string, string>;\n fields: SdkResourceField[];\n isSubmitting: boolean;\n submit: () => void;\n reset: (...keys: string[]) => void;\n /**\n * Set a form value. For recurring eventable updates, use\n * `setData('$operation', { eventable: { scope } })` where `scope` is\n * `this`, `this_and_future`, or `all`.\n */\n setData: (key: string, value: any) => void;\n};\n\ntype FormFieldProps = {\n /** A resource field handle (`resource.fields.someField`) or a workspace field\n * definition (`Sdk.workspace.fields.someField`). */\n field: SdkFormField;\n /** Optional wrapper className. */\n className?: string;\n};\n\ntype WorkflowInputProps = {\n /** A workflow input handle from the render state's `workflowInputs.<key>` map\n * (or `resource.workflow_inputs.<event>.<key>`). */\n input: SdkWorkflowInput;\n /** Optional wrapper className. */\n className?: string;\n};\n\ntype WorkflowInputsProps = {\n /** Optional wrapper className. */\n className?: string;\n};\n\ntype FormWrapperProps = {\n children: React.ReactNode;\n className?: string;\n};\n\ntype UpdateResourceRecordFormResource = Pick<\n SdkResource,\n | 'id'\n | 'fields'\n | 'workflow_inputs'\n | 'name'\n | 'disabled_data_api_endpoints'\n | 'capabilities'\n | 'singular_name'\n> & {\n urls: Pick<ResourceUrls, 'show' | 'update'>;\n};\n\ntype UseUpdateResourceRecordFormResult = {\n /** The form handle \u2014 `data`, `errors`, `fields`, `isSubmitting`, `isLoading`, `submit`, `reset`, `setData`. */\n form: SdkResourceForm & { isLoading: boolean };\n /**\n * Context-wired field input. Renders the correct editor for a resource field handle;\n * returns `null` for handles that are not part of the active form.\n * Must be rendered inside `Form`.\n */\n FieldInput: (props: FormFieldProps) => React.ReactNode;\n /** Context-wired RecordEvent workflow input. Must be rendered inside `Form`. */\n WorkflowInput: (props: WorkflowInputProps) => React.ReactNode;\n /** Renders every applicable RecordEvent workflow input, stacked in order.\n * Must be rendered inside `Form`. */\n WorkflowInputs: (props: WorkflowInputsProps) => React.ReactNode;\n /** The applicable RecordEvent workflow inputs, keyed by input key. */\n workflowInputs: Record<string, SdkWorkflowInput>;\n /**\n * Form wrapper with `onSubmit` pre-wired to `form.submit`. Provides the context that\n * `FieldInput` reads from. Renders a `<form>` element; accepts `className` and `children`.\n */\n Form: (props: FormWrapperProps) => React.ReactNode;\n /**\n * Ready-to-render prompt for pending capability-aware interactions. Render this\n * next to `Form`; it is `null` unless `form.submit()` needs extra operation metadata\n * before saving. For recurring eventable updates, this prompts for\n * `$operation.eventable.scope` and then completes the submit.\n */\n interaction: React.ReactNode;\n};\n\ntype SdkWorkflowResponse = SdkWorkflowOutput | SdkWorkflowPendingResponse;\n\ntype SdkWorkflowOutput =\n | string\n | number\n | boolean\n | null\n | any[]\n | Record<string, any>;\n\ntype SdkWorkflowPendingResponse = { execution_id: string };\n\ntype UseWorkflowFormResult = {\n /** The form handle \u2014 `data`, `errors`, `fields`, `isSubmitting`, `isReady`, `submit`, `reset`, `setData`. */\n form: SdkWorkflowForm;\n /**\n * Context-wired field input. Renders the correct editor for a schema field handle\n * from `fields`; returns `null` for handles that are not part of the active form.\n * Must be rendered inside `Form`.\n */\n FieldInput: React.ComponentType<FormFieldProps>;\n /**\n * Form wrapper with `onSubmit` pre-wired to `form.submit`. Provides the context that\n * `FieldInput` reads from. Renders a `<form>` element; accepts `className` and `children`.\n */\n Form: React.ComponentType<FormWrapperProps>;\n /** The projected schema fields (the same array as `form.fields`). */\n fields: SdkResourceField[];\n /** The workflow's uppercased HTTP method, or an empty string when unavailable. */\n method: string;\n /** `false` when the workflow has no published route settings (unpublished, paused, or unknown id). */\n isAvailable: boolean;\n};\n\ntype SdkWorkflowForm = {\n data: Record<string, any>;\n /** Field-validation messages only. HTTP 409 conflicts and every other\n * non-validation failure are exposed through `failure`, never here. */\n errors: Record<string, string>;\n /** The latest non-validation submission failure, or `null`. Read `status` / `body`,\n * or classify it with `Sdk.utils.error.isConflict`, `isForbidden`, or `isNotFound`. */\n failure: SdkApiError | null;\n fields: SdkResourceField[];\n isSubmitting: boolean;\n isReady: boolean;\n submit: () => void;\n /** Reset the requested values and clear `failure`; a full reset also clears field errors. */\n reset: (...keys: string[]) => void;\n setData: (key: string, value: any) => void;\n /** Replace or update the error map \u2014 pass a complete map or a functional updater.\n * Useful for client-side validation; server 422s populate `errors` automatically. */\n setErrors: (\n errors: Record<string, string> | ((current: Record<string, string>) => Record<string, string>)\n ) => void;\n /** Pass to a `<form>` `onSubmit` when managing the form tag yourself \u2014 calls\n * `preventDefault` and submits. The returned `Form` wrapper already wires this. */\n handleSubmit: (event?: React.FormEvent<HTMLFormElement>) => void;\n};\n\ntype HTMLFormElement = any;\n\ntype SdkUseWorkspaceFieldsFormOptions = {\n /** Workspace field definitions to render, in order. Omit to render every field that is\n * not hidden and not read-only. Undefined, read-only, and hidden entries are dropped. */\n fields?: Array<SdkWorkspaceField | undefined>;\n /** Editable seed values that override the workspace's current values. */\n initialValues?: Record<string, any>;\n /** Locked values submitted even when omitted from `fields`; rendered disabled. Keys of\n * read-only or hidden definitions are dropped. */\n fixedValues?: Record<string, any>;\n /** Called after a successful save with the saved values keyed by field key. */\n onSuccess?: (values: SdkWorkspaceValues) => void;\n};\n\ntype UseWorkspaceFieldsFormResult = {\n form: SdkWorkspaceFieldsForm;\n /** The active field definitions, in render order. */\n fields: SdkWorkspaceField[];\n /** Whether a save can happen: the identity holds `update_workspace_fields` or `manage_workspace_fields` and at\n * least one field or fixed value is active. `submit()` is a no-op otherwise. */\n canSubmit: boolean;\n /** Context-wired input for one workspace field. Must be rendered inside `Form`. */\n FieldInput: (props: FormFieldProps) => React.ReactNode;\n /** Form wrapper with `onSubmit` pre-wired to `form.submit`. */\n Form: (props: FormWrapperProps) => React.ReactNode;\n};\n\ntype SdkWorkspaceFieldsForm = {\n data: Record<string, any>;\n errors: Record<string, string>;\n fields: SdkWorkspaceField[];\n isSubmitting: boolean;\n submit: () => void;\n reset: (...keys: string[]) => void;\n setData: (key: string, value: any) => void;\n};\n\ntype UseWorkflowActionResult = {\n /** Run the workflow with these inputs. Resolves with the parsed response on success\n * and `null` on failure; failures never reject into an unhandled click promise. */\n run: (inputs: Record<string, any>) => Promise<SdkWorkflowResponse | null>;\n /** `true` while the latest run is in flight. */\n isPending: boolean;\n /** Display-ready message for the latest failure, or `null`. */\n errorMessage: string | null;\n /** Structured latest failure. Classify it with `Sdk.utils.error`, including\n * `Sdk.utils.error.isConflict(failure)`. */\n failure: SdkApiError | null;\n /** Parsed response from the latest successful run, or `null`. */\n result: SdkWorkflowResponse | null;\n /** Clear submission state, ignore any in-flight response, and start a fresh\n * managed-idempotency lifecycle. */\n reset: () => void;\n};\n\ntype SdkUseRecordScheduleTimelinesOptions = {\n /** Resource the record belongs to. */\n resourceId: string;\n /** Record whose schedules are fetched. Nullish keeps the hook idle. */\n recordId: string | null | undefined;\n /** Limit the result to a single record-schedule workflow. */\n recordScheduleWorkflowId?: string;\n /**\n * Refetch the timelines on an interval. The function form receives the current\n * timelines and returns `false` to stop. Omit to fetch once per mount.\n *\n * @example\n * ```tsx\n * Sdk.hooks.useRecordScheduleTimelines({\n * resourceId,\n * recordId,\n * pollInterval: (timelines) => (timelines.length ? 60000 : false)\n * });\n * ```\n */\n pollInterval?: SdkPollInterval<SdkRecordScheduleTimeline[]>;\n};\n\ntype SdkRecordScheduleTimeline = {\n workflow_id: string;\n workflow_title: string;\n /** Label of the workflow's anchor date field, e.g. \"Due Date\". */\n anchor_date_label: string;\n /** The record's anchor date value, or `null` when unset or hidden from the viewer. */\n anchor_date_value: string | null;\n /** The workflow's offsets in chronological order. */\n offsets: SdkRecordScheduleTimelineOffset[];\n};\n\ntype SdkRecordScheduleTimelineOffset = {\n /** Signed ISO-8601 duration relative to the anchor date, e.g. `-P7D` for 7 days before. */\n offset: string;\n /** Absolute ISO datetime of the run, or `null` when the anchor date is unset or hidden. */\n scheduled_run_at: string | null;\n /** Pending, dispatched, automatically skipped, or manually skipped by a caller. */\n state: 'pending' | 'sent' | 'skipped' | 'manually_skipped';\n /** `true` on the soonest offset that is still pending \u2014 the upcoming run. */\n is_next: boolean;\n};\n\ntype SdkRecordScheduleTimelinesHandle = {\n /** Skip an upcoming offset. Requires Show and Update access to the record. Refreshes all its timeline queries. */\n skipOffset: (input: {\n workflowId: string;\n offset: string;\n /** Reject a stale selection if the reminder has moved since it was displayed. */\n scheduledRunAt?: string;\n }) => Promise<{ data: SdkRecordScheduleTimeline[] }>;\n isSkipping: boolean;\n /** One timeline per record-schedule workflow the record participates in. */\n timelines: SdkRecordScheduleTimeline[];\n isLoading: boolean;\n isError: boolean;\n refetch: () => Promise<any>;\n};\n\ntype UseDataTableOptions<TData extends RowData> = {\n /** Rows to render. Nullish is treated as an empty table, so loading data can be passed straight through. */\n data: TData[] | null | undefined;\n columns: DataTableColumn<TData>[];\n initialPageSize?: number;\n /**\n * Prepend a checkbox column so end users can select rows for bulk actions. Pair with\n * `DataTable`'s `bulkActions` prop or read the returned selection helpers. Selection\n * is keyed by each row's `id` (falling back to its index), so it survives sorting,\n * filtering, and pagination.\n */\n enableRowSelection?: boolean;\n /**\n * Keeps the table state in the page URL under this query parameter, so that the\n * browser Back button, a reload, or a shared link restores it. The state includes\n * the sort, the search, the page size, the visible columns, and the page number.\n * The value is encoded. `true` uses the key `table`. Give each table on a page a\n * different key; two tables with the same key throw. Omit the key to keep the state\n * in memory only.\n *\n * @example\n * urlStateKey: 'events'\n */\n urlStateKey?: string | true;\n};\n\ntype RowData = any;\n\ntype DataTableColumn<TData extends RowData> = {\n /** Stable column id. Defaults to `accessor` when omitted. */\n id?: string;\n /** Key on the row whose value is read for display, sorting, and filtering. */\n accessor?: Extract<keyof TData, string>;\n /** Column header label. */\n header: string;\n /** Custom cell renderer; receives the row's original data. */\n cell?: (row: TData) => ReactNode;\n /** Defaults to `true` when an `accessor` is present, `false` otherwise. */\n enableSorting?: boolean;\n /** Defaults to `true`. */\n enableHiding?: boolean;\n} & DataTableColumnSizing;\n\ntype ReactNode = React.ReactNode;\n\ntype DataTableColumnSizing = {\n /** Explicit column width in px. Columns without it wrap their cells at 240px. */\n width?: number;\n /**\n * Classes for the box that holds each body cell's content. A `max-w-*` class here replaces\n * the 240px default, for example `max-w-80`, `max-w-none`, or `max-w-40 md:max-w-80`.\n */\n className?: string;\n};\n\ntype UseDataTableResult<TData extends RowData> = {\n table: Table<TData>;\n /** Ids of every selected row (its `id`, or index when absent). */\n selectedRowIds: string[];\n /** The selected rows' original data. */\n selectedRows: TData[];\n /** Number of selected rows. */\n selectedCount: number;\n /** Clears the current selection. */\n clearSelection: () => void;\n};\n\ntype Table<TData = any> = {\n readonly __sdkDataTableRowType?: (row: TData) => TData;\n};\n\ntype ResourceDataTableResource = SdkResourceType &\n Pick<SdkResource, 'id' | 'fields' | 'name' | 'policy_scope' | 'disabled_data_api_endpoints'>;\n\ntype UseResourceDataTableOptions<\n TResource extends ResourceDataTableResource = ResourceDataTableResource,\n TRelations extends readonly SdkResourceRelationPath<NoInfer<TResource>>[] = readonly []\n> = {\n /** Resource handle returned by `Sdk.resource('<resourceId>')`. */\n resource: TResource;\n /** Resource fields or custom column definitions to render. */\n columns?: ResourceDataTableColumn<SdkResourceRecord<TResource, TRelations>>[];\n /** Fields that start visible. ID, Created At, and Updated At are hidden by default. All other policy-permitted fields remain available to show. */\n initialFields?: readonly TResource['fields'][Extract<keyof TResource['fields'], string>][];\n /** Customizes the generated columns for named policy-permitted resource fields. */\n fieldOverrides?: ResourceDataTableFieldOverrides<TResource, TRelations>;\n /**\n * Renders controls for each record in a dedicated trailing column. The column is\n * always visible and does not appear in the column selector.\n */\n actions?: (row: SdkResourceRecord<TResource, TRelations>) => ReactNode;\n /** Number of records to show on each page. Defaults to `10`. */\n initialPageSize?: number;\n /**\n * Ordered sort, primary first, applied when no saved view is selected. The table\n * opens with it unless `urlStateKey` restores a state, and returns to it when the\n * viewer clears the selected saved view. The viewer can change or remove it from\n * the column headers. The unchanged initial sort is not an edit, so the table offers\n * \"Save view\" only after the viewer changes the view. Each `key` must be the sort key of a column in the table; a\n * hidden column counts. Other keys are ignored. Defaults to no sort.\n *\n * @example\n * initialSort: [{ key: 'created_at', direction: 'desc' }]\n */\n initialSort?: readonly ResourceSortDescriptor[];\n /** Soft-delete subset to read from. Defaults to active records only. */\n scope?: RecordScope;\n /** Relation paths to preload alongside record data. Augments relations derived from columns. */\n relations?: TRelations;\n /**\n * Author-defined base filter applied to every query (a `$filter` group or a list of\n * conditions). Invisible and unremovable in the filter UI \u2014 anything the end user\n * filters by is ANDed on top, so it can only narrow this subset. Filter relation\n * fields by nested scalar paths such as `family.id`, not by the relation object path\n * (`family`). Keep the filter the same between renders: a clock read such as\n * `Sdk.utils.time.current()` in it makes a new query on every render, and the table\n * requests without end. See `useRecords`.\n *\n * @example\n * { operator: 'and', conditions: [{ field: 'family.id', operator: '=', value: familyId }] }\n */\n filters?: Record<string, any> | any[] | null;\n /**\n * Compose end-user filtering into queries: the runtime filter (filter chips and the\n * advanced-filters dialog) plus the resource's saved views, which render a selector\n * in `DataTable`'s default toolbar. Picking a saved view loads its conditions into\n * the runtime filter as editable chips, exactly as if the viewer had built them by\n * hand; edits stay local until reset or saved as a new private view. The\n * saved view list is viewer-specific: shared views require the Index Records scope;\n * private views also require ownership or Manage Shared Filters. Defaults to `true`.\n */\n enableFilters?: boolean;\n /**\n * Prepend a checkbox column so end users can select rows for bulk actions. Pair with\n * `DataTable`'s `bulkActions` prop or read the returned selection helpers.\n */\n enableRowSelection?: boolean;\n /**\n * Keeps the table state in the page URL under this query parameter, so that the\n * browser Back button, a reload, or a shared link restores it. The state includes\n * the selected saved view, the filter, the sort, the search, the page size, the\n * visible columns, and the page number. The value is encoded. `true` uses the\n * resource slug as the key. Give each table on a page a different key; two tables\n * with the same key throw. Omit the key to keep the state in memory only.\n *\n * @example\n * urlStateKey: 'students'\n */\n urlStateKey?: string | true;\n};\n\ntype ResourceDataTableColumn<TRecord extends ResourceRecord = ResourceRecord> =\n | SdkResourceField\n | ResourceDataTableFieldColumn\n | ResourceDataTableCustomColumn<TRecord>;\n\ntype ResourceDataTableFieldColumn = {\n field: SdkResourceField;\n header?: string;\n enableSorting?: boolean;\n enableHiding?: boolean;\n} & DataTableColumnSizing;\n\ntype ResourceDataTableCustomColumn<TRecord extends ResourceRecord = ResourceRecord> = {\n id: string;\n header: string;\n cell: (row: TRecord, helpers: ResourceDataTableCellHelpers) => ReactNode;\n /** Maps the column to a server `$sort` key. Omit to make the column non-sortable. */\n sortKey?: string;\n enableSorting?: boolean;\n enableHiding?: boolean;\n} & DataTableColumnSizing;\n\ntype ResourceDataTableCellHelpers = {\n /** Renders a field's value from the current row through the type-aware display registry. */\n FieldValue: React.ComponentType<{ field: SdkResourceField; className?: string }>;\n /** Renders a field's label text. */\n FieldLabel: React.ComponentType<{ field: SdkResourceField; className?: string }>;\n};\n\ntype ResourceDataTableFieldOverrides<\n TResource extends SdkResourceType & Pick<SdkResource, 'fields'> = SdkResourceType &\n Pick<SdkResource, 'fields'>,\n TRelations extends readonly SdkResourceRelationPath<TResource>[] = readonly []\n> = Partial<{\n [TFieldName in Extract<keyof TResource['fields'], string>]: {\n /** Replaces the field's default type-aware cell renderer. */\n cell?: (\n row: SdkResourceRecord<\n TResource,\n readonly [...TRelations, TFieldName & SdkResourceRelationPath<TResource>]\n >,\n helpers: ResourceDataTableCellHelpers\n ) => ReactNode;\n };\n}>;\n\ntype UseResourceDataTableResult<TRecord extends ResourceRecord = ResourceRecord> = {\n table: Table<TRecord>;\n isLoading: boolean;\n totalRecords: number;\n /**\n * True when the records request failed. Pass to `DataTable`'s `isError` prop so\n * the failure shows as an error row instead of an empty \"0 records\" table.\n */\n isError: boolean;\n /**\n * Refetches the current page of records. Pass to `DataTable`'s `onRetry` prop so\n * the error row offers a Retry button. Mutations made through the resource\n * handle (`resource.create/update/delete`) refresh the table automatically \u2014\n * reach for this otherwise only after mutating through other channels (e.g. raw\n * `Sdk.api` calls against custom endpoints).\n */\n refetch: () => Promise<any>;\n /** Runtime filter state for `DataTable`'s `filters` prop / `DataTableFilters`. */\n filters: DataTableFiltersApi;\n /**\n * Ids of every selected record, stable across pagination (keyed by record id).\n * Prefer this for bulk actions \u2014 `selectedRows` only resolves the loaded page.\n */\n selectedRowIds: string[];\n /** Selected records on the currently loaded page. Excludes selections on other pages. */\n selectedRows: TRecord[];\n /** Number of selected records across all pages. */\n selectedCount: number;\n /** Clears the current selection. */\n clearSelection: () => void;\n};\n\ntype DataTableFiltersApi = {\n /** Whether end-user filter controls are available. Defaults to `true`. */\n filteringEnabled?: boolean;\n appliedFilter: FilterGroup | null;\n appliedConditionCount: number;\n apply: (filter: FilterGroup | null) => void;\n clear: () => void;\n /** Disables the dialog trigger when filtering isn't possible yet (e.g. no resource). */\n disabled?: boolean;\n /** Renders the condition editor inside the dialog; receives the draft from the dialog. */\n renderEditor: (editorProps: AdvancedFiltersEditorProps) => ReactNode;\n /**\n * Field + operator catalog for the quick-filter chip bar. When present, `DataTable`'s\n * default layout renders the chip bar instead of the standalone \"Filters\" button\n * (the advanced dialog stays reachable through the chips). Omitted by adapters that\n * can't offer chips.\n */\n quickFilters?: DataTableQuickFiltersApi;\n /**\n * Headless shared-filter state for the backing resource (`useSharedFilters`). When\n * present (resource-backed tables with filtering enabled), `DataTable`'s default\n * layout renders the shared-filter selector in the toolbar. Omitted by adapters that\n * can't offer shared filters.\n */\n sharedFilters?: SharedFiltersApi;\n /**\n * CSV-export state for the backing resource. When present (resource-backed tables\n * whose resource has the export endpoint enabled), `DataTable`'s `isExportable` prop\n * renders the Export control in the default toolbar. Omitted by adapters that\n * cannot offer server exports (e.g. seeded tables).\n */\n export?: DataTableExportApi;\n /**\n * Turns a plain-language description into filter conditions for the backing resource.\n * When present (resource-backed tables with filtering enabled), `DataTable`'s default\n * toolbar renders the \"Ask AI\" filter control for viewers who may use the AI assistant.\n * It resolves to conditions the server already checked; the control adds them to the\n * current filter. Omitted by adapters that cannot ask the server (e.g. seeded tables).\n */\n generateFilter?: (prompt: string) => Promise<GeneratedRecordFilter>;\n};\n\ntype AdvancedFiltersEditorProps = {\n draftFilter: FilterGroup;\n onDraftFilterChange: (filter: FilterGroup) => void;\n};\n\ntype DataTableQuickFiltersApi = {\n fields: QuickFilterField[];\n operators: Record<string, FilterOperatorData>;\n};\n\ntype QuickFilterField = {\n path: string;\n label: string;\n type: VariableType;\n schema: SchemaField;\n /** Related resource id when the chip filters a relation via its nested `.id` path. */\n resource?: string | null;\n /** The related resource's display field, for labeling picked records. */\n display_field_key?: string | null;\n};\n\ntype VariableType = 'boolean' | 'color' | 'component_reference' | 'content' | 'date' | 'datetime' | 'dictionary' | 'domain_reference' | 'dynamic' | 'email' | 'email_layout_reference' | 'event_schedule' | 'field_reference' | 'file_reference' | 'filter' | 'form' | 'guard' | 'identity' | 'liquid' | 'list' | 'location' | 'markdown' | 'money' | 'notification_template_reference' | 'notification_type_reference' | 'null' | 'number' | 'package_input_reference' | 'page_reference' | 'password' | 'phone' | 'point' | 'record' | 'record_reference' | 'recurrence' | 'regular_hours' | 'relation' | 'resolver' | 'resource_field_key_reference' | 'resource_field_value' | 'resource_reference' | 'role_reference' | 'schema' | 'select' | 'text' | 'time' | 'timezone' | 'unknown' | 'url' | 'uuid' | 'wallet_accounting_account' | 'wallet_accounting_entity' | 'workflow_reference' | 'workflow_state_variable_reference' | 'workflow_step_reference';\n\ntype SchemaField = {\n key: string;\n label: string | null;\n type: VariableType;\n settings?: Record<string, any> | null;\n};\n\ntype FilterOperatorData = {\n label: string;\n value: string;\n value_shape: any;\n};\n\ntype DataTableExportApi = {\n /** The backing resource's display name, for export UI copy. */\n resourceName: string;\n /** Whether the current table view contains a field-backed column the server can export. */\n hasExportableVisibleColumns: boolean;\n exportRecords: (options: { columns: DataTableExportColumnScope }) => Promise<RecordExportResult>;\n};\n\ntype DataTableExportColumnScope = 'visible' | 'all';\n\ntype RecordExportResult = {\n /** The generated file's name, e.g. `students-export-2026-08-26.csv`. */\n filename: string;\n};\n\ntype GeneratedRecordFilter = {\n filter: FilterGroup;\n};\n\ntype UseCalendarViewResult = {\n view: CalendarViewMode;\n setView: (view: CalendarViewMode) => void;\n date: Date;\n setDate: (date: Date | ((prev: Date) => Date)) => void;\n windowStart: Date;\n windowEnd: Date;\n title: string;\n goToToday: () => void;\n goToPrevious: () => void;\n goToNext: () => void;\n};\n\ntype CalendarViewMode = 'month' | 'week' | 'day';\n\ntype UseCalendarEventsOptions = {\n resourceId: string;\n from: Date | string;\n to: Date | string;\n filters?: Record<string, any>;\n relations?: readonly string[];\n enabled?: boolean;\n};\n\ntype UseCalendarEventsResult<TRecord extends object = Record<string, any>> = {\n events: CalendarEvent<TRecord>[];\n isLoading: boolean;\n isError: boolean;\n refetch: () => Promise<any>;\n};\n\ntype CalendarEvent<TRecord extends object = Record<string, any>> = {\n id: string;\n start: Date;\n end: Date;\n allDay: boolean;\n record: TRecord;\n isPlaceholder?: boolean;\n};\n\ntype UseNewCalendarEventOptions = {\n /**\n * Title shown on the placeholder event pill while the create modal is open.\n * Defaults to `'New Event'`.\n */\n placeholderTitle?: string;\n};\n\ntype UseNewCalendarEventResult = {\n /** The pending placeholder event, or `null` when no create flow is in progress. */\n placeholder: CalendarEvent | null;\n /**\n * Populate the placeholder from an `onSlotClick` payload. Call this inside\n * your `onSlotClick` handler to immediately show the selected slot on the\n * calendar while the create modal is open.\n */\n setPlaceholder: (payload: SlotClickPayload) => void;\n /** Remove the placeholder. Call this when the create modal is cancelled or after the record is saved. */\n clearPlaceholder: () => void;\n /**\n * Merge the placeholder into an events array. Pass the result directly to\n * `<Sdk.Components.Calendar events={newEvent.mergeEvents(events)} />`.\n *\n * Returns the original array unchanged when there is no active placeholder.\n */\n mergeEvents: (events: CalendarEvent[]) => CalendarEvent[];\n /**\n * The resolved placeholder title. Pass this to\n * `<Sdk.Components.Calendar placeholderTitle={newEvent.placeholderTitle} />`\n * so the hover-preview and the confirmed placeholder pill always show the\n * same title without duplicating the string at the call-site.\n */\n placeholderTitle: string;\n};\n\ntype SlotClickPayload = {\n view: CalendarViewMode;\n start: Date;\n end: Date;\n allDay: boolean;\n};\n\ntype UseCalendarSelectionOptions = {\n resourceId: string;\n filters?: Record<string, any>;\n enabled?: boolean;\n};\n\ntype UseCalendarSelectionResult = {\n groups: CalendarOptionGroup[];\n isLoading: boolean;\n selectedIds: string[];\n setSelectedIds: (ids: string[]) => void;\n filters: Record<string, any> | undefined;\n};\n\ntype CalendarOptionGroup = CalendarOptionsResponse['data'][number];\n\ntype CalendarOptionsResponse = any;\n\ntype UseStepperOptions = {\n /** Index to start on. Clamped to the valid range. Defaults to `0`. */\n initialStep?: number;\n};\n\ntype UseStepperResult = {\n /** Zero-based index of the active step. */\n step: number;\n\n /** Total number of steps. */\n steps: number;\n\n /** Label of the active step. */\n label: string;\n\n /** All step labels, in order. */\n labels: string[];\n\n /** Jump to a step by index. Clamped to the valid range; never validates or blocks. */\n goTo: (index: number) => void;\n\n /** Advance one step. No-op on the last step. */\n next: () => void;\n\n /** Go back one step. No-op on the first step. */\n back: () => void;\n\n /** `true` when on the first step. */\n isFirst: boolean;\n\n /** `true` when on the last step. */\n isLast: boolean;\n};\n\ntype UseThemeResult = {\n theme: 'light' | 'dark';\n mode: SdkThemeMode;\n setTheme: (mode: SdkThemeMode) => void;\n toggleTheme: () => void;\n};\n\ntype SdkThemeMode = 'light' | 'dark' | 'system';\n\ninterface CustomComponentRegistry {}\n\ntype ResourceRecordFormStep<RenderState> = {\n /** Step label shown in the step indicator and the \"Step X of N\" caption. */\n label: string;\n\n /** Field handles rendered stacked, in order, while this step is active. */\n fields?: SdkResourceField[];\n\n /**\n * Custom step layout. Receives the form's render state\n * (`{ FieldInput, form, fields }`) and replaces the default field rendering.\n */\n content?: (state: RenderState) => React.ReactNode;\n\n /** Render a read-only summary of every form field and its current value. */\n review?: boolean;\n};\n\ntype ResourceRecordFormFieldsProps = {\n className?: string;\n};\n\ntype ResourceRecordFormActionsProps = {\n submitLabel?: string;\n defaultSubmitLabel?: string;\n /** When provided, the secondary button becomes a Cancel action wired to this\n * callback (instead of the default Reset). */\n onCancel?: () => void;\n cancelLabel?: string;\n className?: string;\n};\n\ntype AccordionProps = {\n /**\n * Accordion item definitions.\n */\n items: AccordionItem[];\n\n /**\n * Whether one or many items can be open at the same time.\n */\n type?: 'single' | 'multiple';\n\n /**\n * Initially opened item value (or values for `multiple`).\n */\n defaultValue?: string | string[];\n\n /**\n * Controlled open value(s).\n */\n value?: string | string[];\n\n /**\n * Called when the open value(s) change.\n */\n onValueChange?: (value: string | string[]) => void;\n\n /**\n * Whether all items can be closed when `type` is `single`.\n */\n collapsible?: boolean;\n\n className?: string;\n};\n\ntype AccordionItem = {\n /**\n * Unique identifier for the item, used for open/close state.\n */\n value: string;\n\n /**\n * Trigger label shown for the item.\n */\n title: React.ReactNode;\n\n /**\n * Content revealed when the item is open.\n */\n content: React.ReactNode;\n};\n\ntype AdminNavButtonsProps = {\n /** Additional classes for the button group. */\n className?: string;\n\n /** Button visual style. */\n variant?: ButtonProps['variant'];\n\n /** Button size preset. */\n size?: ButtonProps['size'];\n};\n\ntype ButtonProps = any;\n\ntype AiAgentButtonProps = {\n /** Additional classes for the trigger button. */\n className?: string;\n};\n\ntype AlertProps = {\n /**\n * Alert visual style.\n */\n variant?: 'default' | 'info' | 'warning' | 'success' | 'destructive';\n\n /**\n * Optional alert heading.\n */\n title?: React.ReactNode;\n\n /**\n * Primary alert body.\n */\n description?: React.ReactNode;\n\n /**\n * Alternate alert body content used when `description` is not provided.\n */\n children?: React.ReactNode;\n\n className?: string;\n};\n\ntype AnalyticsEmbedProps = {\n /** Additional classes for the embed, e.g. a height override like `h-[40rem]`. */\n className?: string;\n};\n\ntype AvatarProps = {\n /**\n * Image URL. The fallback is shown when this is missing.\n */\n src?: string | null;\n\n /**\n * Accessible image description.\n */\n alt?: string;\n\n /**\n * Fallback text or node when no image is available.\n */\n fallback?: React.ReactNode;\n\n /**\n * Avatar size. Defaults to `md`.\n */\n size?: AvatarSize;\n\n className?: string;\n};\n\ntype AvatarSize = 'sm' | 'md' | 'lg' | 'xl';\n\ntype BadgeProps = PrimitiveHtmlProps<HTMLSpanElement> & {\n /**\n * Visual variant such as default, secondary, destructive, outline, or status variants.\n */\n variant?: 'default' | 'secondary' | 'success' | 'warning' | 'destructive' | 'outline' | null;\n\n /**\n * Badge label.\n */\n children?: React.ReactNode;\n};\n\ntype PrimitiveHtmlProps<TElement> = PrimitiveAriaProps &\n Partial<Pick<React.DOMAttributes<TElement>, PrimitiveDomAttributeName>> &\n Partial<Pick<React.HTMLAttributes<TElement>, PrimitiveHtmlAttributeName>>;\n\ntype PrimitiveAriaProps = Partial<Pick<React.AriaAttributes, PrimitiveAriaAttributeName>>;\n\ntype PrimitiveAriaAttributeName =\n | 'aria-activedescendant'\n | 'aria-atomic'\n | 'aria-autocomplete'\n | 'aria-braillelabel'\n | 'aria-brailleroledescription'\n | 'aria-busy'\n | 'aria-checked'\n | 'aria-colcount'\n | 'aria-colindex'\n | 'aria-colindextext'\n | 'aria-colspan'\n | 'aria-controls'\n | 'aria-current'\n | 'aria-describedby'\n | 'aria-description'\n | 'aria-details'\n | 'aria-disabled'\n | 'aria-dropeffect'\n | 'aria-errormessage'\n | 'aria-expanded'\n | 'aria-flowto'\n | 'aria-grabbed'\n | 'aria-haspopup'\n | 'aria-hidden'\n | 'aria-invalid'\n | 'aria-keyshortcuts'\n | 'aria-label'\n | 'aria-labelledby'\n | 'aria-level'\n | 'aria-live'\n | 'aria-modal'\n | 'aria-multiline'\n | 'aria-multiselectable'\n | 'aria-orientation'\n | 'aria-owns'\n | 'aria-placeholder'\n | 'aria-posinset'\n | 'aria-pressed'\n | 'aria-readonly'\n | 'aria-relevant'\n | 'aria-required'\n | 'aria-roledescription'\n | 'aria-rowcount'\n | 'aria-rowindex'\n | 'aria-rowindextext'\n | 'aria-rowspan'\n | 'aria-selected'\n | 'aria-setsize'\n | 'aria-sort'\n | 'aria-valuemax'\n | 'aria-valuemin'\n | 'aria-valuenow'\n | 'aria-valuetext';\n\ntype PrimitiveDomAttributeName =\n | 'children'\n | 'dangerouslySetInnerHTML'\n | 'onCopy'\n | 'onCopyCapture'\n | 'onCut'\n | 'onCutCapture'\n | 'onPaste'\n | 'onPasteCapture'\n | 'onCompositionEnd'\n | 'onCompositionEndCapture'\n | 'onCompositionStart'\n | 'onCompositionStartCapture'\n | 'onCompositionUpdate'\n | 'onCompositionUpdateCapture'\n | 'onFocus'\n | 'onFocusCapture'\n | 'onBlur'\n | 'onBlurCapture'\n | 'onChange'\n | 'onChangeCapture'\n | 'onBeforeInput'\n | 'onBeforeInputCapture'\n | 'onInput'\n | 'onInputCapture'\n | 'onReset'\n | 'onResetCapture'\n | 'onSubmit'\n | 'onSubmitCapture'\n | 'onInvalid'\n | 'onInvalidCapture'\n | 'onLoad'\n | 'onLoadCapture'\n | 'onError'\n | 'onErrorCapture'\n | 'onKeyDown'\n | 'onKeyDownCapture'\n | 'onKeyPress'\n | 'onKeyPressCapture'\n | 'onKeyUp'\n | 'onKeyUpCapture'\n | 'onAbort'\n | 'onAbortCapture'\n | 'onCanPlay'\n | 'onCanPlayCapture'\n | 'onCanPlayThrough'\n | 'onCanPlayThroughCapture'\n | 'onDurationChange'\n | 'onDurationChangeCapture'\n | 'onEmptied'\n | 'onEmptiedCapture'\n | 'onEncrypted'\n | 'onEncryptedCapture'\n | 'onEnded'\n | 'onEndedCapture'\n | 'onLoadedData'\n | 'onLoadedDataCapture'\n | 'onLoadedMetadata'\n | 'onLoadedMetadataCapture'\n | 'onLoadStart'\n | 'onLoadStartCapture'\n | 'onPause'\n | 'onPauseCapture'\n | 'onPlay'\n | 'onPlayCapture'\n | 'onPlaying'\n | 'onPlayingCapture'\n | 'onProgress'\n | 'onProgressCapture'\n | 'onRateChange'\n | 'onRateChangeCapture'\n | 'onSeeked'\n | 'onSeekedCapture'\n | 'onSeeking'\n | 'onSeekingCapture'\n | 'onStalled'\n | 'onStalledCapture'\n | 'onSuspend'\n | 'onSuspendCapture'\n | 'onTimeUpdate'\n | 'onTimeUpdateCapture'\n | 'onVolumeChange'\n | 'onVolumeChangeCapture'\n | 'onWaiting'\n | 'onWaitingCapture'\n | 'onAuxClick'\n | 'onAuxClickCapture'\n | 'onClick'\n | 'onClickCapture'\n | 'onContextMenu'\n | 'onContextMenuCapture'\n | 'onDoubleClick'\n | 'onDoubleClickCapture'\n | 'onDrag'\n | 'onDragCapture'\n | 'onDragEnd'\n | 'onDragEndCapture'\n | 'onDragEnter'\n | 'onDragEnterCapture'\n | 'onDragExit'\n | 'onDragExitCapture'\n | 'onDragLeave'\n | 'onDragLeaveCapture'\n | 'onDragOver'\n | 'onDragOverCapture'\n | 'onDragStart'\n | 'onDragStartCapture'\n | 'onDrop'\n | 'onDropCapture'\n | 'onMouseDown'\n | 'onMouseDownCapture'\n | 'onMouseEnter'\n | 'onMouseLeave'\n | 'onMouseMove'\n | 'onMouseMoveCapture'\n | 'onMouseOut'\n | 'onMouseOutCapture'\n | 'onMouseOver'\n | 'onMouseOverCapture'\n | 'onMouseUp'\n | 'onMouseUpCapture'\n | 'onSelect'\n | 'onSelectCapture'\n | 'onTouchCancel'\n | 'onTouchCancelCapture'\n | 'onTouchEnd'\n | 'onTouchEndCapture'\n | 'onTouchMove'\n | 'onTouchMoveCapture'\n | 'onTouchStart'\n | 'onTouchStartCapture'\n | 'onPointerDown'\n | 'onPointerDownCapture'\n | 'onPointerMove'\n | 'onPointerMoveCapture'\n | 'onPointerUp'\n | 'onPointerUpCapture'\n | 'onPointerCancel'\n | 'onPointerCancelCapture'\n | 'onPointerEnter'\n | 'onPointerLeave'\n | 'onPointerOver'\n | 'onPointerOverCapture'\n | 'onPointerOut'\n | 'onPointerOutCapture'\n | 'onGotPointerCapture'\n | 'onGotPointerCaptureCapture'\n | 'onLostPointerCapture'\n | 'onLostPointerCaptureCapture'\n | 'onScroll'\n | 'onScrollCapture'\n | 'onScrollEnd'\n | 'onScrollEndCapture'\n | 'onWheel'\n | 'onWheelCapture'\n | 'onAnimationStart'\n | 'onAnimationStartCapture'\n | 'onAnimationEnd'\n | 'onAnimationEndCapture'\n | 'onAnimationIteration'\n | 'onAnimationIterationCapture'\n | 'onToggle'\n | 'onBeforeToggle'\n | 'onTransitionCancel'\n | 'onTransitionCancelCapture'\n | 'onTransitionEnd'\n | 'onTransitionEndCapture'\n | 'onTransitionRun'\n | 'onTransitionRunCapture'\n | 'onTransitionStart'\n | 'onTransitionStartCapture';\n\ntype PrimitiveHtmlAttributeName =\n | 'defaultChecked'\n | 'defaultValue'\n | 'suppressContentEditableWarning'\n | 'suppressHydrationWarning'\n | 'accessKey'\n | 'autoCapitalize'\n | 'autoFocus'\n | 'className'\n | 'contentEditable'\n | 'contextMenu'\n | 'dir'\n | 'draggable'\n | 'enterKeyHint'\n | 'hidden'\n | 'id'\n | 'lang'\n | 'nonce'\n | 'slot'\n | 'spellCheck'\n | 'style'\n | 'tabIndex'\n | 'title'\n | 'translate'\n | 'radioGroup'\n | 'role'\n | 'about'\n | 'content'\n | 'datatype'\n | 'inlist'\n | 'prefix'\n | 'property'\n | 'rel'\n | 'resource'\n | 'rev'\n | 'typeof'\n | 'vocab'\n | 'autoCorrect'\n | 'autoSave'\n | 'color'\n | 'itemProp'\n | 'itemScope'\n | 'itemType'\n | 'itemID'\n | 'itemRef'\n | 'results'\n | 'security'\n | 'unselectable'\n | 'popover'\n | 'popoverTargetAction'\n | 'popoverTarget'\n | 'inert'\n | 'inputMode'\n | 'is'\n | 'exportparts'\n | 'part';\n\ntype HTMLSpanElement = any;\n\ntype CartesianChartProps<TRow extends object = Record<string, any>> = {\n /**\n * Rows to chart.\n */\n data: TRow[];\n\n /**\n * Field key for the x-axis.\n */\n xKey: Extract<keyof TRow, string>;\n\n /**\n * Field key for the y-axis value.\n */\n yKey: Extract<keyof TRow, string>;\n\n /**\n * Optional chart heading.\n */\n title?: string;\n\n /**\n * Chart height in pixels.\n */\n height?: number;\n};\n\ntype BreadcrumbProps = {\n /**\n * Ordered breadcrumb items. The last item is treated as the current page when `href` is\n * omitted.\n */\n items: BreadcrumbItem[];\n\n className?: string;\n};\n\ntype BreadcrumbItem = {\n /**\n * Item label.\n */\n label: React.ReactNode;\n\n /**\n * Optional URL. Omit on the final item to mark it as the current page. Resolve a\n * workspace page with `Sdk.utils.page.url(pageId, params)` instead of hardcoding a route\n * string.\n */\n href?: string;\n};\n\ntype PrimitiveButtonProps = PrimitiveButtonNativeProps & {\n /**\n * Visual style such as default, secondary, destructive, ghost, link, or outline.\n */\n variant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'ghost' | 'link' | 'icon' | null;\n\n /**\n * Button size preset.\n */\n size?: 'default' | 'xs' | 'sm' | 'lg' | 'icon' | 'smIcon' | null;\n\n /**\n * Button label or contents.\n */\n children?: React.ReactNode;\n asChild?: boolean;\n loading?: boolean;\n tooltip?: string;\n};\n\ntype PrimitiveButtonNativeProps = PrimitiveHtmlProps<HTMLButtonElement> &\n Partial<Pick<React.ButtonHTMLAttributes<HTMLButtonElement>, PrimitiveButtonAttributeName>>;\n\ntype HTMLButtonElement = any;\n\ntype PrimitiveButtonAttributeName =\n | 'disabled'\n | 'form'\n | 'formAction'\n | 'formEncType'\n | 'formMethod'\n | 'formNoValidate'\n | 'formTarget'\n | 'name'\n | 'type'\n | 'value';\n\ntype ButtonLinkProps = Omit<LinkProps, 'className'> &\n Partial<Pick<ButtonProps, 'variant' | 'size'>> & {\n className?: string;\n };\n\ntype LinkProps = React.AnchorHTMLAttributes<HTMLAnchorElement> & {\n /**\n * Destination URL or anchor. For another page in this workspace, resolve it from the\n * page's stable id with `Sdk.utils.page.url(pageId, params)` and pass the non-null\n * result \u2014 never a hardcoded route string. Literal values are only for in-page anchors\n * (`#pricing`), absolute external URLs, and `mailto:`/`tel:` addresses.\n */\n href: string;\n\n /**\n * Open in a new tab and add safe rel attributes.\n */\n external?: boolean;\n\n /**\n * Link contents.\n */\n children?: React.ReactNode;\n};\n\ntype HTMLAnchorElement = any;\n\ntype CalendarProps<TRecord extends object = Record<string, any>> = {\n /**\n * Events to display on the calendar.\n */\n events: CalendarEvent<TRecord>[];\n\n /**\n * Active view: `'month'`, `'week'`, or `'day'`.\n */\n view: CalendarViewMode;\n\n /**\n * Anchor date that determines which month, week, or day is shown.\n */\n date: Date;\n\n /**\n * Called when an empty time slot is clicked. Receives the slot's start/end\n * times, view mode, and whether it is an all-day slot.\n */\n onSlotClick?: (payload: SlotClickPayload) => void;\n\n /**\n * Called when the user clicks an event pill.\n */\n onEventClick?: (event: CalendarEvent<TRecord>) => void;\n\n /**\n * Called after the user drags an event to a new time or resizes it. When\n * omitted, drag-and-drop and resize are disabled.\n */\n onEventChange?: (payload: EventChangePayload<TRecord>) => void;\n\n /**\n * Custom renderer for event content. Receives the full `CalendarEvent` and\n * returns content rendered inside the styled event pill. The pill's background\n * color, border, and shape are always applied by the calendar \u2014 only the\n * inner content (title, time label, icons, etc.) is replaced.\n */\n renderEvent?: (event: CalendarEvent<TRecord>) => React.ReactNode;\n\n /**\n * Title shown on the drag-preview event pill while the user hovers over a\n * slot before confirming the click. Pass `newEvent.placeholderTitle` from\n * `useNewCalendarEvent`. Defaults to `'New Event'` when omitted.\n */\n placeholderTitle?: string;\n\n /**\n * Time shaded behind the events of the week and day views, such as `backgroundTimes` from\n * `Sdk.hooks.useAvailability`. Each time is split at local midnight. The month view\n * ignores it.\n */\n backgroundTimes?: CalendarBackgroundTime[];\n\n className?: string;\n};\n\ntype EventChangePayload<TRecord extends object = Record<string, any>> = {\n event: CalendarEvent<TRecord>;\n previous: CalendarEvent<TRecord>;\n interaction: 'move' | 'resize';\n view: CalendarViewMode;\n};\n\ntype CalendarConnectionsProps = {\n /**\n * A Schedulable staff resource. Omit it and `recordId` to manage the signed-in person's\n * own calendars, which needs no permission.\n */\n resourceId?: string;\n\n /**\n * A staff record of that resource. Another person's record needs \"Manage calendar\n * connections\" and Show access to the record. Connections always use the viewer's own\n * Google accounts.\n */\n recordId?: string;\n\n /** Additional classes for the container. */\n className?: string;\n};\n\ntype CalendarNextButtonProps = {\n view: CalendarViewMode;\n onClick: () => void;\n className?: string;\n};\n\ntype CalendarPrevButtonProps = {\n view: CalendarViewMode;\n onClick: () => void;\n className?: string;\n};\n\ntype CalendarSelectorProps = {\n groups: CalendarOptionGroup[];\n isLoading?: boolean;\n selectedIds: string[];\n onChange: (selectedIds: string[]) => void;\n /** Show the \"Select all\" / \"Clear\" bulk actions in the dropdown. Defaults to `true`. */\n showBulkActions?: boolean;\n className?: string;\n};\n\ntype CalendarTitleProps = {\n title: string;\n isLoading?: boolean;\n className?: string;\n};\n\ntype CalendarTodayButtonProps = {\n onClick: () => void;\n className?: string;\n};\n\ntype CalendarViewSwitcherProps = {\n view: CalendarViewMode;\n onViewChange: (view: CalendarViewMode) => void;\n className?: string;\n};\n\ntype CardProps = {\n /**\n * Optional card heading.\n */\n title?: React.ReactNode;\n\n /**\n * Supporting text below the title.\n */\n description?: React.ReactNode;\n\n /**\n * Main card content.\n */\n children?: React.ReactNode;\n\n /**\n * Footer area for actions or metadata.\n */\n footer?: React.ReactNode;\n\n className?: string;\n contentClassName?: string;\n};\n\ntype CollapsibleProps = {\n /**\n * Trigger label shown when collapsed or expanded.\n */\n title: React.ReactNode;\n\n /**\n * Content revealed inside the panel.\n */\n children: React.ReactNode;\n\n /**\n * Controlled open state.\n */\n open?: boolean;\n\n /**\n * Initial uncontrolled open state.\n */\n defaultOpen?: boolean;\n\n /**\n * Called when the panel opens or closes.\n */\n onOpenChange?: (open: boolean) => void;\n\n className?: string;\n};\n\ntype ConfirmDialogProps = {\n /**\n * Dialog heading.\n */\n title: React.ReactNode;\n\n /**\n * Explanation of the action.\n */\n description: React.ReactNode;\n\n /**\n * Confirm button label.\n */\n confirmLabel?: string;\n\n /**\n * Called when the confirm action is selected. Return a promise to show a processing\n * indicator until it settles. The dialog closes when the promise resolves. It stays open\n * when the handler returns `false`, resolves `false`, or rejects, so the page can show the\n * error in the dialog.\n */\n onConfirm: () => void | boolean | Promise<void | boolean>;\n\n /**\n * Element that opens the dialog.\n */\n trigger?: React.ReactNode;\n\n /**\n * Show a processing indicator on the confirm action.\n */\n processing?: boolean;\n\n /**\n * Controlled open state.\n */\n open?: boolean;\n\n /**\n * Called when the dialog opens or closes.\n */\n onOpenChange?: (open: boolean) => void;\n\n /**\n * Confirm action visual style.\n */\n variant?: 'default' | 'destructive';\n};\n\ntype CreateFormResource = SdkResourceType &\n CreateResourceRecordFormResource &\n Pick<SdkResource, 'policy_scope'>;\n\ntype CreateResourceRecordFormProps<TResource extends CreateFormResource = CreateFormResource> = {\n /** Resource handle returned by `Sdk.resource('<resourceId>')`. */\n resource: TResource;\n\n /** Field handles that control which fields the form renders and their order.\n * Omit to render the resource's configured create fields. */\n fields?: SdkResourceField[];\n\n /** Editable seed values \u2014 pre-fill fields the user can still change. */\n initialValues?: Record<string, any>;\n\n /** Locked writable Resource field values \u2014 rendered as disabled inputs and included\n * even when omitted from `fields`. Unknown, system, virtual, or inaccessible keys are\n * ignored. Reactive: if the prop changes after mount the form updates. */\n fixedValues?: Record<string, any>;\n\n /** Include every creatable field, ignoring per-field `show_on_creating` form\n * visibility. For elevated surfaces (e.g. the admin create page). Ignored when\n * explicit `fields` are provided. */\n includeAllFields?: boolean;\n\n /** Additional system-managed relations an elevated surface may expose, rendered\n * attach-only. The resource's authorization relations (roles, permissions) need no\n * entry: a form offers them attach-only through their inline attach switch when the\n * identity may store them. */\n includeSystemManagedRelationKeys?: string[];\n\n /** How relation fields resolve inline attach/create. Defaults to `'settings'`;\n * pass `'always'` to force them on. */\n relationVisibility?: RelationVisibility;\n\n /** Render the resource's published `created`/`saved` RecordEvent workflow inputs in the\n * managed layout, stacked beneath the resource fields (on the final step for `steps`).\n * Defaults to `false`. Ignored when `children` is provided \u2014 render `WorkflowInputs`\n * yourself there. */\n includeWorkflowInputs?: boolean;\n\n /**\n * Multi-step (wizard) layout. Each step groups fields under a label; the form renders the\n * numbered step indicator, \"Step X of N\" caption, Previous/Next navigation, and the submit\n * button on the last step \u2014 no render prop or submit wiring needed. All steps share one\n * form: values persist across steps and the final submit posts the whole record.\n * Use `review: true` for a read-only summary step, or `content` to customize one step's\n * layout. Navigation is free \u2014 moving between steps never validates or blocks.\n * Ignored when `children` is provided.\n */\n steps?: ResourceRecordFormStep<CreateResourceRecordFormRenderState<TResource>>[];\n\n /**\n * Make a retried booking submit return the booking that the first attempt made, instead of a\n * second booking. Each submit writes a key into the field that the resource's booking\n * capability names as its idempotency field. A retry with the same values sends the same key;\n * changed values get a new key. Submitting throws when the resource has no idempotency field.\n * Defaults to `false`.\n */\n idempotent?: boolean;\n\n /** Submit button label. Only used when `children` is not provided. */\n submitLabel?: string;\n\n /** When provided, the default layout's secondary button becomes a Cancel action\n * wired to this callback (instead of Reset). */\n onCancel?: () => void;\n\n /** Cancel button label. Defaults to \"Cancel\". Only used with `onCancel`. */\n cancelLabel?: string;\n\n /** Toast message shown after a successful submission. Pass `null` to suppress. */\n successMessage?: string | null;\n\n /** Called after a successful submission with the created record. */\n onSuccess?: (record: SdkResourceRecord<TResource>) => void;\n\n /** Called when a submission fails, after the form shows its errors. On a Booking resource, a\n * refused booking fails here, so a slot list can refetch. */\n onError?: (error: any) => void;\n\n /** Optional container className. */\n className?: string;\n\n /**\n * Custom layout render prop. When provided, the managed layout is replaced while\n * retaining the form tag and submit handler. It receives reusable `Fields`,\n * `Actions`, and `WorkflowInputs` components, `FieldInput` / `WorkflowInput` for\n * individual controls, and `workflowInputs` \u2014 the resource's published\n * `created`/`saved` RecordEvent workflow inputs keyed by input key.\n *\n * `FieldInput` renders the correctly-wired input for a resource field handle and\n * returns `null` for handles that are not part of the active form. `WorkflowInputs`\n * renders every applicable workflow input; `<WorkflowInput input={workflowInputs.<key>} />`\n * renders one. When a render function is provided, `steps` is ignored.\n *\n * @example\n * <Sdk.Components.CreateResourceRecordForm resource={students}>\n * {({ FieldInput, form }) => (\n * <div className=\"grid grid-cols-2 gap-4\">\n * <FieldInput field={students.fields.first_name} />\n * <FieldInput field={students.fields.last_name} />\n * <FieldInput field={students.fields.email} className=\"col-span-2\" />\n * <Sdk.Components.Button type=\"submit\" disabled={form.isSubmitting}>\n * Create student\n * </Sdk.Components.Button>\n * </div>\n * )}\n * </Sdk.Components.CreateResourceRecordForm>\n */\n children?: (state: CreateResourceRecordFormRenderState<TResource>) => React.ReactNode;\n};\n\ntype CreateResourceRecordFormRenderState<\n TResource extends Pick<SdkResource, 'workflow_inputs'> = Pick<SdkResource, 'workflow_inputs'>\n> = {\n FieldInput: (props: FormFieldProps) => React.ReactNode;\n WorkflowInput: (props: WorkflowInputProps) => React.ReactNode;\n /** Renders every applicable RecordEvent workflow input, stacked in order. */\n WorkflowInputs: (props: WorkflowInputsProps) => React.ReactNode;\n Fields: (props: ResourceRecordFormFieldsProps) => React.ReactNode;\n Actions: (props: ResourceRecordFormActionsProps) => React.ReactNode;\n form: SdkResourceForm;\n fields: SdkResourceField[];\n /** The resource's published `created`/`saved` RecordEvent workflow inputs, keyed\n * by input key \u2014 pass one to `WorkflowInput` or render them all via `WorkflowInputs`. */\n workflowInputs: SdkResourceFormWorkflowInputs<TResource, 'created' | 'saved'>;\n};\n\ntype DataTableProps<TData extends RowData> = {\n /**\n * The TanStack table instance from `Sdk.hooks.useDataTable` (seeded data) or\n * `Sdk.hooks.useResourceDataTable` (resource-backed).\n */\n table: Table<TData>;\n\n /** Renders skeleton rows while data loads (pass the hook's `isLoading`). */\n isLoading?: boolean;\n\n /**\n * Renders an error row instead of rows when the records request failed (pass the\n * hook's `isError`). Without it a failed request looks like an empty result.\n */\n isError?: boolean;\n\n /**\n * Called by the error row's Retry button (pass the hook's `refetch`). The button\n * only renders when provided.\n */\n onRetry?: () => void;\n\n /** Show a search input in the default toolbar. */\n searchable?: boolean;\n\n /** Placeholder for the search input. */\n searchPlaceholder?: string;\n\n /**\n * Runtime filter state for end-user filtering. Usually the `filters` returned by\n * `Sdk.hooks.useResourceDataTable`. When it carries quick-filter fields (resource-backed\n * tables), the default layout renders a Notion-style filter chip bar below the toolbar;\n * otherwise the toolbar shows the classic \"Filters\" button opening the advanced dialog.\n */\n filters?: DataTableFiltersApi;\n\n /** Show the column-visibility toggle in the default toolbar. */\n columnToggle?: boolean;\n\n /**\n * Show the CSV-export dropdown in the default toolbar. Only renders for\n * resource-backed tables (`useResourceDataTable`) whose resource has the export\n * endpoint enabled; the control offers the filtered rows or all rows, and the\n * visible columns or all columns.\n */\n isExportable?: boolean;\n\n /**\n * Extra controls rendered in the default toolbar, between the search input and the\n * column-visibility toggle. A generic slot for any toolbar content.\n */\n toolbarActions?: ReactNode;\n\n /** Extra controls rendered after the column and export controls. */\n toolbarTrailingActions?: ReactNode;\n\n /**\n * Bulk action controls shown in a floating selection bar once one or more rows are\n * selected (requires the hook's `enableRowSelection`). Pass a node, or a function that\n * receives the live selection (`rows`, `rowIds`, `count`, `clear`).\n *\n * Return only the action controls (e.g. Export, Delete). The selection bar already\n * renders the \"{count} selected\" label and a Clear button, so do not add your own \u2014 use\n * the supplied `count` for action labels (\"Export {count}\") and `clear` to reset the\n * selection after an action runs.\n */\n bulkActions?: DataTableBulkActionContent<TData>;\n\n /** Show pagination controls below the table. Defaults to `true`. */\n pagination?: boolean;\n\n /** Called with the row's record when a row is clicked. */\n onRowClick?: (row: TData) => void;\n\n /** Message shown when there are no rows. */\n emptyMessage?: ReactNode;\n\n /** Message shown in the error row when the records request failed. */\n errorMessage?: ReactNode;\n\n className?: string;\n\n /**\n * Advanced: provide a custom layout instead of the default toolbar/table/pagination.\n * Compose `Sdk.Components.DataTableContent`, `DataTableToolbar`, `DataTableSearch`,\n * `DataTableFilters`, `DataTableAddFilter`, `DataTableFilterChips`,\n * `SharedFilters`, `DataTableColumnToggle`, and `DataTablePagination` in any\n * order \u2014 they read the same table (and filters state) from context.\n */\n children?: ReactNode;\n};\n\ntype DataTableBulkActionContent<TData extends RowData> =\n | DataTableBulkActionNode\n | ((selection: DataTableBulkActionSelection<NoInfer<TData>>) => ReactNode);\n\ntype DataTableBulkActionNode =\n DataTableIsAny<ReactNode> extends true ? DataTableBulkActionNodeFallback : ReactNode;\n\ntype DataTableIsAny<T> = 0 extends 1 & T ? true : false;\n\ntype DataTableBulkActionNodeFallback =\n | DataTableBulkActionChild\n | readonly DataTableBulkActionChild[];\n\ntype DataTableBulkActionChild =\n | string\n | number\n | bigint\n | boolean\n | null\n | undefined\n | DataTableBulkActionElement;\n\ntype DataTableBulkActionElement = { type: any; props: any; key: string | null };\n\ntype DataTableBulkActionSelection<TData extends RowData> = {\n /** Selected rows on the loaded page. */\n rows: TData[];\n /** Ids of every selected row, stable across pagination. */\n rowIds: string[];\n /** Number of selected rows across all pages. */\n count: number;\n /** Clears the current selection. */\n clear: () => void;\n};\n\ntype DataTableAddFilterProps = {\n className?: string;\n};\n\ntype DataTableBulkActionsProps<TData = Record<string, any>> = {\n children: React.ReactNode | ((selection: DataTableSelection<TData>) => React.ReactNode);\n className?: string;\n label?: (count: number) => React.ReactNode;\n};\n\ntype DataTableSelection<TData> = {\n rows: TData[];\n rowIds: string[];\n count: number;\n clear: () => void;\n};\n\ntype DataTableViewOptionsProps = {\n className?: string;\n label?: string;\n};\n\ntype DataTableContentProps = {\n className?: string;\n emptyMessage?: React.ReactNode;\n /** Shown instead of rows when the records request failed (`DataTable`'s `isError`). */\n errorMessage?: React.ReactNode;\n skeletonRows?: number;\n};\n\ntype DataTableFilterChipsProps = {\n className?: string;\n};\n\ntype DataTableFiltersProps = {\n /**\n * Filters API from `Sdk.hooks.useResourceDataTable`. Optional when rendered inside a\n * `<Sdk.Components.DataTable filters={...}>` \u2014 it is read from context there.\n */\n filters?: DataTableFiltersApi;\n className?: string;\n};\n\ntype DataTablePaginationProps = {\n className?: string;\n pageSizeOptions?: number[];\n};\n\ntype DataTableRowActionsProps = {\n actions: DataTableRowAction[];\n className?: string;\n label?: string;\n};\n\ntype DataTableRowAction = {\n label: string;\n onSelect: () => void;\n icon?: React.ReactNode;\n destructive?: boolean;\n separatorBefore?: boolean;\n};\n\ntype DataTableSearchProps = {\n placeholder?: string;\n className?: string;\n};\n\ntype DataTableToolbarProps = {\n /** Free-form controls. They follow the `filters` slot in the leading group. */\n children?: React.ReactNode;\n className?: string;\n /**\n * View switcher (saved views). Follows the search in the leading group. In a narrow\n * toolbar it leads the second row and shrinks to the space left beside `filters` and\n * `end`, so its label must be able to truncate.\n */\n views?: React.ReactNode;\n /** Search input. Leads the toolbar; takes its own row in a narrow toolbar. */\n search?: React.ReactNode;\n /**\n * Filter trigger. Follows the search in the leading group. Below 20rem of toolbar width,\n * children marked `data-slot=\"data-table-toolbar-label\"` collapse to screen-reader text.\n */\n filters?: React.ReactNode;\n /**\n * Trailing group (column toggle, export). Right-aligned and kept together. Below 48rem\n * of toolbar width, children marked `data-slot=\"data-table-toolbar-label\"` collapse to\n * screen-reader text so the group fits beside the leading controls.\n */\n end?: React.ReactNode;\n};\n\ntype DeleteResourceRecordProps = {\n /**\n * Resource handle returned by `Sdk.resource('<resourceId>')`.\n */\n resource: DeleteResourceRecordResource &\n SdkResourceQueryHandle<'show'> &\n Pick<SdkResource, 'policy_scope' | 'capabilities'>;\n\n /**\n * Record ID to delete.\n */\n recordId: string | null | undefined;\n\n /**\n * Optional already-loaded record. When omitted for an eventable resource, the\n * component loads the record when the confirmation dialog opens.\n */\n record?: Record<string, any>;\n\n /**\n * Button label.\n */\n label?: string;\n\n /**\n * Heading shown in the confirmation dialog.\n */\n confirmTitle?: React.ReactNode;\n\n /**\n * Supporting text shown in the confirmation dialog.\n */\n confirmDescription?: React.ReactNode;\n\n /**\n * Called after a successful deletion, before navigation.\n */\n onSuccess?: () => void;\n\n /**\n * Optional container className.\n */\n className?: string;\n};\n\ntype DropdownMenuProps = {\n /**\n * Element that opens the menu.\n */\n trigger: React.ReactNode;\n\n /**\n * Menu item definitions.\n */\n items: DropdownMenuItem[];\n\n /**\n * Optional menu heading.\n */\n label?: React.ReactNode;\n\n /**\n * Menu alignment relative to the trigger.\n */\n align?: 'start' | 'center' | 'end';\n\n className?: string;\n};\n\ntype DropdownMenuItem = {\n /**\n * Item label.\n */\n label: React.ReactNode;\n\n /**\n * Optional URL. When present the item renders as a link. Resolve a workspace page with\n * `Sdk.utils.page.url(pageId, params)` instead of hardcoding a route string.\n */\n href?: string;\n\n /**\n * Optional Lucide icon name shown beside the label.\n */\n icon?: string;\n\n /**\n * Disable the item.\n */\n disabled?: boolean;\n\n /**\n * Style the item as a destructive action.\n */\n destructive?: boolean;\n\n /**\n * Called when the item is selected.\n */\n onSelect?: () => void;\n};\n\ntype EmailComposerProps = EmailComposerBaseProps & EmailComposerAudienceProps;\n\ntype EmailComposerBaseProps = {\n /**\n * Resource handle returned by `Sdk.resource('<resourceId>')`.\n * Existing single-record form. Use `records` to link multiple records.\n */\n resource?: SdkResourceQueryHandle<'show'> & Pick<SdkResource, 'capabilities'>;\n\n /**\n * Record the email is about. It is linked to the stored message, and \u2014 without\n * `recipients` \u2014 when the resource is notifiable its email field prefills **To**.\n */\n recordId?: string;\n\n /**\n * Records linked to every email. Replaces `resource` and `recordId` when supplied.\n * Without `recipients`, the first record's notifiable email field prefills **To**.\n */\n records?: Array<{ resourceId: string; recordId: string }>;\n\n /**\n * The shape of the data the page supplies through `data` and the recipients'\n * `name`/`data`, so the `{{` menu in the subject, body, and Code tab can offer it.\n * Without it the keys are inferred from those values, except the keys that prefill\n * an editable region.\n */\n variables?: EmailComposerVariable[];\n\n /**\n * Published email layout that wraps the body. When set, the layout is fixed and the\n * picker is hidden. When omitted, the toolbar offers the workspace's published\n * layouts, or no layout at all.\n */\n emailLayoutId?: string | null;\n\n /**\n * Values every email can read as `{{ key }}` \u2014 the same for all recipients, and\n * the defaults for any variables the chosen layout declares. Recipient `data`\n * overrides them per email. A string under an editable region's key prefills that\n * region: `content` is the body when no layout is chosen, and a layout's regions\n * use the keys it declares. The viewer can edit a prefilled region, and the email\n * sends their edit.\n */\n data?: Record<string, any>;\n\n /**\n * Subject the composer opens with, as Liquid source, so it can use `{{ name }}` and\n * the other variables. The viewer can edit it, and \"Send another\" restores it. The\n * composer reads it once, when it mounts: give the component a new `key` to start\n * again from a new subject and new `data`.\n */\n initialSubject?: string;\n\n /**\n * Called after the email is queued.\n */\n onSent?: (response: SdkSendEmailResponse) => void;\n\n /**\n * Called when the request fails.\n */\n onError?: (error: SdkApiError) => void;\n\n /**\n * Optional container className.\n */\n className?: string;\n};\n\ntype EmailComposerVariable = {\n /** The Liquid name, written as `{{ key }}`. Dotted keys are allowed (`order.total`). */\n key: string;\n label?: string;\n description?: string;\n type?: 'text' | 'number' | 'boolean' | 'date';\n};\n\ntype EmailComposerAudienceProps =\n | {\n /**\n * Page-supplied recipient list. When set, every recipient receives their own email\n * (a bulk send), **To** becomes a recipient summary, and each recipient's `name`\n * and `data` personalize their copy. Omit it to compose one email to the record.\n */\n recipients?: EmailComposerRecipient[];\n previewRecipient?: never;\n onPreviewRecipientChange?: never;\n }\n | ({\n /**\n * Page-supplied recipient list. When set, every recipient receives their own email\n * (a bulk send), **To** becomes a recipient summary, and each recipient's `name`\n * and `data` personalize their copy. Omit it to compose one email to the record.\n */\n recipients: EmailComposerRecipient[];\n } & (\n | {\n /**\n * The email of the recipient the preview renders for. `null`, or an address not in\n * `recipients`, previews the first recipient. Omit it to let the composer's\n * \"Preview as\" switcher choose. A repeated address previews its first entry,\n * which is also the one the server sends.\n */\n previewRecipient: string | null;\n /** Called with the email the viewer picks in \"Preview as\". Required with `previewRecipient`. */\n onPreviewRecipientChange: (email: string) => void;\n }\n | {\n previewRecipient?: undefined;\n /** Called with the email the viewer picks in \"Preview as\". Required with `previewRecipient`. */\n onPreviewRecipientChange?: (email: string) => void;\n }\n ));\n\ntype EmailComposerRecipient = {\n email: string;\n /** Exposed to the subject and body as `{{ name }}` for this recipient. */\n name?: string;\n /** Values that override `data` for this recipient only. */\n data?: Record<string, any>;\n} & (\n | { resourceId?: never; recordId?: never }\n /** Supply both IDs to link this recipient's record to their own email. */\n | { resourceId: string; recordId: string }\n);\n\ntype EmailMessagesProps = {\n /**\n * Resource the record belongs to. Pair it with `recordId` for one record's history.\n */\n resourceId?: string;\n\n /**\n * Record whose email history is shown.\n */\n recordId?: string;\n\n /**\n * Records whose linked emails are shown together. Replaces `resourceId` and\n * `recordId`. Every record is authorized for the viewer before anything loads.\n */\n records?: SdkEmailMessagesRecord[];\n\n /**\n * Show every tracked email in the workspace instead of a record's. Requires the\n * `manage_notifications` permission; other viewers see an explanation instead.\n */\n workspace?: boolean;\n\n /**\n * Filters applied to the chosen scope. With `showFilters` they are the starting\n * point the viewer edits; without it they apply as given.\n */\n filters?: SdkEmailMessagesFilters;\n\n /**\n * Render the shared filter toolbar (subject search plus Date, To, From, and\n * Status chips) above the list so the viewer can narrow it.\n */\n showFilters?: boolean;\n\n /**\n * Rows per page, 1 to 100. Previous/next controls appear when there is more than\n * one page.\n */\n perPage?: number;\n\n /**\n * Milliseconds between automatic refreshes while the tab is visible, like `pollInterval`\n * on the data hooks. A single record's history refreshes every 30 seconds by default so\n * delivery-status changes show up without a reload. `records` and `workspace` lists do\n * not refresh by default because every refresh repeats the authorization and search work\n * for the whole scope; pass a value to opt in, or `false` to turn refreshing off. Values\n * under 1000 are raised to 1000 with a console warning.\n */\n pollInterval?: number | false;\n\n /**\n * Show the rendered email body (Preview / Source tabs) inside the detail dialog.\n */\n showEmailBody?: boolean;\n};\n\ntype EmptyStateProps = {\n /**\n * Primary empty-state message.\n */\n title: string;\n\n /**\n * Short supporting explanation.\n */\n description: string;\n\n /**\n * Optional call to action element.\n */\n action?: React.ReactNode;\n\n /**\n * Whether to show the default icon.\n */\n showIcon?: boolean;\n};\n\ntype ErrorBoundaryProps = {\n /** The section protected from errors. */\n children: React.ReactNode;\n\n /** Custom fallback content or renderer. The renderer receives the error and a reset function. */\n fallback?: React.ReactNode | ((props: ErrorBoundaryFallbackProps) => React.ReactNode);\n\n /** Called before the boundary retries its children. */\n onReset?: () => void;\n\n /** Changing this value clears the current error and retries the section. */\n resetKey?: any;\n};\n\ntype ErrorBoundaryFallbackProps = {\n error: Error;\n reset: () => void;\n};\n\ntype ForgotPasswordFormProps = {\n /**\n * The authenticatable resource whose password is being reset. Defaults to the\n * workspace's sole authenticatable resource; required only when the workspace has\n * more than one.\n */\n resourceId?: string;\n\n /** Prefills the email field. */\n initialIdentifier?: string;\n\n /** Label for the identifier (email) field. */\n identifierLabel?: React.ReactNode;\n\n /** Placeholder for the identifier (email) field. */\n identifierPlaceholder?: string;\n\n /** Contents of the submit button. */\n submitLabel?: React.ReactNode;\n\n /** Additional classes for the form element. */\n className?: string;\n\n /** Additional classes for the submit button. */\n buttonClassName?: string;\n\n /**\n * Where the sign-in that completes the reset continues when it needs two-factor.\n * `'hosted'`, the default, uses Diagonal's challenge page. `'inline'` returns to this\n * page to show the challenge, so render `Sdk.Components.TwoFactorChallenge` and\n * `Sdk.Components.TwoFactorSetup` here too.\n */\n twoFactor?: 'inline' | 'hosted';\n\n /** With `'current'`, the visitor returns to this page after the reset signs them in. */\n returnTo?: 'current';\n};\n\ntype FormFieldDisplayProps = {\n /**\n * A resource field from the resource catalog (`resource.fields.someField`).\n * The field type and display settings are resolved automatically from it.\n */\n field: SdkResourceField;\n\n /**\n * The value to display.\n */\n value?: any;\n\n /**\n * Rendered in place of the default em dash when the value is empty. What counts as\n * empty stays with the field's own display (a blank string, `null`, an empty list,\n * an unresolved relation, ...). Pass `null` to render nothing at all. Omit to\n * inherit an enclosing fallback (such as `KeyValueList`'s) or keep the em dash.\n * The fallback renders exactly as given \u2014 the caller owns its styling, so pass a\n * muted node if it should read as a placeholder rather than a value.\n */\n fallback?: React.ReactNode;\n\n className?: string;\n};\n\ntype FormFieldInputProps = {\n /**\n * A resource field handle from the resource catalog (`resource.fields.someField`)\n * or a workspace field definition (`Sdk.workspace.fields.someField`). The field\n * type and display settings are resolved automatically from the handle.\n */\n field: SdkFormField;\n\n /**\n * Controlled value.\n */\n value?: any;\n\n /**\n * Called with the new value.\n */\n onChange: (value: any) => void;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Whether the input is disabled.\n */\n disabled?: boolean;\n\n /**\n * Form context forwarded to the underlying field component. Relation fields use\n * `context.ancestorResourceIds` to prevent circular relation pickers and\n * `context.errors` for inline validation. Composite fields such as location\n * also read nested errors from `context.errors`, using `errorKeySegments` as\n * the key prefix. Defaults to `null` (no context).\n */\n context?: Record<string, any> | null;\n\n /**\n * Prefix segments for `context.errors` lookups. Location fields read\n * `<prefix>.<subfield>` errors for `line1`, `line2`, `city`, `state`,\n * `postal_code`, and `country`. Pass `['display_address']` for errors such as\n * `display_address.line1`, shown beside Address line 1.\n * Nested inline-create drafts append their draft index and child field key,\n * so a caller rendering the `files` relation passes `['files']` for errors\n * keyed `files.0.file_id`.\n */\n errorKeySegments?: string[];\n};\n\ntype FormCheckboxProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Actions shown beside the input.\n */\n inputActions?: React.ReactNode;\n\n /**\n * Label layout.\n */\n labelPlacement?: 'stacked' | 'inline';\n\n /**\n * Controlled checked state.\n */\n checked?: boolean;\n\n /**\n * Initial checked state for uncontrolled usage.\n */\n defaultChecked?: boolean;\n\n /**\n * Called when the checkbox changes.\n */\n onCheckedChange?: (checked: boolean) => void;\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n\n className?: string;\n};\n\ntype FormCheckboxGroupInputProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Actions shown beside the input.\n */\n inputActions?: React.ReactNode;\n\n /**\n * Checkbox options.\n */\n options: RadioOption[];\n\n /**\n * Selected option values.\n */\n selected: string[];\n\n /**\n * Called with selected values.\n */\n onChange: (values: string[]) => void;\n\n /**\n * Disable all options.\n */\n disabled?: boolean;\n\n className?: string;\n};\n\ntype RadioOption = {\n /**\n * Option label.\n */\n label: React.ReactNode;\n\n /**\n * Option value used by the form.\n */\n value: string;\n\n /**\n * Disable this option.\n */\n disabled?: boolean;\n};\n\ntype FormCodeInputProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * The digits typed so far, in order.\n */\n value?: string;\n\n /**\n * Called with the digits typed so far on every change.\n */\n onChange?: (value: string) => void;\n\n /**\n * Number of digits in the code.\n */\n length?: number;\n\n /**\n * Called with the full code once every cell holds a digit. Verify the code here.\n */\n onComplete?: (code: string) => void;\n\n /**\n * Disable the input, for example while the code is being verified.\n */\n disabled?: boolean;\n\n /**\n * Focus the first cell when the input appears.\n */\n autoFocus?: boolean;\n\n className?: string;\n};\n\ntype FormColorInputProps = PrimitiveFormFieldProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Selected color value.\n */\n value?: string | null;\n\n /**\n * Called when a color is applied.\n */\n onChange?: (value: string) => void;\n\n /**\n * Show workspace theme token swatches.\n */\n showSwatches?: boolean;\n disabled?: boolean;\n placeholder?: string;\n size?: 'default' | 'xs' | 'sm' | 'lg' | 'icon' | 'smIcon' | null;\n};\n\ntype PrimitiveFormFieldProps = Omit<\n PrimitiveHtmlProps<HTMLElement>,\n 'children' | 'className' | 'id' | 'onChange' | 'onSelect'\n> & {\n label?: React.ReactNode;\n labelActions?: React.ReactNode;\n labelPlacement?: 'stacked' | 'inline';\n helpText?: React.ReactNode;\n helpTextPlacement?: 'before-control' | 'after-control';\n error?: string | string[];\n\n /**\n * Show the required-field indicator next to the label. This does not add validation.\n */\n isRequired?: boolean | null;\n\n inputActions?: React.ReactNode;\n id?: string;\n classes?: string;\n className?: string;\n hidden?: boolean;\n role?: React.AriaRole;\n style?: React.CSSProperties;\n tabIndex?: number;\n title?: string;\n 'data-block-id'?: string;\n 'data-block-type'?: string;\n};\n\ntype HTMLElement = any;\n\ntype FormComboboxInputProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Show the required-field indicator next to the label. This does not add validation.\n */\n isRequired?: boolean | null;\n\n /**\n * Available choices.\n */\n options: ComboboxOption[];\n\n /**\n * Controlled selected value.\n */\n value: string | null;\n\n /**\n * Called when the selected value changes.\n */\n onChange: (value: string | null) => void;\n\n /**\n * Placeholder shown before selection.\n */\n placeholder?: string | null;\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n\n /**\n * Allow values not present in `options`.\n */\n allowCustomValue?: boolean;\n\n /**\n * Show a clear button when a value is selected.\n */\n clearable?: boolean;\n\n className?: string;\n};\n\ntype ComboboxOption = any;\n\ntype FormContentInputProps = Omit<\n UiFormContentInputProps,\n 'value' | 'onChange' | 'contentSettings' | 'oneLine'\n> & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Show the required-field indicator next to the label. This does not add validation.\n */\n isRequired?: boolean | null;\n\n /**\n * TipTap JSON content value.\n */\n value: JSONContent | null;\n\n /**\n * Called with the updated TipTap JSON content.\n */\n onChange: (value: JSONContent) => void;\n\n /**\n * Content field settings from the resource field or workflow variable.\n */\n contentSettings?: ContentSettings;\n\n /**\n * Render a single-line content editor.\n */\n oneLine?: boolean;\n};\n\ntype UiFormContentInputProps = any;\n\ntype JSONContent = any;\n\ntype ContentSettings = {\n extensions?: ExtensionsSettings;\n placeholder?: string | null;\n allowed_nodes?: AllowedNodesSettings;\n enable_skill_suggestions?: boolean;\n};\n\ntype ExtensionsSettings = {\n bold?: boolean;\n italic?: boolean;\n strike?: boolean;\n underline?: boolean;\n link?: boolean;\n} | null;\n\ntype AllowedNodesSettings = {\n heading?: boolean;\n blockquote?: boolean;\n bulletList?: boolean;\n orderedList?: boolean;\n media?: boolean;\n} | null;\n\ntype FormDateInputProps = PrimitiveFormFieldProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Selected value such as `yyyy-MM-dd`.\n */\n value: string;\n\n /**\n * Called when the selected date changes.\n */\n onChange: (value: string) => void;\n\n /**\n * Selection granularity.\n */\n granularity?: 'day' | 'month' | 'year';\n disabled?: boolean;\n placeholder?: string;\n\n /**\n * Earliest selectable date, `yyyy-MM-dd`.\n */\n minDate?: string;\n\n /**\n * Latest selectable date, `yyyy-MM-dd`.\n */\n maxDate?: string;\n};\n\ntype FormDatetimeInputProps = PrimitiveFormFieldProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Serialized datetime value.\n */\n value: string;\n\n /**\n * Called when date or time changes.\n */\n onChange: (value: string) => void;\n\n /**\n * Allow second-level precision.\n */\n showSeconds?: boolean;\n disabled?: boolean;\n placeholder?: string;\n clearable?: boolean;\n\n /**\n * Earliest selectable date, `yyyy-MM-dd`.\n */\n minDate?: string;\n\n /**\n * Latest selectable date, `yyyy-MM-dd`.\n */\n maxDate?: string;\n};\n\ntype FormFileUploadProps = PrimitiveFormFieldProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation error shown below the input.\n */\n error?: string | string[];\n\n /**\n * Whether the field is required.\n */\n isRequired?: boolean;\n\n /**\n * Current file value: a temp path (`tmp/\u2026`) after a fresh upload, a stored file ID for\n * an existing file, or null when no file is selected.\n */\n value?: string | null;\n\n /**\n * Called with the temp path after a successful upload, or null when the file is removed.\n * Persist the temp path to your form state; the backend resolves it to a permanent file\n * record on save.\n */\n onChange: (value: string | null) => void;\n\n /**\n * Accepted file types passed to the file picker, e.g. `\".pdf,.docx\"` or `\"image/*\"`.\n */\n accept?: string | string[];\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n};\n\ntype FormLocationInputProps = PrimitiveFormFieldProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input group.\n */\n helpText?: React.ReactNode;\n\n /**\n * General validation error shown below the field group.\n */\n error?: string | string[];\n\n /**\n * Whether the field is required.\n */\n isRequired?: boolean;\n\n /**\n * Current location value.\n */\n value?: LocationValue | null;\n\n /**\n * Called when the location changes.\n */\n onChange?: (value: LocationValue | null) => void;\n\n /**\n * Placeholder for the collapsed address autocomplete input.\n */\n placeholder?: string;\n\n /**\n * Disable address edits while keeping the structured details expandable for inspection.\n */\n disabled?: boolean;\n\n /**\n * Per-subfield validation errors, keyed by address field name\n * (`line1`, `line2`, `city`, `state`, `postal_code`, `country`).\n */\n errors?: LocationAddressFormErrors;\n};\n\ntype LocationValue = {\n point: { x: number | null; y: number | null } | null;\n display_name: string | null;\n line1: string | null;\n line2: string | null;\n city: string | null;\n county: string | null;\n state: string | null;\n country: string | null;\n country_code: string | null;\n postal_code: string | null;\n place_id: string | null;\n};\n\ntype LocationAddressFormErrors = Partial<\n Record<'line1' | 'line2' | 'city' | 'state' | 'postal_code' | 'country', string | string[]>\n>;\n\ntype FormMarkdownInputProps = Omit<\n UiFormMarkdownInputProps,\n | keyof FormFieldPropsWithoutChildren\n | 'showMediaControls'\n | 'allowedMarks'\n | 'allowedBlocks'\n | 'editorClassName'\n | 'value'\n | 'onChange'\n> & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Show the required-field indicator next to the label. This does not add validation.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Input element id.\n */\n id?: string;\n\n /**\n * Markdown string value.\n */\n value: string;\n\n /**\n * Called with the updated Markdown string.\n */\n onChange: (value: string) => void;\n\n /**\n * Placeholder shown while the Markdown value is empty.\n */\n placeholder?: string;\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n\n /**\n * Show the formatting toolbar.\n */\n showFormattingToolbar?: boolean;\n\n /**\n * Place the formatting toolbar inside the input or above it.\n */\n toolbarPlacement?: 'floating' | 'fixed';\n\n /**\n * Accessible name for the input.\n */\n ariaLabel?: string;\n\n /**\n * Additional classes for the editor.\n */\n className?: string;\n};\n\ntype UiFormMarkdownInputProps = any;\n\ntype FormFieldPropsWithoutChildren = any;\n\ntype FormMoneyInputProps = PrimitiveFormInputProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Amount in minor units (e.g. cents).\n */\n value: number | null;\n\n /**\n * Called with the amount in minor units.\n */\n onChange: (value: number | null) => void;\n\n /**\n * ISO currency code. Defaults to USD.\n */\n currency?: string;\n onCurrencyChange?: (currency: string) => void;\n currencies?: Array<{\n readonly label: string;\n readonly value: string;\n readonly symbol: string;\n }>;\n disabled?: boolean;\n placeholder?: string;\n};\n\ntype PrimitiveFormInputProps = PrimitiveFormFieldProps & PrimitiveInputNativeProps;\n\ntype PrimitiveInputNativeProps = Omit<\n PrimitiveHtmlProps<HTMLInputElement>,\n 'children' | 'onChange'\n> &\n Omit<\n Partial<Pick<React.InputHTMLAttributes<HTMLInputElement>, PrimitiveInputAttributeName>>,\n 'onChange' | 'type' | 'value'\n >;\n\ntype HTMLInputElement = any;\n\ntype PrimitiveInputAttributeName =\n | 'accept'\n | 'alt'\n | 'autoComplete'\n | 'capture'\n | 'checked'\n | 'disabled'\n | 'form'\n | 'formAction'\n | 'formEncType'\n | 'formMethod'\n | 'formNoValidate'\n | 'formTarget'\n | 'height'\n | 'list'\n | 'max'\n | 'maxLength'\n | 'min'\n | 'minLength'\n | 'multiple'\n | 'name'\n | 'pattern'\n | 'placeholder'\n | 'readOnly'\n | 'required'\n | 'size'\n | 'src'\n | 'step'\n | 'type'\n | 'value'\n | 'width'\n | 'onChange';\n\ntype FormMultiComboboxInputProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Show the required-field indicator next to the label. This does not add validation.\n */\n isRequired?: boolean | null;\n\n /**\n * Available choices.\n */\n options: MultiSelectOption[];\n\n /**\n * Selected option values.\n */\n selected: string[];\n\n /**\n * Called with selected values.\n */\n onChange: (values: string[]) => void;\n\n /**\n * Placeholder shown when no values are selected.\n */\n placeholder?: string;\n\n className?: string;\n\n /**\n * Show a search input in the menu.\n */\n allowSearch?: boolean;\n\n /**\n * Allow values not present in `options`.\n */\n allowCustomValues?: boolean;\n};\n\ntype MultiSelectOption = any;\n\ntype FormNumberInputProps = Omit<\n UiFormTextInputProps,\n 'value' | 'onChange' | 'type' | 'placeholder'\n> & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Show the required-field indicator next to the label. This does not add validation.\n */\n isRequired?: boolean | null;\n\n /**\n * Controlled value.\n */\n value?: string | number;\n\n /**\n * Called with the new string value.\n */\n onChange: TextInputProps['onChange'];\n\n /**\n * Placeholder text.\n */\n placeholder?: string;\n};\n\ntype UiFormTextInputProps = any;\n\ntype TextInputProps = any;\n\ntype FormPasswordInputProps = PrimitiveFormInputProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Controlled value.\n */\n value?: string;\n\n /**\n * Called with the new value.\n */\n onChange: (value: string) => void;\n\n /**\n * Placeholder text.\n */\n placeholder?: string;\n disabled?: boolean;\n readOnly?: boolean;\n showToggle?: boolean;\n isFocused?: boolean;\n isLoading?: boolean;\n prefixIcon?: React.ElementType;\n};\n\ntype FormPhoneInputProps = Omit<\n UiFormPhoneInputProps,\n 'value' | 'onChange' | 'defaultCountry' | 'allowExtension'\n> & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Phone number value. An optional extension is serialized with the number,\n * such as `+14039014573 ext. 123`.\n */\n value?: UiFormPhoneInputProps['value'];\n\n /**\n * Called when the phone number changes. Receives the number, with an optional\n * extension serialized as `+14039014573 ext. 123`.\n */\n onChange?: UiFormPhoneInputProps['onChange'];\n\n /**\n * Default country code such as `US`.\n */\n defaultCountry?: UiFormPhoneInputProps['defaultCountry'];\n};\n\ntype UiFormPhoneInputProps = any;\n\ntype FormRadioProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Actions shown beside the input.\n */\n inputActions?: React.ReactNode;\n\n /**\n * Radio options.\n */\n options: RadioOption[];\n\n /**\n * Controlled selected value.\n */\n value?: string;\n\n /**\n * Initial selected value for uncontrolled usage.\n */\n defaultValue?: string;\n\n /**\n * Called when the selected value changes.\n */\n onValueChange?: (value: string) => void;\n\n /**\n * Disable all options.\n */\n disabled?: boolean;\n\n className?: string;\n};\n\ntype FormSelectInputProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Actions shown beside the input.\n */\n inputActions?: React.ReactNode;\n\n /**\n * Available choices.\n */\n options: SelectOption[];\n\n /**\n * Controlled selected value.\n */\n value?: string;\n\n /**\n * Initial selected value for uncontrolled usage.\n */\n defaultValue?: string;\n\n /**\n * Called when the selected value changes.\n */\n onValueChange?: (value: string) => void;\n\n /**\n * Placeholder shown before selection.\n */\n placeholder?: string;\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n\n className?: string;\n};\n\ntype SelectOption = {\n /**\n * Option label.\n */\n label: React.ReactNode;\n\n /**\n * Option value used by the form.\n */\n value: string;\n\n /**\n * Disable this option.\n */\n disabled?: boolean;\n};\n\ntype FormSliderProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Actions shown beside the input.\n */\n inputActions?: React.ReactNode;\n\n /**\n * Controlled slider values.\n */\n value?: number[];\n\n /**\n * Initial slider values for uncontrolled usage.\n */\n defaultValue?: number[];\n\n /**\n * Called when values change.\n */\n onValueChange?: (value: number[]) => void;\n\n /**\n * Minimum value.\n */\n min?: number;\n\n /**\n * Maximum value.\n */\n max?: number;\n\n /**\n * Step increment.\n */\n step?: number;\n\n /**\n * Show the current value beside the slider.\n */\n showValue?: boolean;\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n\n className?: string;\n};\n\ntype FormSwitchProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Actions shown beside the input.\n */\n inputActions?: React.ReactNode;\n\n /**\n * Label layout.\n */\n labelPlacement?: 'stacked' | 'inline';\n\n /**\n * Controlled checked state.\n */\n checked?: boolean;\n\n /**\n * Initial checked state for uncontrolled usage.\n */\n defaultChecked?: boolean;\n\n /**\n * Called when the switch changes.\n */\n onCheckedChange?: (checked: boolean) => void;\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n\n className?: string;\n};\n\ntype FormTextAreaProps = PrimitiveFormTextareaProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Controlled value.\n */\n value?: string;\n\n /**\n * Called with the new value.\n */\n onChange: (value: string) => void;\n\n /**\n * Placeholder text.\n */\n placeholder?: string;\n disabled?: boolean;\n rows?: number;\n};\n\ntype PrimitiveFormTextareaProps = PrimitiveFormFieldProps & PrimitiveTextareaNativeProps;\n\ntype PrimitiveTextareaNativeProps = Omit<PrimitiveHtmlProps<HTMLTextAreaElement>, 'onChange'> &\n Omit<\n Partial<\n Pick<React.TextareaHTMLAttributes<HTMLTextAreaElement>, PrimitiveTextareaAttributeName>\n >,\n 'onChange' | 'value'\n >;\n\ntype HTMLTextAreaElement = any;\n\ntype PrimitiveTextareaAttributeName =\n | 'autoComplete'\n | 'cols'\n | 'dirName'\n | 'disabled'\n | 'form'\n | 'maxLength'\n | 'minLength'\n | 'name'\n | 'placeholder'\n | 'readOnly'\n | 'required'\n | 'rows'\n | 'value'\n | 'wrap'\n | 'onChange';\n\ntype FormTextInputProps = PrimitiveFormInputProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Controlled value.\n */\n value?: string;\n\n /**\n * Called with the new value.\n */\n onChange: (value: string) => void;\n\n /**\n * Input type.\n */\n type?:\n | 'text'\n | 'email'\n | 'password'\n | 'number'\n | 'date'\n | 'time'\n | 'datetime-local'\n | 'url'\n | 'search'\n | 'tel'\n | 'color'\n | 'hidden'\n | 'range'\n | 'textarea';\n\n /**\n * Placeholder text.\n */\n placeholder?: string;\n disabled?: boolean;\n autoComplete?: string;\n name?: string;\n isFocused?: boolean;\n isLoading?: boolean;\n prefixIcon?: React.ElementType;\n};\n\ntype FormTimeInputProps = PrimitiveFormFieldProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Selected time value.\n */\n value: string;\n\n /**\n * Called when the selected time changes.\n */\n onChange: (value: string) => void;\n\n /**\n * Allow second-level precision.\n */\n showSeconds?: boolean;\n disabled?: boolean;\n placeholder?: string;\n};\n\ntype FormTimezoneInputProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Actions shown beside the input.\n */\n inputActions?: React.ReactNode;\n\n /**\n * Label layout.\n */\n labelPlacement?: 'stacked' | 'inline';\n\n /**\n * Controlled timezone identifier.\n */\n value?: string;\n\n /**\n * Initial timezone identifier for uncontrolled usage.\n */\n defaultValue?: string;\n\n /**\n * Called when the timezone changes.\n */\n onChange?: (value: string) => void;\n\n /**\n * Placeholder shown before selection.\n */\n placeholder?: string;\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n\n className?: string;\n};\n\ntype HasAnyPermissionProps = PermissionGateProps & {\n /** Permission ids or slugs. The current identity must hold at least one. */\n permissions: readonly string[];\n};\n\ntype PermissionGateProps = {\n /** Content to render when the check passes. It does not mount when the check fails. */\n children?: React.ReactNode;\n\n /** Content to render when the check fails. Renders nothing when omitted. */\n fallback?: React.ReactNode;\n};\n\ntype HasPermissionProps = PermissionGateProps & {\n /** Permission id or slug that the current identity must hold. */\n permission: string;\n};\n\ntype IconProps = {\n /**\n * Lucide icon name in camelCase, PascalCase, or kebab-case.\n */\n name?: string | null;\n\n /**\n * Additional CSS classes.\n */\n className?: string;\n\n /**\n * Icon size in pixels.\n */\n size?: number;\n\n /**\n * SVG stroke width.\n */\n strokeWidth?: number;\n\n /**\n * Direct color override (any CSS color). Defaults to `currentColor`.\n */\n color?: string;\n};\n\ntype IframeProps = React.IframeHTMLAttributes<HTMLIFrameElement> & {\n /**\n * Embed URL. Only use with URLs the workspace trusts.\n */\n src: string;\n\n /**\n * Accessible iframe title.\n */\n title: string;\n\n /**\n * Additional iframe classes.\n */\n className?: string;\n};\n\ntype HTMLIFrameElement = any;\n\ntype ImageProps = Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'alt' | 'src'> & {\n ref?: React.Ref<HTMLImageElement>;\n\n /**\n * Image URL. Pass `Sdk.utils.file(record.file_field)` directly; null shows the fallback.\n */\n src?: string | null;\n\n /**\n * Accessible image description. Use an empty string only when the image is decorative.\n */\n alt: string;\n};\n\ntype HTMLImageElement = any;\n\ntype KeyValueListProps = {\n /**\n * Rows of labeled values.\n */\n items: KeyValueItem[];\n\n /**\n * Rendered in place of a row's value when it is empty (`null`, `undefined`, or an\n * empty string). `FormFieldDisplay` values inherit it too, unless they set their own\n * `fallback`. Omit to leave empty raw values blank and keep the em dash in field\n * displays. The fallback renders exactly as given \u2014 the caller owns its styling, so\n * pass a muted node (e.g. `<em>Not set</em>` or a `text-muted-foreground` span) if it\n * should read as a placeholder rather than a value.\n */\n fallback?: React.ReactNode;\n\n className?: string;\n};\n\ntype KeyValueItem = {\n /**\n * Row label.\n */\n label: React.ReactNode;\n\n /**\n * Row value.\n */\n value: React.ReactNode;\n\n /**\n * Optional supporting text shown below the value.\n */\n description?: React.ReactNode;\n};\n\ntype ListProps = {\n /**\n * List item definitions.\n */\n items: ListItem[];\n\n /**\n * Text shown when `items` is empty.\n */\n emptyText?: string;\n\n className?: string;\n};\n\ntype ListItem = {\n /**\n * Stable identifier for the row, used as the React key.\n */\n id?: string | number;\n\n /**\n * Row title.\n */\n title: React.ReactNode;\n\n /**\n * Optional supporting text below the title.\n */\n description?: React.ReactNode;\n\n /**\n * Optional metadata shown on the right (e.g. status, timestamp).\n */\n meta?: React.ReactNode;\n\n /**\n * Optional row action element.\n */\n action?: React.ReactNode;\n};\n\ntype LoginFormProps = {\n /**\n * The authenticatable resource to sign in against. Defaults to the workspace's sole\n * authenticatable resource; required only when the workspace has more than one.\n */\n resourceId?: string;\n\n /** Label for the identifier (email) field. */\n identifierLabel?: React.ReactNode;\n\n /** Placeholder for the identifier (email) field. */\n identifierPlaceholder?: string;\n\n /** Label for the password field. */\n passwordLabel?: React.ReactNode;\n\n /** Placeholder for the password field. */\n passwordPlaceholder?: string;\n\n /** Contents of the button that submits the email and moves on to verification. */\n continueLabel?: React.ReactNode;\n\n /** Contents of the password submit button. */\n submitLabel?: React.ReactNode;\n\n /** Contents of the button that requests an emailed sign-in link. */\n magicLinkLabel?: React.ReactNode;\n\n /**\n * Contents of the button shown when the identified account has no password yet \u2014 it\n * mails a link to set one, alongside the emailed sign-in link when the resource\n * offers both.\n */\n setPasswordLabel?: React.ReactNode;\n\n /** Additional classes for the form element. */\n className?: string;\n\n /** Additional classes for the submit button. */\n buttonClassName?: string;\n\n /**\n * Id of the page to land on after a successful sign-in \u2014 pass a stable page id, never\n * a path; the server resolves the page's route itself. A stored intended URL \u2014 the\n * protected URL the visitor was bounced to the login page from \u2014 takes precedence, the\n * workspace homepage is the final fallback, and an id that doesn't resolve (unknown,\n * unpublished, parameterized route, other workspace) is ignored rather than failing\n * the login. For destinations that depend on who logged in, redirect to a static page\n * that reads `Sdk.identity` instead.\n */\n redirectPage?: string;\n\n /**\n * Return the visitor to the exact current page URL after password or Magic Link\n * sign-in, path parameters and query string included. Use `redirectPage` instead\n * for a static, parameter-free destination.\n */\n returnTo?: 'current';\n\n /**\n * Where a sign-in that needs two-factor continues. `'inline'`, the default, shows the\n * SMS challenge, or the first MFA setup, in place of this form. `'hosted'` leaves the\n * page for Diagonal's challenge page.\n */\n twoFactor?: 'inline' | 'hosted';\n};\n\ntype UserControlProps = {\n /** Additional classes for the trigger button. */\n className?: string;\n\n /** Button visual style. */\n variant?: ButtonProps['variant'];\n\n /** Button size preset. */\n size?: ButtonProps['size'];\n};\n\ntype MapProps = {\n /**\n * Diagonal location value (e.g. from workflow input data). The component shows a\n * placeholder when maps are unavailable.\n */\n location: any;\n\n /**\n * Container sizing classes.\n */\n className?: string;\n};\n\ntype ModalProps = {\n /**\n * Controlled open state.\n */\n open?: boolean;\n\n /**\n * Initial uncontrolled open state.\n */\n defaultOpen?: boolean;\n\n /**\n * Called when the modal opens or closes.\n */\n onOpenChange?: (open: boolean) => void;\n\n /**\n * Element that opens the modal.\n */\n trigger?: React.ReactNode;\n\n /**\n * Modal heading.\n */\n title?: React.ReactNode;\n\n /**\n * Supporting text below the title.\n */\n description?: React.ReactNode;\n\n /**\n * Main modal body.\n */\n children: React.ReactNode;\n\n /**\n * Footer actions. With an escape action (Cancel), wrap them in\n * `<div className=\"flex w-full items-center justify-between gap-2\">`: a ghost Cancel first,\n * then the other actions in one group with the confirming action last, so Cancel sits at\n * the leading edge and the confirming action at the trailing edge. A lone Close button\n * needs no wrapper; the footer keeps it at the trailing edge.\n */\n footer?: React.ReactNode;\n\n /**\n * Width preset. Defaults to 'md'.\n */\n size?: ModalSize;\n\n /**\n * Whether the modal can be closed by the user via the close button or escape key.\n */\n isCloseable?: boolean;\n\n className?: string;\n};\n\ntype ModalSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\ntype NavMainProps = {\n /** Navigation menu items, typically `Sdk.navigationMenu(id).items`. */\n navigation: any[];\n\n /** Optional group label rendered above the items. */\n label?: string;\n};\n\ntype NotificationComposerProps = {\n /** Saved type, template, and literal context values. Supply recipient only for a type with a recipient resource. */\n input: SdkPreviewNotificationInput;\n /** Complete draft for the chosen channels, or null while resolving, after a resolve failure, or while the choice cannot be sent. Never sends. */\n onChange: (draft: SdkResolvedNotification | null) => void;\n /** The channels the page offers, out of those the template enables. Omit to offer every channel the template enables. */\n channels?: SdkNotificationChannel[];\n /** Start on fewer channels than the recipient can receive. Ignored when the recipient cannot receive one of them. */\n defaultChannel?: SdkNotificationDeliveryChannel;\n /** Omit to allow every layout field. An empty array locks every layout field. */\n editableFields?: string[];\n /** Allow subject edits. Defaults to true. */\n editableSubject?: boolean;\n /** Allow text message edits. Defaults to true. */\n editableSmsBody?: boolean;\n /** Show the optional layout field source editors. Defaults to false. */\n showLayoutFields?: boolean;\n /** Lock all editors while the parent submits its action. */\n disabled?: boolean;\n /** Called when the notification cannot be resolved. Not called while it only waits for variable values. */\n onError?: (error: SdkApiError) => void;\n /** Optional container classes. */\n className?: string;\n};\n\ntype NotificationConsentFormProps = NotificationConsentFormBaseProps &\n NotificationConsentFormChannelProps;\n\ntype NotificationConsentFormBaseProps = {\n /**\n * The notifiable resource of the record whose consent this form manages. Omit it and\n * `recordId` to manage the signed-in person's own record, which needs no permission.\n */\n resourceId?: string;\n\n /**\n * The record whose consent this form manages. Another person's record follows the\n * resource's record policies, as a record edit does.\n */\n recordId?: string;\n\n /** Label of the email row. */\n emailLabel?: React.ReactNode;\n\n /** Text under the email label. */\n emailDescription?: React.ReactNode;\n\n /** Label of the text-message row. */\n smsLabel?: React.ReactNode;\n\n /** Disable every switch, for example while the record has no phone number yet. */\n disabled?: boolean;\n\n /** Called after a switch change was saved. */\n onSaved?: (consent: SdkNotificationConsent) => void;\n\n /** Called when a save fails. The switch reverts and the row shows the error too. */\n onError?: (error: SdkApiError) => void;\n\n /** Show when consent last changed under the rows. */\n showChangedAt?: boolean;\n\n /** Additional classes for the container. */\n className?: string;\n};\n\ntype NotificationConsentFormChannelProps =\n | {\n /**\n * The rows to show, in order. Defaults to email and text messages. A list that\n * includes `sms` requires `smsDisclosure`.\n */\n channels: Array<'email'>;\n smsDisclosure?: never;\n }\n | {\n /**\n * The rows to show, in order. Defaults to email and text messages. A list that\n * includes `sms` requires `smsDisclosure`.\n */\n channels?: NotificationConsentChannel[];\n\n /**\n * Text under the text-message label that tells the person what they agree to. The\n * platform ships no legal wording: write it for your workspace, for example with\n * `Sdk.workspace.name`, and state how to opt out. Required when the rows include SMS.\n */\n smsDisclosure: React.ReactNode;\n };\n\ntype NotificationConsentChannel = 'email' | 'sms';\n\ntype NotificationHistoryProps = {\n /** Resource the record belongs to. Pair it with `recordId` for one record's history. */\n resourceId?: string;\n\n /** Record whose notifications are shown. */\n recordId?: string;\n\n /**\n * Records whose notifications are shown together. Replaces `resourceId` and `recordId`.\n * The viewer needs Show access to every record.\n */\n records?: { resourceId: string; recordId: string }[];\n\n /**\n * Show every notification in the workspace. Requires the `manage_notifications`\n * permission; other viewers see an explanation instead.\n */\n workspace?: boolean;\n\n /** The lifecycle tab that opens first. Defaults to `upcoming`. */\n defaultStatus?: SdkNotificationHistoryStatus;\n\n /** Filters applied to every tab. `emailStatus` applies to the Sent tab only. */\n filters?: NotificationHistoryFilters;\n\n /** Rows per page, 1 to 100. Previous/next controls appear when there is more than one page. */\n perPage?: number;\n\n /** Show the rendered email body (Preview / Source tabs) for each email attempt. */\n showEmailBody?: boolean;\n};\n\ntype NotificationHistoryFilters = Pick<\n SdkNotificationHistoryOptions,\n | 'resourceId'\n | 'search'\n | 'typeName'\n | 'templateName'\n | 'source'\n | 'emailStatus'\n | 'dateFrom'\n | 'dateBefore'\n | 'timezone'\n | 'sort'\n>;\n\ntype NotificationVariablesFormProps = {\n /** The notification type whose variables the form edits. Key the form by it to start over when it changes. */\n notificationTypeId: string;\n /** Values keyed by variable key. A record variable holds its record id. */\n value: Record<string, any>;\n /** Receives the next values: only this type's variables, with each record as its id. Never sends anything. */\n onChange: (value: Record<string, any>) => void;\n /** The failed send, preview, or schedule request. Its `variables` messages show above and beside the inputs. */\n error?: SdkApiError | null;\n /** Lock every input while the parent submits. */\n disabled?: boolean;\n /** Optional container classes. */\n className?: string;\n};\n\ntype PageHeaderProps = {\n /**\n * Main page title.\n */\n title: string;\n\n /**\n * Short page summary.\n */\n description?: string;\n\n /**\n * Action buttons or links displayed beside the title.\n */\n actions?: React.ReactNode;\n\n className?: string;\n};\n\ntype PaymentFormProps = {\n /**\n * Payable resource id (e.g. the Invoices resource).\n */\n payableResourceId: string;\n\n /**\n * Payable record id to pay.\n */\n payableRecordId: string;\n\n /**\n * Partial amount in cents with its currency. Omit to pay the full\n * outstanding balance.\n */\n amount?: { amount: number; currency: string };\n\n /**\n * Pay button label. Defaults to \"Pay\" with the formatted amount.\n */\n label?: string;\n\n /**\n * How a newly entered payment method is saved to the payable's payer for\n * future (off-session) payments. 'always' saves every payment method and shows a\n * disclosure line; 'ask' shows a consent checkbox.\n * Omit to only offer the checkbox to the authenticated payer.\n * Ignored when the payable has no payer to save to. Without `onSubmitPayment`,\n * also ignored for a visitor whom the payer's Payment Methods policies do not\n * let save a payment method, such as a guest: that visitor pays without saving.\n */\n savePaymentMethod?: 'always' | 'ask';\n\n /**\n * Save the successful payment method and make it the payer's default.\n * This applies to both newly entered and already-saved methods.\n * Ignored when the payable has no payer to save to. A newly entered method\n * follows the same visitor rule as `savePaymentMethod`.\n */\n setPaymentMethodAsDefault?: boolean;\n\n /**\n * The purchaser's name and email for an invoice with no payer (a guest\n * purchase), such as the purchaser the page stored for the checkout.\n * Stripe records them on the payment, and Wallet shows them.\n * Ignored when the invoice has a payer.\n * Never used to find a payer or a saved payment method.\n */\n billingDetails?: { name?: string; email?: string };\n\n /**\n * Called after the payment succeeds or enters processing.\n */\n onPaid?: () => void;\n\n /**\n * Submit a Stripe ConfirmationToken through a request workflow instead of the\n * built-in intent endpoint. Token creation does not charge the payment method.\n * Return the Payment: Confirm submitted payment output directly, not an envelope.\n * Custom submission receives either a ConfirmationToken or a saved method record ID.\n * Use the closure to add checkout context. Authorize the payable in the workflow.\n */\n onSubmitPayment?: (submission: PaymentSubmission) => Promise<PaymentSubmissionResult>;\n\n /** Called for succeeded or processing separately. Processing is not settlement. */\n onPaymentResult?: (status: 'succeeded' | 'processing') => void;\n};\n\ntype PaymentSubmission = {\n payable_resource_id: string;\n payable_record_id: string;\n amount: { amount: number; currency: string } | null;\n expected_total: { amount: number; currency: string };\n save_payment_method: boolean;\n set_payment_method_as_default: boolean;\n} & (\n | { confirmation_token_id: string; payment_method_id?: never; payment_attempt_id?: never }\n | { confirmation_token_id?: never; payment_method_id: string; payment_attempt_id: string }\n);\n\ntype PaymentSubmissionResult = {\n payment_intent_id: string;\n payment_record_id?: string | null;\n status: 'succeeded' | 'processing' | 'requires_action';\n client_secret: string | null;\n};\n\ntype PaymentMethodsProps = {\n /**\n * Payer resource id.\n */\n resourceId: string;\n\n /**\n * Payer record id.\n */\n recordId: string;\n\n /** Show and add payment methods for this accounting entity only. */\n accountingEntityId?: string;\n\n /**\n * Only show the saved list and the add flow, hiding the set-default and\n * remove actions.\n */\n collectOnly?: boolean;\n};\n\ntype PieChartProps<TRow extends object = Record<string, any>> = {\n /**\n * Rows to chart.\n */\n data: TRow[];\n\n /**\n * Field key for slice labels.\n */\n nameKey: Extract<keyof TRow, string>;\n\n /**\n * Field key for slice values.\n */\n valueKey: Extract<keyof TRow, string>;\n\n /**\n * Optional chart heading.\n */\n title?: string;\n\n /**\n * Chart height in pixels.\n */\n height?: number;\n};\n\ntype PopoverProps = {\n /**\n * Element that opens the popover.\n */\n trigger: React.ReactNode;\n\n /**\n * Popover body content.\n */\n content: React.ReactNode;\n\n /**\n * Controlled open state.\n */\n open?: boolean;\n\n /**\n * Called when the popover opens or closes.\n */\n onOpenChange?: (open: boolean) => void;\n\n /**\n * Alignment relative to the trigger.\n */\n align?: 'start' | 'center' | 'end';\n\n /**\n * Preferred side relative to the trigger.\n */\n side?: 'top' | 'right' | 'bottom' | 'left';\n\n className?: string;\n};\n\ntype QrCodeProps = {\n /**\n * Value encoded into the QR code. Pass a non-empty value; the component shows a helpful\n * placeholder when missing.\n */\n value?: string | null;\n\n /**\n * Optional label shown beneath the QR code.\n */\n label?: React.ReactNode;\n\n className?: string;\n};\n\ntype RecordFieldsProps = {\n /** Resource handle returned by `Sdk.resource('<resourceId>')`. */\n resource: Pick<SdkResource, 'id' | 'fields' | 'can' | 'update'>;\n\n /** The loaded record whose values are displayed and edited. */\n record: Record<string, any> | null | undefined;\n\n /**\n * Render exactly these fields, in this order, named by key or by handle. System,\n * virtual, and relation fields are honoured here \u2014 naming a field is an explicit\n * request for it. Omit to render every field the page does not already place.\n */\n fields?: readonly FieldSelector[];\n\n /**\n * Catch-all mode: field keys the page places by hand. Everything else on the\n * resource renders here, so fields added later appear without page edits.\n * Ignored when `fields` is given.\n */\n exclude?: readonly FieldSelector[];\n\n /** Section heading. Defaults to \"Additional Fields\"; pass `null` for no heading. */\n title?: React.ReactNode;\n\n /**\n * Whether identities with the `manage_fields` permission see the \"Add field\"\n * action. Defaults to true in catch-all mode and false when `fields` is given \u2014\n * a field created there would not appear in an explicit list.\n */\n canAddFields?: boolean;\n\n /** Optional container className. */\n className?: string;\n\n /** Called after field values are saved on the record. */\n onChanged?: () => void;\n};\n\ntype FieldSelector = string | SdkResourceField;\n\ntype RecordScheduleDisplayProps = {\n /**\n * Resource the record belongs to.\n */\n resourceId: string;\n\n /**\n * Record whose schedule is shown.\n */\n recordId: string;\n\n /**\n * Limit the display to a single record-schedule workflow. Without it, every\n * published schedule the record participates in is shown.\n */\n recordScheduleWorkflowId?: string;\n};\n\ntype ResourceTableProps<\n TResource extends ResourceDataTableResource,\n TRelations extends readonly SdkResourceRelationPath<NoInfer<TResource>>[] = readonly []\n> = UseResourceDataTableOptions<TResource, TRelations> &\n ResourceTablePresentationProps<SdkResourceRecord<TResource, TRelations>>;\n\ntype ResourceTablePresentationProps<TRecord extends RowData> = {\n /** Show a search input in the default toolbar. */\n searchable?: boolean;\n\n /** Placeholder for the search input. */\n searchPlaceholder?: string;\n\n /** Show the column-visibility toggle in the default toolbar. */\n columnToggle?: boolean;\n\n /** Show the CSV-export dropdown when the resource permits exports. */\n isExportable?: boolean;\n\n /** Extra controls rendered in the default toolbar. */\n toolbarActions?: ReactNode;\n\n /**\n * Bulk action controls shown when rows are selected. Providing this prop enables row\n * selection unless `enableRowSelection` is explicitly `false`.\n */\n bulkActions?: DataTableBulkActionContent<TRecord>;\n\n /** Show pagination controls below the table. Defaults to `true`. */\n pagination?: boolean;\n\n /** Called with the resource record when a row is clicked. */\n onRowClick?: (row: TRecord) => void;\n\n /** Message shown when there are no rows. */\n emptyMessage?: ReactNode;\n\n /** Message shown when the records request fails. */\n errorMessage?: ReactNode;\n\n className?: string;\n\n /**\n * Advanced: provide a custom `DataTable` layout. The table and resource-filter\n * contexts remain available to the composable table components.\n */\n children?: ReactNode;\n};\n\ntype RestoreResourceRecordProps = {\n /**\n * Resource handle returned by `Sdk.resource('<resourceId>')`.\n */\n resource: RestoreResourceRecordResource & Pick<SdkResource, 'policy_scope'>;\n\n /**\n * Record ID to restore.\n */\n recordId: string | null | undefined;\n\n /**\n * Button label.\n */\n label?: string;\n\n /**\n * Heading shown in the confirmation dialog.\n */\n confirmTitle?: React.ReactNode;\n\n /**\n * Supporting text shown in the confirmation dialog.\n */\n confirmDescription?: React.ReactNode;\n\n /**\n * Called after a successful restoration, before navigation.\n */\n onSuccess?: () => void;\n\n /**\n * Optional container className.\n */\n className?: string;\n};\n\ntype RichTextProps = {\n /**\n * HTML string to render. Accepts untrusted HTML and sanitizes it before rendering.\n */\n html?: string | null;\n\n /**\n * Rich text nodes used when not rendering raw HTML.\n */\n children?: React.ReactNode;\n\n /**\n * Additional container classes.\n */\n className?: string;\n};\n\ntype ScheduledNotificationComposerProps = {\n /** The occurrence to open, such as an id from useUpcomingNotifications. */\n scheduledNotificationId: string;\n /** Edited raw content, ready to save as content_override. Null while nothing changed, and when the occurrence cannot be edited. */\n onChange: (content: SdkScheduledNotificationContent | null, expectedRevision?: number) => void;\n /** Preview for an unsaved recipient change instead of the saved recipient. */\n recipient?: SdkNotificationRecipient;\n /** Omit to allow every text layout field. An empty array locks every layout field. */\n editableFields?: string[];\n /** Allow subject edits. Defaults to true. */\n editableSubject?: boolean;\n /** Lock all editors while the parent saves. */\n disabled?: boolean;\n /** Called when the occurrence cannot be loaded. */\n onError?: (error: SdkApiError) => void;\n className?: string;\n};\n\ntype SearchBarProps = {\n /**\n * Called with the debounced search value.\n */\n onSearch: (value: string) => void;\n\n /**\n * Input placeholder text.\n */\n placeholder?: string;\n\n /**\n * Initial search value.\n */\n defaultValue?: string;\n\n /**\n * Show a loading affordance.\n */\n isLoading?: boolean;\n className?: string;\n};\n\ntype SeparatorProps = PrimitiveHtmlProps<HTMLDivElement> & {\n asChild?: boolean;\n orientation?: 'horizontal' | 'vertical';\n decorative?: boolean;\n};\n\ntype HTMLDivElement = any;\n\ntype SharedFiltersProps = {\n /**\n * Headless shared-filter state from `Sdk.hooks.useSharedFilters` (or the\n * `filters.sharedFilters` returned by `Sdk.hooks.useResourceDataTable`). Optional when\n * rendered inside a `<Sdk.Components.DataTable filters={...}>` \u2014 it is read from\n * context there.\n */\n sharedFilters?: SharedFiltersApi;\n className?: string;\n};\n\ntype ShowResourceRecordResource = SdkResourceQueryHandle<'show'> &\n Pick<SdkResource, 'fields'>;\n\ntype ShowResourceRecordProps<\n TResource extends ShowResourceRecordResource & Pick<SdkResource, 'policy_scope'>,\n TRelations extends readonly SdkResourceRelationPath<NoInfer<TResource>>[] = readonly []\n> = {\n /** Resource handle returned by `Sdk.resource('<resourceId>')`. */\n resource: TResource;\n\n /** Record ID to display, commonly from `Sdk.helpers.useInput('record_id')` or a selected\n * DataTable row. */\n recordId: string | null | undefined;\n\n /** Field handles that control which fields are rendered and their order.\n * Omit to render all non-system, non-virtual fields accessible at Show scope. */\n fields?: SdkResourceField[];\n\n /** Relation paths to preload and expose on the render prop's typed `record`. */\n relations?: TRelations;\n\n /** Soft-delete subset to read from. Defaults to active records only; pass\n * `RecordScope.ActiveOrArchived` to also display archived (soft-deleted) records. */\n scope?: RecordScope;\n\n /** Optional container className. */\n className?: string;\n\n /**\n * Custom layout render prop. When provided, the default field list is replaced by\n * the children.\n *\n * `FieldLabel` renders the field's label text. `FieldValue` renders the type-aware\n * formatted value (booleans as badges, money with currency, selects as option badges,\n * etc.). Both are context-aware \u2014 they read the value from the loaded record\n * automatically; no `value` prop needed. `FieldValue` also takes a `fallback` to\n * replace the em dash shown for an empty value.\n *\n * @example\n * <Sdk.Components.ShowResourceRecord resource={students} recordId={recordId}>\n * {({ FieldLabel, FieldValue }) => (\n * <dl className=\"grid grid-cols-2 gap-4\">\n * <div>\n * <FieldLabel field={students.fields.name} />\n * <FieldValue field={students.fields.name} />\n * </div>\n * <div>\n * <FieldLabel field={students.fields.status} />\n * <FieldValue field={students.fields.status} fallback=\"Not set\" />\n * </div>\n * </dl>\n * )}\n * </Sdk.Components.ShowResourceRecord>\n */\n children?: (\n state: ShowResourceRecordRenderState<SdkResourceRecord<TResource, TRelations>>\n ) => React.ReactNode;\n};\n\ntype ShowResourceRecordRenderState<\n TRecord extends Record<string, any> = Record<string, any>\n> = {\n FieldLabel: React.ComponentType<ShowResourceRecordFieldComponentProps>;\n FieldValue: React.ComponentType<ShowResourceRecordFieldValueProps>;\n record: TRecord | undefined;\n fields: SdkResourceField[];\n isLoading: boolean;\n};\n\ntype ShowResourceRecordFieldComponentProps = {\n field: SdkResourceField;\n className?: string;\n};\n\ntype ShowResourceRecordFieldValueProps = ShowResourceRecordFieldComponentProps & {\n /**\n * Rendered in place of the default em dash when the field's value is empty.\n * Pass `null` to render nothing at all. Omit to inherit an enclosing fallback\n * (such as `KeyValueList`'s) or keep the em dash. The fallback renders exactly\n * as given \u2014 the caller owns its styling.\n */\n fallback?: React.ReactNode;\n};\n\ntype SidebarProps = PrimitiveHtmlProps<HTMLDivElement> & {\n /** Which edge the sidebar docks to. */\n side?: 'left' | 'right';\n\n /** Visual style: a full-height sidebar, floating panel, or inset panel. */\n variant?: 'sidebar' | 'floating' | 'inset';\n\n /** Collapse behavior: slide offcanvas, shrink to icons, or stay fixed. */\n collapsible?: 'offcanvas' | 'icon' | 'none';\n\n /** Additional classes for the sidebar container. */\n className?: string;\n\n /** Sidebar sections, typically SidebarHeader, SidebarContent, and SidebarFooter. */\n children?: React.ReactNode;\n};\n\ntype SidebarTriggerProps = {\n /** Additional classes for the trigger button. */\n className?: string;\n};\n\ntype SidebarSectionProps = PrimitiveHtmlProps<HTMLElement> & {\n /** Additional classes for the section. */\n className?: string;\n\n /** Section contents. */\n children?: React.ReactNode;\n};\n\ntype SidebarMenuButtonProps = PrimitiveButtonNativeProps & {\n /** Render the child element as the button instead of a native button. */\n asChild?: boolean;\n\n /** Highlight the button as the active menu entry. */\n isActive?: boolean;\n\n /** Visual style preset. */\n variant?: 'default' | 'outline' | null;\n\n /** Button size preset. */\n size?: 'default' | 'sm' | 'lg' | null;\n\n /** Tooltip shown while the sidebar is collapsed to icons. */\n tooltip?: string | SidebarTooltipProps;\n\n /** Click handler for action buttons. */\n onClick?: React.MouseEventHandler<HTMLButtonElement>;\n\n /** Additional classes for the button. */\n className?: string;\n\n /** Button contents, typically an icon and a label. */\n children?: React.ReactNode;\n};\n\ntype SidebarTooltipProps = PrimitiveHtmlProps<HTMLDivElement> & {\n forceMount?: true;\n side?: 'top' | 'right' | 'bottom' | 'left';\n sideOffset?: number;\n align?: 'start' | 'center' | 'end';\n alignOffset?: number;\n arrowPadding?: number;\n avoidCollisions?: boolean;\n collisionBoundary?: Element | null | Array<Element | null>;\n collisionPadding?: number | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>>;\n sticky?: 'partial' | 'always';\n hideWhenDetached?: boolean;\n updatePositionStrategy?: 'optimized' | 'always';\n onEscapeKeyDown?: (event: KeyboardEvent) => void;\n onPointerDownOutside?: (\n event: CustomEvent<{\n originalEvent: PointerEvent;\n }>\n ) => void;\n showArrow?: boolean;\n};\n\ntype Element = any;\n\ntype KeyboardEvent = any;\n\ntype CustomEvent = any;\n\ntype PointerEvent = any;\n\ntype PrimitiveListItemNativeProps = PrimitiveHtmlProps<HTMLLIElement> &\n Partial<Pick<React.LiHTMLAttributes<HTMLLIElement>, PrimitiveListItemAttributeName>>;\n\ntype HTMLLIElement = any;\n\ntype PrimitiveListItemAttributeName = 'value';\n\ntype SidebarProviderProps = PrimitiveHtmlProps<HTMLDivElement> & {\n /** Whether the sidebar starts expanded. Defaults to the persisted cookie state. */\n defaultOpen?: boolean;\n\n /** Controlled expanded state. */\n open?: boolean;\n\n /** Called when the controlled expanded state changes. */\n onOpenChange?: (open: boolean) => void;\n\n /** Additional classes for the provider wrapper. */\n className?: string;\n\n /** The sidebar and the page content it frames. */\n children?: React.ReactNode;\n};\n\ntype PrimitiveSidebarTriggerProps = Omit<PrimitiveButtonProps, 'asChild' | 'children'> & {\n /** Additional classes for the trigger button. */\n className?: string;\n};\n\ntype SimpleTableProps<TRow extends object> = {\n /**\n * Column definitions.\n */\n columns: TableColumn<TRow>[];\n\n /**\n * Rows to display.\n */\n rows: TRow[];\n\n /**\n * Text shown when `rows` is empty.\n */\n emptyText?: string;\n\n /**\n * Optional table caption.\n */\n caption?: string;\n\n className?: string;\n};\n\ntype TableColumn<TRow> = {\n /**\n * Row field key. Used both as the React key and as the default value lookup.\n */\n key: Extract<keyof TRow, string>;\n\n /**\n * Column header content.\n */\n header: React.ReactNode;\n\n /**\n * Optional custom cell renderer.\n */\n render?: (row: TRow) => React.ReactNode;\n};\n\ntype SkeletonProps = PrimitiveHtmlProps<HTMLDivElement> & {\n /**\n * Classes controlling the skeleton size and shape.\n */\n className?: string;\n};\n\ntype SlideoverProps = {\n /**\n * Controlled open state.\n */\n open?: boolean;\n\n /**\n * Initial uncontrolled open state.\n */\n defaultOpen?: boolean;\n\n /**\n * Called when the panel opens or closes.\n */\n onOpenChange?: (open: boolean) => void;\n\n /**\n * Element that opens the panel.\n */\n trigger?: React.ReactNode;\n\n /**\n * Panel heading.\n */\n title?: React.ReactNode;\n\n /**\n * Supporting text below the title.\n */\n description?: React.ReactNode;\n\n /**\n * Panel body.\n */\n children: React.ReactNode;\n\n /**\n * Footer actions. The footer stacks them vertically in source order.\n */\n footer?: React.ReactNode;\n\n /**\n * Side the panel opens from. Defaults to `right`.\n */\n side?: SlideoverSide;\n\n /**\n * Panel width preset. Defaults to `md`.\n */\n size?: SlideoverSize;\n\n className?: string;\n};\n\ntype SlideoverSide = 'top' | 'right' | 'bottom' | 'left';\n\ntype SlideoverSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\ntype SlotPickerProps = {\n /** The result of `Sdk.hooks.useAvailableSlots`. */\n search: Pick<\n SdkAvailableSlotsHandle,\n 'slots' | 'timezone' | 'stoppedEarly' | 'isLoading' | 'error' | 'refetch'\n >;\n /** The ID of the selected slot, or null when no slot is selected. */\n value?: string | null;\n /** Called with the slot that the visitor picks. */\n onChange: (slot: SdkAvailableSlot) => void;\n /** Shown when the search finds no free time. Defaults to `'No times are free in this period.'`. */\n emptyMessage?: string;\n /** Accessible name of the slot group. Defaults to `'Available times'`. */\n label?: string;\n disabled?: boolean;\n className?: string;\n};\n\ntype SpinnerProps = Omit<PrimitiveHtmlProps<HTMLSpanElement>, 'children'> & {\n size?: 'sm' | 'md' | 'lg' | 'xl' | null;\n color?: 'default' | 'primary' | 'muted' | null;\n inline?: boolean | null;\n};\n\ntype StatCardProps = {\n /**\n * Metric label.\n */\n title: React.ReactNode;\n\n /**\n * Primary metric value.\n */\n value: React.ReactNode;\n\n /**\n * Optional supporting context.\n */\n description?: React.ReactNode;\n\n /**\n * Lucide icon name shown in the top-right of the card.\n */\n icon?: string;\n\n /**\n * Trend or comparison text.\n */\n trend?: React.ReactNode;\n\n className?: string;\n};\n\ntype StepProps = {\n /** Step label shown in the indicator and the \"Step X of N: <label>\" caption. */\n label: string;\n\n /** Fields and content shown when this step is the active one. */\n children?: React.ReactNode;\n};\n\ntype StepperProps = {\n /** `Sdk.Components.Step` children, one per step, in order. */\n children: React.ReactNode;\n\n /** Index to start on. Defaults to `0`. */\n initialStep?: number;\n\n /** Label for the back button. Defaults to \"Previous\". */\n backLabel?: string;\n\n /** Label for the advance button. Defaults to \"Next\". */\n nextLabel?: string;\n\n /** Label for the final-step action button. Defaults to \"Submit\". */\n submitLabel?: string;\n\n /**\n * Called when the final step's action button is clicked. When provided, that button is a\n * plain button. When omitted, the final button is `type=\"submit\"`, so an enclosing\n * `Sdk.Components.CreateResourceRecordForm` / `Form` handles submission.\n */\n onComplete?: () => void;\n\n /** Disables the final-step action button, e.g. while the enclosing form is submitting. */\n isSubmitDisabled?: boolean;\n\n className?: string;\n};\n\ntype SubscriptionBillingPreviewProps = SubscriptionBillingPreviewCommonProps &\n (\n | (SdkSubscriptionPreviewOptions & { previewResult?: never })\n | {\n /** Hook result to display instead of fetching from selection inputs. */\n previewResult: SdkSubscriptionPreviewHandle;\n }\n );\n\ntype SubscriptionBillingPreviewCommonProps = {\n /** Quote the visitor previously confirmed. A different quote requires confirmation again. */\n confirmedQuote?: string | null;\n /** Current reviewable quote, or null during loading, failure, or disabled requests. */\n onQuoteChange?: (quote: string | null) => void;\n className?: string;\n};\n\ntype SubscriptionChangePreviewProps = SubscriptionChangePreviewCommonProps &\n (\n | (SdkSubscriptionChangePreviewOptions & { previewResult?: never })\n | {\n /** Hook result to display instead of fetching from selection inputs. */\n previewResult: SdkSubscriptionChangePreviewHandle;\n }\n );\n\ntype SubscriptionChangePreviewCommonProps = {\n /** Quote the visitor previously confirmed. A different quote requires confirmation again. */\n confirmedQuote?: string | null;\n /**\n * Current reviewable quote, or null during loading, failure, disabled requests, and\n * for a selection that matches the current subscription.\n */\n onQuoteChange?: (quote: string | null) => void;\n /**\n * Builds the URL of an invoice page from an invoice record ID, such as with\n * `Sdk.utils.page.url`. No invoice link shows without it, or when it returns null.\n */\n invoiceHref?: (invoiceRecordId: string) => string | null;\n className?: string;\n};\n\ntype SupportChatButtonProps = {\n /** Additional classes for the trigger button. */\n className?: string;\n};\n\ntype TabsProps = {\n /**\n * Tab definitions.\n */\n tabs: Tab[];\n\n /**\n * Initially selected tab for uncontrolled usage.\n */\n defaultValue?: string;\n\n /**\n * Controlled active tab value.\n */\n value?: string;\n\n /**\n * Called when the active tab changes.\n */\n onValueChange?: (value: string) => void;\n\n /**\n * Visual style. Defaults to `pill`.\n */\n variant?: TabsVariant;\n\n className?: string;\n listClassName?: string;\n contentClassName?: string;\n};\n\ntype Tab = {\n /**\n * Tab identifier used by `value`/`defaultValue`.\n */\n value: string;\n\n /**\n * Tab trigger label.\n */\n label: React.ReactNode;\n\n /**\n * Tab panel content.\n */\n content: React.ReactNode;\n};\n\ntype TabsVariant = 'pill' | 'underline' | 'rail';\n\ntype ThemeToggleProps = {\n /** Additional classes for the button. */\n className?: string;\n\n /** Button visual style. */\n variant?: ButtonProps['variant'];\n\n /** Button size preset. */\n size?: ButtonProps['size'];\n};\n\ntype TimerProps = {\n /**\n * Date-like target value. Renders a placeholder for null/invalid values.\n */\n target?: string | number | Date | null;\n\n /**\n * Optional label shown above the timestamp.\n */\n label?: React.ReactNode;\n\n className?: string;\n};\n\ntype TooltipProps = {\n /**\n * Element that receives the tooltip.\n */\n trigger: React.ReactNode;\n\n /**\n * Tooltip text or content.\n */\n content: React.ReactNode;\n\n /**\n * Preferred side for the tooltip.\n */\n side?: 'top' | 'right' | 'bottom' | 'left';\n};\n\ntype TwoFactorBackupCodeListProps = {\n /** The plain backup codes from `codes` of a two-factor hook. */\n codes: string[];\n\n /** Shown under the codes, such as the button that continues. */\n children?: React.ReactNode;\n\n /** Additional classes for the container. */\n className?: string;\n};\n\ntype TwoFactorBackupCodesProps = {\n /** The authenticatable resource. Defaults to the signed-in member's resource. */\n resourceId?: string;\n\n /** Additional classes for the container. */\n className?: string;\n\n /** Additional classes for the main buttons. */\n buttonClassName?: string;\n};\n\ntype TwoFactorChallengeProps = {\n /**\n * The authenticatable resource. Defaults to the resource of the pending sign-in, then\n * the workspace's sole authenticatable resource.\n */\n resourceId?: string;\n\n /** Label of the code input. */\n codeLabel?: React.ReactNode;\n\n /** Contents of the verify buttons. */\n verifyLabel?: React.ReactNode;\n\n /** Called after the visitor chose to start over and the pending sign-in ended. */\n onCancel?: () => void;\n\n /** Additional classes for the container. */\n className?: string;\n\n /** Additional classes for the main buttons. */\n buttonClassName?: string;\n};\n\ntype TwoFactorSendCodeButtonProps = TwoFactorSmsConsentProps & {\n /** Contents of the button. */\n label?: React.ReactNode;\n\n /** The button variant. */\n variant?: 'default' | 'outline' | 'link';\n};\n\ntype TwoFactorSmsConsentProps = {\n /** The flow whose code this consent sends: a sign-in challenge, an MFA setup, or the proof for new backup codes. */\n flow: SdkTwoFactorFlow;\n\n /** The authenticatable resource. Defaults as in `Sdk.hooks.useTwoFactorChallenge`. */\n resourceId?: string;\n\n /** Additional classes for the container. */\n className?: string;\n\n /** Additional classes for the submit button. */\n buttonClassName?: string;\n};\n\ntype TwoFactorSetupProps = {\n /**\n * The authenticatable resource. Defaults to the resource of the pending sign-in, then of\n * the signed-in member.\n */\n resourceId?: string;\n\n /** Called after a signed-in member's new MFA number is active. */\n onComplete?: () => void;\n\n /** Called after the visitor stopped the setup. */\n onCancel?: () => void;\n\n /** Additional classes for the container. */\n className?: string;\n\n /** Additional classes for the main buttons. */\n buttonClassName?: string;\n};\n\ntype UpdateFormResource = SdkResourceType &\n UpdateResourceRecordFormResource &\n Pick<SdkResource, 'policy_scope'>;\n\ntype UpdateResourceRecordFormProps<TResource extends UpdateFormResource = UpdateFormResource> = {\n /** Resource handle returned by `Sdk.resource('<resourceId>')`. */\n resource: TResource;\n\n /** Record ID to edit, commonly from `Sdk.helpers.useInput('record_id')` or a selected DataTable row. */\n recordId: string | null | undefined;\n\n /** Field handles that control which fields the form renders and their order.\n * Omit to render the resource's configured update fields. */\n fields?: SdkResourceField[];\n\n /** Editable seed values applied before the record loads (or when no `recordId`\n * is set). The user can still change them. */\n initialValues?: Record<string, any>;\n\n /** Locked writable Resource field values \u2014 rendered as disabled inputs and included\n * even when omitted from `fields`. Unknown, system, virtual, or inaccessible keys are\n * ignored. Reactive: if the prop changes after mount the form updates. */\n fixedValues?: Record<string, any>;\n\n /** Include every updatable field, ignoring per-field `show_on_updating` form\n * visibility. For elevated surfaces (e.g. the admin edit page). Ignored when\n * explicit `fields` are provided. */\n includeAllFields?: boolean;\n\n /** How relation fields resolve inline attach/create. Defaults to `'settings'`;\n * pass `'always'` to force them on. */\n relationVisibility?: RelationVisibility;\n\n /** Render the resource's published `updated`/`saved` RecordEvent workflow inputs in the\n * managed layout, stacked beneath the resource fields (on the final step for `steps`).\n * Defaults to `false`. Ignored when `children` is provided \u2014 render `WorkflowInputs`\n * yourself there. */\n includeWorkflowInputs?: boolean;\n\n /**\n * Multi-step (wizard) layout. Each step groups fields under a label; the form renders the\n * numbered step indicator, \"Step X of N\" caption, Previous/Next navigation, and the submit\n * button on the last step \u2014 no render prop or submit wiring needed. All steps share one\n * form: values persist across steps and the final submit saves the whole record.\n * Use `review: true` for a read-only summary step, or `content` to customize one step's\n * layout. Navigation is free \u2014 moving between steps never validates or blocks.\n * Ignored when `children` is provided.\n */\n steps?: ResourceRecordFormStep<UpdateResourceRecordFormRenderState<TResource>>[];\n\n /** Submit button label. Only used when `children` is not provided. */\n submitLabel?: string;\n\n /** When provided, the default layout's secondary button becomes a Cancel action\n * wired to this callback (instead of Reset). */\n onCancel?: () => void;\n\n /** Cancel button label. Defaults to \"Cancel\". Only used with `onCancel`. */\n cancelLabel?: string;\n\n /** Toast message shown after a successful submission. Pass `null` to suppress. */\n successMessage?: string | null;\n\n /** Called after a successful submission with the updated record. */\n onSuccess?: (record: SdkResourceRecord<TResource>) => void;\n\n /** Optional container className. */\n className?: string;\n\n /**\n * Custom layout render prop. When provided, the managed layout is replaced while\n * retaining the form tag and submit handler. It receives reusable `Fields`,\n * `Actions`, and `WorkflowInputs` components, `FieldInput` / `WorkflowInput` for\n * individual controls, and `workflowInputs` \u2014 the resource's published\n * `updated`/`saved` RecordEvent workflow inputs keyed by input key.\n *\n * `FieldInput` renders the correctly-wired input for a resource field handle and\n * returns `null` for handles that are not part of the active form. `WorkflowInputs`\n * renders every applicable workflow input; `<WorkflowInput input={workflowInputs.<key>} />`\n * renders one. When a render function is provided, `steps` is ignored.\n *\n * @example\n * <Sdk.Components.UpdateResourceRecordForm resource={students} recordId={recordId}>\n * {({ FieldInput, form }) => (\n * <div className=\"grid grid-cols-2 gap-4\">\n * <FieldInput field={students.fields.first_name} />\n * <FieldInput field={students.fields.last_name} />\n * <FieldInput field={students.fields.email} className=\"col-span-2\" />\n * <Sdk.Components.Button type=\"submit\" disabled={form.isSubmitting}>\n * Save student\n * </Sdk.Components.Button>\n * </div>\n * )}\n * </Sdk.Components.UpdateResourceRecordForm>\n */\n children?: (state: UpdateResourceRecordFormRenderState<TResource>) => React.ReactNode;\n};\n\ntype UpdateResourceRecordFormRenderState<\n TResource extends Pick<SdkResource, 'workflow_inputs'> = Pick<SdkResource, 'workflow_inputs'>\n> = {\n FieldInput: (props: FormFieldProps) => React.ReactNode;\n WorkflowInput: (props: WorkflowInputProps) => React.ReactNode;\n /** Renders every applicable RecordEvent workflow input, stacked in order. */\n WorkflowInputs: (props: WorkflowInputsProps) => React.ReactNode;\n Fields: (props: ResourceRecordFormFieldsProps) => React.ReactNode;\n Actions: (props: ResourceRecordFormActionsProps) => React.ReactNode;\n form: UpdateResourceRecordFormHandle;\n fields: SdkResourceField[];\n /** The resource's published `updated`/`saved` RecordEvent workflow inputs, keyed\n * by input key \u2014 pass one to `WorkflowInput` or render them all via `WorkflowInputs`. */\n workflowInputs: SdkResourceFormWorkflowInputs<TResource, 'saved' | 'updated'>;\n};\n\ntype UpdateResourceRecordFormHandle = SdkResourceForm & {\n /** Internal \u2014 consumed by the turnkey component. */\n isLoading: boolean;\n /** Plural relation fields the form leaves to the caller in `'table'` mode. */\n relationTableFields: SdkResourceField[];\n};\n\ntype VideoProps = React.VideoHTMLAttributes<HTMLVideoElement> & {\n /**\n * Video URL.\n */\n src: string;\n\n /**\n * Accessible title.\n */\n title?: string;\n\n /**\n * Show browser playback controls.\n */\n controls?: boolean;\n\n /**\n * URL of a WebVTT captions file. Provide this for deaf and hard-of-hearing\n * viewers; when set the player exposes a captions track.\n */\n captionsSrc?: string;\n\n /**\n * BCP-47 language of the captions track (defaults to \"en\").\n */\n captionsSrcLang?: string;\n\n /**\n * Human-readable label for the captions track.\n */\n captionsLabel?: string;\n};\n\ntype HTMLVideoElement = any;\n\ntype WorkflowFormProps = {\n /** The workflow's slug or ID. Both values are stable across title renames. */\n workflowId: WorkflowReference;\n\n /** Route parameter values for parameterized published routes\n * (e.g. `/invites/{id}` needs `params={{ id: recordId }}`). */\n params?: Record<string, any>;\n\n /** Input-field keys that control which fields the form renders and their order.\n * Omit to render every field from the workflow's input schema; unknown keys are\n * ignored. */\n fields?: string[];\n\n /** Editable seed values. Captured once on mount \u2014 when seeding from fetched data,\n * gate rendering until it has loaded. The user can still change them; they form\n * the `reset()` baseline. */\n initialValues?: Record<string, any>;\n\n /** Locked field values \u2014 rendered as disabled inputs and always included in the\n * submission payload. Reactive: if the prop changes after mount the form updates. */\n fixedValues?: Record<string, any>;\n\n /** Submit button label. Defaults to \"Submit\". Only used when `children` is not\n * provided. */\n submitLabel?: string;\n\n /** When provided, the default layout's secondary button becomes a Cancel action\n * wired to this callback (instead of Reset). Only used when `children` is not\n * provided. */\n onCancel?: () => void;\n\n /** Cancel button label. Defaults to \"Cancel\". Only used with `onCancel`. */\n cancelLabel?: string;\n\n /** Toast message shown after a successful submission. Defaults to \"Submitted.\"\n * for mutation workflows and to no toast for GET workflows. Pass `null` to\n * suppress. */\n successMessage?: string | null;\n\n /** Called after a successful submission with the workflow's response payload. */\n onSuccess?: (response: SdkWorkflowResponse) => void;\n\n /** Called when the submission resolves with a non-2xx response, with\n * `{ status, data, error }`. Validation errors (422) also populate the form's\n * per-field errors automatically. */\n onError?: (context: WorkflowFormErrorContext) => void;\n\n /** Clear the form back to its initial values after a successful submission.\n * Defaults to `true` for mutation workflows and `false` for GET workflows, so\n * a search form keeps its filters. */\n resetOnSuccess?: boolean;\n\n /** Optional container className. */\n className?: string;\n\n /**\n * Custom layout render prop. When provided, the default field list and submit\n * button are replaced by the children. The form tag and onSubmit handler are\n * still managed by the component \u2014 use a `type=\"submit\"` button or call\n * `form.submit()` to trigger submission.\n *\n * `FieldInput` renders the correctly-wired input for a field handle from\n * `fields` and returns `null` for handles that are not part of the active form.\n * `submitLabel` is ignored when `children` is provided.\n *\n * @example\n * <Sdk.Components.WorkflowForm workflowId=\"send-invite\">\n * {({ FieldInput, form, fields }) => (\n * <div className=\"grid grid-cols-2 gap-4\">\n * {fields.map((field) => (\n * <FieldInput key={field.key} field={field} />\n * ))}\n * <Sdk.Components.Button type=\"submit\" disabled={form.isSubmitting}>\n * Send\n * </Sdk.Components.Button>\n * </div>\n * )}\n * </Sdk.Components.WorkflowForm>\n */\n children?: (state: WorkflowFormRenderState) => React.ReactNode;\n};\n\ntype WorkflowFormErrorContext = {\n status: number;\n data: any;\n error: any;\n};\n\ntype WorkflowFormRenderState = {\n FieldInput: React.ComponentType<FormFieldProps>;\n form: WorkflowFormHandle;\n fields: SdkResourceField[];\n};\n\ntype WorkflowFormHandle = SdkWorkflowForm;\n\ntype WorkspaceFieldsFormProps = {\n /** Workspace field definitions (`Sdk.workspace.fields.<key>`) to render, in order.\n * Omit to render every field that is not hidden and not read-only. Read-only and\n * hidden entries are dropped either way, because the endpoint refuses them. */\n fields?: Array<SdkWorkspaceField | undefined>;\n\n /** Editable seed values that override the workspace's current values. */\n initialValues?: Record<string, any>;\n\n /** Locked values submitted even when omitted from `fields`; rendered disabled. */\n fixedValues?: Record<string, any>;\n\n /** Submit button label. Defaults to \"Save\". Only used when `children` is not provided. */\n submitLabel?: string;\n\n /** When provided, the default layout's secondary button becomes a Cancel action\n * wired to this callback (instead of Reset). */\n onCancel?: () => void;\n\n /** Cancel button label. Defaults to \"Cancel\". Only used with `onCancel`. */\n cancelLabel?: string;\n\n /** Toast message shown after a successful save. Pass `null` to suppress. */\n successMessage?: string | null;\n\n /** Called after a successful save with the saved values keyed by field key. */\n onSuccess?: (values: SdkWorkspaceValues) => void;\n\n /** Optional container className. */\n className?: string;\n\n /**\n * Custom layout render prop. Replaces the managed layout while retaining the form\n * tag and submit handler. `FieldInput` renders the wired input for a workspace\n * field definition and returns `null` for definitions not in the active set.\n *\n * @example\n * <Sdk.Components.WorkspaceFieldsForm fields={[fields.support_email]}>\n * {({ FieldInput, form }) => (\n * <>\n * <FieldInput field={fields.support_email} />\n * <Sdk.Components.Button type=\"submit\" disabled={form.isSubmitting}>Save</Sdk.Components.Button>\n * </>\n * )}\n * </Sdk.Components.WorkspaceFieldsForm>\n */\n children?: (state: WorkspaceFieldsFormRenderState) => React.ReactNode;\n};\n\ntype WorkspaceFieldsFormRenderState = {\n FieldInput: (props: FormFieldProps) => React.ReactNode;\n Fields: (props: ResourceRecordFormFieldsProps) => React.ReactNode;\n Actions: (props: ResourceRecordFormActionsProps) => React.ReactNode;\n form: SdkWorkspaceFieldsForm;\n fields: SdkWorkspaceField[];\n};\n\nconst sdkResourceRelationCatalog: unique symbol;\n\nconst sdkResourceRecordCatalog: unique symbol;\n\ntype DynamicResourceId<TId extends string> =\n TId & (string extends TId ? TId : never);\n\ninterface WorkspaceFieldCatalog { [key: string]: any }\n\ninterface WorkspaceFieldDefinitionCatalog { [key: string]: SdkWorkspaceField }\n}\n\ndeclare namespace Sdk {\n export type ResourceFieldSettingsUi = import(\"diagonal:sdk-internal\").SdkResourceFieldSettingsUi;\n export type ResourceFieldSettings = import(\"diagonal:sdk-internal\").SdkResourceFieldSettings;\n export type ListItemField = import(\"diagonal:sdk-internal\").SdkListItemField;\n export type ResourceField = import(\"diagonal:sdk-internal\").SdkResourceField;\n export type ResourceCapabilityFieldMappings = import(\"diagonal:sdk-internal\").SdkResourceCapabilityFieldMappings;\n export type ResourceSortDescriptor = import(\"diagonal:sdk-internal\").ResourceSortDescriptor;\n export type ResourceSortFieldDescriptor = import(\"diagonal:sdk-internal\").ResourceSortFieldDescriptor;\n export type ResourceSortDirection = import(\"diagonal:sdk-internal\").ResourceSortDirection;\n export type ResourceSortInput = import(\"diagonal:sdk-internal\").ResourceSortInput;\n export type ResourceListParams<TRelations extends readonly string[] = readonly string[]> = import(\"diagonal:sdk-internal\").ResourceListParams<TRelations>;\n export type ResourceSearchBody<TRelations extends readonly string[] = readonly string[]> = import(\"diagonal:sdk-internal\").ResourceSearchBody<TRelations>;\n export type ResourceCountOptions = import(\"diagonal:sdk-internal\").ResourceCountOptions;\n export type ResourceSearchAllOptions<TRelations extends readonly string[] = readonly string[]> = import(\"diagonal:sdk-internal\").ResourceSearchAllOptions<TRelations>;\n export type ResourcePagination = import(\"diagonal:sdk-internal\").ResourcePagination;\n export type ResourceCollectionResult<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").ResourceCollectionResult<TRecord>;\n export type ResourceRecordsResult<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").ResourceRecordsResult<TRecord>;\n export type ResourceRecordResult<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").ResourceRecordResult<TRecord>;\n export type ResourceRecordPayload<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").ResourceRecordPayload<TRecord>;\n export type ResourceBulkUpdateRecord<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").ResourceBulkUpdateRecord<TRecord>;\n export type ResourceDeletePayload = import(\"diagonal:sdk-internal\").ResourceDeletePayload;\n export type ResourceOperationRecord<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").ResourceOperationRecord<TRecord>;\n export type ResourceUrls = import(\"diagonal:sdk-internal\").ResourceUrls;\n export type ResourceFieldUpdatePayload = import(\"diagonal:sdk-internal\").ResourceFieldUpdatePayload;\n export type DndIdentifier = import(\"diagonal:sdk-internal\").SdkDndIdentifier;\n export type DndSortableItem = import(\"diagonal:sdk-internal\").SdkDndSortableItem;\n export type DndItemIdentifier<TItem> = import(\"diagonal:sdk-internal\").SdkDndItemIdentifier<TItem>;\n export type DndSortableOptions<TItem> = import(\"diagonal:sdk-internal\").SdkDndSortableOptions<TItem>;\n export type DndReorderChange<TItem> = import(\"diagonal:sdk-internal\").SdkDndReorderChange<TItem>;\n export type DndReorderResult<TItem> = import(\"diagonal:sdk-internal\").SdkDndReorderResult<TItem>;\n export type DndSortableRenderState = import(\"diagonal:sdk-internal\").SdkDndSortableRenderState;\n export type DndLocation = import(\"diagonal:sdk-internal\").SdkDndLocation;\n export type DndGroupedLocation = import(\"diagonal:sdk-internal\").SdkDndGroupedLocation;\n export type DndMoveContext<TItem, TLocation extends import(\"diagonal:sdk-internal\").SdkDndLocation = import(\"diagonal:sdk-internal\").SdkDndLocation> = import(\"diagonal:sdk-internal\").SdkDndMoveContext<TItem, TLocation>;\n export type DndCanDrag<TItem, TLocation extends import(\"diagonal:sdk-internal\").SdkDndLocation = import(\"diagonal:sdk-internal\").SdkDndLocation> = import(\"diagonal:sdk-internal\").SdkDndCanDrag<TItem, TLocation>;\n export type DndCanDrop<TItem, TLocation extends import(\"diagonal:sdk-internal\").SdkDndLocation = import(\"diagonal:sdk-internal\").SdkDndLocation> = import(\"diagonal:sdk-internal\").SdkDndCanDrop<TItem, TLocation>;\n export type DndSortableListCommonProps<TItem> = import(\"diagonal:sdk-internal\").SdkDndSortableListCommonProps<TItem>;\n export type DndCustomPersistence<TItem> = import(\"diagonal:sdk-internal\").SdkDndCustomPersistence<TItem>;\n export type DndResourcePersistence<TItem> = import(\"diagonal:sdk-internal\").SdkDndResourcePersistence<TItem>;\n export type DndSortableListProps<TItem> = import(\"diagonal:sdk-internal\").SdkDndSortableListProps<TItem>;\n export type DndSortableListResourceProps<TResource extends import(\"diagonal:sdk-internal\").SdkResourceType & Pick<import(\"diagonal:sdk-internal\").SdkResource, 'fields' | 'updateMany'>, TItem extends import(\"diagonal:sdk-internal\").SdkDndResourceItem<NoInfer<TResource>> = import(\"diagonal:sdk-internal\").SdkDndResourceItem<TResource>> = import(\"diagonal:sdk-internal\").SdkDndSortableListResourceProps<TResource, TItem>;\n export type DndSortableListComponent = import(\"diagonal:sdk-internal\").SdkDndSortableListComponent;\n export type DndSortableGroupRenderState<TItem> = import(\"diagonal:sdk-internal\").SdkDndSortableGroupRenderState<TItem>;\n export type DndGroupMoveChange<TItem> = import(\"diagonal:sdk-internal\").SdkDndGroupMoveChange<TItem>;\n export type DndGroupMoveResult<TItem> = import(\"diagonal:sdk-internal\").SdkDndGroupMoveResult<TItem>;\n export type DndSortableGroupsCommonProps<TItem, TGroup> = import(\"diagonal:sdk-internal\").SdkDndSortableGroupsCommonProps<TItem, TGroup>;\n export type DndSortableGroupsCustomPersistence<TItem, TGroup> = import(\"diagonal:sdk-internal\").SdkDndSortableGroupsCustomPersistence<TItem, TGroup>;\n export type DndSortableGroupsResourcePersistence<TItem, TGroup> = import(\"diagonal:sdk-internal\").SdkDndSortableGroupsResourcePersistence<TItem, TGroup>;\n export type DndSortableGroupsProps<TItem, TGroup> = import(\"diagonal:sdk-internal\").SdkDndSortableGroupsProps<TItem, TGroup>;\n export type DndSortableGroupsResourceProps<TResource extends import(\"diagonal:sdk-internal\").SdkResourceType & Pick<import(\"diagonal:sdk-internal\").SdkResource, 'fields' | 'updateMany'>, TItem extends import(\"diagonal:sdk-internal\").SdkDndResourceItem<NoInfer<TResource>>, TGroup> = import(\"diagonal:sdk-internal\").SdkDndSortableGroupsResourceProps<TResource, TItem, TGroup>;\n export type DndSortableGroupsComponent = import(\"diagonal:sdk-internal\").SdkDndSortableGroupsComponent;\n export type DndReorder = import(\"diagonal:sdk-internal\").SdkDndReorder;\n export type Dnd = import(\"diagonal:sdk-internal\").SdkDnd;\n export type CalendarLinks = import(\"diagonal:sdk-internal\").CalendarLinks;\n export type AnalyticsMetric = import(\"diagonal:sdk-internal\").AnalyticsMetric;\n export type AnalyticsTimeSeriesMetric = import(\"diagonal:sdk-internal\").AnalyticsTimeSeriesMetric;\n export type AnalyticsGranularity = import(\"diagonal:sdk-internal\").AnalyticsGranularity;\n export type AnalyticsValue = import(\"diagonal:sdk-internal\").AnalyticsValue;\n export type AnalyticsKpiResult<TMetrics extends readonly import(\"diagonal:sdk-internal\").AnalyticsMetric[] = import(\"diagonal:sdk-internal\").AnalyticsMetric[]> = import(\"diagonal:sdk-internal\").AnalyticsKpiResult<TMetrics>;\n export type AnalyticsRow = import(\"diagonal:sdk-internal\").AnalyticsRow;\n export type AnalyticsKpiParams<TMetrics extends readonly import(\"diagonal:sdk-internal\").AnalyticsMetric[] = import(\"diagonal:sdk-internal\").AnalyticsMetric[]> = import(\"diagonal:sdk-internal\").AnalyticsKpiParams<TMetrics>;\n export type AnalyticsTimeSeriesParams = import(\"diagonal:sdk-internal\").AnalyticsTimeSeriesParams;\n export type AnalyticsLeaderboardParams = import(\"diagonal:sdk-internal\").AnalyticsLeaderboardParams;\n export type AnalyticsDistributionParams = import(\"diagonal:sdk-internal\").AnalyticsDistributionParams;\n export type UseAnalyticsOptions = import(\"diagonal:sdk-internal\").SdkUseAnalyticsOptions;\n export type AnalyticsHandle<TResult> = import(\"diagonal:sdk-internal\").SdkAnalyticsHandle<TResult>;\n export type ResourceAnalytics = import(\"diagonal:sdk-internal\").SdkResourceAnalytics;\n export type ResourceOperation = import(\"diagonal:sdk-internal\").SdkResourceOperation;\n export type ResourceOperations<TResource extends import(\"diagonal:sdk-internal\").SdkResourceType = import(\"diagonal:sdk-internal\").SdkResourceType> = import(\"diagonal:sdk-internal\").SdkResourceOperations<TResource>;\n export type Resource<TFields extends Record<string, import(\"diagonal:sdk-internal\").SdkResourceField> = Record<string, import(\"diagonal:sdk-internal\").SdkResourceField>, TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord, TRelations extends import(\"diagonal:sdk-internal\").ResourceRelationCatalog = import(\"diagonal:sdk-internal\").ResourceRelationCatalog, TWorkflowInputs extends import(\"diagonal:sdk-internal\").SdkResourceWorkflowInputs = import(\"diagonal:sdk-internal\").SdkResourceWorkflowInputs> = import(\"diagonal:sdk-internal\").SdkResource<TFields, TRecord, TRelations, TWorkflowInputs>;\n export type ResourceRelationCatalog = import(\"diagonal:sdk-internal\").ResourceRelationCatalog;\n export type ResourceRecordCatalog = import(\"diagonal:sdk-internal\").ResourceRecordCatalog;\n export type RecordOf<TKey extends keyof import(\"diagonal:sdk-internal\").ResourceRecordCatalog> = import(\"diagonal:sdk-internal\").SdkRecordOf<TKey>;\n export type WorkflowReference = import(\"diagonal:sdk-internal\").WorkflowReference;\n export type PageAliasCatalog = import(\"diagonal:sdk-internal\").PageAliasCatalog;\n export type PageReference = import(\"diagonal:sdk-internal\").PageReference;\n export type PageHandle = import(\"diagonal:sdk-internal\").SdkPageHandle;\n export type ResourceType<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord, TRelations extends import(\"diagonal:sdk-internal\").ResourceRelationCatalog = import(\"diagonal:sdk-internal\").ResourceRelationCatalog> = import(\"diagonal:sdk-internal\").SdkResourceType<TRecord, TRelations>;\n export type ResourceRelationsFrom<TResource extends import(\"diagonal:sdk-internal\").SdkResourceType> = import(\"diagonal:sdk-internal\").ResourceRelationsFrom<TResource>;\n export type ResourceRelationPath<TResource extends import(\"diagonal:sdk-internal\").SdkResourceType> = import(\"diagonal:sdk-internal\").SdkResourceRelationPath<TResource>;\n export type SelectedResourceRecord<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord, TRelationCatalog extends import(\"diagonal:sdk-internal\").ResourceRelationCatalog, TRelations extends readonly Extract<keyof TRelationCatalog, string>[] = readonly []> = import(\"diagonal:sdk-internal\").SdkSelectedResourceRecord<TRecord, TRelationCatalog, TRelations>;\n export type ResourceRecordFrom<TResource extends import(\"diagonal:sdk-internal\").SdkResourceType | null | undefined, TRelations extends readonly import(\"diagonal:sdk-internal\").SdkResourceRelationPath<NonNullable<TResource>>[] = readonly []> = import(\"diagonal:sdk-internal\").SdkResourceRecord<TResource, TRelations>;\n export type ResourceReference = import(\"diagonal:sdk-internal\").SdkResourceReference;\n export type ResourceHandle = import(\"diagonal:sdk-internal\").SdkResourceHandle;\n export type ResourceQueryReference<TUrl extends keyof import(\"diagonal:sdk-internal\").ResourceUrls = 'show' | 'search'> = import(\"diagonal:sdk-internal\").SdkResourceQueryReference<TUrl>;\n export type ResourceQueryHandle<TUrl extends keyof import(\"diagonal:sdk-internal\").ResourceUrls = 'show' | 'search'> = import(\"diagonal:sdk-internal\").SdkResourceQueryHandle<TUrl>;\n export type AnalyticsResource<TKind extends keyof import(\"diagonal:sdk-internal\").SdkResourceAnalytics> = import(\"diagonal:sdk-internal\").SdkAnalyticsResource<TKind>;\n export type CatalogRelationPath<TResourceId extends string> = import(\"diagonal:sdk-internal\").SdkCatalogRelationPath<TResourceId>;\n export type CatalogRecord<TResourceId extends string, TRelations extends readonly import(\"diagonal:sdk-internal\").SdkCatalogRelationPath<TResourceId>[] = readonly []> = import(\"diagonal:sdk-internal\").SdkCatalogRecord<TResourceId, TRelations>;\n export type ReferenceRelationPath<TReference extends import(\"diagonal:sdk-internal\").ResourceTypeReference> = import(\"diagonal:sdk-internal\").SdkReferenceRelationPath<TReference>;\n export type ReferenceRecord<TReference extends import(\"diagonal:sdk-internal\").ResourceTypeReference, TRelations extends readonly import(\"diagonal:sdk-internal\").SdkReferenceRelationPath<TReference>[] = readonly []> = import(\"diagonal:sdk-internal\").SdkReferenceRecord<TReference, TRelations>;\n export type PollInterval<TData> = import(\"diagonal:sdk-internal\").SdkPollInterval<TData>;\n export type UsePollOptions = import(\"diagonal:sdk-internal\").SdkUsePollOptions;\n export type PollControls = import(\"diagonal:sdk-internal\").SdkPollControls;\n export type UseRecordOptions<TRelations extends readonly string[] = readonly string[], TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkUseRecordOptions<TRelations, TRecord>;\n export type RecordHandle<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkRecordHandle<TRecord>;\n export type UseRecordsOptions<TRelations extends readonly string[] = readonly string[], TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkUseRecordsOptions<TRelations, TRecord>;\n export type RecordsHandle<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkRecordsHandle<TRecord>;\n export type UseInfiniteRecordsOptions<TRelations extends readonly string[] = readonly string[]> = import(\"diagonal:sdk-internal\").SdkUseInfiniteRecordsOptions<TRelations>;\n export type InfiniteRecordsHandle<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkInfiniteRecordsHandle<TRecord>;\n export type RecordMutationState<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkRecordMutationState<TRecord>;\n export type UseCreateRecordOptions<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkUseCreateRecordOptions<TRecord>;\n export type CreateRecordHandle<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkCreateRecordHandle<TRecord>;\n export type UseUpdateRecordOptions<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkUseUpdateRecordOptions<TRecord>;\n export type UpdateRecordHandle<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkUpdateRecordHandle<TRecord>;\n export type EmailRecipient = import(\"diagonal:sdk-internal\").SdkEmailRecipient;\n export type SendEmailInput = import(\"diagonal:sdk-internal\").SdkSendEmailInput;\n export type SendEmailResponse = import(\"diagonal:sdk-internal\").SdkSendEmailResponse;\n export type UseSendEmailOptions = import(\"diagonal:sdk-internal\").SdkUseSendEmailOptions;\n export type SendEmailHandle = import(\"diagonal:sdk-internal\").SdkSendEmailHandle;\n export type BulkNotificationRun = import(\"diagonal:sdk-internal\").SdkBulkNotificationRun;\n export type BulkNotificationVariable = import(\"diagonal:sdk-internal\").SdkBulkNotificationVariable;\n export type SendBulkNotificationsInput = import(\"diagonal:sdk-internal\").SdkSendBulkNotificationsInput;\n export type SendBulkNotificationsHandle = import(\"diagonal:sdk-internal\").SdkSendBulkNotificationsHandle;\n export type NotificationRecipient = import(\"diagonal:sdk-internal\").SdkNotificationRecipient;\n export type NotificationDeliveryChannel = import(\"diagonal:sdk-internal\").SdkNotificationDeliveryChannel;\n export type SendNotificationInput = import(\"diagonal:sdk-internal\").SdkSendNotificationInput;\n export type SendNotificationResponse = import(\"diagonal:sdk-internal\").SdkSendNotificationResponse;\n export type NotificationConsentChannels = import(\"diagonal:sdk-internal\").SdkNotificationConsentChannels;\n export type NotificationConsent = import(\"diagonal:sdk-internal\").SdkNotificationConsent;\n export type UseNotificationConsentOptions = import(\"diagonal:sdk-internal\").SdkUseNotificationConsentOptions;\n export type NotificationConsentHandle = import(\"diagonal:sdk-internal\").SdkNotificationConsentHandle;\n export type CalendarUnavailableReason = import(\"diagonal:sdk-internal\").SdkCalendarUnavailableReason;\n export type CalendarLinkState = import(\"diagonal:sdk-internal\").SdkCalendarLinkState;\n export type CalendarAccessRole = import(\"diagonal:sdk-internal\").SdkCalendarAccessRole;\n export type GoogleCalendarLoginStatus = import(\"diagonal:sdk-internal\").SdkGoogleCalendarLoginStatus;\n export type CalendarEventsChoice = import(\"diagonal:sdk-internal\").SdkCalendarEventsChoice;\n export type CalendarStaff = import(\"diagonal:sdk-internal\").SdkCalendarStaff;\n export type CalendarLink = import(\"diagonal:sdk-internal\").SdkCalendarLink;\n export type CalendarLogin = import(\"diagonal:sdk-internal\").SdkCalendarLogin;\n export type CalendarChoice = import(\"diagonal:sdk-internal\").SdkCalendarChoice;\n export type CalendarChoices = import(\"diagonal:sdk-internal\").SdkCalendarChoices;\n export type CalendarResult = import(\"diagonal:sdk-internal\").SdkCalendarResult;\n export type CalendarSignInResult = import(\"diagonal:sdk-internal\").SdkCalendarSignInResult;\n export type CalendarLinkFlags = import(\"diagonal:sdk-internal\").SdkCalendarLinkFlags;\n export type AddCalendarLinkInput = import(\"diagonal:sdk-internal\").SdkAddCalendarLinkInput;\n export type UseCalendarConnectionsOptions = import(\"diagonal:sdk-internal\").SdkUseCalendarConnectionsOptions;\n export type CalendarConnectionsHandle = import(\"diagonal:sdk-internal\").SdkCalendarConnectionsHandle;\n export type UseSendNotificationOptions = import(\"diagonal:sdk-internal\").SdkUseSendNotificationOptions;\n export type SendNotificationHandle = import(\"diagonal:sdk-internal\").SdkSendNotificationHandle;\n export type PreviewNotificationInput = import(\"diagonal:sdk-internal\").SdkPreviewNotificationInput;\n export type NotificationChannelUnavailableReason = import(\"diagonal:sdk-internal\").SdkNotificationChannelUnavailableReason;\n export type NotificationChannelAvailability = import(\"diagonal:sdk-internal\").SdkNotificationChannelAvailability;\n export type NotificationChannel = import(\"diagonal:sdk-internal\").SdkNotificationChannel;\n export type NotificationChannels = import(\"diagonal:sdk-internal\").SdkNotificationChannels;\n export type PreviewNotificationResponse = import(\"diagonal:sdk-internal\").SdkPreviewNotificationResponse;\n export type ResolvedNotificationEmail = import(\"diagonal:sdk-internal\").SdkResolvedNotificationEmail;\n export type ResolvedNotification = import(\"diagonal:sdk-internal\").SdkResolvedNotification;\n export type SendResolvedNotificationInput = import(\"diagonal:sdk-internal\").SdkSendResolvedNotificationInput;\n export type SendResolvedNotificationHandle = import(\"diagonal:sdk-internal\").SdkSendResolvedNotificationHandle;\n export type UpcomingNotification = import(\"diagonal:sdk-internal\").SdkUpcomingNotification;\n export type ScheduledNotificationContent = import(\"diagonal:sdk-internal\").SdkScheduledNotificationContent;\n export type ScheduledNotificationDetail = import(\"diagonal:sdk-internal\").SdkScheduledNotificationDetail;\n export type ScheduledNotificationEdits = import(\"diagonal:sdk-internal\").SdkScheduledNotificationEdits;\n export type UpdateScheduledNotificationInput = import(\"diagonal:sdk-internal\").SdkUpdateScheduledNotificationInput;\n export type UpdateScheduledNotificationHandle = import(\"diagonal:sdk-internal\").SdkUpdateScheduledNotificationHandle;\n export type SendScheduledNotificationNowHandle = import(\"diagonal:sdk-internal\").SdkSendScheduledNotificationNowHandle;\n export type NotificationHistoryStatus = import(\"diagonal:sdk-internal\").SdkNotificationHistoryStatus;\n export type NotificationHistoryKind = import(\"diagonal:sdk-internal\").SdkNotificationHistoryKind;\n export type NotificationSourceKind = import(\"diagonal:sdk-internal\").SdkNotificationSourceKind;\n export type EmailAttemptStatus = import(\"diagonal:sdk-internal\").SdkEmailAttemptStatus;\n export type EmailRecipientRole = import(\"diagonal:sdk-internal\").SdkEmailRecipientRole;\n export type EmailMessagesRecord = import(\"diagonal:sdk-internal\").SdkEmailMessagesRecord;\n export type EmailMessagesFilters = import(\"diagonal:sdk-internal\").SdkEmailMessagesFilters;\n export type EmailMessagesOptions = import(\"diagonal:sdk-internal\").SdkEmailMessagesOptions;\n export type EmailMessage = import(\"diagonal:sdk-internal\").SdkEmailMessage;\n export type EmailMessages = import(\"diagonal:sdk-internal\").SdkEmailMessages;\n export type NotificationHistoryItem = import(\"diagonal:sdk-internal\").SdkNotificationHistoryItem;\n export type NotificationHistory = import(\"diagonal:sdk-internal\").SdkNotificationHistory;\n export type NotificationHistoryOptions = import(\"diagonal:sdk-internal\").SdkNotificationHistoryOptions;\n export type EmailAttemptEvent = import(\"diagonal:sdk-internal\").SdkEmailAttemptEvent;\n export type EmailAttempt = import(\"diagonal:sdk-internal\").SdkEmailAttempt;\n export type EmailAddress = import(\"diagonal:sdk-internal\").SdkEmailAddress;\n export type EmailAttemptRecipients = import(\"diagonal:sdk-internal\").SdkEmailAttemptRecipients;\n export type EmailAttemptAttachment = import(\"diagonal:sdk-internal\").SdkEmailAttemptAttachment;\n export type NotificationHistoryRecord = import(\"diagonal:sdk-internal\").SdkNotificationHistoryRecord;\n export type NotificationHistoryDetail = import(\"diagonal:sdk-internal\").SdkNotificationHistoryDetail;\n export type SmsMessageStatus = import(\"diagonal:sdk-internal\").SdkSmsMessageStatus;\n export type SmsMessage = import(\"diagonal:sdk-internal\").SdkSmsMessage;\n export type UpcomingNotifications = import(\"diagonal:sdk-internal\").SdkUpcomingNotifications;\n export type UpcomingNotificationsOptions = import(\"diagonal:sdk-internal\").SdkUpcomingNotificationsOptions;\n export type ScheduleNotificationInput = import(\"diagonal:sdk-internal\").SdkScheduleNotificationInput;\n export type ScheduledNotificationResult = import(\"diagonal:sdk-internal\").SdkScheduledNotificationResult;\n export type CancelledNotificationResult = import(\"diagonal:sdk-internal\").SdkCancelledNotificationResult;\n export type ScheduleNotificationHandle = import(\"diagonal:sdk-internal\").SdkScheduleNotificationHandle;\n export type CancelScheduledNotificationHandle = import(\"diagonal:sdk-internal\").SdkCancelScheduledNotificationHandle;\n export type HeldNotificationResult = import(\"diagonal:sdk-internal\").SdkHeldNotificationResult;\n export type HoldScheduledNotificationHandle = import(\"diagonal:sdk-internal\").SdkHoldScheduledNotificationHandle;\n export type ResumeScheduledNotificationHandle = import(\"diagonal:sdk-internal\").SdkResumeScheduledNotificationHandle;\n export type NotificationListOptions = import(\"diagonal:sdk-internal\").SdkNotificationListOptions;\n export type NotificationTypeListOptions = import(\"diagonal:sdk-internal\").SdkNotificationTypeListOptions;\n export type NotificationVariableSummary = import(\"diagonal:sdk-internal\").SdkNotificationVariableSummary;\n export type NotificationTypeSummary = import(\"diagonal:sdk-internal\").SdkNotificationTypeSummary;\n export type NotificationTemplateSummary = import(\"diagonal:sdk-internal\").SdkNotificationTemplateSummary;\n export type NotificationTypeList = import(\"diagonal:sdk-internal\").SdkNotificationTypeList;\n export type NotificationTemplateList = import(\"diagonal:sdk-internal\").SdkNotificationTemplateList;\n export type NavigationMenu = import(\"diagonal:sdk-internal\").SdkNavigationMenu;\n export type NavigationMenuCatalog = import(\"diagonal:sdk-internal\").SdkNavigationMenuCatalog;\n export type WorkflowEvent = import(\"diagonal:sdk-internal\").SdkWorkflowEvent;\n export type ResourceWorkflowInputs = import(\"diagonal:sdk-internal\").SdkResourceWorkflowInputs;\n export type ResourceFormWorkflowInputs<TResource extends Pick<import(\"diagonal:sdk-internal\").SdkResource, 'workflow_inputs'>, TEvents extends import(\"diagonal:sdk-internal\").SdkWorkflowEvent> = import(\"diagonal:sdk-internal\").SdkResourceFormWorkflowInputs<TResource, TEvents>;\n export type WorkflowInputSettings = import(\"diagonal:sdk-internal\").SdkWorkflowInputSettings;\n export type WorkflowInput = import(\"diagonal:sdk-internal\").SdkWorkflowInput;\n export type Identity = import(\"diagonal:sdk-internal\").SdkIdentity;\n export type Permissions = import(\"diagonal:sdk-internal\").SdkPermissions;\n export type WorkspaceField = import(\"diagonal:sdk-internal\").SdkWorkspaceField;\n export type FormField = import(\"diagonal:sdk-internal\").SdkFormField;\n export type WorkspaceValues = import(\"diagonal:sdk-internal\").SdkWorkspaceValues;\n export type Workspace = import(\"diagonal:sdk-internal\").SdkWorkspace;\n export type WorkspaceRecord = import(\"diagonal:sdk-internal\").SdkWorkspaceRecord;\n export type Api = import(\"diagonal:sdk-internal\").SdkApi;\n export type ApiError = import(\"diagonal:sdk-internal\").SdkApiError;\n export type ApiQueryKey = import(\"diagonal:sdk-internal\").SdkApiQueryKey;\n export type ApiFetcher<TData> = import(\"diagonal:sdk-internal\").SdkApiFetcher<TData>;\n export type SubscriptionPreview = import(\"diagonal:sdk-internal\").SdkSubscriptionPreview;\n export type SubscriptionPreviewOptions = import(\"diagonal:sdk-internal\").SdkSubscriptionPreviewOptions;\n export type SubscriptionPreviewHandle = import(\"diagonal:sdk-internal\").SdkSubscriptionPreviewHandle;\n export type SubscriptionChangePreview = import(\"diagonal:sdk-internal\").SdkSubscriptionChangePreview;\n export type SubscriptionChangePreviewOptions = import(\"diagonal:sdk-internal\").SdkSubscriptionChangePreviewOptions;\n export type SubscriptionChangePreviewHandle = import(\"diagonal:sdk-internal\").SdkSubscriptionChangePreviewHandle;\n export type SchedulableRecordRef = import(\"diagonal:sdk-internal\").SdkSchedulableRecordRef;\n export type SlotSearchGroup = import(\"diagonal:sdk-internal\").SdkSlotSearchGroup;\n export type AvailableSlotsOptions = import(\"diagonal:sdk-internal\").SdkAvailableSlotsOptions;\n export type AvailableSlot = import(\"diagonal:sdk-internal\").SdkAvailableSlot;\n export type AvailableSlotsHandle = import(\"diagonal:sdk-internal\").SdkAvailableSlotsHandle;\n export type AvailabilityOptions = import(\"diagonal:sdk-internal\").SdkAvailabilityOptions;\n export type AvailabilityPiece = import(\"diagonal:sdk-internal\").SdkAvailabilityPiece;\n export type RecordAvailability = import(\"diagonal:sdk-internal\").SdkRecordAvailability;\n export type AvailabilityHandle = import(\"diagonal:sdk-internal\").SdkAvailabilityHandle;\n export type ScheduleConflictsOptions = import(\"diagonal:sdk-internal\").SdkScheduleConflictsOptions;\n export type ScheduleConflict = import(\"diagonal:sdk-internal\").SdkScheduleConflict;\n export type ScheduleConflictsHandle = import(\"diagonal:sdk-internal\").SdkScheduleConflictsHandle;\n export type PaymentRefundPreview = import(\"diagonal:sdk-internal\").SdkPaymentRefundPreview;\n export type PaymentRefundPreviewOptions = import(\"diagonal:sdk-internal\").SdkPaymentRefundPreviewOptions;\n export type PaymentRefundPreviewHandle = import(\"diagonal:sdk-internal\").SdkPaymentRefundPreviewHandle;\n export type AccountingEntity = import(\"diagonal:sdk-internal\").SdkAccountingEntity;\n export type AccountingEntities = import(\"diagonal:sdk-internal\").SdkAccountingEntities;\n export type SeriesOccurrenceMeta = import(\"diagonal:sdk-internal\").SdkSeriesOccurrenceMeta;\n export type SeriesChangeResult = import(\"diagonal:sdk-internal\").SdkSeriesChangeResult;\n export type BookingCreateResult = import(\"diagonal:sdk-internal\").SdkBookingCreateResult;\n export type PayerEntityBalance = import(\"diagonal:sdk-internal\").SdkPayerEntityBalance;\n export type PayerBalancesOptions = import(\"diagonal:sdk-internal\").SdkPayerBalancesOptions;\n export type PayerBalances = import(\"diagonal:sdk-internal\").SdkPayerBalances;\n export type PackageReleaseHistory = import(\"diagonal:sdk-internal\").SdkPackageReleaseHistory;\n export type PackageReleaseNotesOptions = import(\"diagonal:sdk-internal\").SdkPackageReleaseNotesOptions;\n export type PackageReleaseNotes = import(\"diagonal:sdk-internal\").SdkPackageReleaseNotes;\n export type PackageReleaseNotesProps = import(\"diagonal:sdk-internal\").SdkPackageReleaseNotesProps;\n export type UseApiOptions<TData = any> = import(\"diagonal:sdk-internal\").SdkUseApiOptions<TData>;\n export type ApiHandle<TData> = import(\"diagonal:sdk-internal\").SdkApiHandle<TData>;\n export type RouterVisitOptions = import(\"diagonal:sdk-internal\").SdkRouterVisitOptions;\n export type Router = import(\"diagonal:sdk-internal\").SdkRouter;\n export type AuthResource = import(\"diagonal:sdk-internal\").SdkAuthResource;\n export type TwoFactorMode = import(\"diagonal:sdk-internal\").SdkTwoFactorMode;\n export type TwoFactorPending = import(\"diagonal:sdk-internal\").SdkTwoFactorPending;\n export type AuthLoginInput = import(\"diagonal:sdk-internal\").SdkAuthLoginInput;\n export type AuthMagicLinkInput = import(\"diagonal:sdk-internal\").SdkAuthMagicLinkInput;\n export type AuthPasswordResetInput = import(\"diagonal:sdk-internal\").SdkAuthPasswordResetInput;\n export type Auth = import(\"diagonal:sdk-internal\").SdkAuth;\n export type AuthConfig = import(\"diagonal:sdk-internal\").SdkAuthConfig;\n export type TwoFactorFlow = import(\"diagonal:sdk-internal\").SdkTwoFactorFlow;\n export type UseTwoFactorOptions = import(\"diagonal:sdk-internal\").SdkUseTwoFactorOptions;\n export type TwoFactorChallengeState = import(\"diagonal:sdk-internal\").SdkTwoFactorChallengeState;\n export type TwoFactorRequestState = import(\"diagonal:sdk-internal\").SdkTwoFactorRequestState;\n export type TwoFactorChallengeHandle = import(\"diagonal:sdk-internal\").SdkTwoFactorChallengeHandle;\n export type TwoFactorSetupStep = import(\"diagonal:sdk-internal\").SdkTwoFactorSetupStep;\n export type TwoFactorSetupState = import(\"diagonal:sdk-internal\").SdkTwoFactorSetupState;\n export type TwoFactorSetupHandle = import(\"diagonal:sdk-internal\").SdkTwoFactorSetupHandle;\n export type TwoFactorBackupCodesState = import(\"diagonal:sdk-internal\").SdkTwoFactorBackupCodesState;\n export type TwoFactorBackupCodesHandle = import(\"diagonal:sdk-internal\").SdkTwoFactorBackupCodesHandle;\n export type UseForm = import(\"diagonal:sdk-internal\").SdkUseForm;\n export type WorkflowRouteSettings = import(\"diagonal:sdk-internal\").WorkflowRouteSettings;\n export type PageRouteSettings = import(\"diagonal:sdk-internal\").PageRouteSettings;\n export type WorkspaceFields = import(\"diagonal:sdk-internal\").SdkWorkspaceFields;\n type SdkContentValue = import(\"diagonal:sdk-internal\").SdkContentValue;\n export interface ContentValue extends SdkContentValue {}\n type SdkMoney = import(\"diagonal:sdk-internal\").SdkMoney;\n export interface Money extends SdkMoney {}\n export type MoneyInput = import(\"diagonal:sdk-internal\").SdkMoneyInput;\n type SdkPointValue = import(\"diagonal:sdk-internal\").SdkPointValue;\n export interface PointValue extends SdkPointValue {}\n type SdkRecurrenceDefinition = import(\"diagonal:sdk-internal\").SdkRecurrenceDefinition;\n export interface RecurrenceDefinition extends SdkRecurrenceDefinition {}\n export type RecurrenceValue = import(\"diagonal:sdk-internal\").SdkRecurrenceValue;\n type SdkEventScheduleValue = import(\"diagonal:sdk-internal\").SdkEventScheduleValue;\n export interface EventScheduleValue extends SdkEventScheduleValue {}\n export type RegularHoursDay = import(\"diagonal:sdk-internal\").SdkRegularHoursDay;\n type SdkRegularHoursValue = import(\"diagonal:sdk-internal\").SdkRegularHoursValue;\n export interface RegularHoursValue extends SdkRegularHoursValue {}\n export type LocalStoragePrimitive = import(\"diagonal:sdk-internal\").SdkLocalStoragePrimitive;\n export type LocalStorageObject = import(\"diagonal:sdk-internal\").SdkLocalStorageObject;\n export type LocalStorageArray = import(\"diagonal:sdk-internal\").SdkLocalStorageArray;\n export type LocalStorageValue = import(\"diagonal:sdk-internal\").SdkLocalStorageValue;\n export type LocalStorageSetter<TValue> = import(\"diagonal:sdk-internal\").SdkLocalStorageSetter<TValue>;\n export type LocalStorageResult<TValue> = import(\"diagonal:sdk-internal\").SdkLocalStorageResult<TValue>;\n export type MoneyFormatOptions = import(\"diagonal:sdk-internal\").SdkMoneyFormatOptions;\n export type DateAddUnit = import(\"diagonal:sdk-internal\").SdkDateAddUnit;\n export type DateBoundaryUnit = import(\"diagonal:sdk-internal\").SdkDateBoundaryUnit;\n export type TimeAddUnit = import(\"diagonal:sdk-internal\").SdkTimeAddUnit;\n export type TimeBoundaryUnit = import(\"diagonal:sdk-internal\").SdkTimeBoundaryUnit;\n export type WorkflowTarget = import(\"diagonal:sdk-internal\").SdkWorkflowTarget;\n export type BrandLogoOptions = import(\"diagonal:sdk-internal\").SdkBrandLogoOptions;\n export type ClassValue = import(\"diagonal:sdk-internal\").SdkClassValue;\n export type NavigateOptions = import(\"diagonal:sdk-internal\").SdkNavigateOptions;\n export type TiptapParseInput = import(\"diagonal:sdk-internal\").SdkTiptapParseInput;\n export type TextGenerateOptions = import(\"diagonal:sdk-internal\").SdkTextGenerateOptions;\n type SdkLocationValue = import(\"diagonal:sdk-internal\").SdkLocationValue;\n export interface LocationValue extends SdkLocationValue {}\n export type LocationFormatOptions = import(\"diagonal:sdk-internal\").SdkLocationFormatOptions;\n export type PhoneFormatOptions = import(\"diagonal:sdk-internal\").SdkPhoneFormatOptions;\n export type NumberFormatOptions = import(\"diagonal:sdk-internal\").SdkNumberFormatOptions;\n export type ConfettiOptions = import(\"diagonal:sdk-internal\").SdkConfettiOptions;\n export type ClipboardCopyOptions = import(\"diagonal:sdk-internal\").SdkClipboardCopyOptions;\n export type NotifyToastOptions = import(\"diagonal:sdk-internal\").SdkNotifyToastOptions;\n export type NotifyVariant = import(\"diagonal:sdk-internal\").SdkNotifyVariant;\n export type BadgeVariant = import(\"diagonal:sdk-internal\").SdkBadgeVariant;\n export type DisplayFormatOptions = import(\"diagonal:sdk-internal\").SdkDisplayFormatOptions;\n export type FieldSelector<TKey extends string = string> = import(\"diagonal:sdk-internal\").SdkFieldSelector<TKey>;\n export type RecordInput<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkRecordInput<TRecord>;\n export type FieldValue<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord, TSelector extends import(\"diagonal:sdk-internal\").SdkFieldSelector> = import(\"diagonal:sdk-internal\").SdkFieldValue<TRecord, TSelector>;\n export type DisplayUtils = import(\"diagonal:sdk-internal\").SdkDisplayUtils;\n export type ApiErrorBody = import(\"diagonal:sdk-internal\").SdkApiErrorBody;\n export type Utils = import(\"diagonal:sdk-internal\").SdkUtils;\n export type Helpers = import(\"diagonal:sdk-internal\").SdkHelpers;\n export type Contract = import(\"diagonal:sdk-internal\").SdkContract;\n export type ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord;\n export type ResourceForm = import(\"diagonal:sdk-internal\").SdkResourceForm;\n export type ResourceRecordFormStep<RenderState> = import(\"diagonal:sdk-internal\").ResourceRecordFormStep<RenderState>;\n export type UseWorkspaceFieldsFormOptions = import(\"diagonal:sdk-internal\").SdkUseWorkspaceFieldsFormOptions;\n export type WorkspaceFieldsForm = import(\"diagonal:sdk-internal\").SdkWorkspaceFieldsForm;\n export type UseWorkspaceFieldsFormResult = import(\"diagonal:sdk-internal\").UseWorkspaceFieldsFormResult;\n export type FormFieldProps = import(\"diagonal:sdk-internal\").FormFieldProps;\n export type WorkflowInputProps = import(\"diagonal:sdk-internal\").WorkflowInputProps;\n export type WorkflowInputsProps = import(\"diagonal:sdk-internal\").WorkflowInputsProps;\n export type ResourceRecordFormFieldsProps = import(\"diagonal:sdk-internal\").ResourceRecordFormFieldsProps;\n export type ResourceRecordFormActionsProps = import(\"diagonal:sdk-internal\").ResourceRecordFormActionsProps;\n export type FormWrapperProps = import(\"diagonal:sdk-internal\").FormWrapperProps;\n export type UseCreateResourceRecordFormResult = import(\"diagonal:sdk-internal\").UseCreateResourceRecordFormResult;\n export type UseUpdateResourceRecordFormResult = import(\"diagonal:sdk-internal\").UseUpdateResourceRecordFormResult;\n export type WorkflowForm = import(\"diagonal:sdk-internal\").SdkWorkflowForm;\n export type WorkflowPendingResponse = import(\"diagonal:sdk-internal\").SdkWorkflowPendingResponse;\n export type WorkflowOutput = import(\"diagonal:sdk-internal\").SdkWorkflowOutput;\n export type WorkflowResponse = import(\"diagonal:sdk-internal\").SdkWorkflowResponse;\n export type UseWorkflowActionResult = import(\"diagonal:sdk-internal\").UseWorkflowActionResult;\n export type UseWorkflowFormResult = import(\"diagonal:sdk-internal\").UseWorkflowFormResult;\n export type RecordScheduleTimelineOffset = import(\"diagonal:sdk-internal\").SdkRecordScheduleTimelineOffset;\n export type RecordScheduleTimeline = import(\"diagonal:sdk-internal\").SdkRecordScheduleTimeline;\n export type UseRecordScheduleTimelinesOptions = import(\"diagonal:sdk-internal\").SdkUseRecordScheduleTimelinesOptions;\n export type RecordScheduleTimelinesHandle = import(\"diagonal:sdk-internal\").SdkRecordScheduleTimelinesHandle;\n export type Hooks = import(\"diagonal:sdk-internal\").SdkHooks;\n}\n\ndeclare const Sdk: import(\"diagonal:sdk-internal\").SdkContract;\n";
292231
+ var sdkLib = "// AUTO-GENERATED by scripts/generate-page-sdk-types.mjs - do not edit by hand.\n// Ambient type surface for the page `Sdk` global, loaded into the Monaco\n// editor via addExtraLib for hover + IntelliSense.\n\n// Stubs for global namespaces (React/JSX) referenced by component prop types but\n// not resolvable on disk. Kept loose so the file stays self-contained.\ndeclare namespace React {\n type AnchorHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type AriaAttributes<A = any, B = any, C = any, D = any> = any;\n type AriaRole<A = any, B = any, C = any, D = any> = any;\n type ButtonHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type DOMAttributes<A = any, B = any, C = any, D = any> = any;\n type ElementType<A = any, B = any, C = any, D = any> = any;\n type HTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type IframeHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type ImgHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type InputHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type LiHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type MouseEventHandler<A = any, B = any, C = any, D = any> = any;\n type TextareaHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n type VideoHTMLAttributes<A = any, B = any, C = any, D = any> = any;\n}\n\ndeclare module \"diagonal:sdk-internal\" {\n// A page component: anything callable with its props object. Component prop\n// types reference this instead of `@types/react` so the file stays self-contained.\ntype SdkComponent<P = any> = (props: P) => any;\ntype SdkCustomComponent<T> = T extends (props: infer P, ...args: any[]) => any\n ? unknown extends P\n ? SdkComponent\n : T\n : SdkComponent;\n\ntype SdkResourceFieldSettingsUi = {\n field_type?: DynamicFieldType;\n display_type?: DynamicDisplayType;\n /** Display-specific format. Number fields accept `grouped` or `plain`; year-like\n * field keys default to `plain`. */\n display_format?: string;\n options?: any[];\n placeholder?: string;\n language?: string;\n show_on_creating?: boolean;\n show_on_updating?: boolean;\n show_formatting_toolbar?: boolean;\n toolbar_placement?: 'floating' | 'fixed';\n allow_inline_create_on_creating?: boolean;\n allow_inline_attach_on_creating?: boolean;\n allow_inline_create_on_updating?: boolean;\n allow_inline_attach_on_updating?: boolean;\n /**\n * Runtime-only: resolved inline-create/attach booleans injected client-side by\n * `resolveRecordFormField` in the record-form hooks. Not part of the backend\n * `PageFieldSettingsUiData` wire shape \u2014 populated after the catalog is built and\n * read by `FieldInput` as `resolvedValues` for relation fields.\n */\n injections?: Record<string, any>;\n};\n\ntype DynamicFieldType = 'boolean' | 'boolean_filter' | 'checkbox_group' | 'code' | 'color' | 'combobox' | 'component' | 'content' | 'content_type_settings' | 'css_classes' | 'data_grid_column_definitions' | 'date' | 'datetime' | 'dictionary' | 'dynamic' | 'email_layout_fields_input' | 'email_layout_select' | 'event_handler' | 'event_schedule' | 'file' | 'filter' | 'font' | 'guard_rules' | 'icon' | 'inline_select' | 'interval' | 'json' | 'list' | 'location' | 'markdown' | 'money' | 'navigation_menu' | 'none' | 'number' | 'operation_options' | 'page' | 'password' | 'phone' | 'plain_text' | 'point' | 'rating' | 'record' | 'recurrence' | 'regular_hours' | 'relation' | 'resource' | 'resource_field' | 'resource_field_key' | 'resource_field_value' | 'resource_field_value_entries' | 'resource_field_value_mapper' | 'resource_relation' | 'rich_text' | 'role' | 'schema' | 'schema_key' | 'select' | 'select_options' | 'success_redirect' | 'task' | 'text' | 'textarea' | 'time' | 'timezone' | 'unknown' | 'url' | 'wallet_accounting_account' | 'wallet_accounting_entity' | 'workflow' | 'workflow_fields_input' | 'workflow_select' | 'workflow_variable';\n\ntype DynamicDisplayType = 'boolean' | 'color' | 'content' | 'date' | 'datetime' | 'dictionary' | 'email' | 'event_schedule' | 'file' | 'list' | 'location' | 'markdown' | 'money' | 'number' | 'password' | 'phone' | 'point' | 'recurrence' | 'regular_hours' | 'relation' | 'resource' | 'select' | 'text' | 'time' | 'timezone' | 'wallet_accounting_account' | 'wallet_accounting_entity';\n\ntype SdkResourceFieldSettings = {\n currency?: string;\n integer?: boolean;\n minimum?: number | null;\n maximum?: number | null;\n multi_line?: boolean;\n placeholder?: string;\n yes_label?: string;\n no_label?: string;\n accept?: string[];\n visibility?: string;\n options?: Record<string, any>[];\n sort_order?: number | null;\n /** Item definition for list fields. */\n schema?: SdkListItemField | SdkListItemField[];\n /** Marks a Markdown field accepts. Absent allows every mark. */\n allowed_marks?: string[];\n /** Blocks a Markdown field accepts. Absent allows every block. */\n allowed_blocks?: string[];\n /** Relation pointer \u2014 the SDK dereferences `resource_id` against the flat catalog. */\n resource_id?: string;\n type?: string;\n foreign_key?: string;\n max_connections?: number | null;\n /** Field id of the child's back-reference that a has_many or has_one stores under. */\n inverse_of?: string;\n ui?: SdkResourceFieldSettingsUi;\n};\n\ntype SdkListItemField = {\n key: string;\n type: string;\n label?: string | null;\n description?: string | null;\n guard?: any[] | null;\n settings?: SdkResourceFieldSettings;\n};\n\ntype SdkResourceField = {\n id: string | null;\n key: string;\n label: string | null;\n type: string;\n resource_id: string;\n policy_scope: number | null;\n is_system_field: boolean;\n is_system_managed: boolean;\n /** Whether the field is the resource's configured roles or permissions relation. */\n is_authorization_relation: boolean;\n is_virtual_field: boolean;\n is_exportable: boolean;\n is_required: boolean;\n guard: Record<string, any> | null;\n /** The field's configured description \u2014 render it as help text under inputs. */\n description: string | null;\n settings: SdkResourceFieldSettings;\n /** Dotted accessor path; equals `key` at the top level, `parent.key` once nested. */\n path: string;\n /** A Boolean field's configured default; form checkboxes start from it. */\n default_value?: boolean | null;\n /** Relation-only decoration, filled lazily by the SDK builder from the flat catalog. */\n display_field_key?: string | null;\n column_label?: string | null;\n fields?: Record<string, SdkResourceField> | null;\n};\n\ntype SdkResourceCapabilityFieldMappings = Record<string, SdkResourceField>;\n\ntype ResourceSortDescriptor = { key: string; direction: 'asc' | 'desc' };\n\ntype ResourceSortFieldDescriptor = {\n field: string;\n direction: 'asc' | 'desc';\n};\n\ntype ResourceSortDirection = 1 | -1 | 'asc' | 'desc';\n\ntype ResourceSortInput =\n | string\n | string[]\n | ResourceSortDescriptor\n | ResourceSortFieldDescriptor\n | (ResourceSortDescriptor | ResourceSortFieldDescriptor)[]\n | Record<string, ResourceSortDirection>;\n\ntype ResourceListParams<TRelations extends readonly string[] = readonly string[]> = {\n page?: number;\n perPage?: number;\n per_page?: number;\n relations?: TRelations;\n /** Soft-delete subset to read from. Defaults to active records only. */\n $scope?: RecordScope;\n [key: string]: any;\n};\n\ntype RecordScope = 'active' | 'activeOrArchived' | 'archived';\n\ntype ResourceSearchBody<TRelations extends readonly string[] = readonly string[]> = {\n /** Exact field/value matches sent as `$match`. */\n $match?: Record<string, any>;\n /** Full-text search term sent as `$search`. */\n $search?: string;\n /** Structured `$filter` tree (renamed, not translated \u2014 flat maps are rejected). */\n filter?: Record<string, any>;\n /** Pre-built structured filter tree, sent as-is. */\n $filter?: Record<string, any>;\n /** Convenience sort input, normalized to `$sort` \u2014 see {@link ResourceSortInput}. */\n sort?: ResourceSortInput;\n /** Explicit `$sort` direction map, sent as-is. */\n $sort?: Record<string, string | number>;\n /** Soft-delete subset to read from; renamed to `$scope`. */\n scope?: RecordScope;\n /** Explicit backend record scope, sent as-is. */\n $scope?: RecordScope;\n /** One record for each repeating event; renamed to `$collapse`. See {@link RecordCollapse}. */\n collapse?: RecordCollapse;\n /** Explicit backend collapse, sent as-is. */\n $collapse?: RecordCollapse;\n /** Page number to return. Defaults to 1. */\n page?: number;\n /** Records per page (maximum 500, defaults to 15); renamed to `per_page`. */\n perPage?: number;\n /** Records per page (maximum 500, defaults to 15). */\n per_page?: number;\n /** Relation paths to load alongside each record. */\n relations?: TRelations;\n [key: string]: any;\n};\n\ntype RecordCollapse = 'series';\n\ntype ResourceCountOptions = {\n /** Structured filter tree, JSON-encoded so boolean and null values reach the server unchanged. */\n filter?: Record<string, any>;\n /** Pre-built structured filter tree, sent as-is. */\n $filter?: Record<string, any>;\n /** Soft-delete subset to count. Defaults to active records only. */\n scope?: RecordScope;\n /** Explicit backend scope alias, sent as-is. */\n $scope?: RecordScope;\n};\n\ntype ResourceSearchAllOptions<TRelations extends readonly string[] = readonly string[]> = {\n $match?: Record<string, any>;\n $search?: string;\n filter?: Record<string, any>;\n $filter?: Record<string, any>;\n sort?: ResourceSortInput;\n $sort?: Record<string, string | number>;\n relations?: TRelations;\n scope?: RecordScope;\n $scope?: RecordScope;\n collapse?: RecordCollapse;\n $collapse?: RecordCollapse;\n perPage?: number;\n per_page?: number;\n /** Maximum records to return (1\u20135,000). A warning is logged when matches exceed it. */\n maxRecords: number;\n};\n\ntype ResourcePagination = {\n current_page: number;\n last_page: number;\n per_page: number;\n total: number;\n from: number;\n to: number;\n first_page_url: string;\n last_page_url: string;\n next_page_url: string | null;\n prev_page_url: string | null;\n};\n\ntype ResourceCollectionResult<TRecord extends ResourceRecord = ResourceRecord> = {\n data: TRecord[];\n pagination: ResourcePagination;\n files?: FileMetadataMap;\n};\n\ntype ResourceRecord = { id: string; [key: string]: any };\n\ntype FileMetadataMap = Record<string, FileMetadata>;\n\ntype FileMetadata = {\nreadonly id: string,\nreadonly url: string,\nreadonly is_public: boolean,\nreadonly mime_type: string,\nreadonly original_filename: string,\nreadonly bytes: number,\nreadonly width: number | null,\nreadonly height: number | null,\nreadonly keywords: string[] | null,\nreadonly created_at: string | null,\nreadonly sizes: number[],\n};\n\ntype Diagonal = any;\n\ntype ResourceRecordsResult<TRecord extends ResourceRecord = ResourceRecord> = {\n data: TRecord[];\n};\n\ntype ResourceRecordResult<TRecord extends ResourceRecord = ResourceRecord> = {\n data: TRecord;\n /**\n * `series` is set on a save that changed a repeating series, and `booking` on a create on a\n * Booking resource that returned the first booking of a retry. Both are absent otherwise.\n */\n meta?: { series?: SdkSeriesChangeResult; booking?: SdkBookingCreateResult };\n};\n\ntype SdkSeriesChangeResult = {\n series_id: string;\n /** The counts cover only what ran before the save returned. */\n counts: {\n created: number;\n moved: number;\n updated: number;\n removed: number;\n kept: number;\n failed: number;\n /** Booking dates that wait for rescheduling, the queued remainder included. */\n waiting: number;\n };\n items: Array<{\n id: string;\n status: 'error';\n http_status: number;\n errors: string[];\n code:\n | 'series_kept_referenced'\n | 'series_kept_policy'\n | 'series_kept_locked'\n | 'series_kept_hook'\n | 'series_kept_override'\n | 'series_kept_capacity'\n | 'series_failed';\n }>;\n /**\n * On a Booking resource: every date of the planned range that waits for rescheduling. Each\n * booking is written with the waiting status, to reschedule by hand. `reason` is `full` for a\n * date with no capacity left and `closed` for a date outside open hours on a resource that\n * requires them. `record_id` is null for a date the queued remainder has not written yet.\n */\n waiting: Array<{\n date: string;\n record_id: string | null;\n reason: 'full' | 'closed';\n }>;\n /**\n * What part of the change still runs on the queue after the save returned, or null when it ran\n * whole. `from` is the first date of the queued remainder within the horizon, or null when only\n * the past part waits. `past` is true while the past occurrences of an \"All occurrences\" change\n * still wait. The counts cover only what ran before the save returned, so a save whose run was\n * queued whole has zero counts.\n */\n queued: { from: string | null; past: boolean } | null;\n};\n\ntype SdkBookingCreateResult = {\n replayed: true;\n};\n\ntype ResourceRecordPayload<TRecord extends ResourceRecord = ResourceRecord> =\n Partial<TRecord> & Record<string, any>;\n\ntype ResourceBulkUpdateRecord<TRecord extends ResourceRecord = ResourceRecord> =\n ResourceRecordPayload<TRecord> & { id: string };\n\ntype ResourceDeletePayload = {\n /**\n * Capability-specific operation options. Recurring eventable deletes accept\n * `{ eventable: { scope: 'this' | 'this_and_future' | 'all' } }`.\n */\n $operation?: Record<string, Record<string, any>>;\n};\n\ntype ResourceOperationRecord<TRecord extends ResourceRecord = ResourceRecord> = TRecord;\n\ntype ResourceUrls = {\n /** `GET` list records \u2014 `/api/data/resources/{id}/records`. */\n index: string;\n /** `POST` search records \u2014 `/api/data/resources/{id}/record/search`. */\n search: string;\n /** `GET` count records \u2014 `/api/data/resources/{id}/records/count`. */\n count: string;\n /** `POST` create a record \u2014 `/api/data/resources/{id}/records`. */\n store: string;\n /** `PUT` update multiple records \u2014 `/api/data/resources/{id}/records/bulk`. */\n bulk: string;\n /** `GET` one record \u2014 `/api/data/resources/{id}/records/{record}`. */\n show(recordId: string): string;\n /** `PUT` update a record \u2014 `/api/data/resources/{id}/records/{record}`. */\n update(recordId: string): string;\n /** `DELETE` soft-delete a record \u2014 `/api/data/resources/{id}/records/{record}`. */\n delete(recordId: string): string;\n /** `POST` restore a soft-deleted record \u2014 `/api/data/resources/{id}/records/{record}/restore`. */\n restore(recordId: string): string;\n /**\n * `PUT` update a field's schema \u2014 `/api/data/resources/{id}/fields/{field}`.\n * Requires the `manage_fields` permission; prefer\n * {@link SdkResourceOperations.updateField} over calling this directly.\n */\n field(fieldId: string): string;\n};\n\ntype ResourceFieldUpdatePayload = {\n type?: string;\n label?: string | null;\n description?: string | null;\n guard?: any;\n settings?: Record<string, any>;\n} & Record<string, any>;\n\ntype SdkDndIdentifier = string | number;\n\ntype SdkDndSortableItem = {\n id: SdkDndIdentifier;\n};\n\ntype SdkDndItemIdentifier<TItem> = TItem extends {\n id: infer TIdentifier;\n}\n ? Extract<TIdentifier, SdkDndIdentifier>\n : SdkDndIdentifier;\n\ntype SdkDndSortableOptions<TItem> = {\n /** Return the stable, unique drag id for an item. Items with an `id` do not need this option. */\n getItemId: (item: TItem) => SdkDndIdentifier;\n};\n\ntype SdkDndReorderChange<TItem> = {\n id: SdkDndItemIdentifier<TItem>;\n item: TItem;\n previousIndex: number;\n index: number;\n};\n\ntype SdkDndReorderResult<TItem> = {\n /** The complete array after the move. */\n items: TItem[];\n /** Only items whose array index changed. */\n changes: SdkDndReorderChange<TItem>[];\n};\n\ntype SdkDndSortableRenderState = {\n index: number;\n isDragging: boolean;\n};\n\ntype SdkDndLocation = {\n index: number;\n};\n\ntype SdkDndGroupedLocation = SdkDndLocation & {\n groupId: SdkDndIdentifier;\n};\n\ntype SdkDndMoveContext<TItem, TLocation extends SdkDndLocation = SdkDndLocation> = {\n item: TItem;\n source: TLocation;\n destination: TLocation;\n /** Items in the source collection when the drag began, including the active item. */\n sourceItems: readonly TItem[];\n /** Items currently in the destination collection, excluding the active item. */\n destinationItems: readonly TItem[];\n};\n\ntype SdkDndCanDrag<TItem, TLocation extends SdkDndLocation = SdkDndLocation> = (\n item: TItem,\n source: TLocation\n) => boolean;\n\ntype SdkDndCanDrop<TItem, TLocation extends SdkDndLocation = SdkDndLocation> = (\n context: SdkDndMoveContext<TItem, TLocation>\n) => boolean;\n\ntype SdkDndSortableListCommonProps<TItem> = {\n items: readonly TItem[];\n onError?: (error: any) => void;\n renderItem: (item: TItem, state: SdkDndSortableRenderState) => React.ReactNode;\n /** Optional for objects with an `id`; required at runtime for every other item shape. */\n getItemId?: (item: TItem) => SdkDndIdentifier;\n /** Return a nonempty accessible label for each item. Omit the callback for positional labels. */\n getItemLabel?: (item: TItem) => string;\n /** Return false to prevent an item from being picked up. */\n canDrag?: SdkDndCanDrag<TItem>;\n /** Return false to reject a proposed destination index. */\n canDrop?: SdkDndCanDrop<TItem>;\n ariaLabel?: string;\n layout?: 'vertical' | 'horizontal' | 'grid';\n className?: string;\n itemClassName?: string;\n handleClassName?: string;\n disabled?: boolean;\n};\n\ntype SdkDndCustomPersistence<TItem> = {\n /** Receives the complete reordered list immediately after a successful drop. */\n onItemsChange: (items: TItem[]) => void;\n resource?: never;\n sortField?: never;\n /**\n * Receives only changed positions plus the full result. Returning a rejected promise\n * rolls the optimistic list back and calls `onError`.\n */\n onReorder?: (\n changes: SdkDndReorderChange<TItem>[],\n result: SdkDndReorderResult<TItem>\n ) => any | Promise<any>;\n};\n\ntype SdkDndResourcePersistence<TItem> = {\n /**\n * Optional observer for the complete reordered list. Resource mode owns its optimistic\n * order and reconciles refreshed resource records whether this callback is present or not.\n */\n onItemsChange?: (items: TItem[]) => void;\n /** Resource handle whose existing bulk-update endpoint persists changed positions. */\n resource: Pick<SdkResourceOperations, 'updateMany'>;\n /** Stored Number field key or field handle used for ordering this list. */\n sortField: SdkFieldSelector;\n onReorder?: never;\n};\n\ntype SdkResourceOperations<TResource extends SdkResourceType = SdkResourceType> = {\n /** Capability field mappings resolved to fields \u2014 see {@link SdkResourceCapabilityFieldMappings}. */\n capabilities?: Record<string, SdkResourceCapabilityFieldMappings>;\n /** Record API endpoint URLs for this resource \u2014 see {@link ResourceUrls}. */\n urls: ResourceUrls;\n /**\n * Check whether the acting identity can perform an operation on this resource.\n * This reads the resource's server-resolved `policy_scope`; it is intended for\n * conditionally rendering page controls, while the API remains authoritative.\n *\n * @example\n * ```tsx\n * const invoices = Sdk.resource('...');\n * return invoices.can('create') ? <Sdk.Components.Button>New invoice</Sdk.Components.Button> : null;\n * ```\n */\n can(operation: SdkResourceOperation): boolean;\n /**\n * Page through every record the acting identity can read. Pagination, `relations`,\n * and `$scope` only \u2014 the list endpoint has no filtering or sorting, so passing\n * `filter`/`$filter`/`sort`/`$sort` throws instead of silently returning an\n * unfiltered page. Use {@link SdkResourceOperations.search} for those.\n */\n list<const TOptions extends ResourceListParams = ResourceListParams<readonly []>>(\n params?: TOptions & ValidateSdkResourceRelationOptions<TResource, NoInfer<TOptions>>\n ): Promise<\n ResourceCollectionResult<\n SdkResourceRecord<TResource, SdkResourceRelationsFromOptions<TResource, TOptions>>\n >\n >;\n search<const TOptions extends ResourceSearchBody>(\n body: TOptions & ValidateSdkResourceRelationOptions<TResource, NoInfer<TOptions>>\n ): Promise<\n ResourceCollectionResult<\n SdkResourceRecord<TResource, SdkResourceRelationsFromOptions<TResource, TOptions>>\n >\n >;\n /**\n * Fetch a bounded filtered slice across all of its pages. `maxRecords` is required\n * and capped at 5,000; the SDK logs a warning when more rows match than it returns.\n * Use this only when a client-side aggregate needs actual row values. Prefer\n * {@link SdkResourceOperations.count} for totals and `analytics` for server-side\n * numeric aggregates. Use `useInfiniteRecords` for scrolling collection UIs.\n */\n searchAll<\n const TRelations extends readonly SdkResourceRelationPath<NoInfer<TResource>>[] = readonly []\n >(\n options: ResourceSearchAllOptions<TRelations>\n ): Promise<SdkResourceRecord<TResource, TRelations>[]>;\n /**\n * Return the exact server-computed number of readable matching records without\n * fetching record rows. Accepts the same structured `filter` tree and `scope` as\n * the record hooks.\n */\n count(options?: ResourceCountOptions): Promise<number>;\n /**\n * Fetch one record. Rejects with {@link SdkApiError}; classify policy-denied and\n * missing reads with `Sdk.utils.error.isForbidden` / `isNotFound`. This matters\n * on public pages, where a guest denied by policy must not be shown a false\n * \"record not found\" state.\n */\n get(\n recordId: string\n ): Promise<ResourceRecordResult<ResourceOperationRecord<ResourceRecordFrom<TResource>>>>;\n /**\n * Create a record. On success, cached record and analytics queries are invalidated\n * across resources so reactive hooks (`useResourceDataTable`, `useRecord`,\n * `useAnalytics`, \u2026) refresh automatically \u2014 no manual remount or reload is needed.\n *\n * On a Booking resource, a booking that does not fit rejects with a 422 whose errors hold\n * the message under `$capacity`, such as \"This time is outside open hours.\" Show it to\n * the visitor. The first date of a repeating booking must fit like any booking. When it fits,\n * the create resolves, and `meta.series.waiting` lists each later date that waits for\n * rescheduling, with its `record_id` and `reason`, so the page can offer an action for each\n * date.\n *\n * @example\n * ```ts\n * try {\n * const { meta } = await lessons.create(values);\n * const waiting = meta?.series?.waiting ?? [];\n * if (waiting.length > 0) {\n * setNotice(`${waiting.length} dates need rescheduling`);\n * }\n * } catch (error) {\n * const capacity = Sdk.utils.error.extractApiErrorBody(error)?.errors?.$capacity?.[0];\n * setError(capacity ?? Sdk.utils.error.apiErrorMessage(error, 'The booking was not saved.'));\n * }\n * ```\n */\n create(\n payload: ResourceRecordPayload<ResourceRecordFrom<TResource>>\n ): Promise<ResourceRecordResult<ResourceOperationRecord<ResourceRecordFrom<TResource>>>>;\n /**\n * Update a record. On success, cached record and analytics queries are invalidated\n * across resources so reactive hooks (`useResourceDataTable`, `useRecord`,\n * `useAnalytics`, \u2026) refresh automatically \u2014 no manual remount or reload is needed.\n * On a Booking resource, read a refusal and `meta.series` as `create` describes.\n */\n update(\n recordId: string,\n payload: ResourceRecordPayload<ResourceRecordFrom<TResource>>\n ): Promise<ResourceRecordResult<ResourceOperationRecord<ResourceRecordFrom<TResource>>>>;\n /**\n * Update multiple records with the existing bulk-update endpoint. Empty arrays are a\n * client-side no-op. On success, cached record and analytics queries are invalidated.\n */\n updateMany(\n records: readonly ResourceBulkUpdateRecord<ResourceRecordFrom<TResource>>[]\n ): Promise<ResourceRecordsResult<ResourceOperationRecord<ResourceRecordFrom<TResource>>>>;\n /**\n * Delete a record. On success, cached record and analytics queries are invalidated\n * across resources so reactive hooks refresh automatically (the deleted record's\n * own per-record queries are left alone \u2014 refetching them would 404).\n */\n delete(\n recordId: string,\n payload?: ResourceDeletePayload\n ): Promise<ResourceRecordResult<ResourceOperationRecord<ResourceRecordFrom<TResource>>>>;\n /**\n * Restore a soft-deleted record. On success, cached record and analytics data is\n * refreshed across resources the same way `delete` refreshes it, and the restored\n * record is written straight into the subject's own cache entries.\n *\n * @example\n * ```ts\n * await Sdk.resource('...').restore(recordId);\n * ```\n */\n restore(\n recordId: string\n ): Promise<ResourceRecordResult<ResourceOperationRecord<ResourceRecordFrom<TResource>>>>;\n /**\n * Update a field's schema on this resource \u2014 its label, guard, or `settings`\n * (for a select field, `settings.options` is the editable option list). Accepts\n * the field's key or its handle from `resource.fields`. The acting identity must\n * hold the `manage_fields` permission; without it the endpoint responds 403 \u2014\n * this is a schema operation, so `can(...)` (which reads record policy scope)\n * says nothing about it. `settings` replaces the stored settings \u2014 send the\n * complete object, not a diff. On success the handle's own `fields` metadata is\n * refreshed in place, so UI built from it reflects the change without a reload.\n *\n * @example\n * ```ts\n * const businesses = Sdk.resource('<businesses-resource-id>');\n * const stage = businesses.fields.pipeline_stage;\n * await businesses.updateField(stage, {\n * settings: {\n * ...stage.settings,\n * options: [\n * ...(stage.settings.options ?? []),\n * { value: 'proposal', label: 'Proposal', color: 'blue' }\n * ]\n * }\n * });\n * ```\n */\n updateField(\n field: string | SdkResourceField,\n payload: ResourceFieldUpdatePayload\n ): Promise<SdkResourceField>;\n /**\n * Refresh every cached record and analytics query on the page across resources \u2014\n * lists, single records, relation pickers, calendars, and dashboard aggregations.\n * Cross-resource invalidation is intentional because relation-backed displays,\n * filters, and aggregates can depend on records from another resource. Reach for it\n * after a write the SDK didn't make itself: a workflow run, a websocket event, or a\n * raw `Sdk.api` call against a custom endpoint. Records mutated through\n * `create`/`update`/`delete`/`restore` already refresh on their own.\n *\n * @example\n * ```ts\n * const invoices = Sdk.resource('...');\n * await Sdk.api.post('/api/custom/invoices/reconcile');\n * await invoices.invalidate();\n * ```\n */\n invalidate(): Promise<void>;\n\n /**\n * Aggregate analytics for this resource \u2014 `kpi`, `timeSeries`, `leaderboard`, and\n * `distribution`; see {@link SdkResourceAnalytics}. Always present, and scoped by the resource's\n * read policies for the acting identity.\n *\n * @example\n * ```ts\n * const revenue = await Sdk.resource('...').analytics.kpi({\n * metrics: [{ name: 'total', operation: 'sum', field: 'amount' }]\n * });\n * ```\n */\n analytics: SdkResourceAnalytics;\n\n /**\n * Build calendar invite links for a record on this resource. Returns relative\n * URLs for Google Calendar, Apple/ICS download, and Outlook \u2014 use as `<a href>`\n * targets, not with `Sdk.api`.\n *\n * Only present at runtime on resources with the `eventable` capability configured.\n * On non-eventable resources this property is `undefined`.\n *\n * @example\n * ```tsx\n * const lessons = Sdk.resource('<lessons-resource-id>');\n * const links = lessons.calendarLinks(record.id);\n * <a href={links.google}>Add to Google Calendar</a>\n * <a href={links.apple}>Download .ics</a>\n * <a href={links.outlook}>Add to Outlook</a>\n * ```\n */\n calendarLinks?(recordId: string): CalendarLinks;\n};\n\ntype SdkResourceType<\n TRecord extends ResourceRecord = ResourceRecord,\n TRelations extends ResourceRelationCatalog = ResourceRelationCatalog\n> = {\n readonly [sdkResourceRecordCatalog]?: TRecord;\n readonly [sdkResourceRelationCatalog]?: TRelations;\n};\n\ntype ResourceRelationCatalog = Record<string, Record<string, any>>;\n\ntype SdkResourceOperation =\n | 'list'\n | 'search'\n | 'get'\n | 'create'\n | 'createMany'\n | 'update'\n | 'delete'\n | 'restore'\n | 'count'\n | 'analytics'\n | 'validate';\n\ntype ValidateSdkResourceRelationOptions<\n TResource extends SdkResourceType,\n TOptions extends { relations?: readonly string[] }\n> = TOptions extends { relations: infer TRelations extends readonly string[] }\n ? string extends TRelations[number]\n ? any\n : TRelations extends readonly SdkResourceRelationPath<TResource>[]\n ? any\n : never\n : any;\n\ntype SdkResourceRelationPath<TResource extends SdkResourceType> = Extract<\n keyof ResourceRelationsFrom<TResource>,\n string\n>;\n\ntype ResourceRelationsFrom<TResource extends SdkResourceType> = TResource extends {\n readonly [sdkResourceRelationCatalog]?: infer TRelations extends ResourceRelationCatalog;\n}\n ? TRelations\n : ResourceRelationCatalog;\n\ntype SdkResourceRecord<\n TResource extends SdkResourceType | null | undefined,\n TRelations extends readonly SdkResourceRelationPath<NonNullable<TResource>>[] = readonly []\n> = SdkSelectedResourceRecord<\n ResourceRecordFrom<NonNullable<TResource>>,\n ResourceRelationsFrom<NonNullable<TResource>>,\n TRelations\n>;\n\ntype SdkSelectedResourceRecord<\n TRecord extends ResourceRecord,\n TRelationCatalog extends ResourceRelationCatalog,\n TRelations extends readonly Extract<keyof TRelationCatalog, string>[] = readonly []\n> = TRecord & SelectedResourceRelations<TRelationCatalog, TRelations>;\n\ntype SelectedResourceRelations<\n TRelationCatalog extends ResourceRelationCatalog,\n TRelations extends readonly Extract<keyof TRelationCatalog, string>[]\n> = [TRelations[number]] extends [never]\n ? object\n : string extends keyof TRelationCatalog\n ? UnionToIntersection<TRelationCatalog[TRelations[number]]>\n : IsRelationConstraintFallback<TRelationCatalog, TRelations> extends true\n ? object\n : MergeResourceRelationFragments<TRelationCatalog[TRelations[number]]>;\n\ntype UnionToIntersection<TValue> = (\n TValue extends any ? (value: TValue) => void : never\n) extends (value: infer TIntersection) => void\n ? TIntersection\n : never;\n\ntype IsRelationConstraintFallback<\n TRelationCatalog extends ResourceRelationCatalog,\n TRelations extends readonly string[]\n> = number extends TRelations['length']\n ? [Exclude<keyof TRelationCatalog, TRelations[number]>] extends [never]\n ? true\n : false\n : false;\n\ntype MergeResourceRelationFragments<TUnion> = [RelationKeysRequiringMerge<TUnion>] extends [never]\n ? UnionToIntersection<TUnion>\n : // Generated relation keys are always required and mutable, so rebuilding them does not drop modifiers.\n Omit<UnionToIntersection<TUnion>, RelationKeysRequiringMerge<TUnion>> & {\n [TKey in RelationKeysRequiringMerge<TUnion>]: MergeResourceRelationValue<\n ValueOfUnionKey<TUnion, TKey>\n >;\n };\n\ntype RelationKeysRequiringMerge<TUnion> = {\n [TKey in KeysOfUnion<TUnion>]: IsAny<ValueOfUnionKey<TUnion, TKey>> extends true\n ? never\n : [ValueOfUnionKey<TUnion, TKey>] extends [readonly any[]]\n ? TKey\n : null extends ValueOfUnionKey<TUnion, TKey>\n ? [Exclude<ValueOfUnionKey<TUnion, TKey>, null | undefined>] extends [object]\n ? [\n RelationKeysRequiringMerge<Exclude<ValueOfUnionKey<TUnion, TKey>, null | undefined>>\n ] extends [never]\n ? never\n : TKey\n : never\n : never;\n}[KeysOfUnion<TUnion>];\n\ntype KeysOfUnion<TValue> = TValue extends any ? keyof TValue : never;\n\ntype IsAny<TValue> = 0 extends 1 & TValue ? true : false;\n\ntype ValueOfUnionKey<TUnion, TKey extends PropertyKey> = TUnion extends any\n ? TKey extends keyof TUnion\n ? TUnion[TKey]\n : never\n : never;\n\ntype PropertyKey = any;\n\ntype MergeResourceRelationValue<TValue> =\n IsAny<TValue> extends true\n ? TValue\n : [TValue] extends [readonly any[]]\n ? MergeResourceRelationFragments<ArrayElementOfUnion<TValue>>[]\n :\n | MergeResourceRelationFragments<Exclude<TValue, null | undefined>>\n | (null extends TValue ? null : never)\n | (undefined extends TValue ? undefined : never);\n\ntype ArrayElementOfUnion<TValue> = TValue extends readonly (infer TElement)[] ? TElement : never;\n\ntype ResourceRecordFrom<TResource extends SdkResourceType> = TResource extends {\n readonly [sdkResourceRecordCatalog]?: infer TRecord extends ResourceRecord;\n}\n ? TRecord\n : ResourceRecord;\n\ntype SdkResourceRelationsFromOptions<\n TResource extends SdkResourceType,\n TOptions extends { relations?: readonly string[] }\n> = TOptions extends { relations: infer TRelations extends readonly string[] }\n ? TRelations extends readonly SdkResourceRelationPath<TResource>[]\n ? TRelations\n : readonly []\n : readonly [];\n\ntype SdkResourceAnalytics = {\n /**\n * Compute one or more aggregate metrics across matching records, returning a single\n * {@link AnalyticsKpiResult} keyed by your metric `name`s. Use for headline numbers \u2014 total\n * revenue, active count, average score. Supports `distinct`, unlike the other analytics methods.\n *\n * @example\n * ```ts\n * const stats = await Sdk.resource('...').analytics.kpi({\n * metrics: [\n * { name: 'revenue', operation: 'sum', field: 'amount' },\n * { name: 'customers', operation: 'distinct', field: 'client_id' }\n * ]\n * });\n * ```\n */\n kpi<const TMetrics extends readonly AnalyticsMetric[]>(\n params: AnalyticsKpiParams<TMetrics>\n ): Promise<AnalyticsKpiResult<TMetrics>>;\n\n /**\n * Bucket records into a time series by `date_field` at the chosen `granularity`, computing each\n * metric per bucket. Buckets are returned oldest-first as an array of {@link AnalyticsRow}s, each\n * carrying the `date_field` key (`2026-07` for month, `2026-W28` for week, `2026-07-09` for day,\n * \u2026) plus your metric names. Ideal for line/bar charts over time.\n *\n * @example\n * ```ts\n * const monthly = await Sdk.resource('...').analytics.timeSeries({\n * date_field: 'created_at',\n * granularity: 'month',\n * metrics: [{ name: 'signups', operation: 'count' }]\n * });\n * // [{ created_at: '2026-05', signups: 12 }, { created_at: '2026-06', signups: 31 }, ...]\n * ```\n */\n timeSeries(params: AnalyticsTimeSeriesParams): Promise<AnalyticsRow[]>;\n\n /**\n * Rank entities by a single aggregation, highest first. Each {@link AnalyticsRow} carries the\n * `entity_field` key, the score under `metric_name` (default `value`), and a `count` companion\n * when `operation` is not `count`. Great for \"top N\" lists.\n *\n * @example\n * ```ts\n * const topReps = await Sdk.resource('...').analytics.leaderboard({\n * entity_field: 'sales_rep',\n * operation: 'sum',\n * value_field: 'amount',\n * metric_name: 'revenue',\n * limit: 5\n * });\n * // [{ sales_rep: 'Ada', revenue: { amount: 90000, currency: 'usd' }, count: 9 }, ...]\n * ```\n */\n leaderboard(params: AnalyticsLeaderboardParams): Promise<AnalyticsRow[]>;\n\n /**\n * Group records by `category_field` and return a `count` per category (largest first), plus a\n * summed `total` when `value_field` is given. Each row is an {@link AnalyticsRow} with the\n * `category_field` key, `count`, and optional `total`. Use for pie/donut breakdowns and category\n * tables.\n *\n * @example\n * ```ts\n * const byStatus = await Sdk.resource('...').analytics.distribution({\n * category_field: 'status',\n * value_field: 'amount'\n * });\n * // [{ status: 'paid', count: 120, total: {...} }, { status: 'open', count: 45, total: {...} }]\n * ```\n */\n distribution(params: AnalyticsDistributionParams): Promise<AnalyticsRow[]>;\n};\n\ntype AnalyticsMetric = {\n /** Response key for this metric's value. `alpha_dash` (letters, numbers, `-`, `_`). */\n name: string;\n /** Aggregation to apply. `count` takes no `field`; every other operation requires one. */\n operation: 'count' | 'sum' | 'avg' | 'min' | 'max' | 'distinct';\n /** Field key to aggregate. Omit for `count`; required for every other operation. */\n field?: string;\n};\n\ntype AnalyticsKpiParams<TMetrics extends readonly AnalyticsMetric[] = AnalyticsMetric[]> = {\n /** 1\u201325 metrics to compute across matching records. */\n metrics: TMetrics;\n /** Structured filter tree scoping which records are aggregated \u2014 same `$filter` shape as {@link ResourceSearchBody}. */\n $filter?: Record<string, any>;\n};\n\ntype AnalyticsKpiResult<TMetrics extends readonly AnalyticsMetric[] = AnalyticsMetric[]> = {\n [TMetric in TMetrics[number] as TMetric['name']]?: AnalyticsValue;\n};\n\ntype AnalyticsValue = number | { amount: number; currency: string };\n\ntype AnalyticsTimeSeriesParams = {\n /** Date/datetime field key (or `created_at` / `updated_at`) to bucket rows by. */\n date_field: string;\n /** Bucket size \u2014 see {@link AnalyticsGranularity}. */\n granularity: AnalyticsGranularity;\n /** 1\u201325 metrics to compute per bucket. */\n metrics: AnalyticsTimeSeriesMetric[];\n /** Max buckets to return (1\u20131000, default 100), oldest first. */\n limit?: number;\n /** Structured filter tree \u2014 same `$filter` shape as {@link ResourceSearchBody}. */\n $filter?: Record<string, any>;\n};\n\ntype AnalyticsGranularity = 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';\n\ntype AnalyticsTimeSeriesMetric = {\n /** Response key for this metric's value. `alpha_dash` (letters, numbers, `-`, `_`). */\n name: string;\n /** Aggregation to apply. `count` takes no `field`; every other operation requires one. */\n operation: 'count' | 'sum' | 'avg' | 'min' | 'max';\n /** Field key to aggregate. Omit for `count`; required for every other operation. */\n field?: string;\n};\n\ntype AnalyticsRow = Record<string, any>;\n\ntype AnalyticsLeaderboardParams = {\n /** Field key that identifies the entity each ranked row represents. */\n entity_field: string;\n /** How to score each entity. `count` ranks by record count and takes no `value_field`. */\n operation: 'count' | 'sum' | 'avg';\n /** Numeric/money field to `sum`/`avg`; required for those, omit for `count`. */\n value_field?: string;\n /** Response key for the score (`alpha_dash`, default `value`). */\n metric_name?: string;\n /** Top-N rows to return (1\u2013100, default 10). */\n limit?: number;\n /** Structured filter tree \u2014 same `$filter` shape as {@link ResourceSearchBody}. */\n $filter?: Record<string, any>;\n};\n\ntype AnalyticsDistributionParams = {\n /** Field key to group the distribution by. */\n category_field: string;\n /** Optional numeric/money field summed into a `total` per category. */\n value_field?: string;\n /** Max categories to return (1\u201350, default 10), largest first. */\n limit?: number;\n /** Structured filter tree \u2014 same `$filter` shape as {@link ResourceSearchBody}. */\n $filter?: Record<string, any>;\n};\n\ntype CalendarLinks = {\n /** Redirects to Google Calendar's \"add event\" URL. */\n google: string;\n /** Downloads an `.ics` calendar file. */\n apple: string;\n /** Redirects to Outlook's \"add event\" URL. */\n outlook: string;\n};\n\ntype SdkFieldSelector<TKey extends string = string> = TKey | { readonly key: TKey };\n\ntype SdkDndSortableListProps<TItem> = SdkDndSortableListCommonProps<TItem> &\n (SdkDndCustomPersistence<TItem> | SdkDndResourcePersistence<TItem>);\n\ntype SdkDndSortableListResourceProps<\n TResource extends SdkResourceType & Pick<SdkResource, 'fields' | 'updateMany'>,\n TItem extends SdkDndResourceItem<NoInfer<TResource>> = SdkDndResourceItem<TResource>\n> = SdkDndSortableListCommonProps<TItem> & {\n onItemsChange?: (items: TItem[]) => void;\n resource: TResource;\n sortField: SdkFieldSelector<\n Extract<\n SdkDndNumberFieldKey<ResourceRecordFrom<NoInfer<TResource>>>,\n keyof NoInfer<TResource>['fields']\n >\n >;\n onReorder?: never;\n};\n\ntype SdkResource<\n TFields extends Record<string, SdkResourceField> = Record<string, SdkResourceField>,\n TRecord extends ResourceRecord = ResourceRecord,\n TRelations extends ResourceRelationCatalog = ResourceRelationCatalog,\n TWorkflowInputs extends SdkResourceWorkflowInputs = SdkResourceWorkflowInputs\n> = {\n id: string;\n name: string;\n singular_name: string;\n display_field_id: string | null;\n display_field_key: string | null;\n policy_scope: number | null;\n disabled_data_api_endpoints?: string[];\n fields: TFields;\n workflow_inputs: TWorkflowInputs;\n readonly [sdkResourceRecordCatalog]?: TRecord;\n readonly [sdkResourceRelationCatalog]?: TRelations;\n} & SdkResourceOperations<SdkResourceType<TRecord, TRelations>>;\n\ntype SdkResourceWorkflowInputs = Partial<\n Record<SdkWorkflowEvent, Record<string, SdkWorkflowInput>>\n>;\n\ntype SdkWorkflowEvent = 'created' | 'deleted' | 'restored' | 'saved' | 'updated';\n\ntype SdkWorkflowInput = {\n workflow_id: string;\n workflow_resource_id?: string | null;\n workflow_event?: string | null;\n id: string | null;\n workspace_id?: string | null;\n configurable_id: string | null;\n configurable_type: string | null;\n type: string;\n scope: string | null;\n key: string;\n value: any;\n label: string | null;\n description: string | null;\n guard: any[] | Record<string, any> | null;\n settings: SdkWorkflowInputSettings | null;\n resolver: Record<string, any> | null;\n sort_order?: number | null;\n created_at?: string;\n updated_at?: string;\n deleted_at?: string | null;\n};\n\ntype SdkWorkflowInputSettings = {\n ui?: SdkResourceFieldSettingsUi;\n [key: string]: any;\n};\n\ntype SdkDndResourceItem<TResource extends SdkResourceType = SdkResourceType> =\n ResourceRecordFrom<TResource> & { id: string };\n\ntype SdkDndNumberFieldKey<TItem extends ResourceRecord> = string extends keyof TItem\n ? string\n : Extract<\n {\n [TKey in keyof TItem]-?: NonNullable<TItem[TKey]> extends number ? TKey : never;\n }[keyof TItem],\n string\n >;\n\ntype SdkDndSortableListComponent = {\n <\n TResource extends SdkResourceType & Pick<SdkResource, 'fields' | 'updateMany'>,\n TItem extends SdkDndResourceItem<NoInfer<TResource>>\n >(\n props: SdkDndSortableListResourceProps<TResource, TItem>\n ): React.ReactNode;\n <TItem>(\n props: SdkDndSortableListCommonProps<TItem> & SdkDndCustomPersistence<TItem>\n ): React.ReactNode;\n};\n\ntype SdkDndSortableGroupRenderState<TItem> = {\n id: SdkDndIdentifier;\n items: readonly TItem[];\n isOver: boolean;\n children: React.ReactNode;\n};\n\ntype SdkDndGroupMoveChange<TItem> = {\n id: SdkDndItemIdentifier<TItem>;\n item: TItem;\n source: SdkDndGroupedLocation;\n destination: SdkDndGroupedLocation;\n};\n\ntype SdkDndGroupMoveResult<TItem> = {\n /** All items flattened in group order after the move. */\n items: TItem[];\n /** Only items whose group or index changed. */\n changes: SdkDndGroupMoveChange<TItem>[];\n};\n\ntype SdkDndSortableGroupsCommonProps<TItem, TGroup> = {\n items: readonly TItem[];\n groups: readonly TGroup[];\n getGroupId: (group: TGroup) => SdkDndIdentifier;\n renderGroup: (group: TGroup, state: SdkDndSortableGroupRenderState<TItem>) => React.ReactNode;\n renderItem: (item: TItem, state: SdkDndSortableRenderState) => React.ReactNode;\n /** Optional for objects with an `id`; required at runtime for every other item shape. */\n getItemId?: (item: TItem) => SdkDndIdentifier;\n /** Return a nonempty accessible label for each item. Omit the callback for positional labels. */\n getItemLabel?: (item: TItem) => string;\n getGroupLabel?: (group: TGroup) => string;\n canDrag?: SdkDndCanDrag<TItem, SdkDndGroupedLocation>;\n canDrop?: SdkDndCanDrop<TItem, SdkDndGroupedLocation>;\n onError?: (error: any) => void;\n ariaLabel?: string;\n className?: string;\n groupClassName?: string;\n itemsClassName?: string;\n itemClassName?: string;\n handleClassName?: string;\n disabled?: boolean;\n};\n\ntype SdkDndSortableGroupsCustomPersistence<TItem, TGroup> = {\n getItemGroupId: (item: TItem) => SdkDndIdentifier;\n setItemGroupId: (item: TItem, groupId: SdkDndIdentifier, group: TGroup) => TItem;\n onItemsChange: (items: TItem[]) => void;\n resource?: never;\n groupField?: never;\n sortField?: never;\n /**\n * Receives only changed group positions plus the complete result. A rejected promise\n * rolls the optimistic groups back and calls `onError`.\n */\n onMove?: (\n changes: SdkDndGroupMoveChange<TItem>[],\n result: SdkDndGroupMoveResult<TItem>\n ) => any | Promise<any>;\n};\n\ntype SdkDndSortableGroupsResourcePersistence<TItem, TGroup> = {\n /** Override how a stored group-field value maps to a group id. */\n getItemGroupId?: (item: TItem) => SdkDndIdentifier;\n /**\n * Optionally build the optimistic item value for a destination group. Use this when\n * presentation groups are not the stored relation records. The SDK still persists the\n * destination group id and owns the bulk update, rollback, and cache refresh.\n */\n setItemGroupId?: (item: TItem, groupId: SdkDndIdentifier, group: TGroup) => TItem;\n onItemsChange?: (items: TItem[]) => void;\n resource: Pick<SdkResourceOperations, 'updateMany'>;\n /** Stored field key or handle that assigns an item to a group. */\n groupField: SdkFieldSelector;\n /** Stored Number field key or handle used for ordering within each group. */\n sortField: SdkFieldSelector;\n onMove?: never;\n};\n\ntype SdkDndSortableGroupsProps<TItem, TGroup> = SdkDndSortableGroupsCommonProps<\n TItem,\n TGroup\n> &\n (\n | SdkDndSortableGroupsCustomPersistence<TItem, TGroup>\n | SdkDndSortableGroupsResourcePersistence<TItem, TGroup>\n );\n\ntype SdkDndSortableGroupsResourceProps<\n TResource extends SdkResourceType & Pick<SdkResource, 'fields' | 'updateMany'>,\n TItem extends SdkDndResourceItem<NoInfer<TResource>>,\n TGroup\n> = SdkDndSortableGroupsCommonProps<TItem, TGroup> & {\n getItemGroupId?: (item: TItem) => SdkDndIdentifier;\n setItemGroupId?: (item: TItem, groupId: SdkDndIdentifier, group: TGroup) => TItem;\n onItemsChange?: (items: TItem[]) => void;\n resource: TResource;\n groupField: SdkFieldSelector<Extract<keyof NoInfer<TResource>['fields'], string>>;\n sortField: SdkFieldSelector<\n Extract<\n SdkDndNumberFieldKey<ResourceRecordFrom<NoInfer<TResource>>>,\n keyof NoInfer<TResource>['fields']\n >\n >;\n onMove?: never;\n};\n\ntype SdkDndSortableGroupsComponent = {\n <\n TResource extends SdkResourceType & Pick<SdkResource, 'fields' | 'updateMany'>,\n TItem extends SdkDndResourceItem<NoInfer<TResource>>,\n TGroup\n >(\n props: SdkDndSortableGroupsResourceProps<TResource, TItem, TGroup>\n ): React.ReactNode;\n <TItem, TGroup>(\n props: SdkDndSortableGroupsCommonProps<TItem, TGroup> &\n SdkDndSortableGroupsCustomPersistence<TItem, TGroup>\n ): React.ReactNode;\n};\n\ntype SdkDndReorder = {\n <TItem extends SdkDndSortableItem>(\n items: readonly TItem[],\n activeId: SdkDndIdentifier,\n overId: SdkDndIdentifier | null,\n options?: SdkDndSortableOptions<TItem>\n ): SdkDndReorderResult<TItem>;\n <TItem>(\n items: readonly TItem[],\n activeId: SdkDndIdentifier,\n overId: SdkDndIdentifier | null,\n options: SdkDndSortableOptions<TItem>\n ): SdkDndReorderResult<TItem>;\n};\n\ntype SdkDnd = {\n /**\n * Accessible sortable list with drag handles and keyboard controls. For resource records,\n * pass `resource` and `sortField`; the list owns optimistic ordering, calls `updateMany`,\n * refreshes record queries, and rolls back failures without page-authored state or effects.\n * `onItemsChange` is required only for non-resource lists and optional when a page needs\n * to observe a resource list's new order.\n *\n * @remarks Resource mode keeps `onItemsChange?: (items) => void` optional. Both modes\n * accept `canDrag?: (item, source) => boolean` and\n * `canDrop?: (context) => boolean` constraints.\n *\n * @example\n * ```tsx\n * const students = Sdk.resource('students');\n *\n * return function StudentRanking() {\n * const { data, isLoading, isError } = Sdk.hooks.useRecords(students, {\n * sort: 'rank',\n * perPage: 100\n * });\n *\n * if (isLoading) return <Sdk.Components.Skeleton />;\n * if (isError) return <Sdk.Components.Alert>Could not load students.</Sdk.Components.Alert>;\n *\n * return (\n * <Sdk.Dnd.SortableList\n * items={data}\n * resource={students}\n * sortField={students.fields.rank}\n * renderItem={(student) => <span>{student.name}</span>}\n * getItemLabel={(student) => student.name ?? 'Unnamed student'}\n * ariaLabel=\"Students by rank\"\n * />\n * );\n * }\n * ```\n */\n SortableList: SdkDndSortableListComponent;\n\n /**\n * Accessible sorting across multiple ordered groups. Groups are presentation-agnostic:\n * use them for statuses, positions, categories, lanes, or a Kanban layout. Resource mode\n * persists the grouping field and per-group Number order in one `updateMany` request.\n *\n * @remarks Resource mode keeps `onItemsChange?: (items) => void` optional. Both modes\n * accept `canDrag?: (item, source) => boolean` and\n * `canDrop?: (context) => boolean` for movement constraints between any group shapes.\n * When presentation groups differ from stored relation records, use\n * `setItemGroupId?: (item, groupId, group) => item` to shape the optimistic item; the\n * SDK still owns persistence and sends the destination group id.\n *\n * @example\n * ```tsx\n * const tasks = Sdk.resource('tasks');\n * const statuses = [\n * { id: 'todo', label: 'To do' },\n * { id: 'in_progress', label: 'In progress' },\n * { id: 'done', label: 'Done' }\n * ];\n *\n * return function TaskBoard() {\n * const { data } = Sdk.hooks.useRecords(tasks, { sort: ['status', 'rank'], perPage: 100 });\n *\n * return (\n * <Sdk.Dnd.SortableGroups\n * items={data}\n * groups={statuses}\n * getGroupId={(status) => status.id}\n * getGroupLabel={(status) => status.label}\n * resource={tasks}\n * groupField={tasks.fields.status}\n * sortField={tasks.fields.rank}\n * renderGroup={(status, { children }) => (\n * <section><h2>{status.label}</h2>{children}</section>\n * )}\n * renderItem={(task) => <span>{task.title}</span>}\n * />\n * );\n * }\n * ```\n */\n SortableGroups: SdkDndSortableGroupsComponent;\n\n /**\n * Pure move calculation for custom drag-and-drop UI. Returns the full reordered array and\n * only items whose zero-based indices changed. `SortableList` already calls this internally.\n *\n * @example\n * ```ts\n * const result = Sdk.Dnd.reorder(todos, active.id, over?.id ?? null);\n * setTodos(result.items);\n * await saveChangedTodos(result.changes);\n * ```\n */\n reorder: SdkDndReorder;\n} & Pick<\n any,\n | 'applyModifiers'\n | 'AutoScrollActivator'\n | 'closestCenter'\n | 'closestCorners'\n | 'defaultAnnouncements'\n | 'defaultCoordinates'\n | 'defaultDropAnimation'\n | 'defaultDropAnimationSideEffects'\n | 'defaultKeyboardCoordinateGetter'\n | 'defaultScreenReaderInstructions'\n | 'DndContext'\n | 'DragOverlay'\n | 'getClientRect'\n | 'getFirstCollision'\n | 'getScrollableAncestors'\n | 'KeyboardCode'\n | 'KeyboardSensor'\n | 'MeasuringFrequency'\n | 'MeasuringStrategy'\n | 'MouseSensor'\n | 'PointerSensor'\n | 'pointerWithin'\n | 'rectIntersection'\n | 'TouchSensor'\n | 'TraversalOrder'\n | 'useDndContext'\n | 'useDndMonitor'\n | 'useDraggable'\n | 'useDroppable'\n | 'useSensor'\n | 'useSensors'\n> &\n Pick<\n any,\n | 'arrayMove'\n | 'arraySwap'\n | 'defaultAnimateLayoutChanges'\n | 'defaultNewIndexGetter'\n | 'hasSortableData'\n | 'horizontalListSortingStrategy'\n | 'rectSortingStrategy'\n | 'rectSwappingStrategy'\n | 'sortableKeyboardCoordinates'\n | 'SortableContext'\n | 'useSortable'\n | 'verticalListSortingStrategy'\n > &\n Pick<\n any,\n | 'createSnapModifier'\n | 'restrictToFirstScrollableAncestor'\n | 'restrictToHorizontalAxis'\n | 'restrictToParentElement'\n | 'restrictToVerticalAxis'\n | 'restrictToWindowEdges'\n | 'snapCenterToCursor'\n > &\n Pick<any, 'CSS'>;\n\ntype SdkUseAnalyticsOptions = {\n /** Set `false` to hold the request until the page has what the aggregation needs. */\n enabled?: boolean;\n};\n\ntype SdkAnalyticsHandle<TResult> = {\n data: TResult | undefined;\n isLoading: boolean;\n isError: boolean;\n invalidate: () => Promise<void>;\n};\n\ninterface ResourceRecordCatalog {}\n\ntype SdkRecordOf<TKey extends keyof ResourceRecordCatalog> = ResourceRecordCatalog[TKey];\n\ntype WorkflowReference =\n | Extract<keyof WorkflowCatalog, string>\n | (string & Record<never, never>);\n\ninterface WorkflowCatalog {}\n\ninterface PageAliasCatalog {}\n\ntype PageReference =\n | Extract<keyof PageAliasCatalog, string>\n | (string & Record<never, never>);\n\ntype SdkPageHandle = {\n /** The page's stable id. */\n readonly id: string;\n /** The published route pattern, e.g. `/invoices/{invoice}`, or null when the page is not published. */\n readonly route: string | null;\n /**\n * The page's URL with `params` filled into the route, or null when the page\n * is not published or a route parameter is missing.\n *\n * @example\n * const href = Sdk.page(INVOICE_DETAIL_PAGE_ID).url({ invoice: row.id });\n * return href ? <a href={href}>View</a> : null;\n */\n url(params?: Record<string, any>): string | null;\n /**\n * Navigate to the page with `params` filled into the route. Unpublished\n * pages and missing route params are ignored after a warning.\n *\n * @example\n * Sdk.page(STUDENTS_PAGE_ID).navigate();\n * @example\n * Sdk.page(INVOICE_DETAIL_PAGE_ID).navigate({ invoice: row.id }, { openInNewTab: true });\n */\n navigate(params?: Record<string, any>, options?: TaskNavigateOptions): void;\n};\n\ntype TaskNavigateOptions = {\n openInNewTab?: boolean;\n preserveScroll?: boolean;\n preserveState?: boolean;\n};\n\ntype PageRefParams = Record<string, string | number>;\n\ntype PageRef<\n K extends string = string,\n P extends PageRefParams | undefined = PageRefParams | undefined\n> = {\n /** The page's key, from its `definePage` options. */\n readonly key: K;\n /** The page's id, or null when the page is not deployed or is paused. */\n readonly id: string | null;\n /** The deployed route pattern, e.g. `/members/{id}`, or null when the page is not deployed or is paused. */\n readonly route: string | null;\n /** The page's URL with `params` filled into the route, or null when the page has no route. */\n url(...args: undefined extends P ? [params?: P] : [params: P]): string | null;\n /** Navigate to the page with `params` filled into the route. A page without a route is ignored after a warning. */\n navigate(\n ...args: undefined extends P\n ? [params?: P, options?: TaskNavigateOptions]\n : [params: P, options?: TaskNavigateOptions]\n ): void;\n};\n\ntype SdkResourceReference = SdkResourceHandle | string | null | undefined;\n\ntype SdkResourceHandle = Omit<SdkResource, keyof SdkResourceOperations>;\n\ntype SdkResourceQueryReference<TUrl extends keyof ResourceUrls = 'show' | 'search'> =\n | SdkResourceQueryHandle<TUrl>\n | SdkResourceReference;\n\ntype SdkResourceQueryHandle<TUrl extends keyof ResourceUrls = 'show' | 'search'> =\n SdkResourceType &\n Pick<SdkResource, 'id' | 'name' | 'disabled_data_api_endpoints'> & {\n urls: Pick<ResourceUrls, TUrl>;\n };\n\ntype SdkAnalyticsResource<TKind extends keyof SdkResourceAnalytics> = Pick<\n SdkResource,\n 'id'\n> & {\n analytics: Pick<SdkResourceAnalytics, TKind>;\n};\n\ntype SdkCatalogRelationPath<TResourceId extends string> =\n TResourceId extends keyof ResourceCatalog\n ? SdkResourceRelationPath<ResourceCatalog[TResourceId]>\n : string;\n\ninterface ResourceCatalog {}\n\ntype SdkCatalogRecord<\n TResourceId extends string,\n TRelations extends readonly SdkCatalogRelationPath<TResourceId>[] = readonly []\n> = TResourceId extends keyof ResourceCatalog\n ? SdkResourceRecord<\n ResourceCatalog[TResourceId],\n Extract<TRelations, readonly SdkResourceRelationPath<ResourceCatalog[TResourceId]>[]>\n >\n : ResourceRecord;\n\ntype SdkReferenceRelationPath<TReference extends ResourceTypeReference> = [\n NonNullable<TReference>\n] extends [never]\n ? string\n : NonNullable<TReference> extends SdkResourceType\n ? SdkResourceRelationPath<NonNullable<TReference>>\n : TReference extends string\n ? SdkCatalogRelationPath<TReference>\n : string;\n\ntype ResourceTypeReference =\n | (SdkResourceType & Pick<SdkResource, 'id'>)\n | string\n | null\n | undefined;\n\ntype SdkReferenceRecord<\n TReference extends ResourceTypeReference,\n TRelations extends readonly SdkReferenceRelationPath<TReference>[] = readonly []\n> = [NonNullable<TReference>] extends [never]\n ? ResourceRecord\n : NonNullable<TReference> extends SdkResourceType\n ? TRelations extends readonly SdkResourceRelationPath<NonNullable<TReference>>[]\n ? SdkResourceRecord<NonNullable<TReference>, TRelations>\n : never\n : TReference extends string\n ? TRelations extends readonly SdkCatalogRelationPath<TReference>[]\n ? SdkCatalogRecord<TReference, TRelations>\n : never\n : ResourceRecord;\n\ntype SdkPollInterval<TData> = number | false | ((data: TData) => number | false);\n\ntype SdkUsePollOptions = {\n /**\n * Start polling on mount. Defaults to `true`. Pass `false` to wait for a manual\n * `start()`, for example until the visitor submits something the page then waits on.\n */\n autoStart?: boolean;\n /**\n * Keep polling at full rate while the browser tab is hidden. Defaults to `false`, which\n * slows a hidden tab to one tick in ten rather than stopping it.\n */\n keepAlive?: boolean;\n};\n\ntype SdkPollControls = {\n start: () => void;\n stop: () => void;\n /**\n * When the latest tick finished loading the page data, or `null` before the first\n * one. It changes on every successful tick, even when the data itself did not.\n */\n lastPolledAt: Date | null;\n};\n\ntype SdkUseRecordOptions<\n TRelations extends readonly string[] = readonly string[],\n TRecord extends ResourceRecord = ResourceRecord\n> = {\n relations?: TRelations;\n enabled?: boolean;\n /** Soft-delete subset to fetch from. Defaults to active records only; pass\n * `RecordScope.ActiveOrArchived`/`Archived` to fetch (or restore) archived records. */\n scope?: RecordScope;\n /**\n * Refetch this record on an interval. The function form receives the current record\n * (`undefined` before the first fetch) and returns `false` to stop.\n *\n * @example\n * ```tsx\n * Sdk.hooks.useRecord(orders, orderId, {\n * relations: ['tickets'],\n * pollInterval: (order) => (order?.tickets?.length ? false : 3000)\n * });\n * ```\n */\n pollInterval?: SdkPollInterval<TRecord | undefined>;\n};\n\ntype SdkRecordHandle<TRecord extends ResourceRecord = ResourceRecord> = {\n data: TRecord | undefined;\n isLoading: boolean;\n isError: boolean;\n /**\n * The failed request, or `null`. On public pages, a guest denied by resource\n * policy receives a forbidden error, while an absent record receives not-found;\n * branch with `Sdk.utils.error` instead of treating missing `data` as not-found.\n */\n error: SdkApiError | null;\n /**\n * Whole seconds until the next automatic `pollInterval` request, rounded up, for\n * rendering a refresh countdown. `0` means a refresh is in flight right now. `null`\n * means no refresh is scheduled: before the first request settles, without a\n * `pollInterval`, once a function-form `pollInterval` returns `false`, or while the\n * browser tab is hidden and the poll is paused.\n */\n refreshIn: number | null;\n invalidate: () => Promise<void>;\n};\n\ntype SdkApiError = {\n /** HTTP status code, or `0` when no response was received. */\n readonly status: number;\n /** Parsed response body, or `null` when no response body was received. */\n readonly body: unknown;\n /** Human-readable request failure message. */\n message: string;\n};\n\ntype SdkUseRecordsOptions<\n TRelations extends readonly string[] = readonly string[],\n TRecord extends ResourceRecord = ResourceRecord\n> = {\n /**\n * Relation paths to load alongside each record, e.g. `['family']`. Filtering on a\n * nested path (`family.id`) does not load the relation: it still comes back as its\n * `{ id }` reference, so list it here to read any other related field.\n */\n relations?: TRelations;\n /**\n * Structured `$filter` tree \u2014 the same contract as {@link ResourceSearchBody}'s\n * `filter`: `{ operator: 'and' | 'or' | 'nor', conditions: [...] }`, never a flat\n * map like `{ status: 'paid' }`. `null` means unfiltered, same as omitting it, so\n * saved view state (e.g. `useSharedFilters().filter`) can be passed straight through.\n */\n filter?: Record<string, any> | null;\n /** Sort input in any {@link ResourceSortInput} form; normalized to `$sort`. `null` = default order. */\n sort?: ResourceSortInput | null;\n /** Free-text search across the resource's searchable fields, sent as `$search`. `null` or `''` = no search. */\n search?: string | null;\n /** Page number to fetch. Defaults to 1. */\n page?: number;\n /** Records per page (maximum 500, defaults to 15). `null` = the default page size. */\n perPage?: number | null;\n /** Soft-delete subset to read from. Defaults to active records only. */\n scope?: RecordScope;\n /**\n * `'series'` returns one record for each repeating event of an Eventable resource, sent as\n * `$collapse`: its first date under `sort` (default: the Event Schedule start) among the\n * records that pass every filter. A one-time record is its own entry, and\n * `pagination.total` counts events. `null` = every record. List the dates of one series\n * with a filter on `__meta.capabilities.eventable.series.series_id`.\n *\n * @example\n * ```tsx\n * const upcoming = Sdk.hooks.useInfiniteRecords(events, {\n * collapse: 'series',\n * sort: 'schedule.start',\n * filter: { operator: 'and', conditions: [{ field: 'schedule.start', operator: '>=', value: now }] }\n * });\n *\n * const seriesId = event.__meta?.capabilities?.eventable?.series?.series_id;\n * const dates = Sdk.hooks.useRecords(events, {\n * enabled: seriesId !== undefined,\n * sort: 'schedule.start',\n * filter: {\n * operator: 'and',\n * conditions: [\n * { field: '__meta.capabilities.eventable.series.series_id', operator: '=', value: seriesId },\n * { field: 'schedule.start', operator: '>=', value: now }\n * ]\n * }\n * });\n * ```\n */\n collapse?: RecordCollapse | null;\n /** Set `false` to hold the request until the page has what the query needs. */\n enabled?: boolean;\n /**\n * Refetch this page of records on an interval. The function form receives the current\n * records array (empty before the first fetch) and returns `false` to stop.\n *\n * @example\n * ```tsx\n * Sdk.hooks.useRecords(tickets, {\n * filter: { operator: 'and', conditions: [{ field: 'order_id', operator: '=', value: orderId }] },\n * pollInterval: (records) => (records.length > 0 ? false : 3000)\n * });\n * ```\n */\n pollInterval?: SdkPollInterval<TRecord[]>;\n};\n\ntype SdkRecordsHandle<TRecord extends ResourceRecord = ResourceRecord> = {\n /** Authorized file metadata for the records currently retained by this hook. */\n files: FileMetadataMap;\n /** The records on the current page \u2014 an empty array until the first fetch resolves. */\n data: TRecord[];\n /** Pagination metadata for the current page, `undefined` until the first fetch resolves. */\n pagination: ResourcePagination | undefined;\n isLoading: boolean;\n isError: boolean;\n /** The failed request, or `null`; classify it with `Sdk.utils.error`. */\n error: SdkApiError | null;\n /**\n * Whole seconds until the next automatic `pollInterval` request, rounded up. `0`\n * means a refresh is in flight. `null` means no refresh is scheduled.\n */\n refreshIn: number | null;\n invalidate: () => Promise<void>;\n};\n\ntype SdkUseInfiniteRecordsOptions<TRelations extends readonly string[] = readonly string[]> =\n Omit<SdkUseRecordsOptions<TRelations>, 'page' | 'pollInterval'>;\n\ntype SdkInfiniteRecordsHandle<TRecord extends ResourceRecord = ResourceRecord> = {\n /** Authorized file metadata for the records currently retained by this hook. */\n files: FileMetadataMap;\n /** Up to the ten most recently loaded pages flattened into one record array. */\n data: TRecord[];\n /** Pagination metadata from the most recently loaded page. */\n pagination: ResourcePagination | undefined;\n isLoading: boolean;\n isError: boolean;\n isFetchingNextPage: boolean;\n hasNextPage: boolean;\n fetchNextPage: () => Promise<void>;\n invalidate: () => Promise<void>;\n};\n\ntype SdkRecordMutationState<TRecord extends ResourceRecord = ResourceRecord> = {\n /** The record returned by the most recent successful call. */\n data: TRecord | undefined;\n /** `true` while a request is in flight. */\n isPending: boolean;\n isError: boolean;\n /** The failed request's {@link SdkApiError} (with `status` and `body`), or `null`. */\n error: SdkApiError | null;\n /** Clears `data`, `error`, and `isError` back to their initial state. */\n reset: () => void;\n};\n\ntype SdkUseCreateRecordOptions<TRecord extends ResourceRecord = ResourceRecord> = {\n onSuccess?: (record: TRecord) => void;\n onError?: (error: SdkApiError) => void;\n /**\n * Make a retried booking create return the booking that the first attempt made, instead of a\n * second booking. Each create writes a key into the field that the resource's booking\n * capability names as its idempotency field. A retry with the same payload sends the same key;\n * a changed payload gets a new key. `create` throws when the resource has no idempotency field.\n * Defaults to `false`.\n */\n idempotent?: boolean;\n};\n\ntype SdkCreateRecordHandle<TRecord extends ResourceRecord = ResourceRecord> =\n SdkRecordMutationState<TRecord> & {\n /**\n * Create a record. Resolves with the created record, or `null` when the request\n * failed \u2014 it never rejects, so an un-awaited call from an event handler cannot\n * produce an unhandled rejection. Read `error` for the failure.\n */\n create: (payload: ResourceRecordPayload<TRecord>) => Promise<TRecord | null>;\n };\n\ntype SdkUseUpdateRecordOptions<TRecord extends ResourceRecord = ResourceRecord> = {\n onSuccess?: (record: TRecord) => void;\n onError?: (error: SdkApiError) => void;\n};\n\ntype SdkUpdateRecordHandle<TRecord extends ResourceRecord = ResourceRecord> =\n SdkRecordMutationState<TRecord> & {\n /**\n * Update a record. Resolves with the updated record, or `null` when the request\n * failed \u2014 it never rejects, so an un-awaited call from an event handler cannot\n * produce an unhandled rejection. Read `error` for the failure.\n */\n update: (recordId: string, payload: ResourceRecordPayload<TRecord>) => Promise<TRecord | null>;\n };\n\ntype SdkEmailRecipient = {\n email: string;\n data?: Record<string, any>;\n} & (\n | { resource_id?: never; record_id?: never }\n /** Supply both IDs to link this recipient's record to their email. */\n | { resource_id: string; record_id: string }\n);\n\ntype SdkSendEmailInput = {\n /** Recipients. With `bulk`, each entry receives its own email. */\n to: SdkEmailRecipient[];\n /** Liquid subject template. */\n subject: string;\n /**\n * Layout variables keyed by name. Without `email_layout_id`, `content` is the whole\n * email body (HTML string or TipTap JSON document).\n */\n layout_data: Record<string, any>;\n /** Published email layout that wraps the body. */\n email_layout_id?: string | null;\n /** Not allowed with `bulk`. */\n cc?: string[];\n /** Not allowed with `bulk`. */\n bcc?: string[];\n reply_to?: string[];\n /** Records the email is about; they are linked to the stored message. */\n records?: Array<{ id: string; resource_id: string }>;\n /** File ids the caller can read. */\n attachments?: string[];\n /** Eventable records to attach as calendar invites; at most 5. An archived or cancelled record sends a cancellation. */\n calendar_events?: Array<{ resource_id: string; record_id: string }>;\n /** Send every recipient its own email instead of one email addressed to all. */\n bulk?: boolean;\n};\n\ntype SdkSendEmailResponse = {\n success: boolean;\n status_message: string;\n recipients: {\n to: string[];\n cc: string[];\n bcc: string[];\n reply_to: string[];\n };\n subject: string | null;\n content: string | null;\n attachments: Array<{ id: string; name: string; mime: string }>;\n bulk: boolean;\n};\n\ntype SdkUseSendEmailOptions = {\n onSuccess?: (response: SdkSendEmailResponse) => void;\n onError?: (error: SdkApiError) => void;\n};\n\ntype SdkSendEmailHandle = {\n /**\n * Queue the email. Resolves with the response, or `null` when the request failed \u2014\n * it never rejects, so an un-awaited call from an event handler cannot produce an\n * unhandled rejection. Read `error` for the failure.\n */\n send: (input: SdkSendEmailInput) => Promise<SdkSendEmailResponse | null>;\n /** The response of the most recent successful call. */\n data: SdkSendEmailResponse | undefined;\n /** `true` while a request is in flight. */\n isPending: boolean;\n isError: boolean;\n /** The failed request's {@link SdkApiError} (with `status` and `body`), or `null`. */\n error: SdkApiError | null;\n /** Clears `data`, `error`, and `isError` back to their initial state. */\n reset: () => void;\n};\n\ntype SdkBulkNotificationRun = {\n id: string;\n status: 'queued' | 'processing' | 'completed' | 'failed' | 'interrupted';\n total_count: number;\n processed_count: number;\n queued_count: number;\n failed_count: number;\n uncertain_count: number;\n /** Source records skipped because an earlier notification already used their idempotency key. */\n duplicate_count: number;\n failures: Array<{ record_id: string; errors: Record<string, string[]> }>;\n failures_truncated: boolean;\n error: string | null;\n};\n\ntype SdkBulkNotificationVariable =\n | { literal: any; source_path?: never; recipient_path?: never }\n | { source_path: string; literal?: never; recipient_path?: never }\n | { recipient_path: string; literal?: never; source_path?: never };\n\ntype SdkSendBulkNotificationsInput = {\n resource_id: string;\n notification_type_id: string;\n notification_template_id?: string | null;\n /** Empty string selects the source record; otherwise use a singular relation path. */\n recipient_path: string;\n variables: Record<string, SdkBulkNotificationVariable>;\n attachments?: string[];\n /**\n * Must contain `{source_record_id}`, for example `subscription-period:{source_record_id}:initial-notice`.\n * Each source record gets its own key, so it matches a single send that uses the same key.\n */\n idempotency_key?: string;\n /** Maximum 50,000 source records. Explicit empty filters select all accessible active records. */\n audience:\n | { mode: 'records'; record_ids: string[]; filters?: never }\n | {\n mode: 'filters';\n filters: Record<string, any> | any[];\n record_ids?: never;\n };\n};\n\ntype SdkSendBulkNotificationsHandle = {\n /** Save the audience and start sending. Returns null on request failure. Never automatically retries. */\n send: (input: SdkSendBulkNotificationsInput) => Promise<SdkBulkNotificationRun | null>;\n data: SdkBulkNotificationRun | undefined;\n isPending: boolean;\n isError: boolean;\n error: SdkApiError | null;\n reset: () => void;\n};\n\ntype SdkNotificationRecipient = ResourceRecord & {\n id: string;\n resource_id: string;\n};\n\ntype SdkNotificationDeliveryChannel = 'email' | 'sms' | 'email_and_sms';\n\ntype SdkSendNotificationInput = {\n notification_type_id: string;\n /** Omit to use the type's compatible current default template. */\n notification_template_id?: string | null;\n variables: Record<string, any>;\n /**\n * At most 200 characters, not blank. A send whose key an earlier notification in the workspace\n * used sends nothing and resolves with `replayed: true`. The response carries that notification's\n * IDs only when the viewer can read it; otherwise they are null and empty. Not allowed with `bulk`.\n */\n idempotency_key?: string;\n} & (\n | (Omit<SdkSendEmailInput, 'subject' | 'layout_data' | 'email_layout_id'> & {\n delivery_channel?: 'email';\n recipient?: never;\n })\n | (Pick<SdkSendEmailInput, 'records' | 'attachments' | 'reply_to' | 'calendar_events'> & {\n delivery_channel?: 'email' | 'email_and_sms';\n recipient: SdkNotificationRecipient;\n to?: never;\n cc?: never;\n bcc?: never;\n bulk?: never;\n })\n | (Pick<SdkSendEmailInput, 'records'> & {\n delivery_channel: 'sms';\n recipient: SdkNotificationRecipient;\n to?: never;\n cc?: never;\n bcc?: never;\n bulk?: never;\n attachments?: never;\n reply_to?: never;\n calendar_events?: never;\n })\n);\n\ntype SdkSendNotificationResponse = SdkSendEmailResponse & {\n notification_history_id: string | null;\n /** Email attempt IDs available at response time. Queued workers can add attempts later. */\n email_message_ids: string[];\n sms_messages: { sms_message_id: string; status: string }[];\n /**\n * True when an earlier notification already used the idempotency key, so nothing was sent.\n * A replay names the earlier notification only when the viewer can read it.\n */\n replayed: boolean;\n};\n\ntype SdkNotificationConsentChannels = {\n email?: boolean;\n sms?: boolean;\n};\n\ntype SdkNotificationConsent = {\n resource_id: string;\n record_id: string;\n channels: { email: boolean; sms: boolean };\n /** When consent last changed, or `null` when the person has never been asked. */\n changed_at: string | null;\n};\n\ntype SdkUseNotificationConsentOptions = {\n /** The notifiable resource. Omit both IDs to manage the signed-in identity's own consent. */\n resourceId?: string | null;\n /** The record in that resource. */\n recordId?: string | null;\n /** Set `false` to skip the read, for example until a record is selected. */\n enabled?: boolean;\n onSuccess?: (consent: SdkNotificationConsent) => void;\n onError?: (error: SdkApiError) => void;\n};\n\ntype SdkNotificationConsentHandle = {\n /** The stored consent. A record that was never asked has email on and SMS off. */\n data: SdkNotificationConsent | undefined;\n isLoading: boolean;\n isError: boolean;\n /** The read failure, or `null`. */\n error: ApiError | null;\n /**\n * Save the given channels. An omitted channel keeps its stored value. Resolves with the\n * stored consent, or `null` when the request failed \u2014 it never rejects, so calling it\n * from an `onChange` is safe; read `updateError` for the failure.\n */\n update: (channels: SdkNotificationConsentChannels) => Promise<SdkNotificationConsent | null>;\n /** `true` while a save is in flight. */\n isPending: boolean;\n /** The save failure, or `null`. */\n updateError: ApiError | null;\n reset: () => void;\n};\n\ntype ApiError = Error & {\n readonly status: number;\n readonly body: any;\n};\n\ntype SdkCalendarUnavailableReason = 'signed_out' | 'flag_off' | 'not_schedulable';\n\ntype SdkCalendarLinkState = 'active' | 'waiting_for_write_access' | 'broken';\n\ntype SdkCalendarAccessRole = 'freeBusyReader' | 'reader' | 'writer' | 'owner';\n\ntype SdkGoogleCalendarLoginStatus =\n | 'connected'\n | 'reconnect_required'\n | 'disconnecting'\n | 'disconnected';\n\ntype SdkCalendarEventsChoice = 'keep' | 'remove';\n\ntype SdkCalendarStaff = {\n resource_id: string;\n record_id: string;\n name: string;\n /** The staff record is the signed-in identity. */\n is_viewer: boolean;\n};\n\ntype SdkCalendarLink = {\n id: string;\n calendar_id: string;\n calendar_name: string;\n /** The Google account that the link uses. Only its owner can reconnect it. */\n login_id: string;\n account: string | null;\n login_status: SdkGoogleCalendarLoginStatus | null;\n owner_name: string;\n viewer_owns_login: boolean;\n connected_by_name: string;\n receives_events: boolean;\n blocks_availability: boolean;\n state: SdkCalendarLinkState;\n access_role: SdkCalendarAccessRole | null;\n /** The explanation of the state for this viewer, the same text as the admin page. Null while active. */\n message: string | null;\n /** A reconnect queued an access check that has not run yet. */\n checking: boolean;\n /** When Diagonal last read the calendar's busy time. `null` when it blocks no availability or was not read yet. */\n busy_refreshed_at: string | null;\n /** The calendar blocks availability, but its busy time was not read for 6 hours. Bookings can overlap busy time. */\n busy_stale: boolean;\n};\n\ntype SdkCalendarLogin = {\n id: string;\n account: string;\n status: SdkGoogleCalendarLoginStatus;\n connected_at: string;\n};\n\ntype SdkCalendarChoice = {\n login_id: string;\n account: string;\n calendar_id: string;\n name: string;\n access_role: SdkCalendarAccessRole;\n /** The account's own calendar, which also holds the person's personal events. */\n primary: boolean;\n /** Its share lets Diagonal add events. */\n can_receive_events: boolean;\n /** It is already linked to the staff record. */\n linked: boolean;\n /** Diagonal created it for this workspace with `addLink({ newCalendar: true })`. Suggest it first. */\n created_by_diagonal: boolean;\n};\n\ntype SdkCalendarChoices = {\n calendars: SdkCalendarChoice[];\n /** One line for each Google account that could not list its calendars. */\n errors: string[];\n /**\n * The name of a new calendar, on the signed-in identity's own record. `null` on another\n * person's record, where `newCalendar` is refused.\n */\n new_calendar_name: string | null;\n};\n\ntype SdkCalendarResult = {\n status: 'success' | 'warning' | 'error';\n message: string;\n};\n\ntype SdkCalendarSignInResult = SdkCalendarResult & {\n /** The sign-in added a Google account. A reconnect renews one, so offer its calendars only after this. */\n new_login: boolean;\n};\n\ntype SdkCalendarLinkFlags = {\n /** Diagonal adds the person's events to this calendar. Needs a \"Make changes\" share. */\n receivesEvents: boolean;\n /** Busy time in this calendar blocks the person's availability. */\n blocksAvailability: boolean;\n};\n\ntype SdkAddCalendarLinkInput = SdkCalendarLinkFlags & {\n /** One of the viewer's own Google accounts. */\n loginId: string;\n} & (\n | { calendarId: string; email?: never; newCalendar?: never }\n | { email: string; calendarId?: never; newCalendar?: never }\n | { newCalendar: true; calendarId?: never; email?: never }\n );\n\ntype SdkUseCalendarConnectionsOptions = {\n /** A Schedulable staff resource. Omit both IDs for the signed-in identity's own record. */\n resourceId?: string | null;\n /** A record of that resource. Another person's record needs \"Manage calendar connections\" and Show access. */\n recordId?: string | null;\n};\n\ntype SdkCalendarConnectionsHandle = {\n /** False while loading, after a failure, and for each `reason`. Render nothing then. */\n available: boolean;\n reason: SdkCalendarUnavailableReason | null;\n /** False when this environment has no Google Calendar client. Connect and Add then fail. */\n configured: boolean;\n staff: SdkCalendarStaff | null;\n links: SdkCalendarLink[];\n /** The viewer's own Google accounts. A viewer adds calendars only through these. */\n logins: SdkCalendarLogin[];\n /** The viewer holds \"Manage calendar connections\". */\n canManageOthers: boolean;\n /** The outcome of the Google sign-in that just returned to this page. Show it once. */\n result: SdkCalendarSignInResult | null;\n isLoading: boolean;\n /** The read failure, or `null`. A 403 means the viewer may not manage this staff record. */\n error: ApiError | null;\n /**\n * Leave the page for Google sign-in. The browser returns to this page afterwards and\n * `result` carries the outcome. Rejects with the refusal, for example from an editor\n * preview, which can't open Google sign-in.\n */\n connect: () => Promise<void>;\n /** Renew one of the viewer's own Google accounts. Leaves the page like `connect`. */\n reconnect: (loginId: string) => Promise<void>;\n /**\n * End one of the viewer's own Google accounts. Every calendar that uses it stops working.\n * `events` defaults to `keep`.\n */\n disconnect: (\n loginId: string,\n options?: { events?: SdkCalendarEventsChoice }\n ) => Promise<SdkCalendarResult>;\n /** The calendars that the viewer's Google accounts can link, or one account's with `loginId`. */\n listChoices: (loginId?: string) => Promise<SdkCalendarChoices>;\n addLink: (input: SdkAddCalendarLinkInput) => Promise<SdkCalendarResult>;\n /** `events` applies when `receivesEvents` turns off, and defaults to `keep`. */\n updateLink: (\n linkId: string,\n flags: SdkCalendarLinkFlags,\n options?: { events?: SdkCalendarEventsChoice }\n ) => Promise<SdkCalendarResult>;\n /** Stop using a calendar. `events` defaults to `keep`. */\n removeLink: (\n linkId: string,\n options?: { events?: SdkCalendarEventsChoice }\n ) => Promise<SdkCalendarResult>;\n};\n\ntype SdkUseSendNotificationOptions = Omit<SdkUseSendEmailOptions, 'onSuccess'> & {\n onSuccess?: (response: SdkSendNotificationResponse) => void;\n};\n\ntype SdkSendNotificationHandle = Omit<SdkSendEmailHandle, 'send' | 'data'> & {\n send: (input: SdkSendNotificationInput) => Promise<SdkSendNotificationResponse | null>;\n data: SdkSendNotificationResponse | undefined;\n};\n\ntype SdkPreviewNotificationInput = {\n notification_type_id: string;\n /** Omit to use the type's current default template. */\n notification_template_id?: string | null;\n recipient?: SdkNotificationRecipient;\n variables: Record<string, any>;\n};\n\ntype SdkNotificationChannelUnavailableReason =\n | 'template_off'\n | 'no_permission'\n | 'no_address'\n | 'no_phone'\n | 'opted_out';\n\ntype SdkNotificationChannelAvailability = {\n available: boolean;\n /** Null when the channel is available. */\n reason: SdkNotificationChannelUnavailableReason | null;\n /**\n * The mapped email address or E.164 phone number the channel sends to. Null when the\n * recipient has none, or when the type takes email addresses at send time.\n */\n destination: string | null;\n};\n\ntype SdkNotificationChannel = 'email' | 'sms';\n\ntype SdkNotificationChannels = {\n /** `reason` is `template_off`, `no_permission`, or `no_address`. */\n email: SdkNotificationChannelAvailability;\n /** `reason` is `template_off`, `no_permission`, `no_phone`, or `opted_out`. */\n sms: SdkNotificationChannelAvailability;\n};\n\ntype SdkPreviewNotificationResponse = {\n /** The template that was resolved, including a default chosen by the server. */\n notification_template_id: string;\n /** Null when the template has no email. */\n subject: string | null;\n email_layout_id: string | null;\n /** Null when the template has no email. */\n layout_data: Record<string, any> | null;\n /** The rendered SMS text, ready to edit. Null when the template has no SMS. */\n sms_body: string | null;\n channels: SdkNotificationChannels;\n resolved_variables: Record<string, any>;\n};\n\ntype SdkResolvedNotificationEmail = {\n subject: string;\n email_layout_id: string | null;\n layout_data: Record<string, any>;\n};\n\ntype SdkResolvedNotification = {\n notification_template_id: string;\n resolved_variables: Record<string, any>;\n} & (\n | (SdkResolvedNotificationEmail & {\n delivery_channel: 'email';\n sms_body?: never;\n })\n | (SdkResolvedNotificationEmail & {\n delivery_channel: 'email_and_sms';\n sms_body: string;\n })\n | {\n delivery_channel: 'sms';\n sms_body: string;\n subject?: never;\n email_layout_id?: never;\n layout_data?: never;\n }\n);\n\ntype SdkSendResolvedNotificationInput = Omit<\n SdkPreviewNotificationInput,\n 'variables' | 'notification_template_id' | 'recipient'\n> &\n Pick<SdkResolvedNotification, 'notification_template_id'> & {\n resolved_variables?: Record<string, any>;\n /** The same key that `SdkSendNotificationInput` accepts. */\n idempotency_key?: string;\n } & (\n | (SdkResolvedNotificationEmail &\n Pick<SdkSendEmailInput, 'records' | 'attachments' | 'reply_to' | 'calendar_events'> & {\n delivery_channel?: 'email';\n sms_body?: never;\n } & (\n | { recipient: SdkNotificationRecipient; to?: never }\n | {\n recipient?: never;\n to: Array<SdkEmailRecipient & { data?: never }>;\n }\n ))\n | (SdkResolvedNotificationEmail &\n Pick<SdkSendEmailInput, 'records' | 'attachments' | 'reply_to' | 'calendar_events'> & {\n delivery_channel: 'email_and_sms';\n sms_body: string;\n recipient: SdkNotificationRecipient;\n to?: never;\n })\n | (Pick<SdkSendEmailInput, 'records'> & {\n delivery_channel: 'sms';\n sms_body: string;\n recipient: SdkNotificationRecipient;\n subject?: never;\n email_layout_id?: never;\n layout_data?: never;\n to?: never;\n attachments?: never;\n reply_to?: never;\n calendar_events?: never;\n })\n );\n\ntype SdkSendResolvedNotificationHandle = Omit<SdkSendEmailHandle, 'send' | 'data'> & {\n send: (input: SdkSendResolvedNotificationInput) => Promise<SdkSendNotificationResponse | null>;\n /** The response of the most recent successful call. */\n data: SdkSendNotificationResponse | undefined;\n};\n\ntype SdkUpcomingNotification = {\n id: string;\n revision: number;\n idempotency_key: string | null;\n computed_at: string | null;\n scheduled_at_override: string | null;\n changed_by_resource_id: string | null;\n changed_by_record_id: string | null;\n notification_type_name: string;\n scheduled_at: string | null;\n status: 'draft' | 'scheduled' | 'failed';\n is_one_off: boolean;\n is_paused: boolean;\n /** Held occurrences do not send until they are resumed. */\n is_held: boolean;\n can_cancel: boolean;\n can_edit: boolean;\n can_reschedule: boolean;\n can_save: boolean;\n can_schedule: boolean;\n can_send_now: boolean;\n can_hold: boolean;\n can_resume: boolean;\n};\n\ntype SdkScheduledNotificationContent = {\n email_layout_id: string | null;\n email_subject: string;\n email_layout_data: Record<string, any>;\n};\n\ntype SdkScheduledNotificationDetail = Omit<SdkUpcomingNotification, 'status'> & {\n status: 'draft' | 'scheduled' | 'failed' | 'queued' | 'cancelled' | 'expired';\n notification_type_id: string;\n notification_template_id: string;\n recipient: SdkNotificationRecipient;\n /** Saved override, or the current shared template when no override exists. */\n content: SdkScheduledNotificationContent;\n has_content_override: boolean;\n};\n\ntype SdkScheduledNotificationEdits = {\n expected_revision: number;\n recipient?: SdkNotificationRecipient;\n /** Omit to retain content. Null restores the shared template. Use raw content from ScheduledNotificationComposer, not resolved NotificationComposer output. */\n content_override?: SdkScheduledNotificationContent | null;\n};\n\ntype SdkUpdateScheduledNotificationInput = SdkScheduledNotificationEdits & {\n /** Required with scheduled_at to release a saved draft. */\n transition?: 'schedule';\n /** Explicit send-time override. Future ISO datetime with Z or an explicit offset. */\n scheduled_at?: string;\n};\n\ntype SdkUpdateScheduledNotificationHandle = {\n /** Rejects with ApiError on permission, validation, or dispatch conflict. Never retries automatically. */\n update: (\n id: string,\n input: SdkUpdateScheduledNotificationInput\n ) => Promise<SdkScheduledNotificationResult>;\n isPending: boolean;\n error: ApiError | null;\n reset: () => void;\n};\n\ntype SdkScheduledNotificationResult = {\n id: string;\n revision: number;\n status: 'draft' | 'scheduled' | 'failed' | 'cancelled' | 'queued' | 'expired';\n};\n\ntype SdkSendScheduledNotificationNowHandle = {\n /** Applies edits and queues this occurrence once. Queued does not mean delivered. Never retries automatically. */\n sendNow: (\n id: string,\n input: SdkScheduledNotificationEdits\n ) => Promise<{ id: string; status: 'queued' }>;\n isPending: boolean;\n error: ApiError | null;\n reset: () => void;\n};\n\ntype SdkNotificationHistoryStatus = 'upcoming' | 'sent' | 'failed' | 'cancelled' | 'expired';\n\ntype SdkNotificationHistoryKind = 'scheduled' | 'sent';\n\ntype SdkNotificationSourceKind = 'api' | 'one_off' | 'schedule' | 'workflow';\n\ntype SdkEmailAttemptStatus =\n | 'bounced'\n | 'deferred'\n | 'delivered'\n | 'dropped'\n | 'failed'\n | 'sending'\n | 'sent'\n | 'spam_reported'\n | 'suppressed';\n\ntype SdkEmailRecipientRole = 'to' | 'cc' | 'bcc';\n\ntype SdkEmailMessagesRecord = {\n /** Resource the record belongs to. */\n resourceId: string;\n /** Record whose linked emails are included. */\n recordId: string;\n};\n\ntype SdkEmailMessagesFilters = {\n /** Only emails linked to a record of this resource. */\n resourceId?: string | null;\n /** Case-insensitive subject substring. */\n subject?: string | null;\n /** Exact recipient addresses; an email matches when any of them received it. */\n emails?: string[];\n /** Recipient roles the `emails` filter checks. Defaults to To, CC, and BCC. */\n recipientRoles?: SdkEmailRecipientRole[];\n /** Exact sender address. */\n fromAddress?: string | null;\n /** Delivery status. */\n status?: SdkEmailAttemptStatus | null;\n /** Inclusive first recorded day, `YYYY-MM-DD` in `timezone`. */\n dateFrom?: string | null;\n /** Exclusive end day, `YYYY-MM-DD` in `timezone`. */\n dateBefore?: string | null;\n /** IANA time zone the date range is read in. Defaults to the viewer's browser time zone. */\n timezone?: string;\n /**\n * Also match unlinked emails sent to the selected records' current email address.\n * Requires the `manage_notifications` permission; the option is dropped silently for\n * other viewers so their linked history still loads.\n */\n includeCurrentAddresses?: boolean;\n /** Email field to read the current address from. Defaults to the Notifiable mapping. */\n emailFieldId?: string | null;\n /** Extra addresses to match alongside the resolved current addresses. */\n additionalAddresses?: string[];\n /** Resolved current addresses to leave out. */\n excludedAddresses?: string[];\n};\n\ntype SdkEmailMessagesOptions = {\n /** Pair with recordId for one record's history. */\n resourceId?: string;\n recordId?: string;\n /** Up to 1,000 records. Replaces the single record pair. Each requires Show access. */\n records?: SdkEmailMessagesRecord[];\n /** Explicit workspace scope. Requires manage_notifications. Do not combine with records. */\n workspace?: boolean;\n filters?: SdkEmailMessagesFilters;\n /** Defaults to newest first. Equal sort values are ordered by message ID. */\n sort?: 'created_at' | '-created_at' | 'subject' | '-subject' | 'status' | '-status';\n /** One-based page number. Defaults to 1. */\n page?: number;\n /** Rows per page, 1 to 100. Defaults to 50. */\n perPage?: number;\n /** False pauses the query. Defaults to true. Empty records also leave it idle. */\n enabled?: boolean;\n /** Single-record searches default to 30 seconds. Multi-record and workspace searches do not poll by default. Minimum 1,000 ms. */\n pollInterval?: number | false;\n};\n\ntype SdkEmailMessage = SdkEmailAttempt['message'] & {\n /** Workflow links are present only when the viewer can open them. */\n context: {\n workflow_title: string | null;\n workflow_url: string | null;\n execution_url: string | null;\n } | null;\n};\n\ntype SdkEmailAttempt = {\n message: {\n id: string;\n from_address: string;\n from_name: string | null;\n to_summary: string;\n subject: string;\n status: SdkEmailAttemptStatus;\n has_html_body: boolean;\n created_at: string;\n /** The files the email carried, without its inline images. */\n attachments: SdkEmailAttemptAttachment[];\n };\n /** Provider delivery events, oldest first. */\n events: SdkEmailAttemptEvent[];\n /**\n * Every To, CC, and BCC address. Null without the `manage_notifications` permission;\n * `message.to_summary` still names the first To address.\n */\n recipients: SdkEmailAttemptRecipients | null;\n};\n\ntype SdkEmailAttemptAttachment = {\n name: string;\n mime: string | null;\n /** Size in bytes. */\n size: number | null;\n /** The stored File id. Null when the email did not send a stored file. */\n id: string | null;\n /** A link that opens the file for a viewer who can read the email. Null without a stored file. */\n url: string | null;\n};\n\ntype SdkEmailAttemptEvent = {\n id: string;\n event_type:\n | 'bounce'\n | 'click'\n | 'deferred'\n | 'delivered'\n | 'dropped'\n | 'group_resubscribe'\n | 'group_unsubscribe'\n | 'open'\n | 'processed'\n | 'spamreport'\n | 'unsubscribe';\n recipient_email: string;\n reason: string | null;\n timestamp: string;\n};\n\ntype SdkEmailAttemptRecipients = {\n to: SdkEmailAddress[];\n cc: SdkEmailAddress[];\n bcc: SdkEmailAddress[];\n};\n\ntype SdkEmailAddress = { address: string; name: string | null };\n\ntype SdkEmailMessages = {\n data: SdkEmailMessage[];\n pagination: ResourcePagination;\n};\n\ntype SdkNotificationHistoryItem = {\n id: string;\n kind: SdkNotificationHistoryKind;\n status: SdkNotificationHistoryStatus;\n /** Saved name for sent rows; the current name, or a deleted placeholder, otherwise. */\n notification_type_name: string;\n notification_template_name: string;\n /** Send time for scheduled rows; preparation time for sent rows. ISO 8601, or null for unscheduled drafts. */\n occurred_at: string | null;\n source_kind: SdkNotificationSourceKind;\n /** Schedule or workflow name. Null without the `manage_notifications` permission. */\n source_name: string | null;\n is_paused: boolean;\n is_held: boolean;\n /** Email attempts the notification produced. Always 0 for scheduled rows. */\n email_count: number;\n /** The attempt furthest along the delivery lifecycle. Null without an attempt. */\n email_status: SdkEmailAttemptStatus | null;\n /** Attempts that have `email_status`. Show this count with the status, not `email_count`. */\n email_status_count: number;\n sms_status_counts?: Record<string, number>;\n};\n\ntype SdkNotificationHistory = {\n data: SdkNotificationHistoryItem[];\n pagination: ResourcePagination;\n};\n\ntype SdkNotificationHistoryOptions = {\n /** Lifecycle status to list. Defaults to `upcoming`. */\n status?: SdkNotificationHistoryStatus;\n /** Notifications linked to any of these records. The viewer needs Show access to each. */\n records?: { resourceId: string; recordId: string }[];\n /**\n * List the whole workspace. Requires `manage_notifications`. Ignored when `records`\n * is set. Without `records` or `workspace`, the hook waits.\n */\n workspace?: boolean;\n /** Only notifications linked to a record of this resource. */\n resourceId?: string | null;\n /** Case-insensitive match on type, template, ID, or email subject. */\n search?: string | null;\n /** Exact type name. Sent rows match the saved name. */\n typeName?: string | null;\n /** Exact template name. Sent rows match the saved name. */\n templateName?: string | null;\n /** `none` for one-offs, `workflow`, `api`, or a schedule ID. */\n source?: string | null;\n /** Sent only. An attempt status, or `no_attempt` for notifications without an email. */\n emailStatus?: SdkEmailAttemptStatus | 'no_attempt' | null;\n /** Inclusive first day, `YYYY-MM-DD` in `timezone`. */\n dateFrom?: string | null;\n /** Exclusive end day, `YYYY-MM-DD` in `timezone`. */\n dateBefore?: string | null;\n /** IANA time zone for the date range. Defaults to the workspace time zone. */\n timezone?: string;\n /** `occurred_at` (oldest first) or `-occurred_at` (newest first). */\n sort?: 'occurred_at' | '-occurred_at';\n page?: number;\n /** 1 to 100. Defaults to 25. */\n perPage?: number;\n enabled?: boolean;\n};\n\ntype SdkNotificationHistoryRecord = {\n id: string;\n resource_id: string;\n label: string;\n resource_name: string;\n is_archived: boolean;\n};\n\ntype SdkNotificationHistoryDetail = Omit<\n SdkNotificationHistoryItem,\n 'email_count' | 'email_status' | 'email_status_count' | 'sms_status_counts'\n> & {\n /** Preparation error. Null without the `manage_notifications` permission. */\n error: string | null;\n /** Saved variable values. Empty without the `manage_notifications` permission. */\n variables: Record<string, any>;\n /** Workflow context of a sent notification. Links appear only for viewers who can open them. */\n context: {\n workflow_title: string | null;\n workflow_url: string | null;\n execution_url: string | null;\n } | null;\n /** Linked records the viewer can read. Other links are left out. */\n records: SdkNotificationHistoryRecord[];\n /** For a queued scheduled row, the saved notification to open with kind `sent`. */\n sent_notification_id: string | null;\n /** Readable email attempts in send order. Empty for scheduled rows and prepared notifications without an email. */\n attempts: SdkEmailAttempt[];\n /** Readable SMS messages in send order. A record-scoped viewer sees the first destination only. */\n sms_messages?: SdkSmsMessage[];\n};\n\ntype SdkSmsMessage = {\n id: string;\n from_number: string;\n to_number: string;\n /** The text as sent. */\n body: string;\n status: SdkSmsMessageStatus;\n /** Why a failed or suppressed message did not reach the carrier. */\n status_reason: string | null;\n /** `gsm7` or `ucs2`. */\n encoding: string;\n segments: number;\n sent_at: string | null;\n finalized_at: string | null;\n};\n\ntype SdkSmsMessageStatus =\n | 'queued'\n | 'sending'\n | 'sent'\n | 'delivered'\n | 'failed'\n | 'suppressed';\n\ntype SdkUpcomingNotifications = {\n data: SdkUpcomingNotification[];\n pagination: ResourcePagination;\n};\n\ntype SdkUpcomingNotificationsOptions = {\n resourceId: string | null | undefined;\n recordId: string | null | undefined;\n page?: number;\n perPage?: number;\n enabled?: boolean;\n};\n\ntype SdkScheduleNotificationInput =\n | ({\n notification_type_id: string;\n notification_template_id: string;\n recipient: SdkNotificationRecipient;\n variables: Record<string, any>;\n content_override?: SdkScheduledNotificationContent | null;\n idempotency_key?: string;\n } & (\n | { status: 'draft'; idempotency_key: string; scheduled_at?: never }\n | { status?: 'scheduled'; scheduled_at: string }\n ))\n | {\n notification_schedule_id: string;\n source_record_id: string;\n stage_id: string;\n status: 'draft';\n content_override?: SdkScheduledNotificationContent | null;\n };\n\ntype SdkCancelledNotificationResult = {\n id: string;\n revision: number;\n status: 'cancelled';\n};\n\ntype SdkScheduleNotificationHandle = {\n /** Rejects with ApiError on failure. */\n schedule: (input: SdkScheduleNotificationInput) => Promise<SdkScheduledNotificationResult>;\n isPending: boolean;\n error: ApiError | null;\n reset: () => void;\n};\n\ntype SdkCancelScheduledNotificationHandle = {\n /** Rejects with ApiError on failure, including 409 once delivery has started. */\n cancel: (id: string, expectedRevision: number) => Promise<SdkCancelledNotificationResult>;\n isPending: boolean;\n error: ApiError | null;\n reset: () => void;\n};\n\ntype SdkHeldNotificationResult = {\n id: string;\n revision: number;\n status: 'scheduled' | 'failed';\n is_held: boolean;\n scheduled_at: string;\n};\n\ntype SdkHoldScheduledNotificationHandle = {\n /** Rejects with ApiError on failure, including 409 once delivery has started. */\n hold: (id: string, expectedRevision: number) => Promise<SdkHeldNotificationResult>;\n isPending: boolean;\n error: ApiError | null;\n reset: () => void;\n};\n\ntype SdkResumeScheduledNotificationHandle = {\n /**\n * Pass a future ISO `scheduledAt` with Z or an offset to send at a new time. It is required\n * when the saved time passed during the hold. Rejects with ApiError on failure.\n */\n resume: (\n id: string,\n expectedRevision: number,\n scheduledAt?: string\n ) => Promise<SdkHeldNotificationResult>;\n isPending: boolean;\n error: ApiError | null;\n reset: () => void;\n};\n\ntype SdkNotificationListOptions = {\n page?: number;\n /** Between 1 and 100. Defaults to 50. */\n perPage?: number;\n search?: string;\n /** Only templates that support every requested channel, and only types with such a template. */\n deliveryChannel?: SdkNotificationDeliveryChannel;\n enabled?: boolean;\n};\n\ntype SdkNotificationTypeListOptions = SdkNotificationListOptions & {\n /** notification selects record recipients; email selects address recipients. */\n recipientMode?: 'notification' | 'email';\n};\n\ntype SdkNotificationVariableSummary = {\n key: string;\n /** Falls back to the key in title case. */\n label: string;\n /** The variable type key, such as `text`, `date`, `select`, or `record`. */\n type: string;\n description: string | null;\n required: boolean;\n};\n\ntype SdkNotificationTypeSummary = {\n id: string;\n name: string;\n /** Every variable key, including the derived `record` of a type with a recipient resource. */\n variableKeys: string[];\n /** The values a sender supplies, in order. Never includes the derived `record`. */\n variables: SdkNotificationVariableSummary[];\n notifiable_resource_id: string | null;\n};\n\ntype SdkNotificationTemplateSummary = {\n id: string;\n name: string;\n notification_type_id: string;\n notification_type_name: string;\n email_layout_name: string | null;\n is_default: boolean;\n is_email_enabled: boolean;\n is_sms_enabled: boolean;\n};\n\ntype SdkNotificationTypeList = {\n data: SdkNotificationTypeSummary[];\n pagination: ResourcePagination;\n};\n\ntype SdkNotificationTemplateList = {\n data: SdkNotificationTemplateSummary[];\n pagination: ResourcePagination;\n};\n\ntype SdkNavigationMenu = {\n id: string;\n name: string;\n items: any[];\n};\n\ntype SdkNavigationMenuCatalog = Record<string, SdkNavigationMenu>;\n\ntype SdkResourceFormWorkflowInputs<\n TResource extends Pick<SdkResource, 'workflow_inputs'>,\n TEvents extends SdkWorkflowEvent\n> = [Extract<keyof TResource['workflow_inputs'], TEvents>] extends [never]\n ? Record<never, never>\n : UnionToIntersection<\n NonNullable<\n TResource['workflow_inputs'][Extract<keyof TResource['workflow_inputs'], TEvents>]\n >\n >;\n\ntype SdkIdentity =\n | ({\n id: string;\n workspace_id: string;\n resource_id: string;\n created_at: string;\n updated_at: string;\n $display_name: string;\n $permission_ids: string[];\n $permission_slugs: string[];\n } & PageIdentityRecord)\n | null;\n\ntype PageIdentityRecord = keyof PageIdentityCatalog extends never\n ? Record<string, any>\n : PageIdentityCatalog;\n\ninterface PageIdentityCatalog {}\n\ntype SdkPermissions = {\n /** Return whether the current identity has a permission id or slug. */\n has(permissionIdOrSlug: string): boolean;\n /** Return whether the current identity has at least one of the supplied permission ids or slugs. */\n hasAny(permissionIdsOrSlugs: readonly string[]): boolean;\n /** Return whether the current identity has the system-administrator permission. */\n isSystemAdmin(): boolean;\n};\n\ntype SdkWorkspaceField = {\n id: string | null;\n key: string;\n type: string;\n label: string | null;\n /** Rendered as help text under inputs. */\n description: string | null;\n guard: string[] | Record<string, string[]> | null;\n settings: Record<string, any>;\n /** Derived on the server from `required` guard rules. */\n is_required: boolean;\n /** Read-only system fields and package-locked values. `WorkspaceFieldsForm` never renders these. */\n is_read_only: boolean;\n /** Hidden from the admin field list because it has its own management surface. */\n is_hidden: boolean;\n};\n\ntype SdkFormField = SdkResourceField | SdkWorkspaceField;\n\ntype SdkWorkspaceValues = Record<string, any>;\n\ntype SdkWorkspace = {\n id: string;\n name: string;\n slug: string;\n primary_domain_id: string | null;\n /** The workspace's default navigation menu, resolvable via `Sdk.navigationMenu()`. */\n default_navigation_menu_id: string | null;\n /** Workspace field definitions keyed by field key. Values live in `Sdk.hooks.useWorkspaceValues`. */\n fields: WorkspaceFieldDefinitionCatalog;\n /** @deprecated Load-time snapshot. Read live values with `Sdk.hooks.useWorkspaceValues`. */\n values: WorkspaceFieldCatalog | null;\n} | null;\n\ntype SdkWorkspaceRecord = Omit<NonNullable<SdkWorkspace>, 'fields'>;\n\ntype SdkApi = {\n /** Parsed body of a 2xx GET; throws {@link SdkApiError} on non-2xx. */\n get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;\n /** Parsed body of a 2xx POST; throws {@link SdkApiError} on non-2xx. */\n post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;\n /** Parsed body of a 2xx PUT; throws {@link SdkApiError} on non-2xx. */\n put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;\n /** Parsed body of a 2xx PATCH; throws {@link SdkApiError} on non-2xx. */\n patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;\n /** Parsed body of a 2xx DELETE; throws {@link SdkApiError} on non-2xx. */\n delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;\n};\n\ntype AxiosRequestConfig = any;\n\ntype SdkApiQueryKey = readonly [string, ...any[]];\n\ntype SdkApiFetcher<TData> = (api: SdkApi) => Promise<TData>;\n\ntype SdkSubscriptionPreview = {\nreadonly currency: string,\nreadonly billing_cycle_mode: ('anniversary' | 'calendar_aligned'),\nreadonly interval: ('monthly' | 'quarterly' | 'semi_annual' | 'yearly'),\nreadonly timezone: string,\nreadonly initial: ({\nreadonly lines: ({\nreadonly price_record_id: string,\nreadonly description: string,\nreadonly quantity: number,\nreadonly is_one_time: boolean,\nreadonly full_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly line_total: {\namount: number,\ncurrency: string,\n},\n})[],\nreadonly total: {\namount: number,\ncurrency: string,\n},\nreadonly covered_start: string,\nreadonly covered_end: string,\nreadonly reference_start: string,\nreadonly reference_end: string,\nreadonly days_charged: number,\nreadonly days_in_period: number,\nreadonly is_prorated: boolean,\nreadonly subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly discount_total: {\namount: number,\ncurrency: string,\n},\nreadonly discounts: ({\nreadonly description: string,\nreadonly duration: ('every_invoice' | 'first_invoice' | 'specific_period'),\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly period_start: string | null,\n})[],\n}),\nreadonly additional_invoices: ({\nreadonly lines: ({\nreadonly price_record_id: string,\nreadonly description: string,\nreadonly quantity: number,\nreadonly is_one_time: boolean,\nreadonly full_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly line_total: {\namount: number,\ncurrency: string,\n},\n})[],\nreadonly total: {\namount: number,\ncurrency: string,\n},\nreadonly covered_start: string,\nreadonly covered_end: string,\nreadonly reference_start: string,\nreadonly reference_end: string,\nreadonly days_charged: number,\nreadonly days_in_period: number,\nreadonly is_prorated: boolean,\nreadonly subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly discount_total: {\namount: number,\ncurrency: string,\n},\nreadonly discounts: ({\nreadonly description: string,\nreadonly duration: ('every_invoice' | 'first_invoice' | 'specific_period'),\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly period_start: string | null,\n})[],\n})[],\nreadonly renewal: ({\nreadonly next_renewal_date: string,\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly discount_total: {\namount: number,\ncurrency: string,\n},\n}),\nreadonly trial_ends_at: string | null,\nreadonly quote: string,\nreadonly discounts: ({\nreadonly calculation_type: ('fixed_amount' | 'percentage'),\nreadonly duration: ('every_invoice' | 'first_invoice' | 'specific_period'),\nreadonly percent: number | null,\nreadonly fixed_amount: {\namount: number,\ncurrency: string,\n} | null,\nreadonly description: string,\nreadonly period_start: string | null,\n})[],\nreadonly first_paid_invoice: ({\nreadonly lines: ({\nreadonly price_record_id: string,\nreadonly description: string,\nreadonly quantity: number,\nreadonly is_one_time: boolean,\nreadonly full_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly line_total: {\namount: number,\ncurrency: string,\n},\n})[],\nreadonly total: {\namount: number,\ncurrency: string,\n},\nreadonly covered_start: string,\nreadonly covered_end: string,\nreadonly reference_start: string,\nreadonly reference_end: string,\nreadonly days_charged: number,\nreadonly days_in_period: number,\nreadonly is_prorated: boolean,\nreadonly subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly discount_total: {\namount: number,\ncurrency: string,\n},\nreadonly discounts: ({\nreadonly description: string,\nreadonly duration: ('every_invoice' | 'first_invoice' | 'specific_period'),\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly period_start: string | null,\n})[],\n}) | null,\n};\n\ntype SdkSubscriptionPreviewOptions = {\n /** Subscription resource ID. No payer or business record is needed. */\n resourceId: string;\n /** Eligible price IDs and integer quantities of at least one. */\n items: { price_record_id: string; quantity: number }[];\n /**\n * Up to ten discounts. Requires Store access to the discount resource. A specific-period\n * discount applies to the invoice whose billing period starts on `period_start`.\n */\n discounts?: {\n calculation_type: 'percentage' | 'fixed_amount';\n percent?: number;\n fixed_amount?: { amount: number; currency: string };\n duration: 'first_invoice' | 'every_invoice' | 'specific_period';\n period_start?: string;\n description?: string;\n }[];\n /** Proposed service start, in the subscription's configured timezone. */\n startedAt?: string;\n /** Permitted calendar anchor month. Monthly billing validates only the day. */\n billingAnchorMonth?: number;\n /** Permitted calendar anchor day. */\n billingAnchorDay?: number;\n /** Calendar mode uses billingAnchorMonth and billingAnchorDay. Omitted uses the Subscription capability default. */\n billingCycleMode?: 'anniversary' | 'calendar_aligned';\n /** Activation policy override. Omitted uses the Subscription capability default. */\n activationPolicy?: 'immediate' | 'after_first_payment';\n /** With first_payment_at, dates assume payment on the preview day and move to the payment date. */\n initialTermAnchor?: 'subscription_created_at' | 'first_payment_at';\n /** Days allowed for the first payment. Omitted uses the Subscription capability default. */\n initialPaymentWindowDays?: number;\n /** Trial duration in days. Zero disables a default trial. Use either this or trialEndsAt. */\n trialDays?: number;\n /** ISO trial-end date in the billing timezone. Use either this or trialDays. */\n trialEndsAt?: string;\n /** Saved payment method requirement for a trial. Omitted uses the Subscription capability default. */\n trialPaymentMethodPolicy?: 'optional' | 'required';\n /** Days past due before lapse. Omitted uses the Subscription capability default. */\n gracePeriodDays?: number;\n /** Billing behavior after lapse. Omitted uses the Subscription capability default. */\n lapseBillingBehavior?: 'continue_billing' | 'suspend_renewals' | 'cancel';\n /** Hold requests until the selection is ready. Defaults to true. */\n enabled?: boolean;\n};\n\ntype SdkSubscriptionPreviewHandle = {\n /** Hidden during loading, after failure, and while disabled. */\n preview: SdkSubscriptionPreview | undefined;\n /** Current quote, or null while no terms can be reviewed. */\n quote: string | null;\n isLoading: boolean;\n error: ApiError | null;\n /** Refresh terms after a failed creation or before renewed confirmation. */\n refetch: () => Promise<void>;\n};\n\ntype SdkSubscriptionChangePreview = {\nreadonly mode: ('current_revision' | 'initial_revision' | 'prorated' | 'selection_only' | 'upcoming_revision'),\nreadonly changed: boolean,\nreadonly currency: string,\nreadonly timezone: string,\nreadonly effective_date: string,\nreadonly period_start: string | null,\nreadonly period_end: string | null,\nreadonly days_in_period: number | null,\nreadonly days_remaining: number | null,\nreadonly charges: ({\nreadonly price_record_id: string,\nreadonly description: string,\nreadonly quantity: number,\nreadonly is_one_time: boolean,\nreadonly full_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly line_total: {\namount: number,\ncurrency: string,\n},\n})[],\nreadonly charge_total: {\namount: number,\ncurrency: string,\n},\nreadonly discounts: ({\nreadonly description: string,\nreadonly duration: ('every_invoice' | 'first_invoice' | 'specific_period'),\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly period_start: string | null,\n})[],\nreadonly discount_total: {\namount: number,\ncurrency: string,\n},\nreadonly credits: ({\nreadonly description: string,\nreadonly quantity: number,\nreadonly full_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly gross_amount: {\namount: number,\ncurrency: string,\n},\nreadonly source_invoice_record_id: string,\nreadonly source_line_record_id: string,\nreadonly covered_start: string,\nreadonly covered_end: string,\n})[],\nreadonly credit_total: {\namount: number,\ncurrency: string,\n},\nreadonly amount_due: {\namount: number,\ncurrency: string,\n},\nreadonly net_credit: {\namount: number,\ncurrency: string,\n},\nreadonly applied_to_invoices: {\ninvoice_record_id: string,\namount: {\namount: number,\ncurrency: string,\n},\n}[],\nreadonly applied_to_invoices_total: {\namount: number,\ncurrency: string,\n},\nreadonly credit_to_balance: {\namount: number,\ncurrency: string,\n},\nreadonly outstanding_before: {\namount: number,\ncurrency: string,\n},\nreadonly outstanding_after: {\namount: number,\ncurrency: string,\n},\nreadonly renewal: ({\nreadonly next_renewal_date: string,\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly discount_total: {\namount: number,\ncurrency: string,\n},\n}) | null,\nreadonly replaces_upcoming_items: boolean,\nreadonly quote: string,\nreadonly effective: string,\nreadonly proration_behavior: string,\nreadonly revision: ({\nreadonly scope: string,\nreadonly period: string,\nreadonly invoice_record_id: string,\nreadonly period_start: string,\nreadonly period_end: string,\nreadonly before_lines: ({\nreadonly price_record_id: string,\nreadonly description: string,\nreadonly quantity: number,\nreadonly is_one_time: boolean,\nreadonly full_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly line_total: {\namount: number,\ncurrency: string,\n},\n})[],\nreadonly after_lines: ({\nreadonly price_record_id: string,\nreadonly description: string,\nreadonly quantity: number,\nreadonly is_one_time: boolean,\nreadonly full_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly line_total: {\namount: number,\ncurrency: string,\n},\n})[],\nreadonly before_subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly after_subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly before_discount_total: {\namount: number,\ncurrency: string,\n},\nreadonly after_discount_total: {\namount: number,\ncurrency: string,\n},\nreadonly before_total: {\namount: number,\ncurrency: string,\n},\nreadonly after_total: {\namount: number,\ncurrency: string,\n},\nreadonly conflicts: {\nline_record_id: string,\nreason: string,\n}[],\nreadonly following_renewal: ({\nreadonly next_renewal_date: string,\nreadonly amount: {\namount: number,\ncurrency: string,\n},\nreadonly subtotal: {\namount: number,\ncurrency: string,\n},\nreadonly discount_total: {\namount: number,\ncurrency: string,\n},\n}),\nreadonly starts_on_payment: boolean,\nreadonly activates: boolean,\n}) | null,\nreadonly schedule: ({\nreadonly period_start: string,\nreadonly period_end: string,\nreadonly preserved_invoices: ({\nreadonly invoice_record_id: string,\nreadonly period_start: string,\nreadonly period_end: string,\nreadonly status: string,\nreadonly amount_total: {\namount: number,\ncurrency: string,\n},\n})[],\nreadonly replaced_selections: ({\nreadonly effective_date: string,\nreadonly items: {\nprice_record_id: string,\nquantity: number,\nunit_price: {\namount: number,\ncurrency: string,\n} | null,\n}[],\n})[],\nreadonly recurring_subtotal: {\namount: number,\ncurrency: string,\n},\n}) | null,\nreadonly items: ({\nreadonly price_record_id: string,\nreadonly quantity: number,\nreadonly pricing: string,\nreadonly catalog_unit_price: {\namount: number,\ncurrency: string,\n},\nreadonly current_unit_price: {\namount: number,\ncurrency: string,\n} | null,\nreadonly effective_unit_price: {\namount: number,\ncurrency: string,\n},\n})[],\n};\n\ntype SdkSubscriptionChangePreviewOptions = {\n /** Subscription resource ID. */\n resourceId: string;\n /** Subscription record ID. The visitor needs Show access to this record. */\n recordId: string;\n /** The complete desired selection: price IDs and integer quantities of at least one. */\n items: {\n price_record_id: string;\n quantity: number;\n pricing?: {\n mode: 'catalog' | 'custom';\n unit_price?: { amount: number; currency: string };\n };\n }[];\n /** Apply now, keep current items until renewal, or start at a later billing period. Defaults to now. */\n effective?: 'now' | 'next_renewal' | 'period';\n /**\n * Start date (`YYYY-MM-DD`, in the subscription's preview timezone) of the later billing period\n * for `effective: 'period'`. It must be a future billing boundary that has no invoice. It is\n * ignored for other timings. The hook holds the request until it is set.\n */\n periodStart?: string;\n /** Ordinary current-period billing. Defaults to prorate. Initial and renewal invoice revisions ignore this option and use their invoice terms. */\n prorationBehavior?: 'prorate' | 'none';\n /**\n * Revise an unpaid renewal invoice that was already issued. Use `effective: 'now'` for the\n * current-period invoice and `effective: 'next_renewal'` for the upcoming renewal invoice.\n * `this_renewal` changes only that invoice; `this_and_following` also carries recurring\n * items forward. Omit for an ordinary item change.\n */\n revisionScope?: 'this_renewal' | 'this_and_following';\n /** Hold requests until the visitor is signed in and the selection is ready. Defaults to true. */\n enabled?: boolean;\n};\n\ntype SdkSubscriptionChangePreviewHandle = {\n /** Hidden during loading, after failure, and while disabled. */\n preview: SdkSubscriptionChangePreview | undefined;\n /** Current quote, or null while no terms can be reviewed. */\n quote: string | null;\n isLoading: boolean;\n error: ApiError | null;\n /** Refresh terms after a failed change or before renewed confirmation. */\n refetch: () => Promise<void>;\n};\n\ntype SdkSchedulableRecordRef = { resourceId: string; id: string };\n\ntype SdkSlotSearchGroup = {\n /** Defaults to `'all'`. */\n mode?: 'all' | 'any';\n /** Only for `mode: 'any'`. Defaults to `'first_available'`. */\n strategy?: 'first_available' | 'least_booked' | 'round_robin';\n records: SdkSchedulableRecordRef[];\n};\n\ntype SdkAvailableSlotsOptions = {\n /** Booking resource ID. Its booking settings decide what a slot occupies. */\n resourceId: string;\n /** At least one group, each with at least one record. */\n groups: SdkSlotSearchGroup[];\n /** Start of the search window: a `Date` or an ISO 8601 date-time. */\n from: Date | string;\n /** End of the search window: a `Date` or an ISO 8601 date-time. */\n to: Date | string;\n /** Length of each slot in minutes. */\n durationMinutes: number;\n /** Minutes between slot starts. Defaults to 15. */\n stepMinutes?: number;\n /** The first slot starts at least this many minutes from now. */\n noticeMinutes?: number;\n /** The last slot starts at most this many days from now. */\n maxAdvanceDays?: number;\n /** Units each slot needs, for records with a capacity. Defaults to 1. */\n quantity?: number;\n /** Most slots to return. Defaults to 50; at most 500. */\n maxSlots?: number;\n /** IANA time zone for each slot's `date`. When omitted, read the zone the search used from `timezone`. */\n timezone?: string;\n /** Makes the `round_robin` choice repeatable for the same seed. */\n seed?: string;\n /** Hold the search until its inputs are ready. Defaults to `true`. */\n enabled?: boolean;\n};\n\ntype SdkAvailableSlot = {\n /** Stable for the same start, end, and assignments. */\n id: string;\n start: string;\n end: string;\n /** The local date of `start` in the search time zone, `YYYY-MM-DD`. */\n date: string;\n assignments: {\n group: number;\n records: { resource_id: string; id: string }[];\n }[];\n};\n\ntype SdkAvailableSlotsHandle = {\n /** Slots in time order. Empty while loading, after a failure, and while disabled. */\n slots: SdkAvailableSlot[];\n /** The time zone of each slot's `date`, or null until a search succeeds. */\n timezone: string | null;\n /** True when the search stopped before the end of the window, for example at `maxSlots`. */\n stoppedEarly: boolean;\n isLoading: boolean;\n /** A 422 names the invalid option; a 429 means a guest searched too often. */\n error: ApiError | null;\n refetch: () => Promise<void>;\n};\n\ntype SdkAvailabilityOptions = {\n /** Schedulable resource ID. */\n resourceId: string;\n /** Record IDs of that resource. At most 50. */\n ids: string[];\n /** Start of the window: a `Date` or an ISO 8601 date-time. */\n from: Date | string;\n /** End of the window: a `Date` or an ISO 8601 date-time. At most 62 days after `from`. */\n to: Date | string;\n /** Hold the read until its inputs are ready. Defaults to `true`. */\n enabled?: boolean;\n /**\n * Words on the closed time of `backgroundTimes`. `busy` defaults to \"Busy\" and `closed` to\n * \"Closed\". `false` keeps the closed shading without words. Changing them sends no request.\n */\n labels?: { busy?: string; closed?: string } | false;\n};\n\ntype SdkAvailabilityPiece = {\n /** `external` is busy time on the record's blocking connected calendars. It never names the calendar. */\n source: 'hours' | 'exception' | 'limit' | 'closure' | 'external';\n effect: 'open' | 'closed' | 'replace';\n start: string;\n end: string;\n /** The record that the piece came from, such as an exception record, or null for hours and busy time. */\n record_id: string | null;\n};\n\ntype SdkRecordAvailability = {\n id: string;\n /** The time zone that the record's hours are read in. */\n timezone: string;\n /** Free time in the window after hours, exceptions, limits, closures, and calendar busy time. */\n available: { start: string; end: string }[];\n pieces: SdkAvailabilityPiece[];\n /**\n * Times for `Sdk.Components.Calendar`: free time with the open tone, then closed time inside\n * the record's hours, labelled \"Busy\" for busy time on connected calendars and \"Closed\" for\n * exceptions, closures, and limits. Time outside the record's hours is absent.\n */\n backgroundTimes: CalendarBackgroundTime[];\n};\n\ntype CalendarBackgroundTime = {\n /** A `Date` or an ISO 8601 date-time. */\n start: Date | string;\n /** A `Date` or an ISO 8601 date-time. */\n end: Date | string;\n /** `'open'` shades free time and `'closed'` shades time that is not free. Defaults to `'open'`. */\n tone?: 'open' | 'closed';\n /** Short text shown at the top of the shaded time. */\n label?: string;\n};\n\ntype SdkAvailabilityHandle = {\n /** One entry for each requested record. Empty while loading, after a failure, and while disabled. */\n records: SdkRecordAvailability[];\n isLoading: boolean;\n error: ApiError | null;\n refetch: () => Promise<void>;\n};\n\ntype SdkScheduleConflictsOptions = {\n /** Resource ID of the records that hold time: a Booking resource or an Eventable resource with a Calendar Owner. */\n resourceId: string;\n /** Only conflicts of these records. At most 50. */\n ids?: string[];\n /** Only conflicts on these Schedulable records. At most 50 IDs. */\n bookable?: { resourceId: string; ids: string[] };\n /** Hold the read until its inputs are ready. Defaults to `true`. */\n enabled?: boolean;\n};\n\ntype SdkScheduleConflict = {\n id: number;\n /** `booking` for a booking's claim, `owned_event` for an event on its calendar owner's time. */\n kind: 'booking' | 'owned_event';\n /** The record that holds the time. */\n record_id: string;\n /** The field of that record that names the Schedulable record. */\n relation: string;\n bookable_resource_id: string;\n bookable_id: string;\n /** The record's own start, without booking buffers. */\n start: string;\n /** The record's own end, without booking buffers. */\n end: string;\n /** The start of the time detection checks: a booking's claim with the buffers of its resource. Equals `start` for an owned event. */\n claim_start: string;\n /** The end of the time detection checks. Equals `end` for an owned event. */\n claim_end: string;\n /** What closes the time. `external` is busy time on a blocking connected calendar. It never names the calendar. */\n sources: ('exception' | 'closure' | 'external' | 'limit' | 'hours')[];\n detected_at: string;\n};\n\ntype SdkScheduleConflictsHandle = {\n /** Conflicts whose time has not ended, by start. Empty while loading, after a failure, while disabled, and for a guest. */\n conflicts: SdkScheduleConflict[];\n /** More conflicts remain than the read returned (at most 500). Narrow the read with `ids` or `bookable` to see them. */\n truncated: boolean;\n isLoading: boolean;\n error: ApiError | null;\n refetch: () => Promise<void>;\n};\n\ntype SdkPaymentRefundPreview = {\nreadonly payment_remaining: {\namount: number,\ncurrency: string,\n},\nreadonly change_credit: {\namount: number,\ncurrency: string,\n} | null,\nreadonly invoice_refundable: {\namount: number,\ncurrency: string,\n} | null,\nreadonly refundable: {\namount: number,\ncurrency: string,\n},\n};\n\ntype SdkPaymentRefundPreviewOptions = {\n /** Payment resource ID. */\n resourceId: string;\n /** Payment record ID. The visitor needs Show access to this record. */\n recordId: string;\n /** Hold requests until the visitor is signed in and the payment is known. Defaults to true. */\n enabled?: boolean;\n};\n\ntype SdkPaymentRefundPreviewHandle = {\n /** Hidden during loading, after failure, and while disabled. */\n preview: SdkPaymentRefundPreview | undefined;\n isLoading: boolean;\n error: ApiError | null;\n /** Refresh the limits after a refund or a rejected refund. */\n refetch: () => Promise<void>;\n};\n\ntype SdkAccountingEntity = {\nreadonly id: string,\nreadonly name: string,\nreadonly active: boolean,\nreadonly is_default: boolean,\n};\n\ntype SdkAccountingEntities = {\n /** Ordered by name. Empty while loading, after failure, in a branch, and for a signed-out visitor. */\n entities: SdkAccountingEntity[];\n isLoading: boolean;\n error: ApiError | null;\n};\n\ntype SdkSeriesOccurrenceMeta = {\n series_id: string;\n /** The occurrence key, today its local date (`YYYY-MM-DD`). Treat it as opaque. */\n key: string;\n /** `restored`: a person restored it after the series removed it. `kept`: the series could not change it, or a lock rule, a referrer, a hook, or the editor's policy kept it from removal. */\n state: 'regular' | 'restored' | 'kept';\n /** Why the series kept it. Present only while `state` is `kept`. */\n reason?:\n | 'series_kept_referenced'\n | 'series_kept_policy'\n | 'series_kept_locked'\n | 'series_kept_hook'\n | 'series_kept_override';\n /** Fields this occurrence changed on its own, which series changes leave alone. */\n overrides: string[];\n};\n\ntype SdkPayerEntityBalance = {\nreadonly entity: ({\nreadonly id: string,\nreadonly name: string,\nreadonly active: boolean,\nreadonly is_default: boolean,\n}),\nreadonly available: {\namount: number,\ncurrency: string,\n},\n};\n\ntype SdkPayerBalancesOptions = {\n /** Payer resource ID. */\n resourceId: string;\n /** Payer record ID. The visitor needs Show access to this record and its balance field. */\n recordId: string;\n /** Hold requests until the visitor is signed in and the payer is known. Defaults to true. */\n enabled?: boolean;\n};\n\ntype SdkPayerBalances = {\n /** Each active entity, and each inactive entity with an available balance above zero. Empty while loading, after failure, and while disabled. */\n balances: SdkPayerEntityBalance[];\n /** The payer's balance field. Null without a balance, while loading, after failure, and while disabled. */\n total: { amount: number; currency: string } | null;\n isLoading: boolean;\n error: ApiError | null;\n /** Refresh the amounts after a ledger entry or a payment. */\n refetch: () => Promise<void>;\n};\n\ntype SdkPackageReleaseHistory = any;\n\ntype SdkPackageReleaseNotesOptions = {\n /** Only this installed package. Omit it for every installed package. */\n packageId?: string;\n /** How many releases at or below the installed one to list per package. Newer releases are always listed. Defaults to 20; at most 100. */\n limit?: number;\n /** Hold the request until its inputs are ready. Defaults to true. */\n enabled?: boolean;\n};\n\ntype SdkPackageReleaseNotes = {\n /** Each installed package, by name. Empty while loading, after failure, and while disabled. */\n packages: SdkPackageReleaseHistory[];\n isLoading: boolean;\n error: ApiError | null;\n refetch: () => Promise<void>;\n};\n\ntype SdkPackageReleaseNotesProps = {\n /** Only this installed package. Omit it to list every installed package. */\n packageId?: string;\n /** Show only the installed release and the newer ones, for a dashboard. */\n compact?: boolean;\n /** How many releases at or below the installed one to list per package. */\n limit?: number;\n /** Shown when no package is installed. */\n emptyMessage?: string;\n className?: string;\n};\n\ntype SdkUseApiOptions<TData = any> = {\n /** Hold the request until its inputs are ready. Defaults to `true`. */\n enabled?: boolean;\n /** Keep the last successful value visible while a changed key fetches. */\n keepPreviousData?: boolean;\n /**\n * Refetch this endpoint on an interval. The function form receives the current `data`\n * (`undefined` before the first fetch) and returns `false` to stop.\n *\n * @example\n * ```tsx\n * Sdk.hooks.useApi(\n * ['records', order.id, 'fulfillment'],\n * (api) => api.get(Sdk.utils.workflow.url('...', { order_id: order.id })),\n * { pollInterval: (data) => (data?.shipped ? false : 5000) }\n * );\n * ```\n */\n pollInterval?: SdkPollInterval<TData | undefined>;\n};\n\ntype SdkApiHandle<TData> = {\n data: TData | undefined;\n isLoading: boolean;\n isError: boolean;\n /** The normalized request failure, or `null` before/after a successful request. */\n error: ApiError | null;\n /**\n * Whole seconds until the next automatic `pollInterval` request, rounded up. `0`\n * means a refresh is in flight. `null` means no refresh is scheduled.\n */\n refreshIn: number | null;\n /** Mark this exact key stale and refetch it when active. */\n invalidate: () => Promise<void>;\n};\n\ntype SdkRouterVisitOptions = TaskRouterVisitOptions;\n\ntype TaskRouterVisitOptions = Record<string, any>;\n\ntype SdkRouter = TaskRouter;\n\ntype TaskRouter = {\n /** Visit a URL (defaults to a GET). Re-renders the page with the new props. */\n visit(url: string, options?: TaskRouterVisitOptions): void;\n /** Make a GET visit, encoding `data` into the query string. */\n get(url: string, data?: Record<string, any>, options?: TaskRouterVisitOptions): void;\n /** Make a POST visit with `data` as the request body. */\n post(url: string, data?: Record<string, any>, options?: TaskRouterVisitOptions): void;\n /** Make a PUT visit with `data` as the request body. */\n put(url: string, data?: Record<string, any>, options?: TaskRouterVisitOptions): void;\n /** Make a PATCH visit with `data` as the request body. */\n patch(url: string, data?: Record<string, any>, options?: TaskRouterVisitOptions): void;\n /** Make a DELETE visit to the URL. */\n delete(url: string, options?: TaskRouterVisitOptions): void;\n /** Reload the current page, re-fetching its props without changing the URL. */\n reload(options?: TaskRouterVisitOptions): void;\n};\n\ntype SdkAuthResource = {\n id: string;\n name: string;\n\n /** Whether the resource has a password field, and so can offer a reset link. */\n passwordResetEnabled: boolean;\n\n /**\n * Whether the resource opted in to emailed sign-in links. When false,\n * {@link SdkAuth.sendMagicLink} is rejected server-side for this resource.\n */\n magicLinkEnabled: boolean;\n\n /**\n * Whether the resource requires an SMS code after the password or emailed link. A\n * sign-in then continues with {@link SdkAuth.twoFactor}.\n */\n twoFactorEnabled: boolean;\n};\n\ntype SdkTwoFactorMode = 'hosted' | 'inline';\n\ntype SdkTwoFactorPending = {\n /** The authenticatable resource that the sign-in belongs to. */\n resourceId: string;\n\n /**\n * `'challenge'`: the visitor enters a code sent to the account's MFA number, or a backup\n * code. `'setup'`: the account has no MFA number yet, so the visitor sets one up first.\n */\n stage: 'challenge' | 'setup';\n};\n\ntype SdkAuthLoginInput = {\n identifier: string;\n password: string;\n resourceId?: string;\n redirectPage?: string;\n returnTo?: 'current';\n /** See {@link SdkTwoFactorMode}. Defaults to `'hosted'`. */\n twoFactor?: SdkTwoFactorMode;\n};\n\ntype SdkAuthMagicLinkInput = {\n identifier: string;\n resourceId?: string;\n redirectPage?: string;\n returnTo?: 'current';\n /**\n * See {@link SdkTwoFactorMode}. With `'inline'`, a link that needs two-factor returns\n * to this page, path parameters and query string included, to show the challenge.\n */\n twoFactor?: SdkTwoFactorMode;\n};\n\ntype SdkAuthPasswordResetInput = {\n identifier: string;\n resourceId?: string;\n /** With `'current'`, the visitor returns to this page after the reset signs them in. */\n returnTo?: 'current';\n /**\n * See {@link SdkTwoFactorMode}. With `'inline'`, a reset that needs two-factor returns\n * to this page to show the challenge.\n */\n twoFactor?: SdkTwoFactorMode;\n};\n\ntype SdkAuth = {\n /**\n * The default authenticatable resource id used when {@link SdkAuth.login} is called\n * without a `resourceId`. `null` when the workspace has zero or multiple authenticatable\n * resources \u2014 pass an explicit `resourceId` in that case.\n */\n resourceId: string | null;\n\n /** Every authenticatable resource in the workspace \u2014 see {@link SdkAuthResource}. */\n resources: SdkAuthResource[];\n\n /**\n * The pending two-factor sign-in of this browser, or `null`. It is set after an\n * `'inline'` sign-in whose resource enables two-factor; render the challenge or the\n * setup for it. Always `null` in previews.\n */\n twoFactor: SdkTwoFactorPending | null;\n\n /**\n * Sign in with an identifier (email) and password. Resolves the target resource from\n * `credentials.resourceId`, falling back to {@link SdkAuth.resourceId}; throws if neither\n * is set. Pass `options` (`onError`, `onSuccess`, `preserveState`, `preserveScroll`, \u2026)\n * straight through to the router \u2014 read validation errors (bad credentials, rate limiting)\n * from `onError`. A successful login to a same-origin workspace page follows the 302\n * redirect as an Inertia visit, so `onSuccess` runs and `preserveState` can keep\n * surrounding form data intact; cross-origin destinations still trigger a full-page\n * `X-Inertia-Location` redirect, so `onSuccess` does not run. Control the destination\n * with `credentials.redirectPage` or use `credentials.returnTo: 'current'` for the\n * exact served page URL.\n */\n login(credentials: SdkAuthLoginInput, options?: SdkRouterVisitOptions): void;\n\n /**\n * Email a one-time sign-in link to `identifier`, for resources that enabled magic link\n * login (see {@link SdkAuthResource.magicLinkEnabled} \u2014 the endpoint 404s otherwise).\n * The response is deliberately identical whether or not an account exists, so treat\n * `onSuccess` as \"the request was accepted\", never as \"an account was found\". Unlike\n * `login` this stays on the current page, so `onSuccess` does run. `returnTo: 'current'`\n * also carries that exact served page URL through the emailed link for confirmation in\n * the same tab, another tab, or another browser.\n */\n sendMagicLink(input: SdkAuthMagicLinkInput, options?: SdkRouterVisitOptions): void;\n\n /**\n * Email a password reset link to `identifier`. Like `sendMagicLink`, the response is\n * the same whether or not an account exists, and the visitor stays on the current\n * page so `onSuccess` runs. `Sdk.Components.LoginForm` already offers this inline.\n */\n sendPasswordResetLink(input: SdkAuthPasswordResetInput, options?: SdkRouterVisitOptions): void;\n\n /** Sign the current identity out. Logout also clears this origin's local and session storage. */\n logout(options?: SdkRouterVisitOptions): void;\n};\n\ntype SdkAuthConfig = {\n resourceId: string | null;\n resources: SdkAuthResource[];\n /** The stable id of the currently served Page. Draft and authoring previews omit it. */\n currentPageId?: string | null;\n /** The pending two-factor sign-in of this browser. Previews omit it. */\n twoFactor?: SdkTwoFactorPending | null;\n};\n\ntype SdkTwoFactorFlow = 'challenge' | 'setup' | 'backupCodes';\n\ntype SdkUseTwoFactorOptions = {\n /**\n * The authenticatable resource. Defaults to the resource of the pending sign-in, then of\n * the signed-in identity, then the workspace's sole authenticatable resource.\n */\n resourceId?: string;\n};\n\ntype SdkTwoFactorChallengeState = {\n /**\n * `'consent'`: no code was sent yet; render `Sdk.Components.TwoFactorSmsConsent` with\n * `flow=\"challenge\"`. `'code'`: a code was sent to `maskedPhone`.\n */\n step: 'consent' | 'code';\n\n /** The last four digits of the MFA number, such as `\u2022\u2022\u2022\u2022 0123`. */\n maskedPhone: string;\n\n /** Seconds until `Sdk.Components.TwoFactorSendCodeButton` can send a new code. */\n resendAvailableIn: number;\n\n /** A confirmation from the last send, such as \"We sent you a new code.\" */\n status: string | null;\n\n /** Why the last code could not be sent. A backup code still works. */\n deliveryError: string | null;\n};\n\ntype SdkTwoFactorRequestState = {\n /** The first load is in flight. */\n isLoading: boolean;\n\n /** The flow did not load. */\n error: SdkApiError | null;\n\n /** A step is being submitted. */\n isPending: boolean;\n\n /** The last submitted step failed. Read field messages with `Sdk.utils.error`. */\n actionError: SdkApiError | null;\n\n /**\n * The pending sign-in ended: it expired, or too many codes were wrong. Call `cancel()`\n * to start again from the email step.\n */\n restartRequired: boolean;\n};\n\ntype SdkTwoFactorChallengeHandle = SdkTwoFactorRequestState & {\n /** Whether a pending sign-in waits for this challenge. Nothing loads otherwise. */\n isActive: boolean;\n\n data: SdkTwoFactorChallengeState | undefined;\n\n /**\n * Set when the code passed and the account has no unused backup codes: render\n * `Sdk.Components.TwoFactorBackupCodes` before the visitor continues.\n */\n needsBackupCodes: boolean;\n\n /** Check a 6-digit SMS code. On success the page navigates to the sign-in destination. */\n verifyCode(code: string): Promise<boolean>;\n\n /** Check a backup code, such as `ABCDE-12345`. Each code works once. */\n verifyBackupCode(code: string): Promise<boolean>;\n\n /** End the pending sign-in and return to the email step. */\n cancel(): Promise<void>;\n};\n\ntype SdkTwoFactorSetupStep = 'authorize' | 'phone' | 'consent' | 'verify' | 'complete';\n\ntype SdkTwoFactorSetupState = {\n /**\n * `'authorize'`: a replacement first proves the current number or a backup code.\n * `'phone'`: enter the new MFA number. `'consent'`: render\n * `Sdk.Components.TwoFactorSmsConsent` with `flow=\"setup\"`. `'verify'`: enter the code\n * sent to the new number. `'complete'`: the number is verified; call `finish()`.\n */\n step: SdkTwoFactorSetupStep;\n\n /** Whether this changes an existing MFA number of the signed-in member. */\n isReplacement: boolean;\n\n /** The last four digits of the number this step texts, or `null` before one is entered. */\n maskedPhone: string | null;\n\n /** A confirmation from the last send. */\n status: string | null;\n};\n\ntype SdkTwoFactorSetupHandle = SdkTwoFactorRequestState & {\n /**\n * Whether a setup applies: a pending sign-in in the setup stage, or a signed-in member\n * of a resource that enables two-factor. Nothing loads otherwise.\n */\n isActive: boolean;\n\n data: SdkTwoFactorSetupState | undefined;\n\n /**\n * The new backup codes, once, after the new number is verified. Show them with\n * `Sdk.Components.TwoFactorBackupCodeList`. Never store them.\n */\n codes: string[] | null;\n\n /** Prove the current factor before a replacement, with an SMS code or a backup code. */\n verifyExistingFactor(factor: { code: string } | { recoveryCode: string }): Promise<boolean>;\n\n /** Set the proposed MFA number, with its country code. */\n submitPhone(phone: string): Promise<boolean>;\n\n /** Check the 6-digit code sent to the proposed number. */\n verifyNewNumber(code: string): Promise<boolean>;\n\n /**\n * Finish after `'complete'`. A pending sign-in navigates to its destination; a\n * replacement resolves `true` and the page stays.\n */\n finish(): Promise<boolean>;\n\n /** Stop the setup. A pending sign-in also ends. */\n cancel(): Promise<void>;\n};\n\ntype SdkTwoFactorBackupCodesState = {\n /** How many unused backup codes remain. */\n remainingCount: number;\n\n /**\n * Whether the first set can be generated without a code, right after a sign-in that\n * passed the second factor.\n */\n setupAvailable: boolean;\n\n /** The last four digits of the MFA number. */\n maskedPhone: string;\n\n /** Whether proving the factor by SMS needs consent first. */\n smsConsentRequired: boolean;\n\n /** A confirmation from the last send. */\n status: string | null;\n};\n\ntype SdkTwoFactorBackupCodesHandle = SdkTwoFactorRequestState & {\n /** Whether the signed-in member's resource enables two-factor. Nothing loads otherwise. */\n isActive: boolean;\n\n data: SdkTwoFactorBackupCodesState | undefined;\n\n /**\n * The new codes, once, after `generate()`. Show them with\n * `Sdk.Components.TwoFactorBackupCodeList`. Never store them.\n */\n codes: string[] | null;\n\n /**\n * Generate a new set. Every earlier code stops working. Pass a fresh SMS code or an\n * unused backup code; pass nothing only when `setupAvailable` is true.\n */\n generate(factor?: { code: string } | { recoveryCode: string }): Promise<boolean>;\n\n /** Leave after the first set: go on to the sign-in destination. */\n continue(): void;\n};\n\ntype SdkUseForm = <TForm extends FormDataType<TForm>>(\n defaults: TForm\n) => InertiaFormProps<TForm>;\n\ntype FormDataType = any;\n\ntype InertiaFormProps = any;\n\ntype WorkflowRouteSettings = CoreWorkflowRouteSettings;\n\ntype CoreWorkflowRouteSettings = any;\n\ntype PageRouteSettings = CorePageRouteSettings;\n\ntype CorePageRouteSettings = any;\n\ntype SdkWorkspaceFields = TaskWorkspaceFields;\n\ntype TaskWorkspaceFields = Record<string, any>;\n\ntype SdkContentValue = {\n type?: string;\n text?: string;\n attrs?: Record<string, any> | null;\n marks?: any[] | null;\n content?: SdkContentValue[] | null;\n};\n\ntype SdkMoney = TaskMoney;\n\ntype TaskMoney = { amount: number; currency: string };\n\ntype SdkMoneyInput = TaskMoneyInput;\n\ntype TaskMoneyInput = TaskMoney | null | undefined;\n\ntype SdkPointValue = { x: number; y: number };\n\ntype SdkRecurrenceDefinition = {\n rule: string;\n exceptions: string[];\n /**\n * On a series occurrence, read only: the date a pending series change takes effect. The\n * `rule` of an occurrence is read from its series, and sending it back unchanged changes\n * nothing.\n */\n changes_on?: string | null;\n};\n\ntype SdkRecurrenceValue = SdkRecurrenceDefinition | string;\n\ntype SdkEventScheduleValue = {\n start: string;\n end: string;\n all_day: boolean;\n recurrence: SdkRecurrenceDefinition | null;\n};\n\ntype SdkRegularHoursDay =\n | 'monday'\n | 'tuesday'\n | 'wednesday'\n | 'thursday'\n | 'friday'\n | 'saturday'\n | 'sunday';\n\ntype SdkRegularHoursValue = {\n periods: {\n from: string | null;\n rules: { days: SdkRegularHoursDay[]; start: string; end: string }[];\n }[];\n};\n\ntype SdkLocalStoragePrimitive = boolean | null | number | string;\n\ninterface SdkLocalStorageObject {\n readonly [key: string]: SdkLocalStorageValue;\n}\n\ntype SdkLocalStorageValue =\n | SdkLocalStoragePrimitive\n | SdkLocalStorageObject\n | SdkLocalStorageArray;\n\ninterface SdkLocalStorageArray extends ReadonlyArray<SdkLocalStorageValue> {}\n\ntype SdkLocalStorageSetter<TValue> = (\n value: TValue | null | ((current: TValue | null) => TValue | null)\n) => void;\n\ntype SdkLocalStorageResult<TValue> = readonly [\n value: TValue | null,\n setter: SdkLocalStorageSetter<TValue>\n];\n\ntype SdkMoneyFormatOptions = TaskMoneyFormatOptions;\n\ntype TaskMoneyFormatOptions = {\n showSymbol?: boolean;\n showCode?: boolean;\n};\n\ntype SdkDateAddUnit = TaskDateAddUnit;\n\ntype TaskDateAddUnit = 'days' | 'weeks' | 'months' | 'years';\n\ntype SdkDateBoundaryUnit = TaskDateBoundaryUnit;\n\ntype TaskDateBoundaryUnit = 'week' | 'month' | 'quarter' | 'year';\n\ntype SdkTimeAddUnit = TaskTimeAddUnit;\n\ntype TaskTimeAddUnit =\n | 'seconds'\n | 'minutes'\n | 'hours'\n | 'days'\n | 'weeks'\n | 'months'\n | 'years';\n\ntype SdkTimeBoundaryUnit = TaskTimeBoundaryUnit;\n\ntype TaskTimeBoundaryUnit = 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';\n\ntype SdkWorkflowTarget = TaskWorkflowTarget<WorkflowReference>;\n\ntype TaskWorkflowTarget<TReference extends string = string> =\n | TReference\n | { id: TReference; params?: Record<string, any> }\n | null\n | undefined;\n\ntype SdkBrandLogoOptions = TaskBrandLogoOptions;\n\ntype TaskBrandLogoOptions = {\n variants?: TaskBrandLogoVariant[];\n mode?: 'light' | 'dark';\n};\n\ntype TaskBrandLogoVariant = 'horizontal' | 'icon' | 'wordmark';\n\ntype SdkClassValue = TaskClassValue;\n\ntype TaskClassValue =\n | string\n | number\n | bigint\n | boolean\n | null\n | undefined\n | { [className: string]: any }\n | TaskClassValue[];\n\ntype SdkNavigateOptions = TaskNavigateOptions;\n\ntype SdkTiptapParseInput = TaskTiptapParseInput;\n\ntype TaskTiptapParseInput = {\n content?: any;\n html?: boolean;\n oneLine?: boolean;\n placeholder?: string | null;\n};\n\ntype SdkTextGenerateOptions = TaskTextGenerateOptions;\n\ntype TaskTextGenerateOptions = {\n length?: number;\n includeNumbers?: boolean;\n includeSpecial?: boolean;\n};\n\ntype SdkLocationValue = TaskLocationValue;\n\ntype TaskLocationValue = {\n point: { x: number | null; y: number | null } | null;\n display_name: string | null;\n line1: string | null;\n line2: string | null;\n city: string | null;\n county: string | null;\n state: string | null;\n country: string | null;\n country_code: string | null;\n postal_code: string | null;\n place_id: string | null;\n};\n\ntype SdkLocationFormatOptions = TaskLocationFormatOptions;\n\ntype TaskLocationFormatOptions = {\n /** Use `address` to skip `display_name` and always assemble the postal address. */\n format?: 'label' | 'address';\n};\n\ntype SdkPhoneFormatOptions = TaskPhoneFormatOptions;\n\ntype TaskPhoneFormatOptions = {\n format?: 'national' | 'international' | 'e164' | 'rfc3966';\n};\n\ntype SdkNumberFormatOptions = TaskNumberFormatOptions;\n\ntype TaskNumberFormatOptions = {\n decimals?: number;\n};\n\ntype SdkConfettiOptions = TaskConfettiOptions;\n\ntype TaskConfettiOptions = Record<string, any>;\n\ntype SdkClipboardCopyOptions = TaskClipboardCopyOptions;\n\ntype TaskClipboardCopyOptions = {\n message?: string;\n silent?: boolean;\n};\n\ntype SdkNotifyToastOptions = TaskNotifyToastOptions;\n\ntype TaskNotifyToastOptions = {\n variant?: TaskNotifyVariant;\n description?: string;\n};\n\ntype TaskNotifyVariant = 'default' | 'success' | 'error' | 'warning' | 'info';\n\ntype SdkNotifyVariant = TaskNotifyVariant;\n\ntype SdkBadgeVariant = 'default' | 'secondary' | 'success' | 'warning' | 'destructive';\n\ntype SdkDisplayFormatOptions = {\n /** Forwarded to the existing Page SDK phone formatter. Defaults to `national`. */\n phoneFormat?: 'national' | 'international' | 'e164' | 'rfc3966';\n /** Treat a numeric field value as a duration measured in this unit. */\n durationUnit?: 'milliseconds' | 'seconds' | 'minutes' | 'hours';\n};\n\ntype SdkRecordInput<TRecord extends ResourceRecord = ResourceRecord> =\n | TRecord\n | { fields?: TRecord | null }\n | null\n | undefined;\n\ntype SdkFieldValue<TRecord extends ResourceRecord, TSelector extends SdkFieldSelector> =\n TSelector extends SdkFieldSelector<infer TKey>\n ? TKey extends keyof TRecord\n ? TRecord[TKey]\n : any\n : any;\n\ntype SdkDisplayUtils = {\n /**\n * Format a resource field value for plain-text display. The field handle supplies\n * its type, configured date/time pattern, select options, and boolean labels.\n * Date, time, datetime, money, phone, location, select, number, boolean, content,\n * markdown, and scalar text values are supported; pass `durationUnit` to format a\n * numeric duration. A content value formats to its plain text, with one line for each\n * paragraph. A markdown value formats to its plain text with the syntax removed: one\n * line for each block, link text kept, and link URLs and images dropped. Existing SDK\n * date, time, money, number, phone, and location formatters remain the source of truth.\n *\n * @remarks\n * Configured date/time display patterns are forwarded to those formatters. Their\n * current locale and timezone behavior is preserved; use the dedicated formatter\n * utilities directly when you need their lower-level options. Nullish or malformed\n * values return `null` and report the standard SDK diagnostic.\n *\n * @example\n * const startsAt = Sdk.utils.display.format(events.fields.starts_at, event.starts_at);\n * const summary = Sdk.utils.display.format(events.fields.description, event.description);\n * const duration = Sdk.utils.display.format(events.fields.duration_minutes, event.duration_minutes, {\n * durationUnit: 'minutes'\n * });\n */\n format: (\n field: SdkResourceField | null | undefined,\n value: any,\n options?: SdkDisplayFormatOptions\n ) => string | null;\n\n /**\n * Build initials from the first `count` whitespace-delimited name parts.\n * `count` defaults to `2`. Nullish/blank names or an invalid count return `''`\n * and report the standard SDK diagnostic.\n *\n * @example\n * const fallback = Sdk.utils.display.initials('Ada Lovelace'); // 'AL'\n */\n initials: (name: string | null | undefined, count?: number) => string;\n\n /**\n * Map a select option's configured color to an `Sdk.Components.Badge` variant.\n * `green`/`emerald` \u2192 `success`, `yellow`/`orange` \u2192 `warning`, `red` \u2192\n * `destructive`, `blue`/`pink`/`purple` \u2192 `default`, and missing, custom,\n * `gray`, or `slate` colors \u2192 `secondary`.\n *\n * @example\n * const status = invoices.fields.status;\n * <Sdk.Components.Badge variant={Sdk.utils.display.badgeVariant(status, invoice.status)}>\n * {Sdk.utils.display.format(status, invoice.status)}\n * </Sdk.Components.Badge>\n */\n badgeVariant: (field: SdkResourceField | null | undefined, value: any) => SdkBadgeVariant;\n\n /**\n * Read a field from a flat SDK record or a legacy/external `{ fields: record }`\n * shape. Prefer the nested value when both shapes contain the key. Missing,\n * nullish, or malformed inputs return `null`; pass a field handle to retain the\n * workspace-generated record value type.\n *\n * @example\n * const name = Sdk.utils.display.fieldValue(student, students.fields.name);\n */\n fieldValue: <TRecord extends ResourceRecord, TSelector extends SdkFieldSelector>(\n record: SdkRecordInput<TRecord>,\n field: TSelector\n ) => SdkFieldValue<TRecord, TSelector> | null;\n};\n\ntype SdkApiErrorBody = TaskApiErrorBody;\n\ntype TaskApiErrorBody = {\n message?: string;\n errors?: Record<string, string[]>;\n [key: string]: any;\n};\n\ntype SdkUtils = Omit<TaskUtils, 'workspace' | 'workflow' | 'tiptap'> & {\n /**\n * Render TipTap JSON to plain text, or to sanitized HTML with `html: true`.\n *\n * @deprecated Render a content field with `Sdk.Components.FormFieldDisplay` or\n * `Sdk.Components.RecordFields`. Read its plain text with `Sdk.utils.display.format`.\n * @aiHidden Kept on the runtime SDK for existing pages, but omitted from the generated\n * SDK reference so agents stop using it.\n *\n * @example\n * const text = Sdk.utils.tiptap.parse({ content: record.notes });\n */\n tiptap: TaskUtils['tiptap'];\n /**\n * Format a count with the matching English singular or plural word.\n *\n * @example\n * Sdk.utils.pluralize(1, 'item', 'items'); // '1 item'\n * Sdk.utils.pluralize(2, 'item', 'items'); // '2 items'\n */\n pluralize: (count: number, one: string, other: string) => string;\n /**\n * Build URLs for published workflows by stable slug or ID.\n *\n * @example\n * const action = Sdk.utils.workflow.url('archive-student', { id: student.id });\n * if (action) await Sdk.api.post(action);\n */\n workflow: {\n /**\n * Build a workflow URL. Pass the target by stable slug or ID, or as\n * `{ id, params }`.\n *\n * @returns The published URL with route params interpolated, or `null` when\n * the target cannot be resolved.\n * @remarks Prefer the workflow slug from the workspace workflow catalog. The\n * workflow ID remains valid. Null/undefined targets, unpublished workflows,\n * and missing route params return `null` and warn in development/editor mode.\n */\n url: (target: SdkWorkflowTarget, params?: Record<string, any>) => string | null;\n };\n /**\n * Field-aware plain-text display helpers for formatting values, deriving avatar\n * initials and select badge variants, and reading flat or legacy nested records.\n *\n * @remarks\n * `format` delegates date, time, money, number, phone, and location rendering to the\n * existing SDK formatters so their current locale and timezone behavior remains consistent.\n * Nullish or malformed values return `null` with the standard SDK diagnostic;\n * `fieldValue` returns `null`.\n *\n * @example\n * const status = Sdk.utils.display.format(tasks.fields.status, task.status);\n */\n display: SdkDisplayUtils;\n};\n\ntype TaskUtils = {\n /**\n * Compose class names conditionally and resolve conflicting Tailwind utilities\n * with last-in-list precedence. Accepts strings, nested arrays, conditional\n * class maps, and falsy values.\n *\n * @remarks\n * Write every Tailwind utility as a complete static token in source, including\n * conditional alternatives. Use `{ 'bg-success': isActive }`, not\n * `` `bg-${color}` ``. Complete static tokens keep `tailwind-merge` conflict\n * behavior predictable, remain visible to Page design/source analysis, and avoid\n * relying on runtime CDN compilation for generated utilities.\n *\n * @example\n * const className = Sdk.utils.cn(\n * 'rounded-md px-2 text-sm',\n * isSelected && 'bg-primary text-primary-foreground',\n * ['transition-opacity', { 'opacity-50': isDisabled }]\n * );\n *\n * @example\n * Sdk.utils.cn('px-2', 'px-4'); // 'px-4'\n */\n cn: (...inputs: TaskClassValue[]) => string;\n /**\n * Build the request URL for a published workflow from the page's injected\n * `workflowRouteSettings`, keyed by the workflow's stable ID and indexed by slug.\n *\n * @remarks\n * Workflows are request targets: resolve the URL, guard its documented `null`\n * fallback, then pass the string to `Sdk.api`; or use\n * `Sdk.hooks.useWorkflowForm` for forms. To navigate the user somewhere, target a\n * page with `Sdk.utils.page.url()` / `Sdk.utils.page.navigate()`; the SDK has\n * no workflow-navigation helper, so to send the user *through* a workflow\n * (one that ends in a redirect), assign its URL to `window.location.href`.\n * Prefer the stable workflow slug from the workspace catalog. The stable ID\n * remains valid. Never hand-write `/api/workflows/...` endpoint strings; a\n * hardcoded path silently breaks when the workflow route changes.\n *\n * @example\n * // Call a workflow imperatively by slug (no form):\n * const action = Sdk.utils.workflow.url('approve-invoice');\n * if (action) await Sdk.api.post(action, { invoice_id: row.id });\n *\n * @example\n * // Send the user through a workflow that redirects:\n * const action = Sdk.utils.workflow.url('open-record', { id: row.id });\n * if (action) window.location.href = action;\n */\n workflow: {\n /**\n * Build a workflow URL. Pass the target by stable slug or ID (string) or\n * `{ id, params }`.\n *\n * @returns The published URL with route params interpolated, or `null` when\n * the target cannot be resolved.\n * @remarks Null/undefined targets, unpublished workflows, and missing route\n * params return `null` and warn in development/editor mode.\n * @example\n * const action = Sdk.utils.workflow.url({ id: 'apply-for-loan', params: { applicant_id: '123' } });\n */\n url: (target: TaskWorkflowTarget, params?: Record<string, any>) => string | null;\n };\n /**\n * Build the URL of a published page from the page's injected\n * `pageRouteSettings`, keyed by the page's stable id.\n *\n * @remarks\n * Pages are navigation targets: use the result as a link `href`, or navigate\n * to it directly with `Sdk.utils.page.navigate()`. You cannot submit a form\n * to a page \u2014 to submit data, target a workflow with `Sdk.utils.workflow.url()`\n * or `Sdk.hooks.useWorkflowForm`. **Always** address the page by its stable id\n * through this helper \u2014 never hand-write route strings; the id is\n * rename-safe and a hardcoded path silently breaks when the page's route\n * changes.\n *\n * @example\n * // Link to a page by id:\n * const href = Sdk.utils.page.url(INVOICE_DETAIL_PAGE_ID, { invoice: row.id });\n * return href ? <a href={href}>View invoice</a> : null;\n */\n page: {\n /**\n * Build an absolute URL on the page's selected domain, or the workspace's\n * primary domain when none is selected. Pass the target by stable id (string) or\n * `{ id, params }`.\n *\n * @returns The published URL with route params interpolated, or `null` when\n * the target or its domain cannot be resolved.\n * @remarks Null/undefined targets, unpublished pages, and missing route\n * params return `null` and warn in development/editor mode.\n * @example\n * const href = Sdk.utils.page.url({ id: INVOICE_DETAIL_PAGE_ID, params: { invoice: row.id } });\n */\n url: (target: TaskPageTarget, params?: Record<string, any>) => string | null;\n /**\n * Navigate to a published page by stable id. Builds the page URL from the\n * injected `pageRouteSettings` (like `Sdk.utils.page.url`) and then performs\n * an Inertia visit on the same origin. Other origins use normal browser\n * navigation in the current tab; `openInNewTab` opens a new tab. Pass the\n * target by stable id (string) or `{ id, params }`.\n *\n * @remarks Null/undefined targets, unpublished pages, and missing route\n * params are ignored after a warning in development/editor mode.\n * @example\n * Sdk.utils.page.navigate(STUDENTS_PAGE_ID);\n * @example\n * Sdk.utils.page.navigate({ id: INVOICE_DETAIL_PAGE_ID, params: { invoice: row.id } });\n */\n navigate: (target: TaskPageTarget, options?: TaskNavigateOptions) => void;\n };\n /**\n * Resolve a workspace file id to the URL that serves it, so authors never\n * hardcode absolute file URLs (e.g. `https://portal.example.com/files/<id>`).\n * The returned path is host-relative and hits the `files.proxy` route, which\n * redirects to the file's real storage/CDN location.\n *\n * @remarks\n * Pass the file by id (string) or `{ id }`. A `file_reference` record value is\n * always the File uuid string when set and `null` when unset \u2014 pass it straight in;\n * never check for URLs or `{ url }` objects, those shapes never occur.\n * Null/undefined/malformed input returns `null` and warns in\n * development/editor mode. Pair a non-null result with any element that takes\n * a URL \u2014 an `<img src>`, a download link, etc.\n *\n * Without options, the URL serves the original stored file. Use it for\n * downloads and links. In a page, `Sdk.Components.Image` and a plain `<img>`\n * with this URL select a size automatically: when the page has authorized\n * metadata for the file, the runtime adds `srcset` candidates from the file's\n * available widths and `sizes` from the rendered box. A file without that\n * metadata, or a private file, loads the original.\n *\n * `{ width }` requests one explicit output width in image pixels. The browser\n * does not apply device pixel ratio to it, and the runtime does not replace it\n * with automatic candidates. The server rounds up to an available width, and\n * returns the original when the file cannot be resized. An invalid width\n * returns the unsized URL and warns.\n *\n * `{ quality }` overrides the default transformation quality of 85 with an\n * integer from 1 to 100. It preserves automatic sizing unless `width` is also\n * set. Invalid quality values use the default and warn. Quality does not\n * transform a file without a width request and does not affect original delivery.\n *\n * @example\n * return <Sdk.Components.Image src={Sdk.utils.file(LOGO_FILE_ID)} alt=\"Logo\" />;\n *\n * @example\n * const logoUrl = Sdk.utils.file(LOGO_FILE_ID);\n * return logoUrl ? <img src={logoUrl} alt=\"Logo\" /> : null;\n *\n * @example\n * return <Sdk.Components.Image src={Sdk.utils.file(record.avatar)} alt={record.name} />;\n *\n * @example\n * const href = Sdk.utils.file({ id: attachment.id });\n *\n * @example\n * const thumbnailUrl = Sdk.utils.file(record.photo, { width: 320 });\n *\n * @example\n * return <Sdk.Components.Image src={Sdk.utils.file(record.photo, { quality: 90 })} alt=\"Photo\" />;\n */\n file: (target: TaskFileTarget, options?: TaskFileUrlOptions) => string | null;\n /**\n * Resolve the workspace Brand Kit logo to a servable URL. Walks the\n * brand-kit `logos.<variant>.<light|dark>` slots \u2014 each slot stores a File\n * uuid string or `null` \u2014 and returns the URL of the first uploaded logo,\n * so layouts never re-derive this walk by hand.\n *\n * @remarks\n * Default variant order is `horizontal` \u2192 `wordmark` \u2192 `icon` (widest\n * first); pass `variants` to prefer a different shape (e.g. `['icon']` for\n * a compact sidebar mark). The requested `mode` (default `light`) is\n * preferred, with the other mode as a fallback so a workspace that uploaded\n * only one asset still renders. Returns `null` when no logo is uploaded at\n * all \u2014 render your fallback mark in that case. Unsupported entries in a\n * mixed `variants` list are warned about and skipped. Null or malformed\n * options return `null` and warn in development/editor mode.\n *\n * @example\n * const logoUrl = Sdk.utils.brandLogo();\n * return logoUrl ? <img src={logoUrl} alt=\"Logo\" /> : <FallbackMark />;\n *\n * @example\n * // Compact icon mark for a collapsed sidebar, dark theme first:\n * const iconUrl = Sdk.utils.brandLogo({ variants: ['icon', 'horizontal'], mode: 'dark' });\n */\n brandLogo: (options?: TaskBrandLogoOptions | null) => string | null;\n /**\n * Non-throwing accessors for workspace field values, tolerant of missing keys\n * (returns a default instead of throwing).\n *\n * @example\n * const fromName = utils.workspace.value('outgoing_email_sender_name', 'Diagonal');\n */\n workspace: {\n /**\n * Read a single workspace field value by stable key, returning\n * `defaultValue` (default `null`) when the key is absent or nullish.\n *\n * @example\n * const currency = utils.workspace.value('default_currency', 'usd');\n */\n value: (key: string, defaultValue?: any) => any;\n /**\n * Return a shallow copy of all workspace field values keyed by their stable\n * keys.\n *\n * @example\n * const allFields = utils.workspace.values();\n */\n values: () => TaskWorkspaceFields;\n };\n /**\n * Currency-safe money math. Money values are `{ amount: number; currency: string }`\n * with `amount` in **minor units** (e.g. cents). Every operation preserves the\n * currency; mixing currencies in `add`/`subtract`/`sum` throws.\n *\n * @example\n * const subtotal = Sdk.utils.money.sum(items.map((i) => i.total));\n * const tax = Sdk.utils.money.percentage(subtotal, 8.875);\n * const grand = Sdk.utils.money.add(subtotal, tax);\n * return <div>{Sdk.utils.money.format(grand)}</div>;\n *\n * @remarks Math operations propagate nullish money inputs by returning `null`;\n * they never fabricate a zero in a fallback currency. A currency mismatch\n * between two real values remains a programmer error and throws.\n */\n money: {\n /**\n * Add two money values. Nullish operands return `null`; two real values with\n * different currencies throw.\n * @example\n * const total = Sdk.utils.money.add({ amount: 1000, currency: 'usd' }, { amount: 250, currency: 'usd' });\n */\n add: (a: TaskMoneyInput, b: TaskMoneyInput) => TaskMoney | null;\n /**\n * Subtract `b` from `a`. Nullish operands return `null`; two real values with\n * different currencies throw.\n * @example\n * const change = Sdk.utils.money.subtract({ amount: 2000, currency: 'usd' }, { amount: 1500, currency: 'usd' });\n */\n subtract: (a: TaskMoneyInput, b: TaskMoneyInput) => TaskMoney | null;\n /**\n * Multiply a money value by a plain number, rounding to whole minor units.\n * A nullish money value returns `null`.\n * @example\n * const lineTotal = Sdk.utils.money.multiply({ amount: 1299, currency: 'usd' }, 3);\n */\n multiply: (value: TaskMoneyInput, factor: number) => TaskMoney | null;\n /**\n * Divide a money value by a plain number, rounding to whole minor units.\n * Dividing by `0` yields a zero amount in the same currency with a diagnostic\n * warning. A nullish money value returns `null`.\n * @example\n * const perPerson = Sdk.utils.money.divide({ amount: 10000, currency: 'usd' }, 4);\n */\n divide: (value: TaskMoneyInput, divisor: number) => TaskMoney | null;\n /**\n * Take a percentage of a money value (e.g. `8.875` for 8.875%), rounding to\n * whole minor units. A nullish money value returns `null`.\n * @example\n * const tax = Sdk.utils.money.percentage({ amount: 10000, currency: 'usd' }, 8.875);\n */\n percentage: (value: TaskMoneyInput, percent: number) => TaskMoney | null;\n /**\n * Sum a list of money values. Nullish/non-money entries are skipped with a\n * diagnostic warning; an empty list falls back to the workspace\n * `default_currency` (or `usd`) with a zero amount. Throws on mixed\n * currencies.\n * @example\n * const subtotal = Sdk.utils.money.sum(rows.map((r) => r.amount));\n */\n sum: (values: TaskMoneyInput[]) => TaskMoney;\n /**\n * Format a money value for display via `Intl.NumberFormat` currency style.\n * Nullish or malformed input returns `null` and warns in\n * development/editor mode.\n * @returns A localized currency string, e.g. `\"$1,234.50\"`.\n * @example\n * const label = Sdk.utils.money.format({ amount: 123450, currency: 'usd' });\n */\n format: (value: TaskMoneyInput, options?: TaskMoneyFormatOptions) => string | null;\n };\n /**\n * Coerce raw values into Diagonal-shaped objects.\n *\n * @example\n * const price = Sdk.utils.cast.toMoney(19.99, 'usd');\n */\n cast: {\n /**\n * Convert a number/string into a money value `{ amount, currency }` in minor\n * units (currency defaults to `usd`). Already-money values pass through\n * unchanged; empty/boolean/non-numeric input yields a zero amount.\n * @example\n * const amount = Sdk.utils.cast.toMoney('19.99'); // { amount: 1999, currency: 'usd' }\n */\n toMoney: (value: any, currency?: string) => TaskMoney;\n };\n /**\n * Format location field values as display text.\n *\n * @example\n * const address = Sdk.utils.location.format(business.address); // '123 Main St, Austin, TX 78701'\n */\n location: {\n /**\n * Format a location value as a single-line address. The establishment name\n * (`display_name`) wins when the place has one; otherwise the street, city,\n * state, postal code, and country parts are joined with `', '`. Pass\n * `{ format: 'address' }` to skip `display_name` and always get the assembled\n * postal address. Nullish or malformed values return `null` and warn\n * in development/editor mode.\n * @example\n * const venue = Sdk.utils.location.format(event.venue);\n * const postal = Sdk.utils.location.format(event.venue, { format: 'address' });\n */\n format: (value: any, options?: TaskLocationFormatOptions) => string | null;\n };\n /**\n * Format phone numbers for display.\n *\n * @example\n * const display = Sdk.utils.phone.format('+14155552671'); // '+1 415 555 2671'\n */\n phone: {\n /**\n * Format a phone number string. Default format is `international`. Nullish,\n * invalid, or unparseable numbers return `null` and warn in\n * development/editor mode.\n * @example\n * const national = Sdk.utils.phone.format('+14155552671', { format: 'national' });\n */\n format: (value: any, options?: TaskPhoneFormatOptions) => string | null;\n };\n /**\n * Format plain numbers for display using the browser locale.\n *\n * @example\n * const display = Sdk.utils.number.format(1234.5); // '1,234.50'\n */\n number: {\n /**\n * Format a number (or numeric string) with `Intl.NumberFormat` and the\n * browser locale. `decimals` defaults to `2`. Nullish, non-finite, or\n * otherwise malformed input returns `null` and warns in\n * development/editor mode.\n * @example\n * const whole = Sdk.utils.number.format(1234.5, { decimals: 0 }); // '1,235'\n */\n format: (value: any, options?: TaskNumberFormatOptions) => string | null;\n };\n /**\n * Date math on ISO date strings (`yyyy-MM-dd`), powered by `date-fns`. Use\n * this for calendar-day operations; for full datetimes use `Sdk.utils.time`.\n *\n * @example\n * const dueOn = Sdk.utils.date.add(Sdk.utils.date.current(), 30, 'days');\n */\n date: {\n /**\n * Add `amount` of `unit` to a date and return a `yyyy-MM-dd` string. Nullish\n * or empty input returns `null`.\n * @example\n * const next = Sdk.utils.date.add('2026-01-01', 2, 'weeks'); // '2026-01-15'\n */\n add: (value: string | null | undefined, amount: number, unit: TaskDateAddUnit) => string | null;\n /**\n * Subtract `amount` of `unit` from a date and return a `yyyy-MM-dd` string.\n * Nullish or empty input returns `null`.\n * @example\n * const prior = Sdk.utils.date.subtract('2026-01-15', 1, 'months'); // '2025-12-15'\n */\n subtract: (\n value: string | null | undefined,\n amount: number,\n unit: TaskDateAddUnit\n ) => string | null;\n /**\n * Difference between two dates (`b - a`) in `unit` (default `days`). Nullish\n * or empty input returns `null`.\n * @returns A signed whole number; negative when `b` is before `a`.\n * @example\n * const days = Sdk.utils.date.diff('2026-01-01', '2026-01-31'); // 30\n */\n diff: (\n a: string | null | undefined,\n b: string | null | undefined,\n unit?: TaskDateAddUnit\n ) => number | null;\n /**\n * Start of the given calendar boundary as `yyyy-MM-dd`. Weeks start on\n * Monday. Nullish or empty input returns `null`.\n * @example\n * const monday = Sdk.utils.date.startOf('2026-01-15', 'week');\n */\n startOf: (value: string | null | undefined, unit: TaskDateBoundaryUnit) => string | null;\n /**\n * End of the given calendar boundary as `yyyy-MM-dd`. Weeks end on Sunday.\n * Nullish or empty input returns `null`.\n * @example\n * const monthEnd = Sdk.utils.date.endOf('2026-01-15', 'month'); // '2026-01-31'\n */\n endOf: (value: string | null | undefined, unit: TaskDateBoundaryUnit) => string | null;\n /**\n * Format a date with a `date-fns` pattern (default `yyyy-MM-dd`). Nullish,\n * empty, or malformed input returns `null` and warns in\n * development/editor mode.\n * @example\n * const label = Sdk.utils.date.format('2026-01-15', 'MMM d, yyyy'); // 'Jan 15, 2026'\n */\n format: (value: string | null | undefined, format?: string) => string | null;\n /**\n * Today's date as a `yyyy-MM-dd` string.\n * @example\n * const today = Sdk.utils.date.current();\n */\n current: () => string;\n };\n /**\n * Datetime math on ISO 8601 strings, powered by `date-fns`. Mirrors\n * `Sdk.utils.date` but adds sub-day units (seconds/minutes/hours) and outputs\n * full datetimes.\n *\n * Stored datetimes are always UTC, and `format` renders them in the workspace\n * timezone by default. Pass an IANA timezone to `format` to render in a\n * different one. Never add or subtract fixed offsets to shift displayed times.\n *\n * @example\n * const reminder = Sdk.utils.time.add(Sdk.utils.time.current(), 90, 'minutes');\n */\n time: {\n /**\n * Add `amount` of `unit` to a datetime and return an ISO 8601 string. Nullish\n * or empty input returns `null`.\n * @example\n * const later = Sdk.utils.time.add('2026-01-01T09:00:00Z', 3, 'hours');\n */\n add: (value: string | null | undefined, amount: number, unit: TaskTimeAddUnit) => string | null;\n /**\n * Subtract `amount` of `unit` from a datetime and return an ISO 8601 string.\n * Nullish or empty input returns `null`.\n * @example\n * const earlier = Sdk.utils.time.subtract('2026-01-01T09:00:00Z', 30, 'minutes');\n */\n subtract: (\n value: string | null | undefined,\n amount: number,\n unit: TaskTimeAddUnit\n ) => string | null;\n /**\n * Difference between two datetimes (`b - a`) in `unit` (default `minutes`).\n * Nullish or empty input returns `null`.\n * @returns A signed whole number; negative when `b` is before `a`.\n * @example\n * const minutes = Sdk.utils.time.diff('2026-01-01T09:00:00Z', '2026-01-01T10:30:00Z'); // 90\n */\n diff: (\n a: string | null | undefined,\n b: string | null | undefined,\n unit?: TaskTimeAddUnit\n ) => number | null;\n /**\n * Start of the given datetime boundary as an ISO 8601 string. Weeks start on\n * Monday. Nullish or empty input returns `null`.\n * @example\n * const dayStart = Sdk.utils.time.startOf('2026-01-15T13:45:00Z', 'day');\n */\n startOf: (value: string | null | undefined, unit: TaskTimeBoundaryUnit) => string | null;\n /**\n * End of the given datetime boundary as an ISO 8601 string. Weeks end on\n * Sunday. Nullish or empty input returns `null`.\n * @example\n * const hourEnd = Sdk.utils.time.endOf('2026-01-15T13:45:00Z', 'hour');\n */\n endOf: (value: string | null | undefined, unit: TaskTimeBoundaryUnit) => string | null;\n /**\n * Format a datetime with a `date-fns` pattern (default\n * `yyyy-MM-dd'T'HH:mm:ssXXX`). In pages the default rendering timezone is\n * the workspace timezone; pass `timezone` to render in a different one.\n * Never add or subtract fixed offsets for display. Nullish or malformed\n * input returns `null` and warns in development/editor mode.\n * @example\n * const label = Sdk.utils.time.format('2026-01-15T13:45:00Z', 'MMM d, h:mm a', Sdk.utils.timezone.get());\n */\n format: (value: string | null | undefined, format?: string, timezone?: string) => string | null;\n /**\n * The current datetime as an ISO 8601 string.\n * @example\n * const now = Sdk.utils.time.current();\n */\n current: () => string;\n };\n /**\n * Timezone display + interpretation helpers. Timezone names use IANA values\n * such as `America/New_York`.\n *\n * Use `Sdk.utils.timezone.get()` in pages to read the workspace timezone.\n * `Sdk.utils.time.format` already defaults to it; pass it explicitly to keep\n * the intent visible at the call site, and pass a different IANA zone only\n * when the page genuinely renders one. Use `interpret` to convert\n * user-entered wall-clock datetimes into UTC for storage.\n *\n * @example\n * const label = Sdk.utils.timezone.format('America/New_York', 'abbreviation'); // 'EST' / 'EDT'\n * const tz = Sdk.utils.timezone.get();\n * const utc = Sdk.utils.timezone.interpret('2026-01-15T09:00:00', tz);\n */\n timezone: {\n /**\n * Produce a display label for a timezone. `full` (default) gives a name +\n * offset, `offset` gives `UTC\xB1HH:mm`, `abbreviation` gives e.g. `EDT`, and\n * `city` gives the trailing city name. Nullish or invalid input returns\n * `null` and warns in development/editor mode.\n * @example\n * const city = Sdk.utils.timezone.format('America/New_York', 'city'); // 'New York'\n */\n format: (\n timezone: string | null | undefined,\n format?: 'full' | 'offset' | 'abbreviation' | 'city'\n ) => string | null;\n /**\n * Interpret a wall-clock local date/datetime as occurring in `timezone` and\n * return the equivalent UTC ISO 8601 string. A nullish or empty value\n * propagates as `null`; a nullish/empty timezone returns `null` with a\n * warning. Malformed non-null values or non-empty invalid timezone names\n * still throw.\n * @example\n * const utc = Sdk.utils.timezone.interpret('2026-01-15T09:00:00', 'America/New_York');\n */\n interpret: (value: any, timezone: string | null | undefined) => string | null;\n /**\n * Return the workspace's default IANA timezone. In non-page contexts with no\n * workspace, this falls back to UTC.\n * @example\n * const tz = Sdk.utils.timezone.get();\n */\n get: () => string;\n };\n /**\n * Render rich-text fields stored as TipTap JSON to a string.\n *\n * @example\n * const text = Sdk.utils.tiptap.parse({ content: record.notes });\n */\n tiptap: {\n /**\n * Convert TipTap JSON to plain text (default) or HTML (`html: true`).\n * `oneLine` collapses to a single line; `placeholder` is substituted when\n * empty. Empty/falsy content returns an empty string.\n * @example\n * const html = Sdk.utils.tiptap.parse({ content: record.body, html: true });\n */\n parse: (value: TaskTiptapParseInput) => string;\n };\n /**\n * Generate identifiers and random text.\n *\n * @example\n * const id = Sdk.utils.text.randomUuid();\n */\n text: {\n /**\n * Generate a time-ordered UUID (v7).\n * @example\n * const id = Sdk.utils.text.randomUuid();\n */\n randomUuid: () => string;\n /**\n * Generate a random string. Always includes letters; numbers and special\n * characters are opt-in. Defaults: `length=16`, `includeNumbers=false`,\n * `includeSpecial=false`.\n * @example\n * const token = Sdk.utils.text.generate({ length: 24, includeNumbers: true });\n */\n generate: (options?: TaskTextGenerateOptions) => string;\n };\n /**\n * Copy text to the system clipboard.\n *\n * @example\n * <Sdk.Components.Button onClick={() => Sdk.utils.clipboard.copy(record.id)}>\n * Copy ID\n * </Sdk.Components.Button>\n */\n clipboard: {\n /**\n * Write a string to the system clipboard and show a success/failure toast.\n *\n * Pass `options.message` to customize the success toast, or `options.silent`\n * to suppress it. The failure toast always fires \u2014 a denied or blocked write\n * is otherwise invisible to the caller.\n *\n * @example\n * Sdk.utils.clipboard.copy('https://example.com/share/abc');\n * @example\n * Sdk.utils.clipboard.copy(record.id, { message: 'Record ID copied' });\n */\n copy: (value: string, options?: TaskClipboardCopyOptions) => Promise<void> | void;\n };\n /**\n * Play a celebratory confetti animation \u2014 handy as an `onSuccess` callback.\n *\n * @example\n * const { form } = Sdk.hooks.useWorkflowForm({\n * workflowId: SEND_INVITE_WORKFLOW_ID,\n * initialValues: { email: '' },\n * onSuccess: () => Sdk.utils.confetti.trigger()\n * });\n */\n confetti: {\n /**\n * Trigger the confetti animation.\n * @example\n * Sdk.utils.confetti.trigger();\n */\n trigger: (options?: TaskConfettiOptions) => void;\n };\n /**\n * Show a toast notification \u2014 general-purpose UI feedback for any event\n * (a saved form, a validation error, a background action completing).\n *\n * @example\n * Sdk.utils.notify.toast('Settings saved');\n */\n notify: {\n /**\n * Display a toast. Pass `options.variant` to color it\n * (`success` | `error` | `warning` | `info`) and `options.description` for a\n * second line of supporting text.\n * @example\n * Sdk.utils.notify.toast('Invite sent', { variant: 'success' });\n */\n toast: (message: string, options?: TaskNotifyToastOptions) => void;\n };\n /**\n * Evaluate a saved-filter predicate against a record in the browser \u2014 the same\n * `{ operator, conditions: [{ left, operator, right }, ...] }` shape produced\n * by the filter builder UI. Useful for conditionally showing/hiding UI.\n *\n * @example\n * const shouldShow = Sdk.utils.guard.filterCheck(record, savedFilter);\n * if (!shouldShow) return null;\n */\n guard: {\n /**\n * Run a filter predicate against `input` and return whether it matches. When\n * a condition's `left` is a string it is resolved as a (dot-notation) field\n * path on `input`; other `left` values are used as-is.\n * @example\n * const isActive = Sdk.utils.guard.filterCheck(record, {\n * operator: 'and',\n * conditions: [{ left: 'status', operator: 'eq', right: 'active' }]\n * });\n */\n filterCheck: (input: any, predicate: any) => boolean;\n };\n /**\n * Interpret failures from `Sdk.api`, resource operations, and SDK data hooks\n * without comparing raw HTTP status codes.\n *\n * @example\n * try {\n * await Sdk.resource(INVOICES_RESOURCE_ID).get(recordId);\n * } catch (error) {\n * if (Sdk.utils.error.isForbidden(error)) return showAccessDenied();\n * if (Sdk.utils.error.isNotFound(error)) return showMissingRecord();\n * }\n */\n error: {\n /** Return the parsed API error body, or `null` when none is available. */\n extractApiErrorBody: (error: any) => TaskApiErrorBody | null;\n /** Return the API body's message, or the supplied fallback. */\n apiErrorMessage: (error: any, fallback?: string) => string;\n /** Return whether the request failed field validation (HTTP 422). */\n isValidationError: (error: any) => boolean;\n /** Return whether the request failed because of a domain conflict (HTTP 409). */\n isConflict: (error: any) => boolean;\n /** Return whether the acting identity is not permitted to access the target (HTTP 403). */\n isForbidden: (error: any) => boolean;\n /** Return whether the requested target does not exist (HTTP 404). */\n isNotFound: (error: any) => boolean;\n };\n};\n\ntype TaskPageTarget =\n | string\n | { id: string; params?: Record<string, any> }\n | null\n | undefined;\n\ntype TaskFileTarget = string | { id: string } | null | undefined;\n\ntype TaskFileUrlOptions = {\n /**\n * Requested output width in image pixels, not CSS pixels. Must be a positive\n * integer. The server returns the narrowest available width that covers it,\n * or the original file when the file cannot be resized.\n */\n width?: number;\n\n /**\n * Quality for transformed public images, from 1 to 100. Defaults to 85.\n * Without width, page images keep automatic responsive sizing. Original\n * delivery ignores quality. Invalid values use the default and warn.\n */\n quality?: number;\n};\n\ntype SdkHelpers = {\n /**\n * Read a value from the page's input data by key. Dot paths reach nested\n * values (`'record.id'`). Returns `defaultValue` (default `null`) when the\n * key is absent. Optional page inputs require an explicit, type-compatible\n * default value.\n */\n getInput<TKey extends string>(\n key: PageContextRequiredKey<PageInputCatalog, TKey>\n ): PageContextValue<PageInputCatalog, TKey>;\n getInput<TKey extends string>(\n key: PageContextKey<PageInputCatalog, TKey>,\n defaultValue: PageContextPresentValue<PageInputCatalog, TKey>\n ): PageContextPresentValue<PageInputCatalog, TKey>;\n\n /**\n * Alias of {@link SdkHelpers.getInput} for reads inside the rendered\n * component. The page re-renders with fresh input data, so component reads\n * stay current.\n */\n useInput<TKey extends string>(\n key: PageContextRequiredKey<PageInputCatalog, TKey>\n ): PageContextValue<PageInputCatalog, TKey>;\n useInput<TKey extends string>(\n key: PageContextKey<PageInputCatalog, TKey>,\n defaultValue: PageContextPresentValue<PageInputCatalog, TKey>\n ): PageContextPresentValue<PageInputCatalog, TKey>;\n\n /**\n * Every raw route parameter from the current request URL, keyed by param\n * name. Always an object \u2014 `{}` when the page's route has no params.\n */\n getRouteParams: () => keyof PageRouteParamCatalog extends never\n ? Record<string, any>\n : PageRouteParamCatalog;\n\n /**\n * A single raw route parameter value \u2014 e.g. the `{member}` URL segment as a\n * string. Available even when the param has no route binding configured.\n */\n getRouteParam<TKey extends string>(\n key: PageContextKey<PageRouteParamCatalog, TKey>,\n defaultValue?: any\n ): PageContextValue<PageRouteParamCatalog, TKey>;\n\n /**\n * The hydrated record bound to a route param via a route binding, or\n * `undefined` when that param has no binding configured. This is the\n * resolved resource that {@link SdkHelpers.getRouteParam} only exposes the\n * raw value of.\n */\n getRouteBinding<TKey extends string>(\n key: PageContextKey<PageRouteBindingCatalog, TKey>,\n defaultValue?: any\n ): PageContextValue<PageRouteBindingCatalog, TKey>;\n};\n\ntype PageContextRequiredKey<TCatalog, TKey extends string> = keyof TCatalog extends never\n ? TKey\n : string extends TKey\n ? TKey\n : TKey extends Extract<keyof TCatalog, string>\n ? {} extends Pick<TCatalog, Extract<TKey, keyof TCatalog>>\n ? never\n : TKey\n : never;\n\ninterface PageInputCatalog {}\n\ntype PageContextValue<TCatalog, TKey extends string> = keyof TCatalog extends never\n ? any\n : TKey extends keyof TCatalog\n ? TCatalog[TKey]\n : any;\n\ntype PageContextKey<TCatalog, TKey extends string> = keyof TCatalog extends never\n ? TKey\n : TKey extends Extract<keyof TCatalog, string>\n ? TKey\n : string extends TKey\n ? TKey\n : never;\n\ntype PageContextPresentValue<TCatalog, TKey extends string> = Exclude<\n PageContextValue<TCatalog, TKey>,\n null | undefined\n>;\n\ninterface PageRouteParamCatalog {}\n\ninterface PageRouteBindingCatalog {}\n\ntype SdkContract = {\n /** Namespace of built-in Diagonal SDK components (`Sdk.Components.Button`, `Card`, \u2026). */\n Components: ComponentRegistry;\n\n /**\n * Drag-and-drop primitives and high-level sortable collections. Use `SortableList` for one\n * ordered collection and `SortableGroups` for movement within and between groups. For\n * sortable resource records, pass the query's records, resource handle, and stored Number\n * field. The high-level components own the\n * sensors, keyboard controls, drag handles, item transforms, changed-index calculation,\n * optimistic update, persistence state, and rollback. For resource records, pass the\n * resource handle and stored Number field; the component sorts nulls last, breaks ties\n * by record id, and bulk-updates only records whose positions changed. No resource\n * capability or dedicated reorder endpoint is required.\n *\n * `reorder(items, activeId, overId)` is the same pure move calculation without UI. It\n * returns both the complete `items` array and only the `changes` whose indices moved.\n * Positions are zero-based. Every other member exposes the underlying dnd-kit primitive\n * for custom drag-and-drop experiences outside sortable resource records.\n *\n * @example\n * ```tsx\n * const students = Sdk.resource('students');\n *\n * return function StudentRanking() {\n * const { data } = Sdk.hooks.useRecords(students, { sort: 'rank', perPage: 100 });\n *\n * return (\n * <Sdk.Dnd.SortableList\n * items={data}\n * resource={students}\n * sortField={students.fields.rank}\n * renderItem={(student) => <span>{student.name}</span>}\n * />\n * );\n * }\n * ```\n */\n Dnd: SdkDnd;\n\n /**\n * Namespace of reactive data hooks for analytics, API requests, URL query parameters,\n * reading and writing records, building tables and calendars, and reading app chrome\n * state (sidebar, theme). See {@link SdkHooks} for the full interface.\n *\n * **Convention:** hooks belong here, not at the top level of `SdkContract`.\n * `useForm` and the deprecated top-level calendar aliases remain for backwards compatibility.\n *\n * @example\n * ```tsx\n * const students = Sdk.resource('...');\n * const { data } = Sdk.hooks.useRecord(students, recordId);\n * ```\n *\n * @example\n * ```tsx\n * function PaidInvoicesTile() {\n * const invoices = Sdk.resource('...');\n * const { data, isLoading, isError } = Sdk.hooks.useAnalytics(invoices, 'kpi', {\n * metrics: [{ name: 'paid_count', operation: 'count' }]\n * });\n *\n * if (isLoading) return <Sdk.Components.Skeleton />;\n * if (isError) return <Sdk.Components.Alert>Could not load invoices.</Sdk.Components.Alert>;\n * return <Sdk.Components.Card>{Sdk.utils.number.format(data?.paid_count ?? 0, { decimals: 0 })} paid</Sdk.Components.Card>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * const [params, setParams] = Sdk.hooks.useQueryParams({\n * search: { default: '' },\n * page: { type: 'number', default: 1 },\n * active: { type: 'boolean', default: true },\n * tags: { type: 'array', default: [] }\n * });\n * ```\n */\n hooks: SdkHooks;\n\n /**\n * Namespace of page input + route parameter readers \u2014 `getInput`, `useInput`,\n * `getRouteParams`, `getRouteParam`, and `getRouteBinding`. See {@link SdkHelpers}.\n *\n * @remarks\n * `getInput(key, defaultValue?)` reads a value from the page's input data\n * (workflow-provided values, route params, and route-binding records), supporting dot\n * paths such as `'record.id'`; `useInput` is its alias for reads inside the rendered\n * component. For a page routed at `/members/{member}`, `getRouteParam('member')`\n * returns the raw URL segment, `getRouteParams()` every raw param keyed by name, and\n * `getRouteBinding('member')` the hydrated record when a route binding is configured\n * (`undefined` otherwise). Raw params also remain readable via\n * `getInput('params.member')`, but prefer the dedicated route helpers. Never derive\n * route segments from `window.location` \u2014 the editor preview serves drafts at\n * `/preview/pages/{id}` with params in the query string, so pathname parsing renders the\n * page's not-found/empty state in preview even though the params were delivered. `params`\n * is a reserved key in page output; a page output variable named `params` takes precedence.\n * In the page editor and save-time gate, literal input/param/binding keys and bound-record\n * fields are inferred from the selected page. Runtime-computed string keys retain the\n * permissive dynamic fallback.\n *\n * @example\n * ```tsx\n * return function Page() {\n * const recordId = Sdk.helpers.useInput('record_id');\n * const member = Sdk.helpers.getRouteBinding('member');\n *\n * return <p>{member ? `Editing ${member.name}` : `Record ${recordId}`}</p>;\n * };\n * ```\n */\n helpers: SdkHelpers;\n\n /** Namespace of workspace-authored shared components (`Sdk.Custom.StatusBadge`, \u2026). */\n Custom: CustomComponentRegistry;\n\n /**\n * Look up a workspace resource by its portable slug or UUID and return a\n * {@link SdkResource} bundling metadata (`fields`, `display_field_key`, \u2026) with the\n * record operations `list`, `search`, bounded `searchAll`, `count`, `get`, `create`,\n * `update`, `updateMany`, `delete`, and `restore` (plus `invalidate` to refresh cached record data),\n * the record API endpoint URLs under `urls` (`index`, `search`, `count`, `store`,\n * `bulk`, `show`, `update`, `delete`, `restore`) \u2014 see {@link ResourceUrls} \u2014 and aggregate\n * analytics under `analytics` (`kpi`, `timeSeries`, `leaderboard`, `distribution`) \u2014\n * see {@link SdkResourceAnalytics}.\n * Use `can(operation)` to check the resource's server-resolved policy scope before\n * rendering controls for those operations.\n *\n * @remarks\n * `fields` is a keyed object (`resource.fields.<key>`), not an array \u2014 `.map()`/`.filter()`\n * on it fails to compile, so name each field key you use.\n * Prefer the resource slug shown in the workspace SDK catalog (for example, `students`).\n * Slugs make page source portable across workspaces and packages with matching resource\n * names. UUIDs remain supported as a fallback. Display names are not accepted. Throws if\n * the slug or UUID is unknown; the error lists the available slugs and ids.\n * The SDK renames `perPage` \u2192 `per_page`, `sort` \u2192 `$sort` (accepting `'name'`,\n * `'-created_at'`, `['a', '-b']`, or `{ created_at: -1 }`), and `filter` \u2192 `$filter`\n * before calling the records API. The rename does NOT restructure `filter`: pass a\n * structured filter tree (see {@link ResourceSearchBody}) \u2014 flat maps like\n * `{ status: 'paid' }` are rejected with a 422. Filtering and sorting are `search`-only;\n * `list` throws if given them, because the list endpoint would ignore them. Use\n * `count` for exact totals without rows and `analytics` for server-side numeric\n * aggregates. `searchAll` is only for a bounded client-side aggregate that needs row\n * values; its required `maxRecords` cap warns when the result is incomplete. Resource\n * ordering is explicit: query the desired sort field and pass that same field plus the\n * resource handle to `Sdk.Dnd.SortableList`. Use the bundled handle methods directly;\n * do not monkey-patch or extend handles with your own helpers.\n * `can(operation)` checks resource-policy access, not permission membership; use\n * {@link SdkContract.permissions} for permission ids and slugs. Both client-side\n * helpers are UI conveniences and never replace server-side enforcement.\n * On public pages, `resource.get(recordId)` rejects with forbidden when the guest\n * lacks record policy access and not-found only when the record is absent. Classify\n * the caught error with `Sdk.utils.error.isForbidden` / `isNotFound`; do not infer\n * not-found from missing data.\n * On a Booking resource, `create` and `update` reject a booking that does not fit with a\n * 422 whose message is under `$capacity`: read it with\n * `Sdk.utils.error.extractApiErrorBody(error)?.errors?.$capacity?.[0]` and show it. A\n * repeating booking whose first date fits resolves, and its `meta.series.waiting` lists each\n * later date that waits for rescheduling, so the page can show \"4 dates need rescheduling\"\n * with an action for each `record_id`. `meta.booking.replayed` marks a retried create that returned the first booking.\n *\n * @returns A {@link SdkResource} \u2014 resource metadata plus the record CRUD methods.\n *\n * @example\n * ```tsx\n * return function Page() {\n * const students = Sdk.resource('students');\n * const [rows, setRows] = React.useState([]);\n *\n * React.useEffect(() => {\n * students.list({ perPage: 50 }).then((r) => setRows(r.data ?? []));\n * }, []);\n *\n * return (\n * <Sdk.Components.List\n * items={rows.map((r) => ({ id: r.id, title: r[students.display_field_key] }))}\n * />\n * );\n * };\n * ```\n *\n * @example\n * ```ts\n * const result = await Sdk.resource('invoices').create({\n * amount: 12500,\n * currency: 'usd',\n * client_id: '...'\n * });\n * ```\n *\n * @example\n * ```ts\n * // Filtered search \u2014 `filter` must be a structured tree\n * // `{ operator: 'and' | 'or', conditions: [...] }` of leaf conditions\n * // (`{ field, operator, value }`) and/or nested groups. Operators are the\n * // symbol or snake_case enum values (`=`, `!=`, `>=`, `contains`, `in`,\n * // `between`, `is_null`, \u2026); flat maps like `{ status: 'paid' }` and word\n * // operators like `eq` are rejected with a 422.\n * const invoices = Sdk.resource('invoices');\n * const paid = await invoices.search({\n * filter: {\n * operator: 'and',\n * conditions: [\n * { field: 'status', operator: '=', value: 'paid' },\n * { field: 'total', operator: '>=', value: 100 }\n * ]\n * },\n * sort: '-created_at',\n * perPage: 25\n * });\n * ```\n *\n * @example\n * ```ts\n * const invoices = Sdk.resource('019286e1-7c0a-7a3e-8f21-3b9d5e6a1c4d');\n * const filter = {\n * operator: 'and',\n * conditions: [{ field: 'status', operator: '=', value: 'paid' }]\n * };\n * const paidCount = await invoices.count({ filter });\n * const paidRows = await invoices.searchAll({ filter, maxRecords: 500 });\n * ```\n *\n * @example\n * ```ts\n * // Restore a soft-deleted record.\n * const invoices = Sdk.resource('invoices');\n * await invoices.restore('019286e1-9f2c-7b10-bc44-1a2b3c4d5e6f');\n * ```\n *\n * @example\n * ```ts\n * // Aggregate analytics \u2014 headline KPIs plus a monthly time series, without\n * // building a workflow per chart. See {@link SdkResourceAnalytics}.\n * const invoices = Sdk.resource('invoices');\n * const kpis = await invoices.analytics.kpi({\n * metrics: [{ name: 'revenue', operation: 'sum', field: 'amount' }]\n * });\n * const monthly = await invoices.analytics.timeSeries({\n * date_field: 'created_at',\n * granularity: 'month',\n * metrics: [{ name: 'revenue', operation: 'sum', field: 'amount' }]\n * });\n * ```\n */\n resource<TId extends string>(id: DynamicResourceId<TId>): SdkResource;\n resource<K extends keyof ResourceCatalog>(id: K): ResourceCatalog[K];\n resource(id: string): SdkResource;\n\n /**\n * A page of this workspace by its stable id. The code editor shows and\n * completes the page's name in this position and stores the id, so a renamed\n * page keeps every reference. Use the handle where a page id or URL is\n * expected.\n *\n * @example\n * const invoices = Sdk.page(INVOICES_PAGE_ID);\n * return <a href={invoices.url() ?? '#'}>Invoices</a>;\n * @example\n * Sdk.page(INVOICE_DETAIL_PAGE_ID).navigate({ invoice: row.id });\n * @example\n * <Sdk.Components.PageLink pageId={Sdk.page(INVOICES_PAGE_ID).id}>Invoices</Sdk.Components.PageLink>\n */\n page(id: PageReference): SdkPageHandle;\n\n /**\n * Access a workspace resource by id or slug, or `null` when the current viewer cannot\n * access it.\n *\n * @remarks\n * The nullable twin of {@link SdkContract.resource}. Reach for it only when access is\n * genuinely viewer-dependent (a public page that also serves logged-in members) and the\n * code path is imperative. For data hooks, pass the id or slug straight to\n * `Sdk.hooks.useRecords` / `useRecord` / `useInfiniteRecords` instead; they stay idle\n * for viewers without access and need no null check.\n *\n * @example\n * ```ts\n * const contacts = Sdk.resourceOrNull('business-contacts');\n * if (contacts) {\n * await contacts.create({ status: 'pending' });\n * }\n * ```\n */\n resourceOrNull<TId extends string>(id: DynamicResourceId<TId>): SdkResource | null;\n resourceOrNull<K extends keyof ResourceCatalog>(id: K): ResourceCatalog[K] | null;\n resourceOrNull(id: string): SdkResource | null;\n\n /**\n * Look up a workspace resource's stable id by its slug.\n *\n * @remarks\n * This lookup is policy-independent and returns identification only. It can identify a\n * resource that the current viewer cannot access. It does not expose resource metadata,\n * fields, capabilities, policies, records, or record operations. Continue to use\n * {@link SdkContract.resource} for policy-checked data access. Static slug references are\n * validated when page code is saved.\n *\n * @example\n * ```tsx\n * const isContact = Sdk.identity?.resource_id === Sdk.resourceId('contacts');\n * return isContact ? <ContactHome /> : <DefaultHome />;\n * ```\n */\n resourceId(resourceSlug: string): string;\n\n /**\n * List every resource id available to the current user through {@link SdkContract.resource}.\n *\n * @returns The array of resource ids available to the current user.\n *\n * @example\n * ```ts\n * const ids = Sdk.resourceIds();\n * ```\n */\n resourceIds(): string[];\n\n /**\n * The current workspace record, its field definitions under `fields`, and the\n * load-time value snapshot under `values`.\n *\n * @remarks\n * `workspace.fields` is the definition catalog keyed by stable workspace field key;\n * pass an entry to `Sdk.Components.FormFieldInput` or `Sdk.Components.WorkspaceFieldsForm`.\n * `workspace.values` is a snapshot taken when the page loaded and is deprecated for\n * reads: use `Sdk.hooks.useWorkspaceValues([...keys])`, which updates after a save on\n * this page. `null` when no workspace is resolved, so guard for null/missing\n * (`Sdk.workspace?.fields.support_email`).\n *\n * @example\n * ```tsx\n * const name = Sdk.workspace?.name;\n * const { support_email } = Sdk.hooks.useWorkspaceValues(['support_email']);\n * const supportEmailField = Sdk.workspace?.fields.support_email;\n * ```\n */\n workspace: SdkWorkspace;\n\n /**\n * Look up a navigation menu by its id and return its metadata\n * plus resolved items.\n *\n * @remarks\n * Throws if the id is unknown \u2014 the error lists available ids (also via\n * {@link SdkContract.navigationMenuIds}). Each `items` entry exposes `title`, a resolved\n * `url` (`null` for `dropdown` containers), `type.value`\n * (`'page' | 'url' | 'external_link' | 'dropdown' | 'system_config'`), optional `icon`,\n * `sort_order`, and nested `children` (for `dropdown` items). Prefer\n * `Sdk.Components.Link` / `ButtonLink` for individual nav items and\n * `Sdk.Components.DropdownMenu` for dropdown containers.\n *\n * @returns A {@link SdkNavigationMenu} \u2014 `{ id, name, items }`.\n *\n * @example\n * ```tsx\n * const menu = Sdk.navigationMenu(MAIN_NAVIGATION_MENU_ID);\n * return (\n * <Sdk.Components.List\n * items={menu.items.map((item) => ({\n * id: item.id,\n * title: item.title,\n * href: item.url ?? '#'\n * }))}\n * />\n * );\n * ```\n */\n navigationMenu(id: string): SdkNavigationMenu;\n\n /**\n * List every available navigation menu id for {@link SdkContract.navigationMenu}.\n *\n * @returns The array of navigation menu ids.\n *\n * @example\n * ```ts\n * const ids = Sdk.navigationMenuIds();\n * ```\n */\n navigationMenuIds(): string[];\n\n /**\n * Hook wrapping Inertia's `useForm` for plain Inertia\n * form submissions to your own endpoints.\n *\n * @remarks\n * Same return shape as Inertia's `useForm` \u2014 `.data`, `.errors`, `.processing`,\n * `.setData(...)`, `.submit(...)`, `.post(...)`, etc. For submitting a Diagonal workflow,\n * use `Sdk.Components.WorkflowForm` or `Sdk.hooks.useWorkflowForm` instead.\n *\n * @returns Inertia `InertiaFormProps<TForm>`.\n *\n * @example\n * ```tsx\n * return function Page() {\n * const form = Sdk.useForm({ name: '', email: '' });\n *\n * return (\n * <form\n * onSubmit={(e) => {\n * e.preventDefault();\n * form.post('/students');\n * }}\n * >\n * <Sdk.Components.FormTextInput\n * label=\"Name\"\n * value={form.data.name}\n * onChange={(v) => form.setData('name', v)}\n * />\n * <Sdk.Components.FormTextInput\n * label=\"Email\"\n * value={form.data.email}\n * onChange={(v) => form.setData('email', v)}\n * />\n * <Sdk.Components.Button disabled={form.processing} type=\"submit\">\n * Save\n * </Sdk.Components.Button>\n * </form>\n * );\n * };\n * ```\n */\n useForm: SdkUseForm;\n\n /**\n * Hook that manages calendar view mode, focal date, derived window, title string, and\n * navigation helpers. Pair with `Sdk.hooks.useCalendarEvents` and `Sdk.Components.Calendar`.\n *\n * @remarks\n * Returns `view`, `setView`, `date`, `setDate`, `windowStart`, `windowEnd`, `title`,\n * `goToToday`, `goToPrevious`, and `goToNext`. Pass `windowStart`/`windowEnd` directly to\n * `Sdk.hooks.useCalendarEvents({ from, to })`. Pass `view` and `date` directly to\n * `<Sdk.Components.Calendar view={view} date={date} />`.\n *\n * Use the pre-built header subcomponents (`CalendarTitle`, `CalendarViewSwitcher`,\n * `CalendarPrevButton`, `CalendarTodayButton`, `CalendarNextButton`) to compose a\n * standard calendar header, or build your own from the raw state values.\n *\n * @example\n * ```tsx\n * function Page() {\n * const cal = Sdk.hooks.useCalendarView({ initialView: 'month' });\n * const { events, isLoading } = Sdk.hooks.useCalendarEvents({\n * resourceId: 'YOUR_RESOURCE_ID',\n * from: cal.windowStart,\n * to: cal.windowEnd\n * });\n *\n * return (\n * <div>\n * <div className=\"flex flex-wrap items-center justify-between gap-3 mb-4\">\n * <Sdk.Components.CalendarTitle title={cal.title} isLoading={isLoading} />\n * <div className=\"flex items-center gap-2\">\n * <Sdk.Components.CalendarViewSwitcher view={cal.view} onViewChange={cal.setView} />\n * <Sdk.Components.CalendarPrevButton view={cal.view} onClick={cal.goToPrevious} />\n * <Sdk.Components.CalendarTodayButton onClick={cal.goToToday} />\n * <Sdk.Components.CalendarNextButton view={cal.view} onClick={cal.goToNext} />\n * </div>\n * </div>\n * <Sdk.Components.Calendar events={events} view={cal.view} date={cal.date} />\n * </div>\n * );\n * }\n * ```\n *\n * @deprecated Use `Sdk.hooks.useCalendarView`.\n * @aiHidden\n */\n useCalendarView: SdkHooks['useCalendarView'];\n\n /**\n * Hook that fetches calendar events for a resource and normalises them to the canonical\n * `CalendarEvent = { id, start: Date, end: Date, allDay: boolean, record }` shape.\n *\n * @remarks\n * The `record` field preserves every raw field value (including loaded relations) so your\n * `renderEvent` callback can read e.g. `event.record.title` or `event.record.status`.\n * Pass `from`/`to` directly from `Sdk.hooks.useCalendarView({ \u2026 }).windowStart` / `windowEnd`.\n *\n * @example\n * ```tsx\n * const { events, isLoading } = Sdk.hooks.useCalendarEvents({\n * resourceId: 'YOUR_RESOURCE_ID',\n * from: cal.windowStart,\n * to: cal.windowEnd,\n * relations: ['assigned_to']\n * });\n * ```\n *\n * @deprecated Use `Sdk.hooks.useCalendarEvents`.\n * @aiHidden\n */\n useCalendarEvents: SdkHooks['useCalendarEvents'];\n\n /**\n * Hook that manages an optimistic placeholder event for a calendar create flow.\n *\n * @remarks\n * Call `setPlaceholder(payload)` inside your `onSlotClick` handler to immediately\n * reflect the selected slot on the calendar while a create modal is open. Pass\n * `newEvent.mergeEvents(events)` to the `events` prop and `newEvent.placeholderTitle`\n * to the `placeholderTitle` prop on `<Sdk.Components.Calendar>`. Call\n * `clearPlaceholder()` when the modal is cancelled or after the record is saved\n * (and optionally after refetching events).\n *\n * The hook owns only the intermediate placeholder state; persisting the calendar\n * event is handled by the parent page.\n *\n * @example\n * ```tsx\n * function LessonsCalendar() {\n * const cal = Sdk.hooks.useCalendarView({ initialView: 'week' });\n * const { events, refetch } = Sdk.hooks.useCalendarEvents({\n * resourceId: 'LESSONS_RESOURCE_ID',\n * from: cal.windowStart,\n * to: cal.windowEnd\n * });\n * const newEvent = Sdk.hooks.useNewCalendarEvent({ placeholderTitle: 'New Lesson' });\n * const [isModalOpen, setIsModalOpen] = React.useState(false);\n * const [slotPayload, setSlotPayload] = React.useState(null);\n *\n * function handleSlotClick(payload) {\n * newEvent.setPlaceholder(payload);\n * setSlotPayload(payload);\n * setIsModalOpen(true);\n * }\n *\n * function handleModalCancel() {\n * newEvent.clearPlaceholder();\n * setIsModalOpen(false);\n * }\n *\n * async function handleModalSave(data) {\n * await Sdk.resource('LESSONS_RESOURCE_ID').create(data);\n * newEvent.clearPlaceholder();\n * await refetch();\n * setIsModalOpen(false);\n * }\n *\n * return (\n * <Sdk.Components.Calendar\n * events={newEvent.mergeEvents(events)}\n * placeholderTitle={newEvent.placeholderTitle}\n * view={cal.view}\n * date={cal.date}\n * onSlotClick={handleSlotClick}\n * />\n * );\n * }\n * ```\n *\n * @deprecated Use `Sdk.hooks.useNewCalendarEvent`.\n * @aiHidden\n */\n useNewCalendarEvent: SdkHooks['useNewCalendarEvent'];\n\n /**\n * Inertia router operations using the application's shared request handling\n * (`visit`, `get`, `post`, `put`, `patch`, `delete`, `reload`).\n *\n * @remarks\n * Use for SPA-style navigation and write operations that should trigger Inertia visits.\n * Prefer this over the bare Inertia `router`. Use `reload` to re-fetch the current\n * page's props in place after a mutation.\n *\n * @example\n * ```tsx\n * function SaveButton() {\n * return (\n * <Sdk.Components.Button onClick={() => Sdk.router.post('/students', { name: 'Ada' })}>\n * Save\n * </Sdk.Components.Button>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * function RefreshButton() {\n * return (\n * <Sdk.Components.Button onClick={() => Sdk.router.reload()}>\n * Refresh\n * </Sdk.Components.Button>\n * );\n * }\n * ```\n */\n router: SdkRouter;\n\n /**\n * HTTP helper wrapping the application's shared Axios client \u2014 see {@link SdkApi}.\n *\n * @remarks\n * Use `Sdk.api` for consistent response parsing and error handling.\n *\n * @remarks\n * For the current logged-in identity, read {@link SdkContract.identity} directly \u2014 there is\n * no `/api/me` endpoint to fetch.\n *\n * @remarks\n * To call a Diagonal workflow, do not hand-write its `/api/workflows/...` path. Address the\n * workflow by its stable slug or ID: resolve the URL with `Sdk.utils.workflow.url(reference, params)`,\n * check its documented `null` fallback, and pass the URL here. Alternatively, use\n * `Sdk.Components.WorkflowForm` / `Sdk.hooks.useWorkflowForm` for form submissions. To\n * send the user through a workflow that redirects, assign a resolved non-null URL to\n * `window.location.href`.\n * Id-based references survive route changes and keep page\u2192workflow calls trackable.\n *\n * @example\n * ```tsx\n * const action = Sdk.utils.workflow.url(APPROVE_INVOICE_WORKFLOW_ID);\n * if (action) await Sdk.api.post(action, { invoice_id: 'invoice-id' });\n *\n * await Sdk.api.post('/api/notifications/mark-read', {\n * ids: [Sdk.identity?.id]\n * });\n * ```\n */\n api: SdkApi;\n\n /**\n * The current logged-in identity, or `null` for anonymous requests.\n *\n * @remarks\n * The identity's underlying resource record, verbatim \u2014 the same plain shape as records\n * read via `Sdk.resource(...)` \u2014 plus `$`-prefixed platform-computed keys: `$display_name`\n * (the resource's configured display value) and `$permission_ids` / `$permission_slugs`.\n * Workspace-defined fields such as `name`/`email` are returned as-is and exist only if the\n * auth resource defines them; use `$display_name` when labeling the current user. NOT a\n * workspace user model or bespoke DTO. Secret fields (password, two-factor phone, remember\n * token) are stripped server-side. Read it directly; there is no `/api/me` endpoint to\n * fetch. Always guard for `null` to handle anonymous (unauthenticated) page views.\n * When the workspace has one authenticatable resource, its field catalog is included in\n * editor and save-time checking; multi-resource or otherwise dynamic identity contexts stay\n * permissive.\n *\n * @example\n * ```tsx\n * return function Page() {\n * const name = Sdk.identity?.$display_name ?? 'Guest';\n * return <p>Welcome, {name}</p>;\n * };\n * ```\n */\n identity: SdkIdentity;\n\n /**\n * Sign the current visitor in or out via the existing Dynamic Auth flow \u2014 see\n * {@link SdkAuth}. Use this to build fully custom login pages on public code pages;\n * `Sdk.auth.login` posts to the workspace's authenticatable resource and follows the\n * server redirect on success.\n *\n * @example\n * ```tsx\n * function LoginPage() {\n * const [identifier, setIdentifier] = React.useState('');\n * const [password, setPassword] = React.useState('');\n * const [error, setError] = React.useState(null);\n *\n * return (\n * <Sdk.Components.Button\n * onClick={() =>\n * Sdk.auth.login(\n * { identifier, password },\n * { onError: (errors) => setError(errors.identifier ?? errors.password ?? null) }\n * )\n * }\n * >\n * Sign in\n * </Sdk.Components.Button>\n * );\n * }\n * ```\n */\n auth: SdkAuth;\n\n /**\n * Permission checks for the current identity. Both permission ids and slugs are\n * accepted. Anonymous requests return `false` for every check.\n *\n * @remarks\n * These helpers are for conditionally rendering page UI. Server-side route and\n * policy enforcement remains authoritative. The raw permission arrays remain\n * available on {@link SdkContract.identity}.\n * `Sdk.Components.HasPermission` and `Sdk.Components.HasAnyPermission` gate a\n * section of JSX on the same checks.\n *\n * @example\n * ```tsx\n * if (Sdk.permissions.has('manage-users')) {\n * return <AdminTools />;\n * }\n *\n * return null;\n * ```\n */\n permissions: SdkPermissions;\n\n /**\n * Namespace of SDK utilities (`Sdk.utils.money`, `date`, `page`, \u2026); see\n * {@link SdkUtils}.\n *\n * @remarks\n * Null-safety convention: display formatters and value/URL resolvers never\n * throw for nullish or malformed field data. They return their documented\n * empty fallback (`''` or `null`) and report a diagnostic through the app\n * logger, with a console warning in development or the page editor.\n * Computation helpers propagate nullish inputs by returning `null`; genuine\n * programmer errors such as unsupported units or currency mismatches between\n * two real values may still throw with an actionable message. The collection\n * helper `money.sum` retains valid entries but reports any skipped nullish or\n * malformed entries rather than silently lowering a total.\n *\n * @example\n * ```tsx\n * const href = Sdk.utils.page.url(DETAIL_PAGE_ID, { record: row.id });\n * return href ? <a href={href}>View</a> : null;\n * ```\n */\n utils: SdkUtils;\n};\n\ntype ComponentRegistry = {\n \"Accordion\": SdkComponent<AccordionProps>;\n \"AdminNavButtons\": SdkComponent<AdminNavButtonsProps>;\n \"AiAgentButton\": SdkComponent<AiAgentButtonProps>;\n \"Alert\": SdkComponent<AlertProps>;\n \"AnalyticsEmbed\": SdkComponent<AnalyticsEmbedProps>;\n \"Avatar\": SdkComponent<AvatarProps>;\n \"Badge\": SdkComponent<BadgeProps>;\n \"BarChart\": <TRow extends object>(props: CartesianChartProps<TRow>) => any;\n \"Breadcrumb\": SdkComponent<BreadcrumbProps>;\n \"Button\": SdkComponent<PrimitiveButtonProps>;\n \"ButtonLink\": SdkComponent<ButtonLinkProps>;\n \"Calendar\": <TRecord extends object = Record<string, unknown>>(props: CalendarProps<TRecord>) => any;\n \"CalendarConnections\": SdkComponent<CalendarConnectionsProps>;\n \"CalendarNextButton\": SdkComponent<CalendarNextButtonProps>;\n \"CalendarPrevButton\": SdkComponent<CalendarPrevButtonProps>;\n \"CalendarSelector\": SdkComponent<CalendarSelectorProps>;\n \"CalendarTitle\": SdkComponent<CalendarTitleProps>;\n \"CalendarTodayButton\": SdkComponent<CalendarTodayButtonProps>;\n \"CalendarViewSwitcher\": SdkComponent<CalendarViewSwitcherProps>;\n \"Card\": SdkComponent<CardProps>;\n \"Collapsible\": SdkComponent<CollapsibleProps>;\n \"ConfirmDialog\": SdkComponent<ConfirmDialogProps>;\n \"CreateResourceRecordForm\": <TResource extends CreateFormResource = CreateFormResource>(props: CreateResourceRecordFormProps<TResource>) => any;\n \"DataTable\": <TData extends RowData>(props: DataTableProps<TData>) => any;\n \"DataTableAddFilter\": SdkComponent<DataTableAddFilterProps>;\n \"DataTableBulkActions\": SdkComponent<DataTableBulkActionsProps>;\n \"DataTableColumnToggle\": SdkComponent<Pick<DataTableViewOptionsProps, 'className' | 'label'>>;\n \"DataTableContent\": SdkComponent<DataTableContentProps>;\n \"DataTableFilterChips\": SdkComponent<DataTableFilterChipsProps>;\n \"DataTableFilters\": SdkComponent<DataTableFiltersProps>;\n \"DataTablePagination\": SdkComponent<DataTablePaginationProps>;\n \"DataTableRowActions\": SdkComponent<DataTableRowActionsProps>;\n \"DataTableSearch\": SdkComponent<DataTableSearchProps>;\n \"DataTableToolbar\": SdkComponent<DataTableToolbarProps>;\n \"DeleteResourceRecord\": SdkComponent<DeleteResourceRecordProps>;\n \"DropdownMenu\": SdkComponent<DropdownMenuProps>;\n \"EmailComposer\": SdkComponent<EmailComposerProps>;\n \"EmailMessages\": SdkComponent<EmailMessagesProps>;\n \"EmptyState\": SdkComponent<EmptyStateProps>;\n \"ErrorBoundary\": SdkComponent<ErrorBoundaryProps>;\n \"ForgotPasswordForm\": SdkComponent<ForgotPasswordFormProps>;\n \"FormFieldDisplay\": SdkComponent<FormFieldDisplayProps>;\n \"FormFieldInput\": SdkComponent<FormFieldInputProps>;\n \"FormCheckbox\": SdkComponent<FormCheckboxProps>;\n \"FormCheckboxGroupInput\": SdkComponent<FormCheckboxGroupInputProps>;\n \"FormCodeInput\": SdkComponent<FormCodeInputProps>;\n \"FormColorInput\": SdkComponent<FormColorInputProps>;\n \"FormComboboxInput\": SdkComponent<FormComboboxInputProps>;\n \"FormContentInput\": SdkComponent<FormContentInputProps>;\n \"FormDateInput\": SdkComponent<FormDateInputProps>;\n \"FormDatetimeInput\": SdkComponent<FormDatetimeInputProps>;\n \"FormFileUpload\": SdkComponent<FormFileUploadProps>;\n \"FormLocationInput\": SdkComponent<FormLocationInputProps>;\n \"FormMarkdownInput\": SdkComponent<FormMarkdownInputProps>;\n \"FormMoneyInput\": SdkComponent<FormMoneyInputProps>;\n \"FormMultiComboboxInput\": SdkComponent<FormMultiComboboxInputProps>;\n \"FormNumberInput\": SdkComponent<FormNumberInputProps>;\n \"FormPasswordInput\": SdkComponent<FormPasswordInputProps>;\n \"FormPhoneInput\": SdkComponent<FormPhoneInputProps>;\n \"FormRadio\": SdkComponent<FormRadioProps>;\n \"FormSelectInput\": SdkComponent<FormSelectInputProps>;\n \"FormSlider\": SdkComponent<FormSliderProps>;\n \"FormSwitch\": SdkComponent<FormSwitchProps>;\n \"FormTextArea\": SdkComponent<FormTextAreaProps>;\n \"FormTextInput\": SdkComponent<FormTextInputProps>;\n \"FormTimeInput\": SdkComponent<FormTimeInputProps>;\n \"FormTimezoneInput\": SdkComponent<FormTimezoneInputProps>;\n \"HasAnyPermission\": SdkComponent<HasAnyPermissionProps>;\n \"HasPermission\": SdkComponent<HasPermissionProps>;\n \"Icon\": SdkComponent<IconProps>;\n \"Iframe\": SdkComponent<IframeProps>;\n \"Image\": SdkComponent<ImageProps>;\n \"KeyValueList\": SdkComponent<KeyValueListProps>;\n \"Link\": SdkComponent<LinkProps>;\n \"LineChart\": <TRow extends object>(props: CartesianChartProps<TRow>) => any;\n \"List\": SdkComponent<ListProps>;\n \"LoginForm\": SdkComponent<LoginFormProps>;\n \"LogoutButton\": SdkComponent<UserControlProps>;\n \"Map\": SdkComponent<MapProps>;\n \"Modal\": SdkComponent<ModalProps>;\n \"NavMain\": SdkComponent<NavMainProps>;\n \"NotificationComposer\": SdkComponent<NotificationComposerProps>;\n \"NotificationConsentForm\": SdkComponent<NotificationConsentFormProps>;\n \"NotificationHistory\": SdkComponent<NotificationHistoryProps>;\n \"NotificationVariablesForm\": SdkComponent<NotificationVariablesFormProps>;\n \"PackageReleaseNotes\": SdkComponent<SdkPackageReleaseNotesProps>;\n \"PageHeader\": SdkComponent<PageHeaderProps>;\n \"PaymentForm\": SdkComponent<PaymentFormProps>;\n \"PaymentMethods\": SdkComponent<PaymentMethodsProps>;\n \"PieChart\": <TRow extends object>(props: PieChartProps<TRow>) => any;\n \"Popover\": SdkComponent<PopoverProps>;\n \"QrCode\": SdkComponent<QrCodeProps>;\n \"RecordFields\": SdkComponent<RecordFieldsProps>;\n \"RecordScheduleDisplay\": SdkComponent<RecordScheduleDisplayProps>;\n \"ResourceTable\": <TResource extends ResourceDataTableResource, const TRelations extends readonly SdkResourceRelationPath<NoInfer<TResource>>[] = readonly []>(props: ResourceTableProps<TResource, TRelations>) => any;\n \"RestoreResourceRecord\": SdkComponent<RestoreResourceRecordProps>;\n \"RichText\": SdkComponent<RichTextProps>;\n \"ScheduledNotificationComposer\": SdkComponent<ScheduledNotificationComposerProps>;\n \"SearchBar\": SdkComponent<SearchBarProps>;\n \"Separator\": SdkComponent<SeparatorProps>;\n \"SharedFilters\": SdkComponent<SharedFiltersProps>;\n \"ShowResourceRecord\": <TResource extends ShowResourceRecordResource & Pick<SdkResource, 'policy_scope'>, const TRelations extends readonly SdkResourceRelationPath<NoInfer<TResource>>[] = readonly []>(props: ShowResourceRecordProps<TResource, TRelations>) => any;\n \"Sidebar\": SdkComponent<SidebarProps>;\n \"SidebarCollapseButton\": SdkComponent<SidebarTriggerProps>;\n \"SidebarContent\": SdkComponent<SidebarSectionProps>;\n \"SidebarFooter\": SdkComponent<SidebarSectionProps>;\n \"SidebarHeader\": SdkComponent<SidebarSectionProps>;\n \"SidebarInset\": SdkComponent<SidebarSectionProps>;\n \"SidebarMenu\": SdkComponent<SidebarSectionProps>;\n \"SidebarMenuButton\": SdkComponent<SidebarMenuButtonProps>;\n \"SidebarMenuItem\": SdkComponent<PrimitiveListItemNativeProps>;\n \"SidebarProvider\": SdkComponent<SidebarProviderProps>;\n \"SidebarTrigger\": SdkComponent<PrimitiveSidebarTriggerProps>;\n \"SimpleTable\": <TRow extends object>(props: SimpleTableProps<TRow>) => any;\n \"Skeleton\": SdkComponent<SkeletonProps>;\n \"Slideover\": SdkComponent<SlideoverProps>;\n \"SlotPicker\": SdkComponent<SlotPickerProps>;\n \"Spinner\": SdkComponent<SpinnerProps>;\n \"StatCard\": SdkComponent<StatCardProps>;\n \"Step\": SdkComponent<StepProps>;\n \"Stepper\": SdkComponent<StepperProps>;\n \"SubscriptionBillingPreview\": SdkComponent<SubscriptionBillingPreviewProps>;\n \"SubscriptionChangePreview\": SdkComponent<SubscriptionChangePreviewProps>;\n \"SupportChatButton\": SdkComponent<SupportChatButtonProps>;\n \"Tabs\": SdkComponent<TabsProps>;\n \"ThemeToggle\": SdkComponent<ThemeToggleProps>;\n \"Timer\": SdkComponent<TimerProps>;\n \"Tooltip\": SdkComponent<TooltipProps>;\n \"TwoFactorBackupCodeList\": SdkComponent<TwoFactorBackupCodeListProps>;\n \"TwoFactorBackupCodes\": SdkComponent<TwoFactorBackupCodesProps>;\n \"TwoFactorChallenge\": SdkComponent<TwoFactorChallengeProps>;\n \"TwoFactorSendCodeButton\": SdkComponent<TwoFactorSendCodeButtonProps>;\n \"TwoFactorSetup\": SdkComponent<TwoFactorSetupProps>;\n \"TwoFactorSmsConsent\": SdkComponent<TwoFactorSmsConsentProps>;\n \"UpdateResourceRecordForm\": <TResource extends UpdateFormResource = UpdateFormResource>(props: UpdateResourceRecordFormProps<TResource>) => any;\n \"UserMenu\": SdkComponent<UserControlProps>;\n \"Video\": SdkComponent<VideoProps>;\n \"WorkflowForm\": SdkComponent<WorkflowFormProps>;\n \"WorkspaceFieldsForm\": SdkComponent<WorkspaceFieldsFormProps>;\n};\n\ntype SdkHooks = {\n /**\n * React Query wrapper around a resource analytics aggregation. Choose `kpi`,\n * `timeSeries`, `leaderboard`, or `distribution`; each kind requires the same params\n * and returns the same result as its matching `resource.analytics` method. Returns\n * `{ data, isLoading, isError, invalidate }`. Record mutations refresh analytics\n * across resources because relation-backed filters and aggregates may depend on the\n * changed record.\n *\n * Each metric holds an {@link AnalyticsValue} \u2014 a `number`, or `{ amount, currency }`\n * when the aggregated field is money-typed. Render it through\n * `Sdk.utils.number.format` or `Sdk.utils.money.format`; a money object thrown\n * straight into JSX is not a renderable child.\n *\n * `params` is part of the query key. Keep it the same between renders: a date range\n * built from `Sdk.utils.time.current()` or `new Date()` during render changes on every\n * render and makes the hook request without end. See `useRecords`.\n *\n * @example\n * ```tsx\n * function PaidInvoicesTile() {\n * const invoices = Sdk.resource('...');\n * const { data, isLoading, isError } = Sdk.hooks.useAnalytics(invoices, 'kpi', {\n * metrics: [{ name: 'paid_count', operation: 'count' }],\n * $filter: {\n * operator: 'and',\n * conditions: [{ field: 'status', operator: '=', value: 'paid' }]\n * }\n * });\n *\n * if (isLoading) return <Sdk.Components.Skeleton />;\n * if (isError) return <Sdk.Components.Alert>Could not load invoices.</Sdk.Components.Alert>;\n *\n * return <Sdk.Components.Card>{Sdk.utils.number.format(data?.paid_count ?? 0, { decimals: 0 })} paid</Sdk.Components.Card>;\n * }\n * ```\n */\n useAnalytics<const TMetrics extends readonly AnalyticsMetric[]>(\n resource: SdkAnalyticsResource<'kpi'>,\n kind: 'kpi',\n params: AnalyticsKpiParams<TMetrics>,\n options?: SdkUseAnalyticsOptions\n ): SdkAnalyticsHandle<AnalyticsKpiResult<TMetrics>>;\n useAnalytics(\n resource: SdkAnalyticsResource<'timeSeries'>,\n kind: 'timeSeries',\n params: AnalyticsTimeSeriesParams,\n options?: SdkUseAnalyticsOptions\n ): SdkAnalyticsHandle<AnalyticsRow[]>;\n useAnalytics(\n resource: SdkAnalyticsResource<'leaderboard'>,\n kind: 'leaderboard',\n params: AnalyticsLeaderboardParams,\n options?: SdkUseAnalyticsOptions\n ): SdkAnalyticsHandle<AnalyticsRow[]>;\n useAnalytics(\n resource: SdkAnalyticsResource<'distribution'>,\n kind: 'distribution',\n params: AnalyticsDistributionParams,\n options?: SdkUseAnalyticsOptions\n ): SdkAnalyticsHandle<AnalyticsRow[]>;\n\n /**\n * Reactive escape hatch for custom HTTP endpoints. The key controls caching and\n * deduplication; the fetcher receives the same scoped `get`/`post`/`put`/`patch`/`delete`\n * helpers as {@link SdkContract.api}. Returns\n * `{ data, isLoading, isError, error, refreshIn, invalidate }`, with every failure normalized to\n * {@link ApiError}.\n *\n * Keys are isolated from the app's other React Query caches. Begin a key with\n * `'records'` when the endpoint's result depends on resource records, for example\n * `['records', resource.id, 'summary']`; successful resource create/update/delete/restore\n * operations then invalidate it automatically. Other keys refresh only through their\n * returned `invalidate` function.\n *\n * Keep the key the same between renders. A clock read such as `Date.now()` in the key\n * makes a new query on every render, and the hook requests without end. See\n * `useRecords`.\n *\n * With `options.pollInterval`, `refreshIn` counts the whole seconds until the next\n * automatic request, rounded up. It is `0` while a refresh is in flight and `null` when\n * no refresh is scheduled.\n *\n * @example\n * ```tsx\n * function NotificationSummary() {\n * const summary = Sdk.hooks.useApi(\n * ['notifications', 'summary'],\n * (api) => api.get('/api/notifications/summary')\n * );\n *\n * if (summary.isLoading) return <Sdk.Components.Skeleton />;\n * if (summary.error) return <Sdk.Components.Alert>{summary.error.message}</Sdk.Components.Alert>;\n * return <p>{summary.data?.unread_count ?? 0} unread</p>;\n * }\n * ```\n */\n useApi<TData>(\n key: SdkApiQueryKey,\n fetcher: SdkApiFetcher<TData>,\n options?: SdkUseApiOptions<TData>\n ): SdkApiHandle<TData>;\n\n /**\n * Preview subscription charges without creating records or collecting payment.\n * All amounts and dates come from the backend. Changing inputs invalidates the\n * displayed quote; responses for older inputs cannot replace the current result.\n * Pass the reviewed quote as `confirmed_quote` to Subscription: Create.\n * Pass the same settings to Subscription: Create as the preview, or the confirmed quote does not match.\n * Discounts apply after proration. First-invoice discounts apply to the first paid\n * invoice after a trial and do not reduce the renewal amount.\n *\n * @example\n * const billing = Sdk.hooks.useSubscriptionPreview({\n * resourceId: Sdk.resource('subscriptions').id,\n * items: [{ price_record_id: selectedPriceId, quantity: 1 }],\n * discounts: [{ calculation_type: 'percentage', percent: 10, duration: 'first_invoice' }]\n * });\n * // On a creation failure, await billing.refetch() and confirm the refreshed terms.\n */\n useSubscriptionPreview(options: SdkSubscriptionPreviewOptions): SdkSubscriptionPreviewHandle;\n\n /**\n * Preview a change to the items of an existing subscription without applying it.\n * Pass the complete desired selection. All amounts and dates come from the backend.\n * The preview includes discount rows, discount_total, and each credit's gross_amount\n * before source discounts or credits. The credit amount is net of those reductions.\n * Renewal includes discounts on the next invoice. Discount term changes require\n * a new confirmed_quote.\n * An incomplete subscription uses `effective: 'now'` without `revisionScope`. Its\n * `initial_revision` preview revises the first invoice in place. `revision.starts_on_payment`\n * means its term dates move when payment activates the subscription. `revision.activates`\n * means applying the revision activates the subscription. A settled draft first invoice\n * activates it only after staff open the invoice.\n * For initial revisions, `credit_to_balance` is potential excess. Settlement moves only\n * eligible amounts; excess stays on the overpaid invoice when transfer mappings are missing.\n * With `revisionScope`, the preview revises an unpaid renewal invoice: `mode` is\n * `current_revision` or `upcoming_revision`, and `preview.revision` holds the invoice ID,\n * before and after lines and totals, conflicts, and the following renewal. Pass the same\n * selection, `effective`, `revision_scope`, confirmed quote, and a reason to\n * Subscription: Change Items.\n * With `effective: 'period'` and `periodStart`, `preview.schedule` holds the period dates,\n * invoices kept unchanged (`preserved_invoices`), replaced scheduled selections, and the\n * recurring subtotal. Pass the same items, `effective: 'period'`, `period_start`, and confirmed\n * quote to Subscription: Change Items.\n * Changing inputs invalidates the displayed quote; responses for older inputs cannot\n * replace the current result. `preview.changed` is false when the selection matches\n * the current subscription. The endpoint requires a signed-in visitor. `Sdk.resource`\n * throws for a visitor who cannot see the resource, so resolve the ID with\n * `Sdk.resourceOrNull` on a page that signed-out visitors can open.\n *\n * @example\n * const change = Sdk.hooks.useSubscriptionChangePreview({\n * resourceId: Sdk.resourceOrNull('subscriptions')?.id ?? '',\n * recordId: subscriptionId,\n * items: [{ price_record_id: selectedPriceId, quantity: 1 }],\n * enabled: Sdk.identity !== null\n * });\n * const canConfirm = change.quote !== null && change.preview?.changed === true;\n */\n useSubscriptionChangePreview(\n options: SdkSubscriptionChangePreviewOptions\n ): SdkSubscriptionChangePreviewHandle;\n\n /**\n * Search free slots for a booking across groups of schedulable records. Each group needs all\n * of its records (`mode: 'all'`) or one of them (`mode: 'any'`). A slot is advice: the booking\n * create decides. Changing an option starts a new search, and the slots of the old search are\n * hidden at once. Show `error` as an error state; an empty `slots` list is not a failure.\n * Render the result with `Sdk.Components.SlotPicker`.\n *\n * @example\n * const search = Sdk.hooks.useAvailableSlots({\n * resourceId: Sdk.resource('appointments').id,\n * groups: [{ mode: 'any', records: tutors.map((tutor) => ({ resourceId: tutorsResourceId, id: tutor.id })) }],\n * from: weekStart,\n * to: weekEnd,\n * durationMinutes: 60\n * });\n */\n useAvailableSlots(options: SdkAvailableSlotsOptions): SdkAvailableSlotsHandle;\n\n /**\n * Read the free time of schedulable records in a window of at most 62 days, with the pieces\n * that made it: regular hours, exceptions, limits, closures, and busy time from staff members'\n * blocking connected calendars. Pass a record's `backgroundTimes` to `Sdk.Components.Calendar`\n * to shade open time and label busy and closed time in the week and day views.\n *\n * @example\n * const availability = Sdk.hooks.useAvailability({\n * resourceId: Sdk.resource('tutors').id,\n * ids: [tutorId],\n * from: cal.windowStart,\n * to: cal.windowEnd,\n * labels: { busy: 'Away' }\n * });\n * <Sdk.Components.Calendar\n * events={[]}\n * view={cal.view}\n * date={cal.date}\n * backgroundTimes={availability.records[0]?.backgroundTimes ?? []}\n * />\n */\n useAvailability(options: SdkAvailabilityOptions): SdkAvailabilityHandle;\n\n /**\n * Read the held times of a resource's records that closed time conflicts with: bookings outside\n * open time, and events over a closed exception, a closure, or busy time on the calendar owner's\n * blocking connected calendars. Only a signed-in visitor reads conflicts, and only of records\n * whose time fields and relation the visitor can read. Show `error` as an error state.\n *\n * @example\n * const { conflicts } = Sdk.hooks.useScheduleConflicts({\n * resourceId: Sdk.resource('lessons').id,\n * bookable: { resourceId: Sdk.resource('tutors').id, ids: [tutorId] },\n * enabled: Sdk.identity !== null\n * });\n */\n useScheduleConflicts(options: SdkScheduleConflictsOptions): SdkScheduleConflictsHandle;\n\n /**\n * Preview the refund amount limits of one payment without refunding it.\n * `refundable` is the largest amount that the refund amount checks of Payment: Refund\n * allow, and the amount that Payment: Refund refunds when the amount is omitted.\n * Payment: Refund can still reject a refund for another `payment_refund_rejected`\n * reason. `payment_remaining` is the payment amount less its succeeded refunds.\n * When the invoice supplied unused-service credit to a subscription change,\n * `change_credit` is that credit and `invoice_refundable` is what the invoice can\n * still refund, so `refundable` can be lower than `payment_remaining`. Both are null\n * when no change credit limits the refund, or when the visitor cannot see every\n * payment and succeeded refund on the invoice with its amount. `refundable` still\n * applies the cap. Call `refetch` after a refund.\n * The endpoint requires a signed-in visitor with Show access to the payment.\n *\n * @example\n * const limits = Sdk.hooks.usePaymentRefundPreview({\n * resourceId: Sdk.resourceOrNull('payments')?.id ?? '',\n * recordId: paymentId,\n * enabled: Sdk.identity !== null\n * });\n * const refundable = limits.preview?.refundable;\n * const isFullRefund = refundable?.amount === limits.preview?.payment_remaining.amount;\n * const label = isFullRefund ? 'Refund full payment' : 'Refund available amount';\n * // After the refund request completes, await limits.refetch().\n */\n usePaymentRefundPreview(options: SdkPaymentRefundPreviewOptions): SdkPaymentRefundPreviewHandle;\n\n /**\n * Read the available payer balance of each accounting entity. The amounts are the\n * amounts that checkout uses when it pays an invoice of that entity from the payer\n * balance. The default entity holds ledger entries with no entity, and any part of the\n * total that no ledger entry explains. Each amount is clamped to zero and to the total,\n * so the amounts need not add up to `total`. The list holds each active entity, and each\n * inactive entity whose amount is above zero, ordered by name. A workspace with no\n * entity returns the total and an empty list.\n * The endpoint requires a signed-in visitor with Show access to the payer and its\n * balance field. Call `refetch` after a ledger entry or a payment.\n *\n * @example\n * const payerBalances = Sdk.hooks.usePayerBalances({\n * resourceId: Sdk.resourceOrNull('businesses')?.id ?? '',\n * recordId: businessId,\n * enabled: Sdk.identity !== null\n * });\n * const rows = payerBalances.balances.map((balance) => ({\n * name: balance.entity.name,\n * available: Sdk.utils.money.format(balance.available)\n * }));\n */\n usePayerBalances(options: SdkPayerBalancesOptions): SdkPayerBalances;\n\n /**\n * The release history of the packages this workspace installed: the installed and the\n * newest version of each, and the notes of each published release, newest first. Every\n * release newer than the installed one is listed, so a skipped upgrade shows each\n * version's notes. The endpoint requires a visitor with the Manage Packages permission.\n *\n * @example\n * const releaseNotes = Sdk.hooks.usePackageReleaseNotes({ limit: 5 });\n * const updates = releaseNotes.packages.filter((item) => item.has_newer_release);\n */\n usePackageReleaseNotes(options?: SdkPackageReleaseNotesOptions): SdkPackageReleaseNotes;\n\n /**\n * List the accounting entities of the workspace with their `active` and `is_default`\n * flags, ordered by name. Inactive entities are included. A branch returns the copies\n * of its production workspace's entities. A signed-out visitor gets an empty list and\n * no request is made,\n * so a public page reads the entity of a record from `record.__meta?.accounting_entity`\n * instead. ShowRecord and IndexRecords add it to each invoice, subscription, and payer\n * balance entry record whose entity field the visitor can read. ShowRecord (`useRecord`)\n * uses the field visibility of the Show policy, and IndexRecords (`useRecords`) uses the\n * field visibility of the Index policy. A Show or Index policy with a field list must list\n * the entity field, or the name is absent. Add the entity field to each policy scope\n * that the page uses.\n *\n * @example\n * const { entities } = Sdk.hooks.useAccountingEntities();\n * const options = entities\n * .filter((entity) => entity.active)\n * .map((entity) => ({ value: entity.id, label: entity.name }));\n * // On a public invoice page:\n * const payee = invoice.__meta?.accounting_entity?.name ?? 'Our organization';\n */\n useAccountingEntities(): SdkAccountingEntities;\n\n /**\n * Refresh the page's route bindings and inputs on an interval.\n *\n * Each tick reloads only the page's own data. Reach for it when a backend change\n * arrives from outside the page \u2014 a webhook, a queued job, another user \u2014 and the page\n * must notice it without the visitor pressing anything. Returns `{ start, stop,\n * lastPolledAt }`; polling begins on mount unless `autoStart: false`, and slows down\n * while the tab is hidden. `lastPolledAt` is the time the latest tick finished, or\n * `null` before the first one.\n *\n * This is the route-binding layer. `Sdk.helpers.getRouteBinding(...)` and the other\n * `Sdk.helpers` data only refresh this way, and this is the only refresh mechanism an\n * anonymous visitor on a public page has, because the record API needs authentication.\n * Records fetched with `useRecord`/`useRecords`/`useApi` live in a separate cache that\n * a reload does not touch \u2014 poll those with their own `pollInterval` option.\n *\n * Every tick costs a full server-side page render, so keep the interval at 3000 ms or\n * more and call `stop()` as soon as the awaited change arrives. Intervals below 1000 ms\n * are raised to 1000 ms. A hidden tab slows to one tick in ten; it does not stop.\n *\n * The interval is read once on mount, so changing it later has no effect \u2014 use `stop()`\n * to end polling. When a tick returns changed data, the page source recompiles.\n * Only the returned page function preserves its local React state. Helper components\n * declared in page source and `Sdk.Custom` components can remount and reset their state.\n * Prefer `pollInterval` on `useRecord`/`useRecords`/`useApi` when a page holds state and\n * the record API is available. State initialized from old data does not automatically\n * track new data, so read route bindings during render.\n *\n * @example\n * ```tsx\n * function TicketList() {\n * const { stop } = Sdk.hooks.usePoll(3000);\n * const order = Sdk.helpers.getRouteBinding('event_order');\n * const tickets = order?.tickets ?? [];\n *\n * React.useEffect(() => {\n * if (tickets.length > 0) stop();\n * }, [tickets.length, stop]);\n *\n * if (tickets.length === 0) return <Sdk.Components.Skeleton />;\n * return <ul>{tickets.map((ticket) => <li key={ticket.id}>{ticket.code}</li>)}</ul>;\n * }\n * ```\n */\n usePoll(interval: number, options?: SdkUsePollOptions): SdkPollControls;\n\n /**\n * Bind page-owned filter, search, and pagination state to the current URL query string.\n * Returns `[params, setParams]`; updates merge the supplied keys into the current URL with\n * `history.replaceState`, so they do not reload the page or trigger an Inertia visit.\n * Pass `null` or `undefined` to remove a key. Browser back/forward and `Sdk.router`\n * navigations update `params` reactively. Without a schema, values are strings (or string\n * arrays for repeated keys). A schema can coerce numbers and booleans, read arrays from\n * repeated query keys, and supply defaults for missing or invalid values. Arrays are written\n * as repeated keys (`tag=a&tag=b`). Editor preview plumbing is hidden and reserved, so page\n * code behaves identically on published routes and in preview.\n *\n * @example\n * ```tsx\n * function StudentList() {\n * const [params, setParams] = Sdk.hooks.useQueryParams({\n * search: { default: '' },\n * page: { type: 'number', default: 1 },\n * active: { type: 'boolean', default: true },\n * tags: { type: 'array', default: [] }\n * });\n *\n * return (\n * <Sdk.Components.Button onClick={() => setParams({ page: params.page + 1 })}>\n * Next page\n * </Sdk.Components.Button>\n * );\n * }\n * ```\n */\n useQueryParams(): [QueryParams, SetQueryParams<QueryParams>];\n useQueryParams<const TSchema extends QueryParamSchema>(\n schema: TSchema\n ): [QueryParamsFromSchema<TSchema>, SetQueryParams<QueryParamsFromSchema<TSchema>>];\n\n /**\n * Reactive wrapper around `resource.urls.show(recordId)` for fetching a single\n * record. Returns `{ data, isLoading, isError, error, refreshIn }`. Pass relation paths through\n * `options.relations`, for example `{ relations: ['family'] }`; known resource handles add only\n * selected relations to `data`, so accessing an omitted relation or unknown field is a\n * save-time type error. Runtime-dynamic resource ids retain the generic record fallback.\n *\n * On a public page, `data === undefined` does not prove that the record is absent:\n * guest policy denial is a 403 and a missing record is a 404. Check `error` with\n * `Sdk.utils.error.isForbidden(error)` / `isNotFound(error)` before choosing the UI state.\n *\n * The resource may be a handle from `Sdk.resource()` or a resource id / slug string. The\n * string form is typed from the workspace catalog and stays idle (`data` is `undefined`,\n * no error) for a viewer who cannot access the resource, so it suits pages that serve\n * both guests and members.\n *\n * With `options.pollInterval`, `refreshIn` counts the whole seconds until the next\n * automatic request, rounded up, so a page can show a countdown. It is `0` while a\n * refresh is in flight, and `null` whenever no refresh is scheduled: before the first\n * request settles, without a `pollInterval`, once a function-form `pollInterval` returns\n * `false`, or while the browser tab is hidden and the poll is paused.\n *\n * @example\n * ```tsx\n * function StudentCard({ recordId }) {\n * const students = Sdk.resource('...');\n * const { data: student, isLoading, error } = Sdk.hooks.useRecord(students, recordId, {\n * relations: ['family']\n * });\n *\n * if (isLoading) return <Sdk.Components.Skeleton />;\n * if (Sdk.utils.error.isForbidden(error)) return <AccessDenied />;\n * if (Sdk.utils.error.isNotFound(error)) return <NotFound />;\n * return <div>{student?.name} \u2014 {student?.family?.name}</div>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * function OrderStatus({ recordId }) {\n * const orders = Sdk.resource('...');\n * const { data: order, refreshIn } = Sdk.hooks.useRecord(orders, recordId, {\n * relations: ['tickets'],\n * pollInterval: (record) => (record?.tickets?.length ? false : 3000)\n * });\n *\n * return (\n * <div>\n * {order?.status}\n * {refreshIn === null ? null : <span>Refreshing in {refreshIn}s</span>}\n * </div>\n * );\n * }\n * ```\n */\n useRecord<\n TReference extends SdkResourceQueryReference<'show'>,\n const TRelations extends readonly SdkReferenceRelationPath<NoInfer<TReference>>[] = readonly []\n >(\n resource: TReference,\n recordId: string | null | undefined,\n options?: SdkUseRecordOptions<TRelations, NoInfer<SdkReferenceRecord<TReference, TRelations>>>\n ): SdkRecordHandle<SdkReferenceRecord<TReference, TRelations>>;\n\n /**\n * Reactive wrapper around the record search endpoint for fetching a page of records \u2014\n * the collection twin of `useRecord`, and the right tool for any collection UI that\n * isn't a table (card grids, charts, select options, kanban boards, dashboard tiles).\n * Returns `{ data, pagination, isLoading, isError, refreshIn, invalidate }`, where `data` is the\n * current page of records (`[]` until the first fetch resolves). The previous page\n * stays rendered while a new page, filter, or sort loads, and records mutated through\n * `resource.create/update/delete/restore` refresh it automatically.\n *\n * Pass relation paths through `options.relations`; known resource handles add only\n * selected relations to `data`, so accessing an omitted relation or unknown field is\n * a save-time type error. Runtime-dynamic resource ids retain the generic record\n * fallback. `filter` must be a structured tree (see {@link ResourceSearchBody}) \u2014\n * flat maps like `{ status: 'paid' }` are rejected with a 422. Filtering on a nested\n * relation path (`family.id`) does not load that relation: it still comes back as its\n * `{ id }` reference, so list it in `relations` to read any other related field.\n * With `options.pollInterval`, `refreshIn` counts the whole seconds until the next\n * automatic request, rounded up. It is `0` while a refresh is in flight and `null` when\n * no refresh is scheduled.\n *\n * Every option except `enabled` and `pollInterval` is part of the query key, so keep\n * the values the same between renders. Do not read the clock in them:\n * `Sdk.utils.time.current()`, `Date.now()`, and `new Date()` return a new value on every\n * render, so each response builds a new query and the page requests without end. Read\n * the time once with `React.useState(() => Sdk.utils.time.current())` or at the top of\n * the page factory. `Sdk.utils.time.startOf(...)`/`endOf(...)` of the current time stay\n * the same between renders and are safe. Saving a page rejects a clock read that\n * reaches a query hook's arguments.\n *\n * Reach for `useResourceDataTable` instead when the UI really is a table.\n *\n * The resource may be a handle from `Sdk.resource()` or a resource id / slug string.\n * A string reference is typed from the workspace catalog at authoring time and is\n * resolved against the viewer's accessible resources at render time, so a viewer\n * without access keeps the hook idle (`data` stays `[]`) instead of throwing. Prefer\n * the string form on pages that serve both guests and members.\n *\n * @example\n * ```tsx\n * function BusinessContacts({ contactId }: { contactId: string }) {\n * const { data } = Sdk.hooks.useRecords('business-contacts', {\n * enabled: Boolean(contactId),\n * relations: ['business'],\n * filter: {\n * operator: 'and',\n * conditions: [{ field: 'contact.id', operator: '=', value: contactId }]\n * }\n * });\n *\n * return <ul>{data.map((link) => <li key={link.id}>{link.business?.name}</li>)}</ul>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * function StudentCards() {\n * const students = Sdk.resource('...');\n * const { data, pagination, isLoading } = Sdk.hooks.useRecords(students, {\n * relations: ['family'],\n * filter: {\n * operator: 'and',\n * conditions: [{ field: 'status', operator: '=', value: 'active' }]\n * },\n * sort: '-created_at',\n * perPage: 12\n * });\n *\n * if (isLoading) return <Sdk.Components.Skeleton />;\n *\n * return (\n * <div className=\"grid grid-cols-3 gap-4\">\n * {data.map((student) => (\n * <Sdk.Components.Card key={student.id}>\n * {student.name} \u2014 {student.family?.name}\n * </Sdk.Components.Card>\n * ))}\n * <p>{pagination?.total} students</p>\n * </div>\n * );\n * }\n * ```\n */\n useRecords<\n TReference extends SdkResourceQueryReference<'search'>,\n const TRelations extends readonly SdkReferenceRelationPath<NoInfer<TReference>>[] = readonly []\n >(\n resource: TReference,\n options?: SdkUseRecordsOptions<TRelations, NoInfer<SdkReferenceRecord<TReference, TRelations>>>\n ): SdkRecordsHandle<SdkReferenceRecord<TReference, TRelations>>;\n\n /**\n * Saved views for a resource: the views the viewer may use or\n * manage (policy-gated server-side), the current selection,\n * and the live view state a selection hydrates \u2014 `filter`, `sorts`, `search`,\n * `perPage`, and `columns`, each `null` (`''` for search, `[]` for sorts) when not\n * configured. `sorts` is the ordered sort, primary first; `sort` is its primary entry.\n * The query values spread straight into `useRecords`. `columns` contains the visible\n * table column identifiers, including custom column IDs, in display order.\n * `useResourceDataTable` applies this layout automatically. Selecting applies the\n * saved state; the setters only dirty the local state until `reset`, `create` (save\n * as a new private view), or `update`.\n *\n * `useResourceDataTable` wires this up automatically \u2014 reach for it directly when\n * building a non-table view (lists, card grids, custom layouts). Pair it with\n * `Sdk.Components.SharedFilters` for a ready-made selector dropdown, or build your\n * own from `available` + `select`.\n *\n * @example\n * ```tsx\n * function StudentList() {\n * const students = Sdk.resource('...');\n * const sharedFilters = Sdk.hooks.useSharedFilters(students);\n * const { data } = Sdk.hooks.useRecords(students, {\n * filter: sharedFilters.filter,\n * sort: sharedFilters.sorts,\n * search: sharedFilters.search,\n * perPage: sharedFilters.perPage\n * });\n *\n * return (\n * <div className=\"grid gap-2\">\n * <Sdk.Components.SharedFilters sharedFilters={sharedFilters} />\n * {data.map((student) => (\n * <Sdk.Components.Card key={student.id}>{student.name}</Sdk.Components.Card>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\n useSharedFilters(\n resource: Pick<SdkResource, 'id' | 'policy_scope'>,\n options?: SdkUseSharedFiltersOptions\n ): SharedFiltersApi;\n\n /**\n * Infinite-scroll counterpart to `useRecords`. It accepts the same `relations`,\n * `filter`, `sort`, `perPage`, `scope`, and `enabled` options, then returns up to\n * the ten most recently loaded pages flattened in `data` with `fetchNextPage`,\n * `hasNextPage`, and `isFetchingNextPage`. Older pages are discarded so cache memory\n * and mutation-triggered refetch work remain bounded. Mutations through the resource\n * handle refresh the retained pages. Like `useRecords`, it accepts a resource id / slug\n * string typed from the workspace catalog, which stays idle for a viewer without access.\n * Keep the options the same between renders; see `useRecords` for why clock reads are\n * rejected.\n *\n * @example\n * ```tsx\n * const students = Sdk.resource('...');\n * const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =\n * Sdk.hooks.useInfiniteRecords(students, {\n * filter: {\n * operator: 'and',\n * conditions: [{ field: 'status', operator: '=', value: 'active' }]\n * },\n * sort: '-created_at',\n * perPage: 25\n * });\n * ```\n */\n useInfiniteRecords<\n TReference extends SdkResourceQueryReference<'search'>,\n const TRelations extends readonly SdkReferenceRelationPath<NoInfer<TReference>>[] = readonly []\n >(\n resource: TReference,\n options?: SdkUseInfiniteRecordsOptions<TRelations>\n ): SdkInfiniteRecordsHandle<SdkReferenceRecord<TReference, TRelations>>;\n\n /**\n * Reactive `resource.create` for pages that write records without a form hook.\n * Returns `{ create, data, isPending, isError, error, reset }`. `create` resolves\n * with the created record, or `null` when the request failed \u2014 it never rejects, so\n * calling it straight from an `onClick` is safe; read `error` for the failure.\n * Cached record data refreshes on success.\n *\n * Prefer `Sdk.Components.CreateResourceRecordForm` when a schema-driven form fits.\n * On a Booking resource, a refusal lands on `error`; read its message with\n * `Sdk.utils.error.extractApiErrorBody(error)?.errors?.$capacity?.[0]`. `create` returns the record only, so call `resource.create`\n * when the page must read `meta.series.waiting` of a repeating booking.\n *\n * @example\n * ```tsx\n * function QuickAddTask() {\n * const tasks = Sdk.resource('...');\n * const { create, isPending, error } = Sdk.hooks.useCreateRecord(tasks);\n * const [title, setTitle] = React.useState('');\n *\n * return (\n * <div className=\"flex gap-2\">\n * <Sdk.Components.FormTextInput value={title} onChange={setTitle} />\n * <Sdk.Components.Button\n * disabled={isPending}\n * onClick={async () => {\n * const task = await create({ title });\n * if (task) setTitle('');\n * }}\n * >\n * Add\n * </Sdk.Components.Button>\n * {error ? <Sdk.Components.Alert>{error.message}</Sdk.Components.Alert> : null}\n * </div>\n * );\n * }\n * ```\n */\n useCreateRecord<TResource extends SdkResourceType & Pick<SdkResource, 'create' | 'capabilities'>>(\n resource: TResource,\n options?: SdkUseCreateRecordOptions<SdkResourceRecord<TResource>>\n ): SdkCreateRecordHandle<SdkResourceRecord<TResource>>;\n\n /**\n * Reactive `resource.update` for pages that write records without a form hook \u2014\n * inline status toggles, kanban drag-and-drop, quick edits. Returns\n * `{ update, data, isPending, isError, error, reset }`. `update` resolves with the\n * updated record, or `null` when the request failed \u2014 it never rejects; read `error`\n * for the failure. Cached record data refreshes on success.\n *\n * Prefer `Sdk.Components.UpdateResourceRecordForm` when a schema-driven form fits.\n *\n * @example\n * ```tsx\n * function CompleteButton({ task }) {\n * const tasks = Sdk.resource('...');\n * const { update, isPending } = Sdk.hooks.useUpdateRecord(tasks);\n *\n * return (\n * <Sdk.Components.Button\n * disabled={isPending}\n * onClick={() => update(task.id, { status: 'complete' })}\n * >\n * Complete\n * </Sdk.Components.Button>\n * );\n * }\n * ```\n */\n useUpdateRecord<TResource extends SdkResourceType & Pick<SdkResource, 'update'>>(\n resource: TResource,\n options?: SdkUseUpdateRecordOptions<SdkResourceRecord<TResource>>\n ): SdkUpdateRecordHandle<SdkResourceRecord<TResource>>;\n\n /**\n * Delete flow for a single record, with the impact preview and blocked-delete error\n * body the built-in delete dialog uses. Returns `{ preview, isPreviewLoading,\n * isPreviewError, confirm, isProcessing, deleteError, error, reset }`. `error` is\n * the normalized {@link SdkApiError}; classify it with `Sdk.utils.error`. Open `fetchPreview`\n * when your confirmation UI opens so the preview reflects the moment of decision,\n * then call `confirm()`. Cached record data refreshes on success.\n *\n * Prefer `Sdk.Components.DeleteResourceRecord` when the built-in dialog fits.\n *\n * @example\n * ```tsx\n * function DeleteStudent({ recordId }) {\n * const students = Sdk.resource('...');\n * const [open, setOpen] = React.useState(false);\n * const { preview, confirm, isProcessing, deleteError } = Sdk.hooks.useDeleteResourceRecord({\n * resource: students,\n * recordId,\n * fetchPreview: open,\n * onSuccess: () => setOpen(false)\n * });\n *\n * if (!open) return <Sdk.Components.Button onClick={() => setOpen(true)}>Delete</Sdk.Components.Button>;\n *\n * return (\n * <div>\n * <p>{preview?.summary}</p>\n * {deleteError ? <Sdk.Components.Alert>{deleteError.message}</Sdk.Components.Alert> : null}\n * <Sdk.Components.Button disabled={isProcessing} onClick={() => confirm()}>\n * Confirm delete\n * </Sdk.Components.Button>\n * </div>\n * );\n * }\n * ```\n */\n useDeleteResourceRecord(options: {\n resource: DeleteResourceRecordResource;\n recordId: string | undefined;\n /** Open this gate when the confirmation UI opens so the preview is fetched then. */\n fetchPreview?: boolean;\n onSuccess?: () => void;\n }): DeleteResourceRecordState;\n\n /**\n * Restore flow for a single soft-deleted record, mirroring\n * `useDeleteResourceRecord`. Returns `{ preview, isPreviewLoading, isPreviewError,\n * confirm, isProcessing, restoreError, reset }`. Pair it with\n * `Sdk.hooks.useRecords(resource, { scope: 'archived' })` to list what can be\n * restored. Cached record data refreshes on success.\n *\n * Prefer `Sdk.Components.RestoreResourceRecord` when the built-in dialog fits.\n *\n * @example\n * ```tsx\n * function RestoreStudent({ recordId }) {\n * const students = Sdk.resource('...');\n * const { confirm, isProcessing } = Sdk.hooks.useRestoreResourceRecord({\n * resource: students,\n * recordId\n * });\n *\n * return (\n * <Sdk.Components.Button disabled={isProcessing} onClick={confirm}>\n * Restore\n * </Sdk.Components.Button>\n * );\n * }\n * ```\n */\n useRestoreResourceRecord(options: {\n resource: RestoreResourceRecordResource;\n recordId: string | undefined;\n /** Open this gate when the confirmation UI opens so the preview is fetched then. */\n fetchPreview?: boolean;\n onSuccess?: () => void;\n }): RestoreResourceRecordState;\n\n /**\n * Queue an ad hoc email through `POST /api/email-messages`. Returns\n * `{ send, data, isPending, isError, error, reset }`. `send` resolves with the\n * response, or `null` when the request failed \u2014 it never rejects, so calling it\n * straight from an `onClick` is safe; read `error` for the failure. The viewer's\n * role must hold the `send_emails` permission, or the request fails with a 403.\n *\n * Prefer `Sdk.Components.EmailComposer` when a person writes the email in the page.\n *\n * @example\n * ```tsx\n * function ResendInvoice({ invoice }: { invoice: Sdk.RecordOf<'invoices'> }) {\n * const invoices = Sdk.resource('invoices');\n * const { send, isPending, error } = Sdk.hooks.useSendEmail();\n *\n * return (\n * <div className=\"space-y-2\">\n * <Sdk.Components.Button\n * disabled={isPending}\n * onClick={() =>\n * send({\n * to: [{ email: invoice.email }],\n * subject: 'Invoice {{ number }}',\n * email_layout_id: '...',\n * layout_data: { number: invoice.number, content: '<p>Your invoice is attached.</p>' },\n * records: [{ id: invoice.id, resource_id: invoices.id }]\n * })\n * }\n * >\n * Resend invoice\n * </Sdk.Components.Button>\n * {error ? <Sdk.Components.Alert variant=\"destructive\">{error.message}</Sdk.Components.Alert> : null}\n * </div>\n * );\n * }\n * ```\n */\n useSendEmail(options?: SdkUseSendEmailOptions): SdkSendEmailHandle;\n\n /**\n * Send a notification template through `POST /api/email-messages/send-notification`.\n * Requires notification templates and the send permissions for every selected channel.\n * An omitted delivery_channel means email. SMS goes to the recipient record\u2019s mapped\n * phone number and only when that record opted in; otherwise history shows it as\n * suppressed. Email and SMS requires one template with both channels enabled. Uses the\n * compatible current default when no template is selected. Refreshes email and\n * notification history after success. Returns null on failure and exposes the API\n * error on the handle. Add `calendar_events: [{ resource_id, record_id }]` to attach an\n * Eventable record as a calendar invite that a later send updates or cancels.\n * Pass `idempotency_key` (for example `renewal-notice:${record.id}:initial`) when a notice\n * must reach a recipient at most once. The first send with a key in the workspace claims it;\n * every later send with that key, from a page, workflow, bulk run, or scheduled notification,\n * sends nothing and resolves with `replayed: true`. The response names the first notification\n * (`notification_history_id`, its email and SMS IDs) only when the viewer can read it:\n * with `manage_notifications` or Show access to one of its records. Build the key from the record and the notice, never from a random value or the time.\n *\n * @example\n * ```tsx\n * function WelcomeButton() {\n * const email = Sdk.hooks.useSendNotification();\n * return <Sdk.Components.Button disabled={email.isPending} onClick={() => email.send({\n * notification_type_id: '01990000-0000-7000-8000-000000000001',\n * variables: { name: 'Morgan' },\n * to: [{ email: 'morgan@example.com' }]\n * })}>Send welcome</Sdk.Components.Button>;\n * }\n * ```\n */\n useSendNotification(options?: SdkUseSendNotificationOptions): SdkSendNotificationHandle;\n\n /**\n * Read and change a notifiable record\u2019s consent for email and SMS through\n * `GET` and `PUT /api/notification-consents`. With no `resourceId` and `recordId` it\n * manages the signed-in identity\u2019s own consent and needs no permission. Another record\n * follows the resource\u2019s record policies. A record that was never asked has email on and\n * SMS off, and SMS notifications to it are suppressed until it opts in.\n *\n * @example\n * ```tsx\n * function SmsOptIn() {\n * const consent = Sdk.hooks.useNotificationConsent();\n * if (!consent.data) return null;\n * return (\n * <label className=\"flex items-center gap-2\">\n * <input\n * type=\"checkbox\"\n * checked={consent.data.channels.sms}\n * disabled={consent.isPending}\n * onChange={(event) => consent.update({ sms: event.target.checked })}\n * />\n * Text me event reminders\n * </label>\n * );\n * }\n * ```\n */\n useNotificationConsent(options?: SdkUseNotificationConsentOptions): SdkNotificationConsentHandle;\n\n /**\n * Read and change the Google calendars of one Schedulable staff record through\n * `/api/calendar-connections`. With no `resourceId` and `recordId` it manages the\n * signed-in identity's own calendars and needs no permission. Another staff record needs\n * \"Manage calendar connections\" and Show access to the record. A viewer connects Google\n * accounts and adds calendars only through their own accounts. Nothing is available\n * while the workspace's `calendar_sync` flag is off, for a signed-out visitor (no request\n * is made), and for an identity that is not a Schedulable record: check `available` and\n * render nothing. Every write refreshes the data and rejects with an `ApiError` on a\n * refusal: read its text with `Sdk.utils.error.apiErrorMessage(error)`.\n * `Sdk.Components.CalendarConnections` renders all of it.\n *\n * @example\n * ```tsx\n * function CalendarStatus() {\n * const calendars = Sdk.hooks.useCalendarConnections();\n * if (!calendars.available) return null;\n * if (calendars.links.length === 0) {\n * return <Sdk.Components.Button onClick={() => calendars.connect()}>Connect Google Calendar</Sdk.Components.Button>;\n * }\n * return (\n * <ul>\n * {calendars.links.map((link) => (\n * <li key={link.id}>{link.calendar_name}: {link.message ?? 'Active'}</li>\n * ))}\n * </ul>\n * );\n * }\n * ```\n */\n useCalendarConnections(options?: SdkUseCalendarConnectionsOptions): SdkCalendarConnectionsHandle;\n\n /**\n * Drive the two-factor challenge of a pending sign-in on this page, for a custom UI.\n * `Sdk.Components.TwoFactorChallenge` already renders it. Codes are sent only by\n * `Sdk.Components.TwoFactorSmsConsent` and `Sdk.Components.TwoFactorSendCodeButton` with\n * `flow=\"challenge\"`, which always show the SMS terms.\n *\n * @example\n * ```tsx\n * function Challenge() {\n * const challenge = Sdk.hooks.useTwoFactorChallenge();\n * const [code, setCode] = React.useState('');\n * if (!challenge.isActive || !challenge.data) return null;\n * if (challenge.data.step === 'consent') return <Sdk.Components.TwoFactorSmsConsent flow=\"challenge\" />;\n * return (\n * <>\n * <Sdk.Components.FormCodeInput label=\"Code\" value={code} onChange={setCode} onComplete={challenge.verifyCode} />\n * <Sdk.Components.TwoFactorSendCodeButton flow=\"challenge\" />\n * </>\n * );\n * }\n * ```\n */\n useTwoFactorChallenge(options?: SdkUseTwoFactorOptions): SdkTwoFactorChallengeHandle;\n\n /**\n * Drive MFA setup for a custom UI: the first setup of a pending sign-in, or a signed-in\n * member's change of MFA number. `Sdk.Components.TwoFactorSetup` already renders it.\n * Codes are sent only by `Sdk.Components.TwoFactorSmsConsent` and\n * `Sdk.Components.TwoFactorSendCodeButton` with `flow=\"setup\"`.\n *\n * @example\n * ```tsx\n * function ChangeMfaNumber() {\n * const setup = Sdk.hooks.useTwoFactorSetup();\n * const [phone, setPhone] = React.useState('');\n * if (!setup.isActive || !setup.data) return null;\n * if (setup.data.step === 'phone') {\n * return (\n * <>\n * <Sdk.Components.FormPhoneInput label=\"New MFA number\" value={phone} onChange={setPhone} />\n * <Sdk.Components.Button onClick={() => setup.submitPhone(phone)}>Continue</Sdk.Components.Button>\n * </>\n * );\n * }\n * return <Sdk.Components.TwoFactorSetup />;\n * }\n * ```\n */\n useTwoFactorSetup(options?: SdkUseTwoFactorOptions): SdkTwoFactorSetupHandle;\n\n /**\n * Read and regenerate the signed-in member's backup codes for a custom UI.\n * `Sdk.Components.TwoFactorBackupCodes` already renders it. Codes for the proof are sent\n * only by `Sdk.Components.TwoFactorSmsConsent` and `Sdk.Components.TwoFactorSendCodeButton`\n * with `flow=\"backupCodes\"`.\n *\n * @example\n * ```tsx\n * function BackupCodeCount() {\n * const backupCodes = Sdk.hooks.useTwoFactorBackupCodes();\n * if (!backupCodes.data) return null;\n * return <p>{backupCodes.data.remainingCount} unused backup codes</p>;\n * }\n * ```\n */\n useTwoFactorBackupCodes(options?: SdkUseTwoFactorOptions): SdkTwoFactorBackupCodesHandle;\n\n /**\n * Start background notifications for selected records or a filtered audience. The server\n * saves the audience, template, and published layout before sending. Duplicate recipients\n * on different source records receive separate emails. Save the returned ID in page state\n * or a route parameter to reopen progress with useBulkNotificationRun after navigation.\n * Each new send call creates a separate run. Do not automatically retry a failed request.\n * To make a repeated run safe, pass `idempotency_key` containing `{source_record_id}`, for\n * example `renewal-notice:{source_record_id}:initial`. Each source record gets its own key,\n * which matches a single send with the same expanded key; a source record whose key any\n * earlier notification used sends nothing and counts in `duplicate_count`.\n * @example\n * ```tsx\n * const sender = Sdk.hooks.useSendBulkNotifications();\n * const progress = Sdk.hooks.useBulkNotificationRun(sender.data?.id);\n * // From a user action:\n * await sender.send({\n * resource_id: attendeeResourceId,\n * notification_type_id: notificationTypeId,\n * recipient_path: '',\n * audience: { mode: 'records', record_ids: selectedAttendeeIds },\n * variables: {}\n * });\n * ```\n */\n useSendBulkNotifications(): SdkSendBulkNotificationsHandle;\n\n /**\n * Read your saved bulk send and poll every two seconds while it is queued or processing.\n * A nullish ID keeps the query idle. Polling stops for completed, failed, or interrupted runs.\n * A completed run can contain item failures. Read queued_count, failed_count,\n * uncertain_count, and duplicate_count separately. Only the initiating identity can read this run.\n * @example\n * ```tsx\n * const progress = Sdk.hooks.useBulkNotificationRun(savedRunId);\n * return <p>{progress.data?.queued_count ?? 0} emails queued</p>;\n * ```\n */\n useBulkNotificationRun(runId: string | null | undefined): SdkApiHandle<SdkBulkNotificationRun>;\n\n /**\n * Queue the complete edited NotificationComposer draft without resolving its copy again.\n * The draft carries the template that preview resolved, including the type's default.\n * delivery_channel selects email (default), sms, or email_and_sms; an SMS send carries\n * the edited sms_body as written and needs the send_sms permission. The response\n * includes the notification history ID and the queued or suppressed SMS messages.\n * For a type without a recipient resource, omit recipient and supply to email objects.\n * Preview that type without recipient or to. Accepts the same `idempotency_key` as\n * useSendNotification; a repeated key sends nothing and returns `replayed: true`.\n * @example\n * const notification = Sdk.hooks.useSendResolvedNotification();\n * await notification.send({ notification_type_id: typeId, recipient: member, ...draft });\n */\n useSendResolvedNotification(options?: SdkUseSendEmailOptions): SdkSendResolvedNotificationHandle;\n /**\n * List notification types using the send_emails or send_sms permission. Results are\n * paginated. deliveryChannel keeps the types with a template for every requested channel.\n * Each type lists the `variables` a sender supplies with their label, type, and\n * required flag. Render their inputs with `Sdk.Components.NotificationVariablesForm`.\n * Call invalidate() to refresh after an administrator changes the catalog.\n * @example\n * const types = Sdk.hooks.useNotificationTypes({ recipientMode: 'notification' });\n * const choices = types.data?.data ?? [];\n */\n useNotificationTypes(\n options?: SdkNotificationTypeListOptions\n ): SdkApiHandle<SdkNotificationTypeList>;\n /**\n * List a type's templates, with is_default, is_email_enabled, and is_sms_enabled, using\n * the send_emails or send_sms permission. deliveryChannel keeps the templates that\n * support every requested channel. No request runs until notificationTypeId is set.\n * Results are paginated.\n * @example\n * const templates = Sdk.hooks.useNotificationTemplates(selectedTypeId);\n * const choices = templates.data?.data ?? [];\n */\n useNotificationTemplates(\n notificationTypeId: string | null | undefined,\n options?: SdkNotificationListOptions\n ): SdkApiHandle<SdkNotificationTemplateList>;\n\n /**\n * List pending and failed notifications associated with one source or recipient record.\n * Requires Show on that record; does not require send_emails. Returns scheduling metadata\n * only. can_cancel reflects current permissions; cancellation rechecks them on the server.\n * Waits until both IDs are present. Results are ordered by send time and ID.\n * @example\n * const upcoming = Sdk.hooks.useUpcomingNotifications({ resourceId, recordId, perPage: 10 });\n */\n useUpcomingNotifications(\n options: SdkUpcomingNotificationsOptions\n ): SdkApiHandle<SdkUpcomingNotifications>;\n\n /**\n * Schedule one notification for a notifiable record. Requires send_emails and Show on\n * the recipient and context records. Supply literal variables and an explicit template.\n * scheduled_at must be a future ISO timestamp with Z or an offset; convert local input\n * with new Date(value).toISOString(). Template values resolve again at send preparation.\n * Success refreshes upcoming-notification queries. The promise rejects with ApiError.\n * Use status: \"draft\" without scheduled_at to prepare an unscheduled draft. One-off drafts\n * require idempotency_key. Generated drafts use notification_schedule_id, source_record_id,\n * and stage_id. Saving a draft never releases it; use Send or transition: \"schedule\".\n * An `idempotency_key` makes a repeated schedule call return the first scheduled\n * notification unchanged. At send time the key also stops delivery when a send, bulk item,\n * or workflow already used it, so a manual send and a scheduled send of one notice reach the\n * recipient once.\n * @example\n * const notification = Sdk.hooks.useScheduleNotification();\n * await notification.schedule({ notification_type_id: typeId, notification_template_id: templateId, recipient, variables: {}, scheduled_at: new Date(value).toISOString() });\n */\n useScheduleNotification(): SdkScheduleNotificationHandle;\n\n /**\n * Cancel one pending occurrence. Requires send_emails, recipient Show, and Update on\n * the recipient for one-offs or Show and Update on the source for generated occurrences.\n * Does not change the shared schedule or opt out of future occurrences. Already queued\n * or expired items return 409. Success refreshes upcoming lists. Rejects with ApiError.\n * @example\n * const cancellation = Sdk.hooks.useCancelScheduledNotification();\n * await cancellation.cancel(notification.id, notification.revision);\n */\n useCancelScheduledNotification(): SdkCancelScheduledNotificationHandle;\n\n /**\n * Hold one pending occurrence. It keeps its ID, content, and time but does not send until\n * it is resumed. Send now and cancel still work while it is held. Requires the same access\n * as cancel. Repeated holds succeed. Success refreshes upcoming lists. Rejects with ApiError.\n * @example\n * const hold = Sdk.hooks.useHoldScheduledNotification();\n * await hold.hold(notification.id, notification.revision);\n */\n useHoldScheduledNotification(): SdkHoldScheduledNotificationHandle;\n\n /**\n * Resume a held occurrence so it sends once at its time. When that time passed during the\n * hold, pass a new future time; the old time never sends late. A generated occurrence keeps\n * the explicit time across schedule date changes. Rejects with ApiError.\n * @example\n * const resume = Sdk.hooks.useResumeScheduledNotification();\n * await resume.resume(notification.id, notification.revision, new Date(value).toISOString());\n */\n useResumeScheduledNotification(): SdkResumeScheduledNotificationHandle;\n\n /**\n * Load editable raw content for one occurrence. Waits when id is nullish.\n * Requires send_emails and Show/Update on the one-off recipient, or Show/Update\n * on the generated source and Show on its recipient. Context records are not returned.\n * @example\n * const detail = Sdk.hooks.useScheduledNotification(selectedId);\n */\n useScheduledNotification(\n scheduledNotificationId: string | null | undefined\n ): SdkApiHandle<SdkScheduledNotificationDetail>;\n\n /**\n * Edit a draft, scheduled, or failed occurrence. Pass its expected_revision.\n * A null content_override restores the template. Failed preparation recovers only\n * after successful resolution. Refreshes detail and all upcoming record lists.\n * @example\n * const editing = Sdk.hooks.useUpdateScheduledNotification();\n * await editing.update(id, { expected_revision: notification.revision, content_override: null });\n */\n useUpdateScheduledNotification(): SdkUpdateScheduledNotificationHandle;\n\n /**\n * Apply edits and send the same draft, scheduled, or failed occurrence now, including\n * paused occurrences. The original time cannot send it again. Rejects terminal\n * items with 409. Refreshes detail, upcoming lists, and email history.\n * @example\n * const sending = Sdk.hooks.useSendScheduledNotificationNow();\n * await sending.sendNow(id, { expected_revision: notification.revision });\n */\n useSendScheduledNotificationNow(): SdkSendScheduledNotificationNowHandle;\n\n /**\n * Read tracked email rows, including ad hoc and template emails, for custom tables.\n * Uses the same scope, filters, permissions, and send invalidation as EmailMessages.\n * Each email appears once even when linked to several scoped records. Empty records\n * leave the query idle. Render isError before treating missing data as an empty result.\n * Changing scope, filters, or page clears the previous rows while the query loads.\n * Pagination covers emails only: merge and paginate other activity sources explicitly.\n * @example\n * const emails = Sdk.hooks.useEmailMessages({\n * records: [{ resourceId: businessResource.id, recordId: business.id }],\n * page: 1,\n * perPage: 20\n * });\n */\n useEmailMessages(options: SdkEmailMessagesOptions): SdkApiHandle<SdkEmailMessages>;\n\n /**\n * List notifications by lifecycle status: upcoming, sent, failed, cancelled, or expired.\n * Pass `records` for record-scoped history (Show access to each record) or `workspace`\n * for every notification (`manage_notifications`). Filters narrow the scope and follow\n * the admin notification list. Sent rows keep saved names after template edits.\n * @example\n * const history = Sdk.hooks.useNotificationHistory({ records: [{ resourceId, recordId }], status: 'sent' });\n */\n useNotificationHistory(\n options: SdkNotificationHistoryOptions\n ): SdkApiHandle<SdkNotificationHistory>;\n\n /**\n * Load one notification and its email attempts with delivery events. Pass the list row's\n * `kind` and `id`. Waits when either is nullish. Reads require `manage_notifications` or\n * Show access to a linked record. Load an attempt body with `Sdk.Components.NotificationHistory`\n * or `Sdk.Components.EmailMessages`.\n * @example\n * const detail = Sdk.hooks.useNotificationHistoryDetail(selected?.kind, selected?.id);\n */\n useNotificationHistoryDetail(\n kind: SdkNotificationHistoryKind | null | undefined,\n notificationId: string | null | undefined\n ): SdkApiHandle<SdkNotificationHistoryDetail>;\n\n /**\n * Form hook for creating a resource record via the JSON API.\n * Returns `{ form, FieldInput, Form }` \u2014 a handle with form state, a context-wired field\n * input component, and a submit-pre-wired form wrapper.\n *\n * @aiHidden Kept on the runtime SDK for existing pages, but omitted from the generated\n * SDK reference so the code-builder agent is steered to `Sdk.Components.CreateResourceRecordForm`.\n *\n * @example\n * ```tsx\n * function CreateStudent() {\n * const students = Sdk.resource('...');\n * const { form, FieldInput, Form } = Sdk.hooks.useCreateResourceRecordForm({\n * resource: students,\n * onSuccess: (record) => console.log('Created', record)\n * });\n *\n * return (\n * <Form className=\"grid gap-4\">\n * <FieldInput field={students.fields.first_name} />\n * <FieldInput field={students.fields.email} />\n * <button type=\"submit\" disabled={form.isSubmitting}>Create</button>\n * </Form>\n * );\n * }\n * ```\n */\n useCreateResourceRecordForm(options: {\n resource: CreateResourceRecordFormResource;\n fields?: SdkResourceField[];\n /** Editable seed values \u2014 pre-fill fields the user can still change. */\n initialValues?: Record<string, any>;\n /** Locked writable Resource field values \u2014 rendered disabled and submitted even when\n * omitted from `fields`; unknown, system, virtual, or inaccessible keys are ignored. */\n fixedValues?: Record<string, any>;\n /** Include every creatable field, ignoring per-field `show_on_creating` form\n * visibility. Still excludes system/virtual fields and respects field access. */\n includeAllFields?: boolean;\n /** How relation fields resolve inline attach/create. Defaults to `'settings'`;\n * pass `'always'` to force them on. */\n relationVisibility?: RelationVisibility;\n /** Send a booking idempotency key, kept across retries of the same values. Defaults to\n * `false`. Submitting throws when the resource has no booking idempotency field. */\n idempotent?: boolean;\n onSuccess?: (record: ResourceRecord) => void;\n /** Called when a submit fails, after the form shows its errors. */\n onError?: (error: any) => void;\n }): UseCreateResourceRecordFormResult;\n\n /**\n * Form hook for updating an existing resource record via the JSON API.\n * Loads the record and seeds the form before rendering fields.\n * Returns `{ form, FieldInput, Form, interaction }` \u2014 same shape as the create hook,\n * with an additional `form.isLoading` flag while the record is being fetched and a\n * ready-to-render `interaction` node for capability-aware pre-submit prompts.\n * Direct hook consumers must render `interaction` next to `Form`; otherwise recurring\n * eventable updates can pause at the scope prompt without showing it.\n *\n * @aiHidden Kept on the runtime SDK for existing pages, but omitted from the generated\n * SDK reference so the code-builder agent is steered to `Sdk.Components.UpdateResourceRecordForm`.\n *\n * @example\n * ```tsx\n * function EditStudent({ recordId }) {\n * const students = Sdk.resource('...');\n * const { form, FieldInput, Form, interaction } = Sdk.hooks.useUpdateResourceRecordForm({\n * resource: students,\n * recordId,\n * onSuccess: (record) => console.log('Updated', record)\n * });\n *\n * if (form.isLoading) return <Sdk.Components.Skeleton />;\n *\n * return (\n * <>\n * <Form className=\"grid gap-4\">\n * <FieldInput field={students.fields.first_name} />\n * <FieldInput field={students.fields.email} />\n * <button type=\"submit\" disabled={form.isSubmitting}>Save</button>\n * </Form>\n * {interaction}\n * </>\n * );\n * }\n * ```\n */\n useUpdateResourceRecordForm(options: {\n resource: UpdateResourceRecordFormResource;\n recordId: string | null | undefined;\n fields?: SdkResourceField[];\n /** Editable seed values applied before the record loads (or when no `recordId`). */\n initialValues?: Record<string, any>;\n /** Locked writable Resource field values \u2014 rendered disabled and submitted even when\n * omitted from `fields`; unknown, system, virtual, or inaccessible keys are ignored. */\n fixedValues?: Record<string, any>;\n /** Include every updatable field, ignoring per-field `show_on_updating` form\n * visibility. Still excludes system/virtual fields and respects field access. */\n includeAllFields?: boolean;\n /** How relation fields resolve inline attach/create. Defaults to `'settings'`;\n * pass `'always'` to force them on. */\n relationVisibility?: RelationVisibility;\n onSuccess?: (record: ResourceRecord) => void;\n }): UseUpdateResourceRecordFormResult;\n\n /**\n * Schema-aware workflow form hook \u2014 the headless counterpart of\n * `Sdk.Components.WorkflowForm`. Resolves a\n * published Request workflow's input schema (query fields for GET, body fields for\n * POST/PUT/PATCH, none for DELETE) and returns `{ form, FieldInput, Form, fields,\n * method, isAvailable }`. Reach for it when the form UI must escape the component's\n * managed shell \u2014 e.g. a submit button rendered outside the `<form>`, a bespoke\n * multi-step wizard, or hand-wired inputs bound through `form.data`/`form.setData`.\n * Prefer `Sdk.Components.WorkflowForm` when the default managed form fits.\n * `form.errors` contains only 422 field validation. Other failures are available\n * as `form.failure`; for example, branch on\n * `Sdk.utils.error.isConflict(form.failure)` without inspecting status codes.\n * Success callbacks receive the parsed workflow body directly. Synchronous\n * output can be a scalar, list, record, or object; asynchronous workflows return\n * `{ execution_id }`. Narrow the value before reading authored output fields.\n * Match `initialValues` keys to the workflow's expected `request.body` keys;\n * `initialValues` is captured once on mount, so when seeding from fetched data gate\n * rendering until it has loaded. Rich text inputs use `Sdk.Components.FormContentInput`\n * initialized as TipTap JSON. Markdown inputs use `Sdk.Components.FormMarkdownInput`;\n * disable submit while `form.isSubmitting` or `!form.isReady`.\n *\n * @example\n * ```tsx\n * function InvitePanel() {\n * const { form, FieldInput, Form, fields, isAvailable } = Sdk.hooks.useWorkflowForm({\n * workflowId: 'send-invite'\n * });\n *\n * if (!isAvailable) return null;\n *\n * return (\n * <div className=\"grid gap-4\">\n * <Form className=\"grid gap-4\">\n * {fields.map((field) => <FieldInput key={field.key} field={field} />)}\n * </Form>\n * <Sdk.Components.Button onClick={form.submit} disabled={form.isSubmitting}>\n * Send invite\n * </Sdk.Components.Button>\n * </div>\n * );\n * }\n * ```\n */\n useWorkflowForm(options: {\n /** The workflow's slug or ID. Both values are stable across title renames. */\n workflowId: WorkflowReference;\n /** Route parameter values for parameterized published routes\n * (e.g. `/invites/{id}` needs `params: { id: recordId }`). */\n params?: Record<string, any>;\n /** Input-field keys controlling which schema fields are exposed and their order.\n * Omit for every field from the workflow's input schema; unknown keys are ignored. */\n fields?: string[];\n /** Editable seed values \u2014 captured once on mount; they form the `reset()` baseline. */\n initialValues?: Record<string, any>;\n /** Locked values \u2014 rendered as disabled fields and always submitted. */\n fixedValues?: Record<string, any>;\n /** Defaults to `true` for mutation workflows and `false` for GET workflows, so a\n * search form keeps its filters. */\n resetOnSuccess?: boolean;\n onSuccess?: (response: SdkWorkflowResponse) => void;\n onError?: (context: { status: number; data: any; error: any }) => void;\n }): UseWorkflowFormResult;\n\n /**\n * Form state for editing workspace field values, for layouts that must escape the\n * managed `Sdk.Components.WorkspaceFieldsForm` shell. Submits to\n * `PUT /api/data/workspace-fields/values`; requires `update_workspace_fields` or `manage_workspace_fields`\n * permission.\n *\n * @example\n * ```tsx\n * const { fields } = Sdk.workspace!;\n * const { form, canSubmit, FieldInput, Form } = Sdk.hooks.useWorkspaceFieldsForm({ fields: [fields.support_email] });\n * return (\n * <Form>\n * <FieldInput field={fields.support_email} />\n * <Sdk.Components.Button type=\"submit\" disabled={form.isSubmitting || !canSubmit}>Save</Sdk.Components.Button>\n * </Form>\n * );\n * ```\n */\n useWorkspaceFieldsForm(options: SdkUseWorkspaceFieldsFormOptions): UseWorkspaceFieldsFormResult;\n\n /**\n * Imperatively runs a published Request workflow while managing reactive\n * `{ isPending, errorMessage, failure, result }` state. Use this for button-triggered\n * actions that do not need schema-driven form state. `run(inputs)` resolves with\n * the parsed workflow body on success and `null` on failure; it never rejects.\n * Only the latest concurrent run may update the returned state.\n *\n * Set `idempotent: true` to add a generated UUID under the conventional\n * `idempotency_key` input. Identical retries keep the key, while changed inputs or\n * route parameters rotate it, including the conflict/failure -> edit -> retry flow.\n * `reset()` also starts a fresh key lifecycle. The hook does not poll asynchronous\n * executions.\n *\n * @example\n * ```tsx\n * function ReserveButton({ slotId }) {\n * const action = Sdk.hooks.useWorkflowAction({\n * workflowId: 'reserve-slot',\n * idempotent: true\n * });\n *\n * return (\n * <div>\n * <Sdk.Components.Button\n * disabled={action.isPending}\n * onClick={() => action.run({ slot_id: slotId })}\n * >\n * Reserve\n * </Sdk.Components.Button>\n * {Sdk.utils.error.isConflict(action.failure) ? (\n * <Sdk.Components.Alert>That slot was just taken. Choose another.</Sdk.Components.Alert>\n * ) : action.errorMessage ? (\n * <Sdk.Components.Alert>{action.errorMessage}</Sdk.Components.Alert>\n * ) : null}\n * </div>\n * );\n * }\n * ```\n */\n useWorkflowAction(options: {\n /** The workflow's slug or ID. Both values are stable across title renames. */\n workflowId: WorkflowReference;\n /** Reactive route parameter values for parameterized published routes\n * (e.g. `/invites/{id}` needs `params: { id: recordId }`). */\n params?: Record<string, any>;\n /** Generate and manage the conventional `idempotency_key` input. */\n idempotent?: boolean;\n }): UseWorkflowActionResult;\n\n /**\n * Reactive timelines of a record's schedule workflow runs, the data behind\n * `Sdk.Components.RecordScheduleDisplay`, for pages that render a custom\n * schedule UI. For each published record-schedule workflow the record participates\n * in, returns the anchor date and every offset in chronological order with its\n * state (`pending`, `sent`, `skipped`, or `manually_skipped`) and an `is_next`\n * flag on the upcoming run. Show `manually_skipped` offsets distinctly from\n * automatic skips. Call `skipOffset({ workflowId, offset, scheduledRunAt })` to\n * skip one upcoming pending occurrence. Recomputing the same date preserves its\n * skip; a changed reminder date does not inherit the old skip.\n * Skipping requires Show and Update access to the record. Viewers only see the\n * schedule of records they are allowed to see. Prefer the component when the\n * default timeline rendering fits.\n *\n * @example\n * ```tsx\n * function NextReminder({ invoiceId }) {\n * const { timelines, isLoading } = Sdk.hooks.useRecordScheduleTimelines({\n * resourceId: '0192a1b2-c3d4-7e5f-8a6b-9c0d1e2f3a4b',\n * recordId: invoiceId\n * });\n *\n * if (isLoading) return <Sdk.Components.Skeleton />;\n *\n * const next = timelines\n * .flatMap((timeline) => timeline.offsets)\n * .find((offset) => offset.is_next);\n *\n * return <p>{next ? `Next reminder: ${next.scheduled_run_at}` : 'Nothing scheduled'}</p>;\n * }\n * ```\n */\n useRecordScheduleTimelines(\n options: SdkUseRecordScheduleTimelinesOptions\n ): SdkRecordScheduleTimelinesHandle;\n\n /**\n * Builds a client-side table from seeded, in-memory data (no resource). Sorting,\n * search, and pagination run in the browser. Pair the returned `table` with\n * `Sdk.Components.DataTable`.\n *\n * @example\n * ```tsx\n * const { table } = Sdk.hooks.useDataTable({\n * data: rows,\n * columns: [{ accessor: 'name', header: 'Name' }, { accessor: 'total', header: 'Total' }]\n * });\n * return <Sdk.Components.DataTable table={table} searchable />;\n * ```\n */\n useDataTable<TData extends Record<string, any>>(\n options: UseDataTableOptions<TData>\n ): UseDataTableResult<TData>;\n\n /**\n * Batteries-included resource-backed table. Creates columns for every Index-scope field\n * the viewer can access, uses `initialFields` to select which start visible, wires\n * server-side search / sort / pagination / filters, fetches records, and returns a\n * TanStack `table` for `Sdk.Components.DataTable`. Use `fieldOverrides` for the uncommon\n * field that needs a custom cell. Each cell override receives the row plus row-bound\n * `FieldValue` / `FieldLabel` helpers. Relation field overrides receive the preloaded relation\n * value on the typed row. An override for a field that policy removes does not run.\n *\n * The `columns` option provides complete column control. It accepts field-handle shorthand,\n * an explicit field column (`{ field, header?, enableSorting?, width?, className? }`), or a\n * custom column. Cells wrap at 240px unless the column sets `width`, so one long name or email\n * cannot push the other columns out of view. A `max-w-*` class in `className` replaces the limit.\n * Nested relation field handles (e.g. `students.fields.family.fields.name`) load their\n * relations automatically, but cannot sort.\n * Custom columns that read relation properties from `row` should also list those paths in\n * `relations`; this keeps the callback's record type aligned with its requested data.\n * Resource columns never take react-table `{ accessor, header }` objects \u2014 that shape is\n * `useDataTable`-only (seeded in-memory data) and fails the save-time type check here.\n *\n * Authored `filters` narrow every query invisibly; filter relation fields by nested\n * scalar paths such as `family.id`, not the relation object path (`family`). The\n * returned `filters` object powers the end-user advanced-filters dialog \u2014 pass it to\n * `Sdk.Components.DataTable`'s `filters` prop to show the \"Filters\" toolbar button.\n *\n * @example\n * ```tsx\n * const students = Sdk.resource('students');\n * const { table, isLoading, isError, refetch, filters } = Sdk.hooks.useResourceDataTable({\n * resource: students,\n * initialFields: [students.fields.email, students.fields.family, students.fields.status],\n * fieldOverrides: {\n * family: {\n * cell: (row) => <strong>{row.family.name}</strong>\n * }\n * },\n * filters: { operator: 'and', conditions: [{ field: 'status', operator: '=', value: 'active' }] }\n * });\n *\n * return (\n * <Sdk.Components.DataTable\n * table={table}\n * isLoading={isLoading}\n * isError={isError}\n * onRetry={refetch}\n * searchable\n * filters={filters}\n * onRowClick={(row) => Sdk.router.visit(students.urls.show(row.id))}\n * />\n * );\n * ```\n */\n useResourceDataTable<\n TResource extends ResourceDataTableResource,\n const TRelations extends readonly SdkResourceRelationPath<NoInfer<TResource>>[] = readonly []\n >(\n options: UseResourceDataTableOptions<TResource, TRelations>\n ): UseResourceDataTableResult<SdkResourceRecord<TResource, TRelations>>;\n\n /**\n * Manages calendar view mode, focal date, the visible window, title, and navigation.\n * Pair its `windowStart` and `windowEnd` with `useCalendarEvents`.\n *\n * @example\n * ```tsx\n * const cal = Sdk.hooks.useCalendarView({ initialView: 'month' });\n * return <Sdk.Components.Calendar events={events} view={cal.view} date={cal.date} />;\n * ```\n */\n useCalendarView(options?: {\n initialView?: 'month' | 'week' | 'day';\n initialDate?: Date;\n }): UseCalendarViewResult;\n\n /**\n * Fetches calendar events for a resource and normalises them to the canonical event shape.\n * Pass a stable `from`/`to` window, such as `useCalendarView`'s `windowStart`/`windowEnd`.\n * A window built from `new Date()` during render changes on every render and makes the\n * hook request without end.\n *\n * A resource whose relations point at Schedulable records returns one event for each\n * related Schedulable record, with the same `id`. A booking of a therapist, a room, and a\n * client is three events, one for each calendar, and `event.record.$calendar.id` names the\n * calendar. Show one calendar at a time with a selector, or keep the first event of each\n * `id`.\n *\n * @example\n * ```tsx\n * const { events, isLoading } = Sdk.hooks.useCalendarEvents({\n * resourceId: 'YOUR_RESOURCE_ID',\n * from: cal.windowStart,\n * to: cal.windowEnd\n * });\n * const seen = new Set<string>();\n * const oneEventPerBooking = events.filter((event) => {\n * if (seen.has(event.id)) return false;\n * seen.add(event.id);\n * return true;\n * });\n * ```\n */\n useCalendarEvents<\n TResourceId extends string,\n const TRelations extends readonly SdkCatalogRelationPath<TResourceId>[] = readonly []\n >(\n options: Omit<UseCalendarEventsOptions, 'relations' | 'resourceId'> & {\n resourceId: TResourceId;\n relations?: TRelations;\n }\n ): UseCalendarEventsResult<SdkCatalogRecord<TResourceId, TRelations>>;\n\n /**\n * Manages an optimistic placeholder event while a calendar create flow is open.\n *\n * @example\n * ```tsx\n * const newEvent = Sdk.hooks.useNewCalendarEvent({ placeholderTitle: 'New Lesson' });\n * return <Sdk.Components.Calendar events={newEvent.mergeEvents(events)} />;\n * ```\n */\n useNewCalendarEvent(options?: UseNewCalendarEventOptions): UseNewCalendarEventResult;\n\n /**\n * Hook that loads the schedulable resources (\"calendars\" \u2014 staff, rooms, users) tied to an\n * eventable resource, owns the multi-select state, and composes the selection into a `filters`\n * object ready to hand to `Sdk.hooks.useCalendarEvents`.\n *\n * @remarks\n * Returns `groups` (the selectable calendars, grouped by schedulable resource), `isLoading`,\n * `selectedIds`, `setSelectedIds`, and `filters`. By default every calendar is selected.\n * Pass `groups`/`isLoading`/`selectedIds`/`setSelectedIds` to `Sdk.Components.CalendarSelector`\n * and pass `filters` straight to `Sdk.hooks.useCalendarEvents({ filters })` \u2014 the OR-group fan-out\n * across each calendar's relation paths is handled for you. Selection is in-memory and resets\n * on reload. Provide your own base `filters` to AND additional constraints onto the query.\n *\n * @example\n * ```tsx\n * const cal = Sdk.hooks.useCalendarView();\n * const calendars = Sdk.hooks.useCalendarSelection({ resourceId: 'SESSIONS_ID' });\n * const { events } = Sdk.hooks.useCalendarEvents({\n * resourceId: 'SESSIONS_ID',\n * from: cal.windowStart,\n * to: cal.windowEnd,\n * filters: calendars.filters\n * });\n *\n * return (\n * <div>\n * <Sdk.Components.CalendarSelector\n * groups={calendars.groups}\n * isLoading={calendars.isLoading}\n * selectedIds={calendars.selectedIds}\n * onChange={calendars.setSelectedIds}\n * />\n * <Sdk.Components.Calendar events={events} view={cal.view} date={cal.date} />\n * </div>\n * );\n * ```\n */\n useCalendarSelection(options: UseCalendarSelectionOptions): UseCalendarSelectionResult;\n\n /**\n * Reactive workspace field values for the given keys. Returns one object keyed by the\n * requested keys; an unknown key is `undefined`. Values update after a\n * `WorkspaceFieldsForm` save on this page. Prefer this over `Sdk.workspace.values`,\n * which is a load-time snapshot.\n *\n * The page type check gives each requested value its workspace field's type from the\n * workspace field catalog (`index_workspace_fields` lists the keys and types): for\n * example, a text or email field reads as a `string`. Use the value as that type. Do not\n * add `typeof` checks; the `unknown` in the signature applies only to keys outside the\n * catalog.\n *\n * @example\n * ```tsx\n * const { registration_mode, admin_notification_email } = Sdk.hooks.useWorkspaceValues([\n * 'registration_mode',\n * 'admin_notification_email'\n * ]);\n * ```\n */\n useWorkspaceValues<K extends keyof WorkspaceFieldCatalog & string>(keys: readonly K[]): Pick<WorkspaceFieldCatalog, K>;\n\n /**\n * Reads the sidebar shell state from the nearest `Sdk.Components.SidebarProvider`.\n * Returns the expanded/collapsed `state`, mobile flags, and `toggleSidebar` for\n * building collapse controls. Throws when called outside a SidebarProvider.\n *\n * @example\n * ```tsx\n * function CollapseButton() {\n * const { state, toggleSidebar } = Sdk.hooks.useSidebar();\n * const collapsed = state === 'collapsed';\n * return (\n * <Sdk.Components.SidebarMenuButton onClick={toggleSidebar}>\n * <Sdk.Components.Icon name={collapsed ? 'PanelLeftOpen' : 'PanelLeftClose'} className=\"size-4\" />\n * <span>{collapsed ? 'Expand' : 'Collapse'}</span>\n * </Sdk.Components.SidebarMenuButton>\n * );\n * }\n * ```\n */\n useSidebar(): {\n state: 'expanded' | 'collapsed';\n open: boolean;\n setOpen: (open: boolean) => void;\n openMobile: boolean;\n setOpenMobile: (open: boolean) => void;\n isMobile: boolean;\n toggleSidebar: () => void;\n };\n\n /**\n * Headless state machine for multi-step / wizard forms. Tracks the active step and exposes\n * free navigation (`goTo`/`next`/`back`) \u2014 no validation or gating. Use this for custom step\n * chrome; for the default indicator + Previous/Next UI, use `Sdk.Components.Stepper` instead.\n *\n * @aiHidden Kept on the runtime SDK for custom-chrome pages, but omitted from the generated\n * SDK reference \u2014 the code-builder agent should use the `steps` prop on\n * `Sdk.Components.CreateResourceRecordForm` / `UpdateResourceRecordForm` for multi-step\n * record forms, or `Sdk.Components.Stepper` for wizards that are not record forms.\n *\n * @example\n * ```tsx\n * const { step, steps, label, next, back, isFirst, isLast } = Sdk.hooks.useStepper([\n * 'General', 'Education', 'Review'\n * ]);\n * ```\n */\n useStepper(steps: readonly string[], options?: UseStepperOptions): UseStepperResult;\n\n /**\n * Persist JSON-safe UI state in the browser. Returns `[value, setter]`. The setter\n * accepts a value or functional update; setting `null` removes the stored value.\n * Persistence and synchronization follow the repository's standard local-storage hook.\n *\n * The supplied key is used directly. Choose a stable, page-specific key to avoid\n * collisions with other pages or application features.\n *\n * Use only for non-sensitive UI preferences such as dismissed notices, selected tabs,\n * filters, and display density. Never store secrets, authentication data or tokens,\n * PII, resource records, or sensitive drafts in local storage. Logout clears all stored\n * values.\n *\n * @example\n * ```tsx\n * const [density, setDensity] = Sdk.hooks.useLocalStorage('table-density', 'comfortable');\n *\n * setDensity('compact');\n * setDensity((current) => current === 'compact' ? 'comfortable' : 'compact');\n * setDensity(null); // Remove the stored value.\n * ```\n */\n useLocalStorage(\n key: string,\n initial: string\n ): readonly [value: string | null, setter: SdkLocalStorageSetter<string>];\n useLocalStorage(\n key: string,\n initial: boolean\n ): readonly [value: boolean | null, setter: SdkLocalStorageSetter<boolean>];\n useLocalStorage(\n key: string,\n initial: number\n ): readonly [value: number | null, setter: SdkLocalStorageSetter<number>];\n useLocalStorage<TValue extends SdkLocalStorageValue>(\n key: string,\n initial: TValue\n ): SdkLocalStorageResult<TValue>;\n\n /**\n * Read and control the app theme (system / light / dark). Returns the resolved `theme`\n * currently applied \u2014 reactive, so the component re-renders when it changes \u2014 plus\n * the saved `mode` preference (`'system'` follows the device setting) and the\n * `setTheme` / `toggleTheme` setters. `toggleTheme` cycles system \u2192 dark \u2192 light.\n * The choice persists across visits and applies to the whole app, not just the current\n * page. For the standard toggle button, use `Sdk.Components.ThemeToggle` \u2014 reach for\n * this hook when the page needs custom theme UI.\n *\n * @example\n * ```tsx\n * function ThemeToggle() {\n * const { mode, toggleTheme } = Sdk.hooks.useTheme();\n *\n * return (\n * <Sdk.Components.Button variant=\"ghost\" onClick={toggleTheme}>\n * Theme: {mode}\n * </Sdk.Components.Button>\n * );\n * }\n * ```\n */\n useTheme(): UseThemeResult;\n};\n\ntype QueryParams = Record<string, string | string[]>;\n\ntype SetQueryParams<TParams extends Record<string, any>> = (\n next: QueryParamsPatch<TParams> | ((current: TParams) => QueryParamsPatch<TParams>)\n) => void;\n\ntype QueryParamsPatch<TParams extends Record<string, any>> = Partial<{\n [TKey in keyof TParams]: QueryParamSetValue<TParams[TKey]>;\n}>;\n\ntype QueryParamSetValue<TValue> = DefinedQueryParamSetValue<NonNullable<TValue>>;\n\ntype DefinedQueryParamSetValue<TValue> = TValue extends number\n ? number | null | undefined\n : TValue extends boolean\n ? boolean | null | undefined\n : TValue extends readonly string[]\n ? readonly string[] | null | undefined\n : string | null | undefined;\n\ntype QueryParamSchema = Record<string, QueryParamSchemaEntry>;\n\ntype QueryParamSchemaEntry =\n | { type?: 'string'; default?: string }\n | { type: 'number'; default?: number }\n | { type: 'boolean'; default?: boolean }\n | { type: 'array'; default?: readonly string[] };\n\ntype QueryParamsFromSchema<TSchema extends QueryParamSchema> = {\n [TKey in keyof TSchema]: TSchema[TKey] extends { default: any }\n ? QueryParamValue<TSchema[TKey]>\n : QueryParamValue<TSchema[TKey]> | undefined;\n};\n\ntype QueryParamValue<TEntry extends QueryParamSchemaEntry> = TEntry extends { type: 'number' }\n ? number\n : TEntry extends { type: 'boolean' }\n ? boolean\n : TEntry extends { type: 'array' }\n ? string[]\n : string;\n\ntype SdkUseSharedFiltersOptions = any;\n\ntype SharedFiltersApi = SharedFilterViewState & {\n /** Views the viewer may use or manage, subject to resource policy visibility. */\n available: SharedFilter[];\n isLoading: boolean;\n selectedId: string | null;\n selected: SharedFilter | null;\n /** Applies the stored view state; `null` resets everything to defaults. */\n select: (sharedFilterId: string | null) => void;\n setFilter: (filter: FilterGroup | null) => void;\n /** Replaces the ordered sort list. An empty list clears the sort. */\n setSorts: (sorts: ResourceSortDescriptor[]) => void;\n /** Replaces the whole sort list with this one sort, or clears it when `null`. */\n setSort: (sort: ResourceSortDescriptor | null) => void;\n setSearch: (search: string) => void;\n setPerPage: (perPage: number | null) => void;\n setColumns: (columns: string[] | null) => void;\n /**\n * The view state has been edited away from the selected saved view, or from the\n * defaults when no saved view is selected. A selection that has not loaded yet is\n * never dirty.\n */\n isDirty: boolean;\n /** Re-applies the stored view state, discarding local edits. */\n reset: () => void;\n /** Whether the viewer may save the current view state as a new private view. */\n canCreate: boolean;\n /** Whether the viewer may choose a saved view's sharing scope (the Manage Shared Filters permission). */\n canShare: boolean;\n /**\n * Saves the current view state as a new saved view and selects it. Private by\n * default; `isPrivate: false` shares it with everyone (honored only when `canShare`).\n */\n create: (input: { name: string; isPrivate?: boolean }) => Promise<SharedFilter>;\n isCreating: boolean;\n /** Overwrites the selected view state with the current one. */\n update: () => Promise<SharedFilter>;\n isUpdating: boolean;\n /** Deletes a saved view the viewer manages; the selection falls back to defaults. */\n remove: (sharedFilterId: string) => Promise<void>;\n /**\n * Renames a saved view the viewer manages \u2014 and, when `canShare`, optionally\n * changes its sharing scope. Never sends the view state.\n */\n rename: (\n sharedFilterId: string,\n input: { name: string; isPrivate?: boolean }\n ) => Promise<SharedFilter>;\n};\n\ntype SharedFilterViewState = {\n /** Filter conditions, in the records API's `$filter` shape. */\n filter: FilterGroup | null;\n /** Ordered sort, primary first, directly usable as a `useRecords` sort input. Empty = not configured. */\n sorts: ResourceSortDescriptor[];\n /** The primary sort (`sorts[0]`), or `null` when no sort is configured. */\n sort: ResourceSortDescriptor | null;\n /** Free-text search. Empty string = no search. */\n search: string;\n /** Page size, or `null` for the consumer's default. */\n perPage: number | null;\n /** Visible table column identifiers, including custom IDs, in display order; `null` uses defaults. */\n columns: string[] | null;\n};\n\ntype FilterGroup = {\n operator: LogicalOperator;\n conditions: FilterNode[];\n when?: boolean | Resolver | null;\n};\n\ntype LogicalOperator = 'and' | 'or' | 'nor';\n\ntype FilterNode = FilterGroup | FilterCondition;\n\ntype FilterCondition = {\n field: any;\n operator: string;\n value: any;\n when?: boolean | Resolver | null;\n};\n\ntype Resolver = {\n $type: string;\n $value: any;\n};\n\ntype SharedFilter = SharedFiltersResponse['data'][number];\n\ntype SharedFiltersResponse = any;\n\ntype DeleteResourceRecordResource = Pick<\n SdkResource,\n 'id' | 'name' | 'disabled_data_api_endpoints'\n> & {\n urls: Pick<ResourceUrls, 'delete'>;\n};\n\ninterface DeleteResourceRecordState {\n preview: PreviewData | undefined;\n isPreviewLoading: boolean;\n isPreviewError: boolean;\n confirm: (operation?: OperationMetadata) => void;\n isProcessing: boolean;\n /**\n * Structured error body returned by the API on a blocked delete response.\n * `null` for successful deletes and generic transport errors.\n */\n deleteError: DeleteResourceRecordErrorBody | null;\n /** Normalized failed delete request, or `null`. */\n error: SdkApiError | null;\n reset: () => void;\n}\n\ntype PreviewData = any;\n\ntype OperationMetadata = Record<string, Record<string, any>>;\n\ntype DeleteResourceRecordErrorBody = PreviewData & {\n title: string;\n status: 409;\n detail: string;\n};\n\ntype RestoreResourceRecordResource = Pick<\n SdkResource,\n 'id' | 'name' | 'disabled_data_api_endpoints'\n> & {\n urls: Pick<ResourceUrls, 'restore'>;\n};\n\ninterface RestoreResourceRecordState {\n preview: PreviewData | undefined;\n isPreviewLoading: boolean;\n isPreviewError: boolean;\n confirm: () => void;\n isProcessing: boolean;\n /**\n * Transport error from the restore request, surfaced so the dialog can show\n * a 403 (no policy) or 422 (validation) message. `null` on success.\n */\n restoreError: AxiosError | null;\n reset: () => void;\n}\n\ntype AxiosError = any;\n\ntype CreateResourceRecordFormResource = Pick<\n SdkResource,\n 'id' | 'fields' | 'workflow_inputs' | 'name' | 'disabled_data_api_endpoints' | 'capabilities'\n> & {\n urls: Pick<ResourceUrls, 'store'>;\n};\n\ntype RelationVisibility = 'settings' | 'always' | 'attach-only';\n\ntype UseCreateResourceRecordFormResult = {\n /** The form handle \u2014 `data`, `errors`, `fields`, `isSubmitting`, `submit`, `reset`, `setData`. */\n form: SdkResourceForm;\n /**\n * Context-wired field input. Renders the correct editor for a resource field handle;\n * returns `null` for handles that are not part of the active form.\n * Must be rendered inside `Form`.\n */\n FieldInput: (props: FormFieldProps) => React.ReactNode;\n /** Context-wired RecordEvent workflow input. Must be rendered inside `Form`. */\n WorkflowInput: (props: WorkflowInputProps) => React.ReactNode;\n /** Renders every applicable RecordEvent workflow input, stacked in order.\n * Must be rendered inside `Form`. */\n WorkflowInputs: (props: WorkflowInputsProps) => React.ReactNode;\n /** The applicable RecordEvent workflow inputs, keyed by input key. */\n workflowInputs: Record<string, SdkWorkflowInput>;\n /**\n * Form wrapper with `onSubmit` pre-wired to `form.submit`. Provides the context that\n * `FieldInput` reads from. Renders a `<form>` element; accepts `className` and `children`.\n */\n Form: (props: FormWrapperProps) => React.ReactNode;\n};\n\ntype SdkResourceForm = {\n /**\n * Current form data keyed by resource field. `$operation` is reserved for transient\n * mutation metadata: it is validated and delivered to record-event workflows, but\n * is never persisted on or returned as part of the record. Treat it as untrusted\n * trigger input in downstream workflows.\n */\n data: Record<string, any>;\n errors: Record<string, string>;\n fields: SdkResourceField[];\n isSubmitting: boolean;\n submit: () => void;\n reset: (...keys: string[]) => void;\n /**\n * Set a form value. For recurring eventable updates, use\n * `setData('$operation', { eventable: { scope } })` where `scope` is\n * `this`, `this_and_future`, or `all`.\n */\n setData: (key: string, value: any) => void;\n};\n\ntype FormFieldProps = {\n /** A resource field handle (`resource.fields.someField`) or a workspace field\n * definition (`Sdk.workspace.fields.someField`). */\n field: SdkFormField;\n /** Optional wrapper className. */\n className?: string;\n};\n\ntype WorkflowInputProps = {\n /** A workflow input handle from the render state's `workflowInputs.<key>` map\n * (or `resource.workflow_inputs.<event>.<key>`). */\n input: SdkWorkflowInput;\n /** Optional wrapper className. */\n className?: string;\n};\n\ntype WorkflowInputsProps = {\n /** Optional wrapper className. */\n className?: string;\n};\n\ntype FormWrapperProps = {\n children: React.ReactNode;\n className?: string;\n};\n\ntype UpdateResourceRecordFormResource = Pick<\n SdkResource,\n | 'id'\n | 'fields'\n | 'workflow_inputs'\n | 'name'\n | 'disabled_data_api_endpoints'\n | 'capabilities'\n | 'singular_name'\n> & {\n urls: Pick<ResourceUrls, 'show' | 'update'>;\n};\n\ntype UseUpdateResourceRecordFormResult = {\n /** The form handle \u2014 `data`, `errors`, `fields`, `isSubmitting`, `isLoading`, `submit`, `reset`, `setData`. */\n form: SdkResourceForm & { isLoading: boolean };\n /**\n * Context-wired field input. Renders the correct editor for a resource field handle;\n * returns `null` for handles that are not part of the active form.\n * Must be rendered inside `Form`.\n */\n FieldInput: (props: FormFieldProps) => React.ReactNode;\n /** Context-wired RecordEvent workflow input. Must be rendered inside `Form`. */\n WorkflowInput: (props: WorkflowInputProps) => React.ReactNode;\n /** Renders every applicable RecordEvent workflow input, stacked in order.\n * Must be rendered inside `Form`. */\n WorkflowInputs: (props: WorkflowInputsProps) => React.ReactNode;\n /** The applicable RecordEvent workflow inputs, keyed by input key. */\n workflowInputs: Record<string, SdkWorkflowInput>;\n /**\n * Form wrapper with `onSubmit` pre-wired to `form.submit`. Provides the context that\n * `FieldInput` reads from. Renders a `<form>` element; accepts `className` and `children`.\n */\n Form: (props: FormWrapperProps) => React.ReactNode;\n /**\n * Ready-to-render prompt for pending capability-aware interactions. Render this\n * next to `Form`; it is `null` unless `form.submit()` needs extra operation metadata\n * before saving. For recurring eventable updates, this prompts for\n * `$operation.eventable.scope` and then completes the submit.\n */\n interaction: React.ReactNode;\n};\n\ntype SdkWorkflowResponse = SdkWorkflowOutput | SdkWorkflowPendingResponse;\n\ntype SdkWorkflowOutput =\n | string\n | number\n | boolean\n | null\n | any[]\n | Record<string, any>;\n\ntype SdkWorkflowPendingResponse = { execution_id: string };\n\ntype UseWorkflowFormResult = {\n /** The form handle \u2014 `data`, `errors`, `fields`, `isSubmitting`, `isReady`, `submit`, `reset`, `setData`. */\n form: SdkWorkflowForm;\n /**\n * Context-wired field input. Renders the correct editor for a schema field handle\n * from `fields`; returns `null` for handles that are not part of the active form.\n * Must be rendered inside `Form`.\n */\n FieldInput: React.ComponentType<FormFieldProps>;\n /**\n * Form wrapper with `onSubmit` pre-wired to `form.submit`. Provides the context that\n * `FieldInput` reads from. Renders a `<form>` element; accepts `className` and `children`.\n */\n Form: React.ComponentType<FormWrapperProps>;\n /** The projected schema fields (the same array as `form.fields`). */\n fields: SdkResourceField[];\n /** The workflow's uppercased HTTP method, or an empty string when unavailable. */\n method: string;\n /** `false` when the workflow has no published route settings (unpublished, paused, or unknown id). */\n isAvailable: boolean;\n};\n\ntype SdkWorkflowForm = {\n data: Record<string, any>;\n /** Field-validation messages only. HTTP 409 conflicts and every other\n * non-validation failure are exposed through `failure`, never here. */\n errors: Record<string, string>;\n /** The latest non-validation submission failure, or `null`. Read `status` / `body`,\n * or classify it with `Sdk.utils.error.isConflict`, `isForbidden`, or `isNotFound`. */\n failure: SdkApiError | null;\n fields: SdkResourceField[];\n isSubmitting: boolean;\n isReady: boolean;\n submit: () => void;\n /** Reset the requested values and clear `failure`; a full reset also clears field errors. */\n reset: (...keys: string[]) => void;\n setData: (key: string, value: any) => void;\n /** Replace or update the error map \u2014 pass a complete map or a functional updater.\n * Useful for client-side validation; server 422s populate `errors` automatically. */\n setErrors: (\n errors: Record<string, string> | ((current: Record<string, string>) => Record<string, string>)\n ) => void;\n /** Pass to a `<form>` `onSubmit` when managing the form tag yourself \u2014 calls\n * `preventDefault` and submits. The returned `Form` wrapper already wires this. */\n handleSubmit: (event?: React.FormEvent<HTMLFormElement>) => void;\n};\n\ntype HTMLFormElement = any;\n\ntype SdkUseWorkspaceFieldsFormOptions = {\n /** Workspace field definitions to render, in order. Omit to render every field that is\n * not hidden and not read-only. Undefined, read-only, and hidden entries are dropped. */\n fields?: Array<SdkWorkspaceField | undefined>;\n /** Editable seed values that override the workspace's current values. */\n initialValues?: Record<string, any>;\n /** Locked values submitted even when omitted from `fields`; rendered disabled. Keys of\n * read-only or hidden definitions are dropped. */\n fixedValues?: Record<string, any>;\n /** Called after a successful save with the saved values keyed by field key. */\n onSuccess?: (values: SdkWorkspaceValues) => void;\n};\n\ntype UseWorkspaceFieldsFormResult = {\n form: SdkWorkspaceFieldsForm;\n /** The active field definitions, in render order. */\n fields: SdkWorkspaceField[];\n /** Whether a save can happen: the identity holds `update_workspace_fields` or `manage_workspace_fields` and at\n * least one field or fixed value is active. `submit()` is a no-op otherwise. */\n canSubmit: boolean;\n /** Context-wired input for one workspace field. Must be rendered inside `Form`. */\n FieldInput: (props: FormFieldProps) => React.ReactNode;\n /** Form wrapper with `onSubmit` pre-wired to `form.submit`. */\n Form: (props: FormWrapperProps) => React.ReactNode;\n};\n\ntype SdkWorkspaceFieldsForm = {\n data: Record<string, any>;\n errors: Record<string, string>;\n fields: SdkWorkspaceField[];\n isSubmitting: boolean;\n submit: () => void;\n reset: (...keys: string[]) => void;\n setData: (key: string, value: any) => void;\n};\n\ntype UseWorkflowActionResult = {\n /** Run the workflow with these inputs. Resolves with the parsed response on success\n * and `null` on failure; failures never reject into an unhandled click promise. */\n run: (inputs: Record<string, any>) => Promise<SdkWorkflowResponse | null>;\n /** `true` while the latest run is in flight. */\n isPending: boolean;\n /** Display-ready message for the latest failure, or `null`. */\n errorMessage: string | null;\n /** Structured latest failure. Classify it with `Sdk.utils.error`, including\n * `Sdk.utils.error.isConflict(failure)`. */\n failure: SdkApiError | null;\n /** Parsed response from the latest successful run, or `null`. */\n result: SdkWorkflowResponse | null;\n /** Clear submission state, ignore any in-flight response, and start a fresh\n * managed-idempotency lifecycle. */\n reset: () => void;\n};\n\ntype SdkUseRecordScheduleTimelinesOptions = {\n /** Resource the record belongs to. */\n resourceId: string;\n /** Record whose schedules are fetched. Nullish keeps the hook idle. */\n recordId: string | null | undefined;\n /** Limit the result to a single record-schedule workflow. */\n recordScheduleWorkflowId?: string;\n /**\n * Refetch the timelines on an interval. The function form receives the current\n * timelines and returns `false` to stop. Omit to fetch once per mount.\n *\n * @example\n * ```tsx\n * Sdk.hooks.useRecordScheduleTimelines({\n * resourceId,\n * recordId,\n * pollInterval: (timelines) => (timelines.length ? 60000 : false)\n * });\n * ```\n */\n pollInterval?: SdkPollInterval<SdkRecordScheduleTimeline[]>;\n};\n\ntype SdkRecordScheduleTimeline = {\n workflow_id: string;\n workflow_title: string;\n /** Label of the workflow's anchor date field, e.g. \"Due Date\". */\n anchor_date_label: string;\n /** The record's anchor date value, or `null` when unset or hidden from the viewer. */\n anchor_date_value: string | null;\n /** The workflow's offsets in chronological order. */\n offsets: SdkRecordScheduleTimelineOffset[];\n};\n\ntype SdkRecordScheduleTimelineOffset = {\n /** Signed ISO-8601 duration relative to the anchor date, e.g. `-P7D` for 7 days before. */\n offset: string;\n /** Absolute ISO datetime of the run, or `null` when the anchor date is unset or hidden. */\n scheduled_run_at: string | null;\n /** Pending, dispatched, automatically skipped, or manually skipped by a caller. */\n state: 'pending' | 'sent' | 'skipped' | 'manually_skipped';\n /** `true` on the soonest offset that is still pending \u2014 the upcoming run. */\n is_next: boolean;\n};\n\ntype SdkRecordScheduleTimelinesHandle = {\n /** Skip an upcoming offset. Requires Show and Update access to the record. Refreshes all its timeline queries. */\n skipOffset: (input: {\n workflowId: string;\n offset: string;\n /** Reject a stale selection if the reminder has moved since it was displayed. */\n scheduledRunAt?: string;\n }) => Promise<{ data: SdkRecordScheduleTimeline[] }>;\n isSkipping: boolean;\n /** One timeline per record-schedule workflow the record participates in. */\n timelines: SdkRecordScheduleTimeline[];\n isLoading: boolean;\n isError: boolean;\n refetch: () => Promise<any>;\n};\n\ntype UseDataTableOptions<TData extends RowData> = {\n /** Rows to render. Nullish is treated as an empty table, so loading data can be passed straight through. */\n data: TData[] | null | undefined;\n columns: DataTableColumn<TData>[];\n initialPageSize?: number;\n /**\n * Prepend a checkbox column so end users can select rows for bulk actions. Pair with\n * `DataTable`'s `bulkActions` prop or read the returned selection helpers. Selection\n * is keyed by each row's `id` (falling back to its index), so it survives sorting,\n * filtering, and pagination.\n */\n enableRowSelection?: boolean;\n /**\n * Keeps the table state in the page URL under this query parameter, so that the\n * browser Back button, a reload, or a shared link restores it. The state includes\n * the sort, the search, the page size, the visible columns, and the page number.\n * The value is encoded. `true` uses the key `table`. Give each table on a page a\n * different key; two tables with the same key throw. Omit the key to keep the state\n * in memory only.\n *\n * @example\n * urlStateKey: 'events'\n */\n urlStateKey?: string | true;\n};\n\ntype RowData = any;\n\ntype DataTableColumn<TData extends RowData> = {\n /** Stable column id. Defaults to `accessor` when omitted. */\n id?: string;\n /** Key on the row whose value is read for display, sorting, and filtering. */\n accessor?: Extract<keyof TData, string>;\n /** Column header label. */\n header: string;\n /** Custom cell renderer; receives the row's original data. */\n cell?: (row: TData) => ReactNode;\n /** Defaults to `true` when an `accessor` is present, `false` otherwise. */\n enableSorting?: boolean;\n /** Defaults to `true`. */\n enableHiding?: boolean;\n} & DataTableColumnSizing;\n\ntype ReactNode = React.ReactNode;\n\ntype DataTableColumnSizing = {\n /** Explicit column width in px. Columns without it wrap their cells at 240px. */\n width?: number;\n /**\n * Classes for the box that holds each body cell's content. A `max-w-*` class here replaces\n * the 240px default, for example `max-w-80`, `max-w-none`, or `max-w-40 md:max-w-80`.\n */\n className?: string;\n};\n\ntype UseDataTableResult<TData extends RowData> = {\n table: Table<TData>;\n /** Ids of every selected row (its `id`, or index when absent). */\n selectedRowIds: string[];\n /** The selected rows' original data. */\n selectedRows: TData[];\n /** Number of selected rows. */\n selectedCount: number;\n /** Clears the current selection. */\n clearSelection: () => void;\n};\n\ntype Table<TData = any> = {\n readonly __sdkDataTableRowType?: (row: TData) => TData;\n};\n\ntype ResourceDataTableResource = SdkResourceType &\n Pick<SdkResource, 'id' | 'fields' | 'name' | 'policy_scope' | 'disabled_data_api_endpoints'>;\n\ntype UseResourceDataTableOptions<\n TResource extends ResourceDataTableResource = ResourceDataTableResource,\n TRelations extends readonly SdkResourceRelationPath<NoInfer<TResource>>[] = readonly []\n> = {\n /** Resource handle returned by `Sdk.resource('<resourceId>')`. */\n resource: TResource;\n /** Resource fields or custom column definitions to render. */\n columns?: ResourceDataTableColumn<SdkResourceRecord<TResource, TRelations>>[];\n /** Fields that start visible. ID, Created At, and Updated At are hidden by default. All other policy-permitted fields remain available to show. */\n initialFields?: readonly TResource['fields'][Extract<keyof TResource['fields'], string>][];\n /** Customizes the generated columns for named policy-permitted resource fields. */\n fieldOverrides?: ResourceDataTableFieldOverrides<TResource, TRelations>;\n /**\n * Renders controls for each record in a dedicated trailing column. The column is\n * always visible and does not appear in the column selector.\n */\n actions?: (row: SdkResourceRecord<TResource, TRelations>) => ReactNode;\n /** Number of records to show on each page. Defaults to `10`. */\n initialPageSize?: number;\n /**\n * Ordered sort, primary first, applied when no saved view is selected. The table\n * opens with it unless `urlStateKey` restores a state, and returns to it when the\n * viewer clears the selected saved view. The viewer can change or remove it from\n * the column headers. The unchanged initial sort is not an edit, so the table offers\n * \"Save view\" only after the viewer changes the view. Each `key` must be the sort key of a column in the table; a\n * hidden column counts. Other keys are ignored. Defaults to no sort.\n *\n * @example\n * initialSort: [{ key: 'created_at', direction: 'desc' }]\n */\n initialSort?: readonly ResourceSortDescriptor[];\n /** Soft-delete subset to read from. Defaults to active records only. */\n scope?: RecordScope;\n /** Relation paths to preload alongside record data. Augments relations derived from columns. */\n relations?: TRelations;\n /**\n * Author-defined base filter applied to every query (a `$filter` group or a list of\n * conditions). Invisible and unremovable in the filter UI \u2014 anything the end user\n * filters by is ANDed on top, so it can only narrow this subset. Filter relation\n * fields by nested scalar paths such as `family.id`, not by the relation object path\n * (`family`). Keep the filter the same between renders: a clock read such as\n * `Sdk.utils.time.current()` in it makes a new query on every render, and the table\n * requests without end. See `useRecords`.\n *\n * @example\n * { operator: 'and', conditions: [{ field: 'family.id', operator: '=', value: familyId }] }\n */\n filters?: Record<string, any> | any[] | null;\n /**\n * Compose end-user filtering into queries: the runtime filter (filter chips and the\n * advanced-filters dialog) plus the resource's saved views, which render a selector\n * in `DataTable`'s default toolbar. Picking a saved view loads its conditions into\n * the runtime filter as editable chips, exactly as if the viewer had built them by\n * hand; edits stay local until reset or saved as a new private view. The\n * saved view list is viewer-specific: shared views require the Index Records scope;\n * private views also require ownership or Manage Shared Filters. Defaults to `true`.\n */\n enableFilters?: boolean;\n /**\n * Prepend a checkbox column so end users can select rows for bulk actions. Pair with\n * `DataTable`'s `bulkActions` prop or read the returned selection helpers.\n */\n enableRowSelection?: boolean;\n /**\n * Keeps the table state in the page URL under this query parameter, so that the\n * browser Back button, a reload, or a shared link restores it. The state includes\n * the selected saved view, the filter, the sort, the search, the page size, the\n * visible columns, and the page number. The value is encoded. `true` uses the\n * resource slug as the key. Give each table on a page a different key; two tables\n * with the same key throw. Omit the key to keep the state in memory only.\n *\n * @example\n * urlStateKey: 'students'\n */\n urlStateKey?: string | true;\n};\n\ntype ResourceDataTableColumn<TRecord extends ResourceRecord = ResourceRecord> =\n | SdkResourceField\n | ResourceDataTableFieldColumn\n | ResourceDataTableCustomColumn<TRecord>;\n\ntype ResourceDataTableFieldColumn = {\n field: SdkResourceField;\n header?: string;\n enableSorting?: boolean;\n enableHiding?: boolean;\n} & DataTableColumnSizing;\n\ntype ResourceDataTableCustomColumn<TRecord extends ResourceRecord = ResourceRecord> = {\n id: string;\n header: string;\n cell: (row: TRecord, helpers: ResourceDataTableCellHelpers) => ReactNode;\n /** Maps the column to a server `$sort` key. Omit to make the column non-sortable. */\n sortKey?: string;\n enableSorting?: boolean;\n enableHiding?: boolean;\n} & DataTableColumnSizing;\n\ntype ResourceDataTableCellHelpers = {\n /** Renders a field's value from the current row through the type-aware display registry. */\n FieldValue: React.ComponentType<{ field: SdkResourceField; className?: string }>;\n /** Renders a field's label text. */\n FieldLabel: React.ComponentType<{ field: SdkResourceField; className?: string }>;\n};\n\ntype ResourceDataTableFieldOverrides<\n TResource extends SdkResourceType & Pick<SdkResource, 'fields'> = SdkResourceType &\n Pick<SdkResource, 'fields'>,\n TRelations extends readonly SdkResourceRelationPath<TResource>[] = readonly []\n> = Partial<{\n [TFieldName in Extract<keyof TResource['fields'], string>]: {\n /** Replaces the field's default type-aware cell renderer. */\n cell?: (\n row: SdkResourceRecord<\n TResource,\n readonly [...TRelations, TFieldName & SdkResourceRelationPath<TResource>]\n >,\n helpers: ResourceDataTableCellHelpers\n ) => ReactNode;\n };\n}>;\n\ntype UseResourceDataTableResult<TRecord extends ResourceRecord = ResourceRecord> = {\n table: Table<TRecord>;\n isLoading: boolean;\n totalRecords: number;\n /**\n * True when the records request failed. Pass to `DataTable`'s `isError` prop so\n * the failure shows as an error row instead of an empty \"0 records\" table.\n */\n isError: boolean;\n /**\n * Refetches the current page of records. Pass to `DataTable`'s `onRetry` prop so\n * the error row offers a Retry button. Mutations made through the resource\n * handle (`resource.create/update/delete`) refresh the table automatically \u2014\n * reach for this otherwise only after mutating through other channels (e.g. raw\n * `Sdk.api` calls against custom endpoints).\n */\n refetch: () => Promise<any>;\n /** Runtime filter state for `DataTable`'s `filters` prop / `DataTableFilters`. */\n filters: DataTableFiltersApi;\n /**\n * Ids of every selected record, stable across pagination (keyed by record id).\n * Prefer this for bulk actions \u2014 `selectedRows` only resolves the loaded page.\n */\n selectedRowIds: string[];\n /** Selected records on the currently loaded page. Excludes selections on other pages. */\n selectedRows: TRecord[];\n /** Number of selected records across all pages. */\n selectedCount: number;\n /** Clears the current selection. */\n clearSelection: () => void;\n};\n\ntype DataTableFiltersApi = {\n /** Whether end-user filter controls are available. Defaults to `true`. */\n filteringEnabled?: boolean;\n appliedFilter: FilterGroup | null;\n appliedConditionCount: number;\n apply: (filter: FilterGroup | null) => void;\n clear: () => void;\n /** Disables the dialog trigger when filtering isn't possible yet (e.g. no resource). */\n disabled?: boolean;\n /** Renders the condition editor inside the dialog; receives the draft from the dialog. */\n renderEditor: (editorProps: AdvancedFiltersEditorProps) => ReactNode;\n /**\n * Field + operator catalog for the quick-filter chip bar. When present, `DataTable`'s\n * default layout renders the chip bar instead of the standalone \"Filters\" button\n * (the advanced dialog stays reachable through the chips). Omitted by adapters that\n * can't offer chips.\n */\n quickFilters?: DataTableQuickFiltersApi;\n /**\n * Headless shared-filter state for the backing resource (`useSharedFilters`). When\n * present (resource-backed tables with filtering enabled), `DataTable`'s default\n * layout renders the shared-filter selector in the toolbar. Omitted by adapters that\n * can't offer shared filters.\n */\n sharedFilters?: SharedFiltersApi;\n /**\n * CSV-export state for the backing resource. When present (resource-backed tables\n * whose resource has the export endpoint enabled), `DataTable`'s `isExportable` prop\n * renders the Export control in the default toolbar. Omitted by adapters that\n * cannot offer server exports (e.g. seeded tables).\n */\n export?: DataTableExportApi;\n /**\n * Turns a plain-language description into filter conditions for the backing resource.\n * When present (resource-backed tables with filtering enabled), `DataTable`'s default\n * toolbar renders the \"Ask AI\" filter control for viewers who may use the AI assistant.\n * It resolves to conditions the server already checked; the control adds them to the\n * current filter. Omitted by adapters that cannot ask the server (e.g. seeded tables).\n */\n generateFilter?: (prompt: string) => Promise<GeneratedRecordFilter>;\n};\n\ntype AdvancedFiltersEditorProps = {\n draftFilter: FilterGroup;\n onDraftFilterChange: (filter: FilterGroup) => void;\n};\n\ntype DataTableQuickFiltersApi = {\n fields: QuickFilterField[];\n operators: Record<string, FilterOperatorData>;\n};\n\ntype QuickFilterField = {\n path: string;\n label: string;\n type: VariableType;\n schema: SchemaField;\n /** Related resource id when the chip filters a relation via its nested `.id` path. */\n resource?: string | null;\n /** The related resource's display field, for labeling picked records. */\n display_field_key?: string | null;\n};\n\ntype VariableType = 'boolean' | 'color' | 'component_reference' | 'content' | 'date' | 'datetime' | 'dictionary' | 'domain_reference' | 'dynamic' | 'email' | 'email_layout_reference' | 'event_schedule' | 'field_reference' | 'file_reference' | 'filter' | 'form' | 'guard' | 'identity' | 'liquid' | 'list' | 'location' | 'markdown' | 'money' | 'notification_template_reference' | 'notification_type_reference' | 'null' | 'number' | 'package_input_reference' | 'page_reference' | 'password' | 'phone' | 'point' | 'record' | 'record_reference' | 'recurrence' | 'regular_hours' | 'relation' | 'resolver' | 'resource_field_key_reference' | 'resource_field_value' | 'resource_reference' | 'role_reference' | 'schema' | 'select' | 'text' | 'time' | 'timezone' | 'unknown' | 'url' | 'uuid' | 'wallet_accounting_account' | 'wallet_accounting_entity' | 'workflow_reference' | 'workflow_state_variable_reference' | 'workflow_step_reference';\n\ntype SchemaField = {\n key: string;\n label: string | null;\n type: VariableType;\n settings?: Record<string, any> | null;\n};\n\ntype FilterOperatorData = {\n label: string;\n value: string;\n value_shape: any;\n};\n\ntype DataTableExportApi = {\n /** The backing resource's display name, for export UI copy. */\n resourceName: string;\n /** Whether the current table view contains a field-backed column the server can export. */\n hasExportableVisibleColumns: boolean;\n exportRecords: (options: { columns: DataTableExportColumnScope }) => Promise<RecordExportResult>;\n};\n\ntype DataTableExportColumnScope = 'visible' | 'all';\n\ntype RecordExportResult = {\n /** The generated file's name, e.g. `students-export-2026-08-26.csv`. */\n filename: string;\n};\n\ntype GeneratedRecordFilter = {\n filter: FilterGroup;\n};\n\ntype UseCalendarViewResult = {\n view: CalendarViewMode;\n setView: (view: CalendarViewMode) => void;\n date: Date;\n setDate: (date: Date | ((prev: Date) => Date)) => void;\n windowStart: Date;\n windowEnd: Date;\n title: string;\n goToToday: () => void;\n goToPrevious: () => void;\n goToNext: () => void;\n};\n\ntype CalendarViewMode = 'month' | 'week' | 'day';\n\ntype UseCalendarEventsOptions = {\n resourceId: string;\n from: Date | string;\n to: Date | string;\n filters?: Record<string, any>;\n relations?: readonly string[];\n enabled?: boolean;\n};\n\ntype UseCalendarEventsResult<TRecord extends object = Record<string, any>> = {\n events: CalendarEvent<TRecord>[];\n isLoading: boolean;\n isError: boolean;\n refetch: () => Promise<any>;\n};\n\ntype CalendarEvent<TRecord extends object = Record<string, any>> = {\n id: string;\n start: Date;\n end: Date;\n allDay: boolean;\n record: TRecord;\n isPlaceholder?: boolean;\n};\n\ntype UseNewCalendarEventOptions = {\n /**\n * Title shown on the placeholder event pill while the create modal is open.\n * Defaults to `'New Event'`.\n */\n placeholderTitle?: string;\n};\n\ntype UseNewCalendarEventResult = {\n /** The pending placeholder event, or `null` when no create flow is in progress. */\n placeholder: CalendarEvent | null;\n /**\n * Populate the placeholder from an `onSlotClick` payload. Call this inside\n * your `onSlotClick` handler to immediately show the selected slot on the\n * calendar while the create modal is open.\n */\n setPlaceholder: (payload: SlotClickPayload) => void;\n /** Remove the placeholder. Call this when the create modal is cancelled or after the record is saved. */\n clearPlaceholder: () => void;\n /**\n * Merge the placeholder into an events array. Pass the result directly to\n * `<Sdk.Components.Calendar events={newEvent.mergeEvents(events)} />`.\n *\n * Returns the original array unchanged when there is no active placeholder.\n */\n mergeEvents: (events: CalendarEvent[]) => CalendarEvent[];\n /**\n * The resolved placeholder title. Pass this to\n * `<Sdk.Components.Calendar placeholderTitle={newEvent.placeholderTitle} />`\n * so the hover-preview and the confirmed placeholder pill always show the\n * same title without duplicating the string at the call-site.\n */\n placeholderTitle: string;\n};\n\ntype SlotClickPayload = {\n view: CalendarViewMode;\n start: Date;\n end: Date;\n allDay: boolean;\n};\n\ntype UseCalendarSelectionOptions = {\n resourceId: string;\n filters?: Record<string, any>;\n enabled?: boolean;\n};\n\ntype UseCalendarSelectionResult = {\n groups: CalendarOptionGroup[];\n isLoading: boolean;\n selectedIds: string[];\n setSelectedIds: (ids: string[]) => void;\n filters: Record<string, any> | undefined;\n};\n\ntype CalendarOptionGroup = CalendarOptionsResponse['data'][number];\n\ntype CalendarOptionsResponse = any;\n\ntype UseStepperOptions = {\n /** Index to start on. Clamped to the valid range. Defaults to `0`. */\n initialStep?: number;\n};\n\ntype UseStepperResult = {\n /** Zero-based index of the active step. */\n step: number;\n\n /** Total number of steps. */\n steps: number;\n\n /** Label of the active step. */\n label: string;\n\n /** All step labels, in order. */\n labels: string[];\n\n /** Jump to a step by index. Clamped to the valid range; never validates or blocks. */\n goTo: (index: number) => void;\n\n /** Advance one step. No-op on the last step. */\n next: () => void;\n\n /** Go back one step. No-op on the first step. */\n back: () => void;\n\n /** `true` when on the first step. */\n isFirst: boolean;\n\n /** `true` when on the last step. */\n isLast: boolean;\n};\n\ntype UseThemeResult = {\n theme: 'light' | 'dark';\n mode: SdkThemeMode;\n setTheme: (mode: SdkThemeMode) => void;\n toggleTheme: () => void;\n};\n\ntype SdkThemeMode = 'light' | 'dark' | 'system';\n\ninterface CustomComponentRegistry {}\n\ntype ResourceRecordFormStep<RenderState> = {\n /** Step label shown in the step indicator and the \"Step X of N\" caption. */\n label: string;\n\n /** Field handles rendered stacked, in order, while this step is active. */\n fields?: SdkResourceField[];\n\n /**\n * Custom step layout. Receives the form's render state\n * (`{ FieldInput, form, fields }`) and replaces the default field rendering.\n */\n content?: (state: RenderState) => React.ReactNode;\n\n /** Render a read-only summary of every form field and its current value. */\n review?: boolean;\n};\n\ntype ResourceRecordFormFieldsProps = {\n className?: string;\n};\n\ntype ResourceRecordFormActionsProps = {\n submitLabel?: string;\n defaultSubmitLabel?: string;\n /** When provided, the secondary button becomes a Cancel action wired to this\n * callback (instead of the default Reset). */\n onCancel?: () => void;\n cancelLabel?: string;\n className?: string;\n};\n\ntype AccordionProps = {\n /**\n * Accordion item definitions.\n */\n items: AccordionItem[];\n\n /**\n * Whether one or many items can be open at the same time.\n */\n type?: 'single' | 'multiple';\n\n /**\n * Initially opened item value (or values for `multiple`).\n */\n defaultValue?: string | string[];\n\n /**\n * Controlled open value(s).\n */\n value?: string | string[];\n\n /**\n * Called when the open value(s) change.\n */\n onValueChange?: (value: string | string[]) => void;\n\n /**\n * Whether all items can be closed when `type` is `single`.\n */\n collapsible?: boolean;\n\n className?: string;\n};\n\ntype AccordionItem = {\n /**\n * Unique identifier for the item, used for open/close state.\n */\n value: string;\n\n /**\n * Trigger label shown for the item.\n */\n title: React.ReactNode;\n\n /**\n * Content revealed when the item is open.\n */\n content: React.ReactNode;\n};\n\ntype AdminNavButtonsProps = {\n /** Additional classes for the button group. */\n className?: string;\n\n /** Button visual style. */\n variant?: ButtonProps['variant'];\n\n /** Button size preset. */\n size?: ButtonProps['size'];\n};\n\ntype ButtonProps = any;\n\ntype AiAgentButtonProps = {\n /** Additional classes for the trigger button. */\n className?: string;\n};\n\ntype AlertProps = {\n /**\n * Alert visual style.\n */\n variant?: 'default' | 'info' | 'warning' | 'success' | 'destructive';\n\n /**\n * Optional alert heading.\n */\n title?: React.ReactNode;\n\n /**\n * Primary alert body.\n */\n description?: React.ReactNode;\n\n /**\n * Alternate alert body content used when `description` is not provided.\n */\n children?: React.ReactNode;\n\n className?: string;\n};\n\ntype AnalyticsEmbedProps = {\n /** Additional classes for the embed, e.g. a height override like `h-[40rem]`. */\n className?: string;\n};\n\ntype AvatarProps = {\n /**\n * Image URL. The fallback is shown when this is missing.\n */\n src?: string | null;\n\n /**\n * Accessible image description.\n */\n alt?: string;\n\n /**\n * Fallback text or node when no image is available.\n */\n fallback?: React.ReactNode;\n\n /**\n * Avatar size. Defaults to `md`.\n */\n size?: AvatarSize;\n\n className?: string;\n};\n\ntype AvatarSize = 'sm' | 'md' | 'lg' | 'xl';\n\ntype BadgeProps = PrimitiveHtmlProps<HTMLSpanElement> & {\n /**\n * Visual variant such as default, secondary, destructive, outline, or status variants.\n */\n variant?: 'default' | 'secondary' | 'success' | 'warning' | 'destructive' | 'outline' | null;\n\n /**\n * Badge label.\n */\n children?: React.ReactNode;\n};\n\ntype PrimitiveHtmlProps<TElement> = PrimitiveAriaProps &\n Partial<Pick<React.DOMAttributes<TElement>, PrimitiveDomAttributeName>> &\n Partial<Pick<React.HTMLAttributes<TElement>, PrimitiveHtmlAttributeName>>;\n\ntype PrimitiveAriaProps = Partial<Pick<React.AriaAttributes, PrimitiveAriaAttributeName>>;\n\ntype PrimitiveAriaAttributeName =\n | 'aria-activedescendant'\n | 'aria-atomic'\n | 'aria-autocomplete'\n | 'aria-braillelabel'\n | 'aria-brailleroledescription'\n | 'aria-busy'\n | 'aria-checked'\n | 'aria-colcount'\n | 'aria-colindex'\n | 'aria-colindextext'\n | 'aria-colspan'\n | 'aria-controls'\n | 'aria-current'\n | 'aria-describedby'\n | 'aria-description'\n | 'aria-details'\n | 'aria-disabled'\n | 'aria-dropeffect'\n | 'aria-errormessage'\n | 'aria-expanded'\n | 'aria-flowto'\n | 'aria-grabbed'\n | 'aria-haspopup'\n | 'aria-hidden'\n | 'aria-invalid'\n | 'aria-keyshortcuts'\n | 'aria-label'\n | 'aria-labelledby'\n | 'aria-level'\n | 'aria-live'\n | 'aria-modal'\n | 'aria-multiline'\n | 'aria-multiselectable'\n | 'aria-orientation'\n | 'aria-owns'\n | 'aria-placeholder'\n | 'aria-posinset'\n | 'aria-pressed'\n | 'aria-readonly'\n | 'aria-relevant'\n | 'aria-required'\n | 'aria-roledescription'\n | 'aria-rowcount'\n | 'aria-rowindex'\n | 'aria-rowindextext'\n | 'aria-rowspan'\n | 'aria-selected'\n | 'aria-setsize'\n | 'aria-sort'\n | 'aria-valuemax'\n | 'aria-valuemin'\n | 'aria-valuenow'\n | 'aria-valuetext';\n\ntype PrimitiveDomAttributeName =\n | 'children'\n | 'dangerouslySetInnerHTML'\n | 'onCopy'\n | 'onCopyCapture'\n | 'onCut'\n | 'onCutCapture'\n | 'onPaste'\n | 'onPasteCapture'\n | 'onCompositionEnd'\n | 'onCompositionEndCapture'\n | 'onCompositionStart'\n | 'onCompositionStartCapture'\n | 'onCompositionUpdate'\n | 'onCompositionUpdateCapture'\n | 'onFocus'\n | 'onFocusCapture'\n | 'onBlur'\n | 'onBlurCapture'\n | 'onChange'\n | 'onChangeCapture'\n | 'onBeforeInput'\n | 'onBeforeInputCapture'\n | 'onInput'\n | 'onInputCapture'\n | 'onReset'\n | 'onResetCapture'\n | 'onSubmit'\n | 'onSubmitCapture'\n | 'onInvalid'\n | 'onInvalidCapture'\n | 'onLoad'\n | 'onLoadCapture'\n | 'onError'\n | 'onErrorCapture'\n | 'onKeyDown'\n | 'onKeyDownCapture'\n | 'onKeyPress'\n | 'onKeyPressCapture'\n | 'onKeyUp'\n | 'onKeyUpCapture'\n | 'onAbort'\n | 'onAbortCapture'\n | 'onCanPlay'\n | 'onCanPlayCapture'\n | 'onCanPlayThrough'\n | 'onCanPlayThroughCapture'\n | 'onDurationChange'\n | 'onDurationChangeCapture'\n | 'onEmptied'\n | 'onEmptiedCapture'\n | 'onEncrypted'\n | 'onEncryptedCapture'\n | 'onEnded'\n | 'onEndedCapture'\n | 'onLoadedData'\n | 'onLoadedDataCapture'\n | 'onLoadedMetadata'\n | 'onLoadedMetadataCapture'\n | 'onLoadStart'\n | 'onLoadStartCapture'\n | 'onPause'\n | 'onPauseCapture'\n | 'onPlay'\n | 'onPlayCapture'\n | 'onPlaying'\n | 'onPlayingCapture'\n | 'onProgress'\n | 'onProgressCapture'\n | 'onRateChange'\n | 'onRateChangeCapture'\n | 'onSeeked'\n | 'onSeekedCapture'\n | 'onSeeking'\n | 'onSeekingCapture'\n | 'onStalled'\n | 'onStalledCapture'\n | 'onSuspend'\n | 'onSuspendCapture'\n | 'onTimeUpdate'\n | 'onTimeUpdateCapture'\n | 'onVolumeChange'\n | 'onVolumeChangeCapture'\n | 'onWaiting'\n | 'onWaitingCapture'\n | 'onAuxClick'\n | 'onAuxClickCapture'\n | 'onClick'\n | 'onClickCapture'\n | 'onContextMenu'\n | 'onContextMenuCapture'\n | 'onDoubleClick'\n | 'onDoubleClickCapture'\n | 'onDrag'\n | 'onDragCapture'\n | 'onDragEnd'\n | 'onDragEndCapture'\n | 'onDragEnter'\n | 'onDragEnterCapture'\n | 'onDragExit'\n | 'onDragExitCapture'\n | 'onDragLeave'\n | 'onDragLeaveCapture'\n | 'onDragOver'\n | 'onDragOverCapture'\n | 'onDragStart'\n | 'onDragStartCapture'\n | 'onDrop'\n | 'onDropCapture'\n | 'onMouseDown'\n | 'onMouseDownCapture'\n | 'onMouseEnter'\n | 'onMouseLeave'\n | 'onMouseMove'\n | 'onMouseMoveCapture'\n | 'onMouseOut'\n | 'onMouseOutCapture'\n | 'onMouseOver'\n | 'onMouseOverCapture'\n | 'onMouseUp'\n | 'onMouseUpCapture'\n | 'onSelect'\n | 'onSelectCapture'\n | 'onTouchCancel'\n | 'onTouchCancelCapture'\n | 'onTouchEnd'\n | 'onTouchEndCapture'\n | 'onTouchMove'\n | 'onTouchMoveCapture'\n | 'onTouchStart'\n | 'onTouchStartCapture'\n | 'onPointerDown'\n | 'onPointerDownCapture'\n | 'onPointerMove'\n | 'onPointerMoveCapture'\n | 'onPointerUp'\n | 'onPointerUpCapture'\n | 'onPointerCancel'\n | 'onPointerCancelCapture'\n | 'onPointerEnter'\n | 'onPointerLeave'\n | 'onPointerOver'\n | 'onPointerOverCapture'\n | 'onPointerOut'\n | 'onPointerOutCapture'\n | 'onGotPointerCapture'\n | 'onGotPointerCaptureCapture'\n | 'onLostPointerCapture'\n | 'onLostPointerCaptureCapture'\n | 'onScroll'\n | 'onScrollCapture'\n | 'onScrollEnd'\n | 'onScrollEndCapture'\n | 'onWheel'\n | 'onWheelCapture'\n | 'onAnimationStart'\n | 'onAnimationStartCapture'\n | 'onAnimationEnd'\n | 'onAnimationEndCapture'\n | 'onAnimationIteration'\n | 'onAnimationIterationCapture'\n | 'onToggle'\n | 'onBeforeToggle'\n | 'onTransitionCancel'\n | 'onTransitionCancelCapture'\n | 'onTransitionEnd'\n | 'onTransitionEndCapture'\n | 'onTransitionRun'\n | 'onTransitionRunCapture'\n | 'onTransitionStart'\n | 'onTransitionStartCapture';\n\ntype PrimitiveHtmlAttributeName =\n | 'defaultChecked'\n | 'defaultValue'\n | 'suppressContentEditableWarning'\n | 'suppressHydrationWarning'\n | 'accessKey'\n | 'autoCapitalize'\n | 'autoFocus'\n | 'className'\n | 'contentEditable'\n | 'contextMenu'\n | 'dir'\n | 'draggable'\n | 'enterKeyHint'\n | 'hidden'\n | 'id'\n | 'lang'\n | 'nonce'\n | 'slot'\n | 'spellCheck'\n | 'style'\n | 'tabIndex'\n | 'title'\n | 'translate'\n | 'radioGroup'\n | 'role'\n | 'about'\n | 'content'\n | 'datatype'\n | 'inlist'\n | 'prefix'\n | 'property'\n | 'rel'\n | 'resource'\n | 'rev'\n | 'typeof'\n | 'vocab'\n | 'autoCorrect'\n | 'autoSave'\n | 'color'\n | 'itemProp'\n | 'itemScope'\n | 'itemType'\n | 'itemID'\n | 'itemRef'\n | 'results'\n | 'security'\n | 'unselectable'\n | 'popover'\n | 'popoverTargetAction'\n | 'popoverTarget'\n | 'inert'\n | 'inputMode'\n | 'is'\n | 'exportparts'\n | 'part';\n\ntype HTMLSpanElement = any;\n\ntype CartesianChartProps<TRow extends object = Record<string, any>> = {\n /**\n * Rows to chart.\n */\n data: TRow[];\n\n /**\n * Field key for the x-axis.\n */\n xKey: Extract<keyof TRow, string>;\n\n /**\n * Field key for the y-axis value.\n */\n yKey: Extract<keyof TRow, string>;\n\n /**\n * Optional chart heading.\n */\n title?: string;\n\n /**\n * Chart height in pixels.\n */\n height?: number;\n};\n\ntype BreadcrumbProps = {\n /**\n * Ordered breadcrumb items. The last item is treated as the current page when `href` is\n * omitted.\n */\n items: BreadcrumbItem[];\n\n className?: string;\n};\n\ntype BreadcrumbItem = {\n /**\n * Item label.\n */\n label: React.ReactNode;\n\n /**\n * Optional URL. Omit on the final item to mark it as the current page. Resolve a\n * workspace page with `Sdk.utils.page.url(pageId, params)` instead of hardcoding a route\n * string.\n */\n href?: string;\n};\n\ntype PrimitiveButtonProps = PrimitiveButtonNativeProps & {\n /**\n * Visual style such as default, secondary, destructive, ghost, link, or outline.\n */\n variant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'ghost' | 'link' | 'icon' | null;\n\n /**\n * Button size preset.\n */\n size?: 'default' | 'xs' | 'sm' | 'lg' | 'icon' | 'smIcon' | null;\n\n /**\n * Button label or contents.\n */\n children?: React.ReactNode;\n asChild?: boolean;\n loading?: boolean;\n tooltip?: string;\n};\n\ntype PrimitiveButtonNativeProps = PrimitiveHtmlProps<HTMLButtonElement> &\n Partial<Pick<React.ButtonHTMLAttributes<HTMLButtonElement>, PrimitiveButtonAttributeName>>;\n\ntype HTMLButtonElement = any;\n\ntype PrimitiveButtonAttributeName =\n | 'disabled'\n | 'form'\n | 'formAction'\n | 'formEncType'\n | 'formMethod'\n | 'formNoValidate'\n | 'formTarget'\n | 'name'\n | 'type'\n | 'value';\n\ntype ButtonLinkProps = Omit<LinkProps, 'className'> &\n Partial<Pick<ButtonProps, 'variant' | 'size'>> & {\n className?: string;\n };\n\ntype LinkProps = React.AnchorHTMLAttributes<HTMLAnchorElement> & {\n /**\n * Destination URL or anchor. For another page in this workspace, resolve it from the\n * page's stable id with `Sdk.utils.page.url(pageId, params)` and pass the non-null\n * result \u2014 never a hardcoded route string. Literal values are only for in-page anchors\n * (`#pricing`), absolute external URLs, and `mailto:`/`tel:` addresses.\n */\n href: string;\n\n /**\n * Open in a new tab and add safe rel attributes.\n */\n external?: boolean;\n\n /**\n * Link contents.\n */\n children?: React.ReactNode;\n};\n\ntype HTMLAnchorElement = any;\n\ntype CalendarProps<TRecord extends object = Record<string, any>> = {\n /**\n * Events to display on the calendar.\n */\n events: CalendarEvent<TRecord>[];\n\n /**\n * Active view: `'month'`, `'week'`, or `'day'`.\n */\n view: CalendarViewMode;\n\n /**\n * Anchor date that determines which month, week, or day is shown.\n */\n date: Date;\n\n /**\n * Called when an empty time slot is clicked. Receives the slot's start/end\n * times, view mode, and whether it is an all-day slot.\n */\n onSlotClick?: (payload: SlotClickPayload) => void;\n\n /**\n * Called when the user clicks an event pill.\n */\n onEventClick?: (event: CalendarEvent<TRecord>) => void;\n\n /**\n * Called after the user drags an event to a new time or resizes it. When\n * omitted, drag-and-drop and resize are disabled.\n */\n onEventChange?: (payload: EventChangePayload<TRecord>) => void;\n\n /**\n * Custom renderer for event content. Receives the full `CalendarEvent` and\n * returns content rendered inside the styled event pill. The pill's background\n * color, border, and shape are always applied by the calendar \u2014 only the\n * inner content (title, time label, icons, etc.) is replaced.\n */\n renderEvent?: (event: CalendarEvent<TRecord>) => React.ReactNode;\n\n /**\n * Title shown on the drag-preview event pill while the user hovers over a\n * slot before confirming the click. Pass `newEvent.placeholderTitle` from\n * `useNewCalendarEvent`. Defaults to `'New Event'` when omitted.\n */\n placeholderTitle?: string;\n\n /**\n * Time shaded behind the events of the week and day views, such as `backgroundTimes` from\n * `Sdk.hooks.useAvailability`. Each time is split at local midnight. The month view\n * ignores it.\n */\n backgroundTimes?: CalendarBackgroundTime[];\n\n className?: string;\n};\n\ntype EventChangePayload<TRecord extends object = Record<string, any>> = {\n event: CalendarEvent<TRecord>;\n previous: CalendarEvent<TRecord>;\n interaction: 'move' | 'resize';\n view: CalendarViewMode;\n};\n\ntype CalendarConnectionsProps = {\n /**\n * A Schedulable staff resource. Omit it and `recordId` to manage the signed-in person's\n * own calendars, which needs no permission.\n */\n resourceId?: string;\n\n /**\n * A staff record of that resource. Another person's record needs \"Manage calendar\n * connections\" and Show access to the record. Connections always use the viewer's own\n * Google accounts.\n */\n recordId?: string;\n\n /** Additional classes for the container. */\n className?: string;\n};\n\ntype CalendarNextButtonProps = {\n view: CalendarViewMode;\n onClick: () => void;\n className?: string;\n};\n\ntype CalendarPrevButtonProps = {\n view: CalendarViewMode;\n onClick: () => void;\n className?: string;\n};\n\ntype CalendarSelectorProps = {\n groups: CalendarOptionGroup[];\n isLoading?: boolean;\n selectedIds: string[];\n onChange: (selectedIds: string[]) => void;\n /** Show the \"Select all\" / \"Clear\" bulk actions in the dropdown. Defaults to `true`. */\n showBulkActions?: boolean;\n className?: string;\n};\n\ntype CalendarTitleProps = {\n title: string;\n isLoading?: boolean;\n className?: string;\n};\n\ntype CalendarTodayButtonProps = {\n onClick: () => void;\n className?: string;\n};\n\ntype CalendarViewSwitcherProps = {\n view: CalendarViewMode;\n onViewChange: (view: CalendarViewMode) => void;\n className?: string;\n};\n\ntype CardProps = {\n /**\n * Optional card heading.\n */\n title?: React.ReactNode;\n\n /**\n * Supporting text below the title.\n */\n description?: React.ReactNode;\n\n /**\n * Main card content.\n */\n children?: React.ReactNode;\n\n /**\n * Footer area for actions or metadata.\n */\n footer?: React.ReactNode;\n\n className?: string;\n contentClassName?: string;\n};\n\ntype CollapsibleProps = {\n /**\n * Trigger label shown when collapsed or expanded.\n */\n title: React.ReactNode;\n\n /**\n * Content revealed inside the panel.\n */\n children: React.ReactNode;\n\n /**\n * Controlled open state.\n */\n open?: boolean;\n\n /**\n * Initial uncontrolled open state.\n */\n defaultOpen?: boolean;\n\n /**\n * Called when the panel opens or closes.\n */\n onOpenChange?: (open: boolean) => void;\n\n className?: string;\n};\n\ntype ConfirmDialogProps = {\n /**\n * Dialog heading.\n */\n title: React.ReactNode;\n\n /**\n * Explanation of the action.\n */\n description: React.ReactNode;\n\n /**\n * Confirm button label.\n */\n confirmLabel?: string;\n\n /**\n * Called when the confirm action is selected. Return a promise to show a processing\n * indicator until it settles. The dialog closes when the promise resolves. It stays open\n * when the handler returns `false`, resolves `false`, or rejects, so the page can show the\n * error in the dialog.\n */\n onConfirm: () => void | boolean | Promise<void | boolean>;\n\n /**\n * Element that opens the dialog.\n */\n trigger?: React.ReactNode;\n\n /**\n * Show a processing indicator on the confirm action.\n */\n processing?: boolean;\n\n /**\n * Controlled open state.\n */\n open?: boolean;\n\n /**\n * Called when the dialog opens or closes.\n */\n onOpenChange?: (open: boolean) => void;\n\n /**\n * Confirm action visual style.\n */\n variant?: 'default' | 'destructive';\n};\n\ntype CreateFormResource = SdkResourceType &\n CreateResourceRecordFormResource &\n Pick<SdkResource, 'policy_scope'>;\n\ntype CreateResourceRecordFormProps<TResource extends CreateFormResource = CreateFormResource> = {\n /** Resource handle returned by `Sdk.resource('<resourceId>')`. */\n resource: TResource;\n\n /** Field handles that control which fields the form renders and their order.\n * Omit to render the resource's configured create fields. */\n fields?: SdkResourceField[];\n\n /** Editable seed values \u2014 pre-fill fields the user can still change. */\n initialValues?: Record<string, any>;\n\n /** Locked writable Resource field values \u2014 rendered as disabled inputs and included\n * even when omitted from `fields`. Unknown, system, virtual, or inaccessible keys are\n * ignored. Reactive: if the prop changes after mount the form updates. */\n fixedValues?: Record<string, any>;\n\n /** Include every creatable field, ignoring per-field `show_on_creating` form\n * visibility. For elevated surfaces (e.g. the admin create page). Ignored when\n * explicit `fields` are provided. */\n includeAllFields?: boolean;\n\n /** Additional system-managed relations an elevated surface may expose, rendered\n * attach-only. The resource's authorization relations (roles, permissions) need no\n * entry: a form offers them attach-only through their inline attach switch when the\n * identity may store them. */\n includeSystemManagedRelationKeys?: string[];\n\n /** How relation fields resolve inline attach/create. Defaults to `'settings'`;\n * pass `'always'` to force them on. */\n relationVisibility?: RelationVisibility;\n\n /** Render the resource's published `created`/`saved` RecordEvent workflow inputs in the\n * managed layout, stacked beneath the resource fields (on the final step for `steps`).\n * Defaults to `false`. Ignored when `children` is provided \u2014 render `WorkflowInputs`\n * yourself there. */\n includeWorkflowInputs?: boolean;\n\n /**\n * Multi-step (wizard) layout. Each step groups fields under a label; the form renders the\n * numbered step indicator, \"Step X of N\" caption, Previous/Next navigation, and the submit\n * button on the last step \u2014 no render prop or submit wiring needed. All steps share one\n * form: values persist across steps and the final submit posts the whole record.\n * Use `review: true` for a read-only summary step, or `content` to customize one step's\n * layout. Navigation is free \u2014 moving between steps never validates or blocks.\n * Ignored when `children` is provided.\n */\n steps?: ResourceRecordFormStep<CreateResourceRecordFormRenderState<TResource>>[];\n\n /**\n * Make a retried booking submit return the booking that the first attempt made, instead of a\n * second booking. Each submit writes a key into the field that the resource's booking\n * capability names as its idempotency field. A retry with the same values sends the same key;\n * changed values get a new key. Submitting throws when the resource has no idempotency field.\n * Defaults to `false`.\n */\n idempotent?: boolean;\n\n /** Submit button label. Only used when `children` is not provided. */\n submitLabel?: string;\n\n /** When provided, the default layout's secondary button becomes a Cancel action\n * wired to this callback (instead of Reset). */\n onCancel?: () => void;\n\n /** Cancel button label. Defaults to \"Cancel\". Only used with `onCancel`. */\n cancelLabel?: string;\n\n /** Toast message shown after a successful submission. Pass `null` to suppress. */\n successMessage?: string | null;\n\n /** Called after a successful submission with the created record. */\n onSuccess?: (record: SdkResourceRecord<TResource>) => void;\n\n /** Called when a submission fails, after the form shows its errors. On a Booking resource, a\n * refused booking fails here, so a slot list can refetch. */\n onError?: (error: any) => void;\n\n /** Optional container className. */\n className?: string;\n\n /**\n * Custom layout render prop. When provided, the managed layout is replaced while\n * retaining the form tag and submit handler. It receives reusable `Fields`,\n * `Actions`, and `WorkflowInputs` components, `FieldInput` / `WorkflowInput` for\n * individual controls, and `workflowInputs` \u2014 the resource's published\n * `created`/`saved` RecordEvent workflow inputs keyed by input key.\n *\n * `FieldInput` renders the correctly-wired input for a resource field handle and\n * returns `null` for handles that are not part of the active form. `WorkflowInputs`\n * renders every applicable workflow input; `<WorkflowInput input={workflowInputs.<key>} />`\n * renders one. When a render function is provided, `steps` is ignored.\n *\n * @example\n * <Sdk.Components.CreateResourceRecordForm resource={students}>\n * {({ FieldInput, form }) => (\n * <div className=\"grid grid-cols-2 gap-4\">\n * <FieldInput field={students.fields.first_name} />\n * <FieldInput field={students.fields.last_name} />\n * <FieldInput field={students.fields.email} className=\"col-span-2\" />\n * <Sdk.Components.Button type=\"submit\" disabled={form.isSubmitting}>\n * Create student\n * </Sdk.Components.Button>\n * </div>\n * )}\n * </Sdk.Components.CreateResourceRecordForm>\n */\n children?: (state: CreateResourceRecordFormRenderState<TResource>) => React.ReactNode;\n};\n\ntype CreateResourceRecordFormRenderState<\n TResource extends Pick<SdkResource, 'workflow_inputs'> = Pick<SdkResource, 'workflow_inputs'>\n> = {\n FieldInput: (props: FormFieldProps) => React.ReactNode;\n WorkflowInput: (props: WorkflowInputProps) => React.ReactNode;\n /** Renders every applicable RecordEvent workflow input, stacked in order. */\n WorkflowInputs: (props: WorkflowInputsProps) => React.ReactNode;\n Fields: (props: ResourceRecordFormFieldsProps) => React.ReactNode;\n Actions: (props: ResourceRecordFormActionsProps) => React.ReactNode;\n form: SdkResourceForm;\n fields: SdkResourceField[];\n /** The resource's published `created`/`saved` RecordEvent workflow inputs, keyed\n * by input key \u2014 pass one to `WorkflowInput` or render them all via `WorkflowInputs`. */\n workflowInputs: SdkResourceFormWorkflowInputs<TResource, 'created' | 'saved'>;\n};\n\ntype DataTableProps<TData extends RowData> = {\n /**\n * The TanStack table instance from `Sdk.hooks.useDataTable` (seeded data) or\n * `Sdk.hooks.useResourceDataTable` (resource-backed).\n */\n table: Table<TData>;\n\n /** Renders skeleton rows while data loads (pass the hook's `isLoading`). */\n isLoading?: boolean;\n\n /**\n * Renders an error row instead of rows when the records request failed (pass the\n * hook's `isError`). Without it a failed request looks like an empty result.\n */\n isError?: boolean;\n\n /**\n * Called by the error row's Retry button (pass the hook's `refetch`). The button\n * only renders when provided.\n */\n onRetry?: () => void;\n\n /** Show a search input in the default toolbar. */\n searchable?: boolean;\n\n /** Placeholder for the search input. */\n searchPlaceholder?: string;\n\n /**\n * Runtime filter state for end-user filtering. Usually the `filters` returned by\n * `Sdk.hooks.useResourceDataTable`. When it carries quick-filter fields (resource-backed\n * tables), the default layout renders a Notion-style filter chip bar below the toolbar;\n * otherwise the toolbar shows the classic \"Filters\" button opening the advanced dialog.\n */\n filters?: DataTableFiltersApi;\n\n /** Show the column-visibility toggle in the default toolbar. */\n columnToggle?: boolean;\n\n /**\n * Show the CSV-export dropdown in the default toolbar. Only renders for\n * resource-backed tables (`useResourceDataTable`) whose resource has the export\n * endpoint enabled; the control offers the filtered rows or all rows, and the\n * visible columns or all columns.\n */\n isExportable?: boolean;\n\n /**\n * Extra controls rendered in the default toolbar, between the search input and the\n * column-visibility toggle. A generic slot for any toolbar content.\n */\n toolbarActions?: ReactNode;\n\n /** Extra controls rendered after the column and export controls. */\n toolbarTrailingActions?: ReactNode;\n\n /**\n * Bulk action controls shown in a floating selection bar once one or more rows are\n * selected (requires the hook's `enableRowSelection`). Pass a node, or a function that\n * receives the live selection (`rows`, `rowIds`, `count`, `clear`).\n *\n * Return only the action controls (e.g. Export, Delete). The selection bar already\n * renders the \"{count} selected\" label and a Clear button, so do not add your own \u2014 use\n * the supplied `count` for action labels (\"Export {count}\") and `clear` to reset the\n * selection after an action runs.\n */\n bulkActions?: DataTableBulkActionContent<TData>;\n\n /** Show pagination controls below the table. Defaults to `true`. */\n pagination?: boolean;\n\n /** Called with the row's record when a row is clicked. */\n onRowClick?: (row: TData) => void;\n\n /** Message shown when there are no rows. */\n emptyMessage?: ReactNode;\n\n /** Message shown in the error row when the records request failed. */\n errorMessage?: ReactNode;\n\n className?: string;\n\n /**\n * Advanced: provide a custom layout instead of the default toolbar/table/pagination.\n * Compose `Sdk.Components.DataTableContent`, `DataTableToolbar`, `DataTableSearch`,\n * `DataTableFilters`, `DataTableAddFilter`, `DataTableFilterChips`,\n * `SharedFilters`, `DataTableColumnToggle`, and `DataTablePagination` in any\n * order \u2014 they read the same table (and filters state) from context.\n */\n children?: ReactNode;\n};\n\ntype DataTableBulkActionContent<TData extends RowData> =\n | DataTableBulkActionNode\n | ((selection: DataTableBulkActionSelection<NoInfer<TData>>) => ReactNode);\n\ntype DataTableBulkActionNode =\n DataTableIsAny<ReactNode> extends true ? DataTableBulkActionNodeFallback : ReactNode;\n\ntype DataTableIsAny<T> = 0 extends 1 & T ? true : false;\n\ntype DataTableBulkActionNodeFallback =\n | DataTableBulkActionChild\n | readonly DataTableBulkActionChild[];\n\ntype DataTableBulkActionChild =\n | string\n | number\n | bigint\n | boolean\n | null\n | undefined\n | DataTableBulkActionElement;\n\ntype DataTableBulkActionElement = { type: any; props: any; key: string | null };\n\ntype DataTableBulkActionSelection<TData extends RowData> = {\n /** Selected rows on the loaded page. */\n rows: TData[];\n /** Ids of every selected row, stable across pagination. */\n rowIds: string[];\n /** Number of selected rows across all pages. */\n count: number;\n /** Clears the current selection. */\n clear: () => void;\n};\n\ntype DataTableAddFilterProps = {\n className?: string;\n};\n\ntype DataTableBulkActionsProps<TData = Record<string, any>> = {\n children: React.ReactNode | ((selection: DataTableSelection<TData>) => React.ReactNode);\n className?: string;\n label?: (count: number) => React.ReactNode;\n};\n\ntype DataTableSelection<TData> = {\n rows: TData[];\n rowIds: string[];\n count: number;\n clear: () => void;\n};\n\ntype DataTableViewOptionsProps = {\n className?: string;\n label?: string;\n};\n\ntype DataTableContentProps = {\n className?: string;\n emptyMessage?: React.ReactNode;\n /** Shown instead of rows when the records request failed (`DataTable`'s `isError`). */\n errorMessage?: React.ReactNode;\n skeletonRows?: number;\n};\n\ntype DataTableFilterChipsProps = {\n className?: string;\n};\n\ntype DataTableFiltersProps = {\n /**\n * Filters API from `Sdk.hooks.useResourceDataTable`. Optional when rendered inside a\n * `<Sdk.Components.DataTable filters={...}>` \u2014 it is read from context there.\n */\n filters?: DataTableFiltersApi;\n className?: string;\n};\n\ntype DataTablePaginationProps = {\n className?: string;\n pageSizeOptions?: number[];\n};\n\ntype DataTableRowActionsProps = {\n actions: DataTableRowAction[];\n className?: string;\n label?: string;\n};\n\ntype DataTableRowAction = {\n label: string;\n onSelect: () => void;\n icon?: React.ReactNode;\n destructive?: boolean;\n separatorBefore?: boolean;\n};\n\ntype DataTableSearchProps = {\n placeholder?: string;\n className?: string;\n};\n\ntype DataTableToolbarProps = {\n /** Free-form controls. They follow the `filters` slot in the leading group. */\n children?: React.ReactNode;\n className?: string;\n /**\n * View switcher (saved views). Follows the search in the leading group. In a narrow\n * toolbar it leads the second row and shrinks to the space left beside `filters` and\n * `end`, so its label must be able to truncate.\n */\n views?: React.ReactNode;\n /** Search input. Leads the toolbar; takes its own row in a narrow toolbar. */\n search?: React.ReactNode;\n /**\n * Filter trigger. Follows the search in the leading group. Below 20rem of toolbar width,\n * children marked `data-slot=\"data-table-toolbar-label\"` collapse to screen-reader text.\n */\n filters?: React.ReactNode;\n /**\n * Trailing group (column toggle, export). Right-aligned and kept together. Below 48rem\n * of toolbar width, children marked `data-slot=\"data-table-toolbar-label\"` collapse to\n * screen-reader text so the group fits beside the leading controls.\n */\n end?: React.ReactNode;\n};\n\ntype DeleteResourceRecordProps = {\n /**\n * Resource handle returned by `Sdk.resource('<resourceId>')`.\n */\n resource: DeleteResourceRecordResource &\n SdkResourceQueryHandle<'show'> &\n Pick<SdkResource, 'policy_scope' | 'capabilities'>;\n\n /**\n * Record ID to delete.\n */\n recordId: string | null | undefined;\n\n /**\n * Optional already-loaded record. When omitted for an eventable resource, the\n * component loads the record when the confirmation dialog opens.\n */\n record?: Record<string, any>;\n\n /**\n * Button label.\n */\n label?: string;\n\n /**\n * Heading shown in the confirmation dialog.\n */\n confirmTitle?: React.ReactNode;\n\n /**\n * Supporting text shown in the confirmation dialog.\n */\n confirmDescription?: React.ReactNode;\n\n /**\n * Called after a successful deletion, before navigation.\n */\n onSuccess?: () => void;\n\n /**\n * Optional container className.\n */\n className?: string;\n};\n\ntype DropdownMenuProps = {\n /**\n * Element that opens the menu.\n */\n trigger: React.ReactNode;\n\n /**\n * Menu item definitions.\n */\n items: DropdownMenuItem[];\n\n /**\n * Optional menu heading.\n */\n label?: React.ReactNode;\n\n /**\n * Menu alignment relative to the trigger.\n */\n align?: 'start' | 'center' | 'end';\n\n className?: string;\n};\n\ntype DropdownMenuItem = {\n /**\n * Item label.\n */\n label: React.ReactNode;\n\n /**\n * Optional URL. When present the item renders as a link. Resolve a workspace page with\n * `Sdk.utils.page.url(pageId, params)` instead of hardcoding a route string.\n */\n href?: string;\n\n /**\n * Optional Lucide icon name shown beside the label.\n */\n icon?: string;\n\n /**\n * Disable the item.\n */\n disabled?: boolean;\n\n /**\n * Style the item as a destructive action.\n */\n destructive?: boolean;\n\n /**\n * Called when the item is selected.\n */\n onSelect?: () => void;\n};\n\ntype EmailComposerProps = EmailComposerBaseProps & EmailComposerAudienceProps;\n\ntype EmailComposerBaseProps = {\n /**\n * Resource handle returned by `Sdk.resource('<resourceId>')`.\n * Existing single-record form. Use `records` to link multiple records.\n */\n resource?: SdkResourceQueryHandle<'show'> & Pick<SdkResource, 'capabilities'>;\n\n /**\n * Record the email is about. It is linked to the stored message, and \u2014 without\n * `recipients` \u2014 when the resource is notifiable its email field prefills **To**.\n */\n recordId?: string;\n\n /**\n * Records linked to every email. Replaces `resource` and `recordId` when supplied.\n * Without `recipients`, the first record's notifiable email field prefills **To**.\n */\n records?: Array<{ resourceId: string; recordId: string }>;\n\n /**\n * The shape of the data the page supplies through `data` and the recipients'\n * `name`/`data`, so the `{{` menu in the subject, body, and Code tab can offer it.\n * Without it the keys are inferred from those values, except the keys that prefill\n * an editable region.\n */\n variables?: EmailComposerVariable[];\n\n /**\n * Published email layout that wraps the body. When set, the layout is fixed and the\n * picker is hidden. When omitted, the toolbar offers the workspace's published\n * layouts, or no layout at all.\n */\n emailLayoutId?: string | null;\n\n /**\n * Values every email can read as `{{ key }}` \u2014 the same for all recipients, and\n * the defaults for any variables the chosen layout declares. Recipient `data`\n * overrides them per email. A string under an editable region's key prefills that\n * region: `content` is the body when no layout is chosen, and a layout's regions\n * use the keys it declares. The viewer can edit a prefilled region, and the email\n * sends their edit.\n */\n data?: Record<string, any>;\n\n /**\n * Subject the composer opens with, as Liquid source, so it can use `{{ name }}` and\n * the other variables. The viewer can edit it, and \"Send another\" restores it. The\n * composer reads it once, when it mounts: give the component a new `key` to start\n * again from a new subject and new `data`.\n */\n initialSubject?: string;\n\n /**\n * Called after the email is queued.\n */\n onSent?: (response: SdkSendEmailResponse) => void;\n\n /**\n * Called when the request fails.\n */\n onError?: (error: SdkApiError) => void;\n\n /**\n * Optional container className.\n */\n className?: string;\n};\n\ntype EmailComposerVariable = {\n /** The Liquid name, written as `{{ key }}`. Dotted keys are allowed (`order.total`). */\n key: string;\n label?: string;\n description?: string;\n type?: 'text' | 'number' | 'boolean' | 'date';\n};\n\ntype EmailComposerAudienceProps =\n | {\n /**\n * Page-supplied recipient list. When set, every recipient receives their own email\n * (a bulk send), **To** becomes a recipient summary, and each recipient's `name`\n * and `data` personalize their copy. Omit it to compose one email to the record.\n */\n recipients?: EmailComposerRecipient[];\n previewRecipient?: never;\n onPreviewRecipientChange?: never;\n }\n | ({\n /**\n * Page-supplied recipient list. When set, every recipient receives their own email\n * (a bulk send), **To** becomes a recipient summary, and each recipient's `name`\n * and `data` personalize their copy. Omit it to compose one email to the record.\n */\n recipients: EmailComposerRecipient[];\n } & (\n | {\n /**\n * The email of the recipient the preview renders for. `null`, or an address not in\n * `recipients`, previews the first recipient. Omit it to let the composer's\n * \"Preview as\" switcher choose. A repeated address previews its first entry,\n * which is also the one the server sends.\n */\n previewRecipient: string | null;\n /** Called with the email the viewer picks in \"Preview as\". Required with `previewRecipient`. */\n onPreviewRecipientChange: (email: string) => void;\n }\n | {\n previewRecipient?: undefined;\n /** Called with the email the viewer picks in \"Preview as\". Required with `previewRecipient`. */\n onPreviewRecipientChange?: (email: string) => void;\n }\n ));\n\ntype EmailComposerRecipient = {\n email: string;\n /** Exposed to the subject and body as `{{ name }}` for this recipient. */\n name?: string;\n /** Values that override `data` for this recipient only. */\n data?: Record<string, any>;\n} & (\n | { resourceId?: never; recordId?: never }\n /** Supply both IDs to link this recipient's record to their own email. */\n | { resourceId: string; recordId: string }\n);\n\ntype EmailMessagesProps = {\n /**\n * Resource the record belongs to. Pair it with `recordId` for one record's history.\n */\n resourceId?: string;\n\n /**\n * Record whose email history is shown.\n */\n recordId?: string;\n\n /**\n * Records whose linked emails are shown together. Replaces `resourceId` and\n * `recordId`. Every record is authorized for the viewer before anything loads.\n */\n records?: SdkEmailMessagesRecord[];\n\n /**\n * Show every tracked email in the workspace instead of a record's. Requires the\n * `manage_notifications` permission; other viewers see an explanation instead.\n */\n workspace?: boolean;\n\n /**\n * Filters applied to the chosen scope. With `showFilters` they are the starting\n * point the viewer edits; without it they apply as given.\n */\n filters?: SdkEmailMessagesFilters;\n\n /**\n * Render the shared filter toolbar (subject search plus Date, To, From, and\n * Status chips) above the list so the viewer can narrow it.\n */\n showFilters?: boolean;\n\n /**\n * Rows per page, 1 to 100. Previous/next controls appear when there is more than\n * one page.\n */\n perPage?: number;\n\n /**\n * Milliseconds between automatic refreshes while the tab is visible, like `pollInterval`\n * on the data hooks. A single record's history refreshes every 30 seconds by default so\n * delivery-status changes show up without a reload. `records` and `workspace` lists do\n * not refresh by default because every refresh repeats the authorization and search work\n * for the whole scope; pass a value to opt in, or `false` to turn refreshing off. Values\n * under 1000 are raised to 1000 with a console warning.\n */\n pollInterval?: number | false;\n\n /**\n * Show the rendered email body (Preview / Source tabs) inside the detail dialog.\n */\n showEmailBody?: boolean;\n};\n\ntype EmptyStateProps = {\n /**\n * Primary empty-state message.\n */\n title: string;\n\n /**\n * Short supporting explanation.\n */\n description: string;\n\n /**\n * Optional call to action element.\n */\n action?: React.ReactNode;\n\n /**\n * Whether to show the default icon.\n */\n showIcon?: boolean;\n};\n\ntype ErrorBoundaryProps = {\n /** The section protected from errors. */\n children: React.ReactNode;\n\n /** Custom fallback content or renderer. The renderer receives the error and a reset function. */\n fallback?: React.ReactNode | ((props: ErrorBoundaryFallbackProps) => React.ReactNode);\n\n /** Called before the boundary retries its children. */\n onReset?: () => void;\n\n /** Changing this value clears the current error and retries the section. */\n resetKey?: any;\n};\n\ntype ErrorBoundaryFallbackProps = {\n error: Error;\n reset: () => void;\n};\n\ntype ForgotPasswordFormProps = {\n /**\n * The authenticatable resource whose password is being reset. Defaults to the\n * workspace's sole authenticatable resource; required only when the workspace has\n * more than one.\n */\n resourceId?: string;\n\n /** Prefills the email field. */\n initialIdentifier?: string;\n\n /** Label for the identifier (email) field. */\n identifierLabel?: React.ReactNode;\n\n /** Placeholder for the identifier (email) field. */\n identifierPlaceholder?: string;\n\n /** Contents of the submit button. */\n submitLabel?: React.ReactNode;\n\n /** Additional classes for the form element. */\n className?: string;\n\n /** Additional classes for the submit button. */\n buttonClassName?: string;\n\n /**\n * Where the sign-in that completes the reset continues when it needs two-factor.\n * `'hosted'`, the default, uses Diagonal's challenge page. `'inline'` returns to this\n * page to show the challenge, so render `Sdk.Components.TwoFactorChallenge` and\n * `Sdk.Components.TwoFactorSetup` here too.\n */\n twoFactor?: 'inline' | 'hosted';\n\n /** With `'current'`, the visitor returns to this page after the reset signs them in. */\n returnTo?: 'current';\n};\n\ntype FormFieldDisplayProps = {\n /**\n * A resource field from the resource catalog (`resource.fields.someField`).\n * The field type and display settings are resolved automatically from it.\n */\n field: SdkResourceField;\n\n /**\n * The value to display.\n */\n value?: any;\n\n /**\n * Rendered in place of the default em dash when the value is empty. What counts as\n * empty stays with the field's own display (a blank string, `null`, an empty list,\n * an unresolved relation, ...). Pass `null` to render nothing at all. Omit to\n * inherit an enclosing fallback (such as `KeyValueList`'s) or keep the em dash.\n * The fallback renders exactly as given \u2014 the caller owns its styling, so pass a\n * muted node if it should read as a placeholder rather than a value.\n */\n fallback?: React.ReactNode;\n\n className?: string;\n};\n\ntype FormFieldInputProps = {\n /**\n * A resource field handle from the resource catalog (`resource.fields.someField`)\n * or a workspace field definition (`Sdk.workspace.fields.someField`). The field\n * type and display settings are resolved automatically from the handle.\n */\n field: SdkFormField;\n\n /**\n * Controlled value.\n */\n value?: any;\n\n /**\n * Called with the new value.\n */\n onChange: (value: any) => void;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Whether the input is disabled.\n */\n disabled?: boolean;\n\n /**\n * Form context forwarded to the underlying field component. Relation fields use\n * `context.ancestorResourceIds` to prevent circular relation pickers and\n * `context.errors` for inline validation. Composite fields such as location\n * also read nested errors from `context.errors`, using `errorKeySegments` as\n * the key prefix. Defaults to `null` (no context).\n */\n context?: Record<string, any> | null;\n\n /**\n * Prefix segments for `context.errors` lookups. Location fields read\n * `<prefix>.<subfield>` errors for `line1`, `line2`, `city`, `state`,\n * `postal_code`, and `country`. Pass `['display_address']` for errors such as\n * `display_address.line1`, shown beside Address line 1.\n * Nested inline-create drafts append their draft index and child field key,\n * so a caller rendering the `files` relation passes `['files']` for errors\n * keyed `files.0.file_id`.\n */\n errorKeySegments?: string[];\n};\n\ntype FormCheckboxProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Actions shown beside the input.\n */\n inputActions?: React.ReactNode;\n\n /**\n * Label layout.\n */\n labelPlacement?: 'stacked' | 'inline';\n\n /**\n * Controlled checked state.\n */\n checked?: boolean;\n\n /**\n * Initial checked state for uncontrolled usage.\n */\n defaultChecked?: boolean;\n\n /**\n * Called when the checkbox changes.\n */\n onCheckedChange?: (checked: boolean) => void;\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n\n className?: string;\n};\n\ntype FormCheckboxGroupInputProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Actions shown beside the input.\n */\n inputActions?: React.ReactNode;\n\n /**\n * Checkbox options.\n */\n options: RadioOption[];\n\n /**\n * Selected option values.\n */\n selected: string[];\n\n /**\n * Called with selected values.\n */\n onChange: (values: string[]) => void;\n\n /**\n * Disable all options.\n */\n disabled?: boolean;\n\n className?: string;\n};\n\ntype RadioOption = {\n /**\n * Option label.\n */\n label: React.ReactNode;\n\n /**\n * Option value used by the form.\n */\n value: string;\n\n /**\n * Disable this option.\n */\n disabled?: boolean;\n};\n\ntype FormCodeInputProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * The digits typed so far, in order.\n */\n value?: string;\n\n /**\n * Called with the digits typed so far on every change.\n */\n onChange?: (value: string) => void;\n\n /**\n * Number of digits in the code.\n */\n length?: number;\n\n /**\n * Called with the full code once every cell holds a digit. Verify the code here.\n */\n onComplete?: (code: string) => void;\n\n /**\n * Disable the input, for example while the code is being verified.\n */\n disabled?: boolean;\n\n /**\n * Focus the first cell when the input appears.\n */\n autoFocus?: boolean;\n\n className?: string;\n};\n\ntype FormColorInputProps = PrimitiveFormFieldProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Selected color value.\n */\n value?: string | null;\n\n /**\n * Called when a color is applied.\n */\n onChange?: (value: string) => void;\n\n /**\n * Show workspace theme token swatches.\n */\n showSwatches?: boolean;\n disabled?: boolean;\n placeholder?: string;\n size?: 'default' | 'xs' | 'sm' | 'lg' | 'icon' | 'smIcon' | null;\n};\n\ntype PrimitiveFormFieldProps = Omit<\n PrimitiveHtmlProps<HTMLElement>,\n 'children' | 'className' | 'id' | 'onChange' | 'onSelect'\n> & {\n label?: React.ReactNode;\n labelActions?: React.ReactNode;\n labelPlacement?: 'stacked' | 'inline';\n helpText?: React.ReactNode;\n helpTextPlacement?: 'before-control' | 'after-control';\n error?: string | string[];\n\n /**\n * Show the required-field indicator next to the label. This does not add validation.\n */\n isRequired?: boolean | null;\n\n inputActions?: React.ReactNode;\n id?: string;\n classes?: string;\n className?: string;\n hidden?: boolean;\n role?: React.AriaRole;\n style?: React.CSSProperties;\n tabIndex?: number;\n title?: string;\n 'data-block-id'?: string;\n 'data-block-type'?: string;\n};\n\ntype HTMLElement = any;\n\ntype FormComboboxInputProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Show the required-field indicator next to the label. This does not add validation.\n */\n isRequired?: boolean | null;\n\n /**\n * Available choices.\n */\n options: ComboboxOption[];\n\n /**\n * Controlled selected value.\n */\n value: string | null;\n\n /**\n * Called when the selected value changes.\n */\n onChange: (value: string | null) => void;\n\n /**\n * Placeholder shown before selection.\n */\n placeholder?: string | null;\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n\n /**\n * Allow values not present in `options`.\n */\n allowCustomValue?: boolean;\n\n /**\n * Show a clear button when a value is selected.\n */\n clearable?: boolean;\n\n className?: string;\n};\n\ntype ComboboxOption = any;\n\ntype FormContentInputProps = Omit<\n UiFormContentInputProps,\n 'value' | 'onChange' | 'contentSettings' | 'oneLine'\n> & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Show the required-field indicator next to the label. This does not add validation.\n */\n isRequired?: boolean | null;\n\n /**\n * TipTap JSON content value.\n */\n value: JSONContent | null;\n\n /**\n * Called with the updated TipTap JSON content.\n */\n onChange: (value: JSONContent) => void;\n\n /**\n * Content field settings from the resource field or workflow variable.\n */\n contentSettings?: ContentSettings;\n\n /**\n * Render a single-line content editor.\n */\n oneLine?: boolean;\n};\n\ntype UiFormContentInputProps = any;\n\ntype JSONContent = any;\n\ntype ContentSettings = {\n extensions?: ExtensionsSettings;\n placeholder?: string | null;\n allowed_nodes?: AllowedNodesSettings;\n enable_skill_suggestions?: boolean;\n};\n\ntype ExtensionsSettings = {\n bold?: boolean;\n italic?: boolean;\n strike?: boolean;\n underline?: boolean;\n link?: boolean;\n} | null;\n\ntype AllowedNodesSettings = {\n heading?: boolean;\n blockquote?: boolean;\n bulletList?: boolean;\n orderedList?: boolean;\n media?: boolean;\n} | null;\n\ntype FormDateInputProps = PrimitiveFormFieldProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Selected value such as `yyyy-MM-dd`.\n */\n value: string;\n\n /**\n * Called when the selected date changes.\n */\n onChange: (value: string) => void;\n\n /**\n * Selection granularity.\n */\n granularity?: 'day' | 'month' | 'year';\n disabled?: boolean;\n placeholder?: string;\n\n /**\n * Earliest selectable date, `yyyy-MM-dd`.\n */\n minDate?: string;\n\n /**\n * Latest selectable date, `yyyy-MM-dd`.\n */\n maxDate?: string;\n};\n\ntype FormDatetimeInputProps = PrimitiveFormFieldProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Serialized datetime value.\n */\n value: string;\n\n /**\n * Called when date or time changes.\n */\n onChange: (value: string) => void;\n\n /**\n * Allow second-level precision.\n */\n showSeconds?: boolean;\n disabled?: boolean;\n placeholder?: string;\n clearable?: boolean;\n\n /**\n * Earliest selectable date, `yyyy-MM-dd`.\n */\n minDate?: string;\n\n /**\n * Latest selectable date, `yyyy-MM-dd`.\n */\n maxDate?: string;\n};\n\ntype FormFileUploadProps = PrimitiveFormFieldProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation error shown below the input.\n */\n error?: string | string[];\n\n /**\n * Whether the field is required.\n */\n isRequired?: boolean;\n\n /**\n * Current file value: a temp path (`tmp/\u2026`) after a fresh upload, a stored file ID for\n * an existing file, or null when no file is selected.\n */\n value?: string | null;\n\n /**\n * Called with the temp path after a successful upload, or null when the file is removed.\n * Persist the temp path to your form state; the backend resolves it to a permanent file\n * record on save.\n */\n onChange: (value: string | null) => void;\n\n /**\n * Accepted file types passed to the file picker, e.g. `\".pdf,.docx\"` or `\"image/*\"`.\n */\n accept?: string | string[];\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n};\n\ntype FormLocationInputProps = PrimitiveFormFieldProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input group.\n */\n helpText?: React.ReactNode;\n\n /**\n * General validation error shown below the field group.\n */\n error?: string | string[];\n\n /**\n * Whether the field is required.\n */\n isRequired?: boolean;\n\n /**\n * Current location value.\n */\n value?: LocationValue | null;\n\n /**\n * Called when the location changes.\n */\n onChange?: (value: LocationValue | null) => void;\n\n /**\n * Placeholder for the collapsed address autocomplete input.\n */\n placeholder?: string;\n\n /**\n * Disable address edits while keeping the structured details expandable for inspection.\n */\n disabled?: boolean;\n\n /**\n * Per-subfield validation errors, keyed by address field name\n * (`line1`, `line2`, `city`, `state`, `postal_code`, `country`).\n */\n errors?: LocationAddressFormErrors;\n};\n\ntype LocationValue = {\n point: { x: number | null; y: number | null } | null;\n display_name: string | null;\n line1: string | null;\n line2: string | null;\n city: string | null;\n county: string | null;\n state: string | null;\n country: string | null;\n country_code: string | null;\n postal_code: string | null;\n place_id: string | null;\n};\n\ntype LocationAddressFormErrors = Partial<\n Record<'line1' | 'line2' | 'city' | 'state' | 'postal_code' | 'country', string | string[]>\n>;\n\ntype FormMarkdownInputProps = Omit<\n UiFormMarkdownInputProps,\n | keyof FormFieldPropsWithoutChildren\n | 'showMediaControls'\n | 'allowedMarks'\n | 'allowedBlocks'\n | 'editorClassName'\n | 'value'\n | 'onChange'\n> & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Show the required-field indicator next to the label. This does not add validation.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Input element id.\n */\n id?: string;\n\n /**\n * Markdown string value.\n */\n value: string;\n\n /**\n * Called with the updated Markdown string.\n */\n onChange: (value: string) => void;\n\n /**\n * Placeholder shown while the Markdown value is empty.\n */\n placeholder?: string;\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n\n /**\n * Show the formatting toolbar.\n */\n showFormattingToolbar?: boolean;\n\n /**\n * Place the formatting toolbar inside the input or above it.\n */\n toolbarPlacement?: 'floating' | 'fixed';\n\n /**\n * Accessible name for the input.\n */\n ariaLabel?: string;\n\n /**\n * Additional classes for the editor.\n */\n className?: string;\n};\n\ntype UiFormMarkdownInputProps = any;\n\ntype FormFieldPropsWithoutChildren = any;\n\ntype FormMoneyInputProps = PrimitiveFormInputProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Amount in minor units (e.g. cents).\n */\n value: number | null;\n\n /**\n * Called with the amount in minor units.\n */\n onChange: (value: number | null) => void;\n\n /**\n * ISO currency code. Defaults to USD.\n */\n currency?: string;\n onCurrencyChange?: (currency: string) => void;\n currencies?: Array<{\n readonly label: string;\n readonly value: string;\n readonly symbol: string;\n }>;\n disabled?: boolean;\n placeholder?: string;\n};\n\ntype PrimitiveFormInputProps = PrimitiveFormFieldProps & PrimitiveInputNativeProps;\n\ntype PrimitiveInputNativeProps = Omit<\n PrimitiveHtmlProps<HTMLInputElement>,\n 'children' | 'onChange'\n> &\n Omit<\n Partial<Pick<React.InputHTMLAttributes<HTMLInputElement>, PrimitiveInputAttributeName>>,\n 'onChange' | 'type' | 'value'\n >;\n\ntype HTMLInputElement = any;\n\ntype PrimitiveInputAttributeName =\n | 'accept'\n | 'alt'\n | 'autoComplete'\n | 'capture'\n | 'checked'\n | 'disabled'\n | 'form'\n | 'formAction'\n | 'formEncType'\n | 'formMethod'\n | 'formNoValidate'\n | 'formTarget'\n | 'height'\n | 'list'\n | 'max'\n | 'maxLength'\n | 'min'\n | 'minLength'\n | 'multiple'\n | 'name'\n | 'pattern'\n | 'placeholder'\n | 'readOnly'\n | 'required'\n | 'size'\n | 'src'\n | 'step'\n | 'type'\n | 'value'\n | 'width'\n | 'onChange';\n\ntype FormMultiComboboxInputProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Show the required-field indicator next to the label. This does not add validation.\n */\n isRequired?: boolean | null;\n\n /**\n * Available choices.\n */\n options: MultiSelectOption[];\n\n /**\n * Selected option values.\n */\n selected: string[];\n\n /**\n * Called with selected values.\n */\n onChange: (values: string[]) => void;\n\n /**\n * Placeholder shown when no values are selected.\n */\n placeholder?: string;\n\n className?: string;\n\n /**\n * Show a search input in the menu.\n */\n allowSearch?: boolean;\n\n /**\n * Allow values not present in `options`.\n */\n allowCustomValues?: boolean;\n};\n\ntype MultiSelectOption = any;\n\ntype FormNumberInputProps = Omit<\n UiFormTextInputProps,\n 'value' | 'onChange' | 'type' | 'placeholder'\n> & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Show the required-field indicator next to the label. This does not add validation.\n */\n isRequired?: boolean | null;\n\n /**\n * Controlled value.\n */\n value?: string | number;\n\n /**\n * Called with the new string value.\n */\n onChange: TextInputProps['onChange'];\n\n /**\n * Placeholder text.\n */\n placeholder?: string;\n};\n\ntype UiFormTextInputProps = any;\n\ntype TextInputProps = any;\n\ntype FormPasswordInputProps = PrimitiveFormInputProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Controlled value.\n */\n value?: string;\n\n /**\n * Called with the new value.\n */\n onChange: (value: string) => void;\n\n /**\n * Placeholder text.\n */\n placeholder?: string;\n disabled?: boolean;\n readOnly?: boolean;\n showToggle?: boolean;\n isFocused?: boolean;\n isLoading?: boolean;\n prefixIcon?: React.ElementType;\n};\n\ntype FormPhoneInputProps = Omit<\n UiFormPhoneInputProps,\n 'value' | 'onChange' | 'defaultCountry' | 'allowExtension'\n> & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Phone number value. An optional extension is serialized with the number,\n * such as `+14039014573 ext. 123`.\n */\n value?: UiFormPhoneInputProps['value'];\n\n /**\n * Called when the phone number changes. Receives the number, with an optional\n * extension serialized as `+14039014573 ext. 123`.\n */\n onChange?: UiFormPhoneInputProps['onChange'];\n\n /**\n * Default country code such as `US`.\n */\n defaultCountry?: UiFormPhoneInputProps['defaultCountry'];\n};\n\ntype UiFormPhoneInputProps = any;\n\ntype FormRadioProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Actions shown beside the input.\n */\n inputActions?: React.ReactNode;\n\n /**\n * Radio options.\n */\n options: RadioOption[];\n\n /**\n * Controlled selected value.\n */\n value?: string;\n\n /**\n * Initial selected value for uncontrolled usage.\n */\n defaultValue?: string;\n\n /**\n * Called when the selected value changes.\n */\n onValueChange?: (value: string) => void;\n\n /**\n * Disable all options.\n */\n disabled?: boolean;\n\n className?: string;\n};\n\ntype FormSelectInputProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Actions shown beside the input.\n */\n inputActions?: React.ReactNode;\n\n /**\n * Available choices.\n */\n options: SelectOption[];\n\n /**\n * Controlled selected value.\n */\n value?: string;\n\n /**\n * Initial selected value for uncontrolled usage.\n */\n defaultValue?: string;\n\n /**\n * Called when the selected value changes.\n */\n onValueChange?: (value: string) => void;\n\n /**\n * Placeholder shown before selection.\n */\n placeholder?: string;\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n\n className?: string;\n};\n\ntype SelectOption = {\n /**\n * Option label.\n */\n label: React.ReactNode;\n\n /**\n * Option value used by the form.\n */\n value: string;\n\n /**\n * Disable this option.\n */\n disabled?: boolean;\n};\n\ntype FormSliderProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Actions shown beside the input.\n */\n inputActions?: React.ReactNode;\n\n /**\n * Controlled slider values.\n */\n value?: number[];\n\n /**\n * Initial slider values for uncontrolled usage.\n */\n defaultValue?: number[];\n\n /**\n * Called when values change.\n */\n onValueChange?: (value: number[]) => void;\n\n /**\n * Minimum value.\n */\n min?: number;\n\n /**\n * Maximum value.\n */\n max?: number;\n\n /**\n * Step increment.\n */\n step?: number;\n\n /**\n * Show the current value beside the slider.\n */\n showValue?: boolean;\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n\n className?: string;\n};\n\ntype FormSwitchProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Actions shown beside the input.\n */\n inputActions?: React.ReactNode;\n\n /**\n * Label layout.\n */\n labelPlacement?: 'stacked' | 'inline';\n\n /**\n * Controlled checked state.\n */\n checked?: boolean;\n\n /**\n * Initial checked state for uncontrolled usage.\n */\n defaultChecked?: boolean;\n\n /**\n * Called when the switch changes.\n */\n onCheckedChange?: (checked: boolean) => void;\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n\n className?: string;\n};\n\ntype FormTextAreaProps = PrimitiveFormTextareaProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Controlled value.\n */\n value?: string;\n\n /**\n * Called with the new value.\n */\n onChange: (value: string) => void;\n\n /**\n * Placeholder text.\n */\n placeholder?: string;\n disabled?: boolean;\n rows?: number;\n};\n\ntype PrimitiveFormTextareaProps = PrimitiveFormFieldProps & PrimitiveTextareaNativeProps;\n\ntype PrimitiveTextareaNativeProps = Omit<PrimitiveHtmlProps<HTMLTextAreaElement>, 'onChange'> &\n Omit<\n Partial<\n Pick<React.TextareaHTMLAttributes<HTMLTextAreaElement>, PrimitiveTextareaAttributeName>\n >,\n 'onChange' | 'value'\n >;\n\ntype HTMLTextAreaElement = any;\n\ntype PrimitiveTextareaAttributeName =\n | 'autoComplete'\n | 'cols'\n | 'dirName'\n | 'disabled'\n | 'form'\n | 'maxLength'\n | 'minLength'\n | 'name'\n | 'placeholder'\n | 'readOnly'\n | 'required'\n | 'rows'\n | 'value'\n | 'wrap'\n | 'onChange';\n\ntype FormTextInputProps = PrimitiveFormInputProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Controlled value.\n */\n value?: string;\n\n /**\n * Called with the new value.\n */\n onChange: (value: string) => void;\n\n /**\n * Input type.\n */\n type?:\n | 'text'\n | 'email'\n | 'password'\n | 'number'\n | 'date'\n | 'time'\n | 'datetime-local'\n | 'url'\n | 'search'\n | 'tel'\n | 'color'\n | 'hidden'\n | 'range'\n | 'textarea';\n\n /**\n * Placeholder text.\n */\n placeholder?: string;\n disabled?: boolean;\n autoComplete?: string;\n name?: string;\n isFocused?: boolean;\n isLoading?: boolean;\n prefixIcon?: React.ElementType;\n};\n\ntype FormTimeInputProps = PrimitiveFormFieldProps & {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Selected time value.\n */\n value: string;\n\n /**\n * Called when the selected time changes.\n */\n onChange: (value: string) => void;\n\n /**\n * Allow second-level precision.\n */\n showSeconds?: boolean;\n disabled?: boolean;\n placeholder?: string;\n};\n\ntype FormTimezoneInputProps = {\n /**\n * Field label.\n */\n label?: React.ReactNode;\n\n /**\n * Help text shown below the input.\n */\n helpText?: React.ReactNode;\n\n /**\n * Validation errors shown below the input.\n */\n error?: string | string[];\n\n /**\n * Mark the field as required.\n */\n isRequired?: boolean | null;\n\n /**\n * Actions shown beside the label.\n */\n labelActions?: React.ReactNode;\n\n /**\n * Actions shown beside the input.\n */\n inputActions?: React.ReactNode;\n\n /**\n * Label layout.\n */\n labelPlacement?: 'stacked' | 'inline';\n\n /**\n * Controlled timezone identifier.\n */\n value?: string;\n\n /**\n * Initial timezone identifier for uncontrolled usage.\n */\n defaultValue?: string;\n\n /**\n * Called when the timezone changes.\n */\n onChange?: (value: string) => void;\n\n /**\n * Placeholder shown before selection.\n */\n placeholder?: string;\n\n /**\n * Disable the input.\n */\n disabled?: boolean;\n\n className?: string;\n};\n\ntype HasAnyPermissionProps = PermissionGateProps & {\n /** Permission ids or slugs. The current identity must hold at least one. */\n permissions: readonly string[];\n};\n\ntype PermissionGateProps = {\n /** Content to render when the check passes. It does not mount when the check fails. */\n children?: React.ReactNode;\n\n /** Content to render when the check fails. Renders nothing when omitted. */\n fallback?: React.ReactNode;\n};\n\ntype HasPermissionProps = PermissionGateProps & {\n /** Permission id or slug that the current identity must hold. */\n permission: string;\n};\n\ntype IconProps = {\n /**\n * Lucide icon name in camelCase, PascalCase, or kebab-case.\n */\n name?: string | null;\n\n /**\n * Additional CSS classes.\n */\n className?: string;\n\n /**\n * Icon size in pixels.\n */\n size?: number;\n\n /**\n * SVG stroke width.\n */\n strokeWidth?: number;\n\n /**\n * Direct color override (any CSS color). Defaults to `currentColor`.\n */\n color?: string;\n};\n\ntype IframeProps = React.IframeHTMLAttributes<HTMLIFrameElement> & {\n /**\n * Embed URL. Only use with URLs the workspace trusts.\n */\n src: string;\n\n /**\n * Accessible iframe title.\n */\n title: string;\n\n /**\n * Additional iframe classes.\n */\n className?: string;\n};\n\ntype HTMLIFrameElement = any;\n\ntype ImageProps = Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'alt' | 'src'> & {\n ref?: React.Ref<HTMLImageElement>;\n\n /**\n * Image URL. Pass `Sdk.utils.file(record.file_field)` directly; null shows the fallback.\n */\n src?: string | null;\n\n /**\n * Accessible image description. Use an empty string only when the image is decorative.\n */\n alt: string;\n};\n\ntype HTMLImageElement = any;\n\ntype KeyValueListProps = {\n /**\n * Rows of labeled values.\n */\n items: KeyValueItem[];\n\n /**\n * Rendered in place of a row's value when it is empty (`null`, `undefined`, or an\n * empty string). `FormFieldDisplay` values inherit it too, unless they set their own\n * `fallback`. Omit to leave empty raw values blank and keep the em dash in field\n * displays. The fallback renders exactly as given \u2014 the caller owns its styling, so\n * pass a muted node (e.g. `<em>Not set</em>` or a `text-muted-foreground` span) if it\n * should read as a placeholder rather than a value.\n */\n fallback?: React.ReactNode;\n\n className?: string;\n};\n\ntype KeyValueItem = {\n /**\n * Row label.\n */\n label: React.ReactNode;\n\n /**\n * Row value.\n */\n value: React.ReactNode;\n\n /**\n * Optional supporting text shown below the value.\n */\n description?: React.ReactNode;\n};\n\ntype ListProps = {\n /**\n * List item definitions.\n */\n items: ListItem[];\n\n /**\n * Text shown when `items` is empty.\n */\n emptyText?: string;\n\n className?: string;\n};\n\ntype ListItem = {\n /**\n * Stable identifier for the row, used as the React key.\n */\n id?: string | number;\n\n /**\n * Row title.\n */\n title: React.ReactNode;\n\n /**\n * Optional supporting text below the title.\n */\n description?: React.ReactNode;\n\n /**\n * Optional metadata shown on the right (e.g. status, timestamp).\n */\n meta?: React.ReactNode;\n\n /**\n * Optional row action element.\n */\n action?: React.ReactNode;\n};\n\ntype LoginFormProps = {\n /**\n * The authenticatable resource to sign in against. Defaults to the workspace's sole\n * authenticatable resource; required only when the workspace has more than one.\n */\n resourceId?: string;\n\n /** Label for the identifier (email) field. */\n identifierLabel?: React.ReactNode;\n\n /** Placeholder for the identifier (email) field. */\n identifierPlaceholder?: string;\n\n /** Label for the password field. */\n passwordLabel?: React.ReactNode;\n\n /** Placeholder for the password field. */\n passwordPlaceholder?: string;\n\n /** Contents of the button that submits the email and moves on to verification. */\n continueLabel?: React.ReactNode;\n\n /** Contents of the password submit button. */\n submitLabel?: React.ReactNode;\n\n /** Contents of the button that requests an emailed sign-in link. */\n magicLinkLabel?: React.ReactNode;\n\n /**\n * Contents of the button shown when the identified account has no password yet \u2014 it\n * mails a link to set one, alongside the emailed sign-in link when the resource\n * offers both.\n */\n setPasswordLabel?: React.ReactNode;\n\n /** Additional classes for the form element. */\n className?: string;\n\n /** Additional classes for the submit button. */\n buttonClassName?: string;\n\n /**\n * Id of the page to land on after a successful sign-in \u2014 pass a stable page id, never\n * a path; the server resolves the page's route itself. A stored intended URL \u2014 the\n * protected URL the visitor was bounced to the login page from \u2014 takes precedence, the\n * workspace homepage is the final fallback, and an id that doesn't resolve (unknown,\n * unpublished, parameterized route, other workspace) is ignored rather than failing\n * the login. For destinations that depend on who logged in, redirect to a static page\n * that reads `Sdk.identity` instead.\n */\n redirectPage?: string;\n\n /**\n * Return the visitor to the exact current page URL after password or Magic Link\n * sign-in, path parameters and query string included. Use `redirectPage` instead\n * for a static, parameter-free destination.\n */\n returnTo?: 'current';\n\n /**\n * Where a sign-in that needs two-factor continues. `'inline'`, the default, shows the\n * SMS challenge, or the first MFA setup, in place of this form. `'hosted'` leaves the\n * page for Diagonal's challenge page.\n */\n twoFactor?: 'inline' | 'hosted';\n};\n\ntype UserControlProps = {\n /** Additional classes for the trigger button. */\n className?: string;\n\n /** Button visual style. */\n variant?: ButtonProps['variant'];\n\n /** Button size preset. */\n size?: ButtonProps['size'];\n};\n\ntype MapProps = {\n /**\n * Diagonal location value (e.g. from workflow input data). The component shows a\n * placeholder when maps are unavailable.\n */\n location: any;\n\n /**\n * Container sizing classes.\n */\n className?: string;\n};\n\ntype ModalProps = {\n /**\n * Controlled open state.\n */\n open?: boolean;\n\n /**\n * Initial uncontrolled open state.\n */\n defaultOpen?: boolean;\n\n /**\n * Called when the modal opens or closes.\n */\n onOpenChange?: (open: boolean) => void;\n\n /**\n * Element that opens the modal.\n */\n trigger?: React.ReactNode;\n\n /**\n * Modal heading.\n */\n title?: React.ReactNode;\n\n /**\n * Supporting text below the title.\n */\n description?: React.ReactNode;\n\n /**\n * Main modal body.\n */\n children: React.ReactNode;\n\n /**\n * Footer actions. With an escape action (Cancel), wrap them in\n * `<div className=\"flex w-full items-center justify-between gap-2\">`: a ghost Cancel first,\n * then the other actions in one group with the confirming action last, so Cancel sits at\n * the leading edge and the confirming action at the trailing edge. A lone Close button\n * needs no wrapper; the footer keeps it at the trailing edge.\n */\n footer?: React.ReactNode;\n\n /**\n * Width preset. Defaults to 'md'.\n */\n size?: ModalSize;\n\n /**\n * Whether the modal can be closed by the user via the close button or escape key.\n */\n isCloseable?: boolean;\n\n className?: string;\n};\n\ntype ModalSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\ntype NavMainProps = {\n /** Navigation menu items, typically `Sdk.navigationMenu(id).items`. */\n navigation: any[];\n\n /** Optional group label rendered above the items. */\n label?: string;\n};\n\ntype NotificationComposerProps = {\n /** Saved type, template, and literal context values. Supply recipient only for a type with a recipient resource. */\n input: SdkPreviewNotificationInput;\n /** Complete draft for the chosen channels, or null while resolving, after a resolve failure, or while the choice cannot be sent. Never sends. */\n onChange: (draft: SdkResolvedNotification | null) => void;\n /** The channels the page offers, out of those the template enables. Omit to offer every channel the template enables. */\n channels?: SdkNotificationChannel[];\n /** Start on fewer channels than the recipient can receive. Ignored when the recipient cannot receive one of them. */\n defaultChannel?: SdkNotificationDeliveryChannel;\n /** Omit to allow every layout field. An empty array locks every layout field. */\n editableFields?: string[];\n /** Allow subject edits. Defaults to true. */\n editableSubject?: boolean;\n /** Allow text message edits. Defaults to true. */\n editableSmsBody?: boolean;\n /** Show the optional layout field source editors. Defaults to false. */\n showLayoutFields?: boolean;\n /** Lock all editors while the parent submits its action. */\n disabled?: boolean;\n /** Called when the notification cannot be resolved. Not called while it only waits for variable values. */\n onError?: (error: SdkApiError) => void;\n /** Optional container classes. */\n className?: string;\n};\n\ntype NotificationConsentFormProps = NotificationConsentFormBaseProps &\n NotificationConsentFormChannelProps;\n\ntype NotificationConsentFormBaseProps = {\n /**\n * The notifiable resource of the record whose consent this form manages. Omit it and\n * `recordId` to manage the signed-in person's own record, which needs no permission.\n */\n resourceId?: string;\n\n /**\n * The record whose consent this form manages. Another person's record follows the\n * resource's record policies, as a record edit does.\n */\n recordId?: string;\n\n /** Label of the email row. */\n emailLabel?: React.ReactNode;\n\n /** Text under the email label. */\n emailDescription?: React.ReactNode;\n\n /** Label of the text-message row. */\n smsLabel?: React.ReactNode;\n\n /** Disable every switch, for example while the record has no phone number yet. */\n disabled?: boolean;\n\n /** Called after a switch change was saved. */\n onSaved?: (consent: SdkNotificationConsent) => void;\n\n /** Called when a save fails. The switch reverts and the row shows the error too. */\n onError?: (error: SdkApiError) => void;\n\n /** Show when consent last changed under the rows. */\n showChangedAt?: boolean;\n\n /** Additional classes for the container. */\n className?: string;\n};\n\ntype NotificationConsentFormChannelProps =\n | {\n /**\n * The rows to show, in order. Defaults to email and text messages. A list that\n * includes `sms` requires `smsDisclosure`.\n */\n channels: Array<'email'>;\n smsDisclosure?: never;\n }\n | {\n /**\n * The rows to show, in order. Defaults to email and text messages. A list that\n * includes `sms` requires `smsDisclosure`.\n */\n channels?: NotificationConsentChannel[];\n\n /**\n * Text under the text-message label that tells the person what they agree to. The\n * platform ships no legal wording: write it for your workspace, for example with\n * `Sdk.workspace.name`, and state how to opt out. Required when the rows include SMS.\n */\n smsDisclosure: React.ReactNode;\n };\n\ntype NotificationConsentChannel = 'email' | 'sms';\n\ntype NotificationHistoryProps = {\n /** Resource the record belongs to. Pair it with `recordId` for one record's history. */\n resourceId?: string;\n\n /** Record whose notifications are shown. */\n recordId?: string;\n\n /**\n * Records whose notifications are shown together. Replaces `resourceId` and `recordId`.\n * The viewer needs Show access to every record.\n */\n records?: { resourceId: string; recordId: string }[];\n\n /**\n * Show every notification in the workspace. Requires the `manage_notifications`\n * permission; other viewers see an explanation instead.\n */\n workspace?: boolean;\n\n /** The lifecycle tab that opens first. Defaults to `upcoming`. */\n defaultStatus?: SdkNotificationHistoryStatus;\n\n /** Filters applied to every tab. `emailStatus` applies to the Sent tab only. */\n filters?: NotificationHistoryFilters;\n\n /** Rows per page, 1 to 100. Previous/next controls appear when there is more than one page. */\n perPage?: number;\n\n /** Show the rendered email body (Preview / Source tabs) for each email attempt. */\n showEmailBody?: boolean;\n};\n\ntype NotificationHistoryFilters = Pick<\n SdkNotificationHistoryOptions,\n | 'resourceId'\n | 'search'\n | 'typeName'\n | 'templateName'\n | 'source'\n | 'emailStatus'\n | 'dateFrom'\n | 'dateBefore'\n | 'timezone'\n | 'sort'\n>;\n\ntype NotificationVariablesFormProps = {\n /** The notification type whose variables the form edits. Key the form by it to start over when it changes. */\n notificationTypeId: string;\n /** Values keyed by variable key. A record variable holds its record id. */\n value: Record<string, any>;\n /** Receives the next values: only this type's variables, with each record as its id. Never sends anything. */\n onChange: (value: Record<string, any>) => void;\n /** The failed send, preview, or schedule request. Its `variables` messages show above and beside the inputs. */\n error?: SdkApiError | null;\n /** Lock every input while the parent submits. */\n disabled?: boolean;\n /** Optional container classes. */\n className?: string;\n};\n\ntype PageHeaderProps = {\n /**\n * Main page title.\n */\n title: string;\n\n /**\n * Short page summary.\n */\n description?: string;\n\n /**\n * Action buttons or links displayed beside the title.\n */\n actions?: React.ReactNode;\n\n className?: string;\n};\n\ntype PaymentFormProps = {\n /**\n * Payable resource id (e.g. the Invoices resource).\n */\n payableResourceId: string;\n\n /**\n * Payable record id to pay.\n */\n payableRecordId: string;\n\n /**\n * Partial amount in cents with its currency. Omit to pay the full\n * outstanding balance.\n */\n amount?: { amount: number; currency: string };\n\n /**\n * Pay button label. Defaults to \"Pay\" with the formatted amount.\n */\n label?: string;\n\n /**\n * How a newly entered payment method is saved to the payable's payer for\n * future (off-session) payments. 'always' saves every payment method and shows a\n * disclosure line; 'ask' shows a consent checkbox.\n * Omit to only offer the checkbox to the authenticated payer.\n * Ignored when the payable has no payer to save to. Without `onSubmitPayment`,\n * also ignored for a visitor whom the payer's Payment Methods policies do not\n * let save a payment method, such as a guest: that visitor pays without saving.\n */\n savePaymentMethod?: 'always' | 'ask';\n\n /**\n * Save the successful payment method and make it the payer's default.\n * This applies to both newly entered and already-saved methods.\n * Ignored when the payable has no payer to save to. A newly entered method\n * follows the same visitor rule as `savePaymentMethod`.\n */\n setPaymentMethodAsDefault?: boolean;\n\n /**\n * The purchaser's name and email for an invoice with no payer (a guest\n * purchase), such as the purchaser the page stored for the checkout.\n * Stripe records them on the payment, and Wallet shows them.\n * Ignored when the invoice has a payer.\n * Never used to find a payer or a saved payment method.\n */\n billingDetails?: { name?: string; email?: string };\n\n /**\n * Called after the payment succeeds or enters processing.\n */\n onPaid?: () => void;\n\n /**\n * Submit a Stripe ConfirmationToken through a request workflow instead of the\n * built-in intent endpoint. Token creation does not charge the payment method.\n * Return the Payment: Confirm submitted payment output directly, not an envelope.\n * Custom submission receives either a ConfirmationToken or a saved method record ID.\n * Use the closure to add checkout context. Authorize the payable in the workflow.\n */\n onSubmitPayment?: (submission: PaymentSubmission) => Promise<PaymentSubmissionResult>;\n\n /** Called for succeeded or processing separately. Processing is not settlement. */\n onPaymentResult?: (status: 'succeeded' | 'processing') => void;\n};\n\ntype PaymentSubmission = {\n payable_resource_id: string;\n payable_record_id: string;\n amount: { amount: number; currency: string } | null;\n expected_total: { amount: number; currency: string };\n save_payment_method: boolean;\n set_payment_method_as_default: boolean;\n} & (\n | { confirmation_token_id: string; payment_method_id?: never; payment_attempt_id?: never }\n | { confirmation_token_id?: never; payment_method_id: string; payment_attempt_id: string }\n);\n\ntype PaymentSubmissionResult = {\n payment_intent_id: string;\n payment_record_id?: string | null;\n status: 'succeeded' | 'processing' | 'requires_action';\n client_secret: string | null;\n};\n\ntype PaymentMethodsProps = {\n /**\n * Payer resource id.\n */\n resourceId: string;\n\n /**\n * Payer record id.\n */\n recordId: string;\n\n /** Show and add payment methods for this accounting entity only. */\n accountingEntityId?: string;\n\n /**\n * Only show the saved list and the add flow, hiding the set-default and\n * remove actions.\n */\n collectOnly?: boolean;\n};\n\ntype PieChartProps<TRow extends object = Record<string, any>> = {\n /**\n * Rows to chart.\n */\n data: TRow[];\n\n /**\n * Field key for slice labels.\n */\n nameKey: Extract<keyof TRow, string>;\n\n /**\n * Field key for slice values.\n */\n valueKey: Extract<keyof TRow, string>;\n\n /**\n * Optional chart heading.\n */\n title?: string;\n\n /**\n * Chart height in pixels.\n */\n height?: number;\n};\n\ntype PopoverProps = {\n /**\n * Element that opens the popover.\n */\n trigger: React.ReactNode;\n\n /**\n * Popover body content.\n */\n content: React.ReactNode;\n\n /**\n * Controlled open state.\n */\n open?: boolean;\n\n /**\n * Called when the popover opens or closes.\n */\n onOpenChange?: (open: boolean) => void;\n\n /**\n * Alignment relative to the trigger.\n */\n align?: 'start' | 'center' | 'end';\n\n /**\n * Preferred side relative to the trigger.\n */\n side?: 'top' | 'right' | 'bottom' | 'left';\n\n className?: string;\n};\n\ntype QrCodeProps = {\n /**\n * Value encoded into the QR code. Pass a non-empty value; the component shows a helpful\n * placeholder when missing.\n */\n value?: string | null;\n\n /**\n * Optional label shown beneath the QR code.\n */\n label?: React.ReactNode;\n\n className?: string;\n};\n\ntype RecordFieldsProps = {\n /** Resource handle returned by `Sdk.resource('<resourceId>')`. */\n resource: Pick<SdkResource, 'id' | 'fields' | 'can' | 'update'>;\n\n /** The loaded record whose values are displayed and edited. */\n record: Record<string, any> | null | undefined;\n\n /**\n * Render exactly these fields, in this order, named by key or by handle. System,\n * virtual, and relation fields are honoured here \u2014 naming a field is an explicit\n * request for it. Omit to render every field the page does not already place.\n */\n fields?: readonly FieldSelector[];\n\n /**\n * Catch-all mode: field keys the page places by hand. Everything else on the\n * resource renders here, so fields added later appear without page edits.\n * Ignored when `fields` is given.\n */\n exclude?: readonly FieldSelector[];\n\n /** Section heading. Defaults to \"Additional Fields\"; pass `null` for no heading. */\n title?: React.ReactNode;\n\n /**\n * Whether identities with the `manage_fields` permission see the \"Add field\"\n * action. Defaults to true in catch-all mode and false when `fields` is given \u2014\n * a field created there would not appear in an explicit list.\n */\n canAddFields?: boolean;\n\n /** Optional container className. */\n className?: string;\n\n /** Called after field values are saved on the record. */\n onChanged?: () => void;\n};\n\ntype FieldSelector = string | SdkResourceField;\n\ntype RecordScheduleDisplayProps = {\n /**\n * Resource the record belongs to.\n */\n resourceId: string;\n\n /**\n * Record whose schedule is shown.\n */\n recordId: string;\n\n /**\n * Limit the display to a single record-schedule workflow. Without it, every\n * published schedule the record participates in is shown.\n */\n recordScheduleWorkflowId?: string;\n};\n\ntype ResourceTableProps<\n TResource extends ResourceDataTableResource,\n TRelations extends readonly SdkResourceRelationPath<NoInfer<TResource>>[] = readonly []\n> = UseResourceDataTableOptions<TResource, TRelations> &\n ResourceTablePresentationProps<SdkResourceRecord<TResource, TRelations>>;\n\ntype ResourceTablePresentationProps<TRecord extends RowData> = {\n /** Show a search input in the default toolbar. */\n searchable?: boolean;\n\n /** Placeholder for the search input. */\n searchPlaceholder?: string;\n\n /** Show the column-visibility toggle in the default toolbar. */\n columnToggle?: boolean;\n\n /** Show the CSV-export dropdown when the resource permits exports. */\n isExportable?: boolean;\n\n /** Extra controls rendered in the default toolbar. */\n toolbarActions?: ReactNode;\n\n /**\n * Bulk action controls shown when rows are selected. Providing this prop enables row\n * selection unless `enableRowSelection` is explicitly `false`.\n */\n bulkActions?: DataTableBulkActionContent<TRecord>;\n\n /** Show pagination controls below the table. Defaults to `true`. */\n pagination?: boolean;\n\n /** Called with the resource record when a row is clicked. */\n onRowClick?: (row: TRecord) => void;\n\n /** Message shown when there are no rows. */\n emptyMessage?: ReactNode;\n\n /** Message shown when the records request fails. */\n errorMessage?: ReactNode;\n\n className?: string;\n\n /**\n * Advanced: provide a custom `DataTable` layout. The table and resource-filter\n * contexts remain available to the composable table components.\n */\n children?: ReactNode;\n};\n\ntype RestoreResourceRecordProps = {\n /**\n * Resource handle returned by `Sdk.resource('<resourceId>')`.\n */\n resource: RestoreResourceRecordResource & Pick<SdkResource, 'policy_scope'>;\n\n /**\n * Record ID to restore.\n */\n recordId: string | null | undefined;\n\n /**\n * Button label.\n */\n label?: string;\n\n /**\n * Heading shown in the confirmation dialog.\n */\n confirmTitle?: React.ReactNode;\n\n /**\n * Supporting text shown in the confirmation dialog.\n */\n confirmDescription?: React.ReactNode;\n\n /**\n * Called after a successful restoration, before navigation.\n */\n onSuccess?: () => void;\n\n /**\n * Optional container className.\n */\n className?: string;\n};\n\ntype RichTextProps = {\n /**\n * HTML string to render. Accepts untrusted HTML and sanitizes it before rendering.\n */\n html?: string | null;\n\n /**\n * Rich text nodes used when not rendering raw HTML.\n */\n children?: React.ReactNode;\n\n /**\n * Additional container classes.\n */\n className?: string;\n};\n\ntype ScheduledNotificationComposerProps = {\n /** The occurrence to open, such as an id from useUpcomingNotifications. */\n scheduledNotificationId: string;\n /** Edited raw content, ready to save as content_override. Null while nothing changed, and when the occurrence cannot be edited. */\n onChange: (content: SdkScheduledNotificationContent | null, expectedRevision?: number) => void;\n /** Preview for an unsaved recipient change instead of the saved recipient. */\n recipient?: SdkNotificationRecipient;\n /** Omit to allow every text layout field. An empty array locks every layout field. */\n editableFields?: string[];\n /** Allow subject edits. Defaults to true. */\n editableSubject?: boolean;\n /** Lock all editors while the parent saves. */\n disabled?: boolean;\n /** Called when the occurrence cannot be loaded. */\n onError?: (error: SdkApiError) => void;\n className?: string;\n};\n\ntype SearchBarProps = {\n /**\n * Called with the debounced search value.\n */\n onSearch: (value: string) => void;\n\n /**\n * Input placeholder text.\n */\n placeholder?: string;\n\n /**\n * Initial search value.\n */\n defaultValue?: string;\n\n /**\n * Show a loading affordance.\n */\n isLoading?: boolean;\n className?: string;\n};\n\ntype SeparatorProps = PrimitiveHtmlProps<HTMLDivElement> & {\n asChild?: boolean;\n orientation?: 'horizontal' | 'vertical';\n decorative?: boolean;\n};\n\ntype HTMLDivElement = any;\n\ntype SharedFiltersProps = {\n /**\n * Headless shared-filter state from `Sdk.hooks.useSharedFilters` (or the\n * `filters.sharedFilters` returned by `Sdk.hooks.useResourceDataTable`). Optional when\n * rendered inside a `<Sdk.Components.DataTable filters={...}>` \u2014 it is read from\n * context there.\n */\n sharedFilters?: SharedFiltersApi;\n className?: string;\n};\n\ntype ShowResourceRecordResource = SdkResourceQueryHandle<'show'> &\n Pick<SdkResource, 'fields'>;\n\ntype ShowResourceRecordProps<\n TResource extends ShowResourceRecordResource & Pick<SdkResource, 'policy_scope'>,\n TRelations extends readonly SdkResourceRelationPath<NoInfer<TResource>>[] = readonly []\n> = {\n /** Resource handle returned by `Sdk.resource('<resourceId>')`. */\n resource: TResource;\n\n /** Record ID to display, commonly from `Sdk.helpers.useInput('record_id')` or a selected\n * DataTable row. */\n recordId: string | null | undefined;\n\n /** Field handles that control which fields are rendered and their order.\n * Omit to render all non-system, non-virtual fields accessible at Show scope. */\n fields?: SdkResourceField[];\n\n /** Relation paths to preload and expose on the render prop's typed `record`. */\n relations?: TRelations;\n\n /** Soft-delete subset to read from. Defaults to active records only; pass\n * `RecordScope.ActiveOrArchived` to also display archived (soft-deleted) records. */\n scope?: RecordScope;\n\n /** Optional container className. */\n className?: string;\n\n /**\n * Custom layout render prop. When provided, the default field list is replaced by\n * the children.\n *\n * `FieldLabel` renders the field's label text. `FieldValue` renders the type-aware\n * formatted value (booleans as badges, money with currency, selects as option badges,\n * etc.). Both are context-aware \u2014 they read the value from the loaded record\n * automatically; no `value` prop needed. `FieldValue` also takes a `fallback` to\n * replace the em dash shown for an empty value.\n *\n * @example\n * <Sdk.Components.ShowResourceRecord resource={students} recordId={recordId}>\n * {({ FieldLabel, FieldValue }) => (\n * <dl className=\"grid grid-cols-2 gap-4\">\n * <div>\n * <FieldLabel field={students.fields.name} />\n * <FieldValue field={students.fields.name} />\n * </div>\n * <div>\n * <FieldLabel field={students.fields.status} />\n * <FieldValue field={students.fields.status} fallback=\"Not set\" />\n * </div>\n * </dl>\n * )}\n * </Sdk.Components.ShowResourceRecord>\n */\n children?: (\n state: ShowResourceRecordRenderState<SdkResourceRecord<TResource, TRelations>>\n ) => React.ReactNode;\n};\n\ntype ShowResourceRecordRenderState<\n TRecord extends Record<string, any> = Record<string, any>\n> = {\n FieldLabel: React.ComponentType<ShowResourceRecordFieldComponentProps>;\n FieldValue: React.ComponentType<ShowResourceRecordFieldValueProps>;\n record: TRecord | undefined;\n fields: SdkResourceField[];\n isLoading: boolean;\n};\n\ntype ShowResourceRecordFieldComponentProps = {\n field: SdkResourceField;\n className?: string;\n};\n\ntype ShowResourceRecordFieldValueProps = ShowResourceRecordFieldComponentProps & {\n /**\n * Rendered in place of the default em dash when the field's value is empty.\n * Pass `null` to render nothing at all. Omit to inherit an enclosing fallback\n * (such as `KeyValueList`'s) or keep the em dash. The fallback renders exactly\n * as given \u2014 the caller owns its styling.\n */\n fallback?: React.ReactNode;\n};\n\ntype SidebarProps = PrimitiveHtmlProps<HTMLDivElement> & {\n /** Which edge the sidebar docks to. */\n side?: 'left' | 'right';\n\n /** Visual style: a full-height sidebar, floating panel, or inset panel. */\n variant?: 'sidebar' | 'floating' | 'inset';\n\n /** Collapse behavior: slide offcanvas, shrink to icons, or stay fixed. */\n collapsible?: 'offcanvas' | 'icon' | 'none';\n\n /** Additional classes for the sidebar container. */\n className?: string;\n\n /** Sidebar sections, typically SidebarHeader, SidebarContent, and SidebarFooter. */\n children?: React.ReactNode;\n};\n\ntype SidebarTriggerProps = {\n /** Additional classes for the trigger button. */\n className?: string;\n};\n\ntype SidebarSectionProps = PrimitiveHtmlProps<HTMLElement> & {\n /** Additional classes for the section. */\n className?: string;\n\n /** Section contents. */\n children?: React.ReactNode;\n};\n\ntype SidebarMenuButtonProps = PrimitiveButtonNativeProps & {\n /** Render the child element as the button instead of a native button. */\n asChild?: boolean;\n\n /** Highlight the button as the active menu entry. */\n isActive?: boolean;\n\n /** Visual style preset. */\n variant?: 'default' | 'outline' | null;\n\n /** Button size preset. */\n size?: 'default' | 'sm' | 'lg' | null;\n\n /** Tooltip shown while the sidebar is collapsed to icons. */\n tooltip?: string | SidebarTooltipProps;\n\n /** Click handler for action buttons. */\n onClick?: React.MouseEventHandler<HTMLButtonElement>;\n\n /** Additional classes for the button. */\n className?: string;\n\n /** Button contents, typically an icon and a label. */\n children?: React.ReactNode;\n};\n\ntype SidebarTooltipProps = PrimitiveHtmlProps<HTMLDivElement> & {\n forceMount?: true;\n side?: 'top' | 'right' | 'bottom' | 'left';\n sideOffset?: number;\n align?: 'start' | 'center' | 'end';\n alignOffset?: number;\n arrowPadding?: number;\n avoidCollisions?: boolean;\n collisionBoundary?: Element | null | Array<Element | null>;\n collisionPadding?: number | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>>;\n sticky?: 'partial' | 'always';\n hideWhenDetached?: boolean;\n updatePositionStrategy?: 'optimized' | 'always';\n onEscapeKeyDown?: (event: KeyboardEvent) => void;\n onPointerDownOutside?: (\n event: CustomEvent<{\n originalEvent: PointerEvent;\n }>\n ) => void;\n showArrow?: boolean;\n};\n\ntype Element = any;\n\ntype KeyboardEvent = any;\n\ntype CustomEvent = any;\n\ntype PointerEvent = any;\n\ntype PrimitiveListItemNativeProps = PrimitiveHtmlProps<HTMLLIElement> &\n Partial<Pick<React.LiHTMLAttributes<HTMLLIElement>, PrimitiveListItemAttributeName>>;\n\ntype HTMLLIElement = any;\n\ntype PrimitiveListItemAttributeName = 'value';\n\ntype SidebarProviderProps = PrimitiveHtmlProps<HTMLDivElement> & {\n /** Whether the sidebar starts expanded. Defaults to the persisted cookie state. */\n defaultOpen?: boolean;\n\n /** Controlled expanded state. */\n open?: boolean;\n\n /** Called when the controlled expanded state changes. */\n onOpenChange?: (open: boolean) => void;\n\n /** Additional classes for the provider wrapper. */\n className?: string;\n\n /** The sidebar and the page content it frames. */\n children?: React.ReactNode;\n};\n\ntype PrimitiveSidebarTriggerProps = Omit<PrimitiveButtonProps, 'asChild' | 'children'> & {\n /** Additional classes for the trigger button. */\n className?: string;\n};\n\ntype SimpleTableProps<TRow extends object> = {\n /**\n * Column definitions.\n */\n columns: TableColumn<TRow>[];\n\n /**\n * Rows to display.\n */\n rows: TRow[];\n\n /**\n * Text shown when `rows` is empty.\n */\n emptyText?: string;\n\n /**\n * Optional table caption.\n */\n caption?: string;\n\n className?: string;\n};\n\ntype TableColumn<TRow> = {\n /**\n * Row field key. Used both as the React key and as the default value lookup.\n */\n key: Extract<keyof TRow, string>;\n\n /**\n * Column header content.\n */\n header: React.ReactNode;\n\n /**\n * Optional custom cell renderer.\n */\n render?: (row: TRow) => React.ReactNode;\n};\n\ntype SkeletonProps = PrimitiveHtmlProps<HTMLDivElement> & {\n /**\n * Classes controlling the skeleton size and shape.\n */\n className?: string;\n};\n\ntype SlideoverProps = {\n /**\n * Controlled open state.\n */\n open?: boolean;\n\n /**\n * Initial uncontrolled open state.\n */\n defaultOpen?: boolean;\n\n /**\n * Called when the panel opens or closes.\n */\n onOpenChange?: (open: boolean) => void;\n\n /**\n * Element that opens the panel.\n */\n trigger?: React.ReactNode;\n\n /**\n * Panel heading.\n */\n title?: React.ReactNode;\n\n /**\n * Supporting text below the title.\n */\n description?: React.ReactNode;\n\n /**\n * Panel body.\n */\n children: React.ReactNode;\n\n /**\n * Footer actions. The footer stacks them vertically in source order.\n */\n footer?: React.ReactNode;\n\n /**\n * Side the panel opens from. Defaults to `right`.\n */\n side?: SlideoverSide;\n\n /**\n * Panel width preset. Defaults to `md`.\n */\n size?: SlideoverSize;\n\n className?: string;\n};\n\ntype SlideoverSide = 'top' | 'right' | 'bottom' | 'left';\n\ntype SlideoverSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\ntype SlotPickerProps = {\n /** The result of `Sdk.hooks.useAvailableSlots`. */\n search: Pick<\n SdkAvailableSlotsHandle,\n 'slots' | 'timezone' | 'stoppedEarly' | 'isLoading' | 'error' | 'refetch'\n >;\n /** The ID of the selected slot, or null when no slot is selected. */\n value?: string | null;\n /** Called with the slot that the visitor picks. */\n onChange: (slot: SdkAvailableSlot) => void;\n /** Shown when the search finds no free time. Defaults to `'No times are free in this period.'`. */\n emptyMessage?: string;\n /** Accessible name of the slot group. Defaults to `'Available times'`. */\n label?: string;\n disabled?: boolean;\n className?: string;\n};\n\ntype SpinnerProps = Omit<PrimitiveHtmlProps<HTMLSpanElement>, 'children'> & {\n size?: 'sm' | 'md' | 'lg' | 'xl' | null;\n color?: 'default' | 'primary' | 'muted' | null;\n inline?: boolean | null;\n};\n\ntype StatCardProps = {\n /**\n * Metric label.\n */\n title: React.ReactNode;\n\n /**\n * Primary metric value.\n */\n value: React.ReactNode;\n\n /**\n * Optional supporting context.\n */\n description?: React.ReactNode;\n\n /**\n * Lucide icon name shown in the top-right of the card.\n */\n icon?: string;\n\n /**\n * Trend or comparison text.\n */\n trend?: React.ReactNode;\n\n className?: string;\n};\n\ntype StepProps = {\n /** Step label shown in the indicator and the \"Step X of N: <label>\" caption. */\n label: string;\n\n /** Fields and content shown when this step is the active one. */\n children?: React.ReactNode;\n};\n\ntype StepperProps = {\n /** `Sdk.Components.Step` children, one per step, in order. */\n children: React.ReactNode;\n\n /** Index to start on. Defaults to `0`. */\n initialStep?: number;\n\n /** Label for the back button. Defaults to \"Previous\". */\n backLabel?: string;\n\n /** Label for the advance button. Defaults to \"Next\". */\n nextLabel?: string;\n\n /** Label for the final-step action button. Defaults to \"Submit\". */\n submitLabel?: string;\n\n /**\n * Called when the final step's action button is clicked. When provided, that button is a\n * plain button. When omitted, the final button is `type=\"submit\"`, so an enclosing\n * `Sdk.Components.CreateResourceRecordForm` / `Form` handles submission.\n */\n onComplete?: () => void;\n\n /** Disables the final-step action button, e.g. while the enclosing form is submitting. */\n isSubmitDisabled?: boolean;\n\n className?: string;\n};\n\ntype SubscriptionBillingPreviewProps = SubscriptionBillingPreviewCommonProps &\n (\n | (SdkSubscriptionPreviewOptions & { previewResult?: never })\n | {\n /** Hook result to display instead of fetching from selection inputs. */\n previewResult: SdkSubscriptionPreviewHandle;\n }\n );\n\ntype SubscriptionBillingPreviewCommonProps = {\n /** Quote the visitor previously confirmed. A different quote requires confirmation again. */\n confirmedQuote?: string | null;\n /** Current reviewable quote, or null during loading, failure, or disabled requests. */\n onQuoteChange?: (quote: string | null) => void;\n className?: string;\n};\n\ntype SubscriptionChangePreviewProps = SubscriptionChangePreviewCommonProps &\n (\n | (SdkSubscriptionChangePreviewOptions & { previewResult?: never })\n | {\n /** Hook result to display instead of fetching from selection inputs. */\n previewResult: SdkSubscriptionChangePreviewHandle;\n }\n );\n\ntype SubscriptionChangePreviewCommonProps = {\n /** Quote the visitor previously confirmed. A different quote requires confirmation again. */\n confirmedQuote?: string | null;\n /**\n * Current reviewable quote, or null during loading, failure, disabled requests, and\n * for a selection that matches the current subscription.\n */\n onQuoteChange?: (quote: string | null) => void;\n /**\n * Builds the URL of an invoice page from an invoice record ID, such as with\n * `Sdk.utils.page.url`. No invoice link shows without it, or when it returns null.\n */\n invoiceHref?: (invoiceRecordId: string) => string | null;\n className?: string;\n};\n\ntype SupportChatButtonProps = {\n /** Additional classes for the trigger button. */\n className?: string;\n};\n\ntype TabsProps = {\n /**\n * Tab definitions.\n */\n tabs: Tab[];\n\n /**\n * Initially selected tab for uncontrolled usage.\n */\n defaultValue?: string;\n\n /**\n * Controlled active tab value.\n */\n value?: string;\n\n /**\n * Called when the active tab changes.\n */\n onValueChange?: (value: string) => void;\n\n /**\n * Visual style. Defaults to `pill`.\n */\n variant?: TabsVariant;\n\n className?: string;\n listClassName?: string;\n contentClassName?: string;\n};\n\ntype Tab = {\n /**\n * Tab identifier used by `value`/`defaultValue`.\n */\n value: string;\n\n /**\n * Tab trigger label.\n */\n label: React.ReactNode;\n\n /**\n * Tab panel content.\n */\n content: React.ReactNode;\n};\n\ntype TabsVariant = 'pill' | 'underline' | 'rail';\n\ntype ThemeToggleProps = {\n /** Additional classes for the button. */\n className?: string;\n\n /** Button visual style. */\n variant?: ButtonProps['variant'];\n\n /** Button size preset. */\n size?: ButtonProps['size'];\n};\n\ntype TimerProps = {\n /**\n * Date-like target value. Renders a placeholder for null/invalid values.\n */\n target?: string | number | Date | null;\n\n /**\n * Optional label shown above the timestamp.\n */\n label?: React.ReactNode;\n\n className?: string;\n};\n\ntype TooltipProps = {\n /**\n * Element that receives the tooltip.\n */\n trigger: React.ReactNode;\n\n /**\n * Tooltip text or content.\n */\n content: React.ReactNode;\n\n /**\n * Preferred side for the tooltip.\n */\n side?: 'top' | 'right' | 'bottom' | 'left';\n};\n\ntype TwoFactorBackupCodeListProps = {\n /** The plain backup codes from `codes` of a two-factor hook. */\n codes: string[];\n\n /** Shown under the codes, such as the button that continues. */\n children?: React.ReactNode;\n\n /** Additional classes for the container. */\n className?: string;\n};\n\ntype TwoFactorBackupCodesProps = {\n /** The authenticatable resource. Defaults to the signed-in member's resource. */\n resourceId?: string;\n\n /** Additional classes for the container. */\n className?: string;\n\n /** Additional classes for the main buttons. */\n buttonClassName?: string;\n};\n\ntype TwoFactorChallengeProps = {\n /**\n * The authenticatable resource. Defaults to the resource of the pending sign-in, then\n * the workspace's sole authenticatable resource.\n */\n resourceId?: string;\n\n /** Label of the code input. */\n codeLabel?: React.ReactNode;\n\n /** Contents of the verify buttons. */\n verifyLabel?: React.ReactNode;\n\n /** Called after the visitor chose to start over and the pending sign-in ended. */\n onCancel?: () => void;\n\n /** Additional classes for the container. */\n className?: string;\n\n /** Additional classes for the main buttons. */\n buttonClassName?: string;\n};\n\ntype TwoFactorSendCodeButtonProps = TwoFactorSmsConsentProps & {\n /** Contents of the button. */\n label?: React.ReactNode;\n\n /** The button variant. */\n variant?: 'default' | 'outline' | 'link';\n};\n\ntype TwoFactorSmsConsentProps = {\n /** The flow whose code this consent sends: a sign-in challenge, an MFA setup, or the proof for new backup codes. */\n flow: SdkTwoFactorFlow;\n\n /** The authenticatable resource. Defaults as in `Sdk.hooks.useTwoFactorChallenge`. */\n resourceId?: string;\n\n /** Additional classes for the container. */\n className?: string;\n\n /** Additional classes for the submit button. */\n buttonClassName?: string;\n};\n\ntype TwoFactorSetupProps = {\n /**\n * The authenticatable resource. Defaults to the resource of the pending sign-in, then of\n * the signed-in member.\n */\n resourceId?: string;\n\n /** Called after a signed-in member's new MFA number is active. */\n onComplete?: () => void;\n\n /** Called after the visitor stopped the setup. */\n onCancel?: () => void;\n\n /** Additional classes for the container. */\n className?: string;\n\n /** Additional classes for the main buttons. */\n buttonClassName?: string;\n};\n\ntype UpdateFormResource = SdkResourceType &\n UpdateResourceRecordFormResource &\n Pick<SdkResource, 'policy_scope'>;\n\ntype UpdateResourceRecordFormProps<TResource extends UpdateFormResource = UpdateFormResource> = {\n /** Resource handle returned by `Sdk.resource('<resourceId>')`. */\n resource: TResource;\n\n /** Record ID to edit, commonly from `Sdk.helpers.useInput('record_id')` or a selected DataTable row. */\n recordId: string | null | undefined;\n\n /** Field handles that control which fields the form renders and their order.\n * Omit to render the resource's configured update fields. */\n fields?: SdkResourceField[];\n\n /** Editable seed values applied before the record loads (or when no `recordId`\n * is set). The user can still change them. */\n initialValues?: Record<string, any>;\n\n /** Locked writable Resource field values \u2014 rendered as disabled inputs and included\n * even when omitted from `fields`. Unknown, system, virtual, or inaccessible keys are\n * ignored. Reactive: if the prop changes after mount the form updates. */\n fixedValues?: Record<string, any>;\n\n /** Include every updatable field, ignoring per-field `show_on_updating` form\n * visibility. For elevated surfaces (e.g. the admin edit page). Ignored when\n * explicit `fields` are provided. */\n includeAllFields?: boolean;\n\n /** How relation fields resolve inline attach/create. Defaults to `'settings'`;\n * pass `'always'` to force them on. */\n relationVisibility?: RelationVisibility;\n\n /** Render the resource's published `updated`/`saved` RecordEvent workflow inputs in the\n * managed layout, stacked beneath the resource fields (on the final step for `steps`).\n * Defaults to `false`. Ignored when `children` is provided \u2014 render `WorkflowInputs`\n * yourself there. */\n includeWorkflowInputs?: boolean;\n\n /**\n * Multi-step (wizard) layout. Each step groups fields under a label; the form renders the\n * numbered step indicator, \"Step X of N\" caption, Previous/Next navigation, and the submit\n * button on the last step \u2014 no render prop or submit wiring needed. All steps share one\n * form: values persist across steps and the final submit saves the whole record.\n * Use `review: true` for a read-only summary step, or `content` to customize one step's\n * layout. Navigation is free \u2014 moving between steps never validates or blocks.\n * Ignored when `children` is provided.\n */\n steps?: ResourceRecordFormStep<UpdateResourceRecordFormRenderState<TResource>>[];\n\n /** Submit button label. Only used when `children` is not provided. */\n submitLabel?: string;\n\n /** When provided, the default layout's secondary button becomes a Cancel action\n * wired to this callback (instead of Reset). */\n onCancel?: () => void;\n\n /** Cancel button label. Defaults to \"Cancel\". Only used with `onCancel`. */\n cancelLabel?: string;\n\n /** Toast message shown after a successful submission. Pass `null` to suppress. */\n successMessage?: string | null;\n\n /** Called after a successful submission with the updated record. */\n onSuccess?: (record: SdkResourceRecord<TResource>) => void;\n\n /** Optional container className. */\n className?: string;\n\n /**\n * Custom layout render prop. When provided, the managed layout is replaced while\n * retaining the form tag and submit handler. It receives reusable `Fields`,\n * `Actions`, and `WorkflowInputs` components, `FieldInput` / `WorkflowInput` for\n * individual controls, and `workflowInputs` \u2014 the resource's published\n * `updated`/`saved` RecordEvent workflow inputs keyed by input key.\n *\n * `FieldInput` renders the correctly-wired input for a resource field handle and\n * returns `null` for handles that are not part of the active form. `WorkflowInputs`\n * renders every applicable workflow input; `<WorkflowInput input={workflowInputs.<key>} />`\n * renders one. When a render function is provided, `steps` is ignored.\n *\n * @example\n * <Sdk.Components.UpdateResourceRecordForm resource={students} recordId={recordId}>\n * {({ FieldInput, form }) => (\n * <div className=\"grid grid-cols-2 gap-4\">\n * <FieldInput field={students.fields.first_name} />\n * <FieldInput field={students.fields.last_name} />\n * <FieldInput field={students.fields.email} className=\"col-span-2\" />\n * <Sdk.Components.Button type=\"submit\" disabled={form.isSubmitting}>\n * Save student\n * </Sdk.Components.Button>\n * </div>\n * )}\n * </Sdk.Components.UpdateResourceRecordForm>\n */\n children?: (state: UpdateResourceRecordFormRenderState<TResource>) => React.ReactNode;\n};\n\ntype UpdateResourceRecordFormRenderState<\n TResource extends Pick<SdkResource, 'workflow_inputs'> = Pick<SdkResource, 'workflow_inputs'>\n> = {\n FieldInput: (props: FormFieldProps) => React.ReactNode;\n WorkflowInput: (props: WorkflowInputProps) => React.ReactNode;\n /** Renders every applicable RecordEvent workflow input, stacked in order. */\n WorkflowInputs: (props: WorkflowInputsProps) => React.ReactNode;\n Fields: (props: ResourceRecordFormFieldsProps) => React.ReactNode;\n Actions: (props: ResourceRecordFormActionsProps) => React.ReactNode;\n form: UpdateResourceRecordFormHandle;\n fields: SdkResourceField[];\n /** The resource's published `updated`/`saved` RecordEvent workflow inputs, keyed\n * by input key \u2014 pass one to `WorkflowInput` or render them all via `WorkflowInputs`. */\n workflowInputs: SdkResourceFormWorkflowInputs<TResource, 'saved' | 'updated'>;\n};\n\ntype UpdateResourceRecordFormHandle = SdkResourceForm & {\n /** Internal \u2014 consumed by the turnkey component. */\n isLoading: boolean;\n /** Plural relation fields the form leaves to the caller in `'table'` mode. */\n relationTableFields: SdkResourceField[];\n};\n\ntype VideoProps = React.VideoHTMLAttributes<HTMLVideoElement> & {\n /**\n * Video URL.\n */\n src: string;\n\n /**\n * Accessible title.\n */\n title?: string;\n\n /**\n * Show browser playback controls.\n */\n controls?: boolean;\n\n /**\n * URL of a WebVTT captions file. Provide this for deaf and hard-of-hearing\n * viewers; when set the player exposes a captions track.\n */\n captionsSrc?: string;\n\n /**\n * BCP-47 language of the captions track (defaults to \"en\").\n */\n captionsSrcLang?: string;\n\n /**\n * Human-readable label for the captions track.\n */\n captionsLabel?: string;\n};\n\ntype HTMLVideoElement = any;\n\ntype WorkflowFormProps = {\n /** The workflow's slug or ID. Both values are stable across title renames. */\n workflowId: WorkflowReference;\n\n /** Route parameter values for parameterized published routes\n * (e.g. `/invites/{id}` needs `params={{ id: recordId }}`). */\n params?: Record<string, any>;\n\n /** Input-field keys that control which fields the form renders and their order.\n * Omit to render every field from the workflow's input schema; unknown keys are\n * ignored. */\n fields?: string[];\n\n /** Editable seed values. Captured once on mount \u2014 when seeding from fetched data,\n * gate rendering until it has loaded. The user can still change them; they form\n * the `reset()` baseline. */\n initialValues?: Record<string, any>;\n\n /** Locked field values \u2014 rendered as disabled inputs and always included in the\n * submission payload. Reactive: if the prop changes after mount the form updates. */\n fixedValues?: Record<string, any>;\n\n /** Submit button label. Defaults to \"Submit\". Only used when `children` is not\n * provided. */\n submitLabel?: string;\n\n /** When provided, the default layout's secondary button becomes a Cancel action\n * wired to this callback (instead of Reset). Only used when `children` is not\n * provided. */\n onCancel?: () => void;\n\n /** Cancel button label. Defaults to \"Cancel\". Only used with `onCancel`. */\n cancelLabel?: string;\n\n /** Toast message shown after a successful submission. Defaults to \"Submitted.\"\n * for mutation workflows and to no toast for GET workflows. Pass `null` to\n * suppress. */\n successMessage?: string | null;\n\n /** Called after a successful submission with the workflow's response payload. */\n onSuccess?: (response: SdkWorkflowResponse) => void;\n\n /** Called when the submission resolves with a non-2xx response, with\n * `{ status, data, error }`. Validation errors (422) also populate the form's\n * per-field errors automatically. */\n onError?: (context: WorkflowFormErrorContext) => void;\n\n /** Clear the form back to its initial values after a successful submission.\n * Defaults to `true` for mutation workflows and `false` for GET workflows, so\n * a search form keeps its filters. */\n resetOnSuccess?: boolean;\n\n /** Optional container className. */\n className?: string;\n\n /**\n * Custom layout render prop. When provided, the default field list and submit\n * button are replaced by the children. The form tag and onSubmit handler are\n * still managed by the component \u2014 use a `type=\"submit\"` button or call\n * `form.submit()` to trigger submission.\n *\n * `FieldInput` renders the correctly-wired input for a field handle from\n * `fields` and returns `null` for handles that are not part of the active form.\n * `submitLabel` is ignored when `children` is provided.\n *\n * @example\n * <Sdk.Components.WorkflowForm workflowId=\"send-invite\">\n * {({ FieldInput, form, fields }) => (\n * <div className=\"grid grid-cols-2 gap-4\">\n * {fields.map((field) => (\n * <FieldInput key={field.key} field={field} />\n * ))}\n * <Sdk.Components.Button type=\"submit\" disabled={form.isSubmitting}>\n * Send\n * </Sdk.Components.Button>\n * </div>\n * )}\n * </Sdk.Components.WorkflowForm>\n */\n children?: (state: WorkflowFormRenderState) => React.ReactNode;\n};\n\ntype WorkflowFormErrorContext = {\n status: number;\n data: any;\n error: any;\n};\n\ntype WorkflowFormRenderState = {\n FieldInput: React.ComponentType<FormFieldProps>;\n form: WorkflowFormHandle;\n fields: SdkResourceField[];\n};\n\ntype WorkflowFormHandle = SdkWorkflowForm;\n\ntype WorkspaceFieldsFormProps = {\n /** Workspace field definitions (`Sdk.workspace.fields.<key>`) to render, in order.\n * Omit to render every field that is not hidden and not read-only. Read-only and\n * hidden entries are dropped either way, because the endpoint refuses them. */\n fields?: Array<SdkWorkspaceField | undefined>;\n\n /** Editable seed values that override the workspace's current values. */\n initialValues?: Record<string, any>;\n\n /** Locked values submitted even when omitted from `fields`; rendered disabled. */\n fixedValues?: Record<string, any>;\n\n /** Submit button label. Defaults to \"Save\". Only used when `children` is not provided. */\n submitLabel?: string;\n\n /** When provided, the default layout's secondary button becomes a Cancel action\n * wired to this callback (instead of Reset). */\n onCancel?: () => void;\n\n /** Cancel button label. Defaults to \"Cancel\". Only used with `onCancel`. */\n cancelLabel?: string;\n\n /** Toast message shown after a successful save. Pass `null` to suppress. */\n successMessage?: string | null;\n\n /** Called after a successful save with the saved values keyed by field key. */\n onSuccess?: (values: SdkWorkspaceValues) => void;\n\n /** Optional container className. */\n className?: string;\n\n /**\n * Custom layout render prop. Replaces the managed layout while retaining the form\n * tag and submit handler. `FieldInput` renders the wired input for a workspace\n * field definition and returns `null` for definitions not in the active set.\n *\n * @example\n * <Sdk.Components.WorkspaceFieldsForm fields={[fields.support_email]}>\n * {({ FieldInput, form }) => (\n * <>\n * <FieldInput field={fields.support_email} />\n * <Sdk.Components.Button type=\"submit\" disabled={form.isSubmitting}>Save</Sdk.Components.Button>\n * </>\n * )}\n * </Sdk.Components.WorkspaceFieldsForm>\n */\n children?: (state: WorkspaceFieldsFormRenderState) => React.ReactNode;\n};\n\ntype WorkspaceFieldsFormRenderState = {\n FieldInput: (props: FormFieldProps) => React.ReactNode;\n Fields: (props: ResourceRecordFormFieldsProps) => React.ReactNode;\n Actions: (props: ResourceRecordFormActionsProps) => React.ReactNode;\n form: SdkWorkspaceFieldsForm;\n fields: SdkWorkspaceField[];\n};\n\nconst sdkResourceRelationCatalog: unique symbol;\n\nconst sdkResourceRecordCatalog: unique symbol;\n\ntype DynamicResourceId<TId extends string> =\n TId & (string extends TId ? TId : never);\n\ninterface WorkspaceFieldCatalog { [key: string]: any }\n\ninterface WorkspaceFieldDefinitionCatalog { [key: string]: SdkWorkspaceField }\n}\n\ndeclare namespace Sdk {\n export type ResourceFieldSettingsUi = import(\"diagonal:sdk-internal\").SdkResourceFieldSettingsUi;\n export type ResourceFieldSettings = import(\"diagonal:sdk-internal\").SdkResourceFieldSettings;\n export type ListItemField = import(\"diagonal:sdk-internal\").SdkListItemField;\n export type ResourceField = import(\"diagonal:sdk-internal\").SdkResourceField;\n export type ResourceCapabilityFieldMappings = import(\"diagonal:sdk-internal\").SdkResourceCapabilityFieldMappings;\n export type ResourceSortDescriptor = import(\"diagonal:sdk-internal\").ResourceSortDescriptor;\n export type ResourceSortFieldDescriptor = import(\"diagonal:sdk-internal\").ResourceSortFieldDescriptor;\n export type ResourceSortDirection = import(\"diagonal:sdk-internal\").ResourceSortDirection;\n export type ResourceSortInput = import(\"diagonal:sdk-internal\").ResourceSortInput;\n export type ResourceListParams<TRelations extends readonly string[] = readonly string[]> = import(\"diagonal:sdk-internal\").ResourceListParams<TRelations>;\n export type ResourceSearchBody<TRelations extends readonly string[] = readonly string[]> = import(\"diagonal:sdk-internal\").ResourceSearchBody<TRelations>;\n export type ResourceCountOptions = import(\"diagonal:sdk-internal\").ResourceCountOptions;\n export type ResourceSearchAllOptions<TRelations extends readonly string[] = readonly string[]> = import(\"diagonal:sdk-internal\").ResourceSearchAllOptions<TRelations>;\n export type ResourcePagination = import(\"diagonal:sdk-internal\").ResourcePagination;\n export type ResourceCollectionResult<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").ResourceCollectionResult<TRecord>;\n export type ResourceRecordsResult<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").ResourceRecordsResult<TRecord>;\n export type ResourceRecordResult<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").ResourceRecordResult<TRecord>;\n export type ResourceRecordPayload<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").ResourceRecordPayload<TRecord>;\n export type ResourceBulkUpdateRecord<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").ResourceBulkUpdateRecord<TRecord>;\n export type ResourceDeletePayload = import(\"diagonal:sdk-internal\").ResourceDeletePayload;\n export type ResourceOperationRecord<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").ResourceOperationRecord<TRecord>;\n export type ResourceUrls = import(\"diagonal:sdk-internal\").ResourceUrls;\n export type ResourceFieldUpdatePayload = import(\"diagonal:sdk-internal\").ResourceFieldUpdatePayload;\n export type DndIdentifier = import(\"diagonal:sdk-internal\").SdkDndIdentifier;\n export type DndSortableItem = import(\"diagonal:sdk-internal\").SdkDndSortableItem;\n export type DndItemIdentifier<TItem> = import(\"diagonal:sdk-internal\").SdkDndItemIdentifier<TItem>;\n export type DndSortableOptions<TItem> = import(\"diagonal:sdk-internal\").SdkDndSortableOptions<TItem>;\n export type DndReorderChange<TItem> = import(\"diagonal:sdk-internal\").SdkDndReorderChange<TItem>;\n export type DndReorderResult<TItem> = import(\"diagonal:sdk-internal\").SdkDndReorderResult<TItem>;\n export type DndSortableRenderState = import(\"diagonal:sdk-internal\").SdkDndSortableRenderState;\n export type DndLocation = import(\"diagonal:sdk-internal\").SdkDndLocation;\n export type DndGroupedLocation = import(\"diagonal:sdk-internal\").SdkDndGroupedLocation;\n export type DndMoveContext<TItem, TLocation extends import(\"diagonal:sdk-internal\").SdkDndLocation = import(\"diagonal:sdk-internal\").SdkDndLocation> = import(\"diagonal:sdk-internal\").SdkDndMoveContext<TItem, TLocation>;\n export type DndCanDrag<TItem, TLocation extends import(\"diagonal:sdk-internal\").SdkDndLocation = import(\"diagonal:sdk-internal\").SdkDndLocation> = import(\"diagonal:sdk-internal\").SdkDndCanDrag<TItem, TLocation>;\n export type DndCanDrop<TItem, TLocation extends import(\"diagonal:sdk-internal\").SdkDndLocation = import(\"diagonal:sdk-internal\").SdkDndLocation> = import(\"diagonal:sdk-internal\").SdkDndCanDrop<TItem, TLocation>;\n export type DndSortableListCommonProps<TItem> = import(\"diagonal:sdk-internal\").SdkDndSortableListCommonProps<TItem>;\n export type DndCustomPersistence<TItem> = import(\"diagonal:sdk-internal\").SdkDndCustomPersistence<TItem>;\n export type DndResourcePersistence<TItem> = import(\"diagonal:sdk-internal\").SdkDndResourcePersistence<TItem>;\n export type DndSortableListProps<TItem> = import(\"diagonal:sdk-internal\").SdkDndSortableListProps<TItem>;\n export type DndSortableListResourceProps<TResource extends import(\"diagonal:sdk-internal\").SdkResourceType & Pick<import(\"diagonal:sdk-internal\").SdkResource, 'fields' | 'updateMany'>, TItem extends import(\"diagonal:sdk-internal\").SdkDndResourceItem<NoInfer<TResource>> = import(\"diagonal:sdk-internal\").SdkDndResourceItem<TResource>> = import(\"diagonal:sdk-internal\").SdkDndSortableListResourceProps<TResource, TItem>;\n export type DndSortableListComponent = import(\"diagonal:sdk-internal\").SdkDndSortableListComponent;\n export type DndSortableGroupRenderState<TItem> = import(\"diagonal:sdk-internal\").SdkDndSortableGroupRenderState<TItem>;\n export type DndGroupMoveChange<TItem> = import(\"diagonal:sdk-internal\").SdkDndGroupMoveChange<TItem>;\n export type DndGroupMoveResult<TItem> = import(\"diagonal:sdk-internal\").SdkDndGroupMoveResult<TItem>;\n export type DndSortableGroupsCommonProps<TItem, TGroup> = import(\"diagonal:sdk-internal\").SdkDndSortableGroupsCommonProps<TItem, TGroup>;\n export type DndSortableGroupsCustomPersistence<TItem, TGroup> = import(\"diagonal:sdk-internal\").SdkDndSortableGroupsCustomPersistence<TItem, TGroup>;\n export type DndSortableGroupsResourcePersistence<TItem, TGroup> = import(\"diagonal:sdk-internal\").SdkDndSortableGroupsResourcePersistence<TItem, TGroup>;\n export type DndSortableGroupsProps<TItem, TGroup> = import(\"diagonal:sdk-internal\").SdkDndSortableGroupsProps<TItem, TGroup>;\n export type DndSortableGroupsResourceProps<TResource extends import(\"diagonal:sdk-internal\").SdkResourceType & Pick<import(\"diagonal:sdk-internal\").SdkResource, 'fields' | 'updateMany'>, TItem extends import(\"diagonal:sdk-internal\").SdkDndResourceItem<NoInfer<TResource>>, TGroup> = import(\"diagonal:sdk-internal\").SdkDndSortableGroupsResourceProps<TResource, TItem, TGroup>;\n export type DndSortableGroupsComponent = import(\"diagonal:sdk-internal\").SdkDndSortableGroupsComponent;\n export type DndReorder = import(\"diagonal:sdk-internal\").SdkDndReorder;\n export type Dnd = import(\"diagonal:sdk-internal\").SdkDnd;\n export type CalendarLinks = import(\"diagonal:sdk-internal\").CalendarLinks;\n export type AnalyticsMetric = import(\"diagonal:sdk-internal\").AnalyticsMetric;\n export type AnalyticsTimeSeriesMetric = import(\"diagonal:sdk-internal\").AnalyticsTimeSeriesMetric;\n export type AnalyticsGranularity = import(\"diagonal:sdk-internal\").AnalyticsGranularity;\n export type AnalyticsValue = import(\"diagonal:sdk-internal\").AnalyticsValue;\n export type AnalyticsKpiResult<TMetrics extends readonly import(\"diagonal:sdk-internal\").AnalyticsMetric[] = import(\"diagonal:sdk-internal\").AnalyticsMetric[]> = import(\"diagonal:sdk-internal\").AnalyticsKpiResult<TMetrics>;\n export type AnalyticsRow = import(\"diagonal:sdk-internal\").AnalyticsRow;\n export type AnalyticsKpiParams<TMetrics extends readonly import(\"diagonal:sdk-internal\").AnalyticsMetric[] = import(\"diagonal:sdk-internal\").AnalyticsMetric[]> = import(\"diagonal:sdk-internal\").AnalyticsKpiParams<TMetrics>;\n export type AnalyticsTimeSeriesParams = import(\"diagonal:sdk-internal\").AnalyticsTimeSeriesParams;\n export type AnalyticsLeaderboardParams = import(\"diagonal:sdk-internal\").AnalyticsLeaderboardParams;\n export type AnalyticsDistributionParams = import(\"diagonal:sdk-internal\").AnalyticsDistributionParams;\n export type UseAnalyticsOptions = import(\"diagonal:sdk-internal\").SdkUseAnalyticsOptions;\n export type AnalyticsHandle<TResult> = import(\"diagonal:sdk-internal\").SdkAnalyticsHandle<TResult>;\n export type ResourceAnalytics = import(\"diagonal:sdk-internal\").SdkResourceAnalytics;\n export type ResourceOperation = import(\"diagonal:sdk-internal\").SdkResourceOperation;\n export type ResourceOperations<TResource extends import(\"diagonal:sdk-internal\").SdkResourceType = import(\"diagonal:sdk-internal\").SdkResourceType> = import(\"diagonal:sdk-internal\").SdkResourceOperations<TResource>;\n export type Resource<TFields extends Record<string, import(\"diagonal:sdk-internal\").SdkResourceField> = Record<string, import(\"diagonal:sdk-internal\").SdkResourceField>, TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord, TRelations extends import(\"diagonal:sdk-internal\").ResourceRelationCatalog = import(\"diagonal:sdk-internal\").ResourceRelationCatalog, TWorkflowInputs extends import(\"diagonal:sdk-internal\").SdkResourceWorkflowInputs = import(\"diagonal:sdk-internal\").SdkResourceWorkflowInputs> = import(\"diagonal:sdk-internal\").SdkResource<TFields, TRecord, TRelations, TWorkflowInputs>;\n export type ResourceRelationCatalog = import(\"diagonal:sdk-internal\").ResourceRelationCatalog;\n export type ResourceRecordCatalog = import(\"diagonal:sdk-internal\").ResourceRecordCatalog;\n export type RecordOf<TKey extends keyof import(\"diagonal:sdk-internal\").ResourceRecordCatalog> = import(\"diagonal:sdk-internal\").SdkRecordOf<TKey>;\n export type WorkflowReference = import(\"diagonal:sdk-internal\").WorkflowReference;\n export type PageAliasCatalog = import(\"diagonal:sdk-internal\").PageAliasCatalog;\n export type PageReference = import(\"diagonal:sdk-internal\").PageReference;\n export type PageHandle = import(\"diagonal:sdk-internal\").SdkPageHandle;\n export type PageRefParams = import(\"diagonal:sdk-internal\").PageRefParams;\n export type PageRef<K extends string = string, P extends import(\"diagonal:sdk-internal\").PageRefParams | undefined = import(\"diagonal:sdk-internal\").PageRefParams | undefined> = import(\"diagonal:sdk-internal\").PageRef<K, P>;\n export type ResourceType<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord, TRelations extends import(\"diagonal:sdk-internal\").ResourceRelationCatalog = import(\"diagonal:sdk-internal\").ResourceRelationCatalog> = import(\"diagonal:sdk-internal\").SdkResourceType<TRecord, TRelations>;\n export type ResourceRelationsFrom<TResource extends import(\"diagonal:sdk-internal\").SdkResourceType> = import(\"diagonal:sdk-internal\").ResourceRelationsFrom<TResource>;\n export type ResourceRelationPath<TResource extends import(\"diagonal:sdk-internal\").SdkResourceType> = import(\"diagonal:sdk-internal\").SdkResourceRelationPath<TResource>;\n export type SelectedResourceRecord<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord, TRelationCatalog extends import(\"diagonal:sdk-internal\").ResourceRelationCatalog, TRelations extends readonly Extract<keyof TRelationCatalog, string>[] = readonly []> = import(\"diagonal:sdk-internal\").SdkSelectedResourceRecord<TRecord, TRelationCatalog, TRelations>;\n export type ResourceRecordFrom<TResource extends import(\"diagonal:sdk-internal\").SdkResourceType | null | undefined, TRelations extends readonly import(\"diagonal:sdk-internal\").SdkResourceRelationPath<NonNullable<TResource>>[] = readonly []> = import(\"diagonal:sdk-internal\").SdkResourceRecord<TResource, TRelations>;\n export type ResourceReference = import(\"diagonal:sdk-internal\").SdkResourceReference;\n export type ResourceHandle = import(\"diagonal:sdk-internal\").SdkResourceHandle;\n export type ResourceQueryReference<TUrl extends keyof import(\"diagonal:sdk-internal\").ResourceUrls = 'show' | 'search'> = import(\"diagonal:sdk-internal\").SdkResourceQueryReference<TUrl>;\n export type ResourceQueryHandle<TUrl extends keyof import(\"diagonal:sdk-internal\").ResourceUrls = 'show' | 'search'> = import(\"diagonal:sdk-internal\").SdkResourceQueryHandle<TUrl>;\n export type AnalyticsResource<TKind extends keyof import(\"diagonal:sdk-internal\").SdkResourceAnalytics> = import(\"diagonal:sdk-internal\").SdkAnalyticsResource<TKind>;\n export type CatalogRelationPath<TResourceId extends string> = import(\"diagonal:sdk-internal\").SdkCatalogRelationPath<TResourceId>;\n export type CatalogRecord<TResourceId extends string, TRelations extends readonly import(\"diagonal:sdk-internal\").SdkCatalogRelationPath<TResourceId>[] = readonly []> = import(\"diagonal:sdk-internal\").SdkCatalogRecord<TResourceId, TRelations>;\n export type ReferenceRelationPath<TReference extends import(\"diagonal:sdk-internal\").ResourceTypeReference> = import(\"diagonal:sdk-internal\").SdkReferenceRelationPath<TReference>;\n export type ReferenceRecord<TReference extends import(\"diagonal:sdk-internal\").ResourceTypeReference, TRelations extends readonly import(\"diagonal:sdk-internal\").SdkReferenceRelationPath<TReference>[] = readonly []> = import(\"diagonal:sdk-internal\").SdkReferenceRecord<TReference, TRelations>;\n export type PollInterval<TData> = import(\"diagonal:sdk-internal\").SdkPollInterval<TData>;\n export type UsePollOptions = import(\"diagonal:sdk-internal\").SdkUsePollOptions;\n export type PollControls = import(\"diagonal:sdk-internal\").SdkPollControls;\n export type UseRecordOptions<TRelations extends readonly string[] = readonly string[], TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkUseRecordOptions<TRelations, TRecord>;\n export type RecordHandle<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkRecordHandle<TRecord>;\n export type UseRecordsOptions<TRelations extends readonly string[] = readonly string[], TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkUseRecordsOptions<TRelations, TRecord>;\n export type RecordsHandle<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkRecordsHandle<TRecord>;\n export type UseInfiniteRecordsOptions<TRelations extends readonly string[] = readonly string[]> = import(\"diagonal:sdk-internal\").SdkUseInfiniteRecordsOptions<TRelations>;\n export type InfiniteRecordsHandle<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkInfiniteRecordsHandle<TRecord>;\n export type RecordMutationState<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkRecordMutationState<TRecord>;\n export type UseCreateRecordOptions<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkUseCreateRecordOptions<TRecord>;\n export type CreateRecordHandle<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkCreateRecordHandle<TRecord>;\n export type UseUpdateRecordOptions<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkUseUpdateRecordOptions<TRecord>;\n export type UpdateRecordHandle<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkUpdateRecordHandle<TRecord>;\n export type EmailRecipient = import(\"diagonal:sdk-internal\").SdkEmailRecipient;\n export type SendEmailInput = import(\"diagonal:sdk-internal\").SdkSendEmailInput;\n export type SendEmailResponse = import(\"diagonal:sdk-internal\").SdkSendEmailResponse;\n export type UseSendEmailOptions = import(\"diagonal:sdk-internal\").SdkUseSendEmailOptions;\n export type SendEmailHandle = import(\"diagonal:sdk-internal\").SdkSendEmailHandle;\n export type BulkNotificationRun = import(\"diagonal:sdk-internal\").SdkBulkNotificationRun;\n export type BulkNotificationVariable = import(\"diagonal:sdk-internal\").SdkBulkNotificationVariable;\n export type SendBulkNotificationsInput = import(\"diagonal:sdk-internal\").SdkSendBulkNotificationsInput;\n export type SendBulkNotificationsHandle = import(\"diagonal:sdk-internal\").SdkSendBulkNotificationsHandle;\n export type NotificationRecipient = import(\"diagonal:sdk-internal\").SdkNotificationRecipient;\n export type NotificationDeliveryChannel = import(\"diagonal:sdk-internal\").SdkNotificationDeliveryChannel;\n export type SendNotificationInput = import(\"diagonal:sdk-internal\").SdkSendNotificationInput;\n export type SendNotificationResponse = import(\"diagonal:sdk-internal\").SdkSendNotificationResponse;\n export type NotificationConsentChannels = import(\"diagonal:sdk-internal\").SdkNotificationConsentChannels;\n export type NotificationConsent = import(\"diagonal:sdk-internal\").SdkNotificationConsent;\n export type UseNotificationConsentOptions = import(\"diagonal:sdk-internal\").SdkUseNotificationConsentOptions;\n export type NotificationConsentHandle = import(\"diagonal:sdk-internal\").SdkNotificationConsentHandle;\n export type CalendarUnavailableReason = import(\"diagonal:sdk-internal\").SdkCalendarUnavailableReason;\n export type CalendarLinkState = import(\"diagonal:sdk-internal\").SdkCalendarLinkState;\n export type CalendarAccessRole = import(\"diagonal:sdk-internal\").SdkCalendarAccessRole;\n export type GoogleCalendarLoginStatus = import(\"diagonal:sdk-internal\").SdkGoogleCalendarLoginStatus;\n export type CalendarEventsChoice = import(\"diagonal:sdk-internal\").SdkCalendarEventsChoice;\n export type CalendarStaff = import(\"diagonal:sdk-internal\").SdkCalendarStaff;\n export type CalendarLink = import(\"diagonal:sdk-internal\").SdkCalendarLink;\n export type CalendarLogin = import(\"diagonal:sdk-internal\").SdkCalendarLogin;\n export type CalendarChoice = import(\"diagonal:sdk-internal\").SdkCalendarChoice;\n export type CalendarChoices = import(\"diagonal:sdk-internal\").SdkCalendarChoices;\n export type CalendarResult = import(\"diagonal:sdk-internal\").SdkCalendarResult;\n export type CalendarSignInResult = import(\"diagonal:sdk-internal\").SdkCalendarSignInResult;\n export type CalendarLinkFlags = import(\"diagonal:sdk-internal\").SdkCalendarLinkFlags;\n export type AddCalendarLinkInput = import(\"diagonal:sdk-internal\").SdkAddCalendarLinkInput;\n export type UseCalendarConnectionsOptions = import(\"diagonal:sdk-internal\").SdkUseCalendarConnectionsOptions;\n export type CalendarConnectionsHandle = import(\"diagonal:sdk-internal\").SdkCalendarConnectionsHandle;\n export type UseSendNotificationOptions = import(\"diagonal:sdk-internal\").SdkUseSendNotificationOptions;\n export type SendNotificationHandle = import(\"diagonal:sdk-internal\").SdkSendNotificationHandle;\n export type PreviewNotificationInput = import(\"diagonal:sdk-internal\").SdkPreviewNotificationInput;\n export type NotificationChannelUnavailableReason = import(\"diagonal:sdk-internal\").SdkNotificationChannelUnavailableReason;\n export type NotificationChannelAvailability = import(\"diagonal:sdk-internal\").SdkNotificationChannelAvailability;\n export type NotificationChannel = import(\"diagonal:sdk-internal\").SdkNotificationChannel;\n export type NotificationChannels = import(\"diagonal:sdk-internal\").SdkNotificationChannels;\n export type PreviewNotificationResponse = import(\"diagonal:sdk-internal\").SdkPreviewNotificationResponse;\n export type ResolvedNotificationEmail = import(\"diagonal:sdk-internal\").SdkResolvedNotificationEmail;\n export type ResolvedNotification = import(\"diagonal:sdk-internal\").SdkResolvedNotification;\n export type SendResolvedNotificationInput = import(\"diagonal:sdk-internal\").SdkSendResolvedNotificationInput;\n export type SendResolvedNotificationHandle = import(\"diagonal:sdk-internal\").SdkSendResolvedNotificationHandle;\n export type UpcomingNotification = import(\"diagonal:sdk-internal\").SdkUpcomingNotification;\n export type ScheduledNotificationContent = import(\"diagonal:sdk-internal\").SdkScheduledNotificationContent;\n export type ScheduledNotificationDetail = import(\"diagonal:sdk-internal\").SdkScheduledNotificationDetail;\n export type ScheduledNotificationEdits = import(\"diagonal:sdk-internal\").SdkScheduledNotificationEdits;\n export type UpdateScheduledNotificationInput = import(\"diagonal:sdk-internal\").SdkUpdateScheduledNotificationInput;\n export type UpdateScheduledNotificationHandle = import(\"diagonal:sdk-internal\").SdkUpdateScheduledNotificationHandle;\n export type SendScheduledNotificationNowHandle = import(\"diagonal:sdk-internal\").SdkSendScheduledNotificationNowHandle;\n export type NotificationHistoryStatus = import(\"diagonal:sdk-internal\").SdkNotificationHistoryStatus;\n export type NotificationHistoryKind = import(\"diagonal:sdk-internal\").SdkNotificationHistoryKind;\n export type NotificationSourceKind = import(\"diagonal:sdk-internal\").SdkNotificationSourceKind;\n export type EmailAttemptStatus = import(\"diagonal:sdk-internal\").SdkEmailAttemptStatus;\n export type EmailRecipientRole = import(\"diagonal:sdk-internal\").SdkEmailRecipientRole;\n export type EmailMessagesRecord = import(\"diagonal:sdk-internal\").SdkEmailMessagesRecord;\n export type EmailMessagesFilters = import(\"diagonal:sdk-internal\").SdkEmailMessagesFilters;\n export type EmailMessagesOptions = import(\"diagonal:sdk-internal\").SdkEmailMessagesOptions;\n export type EmailMessage = import(\"diagonal:sdk-internal\").SdkEmailMessage;\n export type EmailMessages = import(\"diagonal:sdk-internal\").SdkEmailMessages;\n export type NotificationHistoryItem = import(\"diagonal:sdk-internal\").SdkNotificationHistoryItem;\n export type NotificationHistory = import(\"diagonal:sdk-internal\").SdkNotificationHistory;\n export type NotificationHistoryOptions = import(\"diagonal:sdk-internal\").SdkNotificationHistoryOptions;\n export type EmailAttemptEvent = import(\"diagonal:sdk-internal\").SdkEmailAttemptEvent;\n export type EmailAttempt = import(\"diagonal:sdk-internal\").SdkEmailAttempt;\n export type EmailAddress = import(\"diagonal:sdk-internal\").SdkEmailAddress;\n export type EmailAttemptRecipients = import(\"diagonal:sdk-internal\").SdkEmailAttemptRecipients;\n export type EmailAttemptAttachment = import(\"diagonal:sdk-internal\").SdkEmailAttemptAttachment;\n export type NotificationHistoryRecord = import(\"diagonal:sdk-internal\").SdkNotificationHistoryRecord;\n export type NotificationHistoryDetail = import(\"diagonal:sdk-internal\").SdkNotificationHistoryDetail;\n export type SmsMessageStatus = import(\"diagonal:sdk-internal\").SdkSmsMessageStatus;\n export type SmsMessage = import(\"diagonal:sdk-internal\").SdkSmsMessage;\n export type UpcomingNotifications = import(\"diagonal:sdk-internal\").SdkUpcomingNotifications;\n export type UpcomingNotificationsOptions = import(\"diagonal:sdk-internal\").SdkUpcomingNotificationsOptions;\n export type ScheduleNotificationInput = import(\"diagonal:sdk-internal\").SdkScheduleNotificationInput;\n export type ScheduledNotificationResult = import(\"diagonal:sdk-internal\").SdkScheduledNotificationResult;\n export type CancelledNotificationResult = import(\"diagonal:sdk-internal\").SdkCancelledNotificationResult;\n export type ScheduleNotificationHandle = import(\"diagonal:sdk-internal\").SdkScheduleNotificationHandle;\n export type CancelScheduledNotificationHandle = import(\"diagonal:sdk-internal\").SdkCancelScheduledNotificationHandle;\n export type HeldNotificationResult = import(\"diagonal:sdk-internal\").SdkHeldNotificationResult;\n export type HoldScheduledNotificationHandle = import(\"diagonal:sdk-internal\").SdkHoldScheduledNotificationHandle;\n export type ResumeScheduledNotificationHandle = import(\"diagonal:sdk-internal\").SdkResumeScheduledNotificationHandle;\n export type NotificationListOptions = import(\"diagonal:sdk-internal\").SdkNotificationListOptions;\n export type NotificationTypeListOptions = import(\"diagonal:sdk-internal\").SdkNotificationTypeListOptions;\n export type NotificationVariableSummary = import(\"diagonal:sdk-internal\").SdkNotificationVariableSummary;\n export type NotificationTypeSummary = import(\"diagonal:sdk-internal\").SdkNotificationTypeSummary;\n export type NotificationTemplateSummary = import(\"diagonal:sdk-internal\").SdkNotificationTemplateSummary;\n export type NotificationTypeList = import(\"diagonal:sdk-internal\").SdkNotificationTypeList;\n export type NotificationTemplateList = import(\"diagonal:sdk-internal\").SdkNotificationTemplateList;\n export type NavigationMenu = import(\"diagonal:sdk-internal\").SdkNavigationMenu;\n export type NavigationMenuCatalog = import(\"diagonal:sdk-internal\").SdkNavigationMenuCatalog;\n export type WorkflowEvent = import(\"diagonal:sdk-internal\").SdkWorkflowEvent;\n export type ResourceWorkflowInputs = import(\"diagonal:sdk-internal\").SdkResourceWorkflowInputs;\n export type ResourceFormWorkflowInputs<TResource extends Pick<import(\"diagonal:sdk-internal\").SdkResource, 'workflow_inputs'>, TEvents extends import(\"diagonal:sdk-internal\").SdkWorkflowEvent> = import(\"diagonal:sdk-internal\").SdkResourceFormWorkflowInputs<TResource, TEvents>;\n export type WorkflowInputSettings = import(\"diagonal:sdk-internal\").SdkWorkflowInputSettings;\n export type WorkflowInput = import(\"diagonal:sdk-internal\").SdkWorkflowInput;\n export type Identity = import(\"diagonal:sdk-internal\").SdkIdentity;\n export type Permissions = import(\"diagonal:sdk-internal\").SdkPermissions;\n export type WorkspaceField = import(\"diagonal:sdk-internal\").SdkWorkspaceField;\n export type FormField = import(\"diagonal:sdk-internal\").SdkFormField;\n export type WorkspaceValues = import(\"diagonal:sdk-internal\").SdkWorkspaceValues;\n export type Workspace = import(\"diagonal:sdk-internal\").SdkWorkspace;\n export type WorkspaceRecord = import(\"diagonal:sdk-internal\").SdkWorkspaceRecord;\n export type Api = import(\"diagonal:sdk-internal\").SdkApi;\n export type ApiError = import(\"diagonal:sdk-internal\").SdkApiError;\n export type ApiQueryKey = import(\"diagonal:sdk-internal\").SdkApiQueryKey;\n export type ApiFetcher<TData> = import(\"diagonal:sdk-internal\").SdkApiFetcher<TData>;\n export type SubscriptionPreview = import(\"diagonal:sdk-internal\").SdkSubscriptionPreview;\n export type SubscriptionPreviewOptions = import(\"diagonal:sdk-internal\").SdkSubscriptionPreviewOptions;\n export type SubscriptionPreviewHandle = import(\"diagonal:sdk-internal\").SdkSubscriptionPreviewHandle;\n export type SubscriptionChangePreview = import(\"diagonal:sdk-internal\").SdkSubscriptionChangePreview;\n export type SubscriptionChangePreviewOptions = import(\"diagonal:sdk-internal\").SdkSubscriptionChangePreviewOptions;\n export type SubscriptionChangePreviewHandle = import(\"diagonal:sdk-internal\").SdkSubscriptionChangePreviewHandle;\n export type SchedulableRecordRef = import(\"diagonal:sdk-internal\").SdkSchedulableRecordRef;\n export type SlotSearchGroup = import(\"diagonal:sdk-internal\").SdkSlotSearchGroup;\n export type AvailableSlotsOptions = import(\"diagonal:sdk-internal\").SdkAvailableSlotsOptions;\n export type AvailableSlot = import(\"diagonal:sdk-internal\").SdkAvailableSlot;\n export type AvailableSlotsHandle = import(\"diagonal:sdk-internal\").SdkAvailableSlotsHandle;\n export type AvailabilityOptions = import(\"diagonal:sdk-internal\").SdkAvailabilityOptions;\n export type AvailabilityPiece = import(\"diagonal:sdk-internal\").SdkAvailabilityPiece;\n export type RecordAvailability = import(\"diagonal:sdk-internal\").SdkRecordAvailability;\n export type AvailabilityHandle = import(\"diagonal:sdk-internal\").SdkAvailabilityHandle;\n export type ScheduleConflictsOptions = import(\"diagonal:sdk-internal\").SdkScheduleConflictsOptions;\n export type ScheduleConflict = import(\"diagonal:sdk-internal\").SdkScheduleConflict;\n export type ScheduleConflictsHandle = import(\"diagonal:sdk-internal\").SdkScheduleConflictsHandle;\n export type PaymentRefundPreview = import(\"diagonal:sdk-internal\").SdkPaymentRefundPreview;\n export type PaymentRefundPreviewOptions = import(\"diagonal:sdk-internal\").SdkPaymentRefundPreviewOptions;\n export type PaymentRefundPreviewHandle = import(\"diagonal:sdk-internal\").SdkPaymentRefundPreviewHandle;\n export type AccountingEntity = import(\"diagonal:sdk-internal\").SdkAccountingEntity;\n export type AccountingEntities = import(\"diagonal:sdk-internal\").SdkAccountingEntities;\n export type SeriesOccurrenceMeta = import(\"diagonal:sdk-internal\").SdkSeriesOccurrenceMeta;\n export type SeriesChangeResult = import(\"diagonal:sdk-internal\").SdkSeriesChangeResult;\n export type BookingCreateResult = import(\"diagonal:sdk-internal\").SdkBookingCreateResult;\n export type PayerEntityBalance = import(\"diagonal:sdk-internal\").SdkPayerEntityBalance;\n export type PayerBalancesOptions = import(\"diagonal:sdk-internal\").SdkPayerBalancesOptions;\n export type PayerBalances = import(\"diagonal:sdk-internal\").SdkPayerBalances;\n export type PackageReleaseHistory = import(\"diagonal:sdk-internal\").SdkPackageReleaseHistory;\n export type PackageReleaseNotesOptions = import(\"diagonal:sdk-internal\").SdkPackageReleaseNotesOptions;\n export type PackageReleaseNotes = import(\"diagonal:sdk-internal\").SdkPackageReleaseNotes;\n export type PackageReleaseNotesProps = import(\"diagonal:sdk-internal\").SdkPackageReleaseNotesProps;\n export type UseApiOptions<TData = any> = import(\"diagonal:sdk-internal\").SdkUseApiOptions<TData>;\n export type ApiHandle<TData> = import(\"diagonal:sdk-internal\").SdkApiHandle<TData>;\n export type RouterVisitOptions = import(\"diagonal:sdk-internal\").SdkRouterVisitOptions;\n export type Router = import(\"diagonal:sdk-internal\").SdkRouter;\n export type AuthResource = import(\"diagonal:sdk-internal\").SdkAuthResource;\n export type TwoFactorMode = import(\"diagonal:sdk-internal\").SdkTwoFactorMode;\n export type TwoFactorPending = import(\"diagonal:sdk-internal\").SdkTwoFactorPending;\n export type AuthLoginInput = import(\"diagonal:sdk-internal\").SdkAuthLoginInput;\n export type AuthMagicLinkInput = import(\"diagonal:sdk-internal\").SdkAuthMagicLinkInput;\n export type AuthPasswordResetInput = import(\"diagonal:sdk-internal\").SdkAuthPasswordResetInput;\n export type Auth = import(\"diagonal:sdk-internal\").SdkAuth;\n export type AuthConfig = import(\"diagonal:sdk-internal\").SdkAuthConfig;\n export type TwoFactorFlow = import(\"diagonal:sdk-internal\").SdkTwoFactorFlow;\n export type UseTwoFactorOptions = import(\"diagonal:sdk-internal\").SdkUseTwoFactorOptions;\n export type TwoFactorChallengeState = import(\"diagonal:sdk-internal\").SdkTwoFactorChallengeState;\n export type TwoFactorRequestState = import(\"diagonal:sdk-internal\").SdkTwoFactorRequestState;\n export type TwoFactorChallengeHandle = import(\"diagonal:sdk-internal\").SdkTwoFactorChallengeHandle;\n export type TwoFactorSetupStep = import(\"diagonal:sdk-internal\").SdkTwoFactorSetupStep;\n export type TwoFactorSetupState = import(\"diagonal:sdk-internal\").SdkTwoFactorSetupState;\n export type TwoFactorSetupHandle = import(\"diagonal:sdk-internal\").SdkTwoFactorSetupHandle;\n export type TwoFactorBackupCodesState = import(\"diagonal:sdk-internal\").SdkTwoFactorBackupCodesState;\n export type TwoFactorBackupCodesHandle = import(\"diagonal:sdk-internal\").SdkTwoFactorBackupCodesHandle;\n export type UseForm = import(\"diagonal:sdk-internal\").SdkUseForm;\n export type WorkflowRouteSettings = import(\"diagonal:sdk-internal\").WorkflowRouteSettings;\n export type PageRouteSettings = import(\"diagonal:sdk-internal\").PageRouteSettings;\n export type WorkspaceFields = import(\"diagonal:sdk-internal\").SdkWorkspaceFields;\n type SdkContentValue = import(\"diagonal:sdk-internal\").SdkContentValue;\n export interface ContentValue extends SdkContentValue {}\n type SdkMoney = import(\"diagonal:sdk-internal\").SdkMoney;\n export interface Money extends SdkMoney {}\n export type MoneyInput = import(\"diagonal:sdk-internal\").SdkMoneyInput;\n type SdkPointValue = import(\"diagonal:sdk-internal\").SdkPointValue;\n export interface PointValue extends SdkPointValue {}\n type SdkRecurrenceDefinition = import(\"diagonal:sdk-internal\").SdkRecurrenceDefinition;\n export interface RecurrenceDefinition extends SdkRecurrenceDefinition {}\n export type RecurrenceValue = import(\"diagonal:sdk-internal\").SdkRecurrenceValue;\n type SdkEventScheduleValue = import(\"diagonal:sdk-internal\").SdkEventScheduleValue;\n export interface EventScheduleValue extends SdkEventScheduleValue {}\n export type RegularHoursDay = import(\"diagonal:sdk-internal\").SdkRegularHoursDay;\n type SdkRegularHoursValue = import(\"diagonal:sdk-internal\").SdkRegularHoursValue;\n export interface RegularHoursValue extends SdkRegularHoursValue {}\n export type LocalStoragePrimitive = import(\"diagonal:sdk-internal\").SdkLocalStoragePrimitive;\n export type LocalStorageObject = import(\"diagonal:sdk-internal\").SdkLocalStorageObject;\n export type LocalStorageArray = import(\"diagonal:sdk-internal\").SdkLocalStorageArray;\n export type LocalStorageValue = import(\"diagonal:sdk-internal\").SdkLocalStorageValue;\n export type LocalStorageSetter<TValue> = import(\"diagonal:sdk-internal\").SdkLocalStorageSetter<TValue>;\n export type LocalStorageResult<TValue> = import(\"diagonal:sdk-internal\").SdkLocalStorageResult<TValue>;\n export type MoneyFormatOptions = import(\"diagonal:sdk-internal\").SdkMoneyFormatOptions;\n export type DateAddUnit = import(\"diagonal:sdk-internal\").SdkDateAddUnit;\n export type DateBoundaryUnit = import(\"diagonal:sdk-internal\").SdkDateBoundaryUnit;\n export type TimeAddUnit = import(\"diagonal:sdk-internal\").SdkTimeAddUnit;\n export type TimeBoundaryUnit = import(\"diagonal:sdk-internal\").SdkTimeBoundaryUnit;\n export type WorkflowTarget = import(\"diagonal:sdk-internal\").SdkWorkflowTarget;\n export type BrandLogoOptions = import(\"diagonal:sdk-internal\").SdkBrandLogoOptions;\n export type ClassValue = import(\"diagonal:sdk-internal\").SdkClassValue;\n export type NavigateOptions = import(\"diagonal:sdk-internal\").SdkNavigateOptions;\n export type TiptapParseInput = import(\"diagonal:sdk-internal\").SdkTiptapParseInput;\n export type TextGenerateOptions = import(\"diagonal:sdk-internal\").SdkTextGenerateOptions;\n type SdkLocationValue = import(\"diagonal:sdk-internal\").SdkLocationValue;\n export interface LocationValue extends SdkLocationValue {}\n export type LocationFormatOptions = import(\"diagonal:sdk-internal\").SdkLocationFormatOptions;\n export type PhoneFormatOptions = import(\"diagonal:sdk-internal\").SdkPhoneFormatOptions;\n export type NumberFormatOptions = import(\"diagonal:sdk-internal\").SdkNumberFormatOptions;\n export type ConfettiOptions = import(\"diagonal:sdk-internal\").SdkConfettiOptions;\n export type ClipboardCopyOptions = import(\"diagonal:sdk-internal\").SdkClipboardCopyOptions;\n export type NotifyToastOptions = import(\"diagonal:sdk-internal\").SdkNotifyToastOptions;\n export type NotifyVariant = import(\"diagonal:sdk-internal\").SdkNotifyVariant;\n export type BadgeVariant = import(\"diagonal:sdk-internal\").SdkBadgeVariant;\n export type DisplayFormatOptions = import(\"diagonal:sdk-internal\").SdkDisplayFormatOptions;\n export type FieldSelector<TKey extends string = string> = import(\"diagonal:sdk-internal\").SdkFieldSelector<TKey>;\n export type RecordInput<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord> = import(\"diagonal:sdk-internal\").SdkRecordInput<TRecord>;\n export type FieldValue<TRecord extends import(\"diagonal:sdk-internal\").ResourceRecord, TSelector extends import(\"diagonal:sdk-internal\").SdkFieldSelector> = import(\"diagonal:sdk-internal\").SdkFieldValue<TRecord, TSelector>;\n export type DisplayUtils = import(\"diagonal:sdk-internal\").SdkDisplayUtils;\n export type ApiErrorBody = import(\"diagonal:sdk-internal\").SdkApiErrorBody;\n export type Utils = import(\"diagonal:sdk-internal\").SdkUtils;\n export type Helpers = import(\"diagonal:sdk-internal\").SdkHelpers;\n export type Contract = import(\"diagonal:sdk-internal\").SdkContract;\n export type ResourceRecord = import(\"diagonal:sdk-internal\").ResourceRecord;\n export type ResourceForm = import(\"diagonal:sdk-internal\").SdkResourceForm;\n export type ResourceRecordFormStep<RenderState> = import(\"diagonal:sdk-internal\").ResourceRecordFormStep<RenderState>;\n export type UseWorkspaceFieldsFormOptions = import(\"diagonal:sdk-internal\").SdkUseWorkspaceFieldsFormOptions;\n export type WorkspaceFieldsForm = import(\"diagonal:sdk-internal\").SdkWorkspaceFieldsForm;\n export type UseWorkspaceFieldsFormResult = import(\"diagonal:sdk-internal\").UseWorkspaceFieldsFormResult;\n export type FormFieldProps = import(\"diagonal:sdk-internal\").FormFieldProps;\n export type WorkflowInputProps = import(\"diagonal:sdk-internal\").WorkflowInputProps;\n export type WorkflowInputsProps = import(\"diagonal:sdk-internal\").WorkflowInputsProps;\n export type ResourceRecordFormFieldsProps = import(\"diagonal:sdk-internal\").ResourceRecordFormFieldsProps;\n export type ResourceRecordFormActionsProps = import(\"diagonal:sdk-internal\").ResourceRecordFormActionsProps;\n export type FormWrapperProps = import(\"diagonal:sdk-internal\").FormWrapperProps;\n export type UseCreateResourceRecordFormResult = import(\"diagonal:sdk-internal\").UseCreateResourceRecordFormResult;\n export type UseUpdateResourceRecordFormResult = import(\"diagonal:sdk-internal\").UseUpdateResourceRecordFormResult;\n export type WorkflowForm = import(\"diagonal:sdk-internal\").SdkWorkflowForm;\n export type WorkflowPendingResponse = import(\"diagonal:sdk-internal\").SdkWorkflowPendingResponse;\n export type WorkflowOutput = import(\"diagonal:sdk-internal\").SdkWorkflowOutput;\n export type WorkflowResponse = import(\"diagonal:sdk-internal\").SdkWorkflowResponse;\n export type UseWorkflowActionResult = import(\"diagonal:sdk-internal\").UseWorkflowActionResult;\n export type UseWorkflowFormResult = import(\"diagonal:sdk-internal\").UseWorkflowFormResult;\n export type RecordScheduleTimelineOffset = import(\"diagonal:sdk-internal\").SdkRecordScheduleTimelineOffset;\n export type RecordScheduleTimeline = import(\"diagonal:sdk-internal\").SdkRecordScheduleTimeline;\n export type UseRecordScheduleTimelinesOptions = import(\"diagonal:sdk-internal\").SdkUseRecordScheduleTimelinesOptions;\n export type RecordScheduleTimelinesHandle = import(\"diagonal:sdk-internal\").SdkRecordScheduleTimelinesHandle;\n export type Hooks = import(\"diagonal:sdk-internal\").SdkHooks;\n}\n\ndeclare const Sdk: import(\"diagonal:sdk-internal\").SdkContract;\n";
292194
292232
  var libFiles = /* @__PURE__ */ new Map([["lib.d.ts", '/*! *****************************************************************************\nCopyright (c) Microsoft Corporation. All rights reserved.\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at http://www.apache.org/licenses/LICENSE-2.0\n\nTHIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED\nWARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,\nMERCHANTABILITY OR NON-INFRINGEMENT.\n\nSee the Apache Version 2.0 License for specific language governing permissions\nand limitations under the License.\n***************************************************************************** */\n\n\n/// <reference lib="es5" />\n/// <reference lib="dom" />\n/// <reference lib="webworker.importscripts" />\n/// <reference lib="scripthost" />\n'], ["lib.decorators.d.ts", '/*! *****************************************************************************\nCopyright (c) Microsoft Corporation. All rights reserved.\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at http://www.apache.org/licenses/LICENSE-2.0\n\nTHIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED\nWARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,\nMERCHANTABILITY OR NON-INFRINGEMENT.\n\nSee the Apache Version 2.0 License for specific language governing permissions\nand limitations under the License.\n***************************************************************************** */\n\n\n/**\n * The decorator context types provided to class element decorators.\n */\ntype ClassMemberDecoratorContext =\n | ClassMethodDecoratorContext\n | ClassGetterDecoratorContext\n | ClassSetterDecoratorContext\n | ClassFieldDecoratorContext\n | ClassAccessorDecoratorContext;\n\n/**\n * The decorator context types provided to any decorator.\n */\ntype DecoratorContext =\n | ClassDecoratorContext\n | ClassMemberDecoratorContext;\n\ntype DecoratorMetadataObject = Record<PropertyKey, unknown> & object;\n\ntype DecoratorMetadata = typeof globalThis extends { Symbol: { readonly metadata: symbol; }; } ? DecoratorMetadataObject : DecoratorMetadataObject | undefined;\n\n/**\n * Context provided to a class decorator.\n * @template Class The type of the decorated class associated with this context.\n */\ninterface ClassDecoratorContext<\n Class extends abstract new (...args: any) => any = abstract new (...args: any) => any,\n> {\n /** The kind of element that was decorated. */\n readonly kind: "class";\n\n /** The name of the decorated class. */\n readonly name: string | undefined;\n\n /**\n * Adds a callback to be invoked after the class definition has been finalized.\n *\n * @example\n * ```ts\n * function customElement(name: string): ClassDecoratorFunction {\n * return (target, context) => {\n * context.addInitializer(function () {\n * customElements.define(name, this);\n * });\n * }\n * }\n *\n * @customElement("my-element")\n * class MyElement {}\n * ```\n */\n addInitializer(initializer: (this: Class) => void): void;\n\n readonly metadata: DecoratorMetadata;\n}\n\n/**\n * Context provided to a class method decorator.\n * @template This The type on which the class element will be defined. For a static class element, this will be\n * the type of the constructor. For a non-static class element, this will be the type of the instance.\n * @template Value The type of the decorated class method.\n */\ninterface ClassMethodDecoratorContext<\n This = unknown,\n Value extends (this: This, ...args: any) => any = (this: This, ...args: any) => any,\n> {\n /** The kind of class element that was decorated. */\n readonly kind: "method";\n\n /** The name of the decorated class element. */\n readonly name: string | symbol;\n\n /** A value indicating whether the class element is a static (`true`) or instance (`false`) element. */\n readonly static: boolean;\n\n /** A value indicating whether the class element has a private name. */\n readonly private: boolean;\n\n /** An object that can be used to access the current value of the class element at runtime. */\n readonly access: {\n /**\n * Determines whether an object has a property with the same name as the decorated element.\n */\n has(object: This): boolean;\n /**\n * Gets the current value of the method from the provided object.\n *\n * @example\n * let fn = context.access.get(instance);\n */\n get(object: This): Value;\n };\n\n /**\n * Adds a callback to be invoked either after static methods are defined but before\n * static initializers are run (when decorating a `static` element), or before instance\n * initializers are run (when decorating a non-`static` element).\n *\n * @example\n * ```ts\n * const bound: ClassMethodDecoratorFunction = (value, context) {\n * if (context.private) throw new TypeError("Not supported on private methods.");\n * context.addInitializer(function () {\n * this[context.name] = this[context.name].bind(this);\n * });\n * }\n *\n * class C {\n * message = "Hello";\n *\n * @bound\n * m() {\n * console.log(this.message);\n * }\n * }\n * ```\n */\n addInitializer(initializer: (this: This) => void): void;\n\n readonly metadata: DecoratorMetadata;\n}\n\n/**\n * Context provided to a class getter decorator.\n * @template This The type on which the class element will be defined. For a static class element, this will be\n * the type of the constructor. For a non-static class element, this will be the type of the instance.\n * @template Value The property type of the decorated class getter.\n */\ninterface ClassGetterDecoratorContext<\n This = unknown,\n Value = unknown,\n> {\n /** The kind of class element that was decorated. */\n readonly kind: "getter";\n\n /** The name of the decorated class element. */\n readonly name: string | symbol;\n\n /** A value indicating whether the class element is a static (`true`) or instance (`false`) element. */\n readonly static: boolean;\n\n /** A value indicating whether the class element has a private name. */\n readonly private: boolean;\n\n /** An object that can be used to access the current value of the class element at runtime. */\n readonly access: {\n /**\n * Determines whether an object has a property with the same name as the decorated element.\n */\n has(object: This): boolean;\n /**\n * Invokes the getter on the provided object.\n *\n * @example\n * let value = context.access.get(instance);\n */\n get(object: This): Value;\n };\n\n /**\n * Adds a callback to be invoked either after static methods are defined but before\n * static initializers are run (when decorating a `static` element), or before instance\n * initializers are run (when decorating a non-`static` element).\n */\n addInitializer(initializer: (this: This) => void): void;\n\n readonly metadata: DecoratorMetadata;\n}\n\n/**\n * Context provided to a class setter decorator.\n * @template This The type on which the class element will be defined. For a static class element, this will be\n * the type of the constructor. For a non-static class element, this will be the type of the instance.\n * @template Value The type of the decorated class setter.\n */\ninterface ClassSetterDecoratorContext<\n This = unknown,\n Value = unknown,\n> {\n /** The kind of class element that was decorated. */\n readonly kind: "setter";\n\n /** The name of the decorated class element. */\n readonly name: string | symbol;\n\n /** A value indicating whether the class element is a static (`true`) or instance (`false`) element. */\n readonly static: boolean;\n\n /** A value indicating whether the class element has a private name. */\n readonly private: boolean;\n\n /** An object that can be used to access the current value of the class element at runtime. */\n readonly access: {\n /**\n * Determines whether an object has a property with the same name as the decorated element.\n */\n has(object: This): boolean;\n /**\n * Invokes the setter on the provided object.\n *\n * @example\n * context.access.set(instance, value);\n */\n set(object: This, value: Value): void;\n };\n\n /**\n * Adds a callback to be invoked either after static methods are defined but before\n * static initializers are run (when decorating a `static` element), or before instance\n * initializers are run (when decorating a non-`static` element).\n */\n addInitializer(initializer: (this: This) => void): void;\n\n readonly metadata: DecoratorMetadata;\n}\n\n/**\n * Context provided to a class `accessor` field decorator.\n * @template This The type on which the class element will be defined. For a static class element, this will be\n * the type of the constructor. For a non-static class element, this will be the type of the instance.\n * @template Value The type of decorated class field.\n */\ninterface ClassAccessorDecoratorContext<\n This = unknown,\n Value = unknown,\n> {\n /** The kind of class element that was decorated. */\n readonly kind: "accessor";\n\n /** The name of the decorated class element. */\n readonly name: string | symbol;\n\n /** A value indicating whether the class element is a static (`true`) or instance (`false`) element. */\n readonly static: boolean;\n\n /** A value indicating whether the class element has a private name. */\n readonly private: boolean;\n\n /** An object that can be used to access the current value of the class element at runtime. */\n readonly access: {\n /**\n * Determines whether an object has a property with the same name as the decorated element.\n */\n has(object: This): boolean;\n\n /**\n * Invokes the getter on the provided object.\n *\n * @example\n * let value = context.access.get(instance);\n */\n get(object: This): Value;\n\n /**\n * Invokes the setter on the provided object.\n *\n * @example\n * context.access.set(instance, value);\n */\n set(object: This, value: Value): void;\n };\n\n /**\n * Adds a callback to be invoked immediately after the auto `accessor` being\n * decorated is initialized (regardless if the `accessor` is `static` or not).\n */\n addInitializer(initializer: (this: This) => void): void;\n\n readonly metadata: DecoratorMetadata;\n}\n\n/**\n * Describes the target provided to class `accessor` field decorators.\n * @template This The `this` type to which the target applies.\n * @template Value The property type for the class `accessor` field.\n */\ninterface ClassAccessorDecoratorTarget<This, Value> {\n /**\n * Invokes the getter that was defined prior to decorator application.\n *\n * @example\n * let value = target.get.call(instance);\n */\n get(this: This): Value;\n\n /**\n * Invokes the setter that was defined prior to decorator application.\n *\n * @example\n * target.set.call(instance, value);\n */\n set(this: This, value: Value): void;\n}\n\n/**\n * Describes the allowed return value from a class `accessor` field decorator.\n * @template This The `this` type to which the target applies.\n * @template Value The property type for the class `accessor` field.\n */\ninterface ClassAccessorDecoratorResult<This, Value> {\n /**\n * An optional replacement getter function. If not provided, the existing getter function is used instead.\n */\n get?(this: This): Value;\n\n /**\n * An optional replacement setter function. If not provided, the existing setter function is used instead.\n */\n set?(this: This, value: Value): void;\n\n /**\n * An optional initializer mutator that is invoked when the underlying field initializer is evaluated.\n * @param value The incoming initializer value.\n * @returns The replacement initializer value.\n */\n init?(this: This, value: Value): Value;\n}\n\n/**\n * Context provided to a class field decorator.\n * @template This The type on which the class element will be defined. For a static class element, this will be\n * the type of the constructor. For a non-static class element, this will be the type of the instance.\n * @template Value The type of the decorated class field.\n */\ninterface ClassFieldDecoratorContext<\n This = unknown,\n Value = unknown,\n> {\n /** The kind of class element that was decorated. */\n readonly kind: "field";\n\n /** The name of the decorated class element. */\n readonly name: string | symbol;\n\n /** A value indicating whether the class element is a static (`true`) or instance (`false`) element. */\n readonly static: boolean;\n\n /** A value indicating whether the class element has a private name. */\n readonly private: boolean;\n\n /** An object that can be used to access the current value of the class element at runtime. */\n readonly access: {\n /**\n * Determines whether an object has a property with the same name as the decorated element.\n */\n has(object: This): boolean;\n\n /**\n * Gets the value of the field on the provided object.\n */\n get(object: This): Value;\n\n /**\n * Sets the value of the field on the provided object.\n */\n set(object: This, value: Value): void;\n };\n\n /**\n * Adds a callback to be invoked immediately after the field being decorated\n * is initialized (regardless if the field is `static` or not).\n */\n addInitializer(initializer: (this: This) => void): void;\n\n readonly metadata: DecoratorMetadata;\n}\n'], ["lib.decorators.legacy.d.ts", '/*! *****************************************************************************\nCopyright (c) Microsoft Corporation. All rights reserved.\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at http://www.apache.org/licenses/LICENSE-2.0\n\nTHIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED\nWARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,\nMERCHANTABILITY OR NON-INFRINGEMENT.\n\nSee the Apache Version 2.0 License for specific language governing permissions\nand limitations under the License.\n***************************************************************************** */\n\n\ndeclare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;\ndeclare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;\ndeclare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;\ndeclare type ParameterDecorator = (target: Object, propertyKey: string | symbol | undefined, parameterIndex: number) => void;\n'], ["lib.dom.asynciterable.d.ts", `/*! *****************************************************************************
292195
292233
  Copyright (c) Microsoft Corporation. All rights reserved.
292196
292234
  Licensed under the Apache License, Version 2.0 (the "License"); you may not use
@@ -293830,6 +293868,37 @@ function buildPageContextDts(context, resources) {
293830
293868
  `);
293831
293869
  }
293832
293870
 
293871
+ // app/pages/resources/js/pageRuntime/sdk/buildPageKeyCatalogTypes.ts
293872
+ init_define_globalThis_DIAGONAL_SDK_RUNTIME();
293873
+ var ROUTE_PARAM = /\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
293874
+ function paramsType(route) {
293875
+ if (route === null) {
293876
+ return null;
293877
+ }
293878
+ const names2 = [...route.matchAll(ROUTE_PARAM)].map((match) => match[1]);
293879
+ if (names2.length === 0) {
293880
+ return "undefined";
293881
+ }
293882
+ return `{ ${names2.map((name) => `${JSON.stringify(name)}: string | number`).join("; ")} }`;
293883
+ }
293884
+ function buildPageKeyCatalogDts(pages) {
293885
+ const properties = pages.map((page) => {
293886
+ const params = paramsType(page.route);
293887
+ const typeArguments = params === null ? JSON.stringify(page.key) : `${JSON.stringify(page.key)}, ${params}`;
293888
+ const documentation = page.route === null ? "" : ` /** ${jsDocText(page.route)} */
293889
+ `;
293890
+ return `${documentation} readonly ${JSON.stringify(page.key)}: PageRef<${typeArguments}>;`;
293891
+ });
293892
+ const catalog = wrapInSdkInternalModule(
293893
+ `interface WorkspacePages {
293894
+ ${properties.join("\n")}
293895
+ }
293896
+ `
293897
+ );
293898
+ return `${catalog}declare const Pages: import(${JSON.stringify(SDK_INTERNAL_MODULE)}).WorkspacePages;
293899
+ `;
293900
+ }
293901
+
293833
293902
  // app/pages/resources/js/pageRuntime/sdk/buildResourceCatalogTypes.ts
293834
293903
  init_define_globalThis_DIAGONAL_SDK_RUNTIME();
293835
293904
 
@@ -294190,6 +294259,7 @@ var WORKFLOW_CATALOG_FILE = "workflowCatalog.d.ts";
294190
294259
  var WORKSPACE_FIELDS_FILE = "workspaceFieldCatalog.d.ts";
294191
294260
  var PAGE_CONTEXT_FILE = "pageContext.d.ts";
294192
294261
  var APP_IMPORTS_FILE = "appImports.d.ts";
294262
+ var PAGE_KEYS_FILE = "pageKeyCatalog.d.ts";
294193
294263
  var sourceFileCache = /* @__PURE__ */ new Map();
294194
294264
  function typeCheckPage(userSource, {
294195
294265
  ts: ts7,
@@ -294200,7 +294270,8 @@ function typeCheckPage(userSource, {
294200
294270
  workspaceFields,
294201
294271
  pageContext,
294202
294272
  workflows,
294203
- appModules = {}
294273
+ appModules = {},
294274
+ pages = null
294204
294275
  }) {
294205
294276
  const wrapped = `${WRAP_PREFIX}${userSource}
294206
294277
  }
@@ -294238,6 +294309,9 @@ function typeCheckPage(userSource, {
294238
294309
  if (pageContextDts !== "") {
294239
294310
  virtual.set(PAGE_CONTEXT_FILE, pageContextDts);
294240
294311
  }
294312
+ if (pages !== null) {
294313
+ virtual.set(PAGE_KEYS_FILE, buildPageKeyCatalogDts(pages));
294314
+ }
294241
294315
  const customComponentFiles = buildCustomComponentTypeFiles(customComponents);
294242
294316
  for (const file2 of customComponentFiles) {
294243
294317
  virtual.set(file2.fileName, file2.content);
@@ -294318,6 +294392,9 @@ function typeCheckPage(userSource, {
294318
294392
  if (virtual.has(PAGE_CONTEXT_FILE)) {
294319
294393
  rootFiles.push(PAGE_CONTEXT_FILE);
294320
294394
  }
294395
+ if (virtual.has(PAGE_KEYS_FILE)) {
294396
+ rootFiles.push(PAGE_KEYS_FILE);
294397
+ }
294321
294398
  rootFiles.push(...customComponentFiles.map((file2) => file2.fileName));
294322
294399
  if (virtual.has(APP_IMPORTS_FILE)) {
294323
294400
  rootFiles.push(APP_IMPORTS_FILE);
@@ -295158,7 +295235,8 @@ function stageComponentReturnShape(source) {
295158
295235
  }
295159
295236
  }
295160
295237
  }
295161
- var APP_MODULE_GLOBALS = /* @__PURE__ */ new Set(["__appImport"]);
295238
+ var APP_MODULE_GLOBALS = ["__appImport"];
295239
+ var PAGE_KEY_GLOBALS = ["Pages"];
295162
295240
  function stageScopeAnalysis(source, extraGlobals) {
295163
295241
  const wrapped = `${SCOPE_WRAPPER_PREFIX}${source}
295164
295242
  }
@@ -295189,7 +295267,7 @@ function stageScopeAnalysis(source, extraGlobals) {
295189
295267
  throw failure("Lint error", new Error(messages.join("\n")));
295190
295268
  }
295191
295269
  }
295192
- function stageTypeCheck(source, resources, customComponents, workspaceFields, pageContext, workflows, appModules) {
295270
+ function stageTypeCheck(source, resources, customComponents, workspaceFields, pageContext, workflows, appModules, pages) {
295193
295271
  const diagnostics = typeCheckPage(source, {
295194
295272
  ts: import_typescript.default,
295195
295273
  sdkLib,
@@ -295199,7 +295277,8 @@ function stageTypeCheck(source, resources, customComponents, workspaceFields, pa
295199
295277
  workspaceFields,
295200
295278
  pageContext,
295201
295279
  workflows,
295202
- appModules
295280
+ appModules,
295281
+ pages
295203
295282
  });
295204
295283
  if (diagnostics.length > 0) {
295205
295284
  const messages = diagnostics.map(
@@ -295394,13 +295473,17 @@ function stageUnstableQueryInputs(source) {
295394
295473
  throw failure("Unstable query input", new Error(messages.join("\n")));
295395
295474
  }
295396
295475
  }
295397
- function lint(source, resources, customComponents, workspaceFields, pageContext, workflows, { appModules = {} } = {}) {
295476
+ function lint(source, resources, customComponents, workspaceFields, pageContext, workflows, { appModules = {}, pages = null } = {}) {
295398
295477
  const hasAppModules = Object.keys(appModules).length > 0;
295478
+ const extraGlobals = /* @__PURE__ */ new Set([
295479
+ ...hasAppModules ? APP_MODULE_GLOBALS : [],
295480
+ ...pages === null ? [] : PAGE_KEY_GLOBALS
295481
+ ]);
295399
295482
  stageParse(source);
295400
295483
  const transformed = stageTransform(source);
295401
295484
  stageSmokeTest(transformed);
295402
295485
  stageComponentReturnShape(source);
295403
- stageScopeAnalysis(transformed, hasAppModules ? APP_MODULE_GLOBALS : /* @__PURE__ */ new Set());
295486
+ stageScopeAnalysis(transformed, extraGlobals);
295404
295487
  stageNullableSdkValues(source);
295405
295488
  stageTypeCheck(
295406
295489
  source,
@@ -295409,7 +295492,8 @@ function lint(source, resources, customComponents, workspaceFields, pageContext,
295409
295492
  workspaceFields,
295410
295493
  pageContext,
295411
295494
  workflows,
295412
- appModules
295495
+ appModules,
295496
+ pages
295413
295497
  );
295414
295498
  stageHardcodedApiPaths(source);
295415
295499
  stageJsxLiteralEscapes(source);
@@ -295455,6 +295539,12 @@ function customComponentCatalog(components) {
295455
295539
  code: component.lint.code
295456
295540
  }));
295457
295541
  }
295542
+ function pageCatalog(pages, failedKeys) {
295543
+ return [
295544
+ ...pages.map((page) => ({ key: page.key, route: page.route })),
295545
+ ...failedKeys.map((key) => ({ key, route: null }))
295546
+ ];
295547
+ }
295458
295548
  function mapPosition(unit, line, column) {
295459
295549
  const { prefixLine, prefixColumn, prefixLength, suffixLine, suffixColumn, suffixLength } = unit.lint;
295460
295550
  if (line === prefixLine && column >= prefixColumn) {
@@ -295520,7 +295610,8 @@ function failureDiagnostics(unit, failure2) {
295520
295610
  function lintUnit(unit, catalogs) {
295521
295611
  try {
295522
295612
  lint(unit.lint.code, catalogs.resources, catalogs.customComponents, null, null, null, {
295523
- appModules: unit.lint.appModules ?? {}
295613
+ appModules: unit.lint.appModules ?? {},
295614
+ pages: catalogs.pages
295524
295615
  });
295525
295616
  } catch (error) {
295526
295617
  if (error instanceof LintFailure) {
@@ -298412,6 +298503,7 @@ var defineEmailLayout = (layout) => layout;
298412
298503
 
298413
298504
  // scripts/lib/runtime-build/app-modules.mjs
298414
298505
  var APP_IMPORT = "__appImport";
298506
+ var IMPLICIT_PAGES_BINDING = "const Pages = Sdk.Pages;";
298415
298507
  var PRELUDE_SKIPPED_KINDS = /* @__PURE__ */ new Set(["host", "package", "style", "style_module", "binary"]);
298416
298508
  var AUTHORING_HELPERS_SOURCE = objectLiteral(cli_exports);
298417
298509
  var AUTHORING_HELPERS_MODULE = Object.entries(cli_exports).map(([name, value]) => `export const ${name} = ${helperSource(value)};
@@ -298526,7 +298618,7 @@ function appModuleClosure(root, imports, external = /* @__PURE__ */ new Set(), {
298526
298618
  requires[specifier] = target;
298527
298619
  pending.push(target);
298528
298620
  }
298529
- modules.set(path, { source, requires });
298621
+ modules.set(path, { source, requires, implicitPages: ui && usesImplicitName(ast, source, "Pages") });
298530
298622
  }
298531
298623
  return modules;
298532
298624
  }
@@ -298542,7 +298634,7 @@ function appPrelude(modules) {
298542
298634
  return "";
298543
298635
  }
298544
298636
  const entries = [...modules].map(([path, module]) => {
298545
- const code2 = compileModule(path, module.source);
298637
+ const code2 = compileModule(path, module.implicitPages ? `${IMPLICIT_PAGES_BINDING} ${module.source}` : module.source);
298546
298638
  return `${JSON.stringify(path)}: [function (exports, require, module) {
298547
298639
  ${code2}
298548
298640
  }, ${JSON.stringify(module.requires)}]`;
@@ -298564,7 +298656,7 @@ ${code2}
298564
298656
  }
298565
298657
 
298566
298658
  if (specifier === ${JSON.stringify(PAGES_SDK_MODULE)} && typeof Sdk !== 'undefined') {
298567
- return { Sdk, default: Sdk };
298659
+ return { Sdk, Pages: Sdk.Pages, default: Sdk };
298568
298660
  }
298569
298661
 
298570
298662
  if (specifier === 'react' && typeof React !== 'undefined') {
@@ -298603,10 +298695,40 @@ function compileModule(path, source) {
298603
298695
  }
298604
298696
  }
298605
298697
 
298698
+ // scripts/lib/runtime-build/page-key.mjs
298699
+ init_define_globalThis_DIAGONAL_SDK_RUNTIME();
298700
+ var PAGE_KEY_PATTERN = /^[a-z][A-Za-z0-9]*$/;
298701
+ var PAGE_KEY_MAX_LENGTH = 64;
298702
+ function isPageKey(value) {
298703
+ return typeof value === "string" && value.length <= PAGE_KEY_MAX_LENGTH && PAGE_KEY_PATTERN.test(value);
298704
+ }
298705
+ function camelWord(word, first) {
298706
+ const lower = word === word.toUpperCase() ? word.toLowerCase() : word;
298707
+ return first ? lower[0].toLowerCase() + lower.slice(1) : lower[0].toUpperCase() + lower.slice(1);
298708
+ }
298709
+ function pageKeyFrom(text, taken = []) {
298710
+ const words = text.normalize("NFKD").replaceAll(/[^\x00-\x7F]/g, "").split(/[^A-Za-z0-9]+/).filter((word) => word !== "");
298711
+ let base2 = words.map((word, index3) => camelWord(word, index3 === 0)).join("");
298712
+ if (base2 === "" || /^[0-9]/.test(base2)) {
298713
+ base2 = `page${base2}`;
298714
+ }
298715
+ const used = new Set(taken);
298716
+ let key = base2.slice(0, PAGE_KEY_MAX_LENGTH);
298717
+ for (let suffix = 2; used.has(key); suffix++) {
298718
+ key = `${base2.slice(0, PAGE_KEY_MAX_LENGTH - String(suffix).length)}${suffix}`;
298719
+ }
298720
+ return key;
298721
+ }
298722
+ function suggestedPageKey(path) {
298723
+ const route = path.replace(/^pages\//, "").replace(/\.tsx$/, "").replace(/(^|\/)index$/, "");
298724
+ return route === "" ? "home" : pageKeyFrom(route);
298725
+ }
298726
+
298606
298727
  // scripts/lib/runtime-build/ui-sources.mjs
298607
298728
  var COMPONENT_NAME_PATTERN = /^[A-Z][A-Za-z0-9]*$/;
298608
298729
  var PAGE_AUTHORIZERS = /* @__PURE__ */ new Set(["authenticated"]);
298609
298730
  var PAGE_KEYS = /* @__PURE__ */ new Set([
298731
+ "key",
298610
298732
  "route",
298611
298733
  "name",
298612
298734
  "title",
@@ -298700,7 +298822,7 @@ function lintUnit2(original, ast, functionNode, helpers, app, functionBinding) {
298700
298822
  const trailing = kept.slice(functionNode.end);
298701
298823
  kept = kept.slice(0, functionNode.start);
298702
298824
  for (const binding of app.bindings.toSorted((left, right) => right.start - left.start)) {
298703
- const replaced = `${binding.text}${"\n".repeat(newlines(binding.original))}`;
298825
+ const replaced = `${binding.lintText ?? binding.text}${"\n".repeat(newlines(binding.original))}`;
298704
298826
  kept = kept.slice(0, binding.start) + replaced + kept.slice(binding.end);
298705
298827
  }
298706
298828
  const importedTypes = ast.program.body.filter((statement2) => statement2.type === "ImportDeclaration").flatMap(
@@ -298758,7 +298880,7 @@ function isTypeOnlySpecifier(statement2, specifier) {
298758
298880
  function importedName(specifier) {
298759
298881
  return specifier.imported.type === "StringLiteral" ? specifier.imported.value : specifier.imported.name;
298760
298882
  }
298761
- function foreignBindingText(statement2) {
298883
+ function foreignBindingText(statement2, { lint: lint3 }) {
298762
298884
  const specifier = statement2.source.value;
298763
298885
  const kind = importKind(specifier);
298764
298886
  const statements2 = [];
@@ -298773,6 +298895,12 @@ function foreignBindingText(statement2) {
298773
298895
  if (host === "Sdk" && name === "Sdk" && local === "Sdk") {
298774
298896
  continue;
298775
298897
  }
298898
+ if (host === "Sdk" && name === "Pages" && lint3) {
298899
+ if (local !== "Pages") {
298900
+ statements2.push(`const ${local} = Pages;`);
298901
+ }
298902
+ continue;
298903
+ }
298776
298904
  if (host === "React" && item.type !== "ImportSpecifier" && local === "React") {
298777
298905
  continue;
298778
298906
  }
@@ -298792,7 +298920,7 @@ function foreignBindingText(statement2) {
298792
298920
  }
298793
298921
  function foreignTypeAliases(statement2) {
298794
298922
  const specifier = statement2.source.value;
298795
- return statement2.specifiers.filter((item) => statement2.source.value !== "react" || isTypeOnlySpecifier(statement2, item)).filter((item) => !(specifier === PAGES_SDK_MODULE && item.local.name === "Sdk")).filter((item) => !(item.type !== "ImportSpecifier" && item.local.name === "React")).map((item) => {
298923
+ return statement2.specifiers.filter((item) => statement2.source.value !== "react" || isTypeOnlySpecifier(statement2, item)).filter((item) => !(specifier === PAGES_SDK_MODULE && (item.local.name === "Sdk" || item.local.name === "Pages"))).filter((item) => !(item.type !== "ImportSpecifier" && item.local.name === "React")).map((item) => {
298796
298924
  const local = item.local.name;
298797
298925
  if (specifier === "react" && item.type === "ImportSpecifier") {
298798
298926
  return `type ${local} = React.${importedName(item)};`;
@@ -298804,9 +298932,10 @@ function foreignBindings(source, ast, imports) {
298804
298932
  return ast.program.body.filter((statement2) => statement2.type === "ImportDeclaration").filter((statement2) => !imports.some((item) => item.statement === statement2)).filter((statement2) => !isAuthoringModule(statement2.source.value)).map((statement2) => ({
298805
298933
  start: statement2.start,
298806
298934
  end: statement2.end,
298807
- text: foreignBindingText(statement2),
298935
+ text: foreignBindingText(statement2, { lint: false }),
298936
+ lintText: foreignBindingText(statement2, { lint: true }),
298808
298937
  original: sliceOf(source, statement2)
298809
- })).filter((binding) => binding.text !== "");
298938
+ })).filter((binding) => binding.text !== "" || binding.lintText !== "");
298810
298939
  }
298811
298940
  function lazyImports(root, path, ast) {
298812
298941
  return moduleSpecifiers2(ast).filter((item) => item.dynamic && item.specifier !== null && importKind(item.specifier) === "app").map((item) => ({ ...item, target: resolveLocalSpecifier(root, path, item.specifier) })).filter((item) => item.target !== null).map((item) => ({ specifier: item.specifier, path: item.target, start: item.call.start }));
@@ -298828,13 +298957,15 @@ function appImports(root, path, source, ast) {
298828
298957
  function functionUnit(source, ast, functionNode, helpers, app) {
298829
298958
  const bindings = app.bindings.map((binding) => `${binding.text}
298830
298959
  `).join("");
298960
+ const implicit = usesImplicitName(ast, source, "Pages") ? `${IMPLICIT_PAGES_BINDING}
298961
+ ` : "";
298831
298962
  const prelude = helpers.map((statement2) => `${sliceOf(source, statement2)}
298832
298963
  `).join("");
298833
298964
  const functionBinding = functionNode.type === "FunctionDeclaration" ? functionNode.id?.name ?? null : null;
298834
298965
  const functionSource = sliceOf(source, functionNode);
298835
298966
  const returned = functionBinding === null ? `${FACTORY_PREFIX}${functionSource}${FACTORY_SUFFIX}` : `${functionSource}
298836
298967
  return ${functionBinding};`;
298837
- const code2 = `${app.prelude}${bindings}${prelude}${returned}`;
298968
+ const code2 = `${app.prelude}${bindings}${implicit}${prelude}${returned}`;
298838
298969
  const moduleReferences = [...app.modules].flatMap(
298839
298970
  ([path, module]) => customComponentReferences(parseSource(module.source, { jsx: path.endsWith("x") }).program).map(
298840
298971
  (reference) => ({ name: reference.name, node: null })
@@ -298845,7 +298976,7 @@ return ${functionBinding};`;
298845
298976
  compiled_code: compile(code2, functionNode),
298846
298977
  // What a draft of this unit runs before its own code: the page builder
298847
298978
  // previews a page's factory body without its imports.
298848
- app_prelude: app.prelude === "" ? null : `${app.prelude}${bindings}`,
298979
+ app_prelude: app.prelude === "" && bindings === "" ? null : `${app.prelude}${bindings}`,
298849
298980
  references: [
298850
298981
  ...moduleReferences,
298851
298982
  ...[functionNode, ...helpers].flatMap((node) => customComponentReferences(node)).toSorted((left, right) => left.node.start - right.node.start)
@@ -299015,11 +299146,12 @@ function buildPage(path, source, ast, root = null, resources = null) {
299015
299146
  });
299016
299147
  const [metaNode, functionNode] = expectDefineCall(declaration, "definePage", 2);
299017
299148
  const { value: meta, nodes } = staticObject(metaNode, { resources });
299018
- for (const key of Object.keys(meta)) {
299019
- if (!PAGE_KEYS.has(key)) {
299020
- throw new SourceError(`Unknown page option "${key}"`, nodes[key]);
299149
+ for (const key2 of Object.keys(meta)) {
299150
+ if (!PAGE_KEYS.has(key2)) {
299151
+ throw new SourceError(`Unknown page option "${key2}"`, nodes[key2]);
299021
299152
  }
299022
299153
  }
299154
+ const key = pageKey(path, meta, nodes, metaNode);
299023
299155
  if (typeof meta.route !== "string" || !meta.route.startsWith("/")) {
299024
299156
  throw new SourceError('"route" must be a string that starts with "/"', nodes.route ?? metaNode);
299025
299157
  }
@@ -299039,6 +299171,7 @@ function buildPage(path, source, ast, root = null, resources = null) {
299039
299171
  }
299040
299172
  return {
299041
299173
  path,
299174
+ key,
299042
299175
  route: meta.route,
299043
299176
  name: optionalMetaString(meta, "name", nodes),
299044
299177
  title: optionalMetaText(meta, "title", nodes),
@@ -299054,6 +299187,7 @@ function buildPage(path, source, ast, root = null, resources = null) {
299054
299187
  params: pageParams(meta, nodes, meta.route),
299055
299188
  ...functionUnit(source, ast, functionNode, helpers, appImports(root, path, source, ast)),
299056
299189
  layoutNode: nodes.layout ?? null,
299190
+ keyNode: nodes.key,
299057
299191
  resourceNodes: {
299058
299192
  meta: metaNode,
299059
299193
  authorizer: nodes.authorizer ?? null,
@@ -299061,6 +299195,37 @@ function buildPage(path, source, ast, root = null, resources = null) {
299061
299195
  }
299062
299196
  };
299063
299197
  }
299198
+ function pageKey(path, meta, nodes, metaNode) {
299199
+ if (meta.key === void 0) {
299200
+ throw new SourceError(
299201
+ `definePage needs a "key". Add key: '${suggestedPageKey(path)}'`,
299202
+ metaNode
299203
+ );
299204
+ }
299205
+ if (!isPageKey(meta.key)) {
299206
+ throw new SourceError(
299207
+ `"key" must be a camelCase name of at most ${PAGE_KEY_MAX_LENGTH} letters and digits that starts with a lowercase letter, such as 'members'`,
299208
+ nodes.key
299209
+ );
299210
+ }
299211
+ return meta.key;
299212
+ }
299213
+ function pageKeyOf(ast) {
299214
+ try {
299215
+ const { declaration } = defaultExportOf(ast, { helpers: "anywhere", allowAppImports: true });
299216
+ const [metaNode] = expectDefineCall(declaration, "definePage", 2);
299217
+ const keyProperty = metaNode.type === "ObjectExpression" ? metaNode.properties.find(
299218
+ (property) => property.type === "ObjectProperty" && !property.computed && (property.key.name ?? property.key.value) === "key"
299219
+ ) : void 0;
299220
+ const value = keyProperty?.value.type === "StringLiteral" ? keyProperty.value.value : null;
299221
+ return isPageKey(value) ? value : null;
299222
+ } catch (error) {
299223
+ if (error instanceof SourceError) {
299224
+ return null;
299225
+ }
299226
+ throw error;
299227
+ }
299228
+ }
299064
299229
 
299065
299230
  // scripts/lib/runtime-build/workflow-tests.mjs
299066
299231
  init_define_globalThis_DIAGONAL_SDK_RUNTIME();
@@ -301066,7 +301231,7 @@ function classify(path) {
301066
301231
  return "ignored";
301067
301232
  }
301068
301233
  }
301069
- function reportDuplicates(items, keyOf, label, diagnostics) {
301234
+ function reportDuplicates(items, keyOf, label, diagnostics, nodeOf = () => null) {
301070
301235
  const seen = /* @__PURE__ */ new Map();
301071
301236
  for (const item of items) {
301072
301237
  const key = keyOf(item);
@@ -301076,7 +301241,7 @@ function reportDuplicates(items, keyOf, label, diagnostics) {
301076
301241
  continue;
301077
301242
  }
301078
301243
  diagnostics.push(
301079
- diagnosticAt(item.path, null, `Duplicate ${label} "${key}". ${first.path} already uses it`)
301244
+ diagnosticAt(item.path, nodeOf(item), `Duplicate ${label} "${key}". ${first.path} already uses it`)
301080
301245
  );
301081
301246
  }
301082
301247
  }
@@ -301182,15 +301347,27 @@ ${expected.trimEnd()}`
301182
301347
  )
301183
301348
  );
301184
301349
  }
301185
- function lintUnits(units, resources, components, diagnostics) {
301350
+ function lintUnits(units, resources, components, pages, diagnostics) {
301186
301351
  const catalogs = {
301187
301352
  resources: resourceCatalog2(resources),
301188
- customComponents: customComponentCatalog(components)
301353
+ customComponents: customComponentCatalog(components),
301354
+ pages
301189
301355
  };
301190
301356
  for (const unit of units) {
301191
301357
  diagnostics.push(...lintUnit(unit, catalogs));
301192
301358
  }
301193
301359
  }
301360
+ function pageKeyOfSource(source) {
301361
+ try {
301362
+ const key = pageKeyOf(parseSource(source, { jsx: true }));
301363
+ return key === null ? [] : [key];
301364
+ } catch (error) {
301365
+ if (error instanceof SyntaxError) {
301366
+ return [];
301367
+ }
301368
+ throw error;
301369
+ }
301370
+ }
301194
301371
  function hasErrors(diagnostics) {
301195
301372
  return diagnostics.some((diagnostic2) => diagnostic2.severity === "error");
301196
301373
  }
@@ -301221,6 +301398,7 @@ function buildWorkspace(root, { assetCatalog = null, writeGeneratedResources = f
301221
301398
  const uiEntries = [];
301222
301399
  const failedLayouts = /* @__PURE__ */ new Set();
301223
301400
  const failedComponents = /* @__PURE__ */ new Set();
301401
+ const failedPageKeys = [];
301224
301402
  for (const path of files) {
301225
301403
  const kind = classify(path);
301226
301404
  if (kind === "ignored") {
@@ -301287,7 +301465,9 @@ function buildWorkspace(root, { assetCatalog = null, writeGeneratedResources = f
301287
301465
  }
301288
301466
  } catch (error) {
301289
301467
  diagnostics.push(diagnosticFromError(path, error));
301290
- if (kind === "layout") {
301468
+ if (kind === "page") {
301469
+ failedPageKeys.push(...pageKeyOfSource(source));
301470
+ } else if (kind === "layout") {
301291
301471
  failedLayouts.add(stem(path));
301292
301472
  } else if (kind === "component") {
301293
301473
  failedComponents.add(stem(path));
@@ -301303,6 +301483,7 @@ function buildWorkspace(root, { assetCatalog = null, writeGeneratedResources = f
301303
301483
  "route",
301304
301484
  diagnostics
301305
301485
  );
301486
+ reportDuplicates(output.pages, (page) => page.key, "page key", diagnostics, (page) => page.keyNode);
301306
301487
  reportDuplicates(output.navigation, (navigation) => navigation.id, "navigation id", diagnostics);
301307
301488
  const skillCheck = checkSkillFiles(skillFiles);
301308
301489
  output.skills.push(...skillCheck.skills);
@@ -301359,6 +301540,7 @@ function buildWorkspace(root, { assetCatalog = null, writeGeneratedResources = f
301359
301540
  ),
301360
301541
  output.resources,
301361
301542
  output.components,
301543
+ pageCatalog(output.pages, failedPageKeys),
301362
301544
  diagnostics
301363
301545
  );
301364
301546
  }
@@ -301373,7 +301555,7 @@ function buildWorkspace(root, { assetCatalog = null, writeGeneratedResources = f
301373
301555
  ({ references: _references, lint: _lint, ...layout }) => layout
301374
301556
  );
301375
301557
  const pages = output.pages.map(
301376
- ({ references, layoutNode, resourceNodes: _resourceNodes, lint: _lint, ...page }) => {
301558
+ ({ references, layoutNode, keyNode: _keyNode, resourceNodes: _resourceNodes, lint: _lint, ...page }) => {
301377
301559
  const names3 = references.map((reference) => reference.name);
301378
301560
  if (page.layout !== null) {
301379
301561
  const layout = layoutsByName.get(page.layout);