Scoped context What the assistant can see, as a list you can change. The working set is bounded by the viewer's own permissions, so two colleagues asking the same question get different answers, correctly. npx shadcn@latest add badge button Tokens this needs: --card, --card-foreground, --muted-foreground, --border, --status-warn, --chart-1 The status, chart, scale, state and direction names are an extension, not a rename. shadcn has --destructive and five --chart-* and nothing else in this territory. ──────────────────────────────────────────────────────────────────────── // ScopeChips.tsx ──────────────────────────────────────────────────────────────────────── import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; /** One thing in the working set: a page, a project, a dataset. It carries * the pages it resolves to, so the list below the chips is computed rather * than asserted. */ export type ScopeItem = { id: string; label: string; pages: string[]; }; type Props = { scope: ScopeItem[]; /** Which item is where the viewer is sitting. It is the default scope and * the one chip that reads as the page itself. */ here?: string; /** Paths inside the scope the viewer's own permissions exclude. Required, * even when empty: scope is bounded by permission, and a component that * cannot say so leaves two colleagues arguing over different answers. */ denied: string[]; onRemove: (id: string) => void; onAdd: () => void; /** How many in-scope pages to name before collapsing to a count. */ listed?: number; }; export function ScopeChips({ scope, here, denied, onRemove, onAdd, listed = 3 }: Props) { const pages = scope.flatMap((s) => s.pages); const rest = pages.length - listed; return (
In scope
Out of scope
{denied.length ? (nothing you can open is excluded
)}