@stigmer/react 3.12.4 → 3.12.5
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/attachment/AttachmentChipList.js +16 -6
- package/attachment/AttachmentChipList.js.map +1 -1
- package/attachment/index.d.ts +1 -1
- package/attachment/index.d.ts.map +1 -1
- package/attachment/index.js.map +1 -1
- package/attachment/useAttachments.d.ts +51 -8
- package/attachment/useAttachments.d.ts.map +1 -1
- package/attachment/useAttachments.js +74 -3
- package/attachment/useAttachments.js.map +1 -1
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +17 -10
- package/composer/SessionComposer.js.map +1 -1
- package/conversation/ConversationComposer.d.ts +19 -3
- package/conversation/ConversationComposer.d.ts.map +1 -1
- package/conversation/ConversationComposer.js +4 -4
- package/conversation/ConversationComposer.js.map +1 -1
- package/conversation/ConversationTemplatePickerDialog.d.ts +63 -0
- package/conversation/ConversationTemplatePickerDialog.d.ts.map +1 -0
- package/conversation/ConversationTemplatePickerDialog.js +151 -0
- package/conversation/ConversationTemplatePickerDialog.js.map +1 -0
- package/conversation/ConversationsWorkbench.d.ts.map +1 -1
- package/conversation/ConversationsWorkbench.js +16 -5
- package/conversation/ConversationsWorkbench.js.map +1 -1
- package/conversation/index.d.ts +3 -1
- package/conversation/index.d.ts.map +1 -1
- package/conversation/index.js +1 -0
- package/conversation/index.js.map +1 -1
- package/conversation/useConversationParticipation.d.ts +41 -4
- package/conversation/useConversationParticipation.d.ts.map +1 -1
- package/conversation/useConversationParticipation.js +13 -2
- package/conversation/useConversationParticipation.js.map +1 -1
- package/identity-account/AccountPreferencesPanel.d.ts +35 -0
- package/identity-account/AccountPreferencesPanel.d.ts.map +1 -0
- package/identity-account/AccountPreferencesPanel.js +187 -0
- package/identity-account/AccountPreferencesPanel.js.map +1 -0
- package/identity-account/index.d.ts +8 -0
- package/identity-account/index.d.ts.map +1 -1
- package/identity-account/index.js +4 -0
- package/identity-account/index.js.map +1 -1
- package/identity-account/useAccountExecutionDefaults.d.ts +36 -0
- package/identity-account/useAccountExecutionDefaults.d.ts.map +1 -0
- package/identity-account/useAccountExecutionDefaults.js +46 -0
- package/identity-account/useAccountExecutionDefaults.js.map +1 -0
- package/identity-account/useMyIdentityAccount.d.ts +55 -0
- package/identity-account/useMyIdentityAccount.d.ts.map +1 -0
- package/identity-account/useMyIdentityAccount.js +37 -0
- package/identity-account/useMyIdentityAccount.js.map +1 -0
- package/identity-account/useUpdateIdentityAccount.d.ts +42 -0
- package/identity-account/useUpdateIdentityAccount.d.ts.map +1 -0
- package/identity-account/useUpdateIdentityAccount.js +53 -0
- package/identity-account/useUpdateIdentityAccount.js.map +1 -0
- package/index.d.ts +11 -9
- package/index.d.ts.map +1 -1
- package/index.js +8 -5
- package/index.js.map +1 -1
- package/internal/StandingContextField.d.ts +37 -0
- package/internal/StandingContextField.d.ts.map +1 -0
- package/internal/StandingContextField.js +24 -0
- package/internal/StandingContextField.js.map +1 -0
- package/library/InstanceVisibilitySelector.d.ts.map +1 -1
- package/library/InstanceVisibilitySelector.js +9 -2
- package/library/InstanceVisibilitySelector.js.map +1 -1
- package/library/ResourceVisibilityControl.d.ts +13 -3
- package/library/ResourceVisibilityControl.d.ts.map +1 -1
- package/library/ResourceVisibilityControl.js +33 -5
- package/library/ResourceVisibilityControl.js.map +1 -1
- package/library/VisibilityOptionRow.d.ts +6 -0
- package/library/VisibilityOptionRow.d.ts.map +1 -1
- package/library/VisibilityOptionRow.js +11 -1
- package/library/VisibilityOptionRow.js.map +1 -1
- package/library/VisibilitySelector.d.ts.map +1 -1
- package/library/VisibilitySelector.js +14 -2
- package/library/VisibilitySelector.js.map +1 -1
- package/library/index.d.ts +4 -2
- package/library/index.d.ts.map +1 -1
- package/library/index.js +2 -1
- package/library/index.js.map +1 -1
- package/library/useCanSetPublicVisibility.d.ts +30 -0
- package/library/useCanSetPublicVisibility.d.ts.map +1 -0
- package/library/useCanSetPublicVisibility.js +41 -0
- package/library/useCanSetPublicVisibility.js.map +1 -0
- package/library/visibilityLevels.d.ts +32 -1
- package/library/visibilityLevels.d.ts.map +1 -1
- package/library/visibilityLevels.js +34 -14
- package/library/visibilityLevels.js.map +1 -1
- package/mcp-server/useMcpServerConnect.d.ts +8 -5
- package/mcp-server/useMcpServerConnect.d.ts.map +1 -1
- package/mcp-server/useMcpServerConnect.js +2 -1
- package/mcp-server/useMcpServerConnect.js.map +1 -1
- package/mcp-server/useMcpServerOAuthConnect.d.ts.map +1 -1
- package/mcp-server/useMcpServerOAuthConnect.js +7 -2
- package/mcp-server/useMcpServerOAuthConnect.js.map +1 -1
- package/oauth-app/CreateOAuthAppForm.d.ts +2 -2
- package/oauth-app/CreateOAuthAppForm.d.ts.map +1 -1
- package/oauth-app/CreateOAuthAppForm.js +13 -4
- package/oauth-app/CreateOAuthAppForm.js.map +1 -1
- package/oauth-app/OAuthAppDetailPanel.d.ts.map +1 -1
- package/oauth-app/OAuthAppDetailPanel.js +32 -5
- package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
- package/organization/OrgPreferencesPanel.d.ts +32 -0
- package/organization/OrgPreferencesPanel.d.ts.map +1 -0
- package/organization/OrgPreferencesPanel.js +90 -0
- package/organization/OrgPreferencesPanel.js.map +1 -0
- package/organization/OrgProfilePanel.d.ts.map +1 -1
- package/organization/OrgProfilePanel.js +5 -4
- package/organization/OrgProfilePanel.js.map +1 -1
- package/organization/index.d.ts +2 -0
- package/organization/index.d.ts.map +1 -1
- package/organization/index.js +1 -0
- package/organization/index.js.map +1 -1
- package/package.json +4 -4
- package/session/NewSessionViewer.d.ts +10 -1
- package/session/NewSessionViewer.d.ts.map +1 -1
- package/session/NewSessionViewer.js +2 -1
- package/session/NewSessionViewer.js.map +1 -1
- package/session/useNewSessionFlow.d.ts +27 -1
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +45 -8
- package/session/useNewSessionFlow.js.map +1 -1
- package/settings/AccountPreferencesSection.d.ts +3 -0
- package/settings/AccountPreferencesSection.d.ts.map +1 -0
- package/settings/AccountPreferencesSection.js +10 -0
- package/settings/AccountPreferencesSection.js.map +1 -0
- package/settings/OrgPreferencesSection.d.ts +3 -0
- package/settings/OrgPreferencesSection.d.ts.map +1 -0
- package/settings/OrgPreferencesSection.js +13 -0
- package/settings/OrgPreferencesSection.js.map +1 -0
- package/settings/index.d.ts +2 -0
- package/settings/index.d.ts.map +1 -1
- package/settings/index.js +2 -0
- package/settings/index.js.map +1 -1
- package/settings/settings-nav.d.ts.map +1 -1
- package/settings/settings-nav.js +17 -1
- package/settings/settings-nav.js.map +1 -1
- package/src/attachment/AttachmentChipList.tsx +19 -8
- package/src/attachment/__tests__/AttachmentChipList.test.tsx +12 -0
- package/src/attachment/__tests__/useAttachments.test.ts +293 -0
- package/src/attachment/index.ts +1 -0
- package/src/attachment/useAttachments.ts +143 -11
- package/src/composer/SessionComposer.tsx +23 -14
- package/src/conversation/ConversationComposer.tsx +38 -4
- package/src/conversation/ConversationTemplatePickerDialog.tsx +441 -0
- package/src/conversation/ConversationsWorkbench.tsx +40 -4
- package/src/conversation/__tests__/ConversationComposer.test.tsx +56 -1
- package/src/conversation/__tests__/ConversationTemplatePickerDialog.test.tsx +224 -0
- package/src/conversation/__tests__/useConversationParticipation.test.tsx +45 -4
- package/src/conversation/index.ts +4 -0
- package/src/conversation/useConversationParticipation.ts +58 -6
- package/src/identity-account/AccountPreferencesPanel.tsx +446 -0
- package/src/identity-account/__tests__/AccountPreferencesPanel.test.tsx +233 -0
- package/src/identity-account/__tests__/useAccountExecutionDefaults.test.tsx +116 -0
- package/src/identity-account/__tests__/useMyIdentityAccount.test.tsx +121 -0
- package/src/identity-account/index.ts +11 -0
- package/src/identity-account/useAccountExecutionDefaults.ts +63 -0
- package/src/identity-account/useMyIdentityAccount.ts +75 -0
- package/src/identity-account/useUpdateIdentityAccount.ts +73 -0
- package/src/index.ts +24 -2
- package/src/internal/StandingContextField.tsx +92 -0
- package/src/library/InstanceVisibilitySelector.tsx +9 -2
- package/src/library/ResourceVisibilityControl.tsx +46 -13
- package/src/library/VisibilityOptionRow.tsx +28 -6
- package/src/library/VisibilitySelector.tsx +14 -2
- package/src/library/__tests__/VisibilitySelector.test.tsx +48 -1
- package/src/library/__tests__/visibilityLevels.test.ts +67 -1
- package/src/library/index.ts +6 -1
- package/src/library/useCanSetPublicVisibility.ts +56 -0
- package/src/library/visibilityLevels.ts +65 -14
- package/src/mcp-server/__tests__/useMcpServerConnect.asyncLane.test.tsx +117 -0
- package/src/mcp-server/useMcpServerConnect.ts +9 -6
- package/src/mcp-server/useMcpServerOAuthConnect.ts +7 -2
- package/src/oauth-app/CreateOAuthAppForm.tsx +58 -3
- package/src/oauth-app/OAuthAppDetailPanel.tsx +84 -3
- package/src/organization/OrgPreferencesPanel.tsx +221 -0
- package/src/organization/OrgProfilePanel.tsx +5 -4
- package/src/organization/__tests__/OrgPreferencesPanel.test.tsx +145 -0
- package/src/organization/__tests__/OrgProfilePanel.test.tsx +108 -0
- package/src/organization/index.ts +2 -0
- package/src/session/NewSessionViewer.tsx +12 -0
- package/src/session/__tests__/useNewSessionFlow.test.tsx +151 -0
- package/src/session/useNewSessionFlow.ts +76 -9
- package/src/settings/AccountPreferencesSection.tsx +25 -0
- package/src/settings/OrgPreferencesSection.tsx +35 -0
- package/src/settings/__tests__/settings-nav.test.ts +27 -0
- package/src/settings/index.ts +2 -0
- package/src/settings/settings-nav.ts +18 -0
- package/src/workflow/ApprovalFormBuilder.tsx +5 -27
- package/styles.css +1 -1
- package/workflow/ApprovalFormBuilder.d.ts.map +1 -1
- package/workflow/ApprovalFormBuilder.js +8 -5
- package/workflow/ApprovalFormBuilder.js.map +1 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { cleanup, renderHook, waitFor } from "@testing-library/react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
|
+
import {
|
|
6
|
+
IdentityAccountSchema,
|
|
7
|
+
type IdentityAccount,
|
|
8
|
+
} from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
9
|
+
import type { DeploymentMode } from "@stigmer/sdk";
|
|
10
|
+
import { StigmerContext } from "../../context";
|
|
11
|
+
import { DeploymentModeContext } from "../../deployment-mode";
|
|
12
|
+
import { useAccountExecutionDefaults } from "../useAccountExecutionDefaults";
|
|
13
|
+
|
|
14
|
+
function accountWithPreferences(
|
|
15
|
+
preferences: Record<string, string>,
|
|
16
|
+
): IdentityAccount {
|
|
17
|
+
return create(IdentityAccountSchema, {
|
|
18
|
+
metadata: { id: "ia-1", name: "Ada", slug: "ada", org: "acme" },
|
|
19
|
+
spec: { idpId: "auth0|abc", preferences },
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function createWrapper(whoAmI: ReturnType<typeof vi.fn>, mode: DeploymentMode = "cloud") {
|
|
24
|
+
const client = { identityAccount: { whoAmI } } as never;
|
|
25
|
+
return function Wrapper({ children }: { children: ReactNode }) {
|
|
26
|
+
return (
|
|
27
|
+
<StigmerContext.Provider value={client}>
|
|
28
|
+
<DeploymentModeContext.Provider value={mode}>
|
|
29
|
+
{children}
|
|
30
|
+
</DeploymentModeContext.Provider>
|
|
31
|
+
</StigmerContext.Provider>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
afterEach(cleanup);
|
|
37
|
+
|
|
38
|
+
describe("useAccountExecutionDefaults", () => {
|
|
39
|
+
it("returns undefined in local mode without issuing any RPC", async () => {
|
|
40
|
+
const whoAmI = vi.fn();
|
|
41
|
+
const { result } = renderHook(() => useAccountExecutionDefaults(), {
|
|
42
|
+
wrapper: createWrapper(whoAmI, "local"),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Settle any pending effects before asserting the no-RPC invariant.
|
|
46
|
+
await waitFor(() => expect(result.current).toBeUndefined());
|
|
47
|
+
expect(whoAmI).not.toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("shapes declared defaults for the launcher's accountDefaults prop", async () => {
|
|
51
|
+
const whoAmI = vi.fn(async () =>
|
|
52
|
+
accountWithPreferences({
|
|
53
|
+
defaultHarness: "cursor",
|
|
54
|
+
defaultNativeModel: "claude-sonnet-4.6",
|
|
55
|
+
defaultCursorModel: "composer-2.5",
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
const { result } = renderHook(() => useAccountExecutionDefaults(), {
|
|
59
|
+
wrapper: createWrapper(whoAmI),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await waitFor(() =>
|
|
63
|
+
expect(result.current).toEqual({
|
|
64
|
+
harness: "cursor",
|
|
65
|
+
nativeModel: "claude-sonnet-4.6",
|
|
66
|
+
cursorModel: "composer-2.5",
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("returns undefined while loading and when nothing is declared", async () => {
|
|
72
|
+
const whoAmI = vi.fn(async () =>
|
|
73
|
+
accountWithPreferences({ standingContext: "Keep answers terse." }),
|
|
74
|
+
);
|
|
75
|
+
const { result } = renderHook(() => useAccountExecutionDefaults(), {
|
|
76
|
+
wrapper: createWrapper(whoAmI),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(result.current).toBeUndefined();
|
|
80
|
+
await waitFor(() => expect(whoAmI).toHaveBeenCalled());
|
|
81
|
+
// standing_context alone declares no execution default.
|
|
82
|
+
expect(result.current).toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("treats an unknown persisted harness value as undeclared", async () => {
|
|
86
|
+
const whoAmI = vi.fn(async () =>
|
|
87
|
+
accountWithPreferences({
|
|
88
|
+
defaultHarness: "devin",
|
|
89
|
+
defaultNativeModel: "claude-sonnet-4.6",
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
const { result } = renderHook(() => useAccountExecutionDefaults(), {
|
|
93
|
+
wrapper: createWrapper(whoAmI),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await waitFor(() =>
|
|
97
|
+
expect(result.current).toEqual({
|
|
98
|
+
harness: undefined,
|
|
99
|
+
nativeModel: "claude-sonnet-4.6",
|
|
100
|
+
cursorModel: undefined,
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("returns undefined on a whoAmI failure — degrades to platform defaults", async () => {
|
|
106
|
+
const whoAmI = vi.fn(async () => {
|
|
107
|
+
throw new Error("boom");
|
|
108
|
+
});
|
|
109
|
+
const { result } = renderHook(() => useAccountExecutionDefaults(), {
|
|
110
|
+
wrapper: createWrapper(whoAmI),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
await waitFor(() => expect(whoAmI).toHaveBeenCalled());
|
|
114
|
+
expect(result.current).toBeUndefined();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import { create } from "@bufbuild/protobuf";
|
|
4
|
+
import {
|
|
5
|
+
IdentityAccountSchema,
|
|
6
|
+
type IdentityAccount,
|
|
7
|
+
} from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
8
|
+
import type { IdentityAccountInput } from "@stigmer/sdk";
|
|
9
|
+
import { StigmerContext } from "../../context";
|
|
10
|
+
import { useMyIdentityAccount } from "../useMyIdentityAccount";
|
|
11
|
+
import { useUpdateIdentityAccount } from "../useUpdateIdentityAccount";
|
|
12
|
+
|
|
13
|
+
const ACCOUNT: IdentityAccount = create(IdentityAccountSchema, {
|
|
14
|
+
metadata: { id: "ia-1", name: "Ada", slug: "ada", org: "acme" },
|
|
15
|
+
spec: { preferences: { standingContext: "Keep answers terse." } },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/** Renders the data hook and exposes its state via data attributes. */
|
|
19
|
+
function FetchProbe() {
|
|
20
|
+
const { account, isLoading, error, refetch } = useMyIdentityAccount();
|
|
21
|
+
return (
|
|
22
|
+
<button
|
|
23
|
+
type="button"
|
|
24
|
+
data-testid="probe"
|
|
25
|
+
data-loading={String(isLoading)}
|
|
26
|
+
data-error={String(error !== null)}
|
|
27
|
+
data-context={account?.spec?.preferences?.standingContext ?? ""}
|
|
28
|
+
onClick={refetch}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function UpdateProbe({ input }: { input: IdentityAccountInput }) {
|
|
34
|
+
const { update, isUpdating, error } = useUpdateIdentityAccount();
|
|
35
|
+
return (
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
data-testid="probe"
|
|
39
|
+
data-updating={String(isUpdating)}
|
|
40
|
+
data-error={String(error !== null)}
|
|
41
|
+
onClick={() => void update(input).catch(() => undefined)}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function withClient(client: unknown, ui: React.ReactNode) {
|
|
47
|
+
return render(
|
|
48
|
+
<StigmerContext.Provider value={client as never}>
|
|
49
|
+
{ui}
|
|
50
|
+
</StigmerContext.Provider>,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
afterEach(cleanup);
|
|
55
|
+
|
|
56
|
+
describe("useMyIdentityAccount", () => {
|
|
57
|
+
it("fetches the caller's account via whoAmI and supports refetch", async () => {
|
|
58
|
+
const whoAmI = vi.fn(async () => ACCOUNT);
|
|
59
|
+
withClient({ identityAccount: { whoAmI } }, <FetchProbe />);
|
|
60
|
+
|
|
61
|
+
await waitFor(() =>
|
|
62
|
+
expect(screen.getByTestId("probe").getAttribute("data-context")).toBe(
|
|
63
|
+
"Keep answers terse.",
|
|
64
|
+
),
|
|
65
|
+
);
|
|
66
|
+
expect(whoAmI).toHaveBeenCalledTimes(1);
|
|
67
|
+
|
|
68
|
+
fireEvent.click(screen.getByTestId("probe"));
|
|
69
|
+
await waitFor(() => expect(whoAmI).toHaveBeenCalledTimes(2));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("surfaces errors as state", async () => {
|
|
73
|
+
const whoAmI = vi.fn(async () => {
|
|
74
|
+
throw new Error("boom");
|
|
75
|
+
});
|
|
76
|
+
withClient({ identityAccount: { whoAmI } }, <FetchProbe />);
|
|
77
|
+
|
|
78
|
+
await waitFor(() =>
|
|
79
|
+
expect(screen.getByTestId("probe").getAttribute("data-error")).toBe(
|
|
80
|
+
"true",
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("useUpdateIdentityAccount", () => {
|
|
87
|
+
it("submits the input through identityAccount.update", async () => {
|
|
88
|
+
const update = vi.fn(async (_input: IdentityAccountInput) => ACCOUNT);
|
|
89
|
+
const input: IdentityAccountInput = {
|
|
90
|
+
name: "Ada",
|
|
91
|
+
org: "acme",
|
|
92
|
+
slug: "ada",
|
|
93
|
+
idpId: "auth0|abc",
|
|
94
|
+
preferences: { standingContext: "New" },
|
|
95
|
+
};
|
|
96
|
+
withClient({ identityAccount: { update } }, <UpdateProbe input={input} />);
|
|
97
|
+
|
|
98
|
+
fireEvent.click(screen.getByTestId("probe"));
|
|
99
|
+
|
|
100
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
101
|
+
expect(update.mock.calls[0]![0]).toEqual(input);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("captures update failures in error state", async () => {
|
|
105
|
+
const update = vi.fn(async () => {
|
|
106
|
+
throw new Error("denied");
|
|
107
|
+
});
|
|
108
|
+
withClient(
|
|
109
|
+
{ identityAccount: { update } },
|
|
110
|
+
<UpdateProbe input={{ name: "Ada", org: "acme", idpId: "x" }} />,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
fireEvent.click(screen.getByTestId("probe"));
|
|
114
|
+
|
|
115
|
+
await waitFor(() =>
|
|
116
|
+
expect(screen.getByTestId("probe").getAttribute("data-error")).toBe(
|
|
117
|
+
"true",
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -3,3 +3,14 @@ export {
|
|
|
3
3
|
type IdentityAccountGateState,
|
|
4
4
|
type UseIdentityAccountGateReturn,
|
|
5
5
|
} from "./useIdentityAccountGate.js";
|
|
6
|
+
export { useMyIdentityAccount } from "./useMyIdentityAccount.js";
|
|
7
|
+
export type {
|
|
8
|
+
UseMyIdentityAccountOptions,
|
|
9
|
+
UseMyIdentityAccountReturn,
|
|
10
|
+
} from "./useMyIdentityAccount.js";
|
|
11
|
+
export { useAccountExecutionDefaults } from "./useAccountExecutionDefaults.js";
|
|
12
|
+
export type { AccountExecutionDefaults } from "./useAccountExecutionDefaults.js";
|
|
13
|
+
export { useUpdateIdentityAccount } from "./useUpdateIdentityAccount.js";
|
|
14
|
+
export type { UseUpdateIdentityAccountReturn } from "./useUpdateIdentityAccount.js";
|
|
15
|
+
export { AccountPreferencesPanel } from "./AccountPreferencesPanel.js";
|
|
16
|
+
export type { AccountPreferencesPanelProps } from "./AccountPreferencesPanel.js";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { useMyIdentityAccount } from "./useMyIdentityAccount.js";
|
|
5
|
+
import { useResourceAvailable, ApiResourceKind } from "../deployment-mode.js";
|
|
6
|
+
import type { HarnessOption } from "../models/harness.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The current user's account-level execution defaults, shaped for the
|
|
10
|
+
* session launcher's `accountDefaults` prop.
|
|
11
|
+
*/
|
|
12
|
+
export interface AccountExecutionDefaults {
|
|
13
|
+
/** Preferred harness for new sessions, when declared. */
|
|
14
|
+
readonly harness?: HarnessOption;
|
|
15
|
+
/** Preferred model for native-harness sessions, when declared. */
|
|
16
|
+
readonly nativeModel?: string;
|
|
17
|
+
/** Preferred model for cursor-harness sessions, when declared. */
|
|
18
|
+
readonly cursorModel?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Data hook that reads the current user's execution defaults
|
|
23
|
+
* (`IdentityAccountSpec.preferences.default_*`) for seeding new-session
|
|
24
|
+
* composers.
|
|
25
|
+
*
|
|
26
|
+
* Returns `undefined` in local mode (no IdentityAccount), while loading,
|
|
27
|
+
* on error, and when no default is declared — every one of those cases
|
|
28
|
+
* degrades to the platform's existing defaults, so consumers wire the
|
|
29
|
+
* result straight through:
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* <NewSessionViewer org={org} accountDefaults={useAccountExecutionDefaults()} />
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* The defaults are a SEED, not an override: an explicit device-local pick
|
|
37
|
+
* outranks them (the layered precedence in `useNewSessionFlow`), and the
|
|
38
|
+
* chosen values ride the execution spec explicitly. Rides
|
|
39
|
+
* `useMyIdentityAccount`'s cross-mount cache, so the seed is available
|
|
40
|
+
* synchronously on every visit after the first.
|
|
41
|
+
*/
|
|
42
|
+
export function useAccountExecutionDefaults(): AccountExecutionDefaults | undefined {
|
|
43
|
+
const available = useResourceAvailable(ApiResourceKind.identity_account);
|
|
44
|
+
const { account } = useMyIdentityAccount({ enabled: available });
|
|
45
|
+
|
|
46
|
+
return useMemo(() => {
|
|
47
|
+
const prefs = account?.spec?.preferences;
|
|
48
|
+
if (!prefs) return undefined;
|
|
49
|
+
|
|
50
|
+
// The proto validates default_harness to the shipped set, but a client
|
|
51
|
+
// must not trust persisted data it did not write — unknown values are
|
|
52
|
+
// treated as undeclared.
|
|
53
|
+
const harness =
|
|
54
|
+
prefs.defaultHarness === "native" || prefs.defaultHarness === "cursor"
|
|
55
|
+
? (prefs.defaultHarness satisfies HarnessOption)
|
|
56
|
+
: undefined;
|
|
57
|
+
const nativeModel = prefs.defaultNativeModel || undefined;
|
|
58
|
+
const cursorModel = prefs.defaultCursorModel || undefined;
|
|
59
|
+
|
|
60
|
+
if (!harness && !nativeModel && !cursorModel) return undefined;
|
|
61
|
+
return { harness, nativeModel, cursorModel };
|
|
62
|
+
}, [account]);
|
|
63
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { IdentityAccount } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
4
|
+
import { useStigmer } from "../hooks.js";
|
|
5
|
+
import { useFetch } from "../internal/useFetch.js";
|
|
6
|
+
|
|
7
|
+
/** Options for {@link useMyIdentityAccount}. */
|
|
8
|
+
export interface UseMyIdentityAccountOptions {
|
|
9
|
+
/**
|
|
10
|
+
* When `false`, the hook is idle: no RPC is issued and `account` stays
|
|
11
|
+
* `null`. Lets always-called hooks (React's rules) skip the doomed
|
|
12
|
+
* whoAmI against a local server — pass
|
|
13
|
+
* `useResourceAvailable(ApiResourceKind.identity_account)` here.
|
|
14
|
+
*
|
|
15
|
+
* @default true
|
|
16
|
+
*/
|
|
17
|
+
readonly enabled?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Return value of {@link useMyIdentityAccount}. */
|
|
21
|
+
export interface UseMyIdentityAccountReturn {
|
|
22
|
+
/** The authenticated user's identity account, or `null` while loading / on error. */
|
|
23
|
+
readonly account: IdentityAccount | null;
|
|
24
|
+
/** `true` while the initial fetch is in flight. */
|
|
25
|
+
readonly isLoading: boolean;
|
|
26
|
+
/** `true` while a background refetch is in flight and stale data is shown. */
|
|
27
|
+
readonly isRefetching: boolean;
|
|
28
|
+
/** Error from the last failed request, or `null` when healthy. */
|
|
29
|
+
readonly error: Error | null;
|
|
30
|
+
/** Re-fetch the account from the server (e.g. after an update). */
|
|
31
|
+
readonly refetch: () => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Data hook that fetches the current authenticated user's full
|
|
36
|
+
* {@link IdentityAccount} via `identityAccount.whoAmI()`.
|
|
37
|
+
*
|
|
38
|
+
* Unlike {@link useWhoAmI} (a cached identity lookup for "is this me?"
|
|
39
|
+
* checks), this hook is a refetchable data source for editors of the
|
|
40
|
+
* caller's own account — after a mutation, call `refetch()` to re-sync.
|
|
41
|
+
* `whoAmI()` returns the complete resource, so no follow-up `get()` is
|
|
42
|
+
* needed.
|
|
43
|
+
*
|
|
44
|
+
* Cloud-only by nature: the OSS local server does not implement
|
|
45
|
+
* IdentityAccount. Gate consumers with
|
|
46
|
+
* `useResourceAvailable(ApiResourceKind.identity_account)` — either by
|
|
47
|
+
* conditional mounting or via {@link UseMyIdentityAccountOptions.enabled}.
|
|
48
|
+
*
|
|
49
|
+
* Cached across mounts under a {@link FetchCacheProvider} (DD-014): a
|
|
50
|
+
* revisit renders the previous result immediately and refetches in the
|
|
51
|
+
* background, so consumers that seed UI from the account (e.g. composer
|
|
52
|
+
* defaults) settle synchronously after the first visit.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* const { account, refetch } = useMyIdentityAccount();
|
|
57
|
+
* const standingContext =
|
|
58
|
+
* account?.spec?.preferences?.standingContext ?? "";
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export function useMyIdentityAccount(
|
|
62
|
+
options?: UseMyIdentityAccountOptions,
|
|
63
|
+
): UseMyIdentityAccountReturn {
|
|
64
|
+
const stigmer = useStigmer();
|
|
65
|
+
const enabled = options?.enabled ?? true;
|
|
66
|
+
|
|
67
|
+
const { data: account, isLoading, isRefetching, error, refetch } = useFetch(
|
|
68
|
+
enabled ? () => stigmer.identityAccount.whoAmI() : null,
|
|
69
|
+
[stigmer, enabled],
|
|
70
|
+
null as IdentityAccount | null,
|
|
71
|
+
{ cacheKey: enabled ? "identity-account:me" : undefined },
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return { account, isLoading, isRefetching, error, refetch };
|
|
75
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import type { IdentityAccountInput } from "@stigmer/sdk";
|
|
5
|
+
import type { IdentityAccount } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
6
|
+
import { useStigmer } from "../hooks.js";
|
|
7
|
+
import { toError } from "../internal/toError.js";
|
|
8
|
+
|
|
9
|
+
/** Return value of {@link useUpdateIdentityAccount}. */
|
|
10
|
+
export interface UseUpdateIdentityAccountReturn {
|
|
11
|
+
/** Submit an {@link IdentityAccountInput} to update an identity account. Resolves with the updated resource. */
|
|
12
|
+
readonly update: (input: IdentityAccountInput) => Promise<IdentityAccount>;
|
|
13
|
+
/** `true` while the update request is in flight. */
|
|
14
|
+
readonly isUpdating: boolean;
|
|
15
|
+
/** Error from the last failed update, or `null` when healthy. */
|
|
16
|
+
readonly error: Error | null;
|
|
17
|
+
/** Reset `error` to `null`. */
|
|
18
|
+
readonly clearError: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Mutation hook that wraps `identityAccount.update()` with loading and
|
|
23
|
+
* error state.
|
|
24
|
+
*
|
|
25
|
+
* The update RPC is a full-spec replacement addressed by `org` + `slug`
|
|
26
|
+
* (the generated input carries no id). Build the input from the loaded
|
|
27
|
+
* resource with `toIdentityAccountUpdateInput` from `@stigmer/sdk` and
|
|
28
|
+
* override only the fields being edited — sending a partial input wipes
|
|
29
|
+
* the unsent spec fields. The same rule applies INSIDE nested messages:
|
|
30
|
+
* override `preferences` by spreading the mapper's complete value first,
|
|
31
|
+
* or the untouched preference fields wipe.
|
|
32
|
+
*
|
|
33
|
+
* Self-service: a user can always update their own account (`can_edit`
|
|
34
|
+
* via the self-ownership relationship written at account creation).
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* const { update, isUpdating, error } = useUpdateIdentityAccount();
|
|
39
|
+
*
|
|
40
|
+
* const mapped = toIdentityAccountUpdateInput(account);
|
|
41
|
+
* await update({
|
|
42
|
+
* ...mapped,
|
|
43
|
+
* preferences: { ...mapped.preferences, standingContext: "Keep answers terse." },
|
|
44
|
+
* });
|
|
45
|
+
* refetch(); // re-sync the editor
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function useUpdateIdentityAccount(): UseUpdateIdentityAccountReturn {
|
|
49
|
+
const stigmer = useStigmer();
|
|
50
|
+
const [isUpdating, setIsUpdating] = useState(false);
|
|
51
|
+
const [error, setError] = useState<Error | null>(null);
|
|
52
|
+
|
|
53
|
+
const clearError = useCallback(() => setError(null), []);
|
|
54
|
+
|
|
55
|
+
const update = useCallback(
|
|
56
|
+
async (input: IdentityAccountInput): Promise<IdentityAccount> => {
|
|
57
|
+
setIsUpdating(true);
|
|
58
|
+
setError(null);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
return await stigmer.identityAccount.update(input);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
setError(toError(err));
|
|
64
|
+
throw err;
|
|
65
|
+
} finally {
|
|
66
|
+
setIsUpdating(false);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
[stigmer],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return { update, isUpdating, error, clearError };
|
|
73
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -502,6 +502,7 @@ export {
|
|
|
502
502
|
visionPreflightMessage,
|
|
503
503
|
} from "./attachment/index.js";
|
|
504
504
|
export type {
|
|
505
|
+
AddFilesOptions,
|
|
505
506
|
AttachmentPhase,
|
|
506
507
|
AttachmentEntry,
|
|
507
508
|
AssessVisionPreflightOptions,
|
|
@@ -833,13 +834,23 @@ export type {
|
|
|
833
834
|
ToolCredentialsReadiness,
|
|
834
835
|
} from "./environment/index.js";
|
|
835
836
|
|
|
836
|
-
// Identity Account — gate hook
|
|
837
|
+
// Identity Account — gate hook, self-account data/mutation hooks, the
|
|
838
|
+
// account preferences editor, and the composer-seeding execution defaults
|
|
837
839
|
export {
|
|
838
840
|
useIdentityAccountGate,
|
|
841
|
+
useMyIdentityAccount,
|
|
842
|
+
useAccountExecutionDefaults,
|
|
843
|
+
useUpdateIdentityAccount,
|
|
844
|
+
AccountPreferencesPanel,
|
|
839
845
|
} from "./identity-account/index.js";
|
|
840
846
|
export type {
|
|
841
847
|
IdentityAccountGateState,
|
|
842
848
|
UseIdentityAccountGateReturn,
|
|
849
|
+
UseMyIdentityAccountOptions,
|
|
850
|
+
UseMyIdentityAccountReturn,
|
|
851
|
+
AccountExecutionDefaults,
|
|
852
|
+
UseUpdateIdentityAccountReturn,
|
|
853
|
+
AccountPreferencesPanelProps,
|
|
843
854
|
} from "./identity-account/index.js";
|
|
844
855
|
|
|
845
856
|
// IAM Policy — data hooks, behavior hooks, headless hook, and styled components for access management
|
|
@@ -921,6 +932,7 @@ export {
|
|
|
921
932
|
useUpdateOrganization,
|
|
922
933
|
CreateOrganizationForm,
|
|
923
934
|
OrgProfilePanel,
|
|
935
|
+
OrgPreferencesPanel,
|
|
924
936
|
OrgSwitcher,
|
|
925
937
|
} from "./organization/index.js";
|
|
926
938
|
export type {
|
|
@@ -933,6 +945,7 @@ export type {
|
|
|
933
945
|
UseUpdateOrganizationReturn,
|
|
934
946
|
CreateOrganizationFormProps,
|
|
935
947
|
OrgProfilePanelProps,
|
|
948
|
+
OrgPreferencesPanelProps,
|
|
936
949
|
OrgSwitcherProps,
|
|
937
950
|
} from "./organization/index.js";
|
|
938
951
|
|
|
@@ -1065,6 +1078,8 @@ export type { SettingsNavItem, SettingsNavGroup } from "./settings/index.js";
|
|
|
1065
1078
|
export { ApiKeysSection } from "./settings/index.js";
|
|
1066
1079
|
export { MembersSection } from "./settings/index.js";
|
|
1067
1080
|
export { OrgProfileSection } from "./settings/index.js";
|
|
1081
|
+
export { OrgPreferencesSection } from "./settings/index.js";
|
|
1082
|
+
export { AccountPreferencesSection } from "./settings/index.js";
|
|
1068
1083
|
export { EnvironmentsSection } from "./settings/index.js";
|
|
1069
1084
|
export { InvitationsSection } from "./settings/index.js";
|
|
1070
1085
|
export { IdentityProvidersSection } from "./settings/index.js";
|
|
@@ -1335,6 +1350,7 @@ export {
|
|
|
1335
1350
|
ConversationControlBanner,
|
|
1336
1351
|
ConversationAttentionBanner,
|
|
1337
1352
|
ConversationComposer,
|
|
1353
|
+
ConversationTemplatePickerDialog,
|
|
1338
1354
|
useConversation,
|
|
1339
1355
|
useConversationList,
|
|
1340
1356
|
useConversationMediaUrl,
|
|
@@ -1364,6 +1380,8 @@ export type {
|
|
|
1364
1380
|
ConversationControlBannerProps,
|
|
1365
1381
|
ConversationAttentionBannerProps,
|
|
1366
1382
|
ConversationComposerProps,
|
|
1383
|
+
ConversationTemplatePickerDialogProps,
|
|
1384
|
+
ConversationReplyPayload,
|
|
1367
1385
|
UseConversationOptions,
|
|
1368
1386
|
UseConversationReturn,
|
|
1369
1387
|
UseConversationListOptions,
|
|
@@ -1422,9 +1440,11 @@ export {
|
|
|
1422
1440
|
VisibilitySelector,
|
|
1423
1441
|
VisibilityBadge,
|
|
1424
1442
|
blueprintVisibilityLevels,
|
|
1425
|
-
|
|
1443
|
+
instanceVisibilityLevels,
|
|
1444
|
+
PUBLIC_LOCKED_REASON,
|
|
1426
1445
|
visibilityLabel,
|
|
1427
1446
|
useUpdateVisibility,
|
|
1447
|
+
useCanSetPublicVisibility,
|
|
1428
1448
|
} from "./library/index.js";
|
|
1429
1449
|
export type {
|
|
1430
1450
|
ScopeToggleProps,
|
|
@@ -1444,8 +1464,10 @@ export type {
|
|
|
1444
1464
|
VisibilityBadgeProps,
|
|
1445
1465
|
VisibilityLevelOption,
|
|
1446
1466
|
BlueprintVisibilityLevelsContext,
|
|
1467
|
+
InstanceVisibilityLevelsContext,
|
|
1447
1468
|
VisibilityResourceKind,
|
|
1448
1469
|
UseUpdateVisibilityReturn,
|
|
1470
|
+
UseCanSetPublicVisibilityReturn,
|
|
1449
1471
|
} from "./library/index.js";
|
|
1450
1472
|
|
|
1451
1473
|
// Manifest — kind-agnostic YAML edit/apply (editor, hooks, dialogs)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { cn } from "@stigmer/theme";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Both `OrganizationPreferences.standing_context` and
|
|
8
|
+
* `IdentityAccountPreferences.standing_context` pin `max_len = 2000`
|
|
9
|
+
* (buf.validate, spec.proto in each package).
|
|
10
|
+
*/
|
|
11
|
+
export const STANDING_CONTEXT_MAX_LEN = 2000;
|
|
12
|
+
/** Show the remaining-characters counter once within this margin. */
|
|
13
|
+
const COUNTER_THRESHOLD = 200;
|
|
14
|
+
|
|
15
|
+
/** Props for {@link StandingContextField}. */
|
|
16
|
+
export interface StandingContextFieldProps {
|
|
17
|
+
/** DOM id for the textarea (label association). */
|
|
18
|
+
readonly id: string;
|
|
19
|
+
/** Current field value. */
|
|
20
|
+
readonly value: string;
|
|
21
|
+
/** Fired with the new value on every edit. */
|
|
22
|
+
readonly onChange: (value: string) => void;
|
|
23
|
+
/** Disables editing while a save is in flight. */
|
|
24
|
+
readonly disabled?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Renders the field as read-only (still selectable/copyable) for
|
|
27
|
+
* viewers without edit permission.
|
|
28
|
+
*/
|
|
29
|
+
readonly readOnly?: boolean;
|
|
30
|
+
/** Example text shown when the field is empty. */
|
|
31
|
+
readonly placeholder?: string;
|
|
32
|
+
/** Helper line rendered under the textarea. */
|
|
33
|
+
readonly helperText?: ReactNode;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Shared textarea for declared-preference standing context — the one
|
|
38
|
+
* free-text field both the organization and account preference editors
|
|
39
|
+
* render. Owns the proto length cap and the remaining-characters
|
|
40
|
+
* counter; the parent panel owns label, load/save, and dirty state.
|
|
41
|
+
*
|
|
42
|
+
* Internal to `@stigmer/react` — the public surface is the panels.
|
|
43
|
+
*/
|
|
44
|
+
export function StandingContextField({
|
|
45
|
+
id,
|
|
46
|
+
value,
|
|
47
|
+
onChange,
|
|
48
|
+
disabled,
|
|
49
|
+
readOnly,
|
|
50
|
+
placeholder,
|
|
51
|
+
helperText,
|
|
52
|
+
}: StandingContextFieldProps) {
|
|
53
|
+
const remaining = STANDING_CONTEXT_MAX_LEN - value.length;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="stg:space-y-1">
|
|
57
|
+
<label
|
|
58
|
+
htmlFor={id}
|
|
59
|
+
className="stg:text-xs stg:font-medium stg:text-foreground"
|
|
60
|
+
>
|
|
61
|
+
Standing context
|
|
62
|
+
</label>
|
|
63
|
+
<textarea
|
|
64
|
+
id={id}
|
|
65
|
+
value={value}
|
|
66
|
+
onChange={(e) => onChange(e.target.value)}
|
|
67
|
+
maxLength={STANDING_CONTEXT_MAX_LEN}
|
|
68
|
+
rows={6}
|
|
69
|
+
disabled={disabled}
|
|
70
|
+
readOnly={readOnly}
|
|
71
|
+
placeholder={readOnly ? undefined : placeholder}
|
|
72
|
+
className={cn(
|
|
73
|
+
"stg:w-full stg:resize-y stg:rounded-md stg:border stg:border-input stg:bg-background stg:px-2.5 stg:py-1.5 stg:text-xs stg:text-foreground",
|
|
74
|
+
"stg:placeholder:text-muted-foreground",
|
|
75
|
+
"stg:focus-visible:outline-none stg:focus-visible:ring-1 stg:focus-visible:ring-ring",
|
|
76
|
+
"stg:disabled:pointer-events-none stg:disabled:opacity-50",
|
|
77
|
+
readOnly && "stg:bg-muted-subtle stg:text-muted-foreground",
|
|
78
|
+
)}
|
|
79
|
+
/>
|
|
80
|
+
{helperText && (
|
|
81
|
+
<p className="stg:text-[0.65rem] stg:text-muted-foreground">
|
|
82
|
+
{helperText}
|
|
83
|
+
</p>
|
|
84
|
+
)}
|
|
85
|
+
{!readOnly && remaining <= COUNTER_THRESHOLD && (
|
|
86
|
+
<p className="stg:text-[0.65rem] stg:text-muted-foreground">
|
|
87
|
+
{remaining} characters left
|
|
88
|
+
</p>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
VisibilitySelector,
|
|
6
6
|
type VisibilitySelectorMode,
|
|
7
7
|
} from "./VisibilitySelector.js";
|
|
8
|
-
import {
|
|
8
|
+
import { useCanSetPublicVisibility } from "./useCanSetPublicVisibility.js";
|
|
9
|
+
import { instanceVisibilityLevels } from "./visibilityLevels.js";
|
|
9
10
|
|
|
10
11
|
/** Props for {@link InstanceVisibilitySelector}. */
|
|
11
12
|
export interface InstanceVisibilitySelectorProps {
|
|
@@ -69,10 +70,16 @@ export function InstanceVisibilitySelector({
|
|
|
69
70
|
disabled = false,
|
|
70
71
|
className,
|
|
71
72
|
}: InstanceVisibilitySelectorProps) {
|
|
73
|
+
// Public is operator-granted in the cloud edition; the hook resolves the
|
|
74
|
+
// caller's grant here so every consumer (create dialogs, list rows) gets
|
|
75
|
+
// the locked Public row without wiring anything.
|
|
76
|
+
const canSetPublic = useCanSetPublicVisibility();
|
|
72
77
|
return (
|
|
73
78
|
<VisibilitySelector
|
|
74
79
|
visibility={visibility}
|
|
75
|
-
options={
|
|
80
|
+
options={instanceVisibilityLevels({
|
|
81
|
+
canSetPublicVisibility: canSetPublic.allowed,
|
|
82
|
+
})}
|
|
76
83
|
onVisibilityChange={onVisibilityChange}
|
|
77
84
|
mode={mode}
|
|
78
85
|
isPending={isPending}
|