@sanity/workflow-studio 0.27.0 → 0.29.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,80 @@
1
1
  # @sanity/workflow-studio
2
2
 
3
+ ## 0.29.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [f44fc33]
8
+ - Updated dependencies [26f25e0]
9
+ - Updated dependencies [25bd1fb]
10
+ - Updated dependencies [3fd04f1]
11
+ - @sanity/workflow-engine@0.29.0
12
+ - @sanity/workflow-react@0.29.0
13
+ - @sanity/workflow-sdk@0.29.0
14
+
15
+ ## 0.28.0
16
+
17
+ ### Minor Changes
18
+
19
+ - 6584554: **BREAKING:** assignment surfaces now read the project's role catalog, so a role
20
+ with no current holders is assignable and carries its project title.
21
+
22
+ Previously the only role vocabulary was the roles loaded members happened to
23
+ hold. A role the project defines but nobody has yet could not be offered at all,
24
+ and a role named by a stored value rendered as its bare machine name because no
25
+ membership record carried a title for it. Both are fixed by reading
26
+ `GET /projects/{projectId}/roles`. That read is gated on
27
+ `sanity-project-roles: read`, which the default user-facing project roles carry
28
+ down to viewer, so it is not expected to need an administrator.
29
+
30
+ The catalog also becomes the bound on what a picker offers, which subtracts as
31
+ well as adds: a role a member holds that the catalog does not list stops being
32
+ offerable, though it still labels correctly wherever it is already assigned. On
33
+ a complete catalog nothing is lost, since every held role is in it.
34
+
35
+ Three API changes, all in `@sanity/workflow-components`:
36
+ - `ProjectMembersState` gains a required `roles` field, the project's assignable
37
+ roles. Anything constructing this state by hand must supply it; `roles: []`
38
+ preserves the previous behaviour, since resolution falls back to the roles
39
+ members hold. `MemberPicker` does not take it — it lists members only, and its
40
+ props were narrowed to the three fields it reads.
41
+ - `memberRoleFor` and `roleLabelFor` take a `RoleVocabulary` (`{members, roles}`)
42
+ in place of a bare member array. The state either adapter returns satisfies it,
43
+ so a caller holding one can pass it straight through.
44
+ - `AssigneeBadge` and `AssigneeBadges` take `vocabulary` in place of `members`,
45
+ for the same reason.
46
+
47
+ Not migrating leaves a type error at each call site rather than a silent
48
+ behaviour change.
49
+
50
+ Hosts using `@sanity/workflow-studio` or `@sanity/workflow-sdk` get the catalog
51
+ without changes: both adapters fetch it beside the member list, each behind a
52
+ per-client, per-project cache, so it costs one extra request per project per page
53
+ load however many surfaces read it. A failed roles read is deliberately
54
+ non-fatal — it degrades to the previous member-derived behaviour rather than
55
+ taking down the member list, and therefore every assignment control, with it. It
56
+ is reported once on the console with its cause, because nothing downstream can
57
+ tell an unreadable catalog from a project that defines no assignable roles. That
58
+ empty result is cached too, so a project whose catalog cannot be read stays on
59
+ the member-derived behaviour until the page reloads.
60
+
61
+ **Docs impact:** Update the assignment concepts and any Studio plugin guidance
62
+ that describes which roles can be assigned, to say the catalog is the source,
63
+ that a role with no holders is offered, and that a held role missing from the
64
+ catalog is not. The `@sanity/workflow-components` README covers the new
65
+ `RoleVocabulary` shape and the picker's behaviour.
66
+
67
+ ### Patch Changes
68
+
69
+ - Updated dependencies [1e4a5da]
70
+ - Updated dependencies [6584554]
71
+ - Updated dependencies [f4405b4]
72
+ - Updated dependencies [5050b06]
73
+ - Updated dependencies [a044ba5]
74
+ - @sanity/workflow-engine@0.28.0
75
+ - @sanity/workflow-sdk@0.28.0
76
+ - @sanity/workflow-react@0.28.0
77
+
3
78
  ## 0.27.0
4
79
 
5
80
  ### Minor Changes
package/README.md CHANGED
@@ -33,10 +33,16 @@ Studio source context (`useSource` / `useClient`).
33
33
  errors surface reactively through `error`.
34
34
  - `useStudioProjectUsers` — the shared Studio project-user directory used by
35
35
  actor resolution and member pickers. It returns the upstream membership and
