@stigmer/react 3.0.7 → 3.0.8-dev.20260612062921

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/access/ManageAccessButton.d.ts +47 -0
  2. package/access/ManageAccessButton.d.ts.map +1 -0
  3. package/access/ManageAccessButton.js +45 -0
  4. package/access/ManageAccessButton.js.map +1 -0
  5. package/access/ManageAccessDialog.d.ts +60 -0
  6. package/access/ManageAccessDialog.d.ts.map +1 -0
  7. package/access/ManageAccessDialog.js +83 -0
  8. package/access/ManageAccessDialog.js.map +1 -0
  9. package/access/index.d.ts +5 -0
  10. package/access/index.d.ts.map +1 -0
  11. package/access/index.js +7 -0
  12. package/access/index.js.map +1 -0
  13. package/access/types.d.ts +56 -0
  14. package/access/types.d.ts.map +1 -0
  15. package/access/types.js +2 -0
  16. package/access/types.js.map +1 -0
  17. package/access/useManageAccess.d.ts +60 -0
  18. package/access/useManageAccess.d.ts.map +1 -0
  19. package/access/useManageAccess.js +41 -0
  20. package/access/useManageAccess.js.map +1 -0
  21. package/agent/AgentDetailView.d.ts.map +1 -1
  22. package/agent/AgentDetailView.js +34 -4
  23. package/agent/AgentDetailView.js.map +1 -1
  24. package/agent-instance/AgentInstanceDetailPanel.d.ts.map +1 -1
  25. package/agent-instance/AgentInstanceDetailPanel.js +14 -4
  26. package/agent-instance/AgentInstanceDetailPanel.js.map +1 -1
  27. package/iam-policy/GrantAccessForm.d.ts +15 -5
  28. package/iam-policy/GrantAccessForm.d.ts.map +1 -1
  29. package/iam-policy/GrantAccessForm.js +15 -12
  30. package/iam-policy/GrantAccessForm.js.map +1 -1
  31. package/iam-policy/OrgMembersPanel.d.ts.map +1 -1
  32. package/iam-policy/OrgMembersPanel.js +2 -1
  33. package/iam-policy/OrgMembersPanel.js.map +1 -1
  34. package/iam-policy/PeopleWithAccess.d.ts +34 -0
  35. package/iam-policy/PeopleWithAccess.d.ts.map +1 -0
  36. package/iam-policy/PeopleWithAccess.js +55 -0
  37. package/iam-policy/PeopleWithAccess.js.map +1 -0
  38. package/iam-policy/PrincipalPicker.d.ts +48 -0
  39. package/iam-policy/PrincipalPicker.d.ts.map +1 -0
  40. package/iam-policy/PrincipalPicker.js +139 -0
  41. package/iam-policy/PrincipalPicker.js.map +1 -0
  42. package/iam-policy/ProviderBadge.d.ts +28 -0
  43. package/iam-policy/ProviderBadge.d.ts.map +1 -0
  44. package/iam-policy/ProviderBadge.js +31 -0
  45. package/iam-policy/ProviderBadge.js.map +1 -0
  46. package/iam-policy/SharePanel.d.ts +13 -5
  47. package/iam-policy/SharePanel.d.ts.map +1 -1
  48. package/iam-policy/SharePanel.js +9 -34
  49. package/iam-policy/SharePanel.js.map +1 -1
  50. package/iam-policy/index.d.ts +3 -0
  51. package/iam-policy/index.d.ts.map +1 -1
  52. package/iam-policy/index.js +3 -0
  53. package/iam-policy/index.js.map +1 -1
  54. package/index.d.ts +7 -5
  55. package/index.d.ts.map +1 -1
  56. package/index.js +6 -3
  57. package/index.js.map +1 -1
  58. package/mcp-server/McpServerDetailView.d.ts.map +1 -1
  59. package/mcp-server/McpServerDetailView.js +41 -12
  60. package/mcp-server/McpServerDetailView.js.map +1 -1
  61. package/organization/OrgProvider.d.ts +9 -0
  62. package/organization/OrgProvider.d.ts.map +1 -1
  63. package/organization/OrgProvider.js +12 -0
  64. package/organization/OrgProvider.js.map +1 -1
  65. package/organization/index.d.ts +1 -1
  66. package/organization/index.d.ts.map +1 -1
  67. package/organization/index.js +1 -1
  68. package/organization/index.js.map +1 -1
  69. package/package.json +12 -12
  70. package/skill/SkillDetailView.d.ts.map +1 -1
  71. package/skill/SkillDetailView.js +31 -3
  72. package/skill/SkillDetailView.js.map +1 -1
  73. package/src/access/ManageAccessButton.tsx +115 -0
  74. package/src/access/ManageAccessDialog.tsx +239 -0
  75. package/src/access/__tests__/ManageAccessButton.test.tsx +62 -0
  76. package/src/access/__tests__/ManageAccessDialog.test.tsx +146 -0
  77. package/src/access/index.ts +21 -0
  78. package/src/access/types.ts +58 -0
  79. package/src/access/useManageAccess.tsx +101 -0
  80. package/src/agent/AgentDetailView.tsx +50 -21
  81. package/src/agent-instance/AgentInstanceDetailPanel.tsx +24 -42
  82. package/src/iam-policy/GrantAccessForm.tsx +30 -35
  83. package/src/iam-policy/OrgMembersPanel.tsx +2 -0
  84. package/src/iam-policy/PeopleWithAccess.tsx +220 -0
  85. package/src/iam-policy/PrincipalPicker.tsx +347 -0
  86. package/src/iam-policy/ProviderBadge.tsx +53 -0
  87. package/src/iam-policy/SharePanel.tsx +20 -165
  88. package/src/iam-policy/index.ts +17 -0
  89. package/src/index.ts +30 -0
  90. package/src/mcp-server/McpServerDetailView.tsx +37 -9
  91. package/src/organization/OrgProvider.tsx +13 -0
  92. package/src/organization/index.ts +1 -1
  93. package/src/skill/SkillDetailView.tsx +34 -9
  94. package/src/workflow/WorkflowDetailView.tsx +49 -22
  95. package/src/workflow/WorkflowExecutionHeader.tsx +12 -1
  96. package/src/workflow/WorkflowExecutionViewer.tsx +8 -1
  97. package/src/workflow/index.ts +4 -0
  98. package/src/workflow/instance/RunVisibilityControl.tsx +116 -0
  99. package/src/workflow/instance/WorkflowInstanceDetailPanel.tsx +55 -42
  100. package/src/workflow/instance/index.ts +5 -0
  101. package/src/workflow/instance/useUpdateWorkflowInstanceExecutionVisibility.ts +74 -0
  102. package/styles.css +1 -1
  103. package/workflow/WorkflowDetailView.d.ts.map +1 -1
  104. package/workflow/WorkflowDetailView.js +31 -3
  105. package/workflow/WorkflowDetailView.js.map +1 -1
  106. package/workflow/WorkflowExecutionHeader.d.ts +7 -0
  107. package/workflow/WorkflowExecutionHeader.d.ts.map +1 -1
  108. package/workflow/WorkflowExecutionHeader.js +2 -2
  109. package/workflow/WorkflowExecutionHeader.js.map +1 -1
  110. package/workflow/WorkflowExecutionViewer.d.ts +6 -1
  111. package/workflow/WorkflowExecutionViewer.d.ts.map +1 -1
  112. package/workflow/WorkflowExecutionViewer.js +2 -2
  113. package/workflow/WorkflowExecutionViewer.js.map +1 -1
  114. package/workflow/index.d.ts +1 -1
  115. package/workflow/index.d.ts.map +1 -1
  116. package/workflow/index.js +1 -1
  117. package/workflow/index.js.map +1 -1
  118. package/workflow/instance/RunVisibilityControl.d.ts +25 -0
  119. package/workflow/instance/RunVisibilityControl.d.ts.map +1 -0
  120. package/workflow/instance/RunVisibilityControl.js +56 -0
  121. package/workflow/instance/RunVisibilityControl.js.map +1 -0
  122. package/workflow/instance/WorkflowInstanceDetailPanel.d.ts.map +1 -1
  123. package/workflow/instance/WorkflowInstanceDetailPanel.js +30 -4
  124. package/workflow/instance/WorkflowInstanceDetailPanel.js.map +1 -1
  125. package/workflow/instance/index.d.ts +2 -0
  126. package/workflow/instance/index.d.ts.map +1 -1
  127. package/workflow/instance/index.js +2 -0
  128. package/workflow/instance/index.js.map +1 -1
  129. package/workflow/instance/useUpdateWorkflowInstanceExecutionVisibility.d.ts +30 -0
  130. package/workflow/instance/useUpdateWorkflowInstanceExecutionVisibility.d.ts.map +1 -0
  131. package/workflow/instance/useUpdateWorkflowInstanceExecutionVisibility.js +39 -0
  132. package/workflow/instance/useUpdateWorkflowInstanceExecutionVisibility.js.map +1 -0
