Skip to content

feat: improve namespace selector UI/UX#2161

Merged
wilsonrivera merged 23 commits intomainfrom
wilson/eng-7026-default-namespace-is-causing-uxui-confusion
Sep 16, 2025
Merged

feat: improve namespace selector UI/UX#2161
wilsonrivera merged 23 commits intomainfrom
wilson/eng-7026-default-namespace-is-causing-uxui-confusion

Conversation

@wilsonrivera
Copy link
Copy Markdown
Contributor

@wilsonrivera wilsonrivera commented Aug 22, 2025

Summary by CodeRabbit

  • New Features

    • Workspace view & API to list namespaces, graphs, and subgraphs; workspace-aware pickers, navigation, and refreshed CLI snippets; workspace refresh after creating a graph.
    • New lightweight Skeleton UI component.
  • Refactor

    • Studio now uses a centralized workspace context/provider; legacy namespace/graph popovers removed and layouts/breadcrumbs simplified.
  • Bug Fixes

    • Subgraph listings respect RBAC and support multi-namespace filtering.

Checklist

namespace-selector.mov

On top of the new namespace selector, I've gone thru all files that retrieve the namespace and organization slug from the query parameters to construct a new URL and replaced them with using the current namespace and the current organization slug for the authenticated user

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 22, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds a GetWorkspace RPC and workspace proto messages, regenerates connect code, implements server handler assembling namespaces→graphs→subgraphs, updates repositories to support multi-namespace filters, and introduces a WorkspaceContext/provider, hook, and UI components; Studio is migrated from router/user namespace resolution to workspace-driven context.

Changes

Cohort / File(s) Summary
Proto API
proto/wg/cosmo/platform/v1/platform.proto
Adds GetWorkspace RPC and messages: WorkspaceNamespace, WorkspaceFederatedGraph, WorkspaceSubgraph, GetWorkspaceRequest, GetWorkspaceResponse.
Generated proto & connect
connect/src/wg/cosmo/platform/v1/platform_pb.ts, connect/src/wg/cosmo/platform/v1/platform_connect.ts, connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts
Generated workspace message types and adds getWorkspace method descriptor and imports for request/response types.
Server handler & wiring
controlplane/src/core/bufservices/PlatformService.ts, controlplane/src/core/bufservices/workspace/getWorkspace.ts
Adds getWorkspace function and wires it into PlatformService to build workspace view for authenticated users.
Repository & type changes (multi-namespace)
controlplane/src/core/repositories/FederatedGraphRepository.ts, controlplane/src/core/repositories/SubgraphRepository.ts, controlplane/src/core/bufservices/federated-graph/getFederatedGraphs.ts, controlplane/src/core/bufservices/subgraph/getSubgraphs.ts, controlplane/src/types/index.ts
Replaces single namespaceId with namespaceIds?: string[], applies inArray filtering, adapts callers; adds RBAC filter to some subgraph listings.
Namespace deletion adaptions
controlplane/src/core/bufservices/namespace/deleteNamespace.ts
Uses namespaceIds array when listing federated graphs/subgraphs during deletion flow.
Workspace provider & hook
studio/src/components/dashboard/workspace-provider.tsx, studio/src/hooks/use-workspace.ts
Adds WorkspaceContext, WorkspaceProvider, WorkspaceContextType, and useWorkspace() hook enforcing provider presence.
Workspace UI components
studio/src/components/dashboard/workspace-command-wrapper.tsx, studio/src/components/dashboard/namespace-selector.tsx, studio/src/components/dashboard/graph-command-group.tsx, studio/src/components/dashboard/graph-selector.tsx, studio/src/components/dashboard/workspace-selector.tsx
New components for workspace/namespace/graph/subgraph selection, Fuse-based filtering, and WorkspaceSelector integrated with context.
App provider & query client
studio/src/components/app-provider.tsx
Moves to WorkspaceProvider, switches to useQueryClient usage, removes global QueryClientProvider, and adjusts query reset flow.
Studio migration — components
Multiple studio/src/components/... files (analytics, toolbar, metrics, changelog, checks, feature flags, lint policy, overview, etc.)
Replace router/user-derived namespace/organizationSlug with useWorkspace()/useCurrentOrganization(); update links, queries, CLI strings; small onError param cleanups.
Studio migration — pages
Multiple studio/src/pages/[organizationSlug]/[namespace]/... (graph/, subgraph/, feature-flags, graphs, subgraphs, cache-warmer, policies, etc.)
Replace route-derived namespace/org with workspace/context-derived values; update queries, navigation, layouts to use WorkspaceSelector; remove some "default" fallbacks.
Layout & selector removals
studio/src/components/layout/graph-layout.tsx, studio/src/components/layout/subgraph-layout.tsx, studio/src/components/layout/title-layout.tsx, studio/src/components/dashboard/NamespaceSelector.tsx
Remove legacy GraphSelect/SubgraphSelect and old NamespaceSelector; integrate WorkspaceSelector into breadcrumbs; expand SubgraphContext shape; minor CSS tweak.
Utilities & UI additions
studio/src/components/ui/skeleton.tsx, studio/src/lib/utils.ts
Adds Skeleton component and distinctBy utility.
Miscellaneous small edits
Various controlplane & studio files (e.g., controlplane/src/core/bufservices/federated-graph/getFederatedGraphById.ts, many studio files)
RBAC filter added to subgraph listing; many components now derive organizationSlug via useCurrentOrganization(); minor error-parameter renames and small adjustments.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: improve namespace selector UI/UX" is short, single-sentence, and accurately captures the primary work described in the PR (introducing a new namespace/workspace selector and migrating components to use workspace context), without noisy file lists or vague terms. It aligns with the PR objectives and the bulk of changes shown in the summary.

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: improve namespace selector UI/UX" is short, single-sentence, and accurately captures the primary work described in the PR (introducing a new namespace/workspace selector and migrating components to use workspace context), without noisy file lists or vague terms. It aligns with the PR objectives and the bulk of changes shown in the summary.

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Aug 22, 2025

Router-nonroot image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-41797fbdd315d6090dfa3951e98d30dfc1d51b1c-nonroot

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Sep 6, 2025

This PR was marked stale due to lack of activity. It will be closed in 14 days.

@github-actions github-actions Bot added the Stale label Sep 6, 2025
@github-actions github-actions Bot removed the Stale label Sep 10, 2025
…-uxui-confusion

# Conflicts:
#	connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go
#	connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts
#	connect/src/wg/cosmo/platform/v1/platform_connect.ts
#	controlplane/src/core/bufservices/PlatformService.ts
#	studio/src/pages/[organizationSlug]/[namespace]/subgraph/[subgraphSlug]/index.tsx
@wilsonrivera wilsonrivera marked this pull request as ready for review September 10, 2025 22:23
@wilsonrivera wilsonrivera requested review from a team as code owners September 10, 2025 22:23
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 23

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (17)
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/cache-operations.tsx (1)

173-176: Fix grammar in user-facing copy

“operation have” → “operations have”; and consider “at planning time”.

-            operation have priority over the top 100 operations computed by
-            planning time.{" "}
+            operations have priority over the top 100 operations computed at
+            planning time.{" "}
studio/src/components/checks/changes-table.tsx (1)

291-307: Don’t create Links with missing dynamic params

If organizationSlug/namespace are undefined, Next.js can complain. Disable the button or fall back to router.query until ready.

-              <Button
-                disabled={!path}
+              <Button
+                disabled={!path || !organizationSlug || !namespace}
                 variant="ghost"
                 size="icon-sm"
                 asChild
                 className="table-action"
               >
                 <Link
                   href={
                     path
                       ? {
                           pathname: `/[organizationSlug]/[namespace]/graph/[slug]/schema`,
                           query: {
                             organizationSlug,
                             namespace,
                             slug: router.query.slug,
                             typename: path?.split(".")?.[0],
                           },
                         }
                       : "#"
                   }
                 >
                   <GlobeIcon />
                 </Link>
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/compositions/[compositionId]/feature-flag/[featureFlagCompositionId]/index.tsx (1)

88-90: Fix typo in tab query key
Update “ffCompostions” to “ffCompositions” in all instances to prevent broken deep links:

  • studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/compositions/[compositionId]/index.tsx (lines 522, 528, 711)
  • studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/compositions/[compositionId]/feature-flag/[featureFlagCompositionId]/index.tsx (line 88)
studio/src/components/lint-policy/linter-config.tsx (1)