36
- profile records without an Editorial Workflows projection, plus the engine's
37
- `ProjectUserDirectory` integration. Concurrent loads are coalesced, the
38
- settled successful directory is reused for the client/project lifetime, and
39
- failed loads retry after a 30-second backoff. A directory that has not
36
+ profile records without an Editorial Workflows projection, the project's
37
+ assignable `roles`, and the engine's `ProjectUserDirectory` integration. The
38
+ role catalog is read alongside the memberships and is the only account of a
39
+ role nobody currently holds; it is filtered to roles a person can be assigned,
40
+ and it is empty when the read is unavailable, which leaves role labels to the
41
+ roles members hold and is reported once on the console with its cause.
42
+ Concurrent loads are coalesced, the settled successful directory is reused for
43
+ the client/project lifetime, and a failed membership load retries after a
44
+ 30-second backoff. That backoff does not cover the catalog: an unavailable one
45
+ is held empty for the client's lifetime rather than re-read. A directory that has not
40
46
  answered reports `loading`, including before its fetch starts (the fetch
41
47
  begins in a consumer's effect), so an empty `users` with `loading: false`
42
48
  and no `error` is a confirmed "no members" rather than a directory nobody
package/dist/index.cjs CHANGED
@@ -226,6 +226,7 @@ const projectUsersByClient = /* @__PURE__ */ new WeakMap;
226
226
  function createProjectUsersStore(client, projectId) {
227
227
  let snapshot = {
228
228
  users: [],
229
+ roles: [],
229
230
  loading: !0,
230
231
  error: void 0
231
232
  }, pending, retryAt = 0, usersById = /* @__PURE__ */ new Map, usersByEmail = /* @__PURE__ */ new Map;
@@ -238,18 +239,23 @@ function createProjectUsersStore(client, projectId) {
238
239
  ...snapshot,
239
240
  loading: !0,
240
241
  error: void 0
241
- }), pending = fetchProjectUsers(client, projectId).then(users => (usersById = new Map(users.flatMap(user => {
242
+ }), pending = Promise.all([ fetchProjectUsers(client, projectId), projectUsers.loadProjectRolesOrNone({
243
+ client: client,
244
+ projectId: projectId
245
+ }) ]).then(([users, roles]) => (usersById = new Map(users.flatMap(user => {
242
246
  const entries = [ [ user.membership.id, user ] ], globalId = user.profile?.sanityUserId;
243
247
  return globalId !== void 0 && globalId !== user.membership.id && entries.push([ globalId, user ]),
244
248
  entries;
245
249
  })), usersByEmail = new Map(users.flatMap(user => user.profile?.email ? [ [ user.profile.email.toLowerCase(), user ] ] : [])),
246
250
  retryAt = 1 / 0, setSnapshot({
247
251
  users: users,
252
+ roles: roles,
248
253
  loading: !1,
249
254
  error: void 0
250
255
  }), pending = void 0, users), error => {
251
256
  throw retryAt = Date.now() + PROJECT_USERS_RETRY_MS, setSnapshot({
252
257
  users: [],
258
+ roles: [],
253
259
  loading: !1,
254
260
  error: error
255
261
  }), pending = void 0, error;
@@ -316,6 +322,7 @@ function studioProjectUserDirectory(client, projectId) {
316
322
 
317
323
  const missingProjectSnapshot = {
318
324
  users: [],
325
+ roles: [],
319
326
  loading: !1,
320
327
  error: new Error("No projectId on the Studio client")
321
328
  }, noopSubscribe = () => () => {};
package/dist/index.d.cts CHANGED
@@ -6,6 +6,7 @@ import type { GdrUri } from "@sanity/workflow-engine";
6
6
  import type { InstancesQueryFilter } from "@sanity/workflow-engine";
7
7
  import { MissingHandlerPolicy } from "@sanity/workflow-engine";
8
8
  import { MutationGuardDoc } from "@sanity/workflow-react";
9
+ import { ProjectRole } from "@sanity/workflow-sdk/project-users";
9
10
  import { ProjectUserDirectory } from "@sanity/workflow-engine";
10
11
  import { ProjectUserProfile } from "@sanity/workflow-sdk/project-users";
11
12
  import { ResourceClientResolver } from "@sanity/workflow-engine";
@@ -61,6 +62,9 @@ export declare function studioProjectUserDirectory(
61
62
 
62
63
  export declare interface StudioProjectUsersState {
63
64
  readonly users: readonly StudioProjectUser[];
65
+ /** The project's assignable roles, whether or not anyone holds them. Empty
66
+ * when the read is unavailable — see {@link loadProjectRolesOrNone}. */
67
+ readonly roles: readonly ProjectRole[];
64
68
  /** True while the directory has not answered, including before its load
65
69
  * starts — the fetch begins in a consumer's effect or the first directory
66
70
  * lookup, one commit after the first render reads this state. An empty
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ import type { GdrUri } from "@sanity/workflow-engine";
6
6
  import type { InstancesQueryFilter } from "@sanity/workflow-engine";
7
7
  import { MissingHandlerPolicy } from "@sanity/workflow-engine";
8
8
  import { MutationGuardDoc } from "@sanity/workflow-react";
9
+ import { ProjectRole } from "@sanity/workflow-sdk/project-users";
9
10
  import { ProjectUserDirectory } from "@sanity/workflow-engine";
10
11
  import { ProjectUserProfile } from "@sanity/workflow-sdk/project-users";
11
12
  import { ResourceClientResolver } from "@sanity/workflow-engine";
@@ -61,6 +62,9 @@ export declare function studioProjectUserDirectory(
61
62
 
62
63
  export declare interface StudioProjectUsersState {
63
64
  readonly users: readonly StudioProjectUser[];
65
+ /** The project's assignable roles, whether or not anyone holds them. Empty
66
+ * when the read is unavailable — see {@link loadProjectRolesOrNone}. */
67
+ readonly roles: readonly ProjectRole[];
64
68
  /** True while the directory has not answered, including before its load
65
69
  * starts — the fetch begins in a consumer's effect or the first directory
66
70
  * lookup, one commit after the first render reads this state. An empty
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import { useSource, useClient } from "sanity";
12
12
 
13
13
  import { useWorkflowTelemetry, useWorkflowSession as useWorkflowSession$1, useDocumentWorkflows as useDocumentWorkflows$1, useWorkflowInstances as useWorkflowInstances$1 } from "@sanity/workflow-react";
14
14
 
15
- import { loadProjectUserProfiles } from "@sanity/workflow-sdk/project-users";
15
+ import { loadProjectRolesOrNone, loadProjectUserProfiles } from "@sanity/workflow-sdk/project-users";
16
16
 
17
17
  function makeStudioObserver(sdk, options) {
18
18
  const {client: client, engineResource: engineResource} = options;
@@ -234,6 +234,7 @@ const projectUsersByClient = /* @__PURE__ */ new WeakMap;
234
234
  function createProjectUsersStore(client, projectId) {
235
235
  let snapshot = {
236
236
  users: [],
237
+ roles: [],
237
238
  loading: !0,
238
239
  error: void 0
239
240
  }, pending, retryAt = 0, usersById = /* @__PURE__ */ new Map, usersByEmail = /* @__PURE__ */ new Map;
@@ -246,18 +247,23 @@ function createProjectUsersStore(client, projectId) {
246
247
  ...snapshot,
247
248
  loading: !0,
248
249
  error: void 0
249
- }), pending = fetchProjectUsers(client, projectId).then(users => (usersById = new Map(users.flatMap(user => {
250
+ }), pending = Promise.all([ fetchProjectUsers(client, projectId), loadProjectRolesOrNone({
251
+ client: client,
252
+ projectId: projectId
253
+ }) ]).then(([users, roles]) => (usersById = new Map(users.flatMap(user => {
250
254
  const entries = [ [ user.membership.id, user ] ], globalId = user.profile?.sanityUserId;
251
255
  return globalId !== void 0 && globalId !== user.membership.id && entries.push([ globalId, user ]),
252
256
  entries;
253
257
  })), usersByEmail = new Map(users.flatMap(user => user.profile?.email ? [ [ user.profile.email.toLowerCase(), user ] ] : [])),
254
258
  retryAt = 1 / 0, setSnapshot({
255
259
  users: users,
260
+ roles: roles,
256
261
  loading: !1,
257
262
  error: void 0
258
263
  }), pending = void 0, users), error => {
259
264
  throw retryAt = Date.now() + PROJECT_USERS_RETRY_MS, setSnapshot({
260
265
  users: [],
266
+ roles: [],
261
267
  loading: !1,
262
268
  error: error
263
269
  }), pending = void 0, error;
@@ -324,6 +330,7 @@ function studioProjectUserDirectory(client, projectId) {
324
330
 
325
331
  const missingProjectSnapshot = {
326
332
  users: [],
333
+ roles: [],
327
334
  loading: !1,
328
335
  error: new Error("No projectId on the Studio client")
329
336
  }, noopSubscribe = () => () => {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/workflow-studio",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
4
4
  "description": "React adapter that drives the @sanity/workflow-engine reactive session in Sanity Studio through the App SDK store.",
5
5
  "keywords": [
6
6
  "sanity",
@@ -50,17 +50,17 @@
50
50
  "react-dom": "^19.2.7",
51
51
  "sanity": "^6",
52
52
  "vitest": "^4.1.8",
53
- "@sanity/workflow-react": "0.27.0",
54
- "@sanity/workflow-engine": "0.27.0",
55
- "@sanity/workflow-sdk": "0.27.0"
53
+ "@sanity/workflow-engine": "0.29.0",
54
+ "@sanity/workflow-react": "0.29.0",
55
+ "@sanity/workflow-sdk": "0.29.0"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "@sanity/sdk": "^2.12.0",
59
59
  "react": "^19.2.7",
60
60
  "sanity": "^6",
61
- "@sanity/workflow-react": "0.27.0",
62
- "@sanity/workflow-sdk": "0.27.0",
63
- "@sanity/workflow-engine": "0.27.0"
61
+ "@sanity/workflow-react": "0.29.0",
62
+ "@sanity/workflow-engine": "0.29.0",
63
+ "@sanity/workflow-sdk": "0.29.0"
64
64
  },
65
65
  "engines": {
66
66
  "node": ">=20"