Skip to content

Commit e621730

Browse files
Altamimi-Devgcgoncalves
authored andcommitted
feat(ui-rewrite): rename Public visibility to Internal and add level tooltips
The "Public" visibility label was misread as internet-anonymous, when it actually means "visible to everyone signed into this platform". Rename the label to "Internal" across all visibility selectors, details panels, and the MCP servers table (wire value stays "public"; DB enum, API, and access-control are untouched). Add a reusable VisibilityInfoTooltip that explains all three levels (Private, Team, Internal) next to each visibility selector and details-panel value, and swap the misleading globe icon for a building icon on the visibility rows. Signed-off-by: Ahmad Al Tamimi <altamimi.dev@gmail.com>
1 parent 10d27fa commit e621730

39 files changed

Lines changed: 187 additions & 76 deletions
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { act } from "react";
2+
import { describe, expect, it } from "vitest";
3+
4+
import { VisibilityInfoTooltip } from "./VisibilityInfoTooltip";
5+
import { renderWithProviders, screen } from "@/test/test-utils";
6+
7+
describe("VisibilityInfoTooltip", () => {
8+
it("renders a focusable info trigger", () => {
9+
renderWithProviders(<VisibilityInfoTooltip />);
10+
11+
const trigger = screen.getByRole("button", { name: "About visibility levels" });
12+
expect(trigger).toBeInTheDocument();
13+
});
14+
15+
it("explains all three visibility levels when focused", async () => {
16+
renderWithProviders(<VisibilityInfoTooltip />);
17+
18+
const trigger = screen.getByRole("button", { name: "About visibility levels" });
19+
act(() => {
20+
trigger.focus();
21+
});
22+
23+
expect((await screen.findAllByText(/^Private:/)).length).toBeGreaterThan(0);
24+
expect((await screen.findAllByText(/^Team:/)).length).toBeGreaterThan(0);
25+
expect(
26+
(await screen.findAllByText(/^Internal: Visible to everyone signed into this platform/))
27+
.length,
28+
).toBeGreaterThan(0);
29+
});
30+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Info } from "lucide-react";
2+
import { useIntl } from "react-intl";
3+
4+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
5+
import { cn } from "@/lib/utils";
6+
7+
interface VisibilityInfoTooltipProps {
8+
className?: string;
9+
side?: "top" | "right" | "bottom" | "left";
10+
}
11+
12+
/**
13+
* Info tooltip explaining the three visibility levels. The wire value "public"
14+
* is surfaced to users as "Internal" because it means "visible to everyone
15+
* signed into this platform", not "on the public internet".
16+
*/
17+
export function VisibilityInfoTooltip({ className, side = "right" }: VisibilityInfoTooltipProps) {
18+
const intl = useIntl();
19+
20+
return (
21+
<TooltipProvider>
22+
<Tooltip>
23+
<TooltipTrigger
24+
type="button"
25+
aria-label={intl.formatMessage({ id: "common.visibility.info.trigger" })}
26+
className={cn(
27+
"rounded text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
28+
className,
29+
)}
30+
>
31+
<Info className="size-3.5" aria-hidden="true" />
32+
</TooltipTrigger>
33+
<TooltipContent side={side} className="max-w-xs flex-col items-start gap-1">
34+
<p>{intl.formatMessage({ id: "common.visibility.info.private" })}</p>
35+
<p>{intl.formatMessage({ id: "common.visibility.info.team" })}</p>
36+
<p>{intl.formatMessage({ id: "common.visibility.info.internal" })}</p>
37+
</TooltipContent>
38+
</Tooltip>
39+
</TooltipProvider>
40+
);
41+
}

client/src/components/gateways/CreateServerForm.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "@/hooks/useCreateServerForm";
1212
import type { CreateServerDetails } from "@/components/gateways/types";
1313
import type { Visibility } from "@/types/server";
14+
import { VisibilityInfoTooltip } from "@/components/common/VisibilityInfoTooltip";
1415

1516
const visibilityOptions: Array<{
1617
value: Visibility;
@@ -113,8 +114,9 @@ export function CreateServerForm({
113114

114115
<div className="mt-12 grid gap-7">
115116
<fieldset className="space-y-3">
116-
<legend className="text-sm font-medium text-foreground">
117+
<legend className="flex items-center gap-1.5 text-sm font-medium text-foreground">
117118
{intl.formatMessage({ id: "gateways.createServer.visibility" })}
119+
<VisibilityInfoTooltip />
118120
</legend>
119121
<div
120122
className="grid h-10 grid-cols-3 rounded-md bg-muted p-1"

client/src/components/gateways/SourceSelection.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import {
44
Activity,
55
ArrowLeft,
66
Box,
7+
Building2,
78
ChevronDown,
89
ChevronRight,
910
CircleSlash,
10-
Globe,
1111
Lock,
1212
MessageSquareCode,
1313
Plus,
@@ -101,7 +101,7 @@ function getVisibilityConfig(visibility: ListedMCPServer["visibility"]) {
101101
case "team":
102102
return { Icon: Shield, labelId: "gateways.createServer.visibility.team" };
103103
default:
104-
return { Icon: Globe, labelId: "gateways.createServer.visibility.public" };
104+
return { Icon: Building2, labelId: "gateways.createServer.visibility.public" };
105105
}
106106
}
107107

client/src/components/gateways/VirtualServerDetailsPanel.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ describe("VirtualServerDetailsPanel render variants", () => {
255255
);
256256
});
257257

258-
it("shows the public visibility label", async () => {
258+
it("shows the internal visibility label", async () => {
259259
render(
260260
<VirtualServerDetailsPanel
261261
server={makeServer({ visibility: "public" })}
@@ -265,7 +265,7 @@ describe("VirtualServerDetailsPanel render variants", () => {
265265
onAddSources={vi.fn()}
266266
/>,
267267
);
268-
expect(await screen.findByText("Public")).toBeInTheDocument();
268+
expect(await screen.findByText("Internal")).toBeInTheDocument();
269269
});
270270

271271
it("shows the private visibility label", async () => {

client/src/components/gateways/VirtualServerDetailsPanel.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
Users,
1515
Wrench,
1616
} from "lucide-react";
17+
import { VisibilityInfoTooltip } from "@/components/common/VisibilityInfoTooltip";
1718
import { MCPIcon } from "@/components/icons/MCPIcon";
1819
import { Badge } from "@/components/ui/badge";
1920
import { Button } from "@/components/ui/button";
@@ -667,6 +668,7 @@ export function VirtualServerDetailsPanel({
667668
<span className="flex items-center gap-2">
668669
<Users className="size-3.5 text-muted-foreground" />
669670
{getVisibilityLabel(server.visibility)}
671+
<VisibilityInfoTooltip side="left" />
670672
</span>
671673
</DetailRow>
672674
<DetailRow label={intl.formatMessage({ id: "gateways.details.version" })}>

client/src/components/mcp-servers/AdvancedSettings.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, it, expect, vi, beforeEach } from "vitest";
2-
import { render, screen } from "@testing-library/react";
2+
import { renderWithProviders as render, screen } from "@/test/test-utils";
33
import userEvent from "@testing-library/user-event";
44
import * as AuthContextModule from "@/auth/AuthContext";
55
import { AdvancedSettings } from "./AdvancedSettings";

client/src/components/mcp-servers/AdvancedSettings.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { OAuth2Auth } from "@/components/mcp-servers/OAuth2Auth";
1818
import { QueryParameterAuth } from "@/components/mcp-servers/QueryParameterAuth";
1919
import { useAuthContext } from "@/auth/AuthContext";
2020
import type { Visibility } from "@/types/server";
21+
import { VisibilityInfoTooltip } from "@/components/common/VisibilityInfoTooltip";
2122

2223
export type { CustomHeader };
2324

@@ -203,12 +204,15 @@ export function AdvancedSettings({
203204
<div className="space-y-6 py-4">
204205
{/* Visibility */}
205206
<div className="space-y-3">
206-
<label
207-
htmlFor="visibility"
208-
className="text-sm font-medium text-neutral-950 dark:text-white"
209-
>
210-
Visibility
211-
</label>
207+
<div className="flex items-center gap-1.5">
208+
<label
209+
htmlFor="visibility"
210+
className="text-sm font-medium text-neutral-950 dark:text-white"
211+
>
212+
Visibility
213+
</label>
214+
<VisibilityInfoTooltip />
215+
</div>
212216
<Select value={visibility} onValueChange={onVisibilityChange}>
213217
<SelectTrigger
214218
id="visibility"
@@ -217,7 +221,7 @@ export function AdvancedSettings({
217221
<SelectValue placeholder="Select visibility" />
218222
</SelectTrigger>
219223
<SelectContent>
220-
<SelectItem value="public">Public</SelectItem>
224+
<SelectItem value="public">Internal</SelectItem>
221225
<SelectItem value="private">Private</SelectItem>
222226
<SelectItem value="team">Team</SelectItem>
223227
</SelectContent>

client/src/components/prompts/PromptDetailsPanel.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,15 +323,15 @@ describe("PromptDetailsPanel", () => {
323323
);
324324

325325
// First prompt is selected initially; the sidebar shows its visibility.
326-
expect(screen.getByText("Public")).toBeInTheDocument();
326+
expect(screen.getByText("Internal")).toBeInTheDocument();
327327
expect(screen.queryByText("Private")).not.toBeInTheDocument();
328328

329329
await user.click(screen.getByRole("tab", { name: /definition/i }));
330330
await user.click(screen.getByRole("cell", { name: "prompt_b" }));
331331

332332
// Sidebar now reflects the row that was picked.
333333
expect(screen.getByText("Private")).toBeInTheDocument();
334-
expect(screen.queryByText("Public")).not.toBeInTheDocument();
334+
expect(screen.queryByText("Internal")).not.toBeInTheDocument();
335335
});
336336

337337
it("keeps the panel title free of an overflow menu (moved to the table)", () => {

client/src/components/prompts/PromptDetailsPanel.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { useEffect, useMemo, useRef, useState } from "react";
22
import type { ReactNode } from "react";
3-
import { Activity, Globe, MessageSquareCode, PanelRightClose } from "lucide-react";
3+
import { Activity, Building2, MessageSquareCode, PanelRightClose } from "lucide-react";
44
import { useIntl } from "react-intl";
5+
import { VisibilityInfoTooltip } from "@/components/common/VisibilityInfoTooltip";
56
import type { PromptRead } from "@/generated/types";
67
import { Badge } from "@/components/ui/badge";
78
import { Button } from "@/components/ui/button";
@@ -282,14 +283,15 @@ export function PromptDetailsPanel({
282283
label={intl.formatMessage({ id: "prompts.details.label.visibility" })}
283284
>
284285
<span className="flex items-center gap-2">
285-
<Globe className="size-3.5 text-muted-foreground" />
286+
<Building2 className="size-3.5 text-muted-foreground" />
286287
{selected.visibility === "team"
287288
? intl.formatMessage({ id: "prompts.details.visibility.team" })
288289
: selected.visibility === "public"
289290
? intl.formatMessage({ id: "prompts.details.visibility.public" })
290291
: selected.visibility === "private"
291292
? intl.formatMessage({ id: "prompts.details.visibility.private" })
292293
: intl.formatMessage({ id: "prompts.details.notAvailable" })}
294+
<VisibilityInfoTooltip side="left" />
293295
</span>
294296
</DetailRow>
295297
<DetailRow

0 commit comments

Comments
 (0)