195-199: Fix typo in success toast

“succesfully” → “successfully”. User-facing string.

-                          description: "Lint Policy applied succesfully.",
+                          description: "Lint Policy applied successfully.",
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/proposals/index.tsx (1)

104-110: Fix “Configure Proposals” link to use workspace namespace

This still uses router.query.namespace, which can diverge from the workspace selection.

-              router.push(
-                `/${user?.currentOrganization.slug}/policies?namespace=${router.query.namespace}#proposals`,
-              );
+              router.push(
+                `/${user?.currentOrganization.slug}/policies?namespace=${namespace}#proposals`,
+              );
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/checks/[checkId]/index.tsx (1)

1503-1544: Force Success button renders only when already forced + missing namespace in one call

Currently the “Force Success” action appears when isForcedSuccess is true and is hidden otherwise, and one invocation omits namespace. Invert the condition and include namespace to avoid a no-op/broken mutation.

-                        {data.check.isForcedSuccess ? (
+                        {!data.check.isForcedSuccess ? (
                           <div className="mt-2 flex space-x-2">
                             <ForceSuccess
                               onSubmit={() =>
                                 forceSuccess({
                                   checkId: id,
                                   graphName: slug,
-                                  namespace,
+                                  namespace,
                                 })
                               }
                             />
                           </div>
                         ) : null}
@@
-                        {data.check.isForcedSuccess ? (
+                        {!data.check.isForcedSuccess ? (
                           <div className="mt-2 flex space-x-2">
                             <ForceSuccess
                               onSubmit={() =>
                                 forceSuccess({
                                   checkId: id,
                                   graphName: slug,
+                                  namespace,
                                 })
                               }
                             />
                           </div>
                         ) : null}

Also applies to: 1558-1568

studio/src/components/analytics/field-usage.tsx (1)

264-267: Handle missing end date in dateRange JSON

dateRange.end can be undefined; mirror the earlier fallback used in onDateRangeChange.

-                                              end: formatISO(dateRange.end),
+                                              end: formatISO(dateRange.end ?? dateRange.start),
studio/src/pages/[organizationSlug]/feature-flags/[featureFlagSlug]/index.tsx (1)

61-70: Breadcrumb may render with an invalid org segment

organizationSlug can be undefined on first render; the breadcrumb href becomes "/undefined/...". Prefer a router fallback.

-const FeatureFlagBreadcrumb = () => {
-  const organizationSlug = useCurrentOrganization()?.slug;
-  const { namespace: { name: namespace } } = useWorkspace();
+const FeatureFlagBreadcrumb = () => {
+  const router = useRouter();
+  const organizationSlug =
+    useCurrentOrganization()?.slug ??
+    (router.query.organizationSlug as string | undefined);
+  const { namespace: { name: namespace } } = useWorkspace();
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/compositions/[compositionId]/index.tsx (1)

730-738: Consistent org slug usage in page-level queries and links

Mirror the same router fallback here to keep the page consistent.

-  const organizationSlug = useCurrentOrganization()?.slug;
+  const organizationSlug =
+    useCurrentOrganization()?.slug ??
+    (router.query.organizationSlug as string | undefined);
studio/src/pages/[organizationSlug]/cache-warmer.tsx (2)

63-63: Fix user-facing typo in title

“namesapce” → “namespace”.

-        title="Could not retrieve the cache warmer config of the namesapce"
+        title="Could not retrieve the cache warmer config of the namespace"

99-103: Fix copy typos and consistency

  • “opeartions” → “operations”
  • “cacheWarmer” → “cache warmer”
-                ? "Enable cache warmer to warm the router with your top opeartions."
-                : "Upgrade your billing plan to use this cacheWarmer."}{" "}
+                ? "Enable cache warmer to warm the router with your top operations."
+                : "Upgrade your billing plan to use the cache warmer."}{" "}
studio/src/components/checks/lint-issues-table.tsx (1)

124-136: Harden the “View” link (guard and encode subgraph)

  • Only render when organizationSlug is present.
  • URL-encode subgraphName to avoid broken links.
-                        <Link
-                          href={`/${organizationSlug}/${
-                            namespace
-                          }/graph/${router.query.slug}/checks/${
-                            router.query.checkId
-                          }?tab=schema&${
-                            l.subgraphName
-                              ? `subgraph=${l.subgraphName}`
-                              : ""
-                          }${
-                            l.issueLocation?.line
-                              ? `#L${l.issueLocation?.line}`
-                              : ""
-                          }`}
-                        >
-                          View
-                        </Link>
+                        {organizationSlug && (
+                          <Link
+                            href={`/${organizationSlug}/${namespace}/graph/${router.query.slug}/checks/${router.query.checkId}?tab=schema&${
+                              l.subgraphName ? `subgraph=${encodeURIComponent(l.subgraphName)}` : ""
+                            }${l.issueLocation?.line ? `#L${l.issueLocation.line}` : ""}`}
+                          >
+                            View
+                          </Link>
+                        )}
studio/src/components/analytics/metrics.tsx (1)

268-287: Gate the header link until route params are available

Prevents broken navigation and preserves UX.

-                <Link
-                  href={{
-                    pathname: `${router.pathname}/traces`,
-                    query: {
-                      organizationSlug,
-                      namespace,
-                      slug: router.query.slug,
-                      filterState: router.query.filterState || "[]",
-                      range,
-                      dateRange,
-                      ...queryParams,
-                    },
-                  }}
-                  className="inline-flex rounded-md px-2 py-1 hover:bg-muted"
-                >
-                  {title}
-                  <ChevronRightIcon className="h4 ml-1 w-4 transition-all group-hover:ml-2" />
-                </Link>
+                {organizationSlug && namespace ? (
+                  <Link
+                    href={{
+                      pathname: `${router.pathname}/traces`,
+                      query: {
+                        organizationSlug,
+                        namespace,
+                        slug: router.query.slug,
+                        filterState: router.query.filterState || "[]",
+                        range,
+                        dateRange,
+                        ...queryParams,
+                      },
+                    }}
+                    className="inline-flex rounded-md px-2 py-1 hover:bg-muted"
+                  >
+                    {title}
+                    <ChevronRightIcon className="h4 ml-1 w-4 transition-all group-hover:ml-2" />
+                  </Link>
+                ) : (
+                  <span className="inline-flex rounded-md px-2 py-1 text-muted-foreground">
+                    {title}
+                  </span>
+                )}
studio/src/components/layout/graph-layout.tsx (2)

77-90: Avoid providing undefined GraphContext while children render.

graphContextData is undefined until graphsData arrives, but children are rendered once the single graph query succeeds. This risks consumers reading an undefined context.

Consider gating render on both results:

const { data: graphsData, isLoading: graphsLoading } = useQuery(getFederatedGraphs);
...
if (isLoading || graphsLoading || !graphsData) {
  render = <Loader fullscreen />;
} else if (error || data?.response?.code !== EnumStatusCode.OK) {
  ...
} else {
  render = <GraphContext.Provider value={graphContextData}>{children}</GraphContext.Provider>;
}

92-181: Return empty nav until routing/workspace are ready to prevent invalid hrefs.

When org/namespace/slug aren’t ready, SideNav should temporarily render no links.

Apply this diff:

   const links: NavLink[] = useMemo(() => {
+    if (!organizationSlug || !namespace || !slug) {
+      return [];
+    }
     const basePath = `/${organizationSlug}/${namespace}/graph/${slug}`;
studio/src/components/layout/subgraph-layout.tsx (2)

62-89: Return empty nav until routing/workspace are ready.

Prevents SideNav hrefs like "/undefined/...".

Apply this diff:

   const links: NavLink[] = useMemo(() => {
-    const basePath = `/${organizationSlug}/${namespace}/subgraph/${slug}`;
+    if (!organizationSlug || !namespace || !slug) {
+      return [];
+    }
+    const basePath = `/${organizationSlug}/${namespace}/subgraph/${slug}`;

117-117: Typo in Tailwind class: duplicated lg: prefix breaks grid on large screens.

Fixes layout on lg+.

Apply this diff:

-      <div className="flex min-h-screen w-full flex-1 flex-col bg-background font-sans antialiased lg:grid lg:lg:grid-cols-[auto_minmax(10px,1fr)] lg:divide-x">
+      <div className="flex min-h-screen w-full flex-1 flex-col bg-background font-sans antialiased lg:grid lg:grid-cols-[auto_minmax(10px,1fr)] lg:divide-x">
🧹 Nitpick comments (62)
studio/src/lib/utils.ts (1)

15-26: Constrain key type and accept readonly arrays to avoid object-identity footguns.

Using Set with non-primitive keys compares by reference; constraining to PropertyKey prevents surprises and makes intent clear. Also allow ReadonlyArray input.

-export function distinctBy<T, TKey>(source: T[], keySelector: (item: T) => TKey) {
-  const keys = new Set<TKey>();
-  return source.filter((item) => {
-    const key = keySelector(item);
-    if (keys.has(key)) {
-      return false;
-    }
-
-    keys.add(key);
-    return true;
-  });
-}
+export function distinctBy<T, K extends PropertyKey>(
+  source: readonly T[],
+  keySelector: (item: T) => K
+): T[] {
+  const seen = new Set<K>();
+  return source.filter((item) => {
+    const key = keySelector(item);
+    if (seen.has(key)) return false;
+    seen.add(key);
+    return true;
+  });
+}
studio/src/components/ui/skeleton.tsx (1)

1-13: forwardRef + aria-hidden for better ergonomics and a11y.

This enables refs for composition (e.g., animations) and hides the decorative skeleton from AT.

-import { cn } from "@/lib/utils"
+import * as React from "react"
+import { cn } from "@/lib/utils"
 
-function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="skeleton"
-      className={cn("bg-accent animate-pulse rounded-md", className)}
-      {...props}
-    />
-  )
-}
+const Skeleton = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
+  ({ className, ...props }, ref) => (
+    <div
+      ref={ref}
+      data-slot="skeleton"
+      aria-hidden="true"
+      className={cn("bg-accent animate-pulse rounded-md", className)}
+      {...props}
+    />
+  )
+)
+Skeleton.displayName = "Skeleton"
 
 export { Skeleton }
studio/src/components/changelog/changelog.tsx (1)

38-38: Encode route params and avoid rendering a bad href until data is ready.

Prevent “/undefined/…” links and ensure safe URL construction.

-                    href={`/${organizationSlug}/${namespace}/graph/${slug}/compositions/${compositionId}`}
+                    href={`/${encodeURIComponent(organizationSlug!)}/${encodeURIComponent(namespaceName!)}/graph/${encodeURIComponent(slug!)}/compositions/${encodeURIComponent(String(compositionId))}`}

Optional (outside this line): gate the link until values exist to avoid relying on non-null assertions.

const isReady = !!(organizationSlug && namespaceName && slug && compositionId);
// ...
<Link
  href={
    isReady
      ? `/${encodeURIComponent(organizationSlug)}/${encodeURIComponent(namespaceName)}/graph/${encodeURIComponent(slug)}/compositions/${encodeURIComponent(String(compositionId))}`
      : "#"
  }
  aria-disabled={!isReady}
  // prefetch={false} // consider if many rows to reduce network churn
  className="flex items-center gap-x-1 text-sm text-primary hover:underline"
>
controlplane/src/types/index.ts (1)

41-41: Clarify/guard precedence between namespaceId vs. namespaceIds.

Today namespaceId silently overrides namespaceIds (see FederatedGraphRepository.list). Consider documenting this in the type or making them mutually exclusive at the type level to avoid ambiguous callers.

Example doc tweak:

 export interface ListFilterOptions {
   namespaceId?: string;
+  /** If provided, used only when `namespaceId` is not set. */
   namespaceIds?: string[];
   limit: number;
   offset: number;
   query?: string;
 }
controlplane/src/core/repositories/FederatedGraphRepository.ts (1)

509-513: Multi-namespace filter LGTM; add small guard/tests.

Behavior is clear: namespaceId takes precedence; else uses inArray(namespaceIds). Consider guarding against whitespace-only IDs and add a test for both fields present (namespaceId wins).

studio/src/components/dashboard/namespace-selector.tsx (5)

27-31: Pathname heuristic is brittle; prefer intent-based check.

Length-based split can misclassify routes. Detect “[namespace]” explicitly and fall back otherwise.

-  const pathname = useMemo(
-    () => router.pathname.split('/').length === 3 ? router.pathname : '/[organizationSlug]/graphs',
-    [router.pathname]
-  );
+  const pathname = useMemo(() => {
+    return router.pathname.includes("[namespace]")
+      ? router.pathname
+      : "/[organizationSlug]/graphs";
+  }, [router.pathname]);

55-61: Comment/code mismatch when resetting filter.

Code resets on close, but the comment says “when opened”. Either change the comment or reset on open (recommended).

-        onOpenChange={(v) => {
-          setOpen(v);
-          if (!v) {
-            // Only reset the filter when the popover is opened
-            setFilter('');
-          }
-        }}
+        onOpenChange={(v) => {
+          setOpen(v);
+          if (v) {
+            // Reset filter on open so users start fresh
+            setFilter('');
+          }
+        }}

32-33: Stable, readable list ordering.

Sort namespace names for consistent UX.

-  const namespaces = Array.from(namespaceByName.keys());
+  const namespaces = useMemo(
+    () => Array.from(namespaceByName.keys()).sort((a, b) => a.localeCompare(b)),
+    [namespaceByName]
+  );

94-101: Harden external link.

Add rel="noreferrer noopener" to the external docs link.

-              <Link
+              <Link
                 target="_blank"
-                className="text-primary"
+                rel="noreferrer noopener"
+                className="text-primary"
                 href={`${docsBaseURL}/cli/essentials#namespaces`}
               >

114-121: Set workspace first to avoid UI flicker.

Update workspace state before navigation so dependent components render immediately.

-                    onSelect={() => {
-                      router.push({
+                    onSelect={() => {
+                      setNamespace(ns, false);
+                      router.push({
                         pathname,
                         query: { organizationSlug, namespace: ns },
                       });
-
-                      setOpen(false);
-                      setNamespace(ns, false);
+                      setOpen(false);
                     }}>
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/compositions/index.tsx (2)

55-55: Avoid shadowing “namespace” and reduce brittle destructuring

Using a nested destructure + alias can break if the shape changes and shadows a common identifier. Prefer accessing namespace.name explicitly.

-const { namespace: { name: namespace } } = useWorkspace();
+const { namespace } = useWorkspace();

69-70: Pass the resolved name explicitly

Match the tweak above by sending namespace.name to the query input.

-      namespace,
+      namespace: namespace.name,
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/schema/index.tsx (1)

1411-1412: Prefer explicit accessors and optional gating

Minor readability: avoid aliasing to “namespace” string; also consider exposing isLoading to gate initial fetches if needed.

-const { namespace: { name: namespace } } = useWorkspace();
+const { isLoading, namespace } = useWorkspace();

Follow-up: use namespace.name where referenced in this file (e.g., request payload). If you see premature queries during workspace load, set enabled: !isLoading on the useQuery.

studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/checks/index.tsx (2)

75-75: Slightly simplify namespace access

Avoid aliasing to a common identifier and prefer direct property access.

-const { namespace: { name: namespace } } = useWorkspace();
+const { namespace } = useWorkspace();

92-93: Propagate the explicit name in requests

Keep the request self-documenting by sending namespace.name.

-      namespace,
+      namespace: namespace.name,
studio/src/hooks/use-workspace.ts (1)

4-11: Solid, typed hook with safety guard

Nice typed wrapper with an explicit error if the provider is missing. Consider adding a short JSDoc for discoverability.

 export function useWorkspace(): WorkspaceContextType {
+  /** Access the current WorkspaceContext. Throws if used outside <WorkspaceProvider>. */
   const context = useContext(WorkspaceContext);
controlplane/src/core/bufservices/PlatformService.ts (1)

794-797: Expose GetWorkspace on PlatformService

Good addition; mirrors other resolvers’ structure. Consider keeping method ordering consistent (grouping/alphabetical) to ease future diffs.

studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/changelog/index.tsx (1)

103-104: Pass explicit namespace name

Align with the simpler access pattern.

-      namespace,
+      namespace: namespace,

If you adopt the earlier suggestion (const { namespace } = useWorkspace()), then use namespace.name here:

-      namespace,
+      namespace: namespace.name,
studio/src/components/schema/empty-schema-state.tsx (1)

11-11: Simplify namespace access

Minor readability nit.

-const { namespace: { name: namespace } } = useWorkspace();
+const { namespace } = useWorkspace();
studio/src/components/feature-flags-table.tsx (2)

30-30: Simplify namespace access

Prefer accessing namespace.name directly elsewhere.

-const { namespace: { name: namespace } } = useWorkspace();
+const { namespace } = useWorkspace();

58-58: LGTM: CLI examples now interpolate workspace namespace

All three commands correctly reference the active workspace. Consider centralizing CLI string builders to avoid drift across files.

I can extract a small helper (e.g., buildWgcCmd({ kind, namespace, ... })) and update call sites—want me to push a follow-up PR?

Also applies to: 64-64, 67-68

studio/src/components/cache/cache-warmer-config.tsx (1)

93-99: Capture/log errors for observability

Ignoring the error param is fine for UX, but consider logging it (e.g., to Sentry) to aid debugging.

- onError: (_) => {
+ onError: (err) => {
+   console.error('configureCacheWarmer failed', err);
    toast({
      description:
        "Could not set the cache warmer config. Please try again.",
      duration: 3000,
    });
  },
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/cache-operations.tsx (1)

142-143: Avoid re-introducing namespace via query

With workspace context in place, you likely don’t need to pass ?namespace=... in the navigation. Prefer relying on the provider to keep the current namespace.

- `/${user?.currentOrganization.slug}/cache-warmer?namespace=${namespace}`,
+ `/${user?.currentOrganization.slug}/cache-warmer`,

If the destination still reads namespace from the URL, confirm and keep as-is.

studio/src/components/checks/changes-table.tsx (1)

155-156: Prefer consistent namespace source

You introduced namespace from useWorkspace here, but mutate/invalidate calls still use graphContext?.graph?.namespace. Standardize on the workspace namespace to avoid drift.
Example:

namespace
// instead of graphContext?.graph?.namespace

Also ensure organizationSlug/namespace are present before building Links.

studio/src/components/lint-policy/linter-config.tsx (1)

146-154: Minor: remove unused error param

The error variable isn’t used. Rename to _ or drop it to satisfy linters.

-                onError: (error) => {
+                onError: () => {
                   toast({
                     description: checked

Same for the onError handler in the “Apply” button section below.

studio/src/components/checks/override.tsx (1)

131-141: Guard against undefined organizationSlug in Explorer link

If useCurrentOrganization() hasn’t resolved, organizationSlug can be undefined, producing broken hrefs.

-                            organizationSlug,
+                            organizationSlug: organizationSlug ?? (router.query.organizationSlug as string),
-                            namespace,
+                            namespace: namespace ?? (router.query.namespace as string),
studio/src/components/analytics/toolbar.tsx (1)

31-35: Avoid building links with undefined org/namespace

On initial render, hooks may return undefined. Prevent invalid dynamic route interpolation.

-  const query: ParsedUrlQueryInput = {
+  if (!organizationSlug || !namespace) return null;
+  const query: ParsedUrlQueryInput = {
     organizationSlug,
     namespace,
     slug: router.query.slug,
   };
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/checks/[checkId]/index.tsx (1)

266-277: Optional: hide Force Success by RBAC

Consider gating the Force Success control behind an access check (e.g., admin/developer), similar to other screens.

studio/src/components/analytics/field-usage.tsx (1)

251-256: Minor: reuse local slug variable for consistency

You already captured slug above; use it here to avoid double-sourcing.

-                                        slug: router.query.slug,
+                                        slug,
studio/src/pages/[organizationSlug]/policies.tsx (1)

28-29: Shorthand object property for readability

Use { namespace } instead of { namespace: namespace }.

-} = useQuery(getNamespaceLintConfig, {
-  namespace: namespace,
+} = useQuery(getNamespaceLintConfig, {
+  namespace,
 });
@@
-} = useQuery(getNamespaceGraphPruningConfig, { namespace: namespace });
+} = useQuery(getNamespaceGraphPruningConfig, { namespace });
@@
-} = useQuery(getNamespaceChecksConfig, { namespace: namespace });
+} = useQuery(getNamespaceChecksConfig, { namespace });
@@
-  } = useQuery(
-    getNamespaceProposalConfig,
-    {
-      namespace: namespace,
-    },
+  } = useQuery(
+    getNamespaceProposalConfig,
+    {
+      namespace,
+    },

Also applies to: 36-37, 43-44, 53-54

studio/src/components/federatedgraphs-cards.tsx (2)

513-519: CLI robustness: quote namespace to tolerate special characters

Safer if namespaces ever include non-word chars.

-                command={`npx wgc federated-graph create production --namespace ${namespace} --label-matcher ${labels} --routing-url http://localhost:3002/graphql`}
+                command={`npx wgc federated-graph create production --namespace "${namespace}" --label-matcher ${labels} --routing-url http://localhost:3002/graphql`}
@@
-                command={`npx wgc monograph create production --namespace ${namespace} --routing-url http://localhost:3002/graphql  --graph-url http://localhost:4000/graphql`}
+                command={`npx wgc monograph create production --namespace "${namespace}" --routing-url http://localhost:3002/graphql  --graph-url http://localhost:4000/graphql`}

150-158: UX: don’t redirect away on migration error

Keeping users on the dialog so they can fix input and retry is friendlier.

-        onError: (_) => {
+        onError: (/* error */) => {
           toast({
             description: "Could not migrate the graph. Please try again.",
             duration: 3000,
           });
           setOpen(false);
           setIsMigrating(false);
-          router.replace(`/${currentOrg?.name}/graphs`);
+          // stay on page; no redirect on error
         },
studio/src/components/overview/OverviewToolbar.tsx (1)

22-26: Minor: memoize query object to avoid needless re-renders

Not critical, but cheap to memoize when passed to multiple Links.

-  const query = {
-    namespace,
-    organizationSlug,
-    slug: router.query.slug,
-  };
+  const query = React.useMemo(
+    () => ({ namespace, organizationSlug, slug: router.query.slug }),
+    [namespace, organizationSlug, router.query.slug],
+  );
studio/src/components/checks/graph-pruning-issues-table.tsx (3)

47-49: Unify org slug source and avoid non-null assertion elsewhere

Prefer a single, reliable org slug. Also prepare for absence to prevent malformed URLs.

Apply:

-  const { namespace: { name: namespace } } = useWorkspace();
-  const organizationSlug = useCurrentOrganization()?.slug;
+  const { namespace: { name: namespace } } = useWorkspace();
+  const orgSlug = useCurrentOrganization()?.slug ?? user?.currentOrganization?.slug;

58-63: Guard navigation when org slug is missing; use unified slug

Avoid pushing a path like "/undefined/...".

-              router.push(
-                `/${user!.currentOrganization.slug}/policies?namespace=${
-                  graphContext?.graph?.namespace ?? namespace
-                }`,
-              );
+              if (!orgSlug) return;
+              router.push(
+                `/${orgSlug}/policies?namespace=${graphContext?.graph?.namespace ?? namespace}`,
+              );

137-148: Harden URL building: encode params and normalize Next.js query types

  • router.query.* can be string | string[]; normalize to first value.
  • encode subgraphName; avoid stray “&” when absent.
-                          href={`/${organizationSlug}/${
-                            namespace
-                          }/graph/${router.query.slug}/checks/${
-                            router.query.checkId
-                          }?tab=schema&${
-                            l.subgraphName ? `subgraph=${l.subgraphName}` : ""
-                          }${
-                            l.issueLocation?.line
-                              ? `#L${l.issueLocation?.line}`
-                              : ""
-                          }`}
+                          href={`/${orgSlug}/${namespace}/graph/${
+                            Array.isArray(router.query.slug) ? router.query.slug[0] : router.query.slug
+                          }/checks/${
+                            Array.isArray(router.query.checkId) ? router.query.checkId[0] : router.query.checkId
+                          }?tab=schema${
+                            l.subgraphName ? `&subgraph=${encodeURIComponent(l.subgraphName)}` : ""
+                          }${
+                            l.issueLocation?.line ? `#L${l.issueLocation.line}` : ""
+                          }`}
studio/src/components/create-graph.tsx (3)

130-132: Invalidate (not refetch) to cover inactive queries; optionally prefetch

refetchQueries only refetches active queries by default. Invalidate is safer, and you can prefetch if you need data immediately.

-          // We need to refresh the workspace after creating a graph
-          await queryClient.refetchQueries({ queryKey: createConnectQueryKey(getWorkspace) });
+          // Refresh workspace cache after creating a graph
+          await queryClient.invalidateQueries({ queryKey: createConnectQueryKey(getWorkspace) });
+          // Optional: prefetch if immediate freshness is required on next screen
+          // await queryClient.prefetchQuery({ queryKey: createConnectQueryKey(getWorkspace) });

50-54: Derive and validate org slug once; reuse

Avoid “undefined” in paths if user context is momentarily unavailable.

   const router = useRouter();
   const user = useUser();
   const { namespace: { name: namespace } } = useWorkspace();
+  const orgSlug = user?.currentOrganization?.slug;
   const queryClient = useQueryClient();

132-134: Guard navigation if org slug is missing

Prevents bad routes; also improves error UX.

-          router.replace(
-            `/${user?.currentOrganization.slug}/${namespace}/graph/${data.name}`,
-          );
+          if (!orgSlug) {
+            toast({ description: "Missing organization context.", duration: 3000 });
+            return;
+          }
+          router.replace(`/${orgSlug}/${namespace}/graph/${data.name}`);
studio/src/components/feature-flag-details.tsx (1)

84-85: Use the active workspace namespace in the enable command for consistency

Other CLI snippets are workspace-derived; align this one too.

-            command={`npx wgc feature-flag enable <feature-flag-name> --namespace <namespace>`}
+            command={`npx wgc feature-flag enable <feature-flag-name> --namespace ${namespace}`}
studio/src/pages/[organizationSlug]/cache-warmer.tsx (1)

170-170: Remove duplicate period

Minor text polish.

-    "Configure cache warming to warm your router with top operations..",
+    "Configure cache warming to warm your router with top operations.",
studio/src/components/analytics/metrics.tsx (1)

323-335: Avoid generating per-row links until params exist

Set href to undefined when required params are missing; BarList will render non-clickable rows.

-          href: isSubgraphAnalytics
-            ? undefined
-            : {
+          href: isSubgraphAnalytics || !organizationSlug || !namespace
+            ? undefined
+            : {
                 pathname: `${router.pathname}/traces`,
                 query: {
                   organizationSlug,
                   namespace,
                   slug: router.query.slug,
                   filterState: createFilterState({
                     operationName: row.name,
                     operationHash: row.hash,
                   }),
                   range,
                   dateRange,
                 },
               },
studio/src/components/dashboard/graph-selector.tsx (1)

27-33: Fix misleading comment on filter reset timing

Code resets filter on close, but the comment says "when opened".

-            // Only reset the filter when the popover is opened
+            // Reset the filter when the popover is closed
controlplane/src/core/bufservices/workspace/getWorkspace.ts (3)

50-58: Avoid “magic” limit=0; rely on undefined to mean “no limit”

Repository checks if (opts.limit), so 0 already behaves as “no limit”; passing undefined clarifies intent.

-      offset: 0, // From the beginning
-      limit: 0, // Retrieve all federated graphs
+      offset: 0, // From the beginning
+      // limit omitted => retrieve all federated graphs

67-101: Potential N+1 on subgraphs retrieval

One DB call per federated graph can get expensive on large workspaces. Consider a repository method to fetch subgraphs for multiple federated graphs in one query and group in-memory.


104-107: Comment nit: you’re sorting graphs, not namespaces, here

The namespaces were already sorted earlier; update the comment for accuracy.

-    // Finally, sort the namespaces alphabetically
+    // Finally, sort each namespace's graphs alphabetically
connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts (1)

141-156: Consider marking GetWorkspace as idempotent to enable safe GET and caching.
If the server guarantees no side effects, set NoSideEffects like analytics methods. This allows Connect-Web (with useHttpGet: true) to use HTTP GET and improves CDN/cache friendliness.

Apply this diff:

 export const getWorkspace = {
   localName: "getWorkspace",
   name: "GetWorkspace",
   kind: MethodKind.Unary,
   I: GetWorkspaceRequest,
   O: GetWorkspaceResponse,
+      idempotency: MethodIdempotency.NoSideEffects,
   service: {
     typeName: "wg.cosmo.platform.v1.PlatformService"
   }
 } as const;
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/clients.tsx (3)

171-176: Use current organization slug from context, not router.
Keeps slug source consistent with the rest of the page and resilient to route param drift.

Apply this diff:

-  const { namespace: { name: namespace } } = useWorkspace();
-  const organizationSlug = router.query.organizationSlug as string;
+  const { namespace: { name: namespace } } = useWorkspace();
+  const organizationSlug = useCurrentOrganization()?.slug ?? "";

359-365: Unify slug source in analytics link.
Use the org slug from context rather than router.query for consistency.

Apply this diff:

   <Link
     href={{
       pathname: `/[organizationSlug]/[namespace]/graph/[slug]/analytics`,
       query: {
-        organizationSlug: router.query.organizationSlug,
+        organizationSlug,
         namespace,
         slug: router.query.slug,
         filterState: createFilterState({
           operationPersistedId: op.id,
         }),
       },
     }}
   >

607-616: URL-encode filterState to avoid malformed URLs.
filterState contains JSON; encode to ensure safe routing.

Apply this diff:

-      return `/${organizationSlug}/${namespace}/graph/${slug}/analytics?filterState=${JSON.stringify(
-        filters,
-      )}`;
+      return `/${organizationSlug}/${namespace}/graph/${slug}/analytics?filterState=${encodeURIComponent(
+        JSON.stringify(filters),
+      )}`;
     } else {
-      return `/${organizationSlug}/${namespace}/graph/${slug}/analytics/traces?filterState=${JSON.stringify(
-        filters,
-      )}`;
+      return `/${organizationSlug}/${namespace}/graph/${slug}/analytics/traces?filterState=${encodeURIComponent(
+        JSON.stringify(filters),
+      )}`;
     }
studio/src/pages/[organizationSlug]/subgraphs.tsx (1)

41-46: Align namespace handling across queries.

getSubgraphs uses the workspace namespace directly. getFeatureSubgraphs (Line 54) still falls back to "default". For consistency and fewer surprises, pass the workspace namespace there too.

Replace:

  • namespace: namespace || "default"
    With:
  • namespace
studio/src/components/dashboard/graph-command-group.tsx (3)

83-97: Harden route template computation and org slug availability.

If organizationSlug is absent, router.push will construct an invalid URL. Consider a fallback to router.query.organizationSlug and add a quick guard.

-  const organizationSlug = useCurrentOrganization()?.slug;
+  const organizationSlug = useCurrentOrganization()?.slug ?? (router.query.organizationSlug as string | undefined);
+  if (!organizationSlug) {
+    // Avoid pushing an invalid route; silently no-op or show a toast
+  }

Confirm org slug is always available in contexts where this component renders.


99-106: Remove redundant React key on CommandItem.

Key is only used by the immediate list renderer. Here, CommandItem isn’t directly in the map; the key belongs to the parent loops. This inner key is unnecessary.

-    <CommandItem
-      key={`graph-${namespace.name}-${name}`}
+    <CommandItem

107-117: Decide whether namespace change should update URL params.

If the upstream setNamespace applies route params by default, calling router.push here might double-update. Prefer a single source of truth: either rely on setNamespace(namespace, true) and let the provider update params, or keep router.push here and call setNamespace(namespace, false).

-        setNamespace(namespace.name);
+        setNamespace(namespace.name, false);
studio/src/components/dashboard/workspace-selector.tsx (1)

20-36: Slug matching is case-sensitive on one side; normalize and handle array type.

router.query.slug can be string|string[]. Also, comparing lowercased name to raw slug can fail if slug isn’t lowercased.

-      const currentSlug = router.query.slug as string;
+      const slugParam = router.query.slug;
+      const currentSlug = (Array.isArray(slugParam) ? slugParam[0] : slugParam)?.toLowerCase();
       return [
-        routeSegment === "graph"
-          ? namespace.graphs.find((graph) => graph.name.toLowerCase() === currentSlug)
+        routeSegment === "graph"
+          ? namespace.graphs.find((graph) => graph.name.toLowerCase() === currentSlug)
           : undefined,
studio/src/components/dashboard/workspace-provider.tsx (1)

29-33: Handle namespace query param type and avoid defaulting namespaces to a placeholder array.

router.query.namespace can be string|string[]; also, initializing namespaces with ["default"] can mask “no data yet” and affect validation.

-  const namespaceParam = router.query.namespace as string;
+  const nsParam = router.query.namespace;
+  const namespaceParam = Array.isArray(nsParam) ? nsParam[0] : nsParam;
-  const [namespaces, setNamespaces] = useState([DEFAULT_NAMESPACE_NAME]);
+  const [namespaces, setNamespaces] = useState<string[]>([]);
studio/src/components/dashboard/workspace-command-wrapper.tsx (3)

35-94: Tighten Fuse.js typing and reuse instances for graphs/subgraphs.

Using Fuse hides type issues; also reusing specialized instances is clearer and marginally cheaper.

Apply this diff:

-  const filteredGraphs = useMemo<WorkspaceNamespace[]>(() => {
+  const filteredGraphs = useMemo<WorkspaceNamespace[]>(() => {
     const filterValue = filter?.trim().toLowerCase() ?? '';
     if (filterValue.length === 0) {
       // There is no filter, so return all namespaces
       return Array.from(namespaceByName.values());
     }
 
-    const fuse = new Fuse<unknown>([], { keys: ['name'], threshold: 0.3, });
+    const graphFuse = new Fuse<WorkspaceFederatedGraph>([], { keys: ['name'], threshold: 0.3, ignoreLocation: true });
+    const subgraphFuse = new Fuse<WorkspaceSubgraph>([], { keys: ['name'], threshold: 0.3, ignoreLocation: true });
     const searchResults: WorkspaceNamespace[] = [];
     for (const wns of Array.from(namespaceByName.values())) {
@@
-      for (const graph of wns.graphs) {
-        fuse.setCollection([graph]);
-        if (fuse.search(filterValue).length > 0) {
+      for (const graph of wns.graphs) {
+        graphFuse.setCollection([graph]);
+        if (graphFuse.search(filterValue).length > 0) {
           // The graph contains the filter value, we need to add it to the search results
           clonedWns.graphs.push(graph);
           continue;
         }
@@
-        fuse.setCollection(graph.subgraphs);
-        const matchingSubgraphs = fuse.search(filterValue);
+        subgraphFuse.setCollection(graph.subgraphs);
+        const matchingSubgraphs = subgraphFuse.search(filterValue);

If desired, also sort namespaces/graphs alphabetically when not filtering to ensure stable ordering.


118-132: Use stable keys and drop the extra fragment.

Index-based keys can cause stale renders; prefer id or name. The surrounding fragment is unnecessary.

Apply this diff:

-              ) : filteredGraphs.map((wns, index) => (
-                <>
-                    <GraphCommandGroup
-                      key={`namespace-${index}`}
+              ) : filteredGraphs.map((wns) => (
+                    <GraphCommandGroup
+                      key={`namespace-${wns.id || wns.name}`}
                       isFiltering={isFiltering}
                       namespace={wns}
-                      namespaceIndex={index}
+                      namespaceIndex={0}
                       activeGraphId={activeGraph?.id}
                       activeSubgraphId={activeSubgraph?.id}
                       setNamespace={(ns) => {
                         setNamespace(ns, false);
                         close();
                       }}
                     />
-                </>
               ))}

105-110: Minor a11y improvement: label the search input.

Add aria-label for screen readers.

Apply this diff:

-        {showFilter && (<CommandInput
+        {showFilter && (<CommandInput
           value={filter}
           onValueChange={setFilter}
-          placeholder="Search graphs and subgraphs"
+          placeholder="Search graphs and subgraphs"
+          aria-label="Search graphs and subgraphs"
         />)}
proto/wg/cosmo/platform/v1/platform.proto (3)

2573-2599: Align field naming with file/style conventions (snake_case), especially target_id

Most messages in this file use snake_case (e.g., target_id in FederatedGraph/Subgraph). New workspace messages introduce camelCase (targetId). For consistency and easier grepping across languages, prefer snake_case.

 message WorkspaceFederatedGraph {
   string id = 1;
-  string targetId = 2;
+  string target_id = 2;
   string name = 3;
   bool isContract = 4;
   repeated WorkspaceSubgraph subgraphs = 5;
 }

 message WorkspaceSubgraph {
   string id = 1;
-  string targetId = 2;
+  string target_id = 2;
   string name = 3;
 }

Optional: add brief comments clarifying whether name is FQDN or short name to mirror earlier docs in this file.


2965-2967: Mark GetWorkspace as read-only and confirm auth scoping

This is a read path and should be safe to cache. Add idempotency for clarity, and ensure the server filters to the caller’s accessible namespaces.

-  rpc GetWorkspace(GetWorkspaceRequest) returns (GetWorkspaceResponse) {}
+  rpc GetWorkspace(GetWorkspaceRequest) returns (GetWorkspaceResponse) {
+    option idempotency_level = NO_SIDE_EFFECTS;
+  }

Please confirm the handler enforces org/namespace authorization derived from the authenticated user context.


352-357: Remove or integrate SubgraphMinimal
The SubgraphMinimal message is defined but never referenced in any RPCs or other messages. Either remove it or replace existing ad-hoc triplets (e.g. GetSubgraphByNameResponse.LinkedSubgraph) with this type.

Comment thread connect/src/wg/cosmo/platform/v1/platform_pb.ts Outdated
Comment thread proto/wg/cosmo/platform/v1/platform.proto Outdated
Comment thread studio/src/components/analytics/field-usage.tsx
Comment thread studio/src/components/app-provider.tsx
Comment thread studio/src/components/schema/toolbar.tsx
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
studio/src/components/federatedgraphs-cards.tsx (1)

309-309: Fix displayed Docker command: use --pull always, not -e pull=always

The visible snippet shows an env var instead of the Docker flag, diverging from the copyable command and likely confusing users.

-      <span>{`  -e pull=always \\`}</span>
+      <span>{`  --pull always \\`}</span>
♻️ Duplicate comments (1)
studio/src/components/federatedgraphs-cards.tsx (1)

148-149: Resolved: redirect now uses org slug (not name)

This addresses the earlier 404 issue reported in past reviews.

Also applies to: 157-157

🧹 Nitpick comments (7)
connect/src/wg/cosmo/platform/v1/platform_pb.ts (2)

20353-20366: Empty GetWorkspaceRequest: OK, but note future-proofing

Empty request keeps RPC simple; if you foresee filters/pagination, prefer adding fields in proto now (server can ignore) and re-gen rather than editing generated TS later.


20384-20426: Payload growth risk: consider minimal vs full workspace views

namespaces[].graphs[].subgraphs[] can get large. If UI only needs names/ids for the selector, consider a lightweight RPC/fields (e.g., minimal=true) at the proto level to shrink payloads and speed up first paint. Change in proto; re-gen TS.

studio/src/components/federatedgraphs-cards.tsx (5)

91-93: Guard and URL-encode org slug before redirect

useCurrentOrganization()?.slug can be undefined during initial render or on edge cases, and slugs may need encoding. Guard and encode to avoid /undefined/graphs or double slashes.

Apply:

-  const organizationSlug = useCurrentOrganization()?.slug;
+  const organizationSlug = useCurrentOrganization()?.slug;
+  const orgPath = organizationSlug ? encodeURIComponent(organizationSlug) : undefined;
@@
-          router.replace(`/${organizationSlug}/graphs`);
+          router.replace(orgPath ? `/${orgPath}/graphs` : "/");
@@
-          router.replace(`/${organizationSlug}/graphs`);
+          router.replace(orgPath ? `/${orgPath}/graphs` : "/");

If there is a preferred fallback route (e.g., a landing page), replace / accordingly.

Also applies to: 148-149, 157-157


150-158: Don’t redirect on migration error; keep dialog open so users can fix inputs

Closing and redirecting on failure forces users to restart. Log the error and keep the dialog open.

-        onError: (_) => {
-          toast({
-            description: "Could not migrate the graph. Please try again.",
-            duration: 3000,
-          });
-          setOpen(false);
-          setIsMigrating(false);
-          router.replace(`/${organizationSlug}/graphs`);
-        },
+        onError: (err) => {
+          console.error("migrateFromApollo failed", err);
+          toast({
+            description: "Could not migrate the graph. Please try again.",
+            duration: 3000,
+          });
+          setIsMigrating(false);
+          // Keep dialog open so users can adjust inputs.
+        },

330-333: Quote dynamic values in the token command

Namespaces or graph names with special characters/spaces can break the command. Quote both.

-  const createTokenCommand = `npx wgc router token create <name> ${namespace ? `-n ${namespace}` : ""} -g ${graphName}`;
+  const createTokenCommand = `npx wgc router token create <name> ${namespace ? `-n "${namespace}"` : ""} -g "${graphName}"`;
@@
-                  {` ${namespace ? `-n ${namespace}` : ""} -g ${graphName}`}
+                  {` ${namespace ? `-n "${namespace}"` : ""} -g "${graphName}"`}

Also applies to: 390-395


513-514: Quote namespace in CLI create commands

Prevents shell parsing issues if the namespace contains special chars.

-                command={`npx wgc federated-graph create production --namespace ${namespace} --label-matcher ${labels} --routing-url http://localhost:3002/graphql`}
+                command={`npx wgc federated-graph create production --namespace "${namespace}" --label-matcher ${labels} --routing-url http://localhost:3002/graphql`}
@@
-                command={`npx wgc monograph create production --namespace ${namespace} --routing-url http://localhost:3002/graphql  --graph-url http://localhost:4000/graphql`}
+                command={`npx wgc monograph create production --namespace "${namespace}" --routing-url http://localhost:3002/graphql  --graph-url http://localhost:4000/graphql`}

Also applies to: 518-519


571-573: URL-encode path segments in Link to avoid broken routes

Encode namespace and name (and slug if not guaranteed safe).

-    <Link
-      href={`/${user?.currentOrganization?.slug}/${graph.namespace}/graph/${graph.name}`}
-      className="project-list-item group"
-    >
+    <Link
+      href={`/${encodeURIComponent(user?.currentOrganization?.slug ?? "")}/${encodeURIComponent(graph.namespace)}/graph/${encodeURIComponent(graph.name)}`}
+      className="project-list-item group"
+    >

Consider skipping render if user?.currentOrganization?.slug is absent to avoid // routes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5382072 and d71e85f.

⛔ Files ignored due to path filters (1)
  • connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (4)
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts (2 hunks)
  • proto/wg/cosmo/platform/v1/platform.proto (3 hunks)
  • studio/src/components/feature-flag-details.tsx (5 hunks)
  • studio/src/components/federatedgraphs-cards.tsx (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • studio/src/components/feature-flag-details.tsx
  • proto/wg/cosmo/platform/v1/platform.proto
🧰 Additional context used
🧬 Code graph analysis (2)
connect/src/wg/cosmo/platform/v1/platform_pb.ts (2)
connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go (21)
  • SubgraphMinimal (3712-3720)
  • SubgraphMinimal (3735-3735)
  • SubgraphMinimal (3750-3752)
  • WorkspaceNamespace (24480-24488)
  • WorkspaceNamespace (24503-24503)
  • WorkspaceNamespace (24518-24520)
  • WorkspaceFederatedGraph (24543-24553)
  • WorkspaceFederatedGraph (24568-24568)
  • WorkspaceFederatedGraph (24583-24585)
  • WorkspaceSubgraph (24622-24630)
  • WorkspaceSubgraph (24645-24645)
  • WorkspaceSubgraph (24660-24662)
  • GetWorkspaceRequest (24685-24689)
  • GetWorkspaceRequest (24704-24704)
  • GetWorkspaceRequest (24719-24721)
  • GetWorkspaceResponse (24723-24730)
  • GetWorkspaceResponse (24745-24745)
  • GetWorkspaceResponse (24760-24762)
  • Response (712-720)
  • Response (735-735)
  • Response (750-752)
connect/src/wg/cosmo/node/v1/node_pb.ts (1)
  • Response (413-453)
studio/src/components/federatedgraphs-cards.tsx (2)
studio/src/hooks/use-workspace.ts (1)
  • useWorkspace (4-11)
studio/src/hooks/use-current-organization.ts (1)
  • useCurrentOrganization (3-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: build_test
  • GitHub Check: integration_test (./events)
  • GitHub Check: image_scan (nonroot)
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: build_push_image (nonroot)
  • GitHub Check: build_push_image
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: build_test
  • GitHub Check: build_test
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
🔇 Additional comments (8)
connect/src/wg/cosmo/platform/v1/platform_pb.ts (5)

2777-2824: SubgraphMinimal: field layout matches proto/go — looks good

Fields id/name/namespace map 1:1 to the Go code, with correct numbers and scalar types. No issues spotted in the TS codegen.


20194-20302: WorkspaceNamespace/WorkspaceFederatedGraph: schema matches Go; good addition

id/name/graphs and id/targetId/name/isContract/subgraphs align with Go code and intended workspace shape. No TS runtime issues.


2777-2824: SubgraphMinimal definitions are aligned across TS and Go
Fields id, name, and namespace are defined in the same order (1–3) in both languages.


20194-20302: No additional work on isContract/targetId needed
Verified that contract graphs are consistently flagged (via graph.contract/isContract) in cards, status indicators, and command groups, and that targetId is used uniformly for routing and permissions throughout Studio.


20384-20408: Response type correctly bound: GetWorkspaceResponse.response uses the locally defined Response class (typeName = "wg.cosmo.platform.v1.Response") and no node.v1.Response references were found.

studio/src/components/federatedgraphs-cards.tsx (3)

60-62: Hook migration to workspace/org context looks good

Switching from router query params to useWorkspace and useCurrentOrganization aligns with the new source of truth.


473-474: Workspace hook usage in Empty looks good

Consistent with the new context-driven model.


128-129: Namespace “name” is correct – the service uses namespaceRepo.byName(req.namespace) (migrateFromApollo.ts:47 ff.), so passing useWorkspace().namespace.name matches the RPC contract.

Comment thread controlplane/src/types/index.ts
Comment thread controlplane/src/core/bufservices/workspace/getWorkspace.ts Outdated
Comment thread proto/wg/cosmo/platform/v1/platform.proto Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
controlplane/src/core/repositories/SubgraphRepository.ts (1)

699-706: Potential SQL injection via raw string in LIKE on subgraphs.id.

sql.raw(... '%${opts.query}%') interpolates unsanitized input. Prefer parameterized expressions with like(sql\...`, value)`.

Apply this diff in both list and listAvailable:

-      conditions.push(
-        or(
-          like(schema.targets.name, `%${opts.query}%`),
-          sql.raw(`${getTableName(schema.subgraphs)}.${schema.subgraphs.id.name}::text like '%${opts.query}%'`),
-        ),
-      );
+      conditions.push(
+        or(
+          like(schema.targets.name, `%${opts.query}%`),
+          // Safe casting with parameterization
+          like(sql`${schema.subgraphs.id}::text`, `%${opts.query}%`),
+        ),
+      );

Also applies to: 761-768

♻️ Duplicate comments (2)
proto/wg/cosmo/platform/v1/platform.proto (1)

316-322: Past concern appears resolved: no includeSubgraphs flag present anymore

Previous feedback about includeSubgraphs without response wiring looks addressed; nothing to change here.

Also applies to: 331-349, 351-355

controlplane/src/types/index.ts (1)

40-40: Switching to namespaceIds?: string[] is the right call.

This aligns with multi-namespace filtering and addresses prior feedback about supporting multiple namespaces.

Run to spot any lingering call sites still using namespaceId in filter objects:

#!/bin/bash
# Find object-literal uses of `namespaceId:` (may surface some false positives in SQL strings).
rg -nP -C2 --type=ts '\bnamespaceId\s*:'
# Find type/interface references to `namespaceId?`
rg -nP -C2 --type=ts '\bnamespaceId\?\s*:'
🧹 Nitpick comments (5)
proto/wg/cosmo/platform/v1/platform.proto (4)

2566-2585: Prefer snake_case field names for new messages to align with proto style and most of this file

Suggest renaming targetId → target_id and isContract → is_contract for consistency.

 message WorkspaceFederatedGraph {
   string id = 1;
-  string targetId = 2;
+  string target_id = 2;
   string name = 3;
-  bool isContract = 4;
+  bool is_contract = 4;
   repeated WorkspaceSubgraph subgraphs = 5;
 }

 message WorkspaceSubgraph {
   string id = 1;
-  string targetId = 2;
+  string target_id = 2;
   string name = 3;
 }

2566-2570: Name collision risk with existing Namespace message

WorkspaceNamespace mirrors Namespace (id, name) but adds graphs. Consider a name like WorkspaceViewNamespace or reuse Namespace where possible to reduce cognitive load.


2586-2591: Consider payload controls (large orgs)

GetWorkspace may return many graphs/subgraphs. Consider optional include_subgraphs, per-namespace filters, or pagination in the request to cap response size.


2958-2960: Mark GetWorkspace as read-only

Add idempotency_level = NO_SIDE_EFFECTS for clearer semantics and gateway caching eligibility, similar to other read-only RPCs in this file.

-  rpc GetWorkspace(GetWorkspaceRequest) returns (GetWorkspaceResponse) {}
+  rpc GetWorkspace(GetWorkspaceRequest) returns (GetWorkspaceResponse) {
+    option idempotency_level = NO_SIDE_EFFECTS;
+  }
controlplane/src/core/repositories/SubgraphRepository.ts (1)

736-747: Avoid N+1 queries when building DTOs.

Looping over targets and calling byTargetId per row scales poorly. Consider expanding the select/join to fetch fields needed for SubgraphDTO in one query (including namespace name, labels, and schemaVersion columns) and mapping directly.

Also applies to: 806-815

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d71e85f and 8c32c8c.

⛔ Files ignored due to path filters (1)
  • connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (9)
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts (1 hunks)
  • controlplane/src/core/bufservices/federated-graph/getFederatedGraphs.ts (1 hunks)
  • controlplane/src/core/bufservices/namespace/deleteNamespace.ts (1 hunks)
  • controlplane/src/core/bufservices/subgraph/getSubgraphs.ts (1 hunks)
  • controlplane/src/core/bufservices/workspace/getWorkspace.ts (1 hunks)
  • controlplane/src/core/repositories/FederatedGraphRepository.ts (1 hunks)
  • controlplane/src/core/repositories/SubgraphRepository.ts (3 hunks)
  • controlplane/src/types/index.ts (1 hunks)
  • proto/wg/cosmo/platform/v1/platform.proto (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • controlplane/src/core/repositories/FederatedGraphRepository.ts
  • controlplane/src/core/bufservices/workspace/getWorkspace.ts
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-29T10:28:04.846Z
Learnt from: JivusAyrus
PR: wundergraph/cosmo#2156
File: controlplane/src/core/repositories/SubgraphRepository.ts:1749-1751
Timestamp: 2025-08-29T10:28:04.846Z
Learning: In the controlplane codebase, authentication and authorization checks (including organization scoping) are handled at the service layer in files like unlinkSubgraph.ts before calling repository methods. Repository methods like unlinkSubgraph() in SubgraphRepository.ts can focus purely on data operations without redundant security checks.

Applied to files:

  • controlplane/src/core/repositories/SubgraphRepository.ts
  • controlplane/src/core/bufservices/namespace/deleteNamespace.ts
📚 Learning: 2025-09-08T20:57:07.946Z
Learnt from: JivusAyrus
PR: wundergraph/cosmo#2156
File: controlplane/src/core/repositories/SubgraphRepository.ts:1746-1763
Timestamp: 2025-09-08T20:57:07.946Z
Learning: The checkSubgraphSchema.ts file already correctly implements linked subgraph functionality, using byName(linkedSubgraph.name, linkedSubgraph.namespace) to fetch target subgraphs and properly handles parse(newSchemaSDL) for schema building. The implementation doesn't need fixes for byId usage or schema parsing as it's already correct.

Applied to files:

  • controlplane/src/core/repositories/SubgraphRepository.ts
  • controlplane/src/core/bufservices/subgraph/getSubgraphs.ts
🧬 Code graph analysis (1)
controlplane/src/core/bufservices/subgraph/getSubgraphs.ts (2)
controlplane/src/core/repositories/FederatedGraphRepository.ts (2)
  • list (503-552)
  • count (555-570)
controlplane/src/core/repositories/SubgraphRepository.ts (2)
  • list (689-749)
  • count (819-854)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: CodeQL
  • GitHub Check: build-router
  • GitHub Check: integration_test (./events)
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: build_push_image (nonroot)
  • GitHub Check: build_test
  • GitHub Check: image_scan (nonroot)
  • GitHub Check: image_scan
  • GitHub Check: build_push_image
  • GitHub Check: build_test
  • GitHub Check: build_test
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: build_test
  • GitHub Check: build_push_image
🔇 Additional comments (4)
controlplane/src/core/bufservices/federated-graph/getFederatedGraphs.ts (1)

51-51: Good migration to multi-namespace filter.

Passing namespaceIds: namespaceId ? [namespaceId] : undefined matches the repo’s new contract and preserves the old semantics when no namespace is provided.

controlplane/src/core/bufservices/namespace/deleteNamespace.ts (1)

83-96: LGTM: namespace deletion now uses namespaceIds consistently.

Using limit: 0/offset: 0 to fetch all works with current repo semantics; no functional regression.

controlplane/src/core/repositories/SubgraphRepository.ts (1)

695-697: LGTM: multi-namespace filtering is correctly guarded and applied.

inArray(schema.targets.namespaceId, opts.namespaceIds) only when non-empty preserves “all namespaces” behavior.

Also applies to: 757-759, 825-827

controlplane/src/core/bufservices/subgraph/getSubgraphs.ts (1)

42-42: LGTM: callers now pass namespaceIds consistently.

Deriving namespaceIds from a single namespaceId preserves prior behavior without widening results unexpectedly.

Also applies to: 46-46, 53-53

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
studio/src/components/dashboard/namespace-selector.tsx (4)

58-60: Fix misleading comment (logic resets on close, not on open).

-            // Only reset the filter when the popover is opened
+            // Reset the filter when the popover closes

32-32: Deterministic, readable ordering of namespaces.
Sort once with useMemo so the list is stable and easier to scan.

-  const namespaces = Array.from(namespaceByName.keys());
+  const namespaces = useMemo(
+    () => Array.from(namespaceByName.keys()).sort((a, b) => a.localeCompare(b)),
+    [namespaceByName]
+  );

37-49: Add accessible labels to link/trigger and expose expanded state.
Improves a11y for screen readers.

         <Link
           href={{
             pathname,
             query: { organizationSlug, namespace: namespace.name },
           }}
           className={cn(
             "bg-primary/15 hover:bg-primary/30 text-primary transition-colors duration-150 pl-3 pr-2 py-1.5 rounded-l-lg text-sm flex-shrink-0",
             truncateNamespace && "max-w-[180px] lg:max-w-xs truncate"
           )}
           onClick={() => setNamespace(namespace.name, false)}
+          aria-label={`Current namespace: ${namespace.name}. Open graphs for this namespace.`}
         >
@@
           <button
             type="button"
             className={cn(
               "bg-primary/15 hover:bg-primary/30 text-primary transition-colors duration-150 text-sm flex-shrink-0 border-none outline-none",
               isViewingGraphOrSubgraph
                 ? "rounded-r-lg pl-2 pr-3 py-2"
                 : "flex justify-start items-center gap-4 rounded-lg px-3 py-1.5"
             )}
+            aria-haspopup="listbox"
+            aria-expanded={isOpen}
+            aria-label="Select namespace"
           >

Also applies to: 64-71


98-103: External link hardening: add rel to target=_blank.
Prevents reverse tabnabbing.

-              <Link
-                target="_blank"
-                className="text-primary"
-                href={`${docsBaseURL}/cli/essentials#namespaces`}
-              >
+              <Link
+                target="_blank"
+                rel="noreferrer noopener"
+                className="text-primary"
+                href={`${docsBaseURL}/cli/essentials#namespaces`}
+              >
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c32c8c and a0c2e3c.

📒 Files selected for processing (3)
  • studio/src/components/dashboard/graph-selector.tsx (1 hunks)
  • studio/src/components/dashboard/namespace-selector.tsx (1 hunks)
  • studio/src/components/dashboard/workspace-selector.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • studio/src/components/dashboard/workspace-selector.tsx
  • studio/src/components/dashboard/graph-selector.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
studio/src/components/dashboard/namespace-selector.tsx (5)
studio/src/hooks/use-workspace.ts (1)
  • useWorkspace (4-11)
studio/src/hooks/use-current-organization.ts (1)
  • useCurrentOrganization (3-6)
studio/src/lib/utils.ts (1)
  • cn (6-8)
studio/src/components/dashboard/workspace-command-wrapper.tsx (1)
  • WorkspaceCommandWrapper (25-139)
studio/src/lib/constants.ts (1)
  • docsBaseURL (1-1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: integration_test (./events)
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: build_test
  • GitHub Check: build_test
  • GitHub Check: Analyze (go)

Comment thread studio/src/components/dashboard/namespace-selector.tsx
@wilsonrivera wilsonrivera merged commit 33f870e into main Sep 16, 2025
47 checks passed
@wilsonrivera wilsonrivera deleted the wilson/eng-7026-default-namespace-is-causing-uxui-confusion branch September 16, 2025 16:44
@Noroth Noroth mentioned this pull request Sep 30, 2025
5 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Oct 21, 2025
5 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Nov 13, 2025
5 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Mar 6, 2026
5 tasks
This was referenced Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants