@sanity/workflow-engine 0.25.0 → 0.27.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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @sanity/workflow-engine
2
2
 
3
+ ## 0.27.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 780cf93: **BREAKING:** `ProjectMember.roles` is now `readonly {name: string; title?: string}[]` instead of `readonly string[]`, and `AssigneeStack`'s `roles` prop takes the same records rather than name strings. This affects integrations that build or read `ProjectMember` rows directly, or render `AssigneeStack` themselves. Read `role.name` wherever a role name was read before, and pass role records rather than name strings into `projectMemberRow` and `AssigneeStack`. Until migrated, TypeScript fails at those sites. Integrations using `useProjectMembers` from `@sanity/workflow-sdk` or the Studio plugin's own equivalent get the new shape without changes, because the adapters own the projection.
8
+
9
+ A role now reads as its project title rather than its machine name — `Administrator`, not `administrator` — wherever one is shown: picker role rows, assignee badges, the instance-snapshot pills, and the hover hint naming a collapsed assignee cluster. A square role avatar takes its letters from the title too, so `Blueprints Deployer` reads as `BD` where `blueprints-deployer` could only ever give one letter; its colour still derives from the machine name, so retitling a project role keeps the role's colour. A role whose membership record carries no title — or a title that is only whitespace, which counts as none — and a role named by an assignee that no current member holds, both still read as the machine name. `roleLabel`, `roleLabelFor`, and `memberRoleFor` are exported for callers rendering their own role labels.
10
+
11
+ Both people-pickers now say who a person is, so choosing one no longer requires knowing the org chart. Search matches role names and titles as well as display name and email, so typing a role narrows the list to its holders. Members are also listed by display name rather than in the order the host supplied them, which was the sequence people joined the project — expect the list order to change, and on a large project to become usable. And hovering a row previews the account behind the name — a larger avatar badged with the person's identity provider, their email, and the roles they hold. Marks exist for Google, GitHub, and `saml-`-prefixed deployments; any other provider badges nothing rather than showing an empty circle. That is what tells two members sharing a display name apart, which no role label could. Rows share one tooltip delay group, so the wait to open is paid once and moving along the list swaps the preview rather than waiting again at each row.
12
+
13
+ `MemberAvatar` takes an optional `loginProvider` to badge the provider mark onto its corner. `ProjectMember` gains optional `loginProvider` and `isCurrentUser`; both are populated by the adapters, so integrations using `useProjectMembers` get them without changes.
14
+
15
+ Neither picker ranks or groups its member rows by the roles those members hold, and neither claims who is eligible for an activity. (The roles-and-members picker still lists selectable roles above the members, as it did before.) An activity has no role gate; only an action does, and a manually fired action's `roles` is folded into its `filter` at desugar, so the accepted set is not readable from a deployed definition. A picker has nothing to derive eligibility from, and a ranked list would assert something no engine or Content Lake check backs.
16
+
17
+ Stored values are unchanged. An assignee still persists the machine role name, and titles are display only.
18
+
19
+ **Docs impact:** Update the `@sanity/workflow-components` member-selection reference for the `ProjectMember.roles` and `AssigneeStack` `roles` shapes and the `roleLabel` / `roleLabelFor` / `memberRoleFor` exports (done in this package's README). Any concept or guide page showing a role in Studio UI should show titles rather than machine names, and the assignment guide should state that a picker never restricts or ranks who may be assigned. Add the `ProjectMember.roles` migration to the release notes.
20
+
21
+ ## 0.26.0
22
+
3
23
  ## 0.25.0
4
24
 
5
25
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -5386,12 +5386,20 @@ function resolveMetadata(metadata, ctx) {
5386
5386
  return out;
5387
5387
  }
5388
5388
 
5389
+ function userLoginProvider(user) {
5390
+ return user?.provider ?? user?.loginProvider;
5391
+ }
5392
+
5389
5393
  function isRecord(value) {
5390
5394
  return typeof value == "object" && value !== null && !Array.isArray(value);
5391
5395
  }
5392
5396
 
5397
+ function optionalString(value) {
5398
+ return value === void 0 || typeof value == "string";
5399
+ }
5400
+
5393
5401
  function isClientProjectUser(value) {
5394
- return isRecord(value) && typeof value.id == "string" && (value.sanityUserId === void 0 || typeof value.sanityUserId == "string") && (value.displayName === void 0 || typeof value.displayName == "string") && (value.email === void 0 || typeof value.email == "string") && (value.imageUrl === void 0 || value.imageUrl === null || typeof value.imageUrl == "string");
5402
+ return isRecord(value) && typeof value.id == "string" && optionalString(value.sanityUserId) && optionalString(value.displayName) && optionalString(value.email) && (value.imageUrl === null || optionalString(value.imageUrl)) && optionalString(value.provider) && optionalString(value.loginProvider);
5395
5403
  }
5396
5404
 
5397
5405
  function objectProperty(value, property) {
@@ -13628,6 +13636,8 @@ exports.unboundRequirementReads = unboundRequirementReads;
13628
13636
 
13629
13637
  exports.unsatisfiedTransitionSummaries = unsatisfiedTransitionSummaries;
13630
13638
 
13639
+ exports.userLoginProvider = userLoginProvider;
13640
+
13631
13641
  exports.validateDefinition = validateDefinition;
13632
13642
 
13633
13643
  exports.verdictGuardsForInstance = verdictGuardsForInstance;
package/dist/index.d.cts CHANGED
@@ -1682,6 +1682,18 @@ export declare interface ClientProjectUser {
1682
1682
  readonly displayName?: string;
1683
1683
  readonly email?: string;
1684
1684
  readonly imageUrl?: string | null;
1685
+ /**
1686
+ * Which identity provider the person signs in with (`google`, `github`, or a
1687
+ * `saml-<name>` deployment) — display only.
1688
+ *
1689
+ * Both spellings are declared because the two project-user endpoints
1690
+ * disagree: the project-hosted `/users/<id>` the adapters read answers
1691
+ * `provider`, while the management `/projects/<id>/users/<id>` answers
1692
+ * `loginProvider`. Read them through {@link userLoginProvider} rather than
1693
+ * picking one.
1694
+ */
1695
+ readonly provider?: string;
1696
+ readonly loginProvider?: string;
1685
1697
  readonly [key: string]: unknown;
1686
1698
  }
1687
1699
 
@@ -8801,6 +8813,12 @@ export declare function unsatisfiedTransitionSummaries(
8801
8813
  summary: string;
8802
8814
  }[];
8803
8815
 
8816
+ /** The identity provider under whichever spelling the answering endpoint used
8817
+ * — the ONE place that disagreement is resolved. */
8818
+ export declare function userLoginProvider(
8819
+ user: ClientProjectUser | undefined,
8820
+ ): string | undefined;
8821
+
8804
8822
  /**
8805
8823
  * Validate a (stored, desugared) definition before deploy: parse-check every
8806
8824
  * GROQ string (malformed predicates, conditions, bindings, triggers) and
package/dist/index.d.ts CHANGED
@@ -1682,6 +1682,18 @@ export declare interface ClientProjectUser {
1682
1682
  readonly displayName?: string;
1683
1683
  readonly email?: string;
1684
1684
  readonly imageUrl?: string | null;
1685
+ /**
1686
+ * Which identity provider the person signs in with (`google`, `github`, or a
1687
+ * `saml-<name>` deployment) — display only.
1688
+ *
1689
+ * Both spellings are declared because the two project-user endpoints
1690
+ * disagree: the project-hosted `/users/<id>` the adapters read answers
1691
+ * `provider`, while the management `/projects/<id>/users/<id>` answers
1692
+ * `loginProvider`. Read them through {@link userLoginProvider} rather than
1693
+ * picking one.
1694
+ */
1695
+ readonly provider?: string;
1696
+ readonly loginProvider?: string;
1685
1697
  readonly [key: string]: unknown;
1686
1698
  }
1687
1699
 
@@ -8801,6 +8813,12 @@ export declare function unsatisfiedTransitionSummaries(
8801
8813
  summary: string;
8802
8814
  }[];
8803
8815
 
8816
+ /** The identity provider under whichever spelling the answering endpoint used
8817
+ * — the ONE place that disagreement is resolved. */
8818
+ export declare function userLoginProvider(
8819
+ user: ClientProjectUser | undefined,
8820
+ ): string | undefined;
8821
+
8804
8822
  /**
8805
8823
  * Validate a (stored, desugared) definition before deploy: parse-check every
8806
8824
  * GROQ string (malformed predicates, conditions, bindings, triggers) and
package/dist/index.js CHANGED
@@ -5371,12 +5371,20 @@ function resolveMetadata(metadata, ctx) {
5371
5371
  return out;
5372
5372
  }
5373
5373
 
5374
+ function userLoginProvider(user) {
5375
+ return user?.provider ?? user?.loginProvider;
5376
+ }
5377
+
5374
5378
  function isRecord(value) {
5375
5379
  return typeof value == "object" && value !== null && !Array.isArray(value);
5376
5380
  }
5377
5381
 
5382
+ function optionalString(value) {
5383
+ return value === void 0 || typeof value == "string";
5384
+ }
5385
+
5378
5386
  function isClientProjectUser(value) {
5379
- return isRecord(value) && typeof value.id == "string" && (value.sanityUserId === void 0 || typeof value.sanityUserId == "string") && (value.displayName === void 0 || typeof value.displayName == "string") && (value.email === void 0 || typeof value.email == "string") && (value.imageUrl === void 0 || value.imageUrl === null || typeof value.imageUrl == "string");
5387
+ return isRecord(value) && typeof value.id == "string" && optionalString(value.sanityUserId) && optionalString(value.displayName) && optionalString(value.email) && (value.imageUrl === null || optionalString(value.imageUrl)) && optionalString(value.provider) && optionalString(value.loginProvider);
5380
5388
  }
5381
5389
 
5382
5390
  function objectProperty(value, property) {
@@ -12994,4 +13002,4 @@ function displayDescription(typeKey) {
12994
13002
  if (typeKey) return DISPLAY[typeKey]?.description;
12995
13003
  }
12996
13004
 
12997
- export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_KIND_DISPLAY, ACTOR_KINDS, ANONYMOUS_IDENTITY, AUTHORING_DISPLAY, ActionDisabledError, ActionParamsInvalidError, CONDITION_VARS, CONTEXT_ENTRY_DISPLAY, CascadeLimitError, ConcurrentCommitEffectOpsError, ConcurrentCompleteEffectError, ConcurrentEditFieldError, ConcurrentFireActionError, ContractViolationError, DATA_MODEL_CHANGES, DATA_MODEL_MIN_READER, DATA_MODEL_VERSION, DEFAULT_CONTENT_PERSPECTIVE, DEFAULT_EFFECT_LEASE_MS, DEFAULT_IDEMPOTENCY_TTL_MS, DEFAULT_TRANSITION_WHEN, DISPLAY, DRIVER_KINDS, DRIVER_KIND_DISPLAY, DefinitionInUseError, DefinitionNotFoundError, EFFECT_COMMIT_DISPATCH_CAP, EFFECT_COMMIT_QUEUE_DEPTH, ENGINE_API_VERSION, EXECUTION_KINDS, EXECUTOR_CLASSIFICATIONS, EXECUTOR_CLASSIFICATION_DISPLAY, EditFieldDeniedError, EffectCommitQueueOverflowError, EffectNotFoundError, EffectOpsInvalidError, EffectOutputsInvalidError, FIELD_KIND_DISPLAY, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GROUP_KIND_DISPLAY, GUARD_DOC_TYPE, GUARD_OWNER, GUARD_PREDICATE_VARS, HISTORY_DISPLAY, InitialFieldsInvalidError, InstanceNotFoundError, MAX_COUNTERFACTUAL_INDEX2 as MAX_COUNTERFACTUAL_INDEX, MissingHandlerError, ModelVersionAheadError, MutationGuardDeniedError, MutationGuardDocSchema, NEUTRAL_MARK, OP_DISPLAY, OUTCOME_MARKS, PartialGuardDeployError, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, ReaderModelAcknowledgementError, RefResourceUndeclaredError, RequiredFieldNotProvidedError, START_FILTER_VARS, START_REQUIREMENT_VARS, SYSTEM_IDENTITY, SpawnContractsInvalidError, StaleEffectClaimError, StartNotAllowedError, StartNotPrimedError, StartNotSettledError, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, WorkflowActionFired, WorkflowActivityReset, WorkflowDefinitionDeleted, WorkflowDefinitionDeployed, WorkflowEffectCompleted, WorkflowEffectStateReported, WorkflowEffectsDrained, WorkflowError, WorkflowFieldEdited, WorkflowInstanceAborted, WorkflowInstanceSchema, WorkflowInstanceStarted, WorkflowInstanceTicked, WorkflowStageSet, WorkflowStageTransitioned, WorkflowStateDivergedError, abortReason, acceptsDocumentType, aclPathForResource, actionDisabledDetail, actionRendering, actionVerdict, activityAutonomyOf, analyzeCondition2 as analyzeCondition, applicableDefinitions, assertReadableModel, assertReaderModelAcknowledgement, atomReadsDataset2 as atomReadsDataset, autonomySummary, availableActions, buildInitialFields, buildSnapshot, checklistLines, classifyPrincipalId, clientConfigFromResource, clientProjectUserDirectory, compileGuard, computeDiffEntries, conditionFieldReadNames, conditionSitesOf, contentDocQuery, contentDraftFallback, contentReleaseName, contextMap, createEngine, createTelemetryIntake, datasetResourceParts, defaultLoggerFactory, definitionDeployedData, definitionLookupGroq, definitionTagsGroq, definitionsListGroq, deniedGuardLabels, deniedGuardRefs, denyingGuards, deployStageGuards, deployedTagsGroq, deriveActivityKind, deriveExecutorClassification, deriveWorkflowAutonomy, describeAtom, describeAutonomyWait, describeCondition, describeDefinition, describeFieldInsight, describeNode, describeSite, describeSiteHeading, diagnoseInputFromEvaluation, diagnoseInstance, diffEntry, displayDescription, displayTitle, documentActionDenials, documentPrefilter, driverKind, effectOutputsMap, entryDocRefs, errorMessage, evaluateFromSnapshot, evaluateMutationGuard, evaluateStartFilter, expandResourceAliases, explainCondition2 as explainCondition, explainStartRequirement, extractDocumentId, fieldTreeShape, findCurrentActivityEntry, findOpenStageEntry, formatRead, gdrFromResource, gdrRef, gdrUri, groupMembershipNames, groupSitesOf, guardMatches, guardsForDefinition, guardsForInstance, guardsForResource, guillemets2 as guillemets, hasSingleSubjectRequirement, hashDefinitionContent, humanize2 as humanize, inFlightFilter, initialFieldIssues, instanceDocId, instanceGuardQuery, instanceWatchesDocument, instancesGuardQuery, instancesQuery, isCascadeFired, isClaimExpired, isClientProjectUser, isComparisonOp, isDefinitionApplicable, isFilterScopedOut, isGdr, isInputSourced, isNotesEntry, isProjectUserNotFoundError, isSingleDocRefEntry, isSingleDocRefKind, isStartableDefinition, isSubjectEntry, isTelemetryEnvDenied, isTerminalActivityStatus, isTerminalStage, isTodoListEntry, isTodoListItem, isUnprimed, lakeGuardId, lakePrincipalId, latestDefinitionsGroq, latestDeployedDefinitions, lintEffectOutputs, minReaderModelOf, missingRequiredInputs, modelVersionOf, narrateAutonomyWaits, noopTelemetry, parentRef, parseDefinitionInput, parseDefinitionSnapshot, parseDefinitionSnapshotValue, parseGdr, parseGuardDocument, parseInstanceDocument, parseResourceGdr, processShellUserProperties, projectStartSliceRow, projectToWatchRef, quoted2 as quoted, readInstanceDoc, readsRaw, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, refsOf, rejectedRefTypes, releaseDocId, releaseRef, remediationsFor, requiredModelFeatures, requiredReaderModel, resolveAccess, resolveActor, resolveClientActor, resolveFieldEntry$1 as resolveFieldEntry, resourceAliasesToMap, resourceFromParsed, resourceGdr, retractStageGuards, sameResource, scalarValidationIssues, schemaTreeShape, sentenceCase, silentLogger, singleSubjectRequirementRefused, stageAutonomyOf, startFieldsParam, startKindOf, startRefusal, stripSystemFields, subjectDenialLabels, subscriptionDocument, subscriptionDocumentsForInstance, sweepStaleClaims, tagScopeFilter, terminalState, toBareId, tryParseGdr, unboundRequirementReads, unsatisfiedTransitionSummaries, validateDefinition, validateTag, verdictGuardsForInstance, wallClock, whatIfCondition2 as whatIfCondition, withAssignment, workflow };
13005
+ export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_KIND_DISPLAY, ACTOR_KINDS, ANONYMOUS_IDENTITY, AUTHORING_DISPLAY, ActionDisabledError, ActionParamsInvalidError, CONDITION_VARS, CONTEXT_ENTRY_DISPLAY, CascadeLimitError, ConcurrentCommitEffectOpsError, ConcurrentCompleteEffectError, ConcurrentEditFieldError, ConcurrentFireActionError, ContractViolationError, DATA_MODEL_CHANGES, DATA_MODEL_MIN_READER, DATA_MODEL_VERSION, DEFAULT_CONTENT_PERSPECTIVE, DEFAULT_EFFECT_LEASE_MS, DEFAULT_IDEMPOTENCY_TTL_MS, DEFAULT_TRANSITION_WHEN, DISPLAY, DRIVER_KINDS, DRIVER_KIND_DISPLAY, DefinitionInUseError, DefinitionNotFoundError, EFFECT_COMMIT_DISPATCH_CAP, EFFECT_COMMIT_QUEUE_DEPTH, ENGINE_API_VERSION, EXECUTION_KINDS, EXECUTOR_CLASSIFICATIONS, EXECUTOR_CLASSIFICATION_DISPLAY, EditFieldDeniedError, EffectCommitQueueOverflowError, EffectNotFoundError, EffectOpsInvalidError, EffectOutputsInvalidError, FIELD_KIND_DISPLAY, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GROUP_KIND_DISPLAY, GUARD_DOC_TYPE, GUARD_OWNER, GUARD_PREDICATE_VARS, HISTORY_DISPLAY, InitialFieldsInvalidError, InstanceNotFoundError, MAX_COUNTERFACTUAL_INDEX2 as MAX_COUNTERFACTUAL_INDEX, MissingHandlerError, ModelVersionAheadError, MutationGuardDeniedError, MutationGuardDocSchema, NEUTRAL_MARK, OP_DISPLAY, OUTCOME_MARKS, PartialGuardDeployError, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, ReaderModelAcknowledgementError, RefResourceUndeclaredError, RequiredFieldNotProvidedError, START_FILTER_VARS, START_REQUIREMENT_VARS, SYSTEM_IDENTITY, SpawnContractsInvalidError, StaleEffectClaimError, StartNotAllowedError, StartNotPrimedError, StartNotSettledError, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, WorkflowActionFired, WorkflowActivityReset, WorkflowDefinitionDeleted, WorkflowDefinitionDeployed, WorkflowEffectCompleted, WorkflowEffectStateReported, WorkflowEffectsDrained, WorkflowError, WorkflowFieldEdited, WorkflowInstanceAborted, WorkflowInstanceSchema, WorkflowInstanceStarted, WorkflowInstanceTicked, WorkflowStageSet, WorkflowStageTransitioned, WorkflowStateDivergedError, abortReason, acceptsDocumentType, aclPathForResource, actionDisabledDetail, actionRendering, actionVerdict, activityAutonomyOf, analyzeCondition2 as analyzeCondition, applicableDefinitions, assertReadableModel, assertReaderModelAcknowledgement, atomReadsDataset2 as atomReadsDataset, autonomySummary, availableActions, buildInitialFields, buildSnapshot, checklistLines, classifyPrincipalId, clientConfigFromResource, clientProjectUserDirectory, compileGuard, computeDiffEntries, conditionFieldReadNames, conditionSitesOf, contentDocQuery, contentDraftFallback, contentReleaseName, contextMap, createEngine, createTelemetryIntake, datasetResourceParts, defaultLoggerFactory, definitionDeployedData, definitionLookupGroq, definitionTagsGroq, definitionsListGroq, deniedGuardLabels, deniedGuardRefs, denyingGuards, deployStageGuards, deployedTagsGroq, deriveActivityKind, deriveExecutorClassification, deriveWorkflowAutonomy, describeAtom, describeAutonomyWait, describeCondition, describeDefinition, describeFieldInsight, describeNode, describeSite, describeSiteHeading, diagnoseInputFromEvaluation, diagnoseInstance, diffEntry, displayDescription, displayTitle, documentActionDenials, documentPrefilter, driverKind, effectOutputsMap, entryDocRefs, errorMessage, evaluateFromSnapshot, evaluateMutationGuard, evaluateStartFilter, expandResourceAliases, explainCondition2 as explainCondition, explainStartRequirement, extractDocumentId, fieldTreeShape, findCurrentActivityEntry, findOpenStageEntry, formatRead, gdrFromResource, gdrRef, gdrUri, groupMembershipNames, groupSitesOf, guardMatches, guardsForDefinition, guardsForInstance, guardsForResource, guillemets2 as guillemets, hasSingleSubjectRequirement, hashDefinitionContent, humanize2 as humanize, inFlightFilter, initialFieldIssues, instanceDocId, instanceGuardQuery, instanceWatchesDocument, instancesGuardQuery, instancesQuery, isCascadeFired, isClaimExpired, isClientProjectUser, isComparisonOp, isDefinitionApplicable, isFilterScopedOut, isGdr, isInputSourced, isNotesEntry, isProjectUserNotFoundError, isSingleDocRefEntry, isSingleDocRefKind, isStartableDefinition, isSubjectEntry, isTelemetryEnvDenied, isTerminalActivityStatus, isTerminalStage, isTodoListEntry, isTodoListItem, isUnprimed, lakeGuardId, lakePrincipalId, latestDefinitionsGroq, latestDeployedDefinitions, lintEffectOutputs, minReaderModelOf, missingRequiredInputs, modelVersionOf, narrateAutonomyWaits, noopTelemetry, parentRef, parseDefinitionInput, parseDefinitionSnapshot, parseDefinitionSnapshotValue, parseGdr, parseGuardDocument, parseInstanceDocument, parseResourceGdr, processShellUserProperties, projectStartSliceRow, projectToWatchRef, quoted2 as quoted, readInstanceDoc, readsRaw, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, refsOf, rejectedRefTypes, releaseDocId, releaseRef, remediationsFor, requiredModelFeatures, requiredReaderModel, resolveAccess, resolveActor, resolveClientActor, resolveFieldEntry$1 as resolveFieldEntry, resourceAliasesToMap, resourceFromParsed, resourceGdr, retractStageGuards, sameResource, scalarValidationIssues, schemaTreeShape, sentenceCase, silentLogger, singleSubjectRequirementRefused, stageAutonomyOf, startFieldsParam, startKindOf, startRefusal, stripSystemFields, subjectDenialLabels, subscriptionDocument, subscriptionDocumentsForInstance, sweepStaleClaims, tagScopeFilter, terminalState, toBareId, tryParseGdr, unboundRequirementReads, unsatisfiedTransitionSummaries, userLoginProvider, validateDefinition, validateTag, verdictGuardsForInstance, wallClock, whatIfCondition2 as whatIfCondition, withAssignment, workflow };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/workflow-engine",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "description": "Workflow / BPM engine for Sanity content — define workflows as data, run them as instances against a Sanity client.",
5
5
  "keywords": [
6
6
  "bpm",