@stigmer/react 3.0.7 → 3.0.8-dev.20260612073024

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 (133) 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 +8 -6
  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 +4 -4
  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 +31 -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/session/__tests__/execution-target.test.ts +18 -0
  94. package/src/skill/SkillDetailView.tsx +34 -9
  95. package/src/workflow/WorkflowDetailView.tsx +49 -22
  96. package/src/workflow/WorkflowExecutionHeader.tsx +12 -1
  97. package/src/workflow/WorkflowExecutionViewer.tsx +8 -1
  98. package/src/workflow/index.ts +4 -0
  99. package/src/workflow/instance/RunVisibilityControl.tsx +116 -0
  100. package/src/workflow/instance/WorkflowInstanceDetailPanel.tsx +55 -42
  101. package/src/workflow/instance/index.ts +5 -0
  102. package/src/workflow/instance/useUpdateWorkflowInstanceExecutionVisibility.ts +74 -0
  103. package/styles.css +1 -1
  104. package/workflow/WorkflowDetailView.d.ts.map +1 -1
  105. package/workflow/WorkflowDetailView.js +31 -3
  106. package/workflow/WorkflowDetailView.js.map +1 -1
  107. package/workflow/WorkflowExecutionHeader.d.ts +7 -0
  108. package/workflow/WorkflowExecutionHeader.d.ts.map +1 -1
  109. package/workflow/WorkflowExecutionHeader.js +2 -2
  110. package/workflow/WorkflowExecutionHeader.js.map +1 -1
  111. package/workflow/WorkflowExecutionViewer.d.ts +6 -1
  112. package/workflow/WorkflowExecutionViewer.d.ts.map +1 -1
  113. package/workflow/WorkflowExecutionViewer.js +2 -2
  114. package/workflow/WorkflowExecutionViewer.js.map +1 -1
  115. package/workflow/index.d.ts +1 -1
  116. package/workflow/index.d.ts.map +1 -1
  117. package/workflow/index.js +1 -1
  118. package/workflow/index.js.map +1 -1
  119. package/workflow/instance/RunVisibilityControl.d.ts +25 -0
  120. package/workflow/instance/RunVisibilityControl.d.ts.map +1 -0
  121. package/workflow/instance/RunVisibilityControl.js +56 -0
  122. package/workflow/instance/RunVisibilityControl.js.map +1 -0
  123. package/workflow/instance/WorkflowInstanceDetailPanel.d.ts.map +1 -1
  124. package/workflow/instance/WorkflowInstanceDetailPanel.js +30 -4
  125. package/workflow/instance/WorkflowInstanceDetailPanel.js.map +1 -1
  126. package/workflow/instance/index.d.ts +2 -0
  127. package/workflow/instance/index.d.ts.map +1 -1
  128. package/workflow/instance/index.js +2 -0
  129. package/workflow/instance/index.js.map +1 -1
  130. package/workflow/instance/useUpdateWorkflowInstanceExecutionVisibility.d.ts +30 -0
  131. package/workflow/instance/useUpdateWorkflowInstanceExecutionVisibility.d.ts.map +1 -0
  132. package/workflow/instance/useUpdateWorkflowInstanceExecutionVisibility.js +39 -0
  133. 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