@@ -1,12 +1,9 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useState } from "react";
4
3
  import type { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
5
- import type { PrincipalAccess } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/io_pb";
6
4
  import { cn } from "@stigmer/theme";
7
- import { getUserMessage, iamRoleToString } from "@stigmer/sdk";
8
- import { useShareFlow, type ShareFlowResource } from "./useShareFlow";
9
- import { GrantAccessForm } from "./GrantAccessForm";
5
+ import type { ShareFlowResource } from "./useShareFlow";
6
+ import { PeopleWithAccess } from "./PeopleWithAccess";
10
7
 
11
8
  /** Props for {@link SharePanel}. */
12
9
  export interface SharePanelProps {
@@ -16,6 +13,11 @@ export interface SharePanelProps {
16
13
  readonly resourceKindString: string;
17
14
  /** ApiResourceKind enum value for grantable-role lookup. */
18
15
  readonly resourceKind: ApiResourceKind;
16
+ /**
17
+ * Organization the resource belongs to (`metadata.org`). Drives the
18
+ * org-member typeahead in the grant form.
19
+ */
20
+ readonly orgId: string;
19
21
  /** Fired when the user closes the panel. */
20
22
  readonly onClose?: () => void;
21
23
  /** Additional CSS class names for the root container. */
@@ -26,9 +28,11 @@ export interface SharePanelProps {
26
28
  * Self-contained sharing panel that displays who has access to a
27
29
  * resource and allows granting/revoking access.
28
30
  *
29
- * Combines the access list (from {@link useShareFlow}) with the
30
- * {@link GrantAccessForm} in a single panel suitable for embedding
31
- * in a popover, sidebar, or dialog.
31
+ * A thin header-and-close wrapper around {@link PeopleWithAccess} the same
32
+ * "people with access" body the unified Manage access dialog renders — so the
33
+ * two surfaces stay byte-for-byte consistent. Suitable for embedding in a
34
+ * popover or sidebar; for the full visibility + people experience use the
35
+ * Manage access dialog instead.
32
36
  *
33
37
  * All visual properties flow through `--stgm-*` design tokens.
34
38
  *
@@ -38,6 +42,7 @@ export interface SharePanelProps {
38
42
  * resource={{ kind: "session", id: sessionId, resourceKind: ApiResourceKind.session }}
39
43
  * resourceKindString="session"
40
44
  * resourceKind={ApiResourceKind.session}
45
+ * orgId={orgId}
41
46
  * onClose={() => setOpen(false)}
42
47
  * />
43
48
  * ```
@@ -46,22 +51,10 @@ export function SharePanel({
46
51
  resource,
47
52
  resourceKindString,
48
53
  resourceKind,
54
+ orgId,
49
55
  onClose,
50
56
  className,
51
57
  }: SharePanelProps) {
52
- const {
53
- accessList,
54
- isLoading,
55
- fetchError,
56
- revokeAccess,
57
- isRevoking,
58
- revokeError,
59
- refetch,
60
- hasGrantableRoles,
61
- } = useShareFlow(resource);
62
-
63
- const [showGrantForm, setShowGrantForm] = useState(false);
64
-
65
58
  return (
66
59
  <div
67
60
  className={cn("flex flex-col gap-4 p-4", className)}
@@ -88,146 +81,16 @@ export function SharePanel({
88
81
  )}
89
82
  </div>
90
83
 
91
- {/* Access list */}
92
- <div className="space-y-1">
93
- <p className="text-xs text-muted-foreground">
94
- {isLoading
95
- ? "Loading access list..."
96
- : `${accessList.length} ${accessList.length === 1 ? "person" : "people"} with access`}
97
- </p>
98
-
99
- {fetchError && (
100
- <p className="text-destructive text-[0.65rem]" role="alert">
101
- {getUserMessage(fetchError)}
102
- </p>
103
- )}
104
-
105
- {!isLoading && accessList.length > 0 && (
106
- <ul className="space-y-1 mt-2" aria-label="People with access">
107
- {accessList.map((entry) => (
108
- <AccessEntry
109
- key={entry.principal?.id ?? "unknown"}
110
- entry={entry}
111
- resourceKindString={resourceKindString}
112
- resourceId={resource.id}
113
- onRevoke={revokeAccess}
114
- isRevoking={isRevoking}
115
- />
116
- ))}
117
- </ul>
118
- )}
119
-
120
- {revokeError && (
121
- <p className="text-destructive text-[0.65rem]" role="alert">
122
- {getUserMessage(revokeError)}
123
- </p>
124
- )}
125
- </div>
126
-
127
- {/* Grant form */}
128
- {hasGrantableRoles && (
129
- <div className="border-t border-border pt-3">
130
- {showGrantForm ? (
131
- <GrantAccessForm
132
- resourceKind={resourceKind}
133
- resourceKindString={resourceKindString}
134
- resourceId={resource.id}
135
- onGranted={() => {
136
- setShowGrantForm(false);
137
- refetch();
138
- }}
139
- onCancel={() => setShowGrantForm(false)}
140
- />
141
- ) : (
142
- <button
143
- type="button"
144
- onClick={() => setShowGrantForm(true)}
145
- className={cn(
146
- "w-full rounded-md px-3 py-1.5 text-xs font-medium text-center",
147
- "border border-dashed border-border",
148
- "text-muted-foreground hover:text-foreground hover:border-foreground/30",
149
- "hover:bg-accent-hover transition-colors",
150
- )}
151
- >
152
- + Add people
153
- </button>
154
- )}
155
- </div>
156
- )}
84
+ <PeopleWithAccess
85
+ resource={resource}
86
+ resourceKindString={resourceKindString}
87
+ resourceKind={resourceKind}
88
+ orgId={orgId}
89
+ />
157
90
  </div>
158
91
  );
159
92
  }
160
93
 
161
- // ---------------------------------------------------------------------------
162
- // Internal subcomponents
163
- // ---------------------------------------------------------------------------
164
-
165
- function AccessEntry({
166
- entry,
167
- resourceKindString,
168
- resourceId,
169
- onRevoke,
170
- isRevoking,
171
- }: {
172
- readonly entry: PrincipalAccess;
173
- readonly resourceKindString: string;
174
- readonly resourceId: string;
175
- readonly onRevoke: (principalId: string, role: string) => Promise<void>;
176
- readonly isRevoking: boolean;
177
- }) {
178
- const principal = entry.principal;
179
- const roles = entry.roles;
180
-
181
- const displayName = principal?.name || principal?.email || principal?.id || "Unknown";
182
- const primaryRole = roles[0]?.role;
183
-
184
- const handleRevoke = useCallback(async () => {
185
- if (!principal?.id || !primaryRole?.code) return;
186
- await onRevoke(principal.id, primaryRole.code);
187
- }, [principal?.id, primaryRole?.code, onRevoke]);
188
-
189
- return (
190
- <li className="flex items-center justify-between gap-2 rounded-md px-2 py-1.5 hover:bg-accent-hover group">
191
- <div className="flex items-center gap-2 min-w-0">
192
- <div
193
- className="h-6 w-6 rounded-full bg-muted flex items-center justify-center text-[0.6rem] font-medium text-muted-foreground shrink-0"
194
- aria-hidden="true"
195
- >
196
- {(principal?.name?.[0] ?? principal?.email?.[0] ?? "?").toUpperCase()}
197
- </div>
198
- <div className="min-w-0">
199
- <p className="text-xs text-foreground truncate">{displayName}</p>
200
- {principal?.email && principal.name && (
201
- <p className="text-[0.6rem] text-muted-foreground truncate">
202
- {principal.email}
203
- </p>
204
- )}
205
- </div>
206
- </div>
207
-
208
- <div className="flex items-center gap-1.5 shrink-0">
209
- <span className="text-[0.6rem] text-muted-foreground capitalize">
210
- {primaryRole?.name ?? primaryRole?.code ?? "—"}
211
- </span>
212
- <button
213
- type="button"
214
- onClick={handleRevoke}
215
- disabled={isRevoking}
216
- aria-label={`Remove ${displayName}'s access`}
217
- className={cn(
218
- "rounded p-0.5 text-muted-foreground opacity-0 group-hover:opacity-100",
219
- "hover:text-destructive hover:bg-destructive/10",
220
- "disabled:pointer-events-none disabled:opacity-50",
221
- "transition-opacity",
222
- )}
223
- >
224
- <RemoveIcon />
225
- </button>
226
- </div>
227
- </li>
228
- );
229
- }
230
-
231
94
  function CloseIcon() {
232
95
  return (
233
96
  <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
@@ -235,11 +98,3 @@ function CloseIcon() {
235
98
  </svg>
236
99
  );
237
100
  }
238
-
239
- function RemoveIcon() {
240
- return (
241
- <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
242
- <path d="M4 4l8 8M12 4l-8 8" />
243
- </svg>
244
- );
245
- }
@@ -43,8 +43,25 @@ export {
43
43
 
44
44
  export { RoleSelector, type RoleSelectorProps } from "./RoleSelector";
45
45
 
46
+ export {
47
+ PrincipalPicker,
48
+ type PrincipalPickerProps,
49
+ type SelectedPrincipal,
50
+ } from "./PrincipalPicker";
51
+
52
+ export {
53
+ ProviderBadge,
54
+ providerLabel,
55
+ type ProviderBadgeProps,
56
+ } from "./ProviderBadge";
57
+
46
58
  export { GrantAccessForm, type GrantAccessFormProps } from "./GrantAccessForm";
47
59
 
60
+ export {
61
+ PeopleWithAccess,
62
+ type PeopleWithAccessProps,
63
+ } from "./PeopleWithAccess";
64
+
48
65
  export {
49
66
  OrgMembersPanel,
50
67
  type OrgMembersPanelProps,
package/src/index.ts CHANGED
@@ -588,7 +588,11 @@ export {
588
588
  useShareFlow,
589
589
  useCheckPermission,
590
590
  RoleSelector,
591
+ PrincipalPicker,
592
+ ProviderBadge,
593
+ providerLabel,
591
594
  GrantAccessForm,
595
+ PeopleWithAccess,
592
596
  OrgMembersPanel,
593
597
  SharePanel,
594
598
  PermissionGate,
@@ -610,17 +614,39 @@ export type {
610
614
  UseCheckPermissionReturn,
611
615
  PermissionCheckResource,
612
616
  RoleSelectorProps,
617
+ PrincipalPickerProps,
618
+ SelectedPrincipal,
619
+ ProviderBadgeProps,
613
620
  GrantAccessFormProps,
621
+ PeopleWithAccessProps,
614
622
  OrgMembersPanelProps,
615
623
  SharePanelProps,
616
624
  PermissionGateProps,
617
625
  } from "./iam-policy";
618
626
 
627
+ // Access — unified "Manage access" experience (visibility + people) as one
628
+ // dialog, with a kebab hook and a visible-button trigger.
629
+ export {
630
+ ManageAccessDialog,
631
+ ManageAccessButton,
632
+ useManageAccess,
633
+ } from "./access";
634
+ export type {
635
+ ManageAccessDialogProps,
636
+ ManageAccessButtonProps,
637
+ UseManageAccessArgs,
638
+ UseManageAccessReturn,
639
+ AccessResource,
640
+ AccessVisibility,
641
+ AccessExtraSection,
642
+ } from "./access";
643
+
619
644
  // Organization — context provider, hooks, data hooks, behavior hooks, styled form, profile panel, and org switcher
620
645
  export {
621
646
  OrgProvider,
622
647
  useOrg,
623
648
  useActiveOrgSlug,
649
+ useActiveOrgId,
624
650
  useOrgGate,
625
651
  useOrganization,
626
652
  useCreateOrganization,
@@ -1240,11 +1266,13 @@ export {
1240
1266
  useWorkflowInstance,
1241
1267
  useCreateWorkflowInstance,
1242
1268
  useUpdateWorkflowInstance,
1269
+ useUpdateWorkflowInstanceExecutionVisibility,
1243
1270
  useDeleteWorkflowInstance,
1244
1271
  WorkflowInstanceEmptyState,
1245
1272
  WorkflowInstanceList,
1246
1273
  CreateWorkflowInstanceDialog,
1247
1274
  WorkflowInstanceDetailPanel,
1275
+ RunVisibilityControl,
1248
1276
  // T15: Workflow Template Gallery
1249
1277
  PATTERN_LABELS,
1250
1278
  WORKFLOW_CATEGORY_LABELS,
@@ -1367,11 +1395,13 @@ export type {
1367
1395
  UseWorkflowInstanceReturn,
1368
1396
  UseCreateWorkflowInstanceReturn,
1369
1397
  UseUpdateWorkflowInstanceReturn,
1398
+ UseUpdateWorkflowInstanceExecutionVisibilityReturn,
1370
1399
  UseDeleteWorkflowInstanceReturn,
1371
1400
  WorkflowInstanceEmptyStateProps,
1372
1401
  WorkflowInstanceListProps,
1373
1402
  CreateWorkflowInstanceDialogProps,
1374
1403
  WorkflowInstanceDetailPanelProps,
1404
+ RunVisibilityControlProps,
1375
1405
  // T15: Workflow Template Gallery types
1376
1406
  WorkflowTemplateData,
1377
1407
  WorkflowTemplateCategory,
@@ -23,10 +23,12 @@ import type { OAuthConnectPhase } from "./useMcpServerOAuthConnect";
23
23
  import { useDisconnectOAuth } from "./useDisconnectOAuth";
24
24
  import { useOrgOAuthApp } from "./useOrgOAuthApp";
25
25
  import { OAuthAppForm } from "./OAuthAppForm";
26
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
26
27
  import { ErrorMessage } from "../error/ErrorMessage";
27
28
  import { EnvVarForm } from "../environment/EnvVarForm";
28
29
  import type { EnvVarFormVariable } from "../environment/EnvVarForm";
29
- import { ResourceVisibilityControl } from "../library/ResourceVisibilityControl";
30
+ import { VisibilityBadge } from "../library/VisibilitySelector";
31
+ import { useManageAccess } from "../access/useManageAccess";
30
32
  import { Tabs, type TabItem } from "../tabs/Tabs";
31
33
  import { ResourceDetailShell } from "../resource-detail/ResourceDetailShell";
32
34
  import { Section } from "../resource-detail/Section";
@@ -338,6 +340,29 @@ export function McpServerDetailView({
338
340
  oauth.clearError();
339
341
  }, [connection, oauth]);
340
342
 
343
+ // Unified Manage access — visibility (General access) over explicit grants
344
+ // (People), opened from the kebab. Closes the blueprint share gap for MCP
345
+ // servers.
346
+ const access = useManageAccess({
347
+ resource: mcpServer?.metadata
348
+ ? {
349
+ kind: ApiResourceKind.mcp_server,
350
+ kindString: "mcp_server",
351
+ id: mcpServer.metadata.id,
352
+ org: mcpServer.metadata.org,
353
+ name: mcpServer.metadata.name,
354
+ }
355
+ : null,
356
+ visibility: mcpServer?.metadata
357
+ ? {
358
+ kind: "mcpServer",
359
+ current: mcpServer.metadata.visibility,
360
+ org: mcpServer.metadata.org,
361
+ onChanged: refetch,
362
+ }
363
+ : undefined,
364
+ });
365
+
341
366
  if (isLoading) return <LoadingSkeleton className={className} />;
342
367
  if (error)
343
368
  return <ErrorMessage error={error} retry={refetch} className={className} />;
@@ -380,16 +405,16 @@ export function McpServerDetailView({
380
405
  updatedAt: specAudit?.updatedAt ? timestampDate(specAudit.updatedAt) : null,
381
406
  };
382
407
 
408
+ // Inline visibility is read-only (at-a-glance); editing lives in the
409
+ // Manage access dialog, the single writer for both access axes.
383
410
  const visibilityControl = meta ? (
384
- <ResourceVisibilityControl
385
- kind="mcpServer"
386
- resourceId={meta.id}
387
- visibility={meta.visibility}
388
- org={meta.org || org}
389
- onChanged={refetch}
390
- />
411
+ <VisibilityBadge visibility={meta.visibility} />
391
412
  ) : undefined;
392
413
 
414
+ const mergedActions = access.action
415
+ ? [...(actions ?? []), access.action]
416
+ : actions;
417
+
393
418
  const headerMetaExtra = (
394
419
  <>
395
420
  {status && <ValidationStateBadge state={status.validationState} />}
@@ -409,13 +434,14 @@ export function McpServerDetailView({
409
434
  ) : undefined;
410
435
 
411
436
  return (
437
+ <>
412
438
  <ResourceDetailShell
413
439
  header={headerMeta}
414
440
  visibilityControl={visibilityControl}
415
441
  headerMetaExtra={headerMetaExtra}
416
442
  headerBanner={headerBanner}
417
443
  primaryAction={primaryAction}
418
- actions={actions}
444
+ actions={mergedActions}
419
445
  className={className}
420
446
  >
421
447
  {(editable || spec?.description) && (
@@ -576,6 +602,8 @@ export function McpServerDetailView({
576
602
  <TagsSection tags={spec?.tags ?? []} editable={editable} isSaving={isUpdating} saveMcpField={saveMcpField} />
577
603
  )}
578
604
  </ResourceDetailShell>
605
+ {access.dialog}
606
+ </>
579
607
  );
580
608
  }
581
609
 
@@ -182,3 +182,16 @@ export function useActiveOrgSlug(): string {
182
182
  const { activeOrg } = useOrg();
183
183
  return activeOrg?.metadata?.slug ?? "";
184
184
  }
185
+
186
+ /**
187
+ * Convenience accessor: returns the active org's system ID (`metadata.id`),
188
+ * or an empty string when no org is selected.
189
+ *
190
+ * This is the identifier used as the `organization` object in authorization
191
+ * (FGA) — e.g. for member lookups in the share picker — as opposed to the
192
+ * human-readable slug returned by {@link useActiveOrgSlug}.
193
+ */
194
+ export function useActiveOrgId(): string {
195
+ const { activeOrg } = useOrg();
196
+ return activeOrg?.metadata?.id ?? "";
197
+ }
@@ -1,4 +1,4 @@
1
- export { OrgProvider, useOrg, useActiveOrgSlug } from "./OrgProvider";
1
+ export { OrgProvider, useOrg, useActiveOrgSlug, useActiveOrgId } from "./OrgProvider";
2
2
  export type { OrgContextValue } from "./OrgProvider";
3
3
  export { useOrgGate } from "./useOrgGate";
4
4
  export type {
@@ -7,10 +7,12 @@ import { timestampDate } from "@bufbuild/protobuf/wkt";
7
7
  import type { Skill } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
8
8
  import type { GitProvenance } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/status_pb";
9
9
  import { SkillState } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/status_pb";
10
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
10
11
  import { useSkill } from "./useSkill";
11
12
  import { SkillFileBrowser } from "./SkillFileBrowser";
12
13
  import { ErrorMessage } from "../error/ErrorMessage";
13
- import { ResourceVisibilityControl } from "../library/ResourceVisibilityControl";
14
+ import { VisibilityBadge } from "../library/VisibilitySelector";
15
+ import { useManageAccess } from "../access/useManageAccess";
14
16
  import { MARKDOWN_COMPONENTS, REMARK_PLUGINS, stripFrontmatter } from "../internal/markdown-components";
15
17
  import { ResourceDetailShell } from "../resource-detail/ResourceDetailShell";
16
18
  import { Section } from "../resource-detail/Section";
@@ -207,6 +209,28 @@ export function SkillDetailView({
207
209
  }
208
210
  }, [skill]);
209
211
 
212
+ // Unified Manage access — visibility (General access) over explicit grants
213
+ // (People), opened from the kebab. Closes the blueprint share gap for skills.
214
+ const access = useManageAccess({
215
+ resource: skill?.metadata
216
+ ? {
217
+ kind: ApiResourceKind.skill,
218
+ kindString: "skill",
219
+ id: skill.metadata.id,
220
+ org: skill.metadata.org,
221
+ name: skill.metadata.name,
222
+ }
223
+ : null,
224
+ visibility: skill?.metadata
225
+ ? {
226
+ kind: "skill",
227
+ current: skill.metadata.visibility,
228
+ org: skill.metadata.org,
229
+ onChanged: refetch,
230
+ }
231
+ : undefined,
232
+ });
233
+
210
234
  if (isLoading) return <LoadingSkeleton className={className} />;
211
235
  if (error)
212
236
  return <ErrorMessage error={error} retry={refetch} className={className} />;
@@ -230,16 +254,16 @@ export function SkillDetailView({
230
254
  statusLabel: status ? skillStateLabel(status.state) : undefined,
231
255
  };
232
256
 
257
+ // Inline visibility is read-only (at-a-glance); editing lives in the
258
+ // Manage access dialog, the single writer for both access axes.
233
259
  const visibilityControl = meta ? (
234
- <ResourceVisibilityControl
235
- kind="skill"
236
- resourceId={meta.id}
237
- visibility={meta.visibility}
238
- org={meta.org || org}
239
- onChanged={refetch}
240
- />
260
+ <VisibilityBadge visibility={meta.visibility} />
241
261
  ) : undefined;
242
262
 
263
+ const mergedActions = access.action
264
+ ? [...(actions ?? []), access.action]
265
+ : actions;
266
+
243
267
  let tabContent: React.ReactNode;
244
268
  if (activeAdditionalTab) {
245
269
  tabContent = activeAdditionalTab.content;
@@ -267,7 +291,7 @@ export function SkillDetailView({
267
291
  header={headerMeta}
268
292
  visibilityControl={visibilityControl}
269
293
  primaryAction={primaryAction}
270
- actions={actions}
294
+ actions={mergedActions}
271
295
  tabs={effectiveTabs}
272
296
  activeTab={effectiveTabs ? effectiveActiveTab : undefined}
273
297
  onTabChange={effectiveTabs ? effectiveOnTabChange : undefined}
@@ -276,6 +300,7 @@ export function SkillDetailView({
276
300
  >
277
301
  {tabContent}
278
302
  </ResourceDetailShell>
303
+ {access.dialog}
279
304
  <SkillDiffDialog state={diffState} onClose={closeDiff} />
280
305
  </>
281
306
  );
@@ -22,7 +22,9 @@ import { WorkflowInstanceList } from "./instance/WorkflowInstanceList";
22
22
  import { WorkflowVersionsTab } from "./WorkflowVersionsTab";
23
23
  import { useWorkflowVersions } from "./useWorkflowVersions";
24
24
  import { ErrorMessage } from "../error/ErrorMessage";
25
- import { ResourceVisibilityControl } from "../library/ResourceVisibilityControl";
25
+ import { VisibilityBadge } from "../library/VisibilitySelector";
26
+ import { useManageAccess } from "../access/useManageAccess";
27
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
26
28
  import { formatDurationSec } from "./format-utils";
27
29
  import { ResourceDetailShell } from "../resource-detail/ResourceDetailShell";
28
30
  import { Section } from "../resource-detail/Section";
@@ -232,6 +234,28 @@ export function WorkflowDetailView({
232
234
  }
233
235
  }, [workflow]);
234
236
 
237
+ // Unified Manage access — visibility (General access) over explicit grants
238
+ // (People), opened from the kebab. Replaces the host's bespoke share popover.
239
+ const access = useManageAccess({
240
+ resource: workflow?.metadata
241
+ ? {
242
+ kind: ApiResourceKind.workflow,
243
+ kindString: "workflow",
244
+ id: workflow.metadata.id,
245
+ org: workflow.metadata.org || org,
246
+ name: workflow.metadata.name,
247
+ }
248
+ : null,
249
+ visibility: workflow?.metadata
250
+ ? {
251
+ kind: "workflow",
252
+ current: workflow.metadata.visibility,
253
+ org: workflow.metadata.org || org,
254
+ onChanged: refetch,
255
+ }
256
+ : undefined,
257
+ });
258
+
235
259
  if (isLoading) return <LoadingSkeleton className={className} />;
236
260
  if (error)
237
261
  return <ErrorMessage error={error} retry={refetch} className={className} />;
@@ -257,16 +281,16 @@ export function WorkflowDetailView({
257
281
  <ValidationIndicator state={validationState} />
258
282
  ) : undefined;
259
283
 
284
+ // Inline visibility is read-only (at-a-glance); editing lives in the
285
+ // Manage access dialog, the single writer for both access axes.
260
286
  const visibilityControl = meta ? (
261
- <ResourceVisibilityControl
262
- kind="workflow"
263
- resourceId={meta.id}
264
- visibility={meta.visibility}
265
- org={meta.org || org}
266
- onChanged={refetch}
267
- />
287
+ <VisibilityBadge visibility={meta.visibility} />
268
288
  ) : undefined;
269
289
 
290
+ const mergedActions = access.action
291
+ ? [...(actions ?? []), access.action]
292
+ : actions;
293
+
270
294
  let tabContent: React.ReactNode;
271
295
  if (activeAdditionalTab) {
272
296
  tabContent = activeAdditionalTab.content;
@@ -309,20 +333,23 @@ export function WorkflowDetailView({
309
333
  }
310
334
 
311
335
  return (
312
- <ResourceDetailShell
313
- header={headerMeta}
314
- visibilityControl={visibilityControl}
315
- headerMetaExtra={headerMetaExtra}
316
- primaryAction={primaryAction}
317
- actions={actions}
318
- tabs={effectiveTabs}
319
- activeTab={effectiveActiveTab}
320
- onTabChange={effectiveOnTabChange}
321
- tabsAriaLabel="Workflow detail tabs"
322
- className={className}
323
- >
324
- {tabContent}
325
- </ResourceDetailShell>
336
+ <>
337
+ <ResourceDetailShell
338
+ header={headerMeta}
339
+ visibilityControl={visibilityControl}
340
+ headerMetaExtra={headerMetaExtra}
341
+ primaryAction={primaryAction}
342
+ actions={mergedActions}
343
+ tabs={effectiveTabs}
344
+ activeTab={effectiveActiveTab}
345
+ onTabChange={effectiveOnTabChange}
346
+ tabsAriaLabel="Workflow detail tabs"
347
+ className={className}
348
+ >
349
+ {tabContent}
350
+ </ResourceDetailShell>
351
+ {access.dialog}
352
+ </>
326
353
  );
327
354
  }
328
355
 
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { memo, useMemo } from "react";
3
+ import { memo, useMemo, type ReactNode } from "react";
4
4
  import type { WorkflowExecution } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
5
5
  import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/enum_pb";
6
6
  import { cn } from "@stigmer/theme";
@@ -21,6 +21,12 @@ export interface WorkflowExecutionHeaderProps {
21
21
  readonly isDiagnosing?: boolean;
22
22
  /** Called when the user clicks "Compare with..." on a terminal execution. */
23
23
  readonly onCompare?: () => void;
24
+ /**
25
+ * Host-supplied action elements rendered at the trailing edge of the
26
+ * header (e.g. a Share control). Kept routing/auth-agnostic per DD-004 —
27
+ * the SDK renders the slot; the host owns its behavior.
28
+ */
29
+ readonly headerActions?: ReactNode;
24
30
  readonly className?: string;
25
31
  }
26
32
 
@@ -53,6 +59,7 @@ export const WorkflowExecutionHeader = memo(function WorkflowExecutionHeader({
53
59
  onDiagnose,
54
60
  isDiagnosing,
55
61
  onCompare,
62
+ headerActions,
56
63
  className,
57
64
  }: WorkflowExecutionHeaderProps) {
58
65
  const phase = execution.status?.phase ?? ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED;
@@ -160,6 +167,10 @@ export const WorkflowExecutionHeader = memo(function WorkflowExecutionHeader({
160
167
  />
161
168
  </>
162
169
  )}
170
+
171
+ {headerActions && (
172
+ <div className="relative flex items-center">{headerActions}</div>
173
+ )}
163
174
  </div>
164
175
  </header>
165
176
  );