@@ -150,6 +150,7 @@ export type {
150
150
  SearchResultGroup,
151
151
  SessionViewerProps,
152
152
  NewSessionViewerProps,
153
+ ExecutionTargetOption,
153
154
  RuntimeEnvProvider,
154
155
  SessionAudience,
155
156
  SessionInspectorProps,
@@ -588,7 +589,11 @@ export {
588
589
  useShareFlow,
589
590
  useCheckPermission,
590
591
  RoleSelector,
592
+ PrincipalPicker,
593
+ ProviderBadge,
594
+ providerLabel,
591
595
  GrantAccessForm,
596
+ PeopleWithAccess,
592
597
  OrgMembersPanel,
593
598
  SharePanel,
594
599
  PermissionGate,
@@ -610,17 +615,39 @@ export type {
610
615
  UseCheckPermissionReturn,
611
616
  PermissionCheckResource,
612
617
  RoleSelectorProps,
618
+ PrincipalPickerProps,
619
+ SelectedPrincipal,
620
+ ProviderBadgeProps,
613
621
  GrantAccessFormProps,
622
+ PeopleWithAccessProps,
614
623
  OrgMembersPanelProps,
615
624
  SharePanelProps,
616
625
  PermissionGateProps,
617
626
  } from "./iam-policy";
618
627
 
628
+ // Access — unified "Manage access" experience (visibility + people) as one
629
+ // dialog, with a kebab hook and a visible-button trigger.
630
+ export {
631
+ ManageAccessDialog,
632
+ ManageAccessButton,
633
+ useManageAccess,
634
+ } from "./access";
635
+ export type {
636
+ ManageAccessDialogProps,
637
+ ManageAccessButtonProps,
638
+ UseManageAccessArgs,
639
+ UseManageAccessReturn,
640
+ AccessResource,
641
+ AccessVisibility,
642
+ AccessExtraSection,
643
+ } from "./access";
644
+
619
645
  // Organization — context provider, hooks, data hooks, behavior hooks, styled form, profile panel, and org switcher
620
646
  export {
621
647
  OrgProvider,
622
648
  useOrg,
623
649
  useActiveOrgSlug,
650
+ useActiveOrgId,
624
651
  useOrgGate,
625
652
  useOrganization,
626
653
  useCreateOrganization,
@@ -1240,11 +1267,13 @@ export {
1240
1267
  useWorkflowInstance,
1241
1268
  useCreateWorkflowInstance,
1242
1269
  useUpdateWorkflowInstance,
1270
+ useUpdateWorkflowInstanceExecutionVisibility,
1243
1271
  useDeleteWorkflowInstance,
1244
1272
  WorkflowInstanceEmptyState,
1245
1273
  WorkflowInstanceList,
1246
1274
  CreateWorkflowInstanceDialog,
1247
1275
  WorkflowInstanceDetailPanel,
1276
+ RunVisibilityControl,
1248
1277
  // T15: Workflow Template Gallery
1249
1278
  PATTERN_LABELS,
1250
1279
  WORKFLOW_CATEGORY_LABELS,
@@ -1367,11 +1396,13 @@ export type {
1367
1396
  UseWorkflowInstanceReturn,
1368
1397
  UseCreateWorkflowInstanceReturn,
1369
1398
  UseUpdateWorkflowInstanceReturn,
1399
+ UseUpdateWorkflowInstanceExecutionVisibilityReturn,
1370
1400
  UseDeleteWorkflowInstanceReturn,
1371
1401
  WorkflowInstanceEmptyStateProps,
1372
1402
  WorkflowInstanceListProps,
1373
1403
  CreateWorkflowInstanceDialogProps,
1374
1404
  WorkflowInstanceDetailPanelProps,
1405
+ RunVisibilityControlProps,
1375
1406
  // T15: Workflow Template Gallery types
1376
1407
  WorkflowTemplateData,
1377
1408
  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 {
@@ -5,6 +5,14 @@ import {
5
5
  fromProtoExecutionTarget,
6
6
  type ExecutionTargetOption,
7
7
  } from "../execution-target";
8
+ // Public-API contract guard (issue #171): ExecutionTargetOption must stay
9
+ // re-exported from the package root so consumers can type the
10
+ // StigmerProvider `executionTarget` prop. This guarantee is enforced at
11
+ // compile time by `tsc` (npm run typecheck) — vitest runs through esbuild and
12
+ // erases type-only imports, so removing the root re-export would NOT fail the
13
+ // test run, only the typecheck. A type-only import keeps the test fast (no
14
+ // root-barrel evaluation at runtime).
15
+ import type { ExecutionTargetOption as RootExecutionTargetOption } from "../../index";
8
16
 
9
17
  describe("toProtoExecutionTarget", () => {
10
18
  it("maps local to ExecutionTarget.LOCAL", () => {
@@ -43,3 +51,13 @@ describe("round-trip conversion", () => {
43
51
  expect(fromProtoExecutionTarget(toProtoExecutionTarget("cloud"))).toBe("cloud");
44
52
  });
45
53
  });
54
+
55
+ describe("package root export", () => {
56
+ it("re-exports ExecutionTargetOption from the package root (issue #171)", () => {
57
+ // Assigning through the root-imported type forces `tsc` to resolve the
58
+ // root re-export; if it is dropped from src/index.ts, typecheck fails
59
+ // (TS2305) in CI. The runtime assertion keeps this a real test too.
60
+ const target: RootExecutionTargetOption = "local";
61
+ expect(toProtoExecutionTarget(target)).toBe(ExecutionTarget.LOCAL);
62
+ });
63
+ });
@@ -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