copy: unify user-facing "company" wording to "organization" (#12243)
This commit is contained in:
parent
d785b19213
commit
4d82f5eaea
10
README.md
10
README.md
|
|
@ -67,7 +67,7 @@ It looks like a task manager. Under the hood: org charts, budgets, governance, g
|
|||
|
||||
## Paperclip is right for you if
|
||||
|
||||
- ✅ You want to build **autonomous AI companies**
|
||||
- ✅ You want to build **autonomous AI organizations**
|
||||
- ✅ You **coordinate many different agents** (OpenClaw, Codex, Claude, Cursor) toward a common goal
|
||||
- ✅ You have **20 simultaneous Claude Code terminals** open and lose track of what everyone is doing
|
||||
- ✅ You want agents running **autonomously 24/7**, but still want to audit work and chime in when needed
|
||||
|
|
@ -106,7 +106,7 @@ Any agent, any runtime, one org chart. If it can receive a heartbeat, it's hired
|
|||
</td>
|
||||
<td align="center" width="33%">
|
||||
<h3>🎯 Goal Alignment</h3>
|
||||
Every task traces back to the company mission. Agents know <em>what</em> to do and <em>why</em>.
|
||||
Every task traces back to the organization mission. Agents know <em>what</em> to do and <em>why</em>.
|
||||
</td>
|
||||
<td align="center" width="33%">
|
||||
<h3>💓 Heartbeats</h3>
|
||||
|
|
@ -119,8 +119,8 @@ Agents wake on a schedule, check work, and act. Delegation flows up and down the
|
|||
Monthly budgets per agent. When they hit the limit, they stop. No runaway costs.
|
||||
</td>
|
||||
<td align="center">
|
||||
<h3>🏢 Multi-Company</h3>
|
||||
One deployment, many companies. Complete data isolation. One control plane for your portfolio.
|
||||
<h3>🏢 Multi-Organization</h3>
|
||||
One deployment, many organizations. Complete data isolation. One control plane for your portfolio.
|
||||
</td>
|
||||
<td align="center">
|
||||
<h3>🎫 Ticket System</h3>
|
||||
|
|
@ -170,7 +170,7 @@ Paperclip handles the hard orchestration details correctly.
|
|||
| **Governance with rollback.** | Approval gates are enforced, config changes are revisioned, and bad changes can be rolled back safely. |
|
||||
| **Goal-aware execution.** | Tasks carry full goal ancestry so agents consistently see the "why," not just a title. |
|
||||
| **Portable company templates.** | Export/import orgs, agents, and skills with secret scrubbing and collision handling. |
|
||||
| **True multi-company isolation.** | Every entity is company-scoped, so one deployment can run many companies with separate data and audit trails. |
|
||||
| **True multi-organization isolation.** | Every entity is company-scoped, so one deployment can run many companies with separate data and audit trails. |
|
||||
|
||||
<br/>
|
||||
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ npx paperclipai context set --api-key-env-var-name PAPERCLIP_API_KEY
|
|||
export PAPERCLIP_API_KEY=...
|
||||
```
|
||||
|
||||
## Company Commands
|
||||
## Organization Commands
|
||||
|
||||
```sh
|
||||
npx paperclipai company list
|
||||
|
|
@ -633,7 +633,7 @@ npx paperclipai skills install paperclipai:optional:browser:agent-browser --comp
|
|||
External GitHub, skills.sh, local-path, and URL sources still go through
|
||||
`skills import`; catalog commands are for the app-shipped catalog only.
|
||||
|
||||
### Company library
|
||||
### Organization library
|
||||
|
||||
```sh
|
||||
npx paperclipai skills list --company-id <company-id>
|
||||
|
|
@ -762,7 +762,7 @@ bootstrap credentials in Paperclip secrets.
|
|||
|
||||
Per-company provider vaults (multiple vault instances per provider, default
|
||||
vault selection, coming-soon GCP/Vault) can be configured from the board UI under
|
||||
`Company Settings → Secrets → Provider vaults` or through the provider-config CLI
|
||||
`Organization Settings → Secrets → Provider vaults` or through the provider-config CLI
|
||||
commands above. See the
|
||||
[secrets deploy guide](../docs/deploy/secrets.md#provider-vaults) and
|
||||
[API reference](../docs/api/secrets.md#provider-vaults) for the contract.
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ default provider — no migration is required.
|
|||
|
||||
### Where to configure
|
||||
|
||||
Open `Company Settings → Secrets` in the board UI and switch to the
|
||||
Open `Organization Settings → Secrets` in the board UI and switch to the
|
||||
`Provider vaults` tab. From there you can:
|
||||
|
||||
- Create a vault for any supported provider family.
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ summary: Companies, agents, issues, delegation, heartbeats, and governance
|
|||
|
||||
Paperclip organizes autonomous AI work around six key concepts.
|
||||
|
||||
## Company
|
||||
## Organization
|
||||
|
||||
A company is the top-level unit of organization. Each company has:
|
||||
An organization is the top-level unit in Paperclip. Each organization has:
|
||||
|
||||
- A **goal** — the reason it exists (e.g. "Build the #1 AI note-taking app at $1M MRR")
|
||||
- **Employees** — every employee is an AI agent
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ async function runOnboardingWizard(page: Page, companyName: string) {
|
|||
if (await startBtn.count()) await startBtn.first().click();
|
||||
|
||||
// Step 0: front door (skipped when the wizard opens on the create path).
|
||||
const frontDoor = page.getByText("Build a new company");
|
||||
const frontDoor = page.getByText("Build a new organization");
|
||||
if (await frontDoor.count()) await frontDoor.first().click();
|
||||
|
||||
// Step 1: company name.
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ function shot(name: string) {
|
|||
|
||||
async function openWizard(page: import("@playwright/test").Page) {
|
||||
await page.goto("/onboarding");
|
||||
const startBtn = page.getByRole("button", { name: /Start Onboarding|New Company|Add Agent/ });
|
||||
const startBtn = page.getByRole("button", { name: /Start Onboarding|New Organization|Add Agent/ });
|
||||
if (await startBtn.count()) {
|
||||
await startBtn.first().click();
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ test.describe("NUX Phase 4 visual QA", () => {
|
|||
await openWizard(page);
|
||||
// Front door shows when the wizard doesn't open directly on the create
|
||||
// path (e.g. another spec already created a company on this instance).
|
||||
const createCard = page.getByRole("button", { name: /Build a new company/ });
|
||||
const createCard = page.getByRole("button", { name: /Build a new organization/ });
|
||||
if (await createCard.count()) {
|
||||
await createCard.first().click();
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ test.describe("NUX Phase 4 visual QA", () => {
|
|||
page.getByRole("heading", { name: "Welcome to Paperclip" }),
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Build a new company" }),
|
||||
page.getByRole("heading", { name: "Build a new organization" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Add agents to your org" }),
|
||||
|
|
@ -131,7 +131,7 @@ test.describe("NUX Phase 4 visual QA", () => {
|
|||
// Composer renders once a company is selected. (Regression guard for the
|
||||
// Rules-of-Hooks crash that previously blanked this page — see PAP-50.)
|
||||
await expect(
|
||||
page.getByPlaceholder("Ask anything about your company..."),
|
||||
page.getByPlaceholder("Ask anything about your organization..."),
|
||||
).toBeVisible({ timeout: 20_000 });
|
||||
await page.waitForTimeout(2_000); // let welcome bubble + suggestion chips stage in
|
||||
await page.screenshot({ path: shot("06-board-chat.png") });
|
||||
|
|
|
|||
|
|
@ -41,12 +41,12 @@ test.describe("Onboarding wizard", () => {
|
|||
// The wizard may open on a launcher card or directly on the capsule
|
||||
// wizard; the front door (step 0) requires a click into the create path.
|
||||
const startBtn = page.getByRole("button", {
|
||||
name: /Start Onboarding|New Company|Add Agent/,
|
||||
name: /Start Onboarding|New Organization|Add Agent/,
|
||||
});
|
||||
if (await startBtn.count()) {
|
||||
await startBtn.first().click();
|
||||
}
|
||||
const createCard = page.getByRole("button", { name: /Build a new company/ });
|
||||
const createCard = page.getByRole("button", { name: /Build a new organization/ });
|
||||
if (await createCard.count()) {
|
||||
await createCard.first().click();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,10 +48,10 @@ test("captures planning mode UI for desktop and mobile", async ({ page }) => {
|
|||
});
|
||||
|
||||
await page.goto("/onboarding");
|
||||
const startBtn = page.getByRole("button", { name: /Start Onboarding|New Company|Add Agent/ });
|
||||
const startBtn = page.getByRole("button", { name: /Start Onboarding|New Organization|Add Agent/ });
|
||||
if (await startBtn.count()) await startBtn.first().click();
|
||||
|
||||
const createCard = page.getByRole("button", { name: /Build a new company/ });
|
||||
const createCard = page.getByRole("button", { name: /Build a new organization/ });
|
||||
if (await createCard.count()) await createCard.first().click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "What is the name of your organization?" })).toBeVisible({ timeout: 15_000 });
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ describe("App Activity routing (PAP-16302)", () => {
|
|||
it("serves the merged Activity page at /:company/activity", async () => {
|
||||
const root = renderAppAt(container, "/PAP/activity");
|
||||
await waitForRoute(container, "ACTIVITY_PAGE@/PAP/activity");
|
||||
expect(container.textContent).not.toContain("No company matches prefix");
|
||||
expect(container.textContent).not.toContain("No organization matches prefix");
|
||||
flushSync(() => root.unmount());
|
||||
});
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ describe("App Activity routing (PAP-16302)", () => {
|
|||
it("redirects the bare /audit deep link through to the prefixed Activity page", async () => {
|
||||
const root = renderAppAt(container, "/audit");
|
||||
await waitForRoute(container, "ACTIVITY_PAGE@/PAP/activity?mode=agents");
|
||||
expect(container.textContent).not.toContain("No company matches prefix");
|
||||
expect(container.textContent).not.toContain("No organization matches prefix");
|
||||
flushSync(() => root.unmount());
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -132,14 +132,14 @@ describe("App Cases routing (PAP-13002)", () => {
|
|||
it("redirects unprefixed /cases to the company-prefixed list page", async () => {
|
||||
const root = await renderAppAt(container, "/cases");
|
||||
await waitForRoute(container, "CASES_LIST_PAGE");
|
||||
expect(container.textContent).not.toContain("No company matches prefix");
|
||||
expect(container.textContent).not.toContain("No organization matches prefix");
|
||||
flushSync(() => root.unmount());
|
||||
});
|
||||
|
||||
it("redirects unprefixed /cases/:id to the company-prefixed detail page", async () => {
|
||||
const root = await renderAppAt(container, "/cases/PAP-C5");
|
||||
await waitForRoute(container, "CASE_DETAIL_PAGE");
|
||||
expect(container.textContent).not.toContain("No company matches prefix");
|
||||
expect(container.textContent).not.toContain("No organization matches prefix");
|
||||
flushSync(() => root.unmount());
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -115,9 +115,9 @@ describe("CloudAccessGate", () => {
|
|||
});
|
||||
|
||||
const root = renderGate(container);
|
||||
await waitForText(container, "No company access");
|
||||
await waitForText(container, "No organization access");
|
||||
|
||||
expect(container.textContent).toContain("No company access");
|
||||
expect(container.textContent).toContain("No organization access");
|
||||
expect(container.textContent).not.toContain("Outlet content");
|
||||
|
||||
unmountRoot(root);
|
||||
|
|
@ -141,7 +141,7 @@ describe("CloudAccessGate", () => {
|
|||
await waitForText(container, "Outlet content");
|
||||
|
||||
expect(container.textContent).toContain("Outlet content");
|
||||
expect(container.textContent).not.toContain("No company access");
|
||||
expect(container.textContent).not.toContain("No organization access");
|
||||
|
||||
unmountRoot(root);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -463,13 +463,13 @@ export function OnboardingRoutePage() {
|
|||
const title = matchedCompany
|
||||
? `Add another agent to ${matchedCompany.name}`
|
||||
: companies.length > 0
|
||||
? "Create another company"
|
||||
: "Create your first company";
|
||||
? "Create another organization"
|
||||
: "Create your first organization";
|
||||
const description = matchedCompany
|
||||
? "Run onboarding again to add an agent and a starter task for this company."
|
||||
? "Run onboarding again to add an agent and a starter task for this organization."
|
||||
: companies.length > 0
|
||||
? "Run onboarding again to create another company and seed its first agent."
|
||||
: "Get started by creating a company and your first agent.";
|
||||
? "Run onboarding again to create another organization and seed its first agent."
|
||||
: "Get started by creating an organization and your first agent.";
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-xl py-10">
|
||||
|
|
@ -588,10 +588,10 @@ function NoCompaniesStartPage() {
|
|||
<div className="mx-auto max-w-xl py-10">
|
||||
<div className="rounded-lg border border-border bg-card p-6">
|
||||
<h1 className="text-xl font-semibold">
|
||||
{t("app.noCompanies.title", { defaultValue: "Create your first company" })}
|
||||
{t("app.noCompanies.title", { defaultValue: "Create your first organization" })}
|
||||
</h1>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
{t("app.noCompanies.description", { defaultValue: "Get started by creating a company." })}
|
||||
{t("app.noCompanies.description", { defaultValue: "Get started by creating an organization." })}
|
||||
</p>
|
||||
<div className="mt-4">
|
||||
{/* Same as the onboarding route: no Cloud origin means nowhere to
|
||||
|
|
@ -611,7 +611,7 @@ function NoCompaniesStartPage() {
|
|||
: openOnboarding()
|
||||
}
|
||||
>
|
||||
{t("app.noCompanies.newCompany", { defaultValue: "New Company" })}
|
||||
{t("app.noCompanies.newCompany", { defaultValue: "New Organization" })}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ export function ClaudeLocalAdvancedFields({
|
|||
</Field>
|
||||
<Field
|
||||
label="ACP state directory"
|
||||
hint="Optional ACP session state directory. Defaults to Paperclip-managed company/agent scoped storage."
|
||||
hint="Optional ACP session state directory. Defaults to Paperclip-managed organization/agent scoped storage."
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<DraftInput
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ export function CodexLocalConfigFields({
|
|||
</Field>
|
||||
<Field
|
||||
label="ACP state directory"
|
||||
hint="Optional ACP session state directory. Defaults to Paperclip-managed company/agent scoped storage."
|
||||
hint="Optional ACP session state directory. Defaults to Paperclip-managed organization/agent scoped storage."
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<DraftInput
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ export function GeminiLocalConfigFields({
|
|||
</Field>
|
||||
<Field
|
||||
label="ACP state directory"
|
||||
hint="Optional ACP session state directory. Defaults to Paperclip-managed company/agent scoped storage."
|
||||
hint="Optional ACP session state directory. Defaults to Paperclip-managed organization/agent scoped storage."
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<DraftInput
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
|||
);
|
||||
const createSecret = useMutation({
|
||||
mutationFn: (input: { name: string; value: string }) => {
|
||||
if (!selectedCompanyId) throw new Error("Select a company to create secrets");
|
||||
if (!selectedCompanyId) throw new Error("Select an organization to create secrets");
|
||||
return secretsApi.create(selectedCompanyId, input);
|
||||
},
|
||||
onSuccess: () => {
|
||||
|
|
@ -334,7 +334,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
|||
|
||||
const uploadMarkdownImage = useMutation({
|
||||
mutationFn: async ({ file, namespace }: { file: File; namespace: string }) => {
|
||||
if (!selectedCompanyId) throw new Error("Select a company to upload images");
|
||||
if (!selectedCompanyId) throw new Error("Select an organization to upload images");
|
||||
return assetsApi.uploadImage(selectedCompanyId, file, namespace);
|
||||
},
|
||||
});
|
||||
|
|
@ -711,7 +711,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
|||
: ["agents", "none", "detect-model", adapterType],
|
||||
queryFn: () => {
|
||||
if (!selectedCompanyId) {
|
||||
throw new Error("Select a company to detect the model");
|
||||
throw new Error("Select an organization to detect the model");
|
||||
}
|
||||
return agentsApi.detectModel(selectedCompanyId, adapterType);
|
||||
},
|
||||
|
|
@ -867,7 +867,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
|||
const testEnvironment = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!selectedCompanyId) {
|
||||
throw new Error("Select a company to test adapter environment");
|
||||
throw new Error("Select an organization to test adapter environment");
|
||||
}
|
||||
const flushedEnv = flushEnvironmentDraft();
|
||||
const adapterConfigPatch = flushedEnv ? { env: flushedEnv } : undefined;
|
||||
|
|
@ -1052,7 +1052,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
|||
(!loginNeedsPty || providerSupportsLoginPty);
|
||||
const runEnvironmentTest = useCallback(async () => {
|
||||
if (!selectedCompanyId) {
|
||||
throw new Error("Select a company to test adapter environment");
|
||||
throw new Error("Select an organization to test adapter environment");
|
||||
}
|
||||
setTestActionPending(true);
|
||||
setTestActionError(null);
|
||||
|
|
@ -1454,7 +1454,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
|||
) : (
|
||||
<div className="rounded-md border border-amber-500/25 bg-amber-500/10 px-3 py-2 text-xs text-amber-800 dark:text-amber-200">
|
||||
This instance requires the Kubernetes sandbox, but no managed Kubernetes
|
||||
environment is available for this company yet. Configure one before creating
|
||||
environment is available for this organization yet. Configure one before creating
|
||||
agents; execution will not fall back to local.
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ export function AppsSidebar() {
|
|||
className="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
|
||||
>
|
||||
<ChevronLeft className="h-3.5 w-3.5 shrink-0" />
|
||||
<span className="truncate">{selectedCompany?.name ?? "Company"}</span>
|
||||
<span className="truncate">{selectedCompany?.name ?? "Organization"}</span>
|
||||
</Link>
|
||||
<div className="flex items-center gap-2 px-2 py-1">
|
||||
<AppWindow className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ export function BootstrapPendingPage({
|
|||
<div>
|
||||
<h1 className="text-xl font-semibold">You're the instance admin</h1>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
Setup is complete. Taking you to onboarding to create your first company...
|
||||
Setup is complete. Taking you to onboarding to create your first organization...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,13 +13,13 @@ function NoBoardAccessPage() {
|
|||
return (
|
||||
<div className="mx-auto max-w-xl py-10">
|
||||
<Card className="block p-6">
|
||||
<h1 className="text-xl font-semibold">No company access</h1>
|
||||
<h1 className="text-xl font-semibold">No organization access</h1>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
This account is signed in, but it does not have an active company membership or instance-admin access on
|
||||
This account is signed in, but it does not have an active organization membership or instance-admin access on
|
||||
this Paperclip instance.
|
||||
</p>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
Use a company invite or sign in with an account that already belongs to this org.
|
||||
Use an organization invite or sign in with an account that already belongs to this org.
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export function CompanySettingsSidebar() {
|
|||
className="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
|
||||
>
|
||||
<ChevronLeft className="h-3.5 w-3.5 shrink-0" />
|
||||
<span className="truncate">{selectedCompany?.name ?? "Company"}</span>
|
||||
<span className="truncate">{selectedCompany?.name ?? "Organization"}</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -50,14 +50,14 @@ export function CompanySwitcher({ open: controlledOpen, onOpenChange }: CompanyS
|
|||
<span className={`h-2 w-2 rounded-full shrink-0 ${statusDotColor(selectedCompany.status)}`} />
|
||||
)}
|
||||
<span className="text-sm font-medium truncate">
|
||||
{selectedCompany?.name ?? "Select company"}
|
||||
{selectedCompany?.name ?? "Select organization"}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronsUpDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-(--sz-220px)">
|
||||
<DropdownMenuLabel>Companies</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>Organizations</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{sidebarCompanies.map((company) => (
|
||||
<DropdownMenuItem
|
||||
|
|
@ -75,7 +75,7 @@ export function CompanySwitcher({ open: controlledOpen, onOpenChange }: CompanyS
|
|||
// give the customer the way out, since nothing else in the app does.
|
||||
companyListUnavailable ? (
|
||||
<>
|
||||
<DropdownMenuItem disabled>Couldn't load companies</DropdownMenuItem>
|
||||
<DropdownMenuItem disabled>Couldn't load organizations</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onSelect={(event) => {
|
||||
// Keep the menu open so the result of the retry is visible.
|
||||
|
|
@ -88,7 +88,7 @@ export function CompanySwitcher({ open: controlledOpen, onOpenChange }: CompanyS
|
|||
</DropdownMenuItem>
|
||||
</>
|
||||
) : (
|
||||
<DropdownMenuItem disabled>No companies</DropdownMenuItem>
|
||||
<DropdownMenuItem disabled>No organizations</DropdownMenuItem>
|
||||
)
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
|
|
@ -101,7 +101,7 @@ export function CompanySwitcher({ open: controlledOpen, onOpenChange }: CompanyS
|
|||
<DropdownMenuItem asChild>
|
||||
<Link to="/companies" className="no-underline text-inherit">
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Manage Companies
|
||||
Manage Organizations
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ export function ConfigureBuiltInAgentModal({
|
|||
<div className="space-y-4">
|
||||
<InlineBanner tone="info" compact>
|
||||
Creates <strong>{definition.displayName}</strong> in your roster, badged{" "}
|
||||
<strong>Built-in</strong>. Companies that require hire approval will queue this for the
|
||||
<strong>Built-in</strong>. Organizations that require hire approval will queue this for the
|
||||
board.
|
||||
</InlineBanner>
|
||||
|
||||
|
|
|
|||
|
|
@ -115,13 +115,13 @@ function formatVerb(
|
|||
case "goal.deleted":
|
||||
return "deleted goal";
|
||||
case "company.created":
|
||||
return "created company";
|
||||
return "created organization";
|
||||
case "company.updated":
|
||||
return "updated company";
|
||||
return "updated organization";
|
||||
case "company.archived":
|
||||
return "archived company";
|
||||
return "archived organization";
|
||||
case "company.budget_updated":
|
||||
return "updated company budget";
|
||||
return "updated organization budget";
|
||||
|
||||
default:
|
||||
return action.replace(/[._]/g, " ");
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export function FrontDoor({ onChoose }: FrontDoorProps) {
|
|||
<Rocket className="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-sm">Build a new company</h3>
|
||||
<h3 className="font-semibold text-sm">Build a new organization</h3>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Begin with a mission, bring on a lead agent, and grow a team of agents to do the work.
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ export function InteractionGovernancePanel({
|
|||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Thread interactions are open by default:{" "}
|
||||
<span className="font-medium text-foreground">Anyone</span> in the company — the
|
||||
<span className="font-medium text-foreground">Anyone</span> in the organization — the
|
||||
board or any agent, including the one that asked — can respond. Narrow a kind
|
||||
only when you need to.{" "}
|
||||
<span className="font-medium text-foreground">Default policy</span> is the
|
||||
|
|
|
|||
|
|
@ -3948,7 +3948,7 @@ describe("IssueAssigneePausedNotice", () => {
|
|||
);
|
||||
});
|
||||
|
||||
expect(container.textContent).toContain("arrived paused from a company import");
|
||||
expect(container.textContent).toContain("arrived paused from an organization import");
|
||||
const resumeButton = container.querySelector(
|
||||
'[data-testid="issue-assignee-paused-resume"]',
|
||||
) as HTMLButtonElement | null;
|
||||
|
|
|
|||
|
|
@ -644,7 +644,7 @@ export function IssueAssigneePausedNotice({
|
|||
agent.pauseReason === "budget"
|
||||
? "It was paused by a budget hard stop."
|
||||
: agent.pauseReason === "import"
|
||||
? "It arrived paused from a company import — imported agents stay parked until you resume them."
|
||||
? "It arrived paused from an organization import — imported agents stay parked until you resume them."
|
||||
: agent.pauseReason === "system"
|
||||
? "It was paused by the system."
|
||||
: "It was paused manually.";
|
||||
|
|
|
|||
|
|
@ -1276,7 +1276,7 @@ describe("IssueThreadInteractionCard resolver audience", () => {
|
|||
expect(audience?.getAttribute("data-audience-policy")).toBe("human_only");
|
||||
expect(
|
||||
host.querySelector('[data-testid="interaction-audience-note"]')?.textContent,
|
||||
).toBe("Company interaction governance narrowed this from Anyone to Human only.");
|
||||
).toBe("Organization interaction governance narrowed this from Anyone to Human only.");
|
||||
});
|
||||
|
||||
it("explains a legacy card that predates the open default", () => {
|
||||
|
|
|
|||
|
|
@ -3682,7 +3682,7 @@ export function IssueThreadInteractionCard({
|
|||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" className="max-w-xs text-xs">
|
||||
Directed to {addresseeLabel}. Agent-addressed interactions are owned by that agent and are kept out of the open company attention feed.
|
||||
Directed to {addresseeLabel}. Agent-addressed interactions are owned by that agent and are kept out of the open organization attention feed.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
|
|
@ -3853,7 +3853,7 @@ function ResolvedByAgentChip() {
|
|||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" className="max-w-xs text-xs">
|
||||
Resolved by an agent under the company's interaction governance policy — audit-distinct from a human board resolution.
|
||||
Resolved by an agent under the organization's interaction governance policy — audit-distinct from a human board resolution.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -699,7 +699,7 @@ const SecretField = React.memo(({
|
|||
label={label}
|
||||
description={
|
||||
description ||
|
||||
"Pick an existing company secret, or paste a raw value (Paperclip will store it as a secret on save)."
|
||||
"Pick an existing organization secret, or paste a raw value (Paperclip will store it as a secret on save)."
|
||||
}
|
||||
required={isRequired}
|
||||
error={error}
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ describe("NewAgentDialog", () => {
|
|||
});
|
||||
|
||||
expect(container.textContent).toContain("Generate a one-time onboarding prompt");
|
||||
expect(container.textContent).not.toContain("Company Invites");
|
||||
expect(container.textContent).not.toContain("Organization Invites");
|
||||
|
||||
const generateButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(button) => button.textContent === "Generate onboarding prompt",
|
||||
|
|
|
|||
|
|
@ -352,7 +352,7 @@ export function NewAgentDialog() {
|
|||
</label>
|
||||
|
||||
<div className="rounded-lg border border-border px-4 py-3 text-sm text-muted-foreground">
|
||||
Agent invites create a join request first. A company admin still approves the request before the agent can claim its API key.
|
||||
Agent invites create a join request first. An organization admin still approves the request before the agent can claim its API key.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
@ -385,7 +385,7 @@ export function NewAgentDialog() {
|
|||
) : null}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Send this prompt to the external agent that should join this company.
|
||||
Send this prompt to the external agent that should join this organization.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import { MarkdownEditor, type MarkdownEditorRef } from "./MarkdownEditor";
|
|||
import { StatusBadge } from "./StatusBadge";
|
||||
|
||||
const levelLabels: Record<string, string> = {
|
||||
company: "Company",
|
||||
company: "Organization",
|
||||
team: "Team",
|
||||
agent: "Agent",
|
||||
task: "Task",
|
||||
|
|
@ -70,7 +70,7 @@ export function NewGoalDialog() {
|
|||
|
||||
const uploadDescriptionImage = useMutation({
|
||||
mutationFn: async (file: File) => {
|
||||
if (!selectedCompanyId) throw new Error("No company selected");
|
||||
if (!selectedCompanyId) throw new Error("No organization selected");
|
||||
return assetsApi.uploadImage(selectedCompanyId, file, "goals/drafts");
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -570,7 +570,7 @@ describe("NewIssueDialog", () => {
|
|||
await waitForAssertion(() => {
|
||||
expect(container.querySelector('[data-testid="new-issue-paused-assignee-note"]')).not.toBeNull();
|
||||
});
|
||||
expect(container.textContent).toContain("arrived paused from a company import");
|
||||
expect(container.textContent).toContain("arrived paused from an organization import");
|
||||
|
||||
act(() => root.unmount());
|
||||
});
|
||||
|
|
|
|||
|
|
@ -688,7 +688,7 @@ export function NewIssueDialog() {
|
|||
|
||||
const uploadDescriptionImage = useMutation({
|
||||
mutationFn: async (file: File) => {
|
||||
if (!effectiveCompanyId) throw new Error("No company selected");
|
||||
if (!effectiveCompanyId) throw new Error("No organization selected");
|
||||
return assetsApi.uploadImage(effectiveCompanyId, file, "issues/drafts");
|
||||
},
|
||||
});
|
||||
|
|
@ -2341,7 +2341,7 @@ export function NewIssueDialog() {
|
|||
<div data-testid="new-issue-paused-assignee-note" className="mx-4 mb-2">
|
||||
<InlineBanner tone="warning" icon={PauseCircle} compact>
|
||||
<span className="font-medium">{selectedAssigneeAgent.name}</span> is paused and will not start work on this task until it is resumed
|
||||
{selectedAssigneeAgent.pauseReason === "import" ? " — it arrived paused from a company import" : ""}. You can resume it from the task page after creating the task.
|
||||
{selectedAssigneeAgent.pauseReason === "import" ? " — it arrived paused from an organization import" : ""}. You can resume it from the task page after creating the task.
|
||||
</InlineBanner>
|
||||
</div>
|
||||
) : null}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ export function NewProjectDialog() {
|
|||
|
||||
const uploadDescriptionImage = useMutation({
|
||||
mutationFn: async (file: File) => {
|
||||
if (!selectedCompanyId) throw new Error("No company selected");
|
||||
if (!selectedCompanyId) throw new Error("No organization selected");
|
||||
return assetsApi.uploadImage(selectedCompanyId, file, "projects/drafts");
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -961,7 +961,7 @@ describe("OnboardingWizard — which step it lands on", () => {
|
|||
expect(segments.every((segment) => segment.disabled)).toBe(true);
|
||||
|
||||
// And company creation is genuinely unreachable, not merely unlinked.
|
||||
expect(document.body.textContent).not.toContain("Name your company");
|
||||
expect(document.body.textContent).not.toContain("Name your organization");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1025,7 +1025,7 @@ function OnboardingWizardInner({
|
|||
): Promise<AdapterEnvironmentTestResult | null> {
|
||||
if (!createdCompanyId) {
|
||||
setAdapterEnvError(
|
||||
"Create or select a company before testing adapter environment."
|
||||
"Create or select an organization before testing adapter environment."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -1209,7 +1209,7 @@ function OnboardingWizardInner({
|
|||
|
||||
setStep(3); // → Create your team lead
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to create company");
|
||||
setError(err instanceof Error ? err.message : "Failed to create organization");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -1255,7 +1255,7 @@ function OnboardingWizardInner({
|
|||
setSelectedCompanyId(company.id);
|
||||
setStep(3);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to create company");
|
||||
setError(err instanceof Error ? err.message : "Failed to create organization");
|
||||
} finally {
|
||||
creatingCompanyRef.current = false;
|
||||
setLoading(false);
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa
|
|||
});
|
||||
const createSecret = useMutation({
|
||||
mutationFn: (input: { name: string; value: string }) => {
|
||||
if (!selectedCompanyId) throw new Error("Select a company to create secrets");
|
||||
if (!selectedCompanyId) throw new Error("Select an organization to create secrets");
|
||||
return secretsApi.create(selectedCompanyId, input);
|
||||
},
|
||||
onSuccess: () => {
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ export function ReportsToPicker({
|
|||
)}
|
||||
{unknownManager && (
|
||||
<div className="px-2 py-1.5 text-xs text-muted-foreground border-b border-border mb-0.5">
|
||||
Saved manager is missing from this company. Choose a new manager or clear.
|
||||
Saved manager is missing from this organization. Choose a new manager or clear.
|
||||
</div>
|
||||
)}
|
||||
{rows.map((a) => (
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ describe("SecretBindingPicker", () => {
|
|||
await render(readyContext("active"));
|
||||
|
||||
expect(container.textContent).toContain("DAYTONA_API_KEY — Other Team");
|
||||
expect(container.textContent).toContain("Owned by the Other Team company");
|
||||
expect(container.textContent).toContain("Owned by the Other Team organization");
|
||||
expect(container.textContent).not.toContain("Missing secret");
|
||||
expect(container.querySelector("select")?.className).not.toContain("border-destructive");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ export function SecretBindingPicker({
|
|||
{selectedMissing && value ? (
|
||||
<option value={value.secretId}>
|
||||
{missingHint
|
||||
? `${missingHint.name} — ${missingHint.companyName ?? "another company"}`
|
||||
? `${missingHint.name} — ${missingHint.companyName ?? "another organization"}`
|
||||
: hintsPending
|
||||
? `Secret (${value.secretId.slice(0, 8)}…)`
|
||||
: `Missing secret (${value.secretId.slice(0, 8)}…)`}
|
||||
|
|
@ -257,7 +257,7 @@ export function SecretBindingPicker({
|
|||
) : crossCompanyHint ? (
|
||||
<p className="text-(length:--text-micro) text-muted-foreground flex items-center gap-1">
|
||||
<AlertCircle className="h-3 w-3" />
|
||||
Owned by {crossCompanyHint.companyName ? `the ${crossCompanyHint.companyName} company` : "another company"}. The binding keeps working; selecting a secret from this list re-points it here.
|
||||
Owned by {crossCompanyHint.companyName ? `the ${crossCompanyHint.companyName} organization` : "another organization"}. The binding keeps working; selecting a secret from this list re-points it here.
|
||||
</p>
|
||||
) : missingHint ? (
|
||||
<p className="text-(length:--text-micro) text-destructive flex items-center gap-1">
|
||||
|
|
|
|||
|
|
@ -352,7 +352,7 @@ describe("Sidebar", () => {
|
|||
|
||||
const sections = [...container.querySelectorAll("nav > div")];
|
||||
const workSection = sections.find((section) => section.textContent?.startsWith("Work"));
|
||||
const companySection = sections.find((section) => section.textContent?.startsWith("Company"));
|
||||
const companySection = sections.find((section) => section.textContent?.startsWith("Organization"));
|
||||
expect(workSection?.textContent).toContain("Skills");
|
||||
expect(companySection?.textContent).not.toContain("Skills");
|
||||
|
||||
|
|
@ -411,7 +411,7 @@ describe("Sidebar", () => {
|
|||
|
||||
const sections = [...container.querySelectorAll("nav > div")];
|
||||
const workSection = sections.find((section) => section.textContent?.startsWith("Work"));
|
||||
const companySection = sections.find((section) => section.textContent?.startsWith("Company"));
|
||||
const companySection = sections.find((section) => section.textContent?.startsWith("Organization"));
|
||||
expect(workSection?.textContent).not.toContain("Timeline");
|
||||
expect(companySection?.textContent).toContain("Timeline");
|
||||
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ export function Sidebar() {
|
|||
|
||||
<SidebarAgents streamlined={streamlined} />
|
||||
|
||||
<SidebarSection label="Company" collapsible={{ open: companyOpen, onOpenChange: setCompanyOpen }}>
|
||||
<SidebarSection label="Organization" collapsible={{ open: companyOpen, onOpenChange: setCompanyOpen }}>
|
||||
<SidebarNavItem to="/org" label="Org" icon={Network} />
|
||||
{showApps ? <SidebarNavItem to="/apps" label="Apps" icon={AppWindow} /> : null}
|
||||
<SidebarNavItem to="/timeline" label="Timeline" icon={GanttChartSquare} />
|
||||
|
|
|
|||
|
|
@ -240,10 +240,10 @@ describe("SidebarCompanyMenu", () => {
|
|||
|
||||
const { root } = renderMenu();
|
||||
await flushReact();
|
||||
await openMenu("Open Acme Labs company switcher");
|
||||
await openMenu("Open Acme Labs organization switcher");
|
||||
|
||||
expect(document.body.textContent).toContain("Couldn't load companies");
|
||||
expect(document.body.textContent).not.toContain("No companies");
|
||||
expect(document.body.textContent).toContain("Couldn't load organizations");
|
||||
expect(document.body.textContent).not.toContain("No organizations");
|
||||
|
||||
const retryItem = Array.from(document.body.querySelectorAll('[role="menuitem"]')).find(
|
||||
(item) => item.textContent?.includes("Try again"),
|
||||
|
|
@ -269,10 +269,10 @@ describe("SidebarCompanyMenu", () => {
|
|||
|
||||
const { root } = renderMenu();
|
||||
await flushReact();
|
||||
await openMenu("Open Acme Labs company switcher");
|
||||
await openMenu("Open Acme Labs organization switcher");
|
||||
|
||||
expect(document.body.textContent).toContain("No companies");
|
||||
expect(document.body.textContent).not.toContain("Couldn't load companies");
|
||||
expect(document.body.textContent).toContain("No organizations");
|
||||
expect(document.body.textContent).not.toContain("Couldn't load organizations");
|
||||
|
||||
act(() => {
|
||||
root.unmount();
|
||||
|
|
@ -295,7 +295,7 @@ describe("SidebarCompanyMenu", () => {
|
|||
await flushReact();
|
||||
await flushReact();
|
||||
|
||||
const trigger = container.querySelector('button[aria-label="Open Acme Labs company switcher"]');
|
||||
const trigger = container.querySelector('button[aria-label="Open Acme Labs organization switcher"]');
|
||||
expect(trigger).not.toBeNull();
|
||||
act(() => {
|
||||
trigger?.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, button: 0 }));
|
||||
|
|
@ -329,7 +329,7 @@ describe("SidebarCompanyMenu", () => {
|
|||
|
||||
expect(container.textContent).toContain("Acme Labs");
|
||||
|
||||
const trigger = container.querySelector('button[aria-label="Open Acme Labs company switcher"]');
|
||||
const trigger = container.querySelector('button[aria-label="Open Acme Labs organization switcher"]');
|
||||
expect(trigger).not.toBeNull();
|
||||
|
||||
act(() => {
|
||||
|
|
@ -338,7 +338,7 @@ describe("SidebarCompanyMenu", () => {
|
|||
});
|
||||
await flushReact();
|
||||
|
||||
expect(document.body.textContent).toContain("Switch company");
|
||||
expect(document.body.textContent).toContain("Switch organization");
|
||||
expect(document.body.textContent).toContain("Edit");
|
||||
expect(document.body.textContent).toContain("Strata");
|
||||
expect(document.body.textContent).toContain("ANA");
|
||||
|
|
@ -359,7 +359,7 @@ describe("SidebarCompanyMenu", () => {
|
|||
expect(mockAuthApi.signOut).toHaveBeenCalledTimes(1);
|
||||
expect(mockNavigateTopLevel).not.toHaveBeenCalled();
|
||||
expect(queryClient.getQueryState(queryKeys.health)?.isInvalidated).toBe(true);
|
||||
expect(document.body.textContent).not.toContain("Switch company");
|
||||
expect(document.body.textContent).not.toContain("Switch organization");
|
||||
|
||||
act(() => {
|
||||
root.unmount();
|
||||
|
|
@ -382,7 +382,7 @@ describe("SidebarCompanyMenu", () => {
|
|||
await flushReact();
|
||||
await flushReact();
|
||||
|
||||
const trigger = container.querySelector('button[aria-label="Open Acme Labs company switcher"]');
|
||||
const trigger = container.querySelector('button[aria-label="Open Acme Labs organization switcher"]');
|
||||
expect(trigger).not.toBeNull();
|
||||
|
||||
act(() => {
|
||||
|
|
@ -439,7 +439,7 @@ describe("SidebarCompanyMenu", () => {
|
|||
await flushReact();
|
||||
await flushReact();
|
||||
|
||||
const trigger = container.querySelector('button[aria-label="Open Acme Labs company switcher"]');
|
||||
const trigger = container.querySelector('button[aria-label="Open Acme Labs organization switcher"]');
|
||||
expect(trigger).not.toBeNull();
|
||||
|
||||
act(() => {
|
||||
|
|
@ -470,7 +470,7 @@ describe("SidebarCompanyMenu", () => {
|
|||
await flushReact();
|
||||
await flushReact();
|
||||
|
||||
await openMenu("Open Acme Labs company switcher");
|
||||
await openMenu("Open Acme Labs organization switcher");
|
||||
|
||||
const createItem = Array.from(document.body.querySelectorAll('[data-slot="dropdown-menu-item"]'))
|
||||
.find((element) => element.textContent?.includes("Create new organization..."));
|
||||
|
|
@ -500,7 +500,7 @@ describe("SidebarCompanyMenu", () => {
|
|||
const { root } = renderMenu();
|
||||
await flushReact();
|
||||
|
||||
const trigger = container.querySelector('button[aria-label="Open Acme Labs company switcher"]');
|
||||
const trigger = container.querySelector('button[aria-label="Open Acme Labs organization switcher"]');
|
||||
expect(trigger).not.toBeNull();
|
||||
expect(trigger?.className).toContain("min-w-0");
|
||||
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ export function SidebarCompanyMenu({ open: controlledOpen, onOpenChange }: Sideb
|
|||
?? null
|
||||
: null;
|
||||
const createStackUrl = isCloud ? cloudStackCreateUrl(cloudBaseUrl) : null;
|
||||
const switcherNoun = isCloud ? "organization" : "company";
|
||||
const switcherNoun = "organization";
|
||||
// The one name the chrome shows for "where am I": the stack in cloud, the
|
||||
// company when self-hosted.
|
||||
const currentName = isCloud
|
||||
|
|
@ -368,7 +368,7 @@ export function SidebarCompanyMenu({ open: controlledOpen, onOpenChange }: Sideb
|
|||
)}
|
||||
title={currentName ?? undefined}
|
||||
>
|
||||
{currentName ?? (isCloud ? "Select organization" : "Select company")}
|
||||
{currentName ?? `Select ${switcherNoun}`}
|
||||
</span>
|
||||
</span>
|
||||
{!rail && <ChevronsUpDown className="size-3.5 shrink-0 text-muted-foreground" />}
|
||||
|
|
@ -377,7 +377,7 @@ export function SidebarCompanyMenu({ open: controlledOpen, onOpenChange }: Sideb
|
|||
<DropdownMenuContent align="start" sideOffset={8} className="w-64 p-1">
|
||||
<div className="flex items-center justify-between gap-2 px-2 py-1.5">
|
||||
<DropdownMenuLabel className="p-0 text-(length:--text-micro) font-semibold uppercase text-muted-foreground">
|
||||
{isCloud ? "Switch organization" : "Switch company"}
|
||||
{`Switch ${switcherNoun}`}
|
||||
</DropdownMenuLabel>
|
||||
{/* Stack order is owned by cloud's own portfolio in v1, so the
|
||||
drag-to-reorder affordance stays self-hosted-only. */}
|
||||
|
|
@ -445,7 +445,7 @@ export function SidebarCompanyMenu({ open: controlledOpen, onOpenChange }: Sideb
|
|||
// offer the way back.
|
||||
companyListUnavailable ? (
|
||||
<>
|
||||
<DropdownMenuItem disabled>Couldn't load companies</DropdownMenuItem>
|
||||
<DropdownMenuItem disabled>Couldn't load organizations</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onSelect={(event) => {
|
||||
// Keep the menu open so the result of the retry is visible.
|
||||
|
|
@ -458,7 +458,7 @@ export function SidebarCompanyMenu({ open: controlledOpen, onOpenChange }: Sideb
|
|||
</DropdownMenuItem>
|
||||
</>
|
||||
) : (
|
||||
<DropdownMenuItem disabled>No companies</DropdownMenuItem>
|
||||
<DropdownMenuItem disabled>No organizations</DropdownMenuItem>
|
||||
)
|
||||
) : null}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ export function TrustPresetSection({
|
|||
</div>
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
CE saves one containment boundary at a time. Saved policies include this company id.
|
||||
CE saves one containment boundary at a time. Saved policies include this organization id.
|
||||
</p>
|
||||
{boundaryTarget ? (
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -987,8 +987,8 @@ export function WorkspaceFileBrowser({
|
|||
body = (
|
||||
<StateMessage
|
||||
icon={<FolderOpen aria-hidden="true" className="h-5 w-5 text-muted-foreground" />}
|
||||
title="No company selected"
|
||||
body="Choose a company before browsing another project workspace."
|
||||
title="No organization selected"
|
||||
body="Choose an organization before browsing another project workspace."
|
||||
/>
|
||||
);
|
||||
} else if (source === "other" && projectsQuery.isFetching && projectsWithWorkspaces.length === 0) {
|
||||
|
|
@ -1004,7 +1004,7 @@ export function WorkspaceFileBrowser({
|
|||
<StateMessage
|
||||
icon={<FolderOpen aria-hidden="true" className="h-5 w-5 text-muted-foreground" />}
|
||||
title="No project workspaces"
|
||||
body="No same-company project has a registered workspace to browse."
|
||||
body="No same-organization project has a registered workspace to browse."
|
||||
/>
|
||||
);
|
||||
} else if (listQuery.isFetching && !data) {
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export function SecretPopoverForm({
|
|||
<PopoverTitle className="text-sm font-medium">{heading}</PopoverTitle>
|
||||
{mode === "store" ? (
|
||||
<PopoverDescription className="text-(length:--text-micro) text-muted-foreground">
|
||||
Moves the typed value into an encrypted company secret and binds{" "}
|
||||
Moves the typed value into an encrypted organization secret and binds{" "}
|
||||
<span className="font-mono">{initialName || "this variable"}</span> to it.
|
||||
</PopoverDescription>
|
||||
) : null}
|
||||
|
|
|
|||
|
|
@ -806,9 +806,9 @@ describe("EnvironmentVariablesEditor", () => {
|
|||
pointerClick(sourceButton!);
|
||||
settleFakeTimers();
|
||||
const secretItem = [...document.querySelectorAll<HTMLElement>('[role="menuitem"]')].find((el) =>
|
||||
el.textContent?.includes("Company secret"),
|
||||
el.textContent?.includes("Organization secret"),
|
||||
);
|
||||
expect(secretItem, "Company secret menu item should be present").toBeTruthy();
|
||||
expect(secretItem, "Organization secret menu item should be present").toBeTruthy();
|
||||
pointerClick(secretItem!);
|
||||
settleFakeTimers();
|
||||
// The store popover is open (heading rendered) and stays open — it must not
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ export function EnvironmentVariableRow({
|
|||
row.source === "text"
|
||||
? "Text value"
|
||||
: row.source === "secret"
|
||||
? "Company secret reference"
|
||||
? "Organization secret reference"
|
||||
: "User secret reference";
|
||||
const nameErrorId = `${row.id}-name-error`;
|
||||
const healthId = `${row.id}-health`;
|
||||
|
|
@ -284,8 +284,8 @@ export function EnvironmentVariableRow({
|
|||
<span className="text-(length:--text-micro) text-muted-foreground">Store the value inline as plain text.</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="flex-col items-start gap-0.5" onSelect={() => switchSource("secret")}>
|
||||
<span className="text-sm">Company secret</span>
|
||||
<span className="text-(length:--text-micro) text-muted-foreground">Resolve a stored company secret at run start.</span>
|
||||
<span className="text-sm">Organization secret</span>
|
||||
<span className="text-(length:--text-micro) text-muted-foreground">Resolve a stored organization secret at run start.</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="flex-col items-start gap-0.5" onSelect={() => switchSource("user_secret")}>
|
||||
<span className="text-sm">User secret</span>
|
||||
|
|
|
|||
|
|
@ -426,8 +426,8 @@ describe("FolderControls", () => {
|
|||
});
|
||||
|
||||
const body = document.body.textContent ?? "";
|
||||
expect(body.indexOf("My Skills")).toBeLessThan(body.indexOf("Company"));
|
||||
expect(body.indexOf("Company")).toBeLessThan(body.indexOf("Engineering"));
|
||||
expect(body.indexOf("My Skills")).toBeLessThan(body.indexOf("Organization"));
|
||||
expect(body.indexOf("Organization")).toBeLessThan(body.indexOf("Engineering"));
|
||||
expect(body.indexOf("Engineering")).toBeLessThan(body.indexOf("Code Review"));
|
||||
expect(body.indexOf("Code Review")).toBeLessThan(body.indexOf("Projects"));
|
||||
expect(body.indexOf("Projects")).toBeLessThan(body.indexOf("Bundled"));
|
||||
|
|
|
|||
|
|
@ -466,7 +466,7 @@ export function MobileFolderSheet({
|
|||
<>
|
||||
{model.my ? renderBranch(model.my, "My Skills") : null}
|
||||
<div className="px-2 pb-0.5 pt-2 text-(length:--text-micro) font-medium uppercase tracking-wide text-muted-foreground">
|
||||
Company
|
||||
Organization
|
||||
</div>
|
||||
{model.company.map((node) => renderBranch(node))}
|
||||
{model.projects ? renderBranch(model.projects, "Projects") : null}
|
||||
|
|
@ -637,7 +637,7 @@ export function FolderFormDialog({
|
|||
<DialogHeader>
|
||||
<DialogTitle>{isEdit ? "Edit folder" : "Create folder"}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{kind === "routine" ? "Organize routines in this company." : "Organize installed company skills."}
|
||||
{kind === "routine" ? "Organize routines in this organization." : "Organize installed organization skills."}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
|
|
|
|||
|
|
@ -311,7 +311,7 @@ export function SkillFolderRail({
|
|||
) : null}
|
||||
|
||||
{/* Company — plain top-level company folders */}
|
||||
<RailHeading label="Company" onCreate={() => onCreateFolder(null)} />
|
||||
<RailHeading label="Organization" onCreate={() => onCreateFolder(null)} />
|
||||
{model.company.length > 0 ? (
|
||||
model.company.map((node) => (
|
||||
<TreeBranch
|
||||
|
|
@ -335,7 +335,7 @@ export function SkillFolderRail({
|
|||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="px-2 py-1 text-xs text-muted-foreground">No company folders yet.</div>
|
||||
<div className="px-2 py-1 text-xs text-muted-foreground">No organization folders yet.</div>
|
||||
)}
|
||||
|
||||
{/* Projects — auto-managed, read-only structure */}
|
||||
|
|
@ -649,7 +649,7 @@ function TreeBranch({
|
|||
<DropdownMenuItem onSelect={() => onEditFolder(folder)}>Edit color</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={() => onMoveFolder(folder, isInMySkills ? "company" : "my")}>
|
||||
<MoveRight className="h-3.5 w-3.5" />
|
||||
Move to {isInMySkills ? "Company" : "My Skills"}
|
||||
Move to {isInMySkills ? "Organization" : "My Skills"}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem variant="destructive" onSelect={() => onDeleteFolder(folder)}>
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ describe("reservedRootLabel", () => {
|
|||
describe("skillFolderDisplayPath", () => {
|
||||
it("prefixes company folders and preserves reserved-root labels", () => {
|
||||
const model = buildSkillFolderTree(folders);
|
||||
expect(skillFolderDisplayPath(model, "eng-review")).toBe("Company / Engineering / Review");
|
||||
expect(skillFolderDisplayPath(model, "eng-review")).toBe("Organization / Engineering / Review");
|
||||
expect(skillFolderDisplayPath(model, "mine")).toBe("My Skills / dotta");
|
||||
expect(skillFolderDisplayPath(model, "bundled-git")).toBe("Bundled / git");
|
||||
expect(skillFolderDisplayPath(model, null)).toBeNull();
|
||||
|
|
@ -110,7 +110,7 @@ describe("skillFolderDisplayPath", () => {
|
|||
|
||||
describe("skillFolderPathDisplayFallback", () => {
|
||||
it("humanizes cold detail paths before folder metadata loads", () => {
|
||||
expect(skillFolderPathDisplayFallback("engineering/code-review")).toBe("Company / Engineering / Code Review");
|
||||
expect(skillFolderPathDisplayFallback("engineering/code-review")).toBe("Organization / Engineering / Code Review");
|
||||
expect(skillFolderPathDisplayFallback("my/local-board/drafts")).toBe("My Skills / Local Board / Drafts");
|
||||
expect(skillFolderPathDisplayFallback("bundled/review-pr")).toBe("Bundled / Review Pr");
|
||||
expect(skillFolderPathDisplayFallback(null)).toBeNull();
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ export function skillFolderDisplayPath(
|
|||
const trail = folderBreadcrumbTrail(model, folderId);
|
||||
if (trail.length === 0) return null;
|
||||
const labels = trail.map((folder) => reservedRootLabel(folder));
|
||||
if (!trail[0]?.systemKey) labels.unshift("Company");
|
||||
if (!trail[0]?.systemKey) labels.unshift("Organization");
|
||||
return labels.join(" / ");
|
||||
}
|
||||
|
||||
|
|
@ -189,7 +189,7 @@ export function skillFolderPathDisplayFallback(folderPath: string | null | undef
|
|||
if (root === "my") labels[0] = "My Skills";
|
||||
else if (root === "projects") labels[0] = "Projects";
|
||||
else if (root === "bundled") labels[0] = "Bundled";
|
||||
else labels.unshift("Company");
|
||||
else labels.unshift("Organization");
|
||||
return labels.join(" / ");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -85,8 +85,8 @@ const activityGatePolicyOptions = [
|
|||
const activityGateScopeOptions = [
|
||||
{
|
||||
value: "company",
|
||||
title: "Company-wide",
|
||||
description: "Any activity across the company counts as a reason to run.",
|
||||
title: "Organization-wide",
|
||||
description: "Any activity across the organization counts as a reason to run.",
|
||||
},
|
||||
{
|
||||
value: "project",
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ function platformDenial() {
|
|||
describe("SkillPolicyDenialNotice", () => {
|
||||
it("renders a State B policy denial with title and remediation", () => {
|
||||
const el = render(<SkillPolicyDenialNotice denial={policyDenial()} />);
|
||||
expect(el.textContent).toContain("restricted by your company policy");
|
||||
expect(el.textContent).toContain("restricted by your organization policy");
|
||||
expect(el.textContent).toContain("administrator can change the skill policy");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -967,7 +967,7 @@ export const humanOnlyRequestConfirmationInteraction =
|
|||
createRequestConfirmationInteraction({
|
||||
id: "interaction-confirmation-human-only",
|
||||
title: "Approve the customer-facing announcement",
|
||||
summary: "Reserved for a human on the board because it commits the company publicly.",
|
||||
summary: "Reserved for a human on the board because it commits the organization publicly.",
|
||||
requestedResolverPolicy: "human_only",
|
||||
});
|
||||
|
||||
|
|
@ -976,7 +976,7 @@ export const companyCappedRequestConfirmationInteraction =
|
|||
createRequestConfirmationInteraction({
|
||||
id: "interaction-confirmation-company-capped",
|
||||
title: "Confirm the data retention change",
|
||||
summary: "Asked for an open audience; the company caps this kind at a human decision.",
|
||||
summary: "Asked for an open audience; the organization caps this kind at a human decision.",
|
||||
requestedResolverPolicy: "anyone",
|
||||
effectiveResolverPolicy: "human_only",
|
||||
effectiveResolverPolicySource: "company_cap",
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ export function useResourceMembershipMutation(companyId: string | null | undefin
|
|||
|
||||
return useMutation({
|
||||
mutationFn: (variables: MutationVariables) => {
|
||||
if (!companyId) throw new Error("Select a company first.");
|
||||
if (!companyId) throw new Error("Select an organization first.");
|
||||
const body = { state: variables.state, starred: variables.starred };
|
||||
return variables.resourceType === "project"
|
||||
? resourceMembershipsApi.updateProject(companyId, variables.resourceId, body)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"app": {
|
||||
"noCompanies": {
|
||||
"title": "Create your first company",
|
||||
"description": "Get started by creating a company.",
|
||||
"newCompany": "New Company"
|
||||
"title": "Create your first organization",
|
||||
"description": "Get started by creating an organization.",
|
||||
"newCompany": "New Organization"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,8 +91,8 @@ const ACTIVITY_ROW_VERBS: Record<string, string> = {
|
|||
"goal.deleted": "deleted",
|
||||
"cost.reported": "reported cost for",
|
||||
"cost.recorded": "recorded cost for",
|
||||
"company.created": "created company",
|
||||
"company.updated": "updated company",
|
||||
"company.created": "created organization",
|
||||
"company.updated": "updated organization",
|
||||
"company.archived": "archived",
|
||||
"company.reactivated": "reactivated",
|
||||
"company.budget_updated": "updated budget for",
|
||||
|
|
|
|||
|
|
@ -79,14 +79,14 @@ describe("describeInteractionAudience", () => {
|
|||
expect(audience.narrowedBy).toBeNull();
|
||||
expect(audience.narrowedNote).toBeNull();
|
||||
expect(audience.summary).toBe(
|
||||
"Anyone in the company can respond — the board or any agent, including the one that asked.",
|
||||
"Anyone in the organization can respond — the board or any agent, including the one that asked.",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not present an open card as board-required", () => {
|
||||
const audience = describeInteractionAudience({ interaction: confirmation() });
|
||||
expect(audience.summary).not.toMatch(/only .*board/i);
|
||||
expect(audience.summary).toMatch(/anyone in the company/i);
|
||||
expect(audience.summary).toMatch(/anyone in the organization/i);
|
||||
});
|
||||
|
||||
it("names the excluded creator for an explicit not_creator restriction", () => {
|
||||
|
|
@ -102,7 +102,7 @@ describe("describeInteractionAudience", () => {
|
|||
expect(audience.isOpen).toBe(false);
|
||||
expect(audience.label).toBe("Anyone except creator");
|
||||
expect(audience.narrowedBy).toBe("requested");
|
||||
expect(audience.summary).toBe("Anyone in the company except ClaudeCoder can respond.");
|
||||
expect(audience.summary).toBe("Anyone in the organization except ClaudeCoder can respond.");
|
||||
// An explicitly requested restriction needs no extra explanation.
|
||||
expect(audience.narrowedNote).toBeNull();
|
||||
});
|
||||
|
|
@ -116,7 +116,7 @@ describe("describeInteractionAudience", () => {
|
|||
}),
|
||||
});
|
||||
expect(audience.summary).toBe(
|
||||
"Anyone in the company except the agent that created it can respond.",
|
||||
"Anyone in the organization except the agent that created it can respond.",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -189,7 +189,7 @@ describe("describeInteractionAudience", () => {
|
|||
});
|
||||
expect(audience.narrowedBy).toBe("company_cap");
|
||||
expect(audience.narrowedNote).toBe(
|
||||
"Company interaction governance narrowed this from Anyone to Human only.",
|
||||
"Organization interaction governance narrowed this from Anyone to Human only.",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -206,7 +206,7 @@ describe("describeInteractionAudience", () => {
|
|||
expect(audience.narrowedNote).toBe(
|
||||
"Created before Anyone became the default, so it stays restricted. A new card would be open.",
|
||||
);
|
||||
expect(audience.summary).toBe("Anyone in the company except ClaudeCoder can respond.");
|
||||
expect(audience.summary).toBe("Anyone in the organization except ClaudeCoder can respond.");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -294,7 +294,7 @@ describe("describeAttentionResolverAudience", () => {
|
|||
})));
|
||||
expect(audience?.narrowedBy).toBe("company_cap");
|
||||
expect(audience?.narrowedNote).toBe(
|
||||
"Company interaction governance narrowed this from Anyone to Human only.",
|
||||
"Organization interaction governance narrowed this from Anyone to Human only.",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -50,9 +50,9 @@ const RESOLVER_POLICY_LABELS: Record<IssueThreadInteractionCanonicalResolverPoli
|
|||
*/
|
||||
const RESOLVER_POLICY_EFFECTS: Record<IssueThreadInteractionCanonicalResolverPolicy, string> = {
|
||||
anyone:
|
||||
"Anyone in the company can respond — the board or any agent, including the one that asked.",
|
||||
"Anyone in the organization can respond — the board or any agent, including the one that asked.",
|
||||
not_creator:
|
||||
"Anyone in the company except the agent that created the card, and its run. Use this when the answer has to come from someone else.",
|
||||
"Anyone in the organization except the agent that created the card, and its run. Use this when the answer has to come from someone else.",
|
||||
human_only: "Only a person on the board can respond. Agents are turned away.",
|
||||
};
|
||||
|
||||
|
|
@ -184,8 +184,8 @@ export function describeResolverAudience({
|
|||
: hasAddressee
|
||||
? `Only ${addressee} or a person on the board can respond.`
|
||||
: policy === "not_creator"
|
||||
? `Anyone in the company except ${creator} can respond.`
|
||||
: "Anyone in the company can respond — the board or any agent, including the one that asked.";
|
||||
? `Anyone in the organization except ${creator} can respond.`
|
||||
: "Anyone in the organization can respond — the board or any agent, including the one that asked.";
|
||||
|
||||
// Same fact, fewer words: a collapsed row has to answer "is this mine to
|
||||
// decide?" in one glance, next to the buttons that act on the answer.
|
||||
|
|
@ -207,7 +207,7 @@ export function describeResolverAudience({
|
|||
const narrowedNote = source === "governed_action"
|
||||
? "This card runs a governed action, so it stays human-only whatever audience was requested."
|
||||
: source === "company_cap"
|
||||
? `Company interaction governance narrowed this from ${RESOLVER_POLICY_LABELS[requestedPolicy]} to ${RESOLVER_POLICY_LABELS[policy]}.`
|
||||
? `Organization interaction governance narrowed this from ${RESOLVER_POLICY_LABELS[requestedPolicy]} to ${RESOLVER_POLICY_LABELS[policy]}.`
|
||||
: provenance === "legacy_inherited_restriction"
|
||||
? "Created before Anyone became the default, so it stays restricted. A new card would be open."
|
||||
: null;
|
||||
|
|
|
|||
|
|
@ -37,14 +37,14 @@ describe("classifySkillDenial", () => {
|
|||
);
|
||||
expect(denial).not.toBeNull();
|
||||
expect(denial?.state).toBe("policy");
|
||||
expect(denial?.title).toBe("Installing external skills is restricted by your company policy.");
|
||||
expect(denial?.title).toBe("Installing external skills is restricted by your organization policy.");
|
||||
expect(denial?.remediation).toBe("Contact a company administrator to change the skill policy.");
|
||||
});
|
||||
|
||||
it("recognises a policy denial from reason alone (policy_default) and falls back to default remediation", () => {
|
||||
const denial = classifySkillDenial(apiError(403, { reason: "policy_default" }));
|
||||
expect(denial?.state).toBe("policy");
|
||||
expect(denial?.title).toBe("This action is restricted by your company policy.");
|
||||
expect(denial?.title).toBe("This action is restricted by your organization policy.");
|
||||
expect(denial?.remediation).toContain("administrator can change the skill policy");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -59,14 +59,14 @@ export interface SkillDenial {
|
|||
}
|
||||
|
||||
const DEFAULT_POLICY_REMEDIATION =
|
||||
"A company administrator can change the skill policy to allow this.";
|
||||
"An organization administrator can change the skill policy to allow this.";
|
||||
const DEFAULT_ADMIN_REMEDIATION =
|
||||
"This requires company administration access. Ask an administrator to make this change.";
|
||||
"This requires organization administration access. Ask an administrator to make this change.";
|
||||
|
||||
/** Human-readable titles for the platform-invariant codes (State C). */
|
||||
const PLATFORM_TITLES: Record<string, string> = {
|
||||
skill_authentication_required: "Sign in to manage skills.",
|
||||
skill_company_boundary_denied: "This skill belongs to another company.",
|
||||
skill_company_boundary_denied: "This skill belongs to another organization.",
|
||||
skill_workspace_boundary_denied: "This skill source is outside an allowed workspace.",
|
||||
skill_source_validation_failed: "This skill source failed validation.",
|
||||
skill_unsafe_content_blocked: "This skill contains unsafe content.",
|
||||
|
|
@ -77,9 +77,9 @@ const PLATFORM_TITLES: Record<string, string> = {
|
|||
/** Default remediation copy per platform-invariant code — framed as a fix, never a grant. */
|
||||
const PLATFORM_REMEDIATIONS: Record<string, string> = {
|
||||
skill_authentication_required: "Sign in and try again.",
|
||||
skill_company_boundary_denied: "Open the skill from the company that owns it.",
|
||||
skill_company_boundary_denied: "Open the skill from the organization that owns it.",
|
||||
skill_workspace_boundary_denied:
|
||||
"Import from a configured Paperclip workspace or the company managed-skill directory.",
|
||||
"Import from a configured Paperclip workspace or the organization managed-skill directory.",
|
||||
skill_source_validation_failed: "Fix the flagged source and retry.",
|
||||
skill_unsafe_content_blocked:
|
||||
"Remove the fetch-and-execute or unsafe pattern before saving.",
|
||||
|
|
@ -119,8 +119,8 @@ export function classifySkillDenial(
|
|||
|| reason === "policy_default";
|
||||
if (isPolicyDenial) {
|
||||
const title = actionLabel
|
||||
? `${actionLabel} is restricted by your company policy.`
|
||||
: "This action is restricted by your company policy.";
|
||||
? `${actionLabel} is restricted by your organization policy.`
|
||||
: "This action is restricted by your organization policy.";
|
||||
return {
|
||||
state: "policy",
|
||||
code,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export const TRUST_PRESET_LABELS: Record<TrustPreset, string> = {
|
|||
};
|
||||
|
||||
export const TRUST_PRESET_DESCRIPTIONS: Record<TrustPreset, string> = {
|
||||
standard: "Company-visible collaboration. This is the default for normal work.",
|
||||
standard: "Organization-visible collaboration. This is the default for normal work.",
|
||||
low_trust_review:
|
||||
"Contained for hostile or untrusted input. Narrow Paperclip API, quarantined output. Use for PR review and external-content triage.",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -298,7 +298,7 @@ describe("resolveWorkspaceAccessState", () => {
|
|||
},
|
||||
});
|
||||
expect(access).toMatchObject({ state: "validating", action: { kind: "wait" } });
|
||||
expect(access.description).toContain("restored no company or issue rows");
|
||||
expect(access.description).toContain("restored no organization or issue rows");
|
||||
});
|
||||
|
||||
it("degrades a verified clone whose database regressed and offers one repair", () => {
|
||||
|
|
@ -398,7 +398,7 @@ describe("describeWorkspaceReadinessCause", () => {
|
|||
failurePhase: "cloned_membership_missing",
|
||||
},
|
||||
}),
|
||||
).toBe("No cloned user has an active company membership.");
|
||||
).toBe("No cloned user has an active organization membership.");
|
||||
});
|
||||
|
||||
it("falls back to the reason and finally to null", () => {
|
||||
|
|
|
|||
|
|
@ -106,9 +106,9 @@ const HANDOFF_REASON_COPY: Record<string, string> = {
|
|||
|
||||
const READINESS_FAILURE_COPY: Record<string, string> = {
|
||||
database_unreachable: "The isolated database is not answering.",
|
||||
clone_data_missing: "The clone restored no company or issue rows.",
|
||||
clone_data_missing: "The clone restored no organization or issue rows.",
|
||||
clone_data_unreadable: "The cloned product tables could not be read.",
|
||||
cloned_membership_missing: "No cloned user has an active company membership.",
|
||||
cloned_membership_missing: "No cloned user has an active organization membership.",
|
||||
cloned_identity_unreadable: "The cloned identity tables could not be read.",
|
||||
auth_handoff_not_configured: "The workspace was started without a login handoff key.",
|
||||
seed_manifest_unreadable: "The seed manifest is unreadable, so the restore cannot be trusted.",
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ export function AdapterManager() {
|
|||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([
|
||||
{ label: selectedCompany?.name ?? "Company", href: "/dashboard" },
|
||||
{ label: selectedCompany?.name ?? "Organization", href: "/dashboard" },
|
||||
{ label: "Settings", href: "/company/settings" },
|
||||
{ label: "Adapters" },
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -2166,9 +2166,9 @@ function ConfigurationTab({
|
|||
: taskAssignSource === "agent_creator"
|
||||
? "Enabled automatically while this agent can create new agents."
|
||||
: taskAssignSource === "explicit_grant"
|
||||
? "Enabled via explicit company permission grant."
|
||||
? "Enabled via explicit organization permission grant."
|
||||
: taskAssignSource === "simple_default"
|
||||
? "Enabled by simple company-wide task assignment defaults."
|
||||
? "Enabled by simple organization-wide task assignment defaults."
|
||||
: "Disabled unless explicitly granted.";
|
||||
|
||||
return (
|
||||
|
|
@ -2243,7 +2243,7 @@ function ConfigurationTab({
|
|||
<div className="space-y-1">
|
||||
<div>Can create/import skills</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Lets this agent install, import, create, and scan company skills without creating agents.
|
||||
Lets this agent install, import, create, and scan organization skills without creating agents.
|
||||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
|
|
@ -2430,7 +2430,7 @@ export function PromptsTab({
|
|||
|
||||
const uploadMarkdownImage = useMutation({
|
||||
mutationFn: async ({ file, namespace }: { file: File; namespace: string }) => {
|
||||
if (!selectedCompanyId) throw new Error("Select a company to upload images");
|
||||
if (!selectedCompanyId) throw new Error("Select an organization to upload images");
|
||||
return assetsApi.uploadImage(selectedCompanyId, file, namespace);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ describe("AgentToolsTab", () => {
|
|||
expect(text).toContain("Production GitHub");
|
||||
expect(text).toContain("GitHub safe");
|
||||
expect(text).toContain("Access profiles");
|
||||
expect(text).toContain("Company default");
|
||||
expect(text).toContain("Organization default");
|
||||
expect(container.querySelector('a[href="/apps/advanced/profiles/prof-1"]')?.textContent).toBe("GitHub safe");
|
||||
expect(container.querySelector('a[href="/apps/advanced/profiles?check=1"]')?.textContent).toBe("Check access");
|
||||
// Governing policy #1 is the company-wide require_approval rule.
|
||||
|
|
|
|||
|
|
@ -549,7 +549,7 @@ export function AgentToolsTab({ agent, companyId }: { agent: AgentDetailRecord;
|
|||
{profile.summary.isCompanyDefault ? (
|
||||
<div className="mt-1">
|
||||
<span className="rounded border border-border px-1.5 py-0.5 text-(length:--text-nano) uppercase text-muted-foreground">
|
||||
Company default
|
||||
Organization default
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
|
|
|||
|
|
@ -328,7 +328,7 @@ export function Agents() {
|
|||
}, [builtInAgentsEnabled, instanceSettings, navigate, requestedTab, selectedCompanyId]);
|
||||
|
||||
if (!selectedCompanyId) {
|
||||
return <EmptyState icon={Bot} message="Select a company to view agents." />;
|
||||
return <EmptyState icon={Bot} message="Select an organization to view agents." />;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export function Approvals() {
|
|||
).length;
|
||||
|
||||
if (!selectedCompanyId) {
|
||||
return <p className="text-sm text-muted-foreground">Select a company first.</p>;
|
||||
return <p className="text-sm text-muted-foreground">Select an organization first.</p>;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ export function Artifacts() {
|
|||
}, [setBreadcrumbs, viewingSelectedStack, selectedGroup]);
|
||||
|
||||
if (!selectedCompanyId) {
|
||||
return <EmptyState icon={Package} message="Select a company to view artifacts." />;
|
||||
return <EmptyState icon={Package} message="Select an organization to view artifacts." />;
|
||||
}
|
||||
|
||||
const showGroupCards = viewingStackList;
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ function hasWelcome(container: HTMLElement) {
|
|||
}
|
||||
|
||||
function hasChips(container: HTMLElement) {
|
||||
return (container.textContent ?? "").includes("Draft a Company Brief");
|
||||
return (container.textContent ?? "").includes("Draft an Organization Brief");
|
||||
}
|
||||
|
||||
describe("BoardChat staged typing intro", () => {
|
||||
|
|
|
|||
|
|
@ -658,9 +658,9 @@ export function BoardChat() {
|
|||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center max-w-sm">
|
||||
<h2 className="text-lg font-semibold">No company selected</h2>
|
||||
<h2 className="text-lg font-semibold">No organization selected</h2>
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
Select a company to start chatting with your board concierge.
|
||||
Select an organization to start chatting with your board concierge.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -692,7 +692,7 @@ export function BoardChat() {
|
|||
{ceoAgent?.name ?? "Conference Room"}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{selectedCompany?.name ?? "Your company"}
|
||||
{selectedCompany?.name ?? "Your organization"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-0.5">
|
||||
|
|
@ -756,8 +756,8 @@ export function BoardChat() {
|
|||
|
||||
const chips: Array<{ label: string; prompt: string }> = [
|
||||
{
|
||||
label: "Draft a Company Brief",
|
||||
prompt: `Draft a one-page Company Brief for ${companyName} — include our mission, team roster, and first priorities.`,
|
||||
label: "Draft an Organization Brief",
|
||||
prompt: `Draft a one-page Organization Brief for ${companyName} — include our mission, team roster, and first priorities.`,
|
||||
},
|
||||
{
|
||||
label: "Create a hiring plan",
|
||||
|
|
@ -967,7 +967,7 @@ export function BoardChat() {
|
|||
value={input}
|
||||
onChange={setInput}
|
||||
onSubmit={handleSend}
|
||||
placeholder="Ask anything about your company..."
|
||||
placeholder="Ask anything about your organization..."
|
||||
submitKey="enter"
|
||||
surface="translucent"
|
||||
submitting={sending}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ export function BoardClaimPage() {
|
|||
<Card className="block p-6">
|
||||
<h1 className="text-xl font-semibold">Claim Board ownership</h1>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
This will promote your user to instance admin and migrate company ownership access from local trusted mode.
|
||||
This will promote your user to instance admin and migrate organization ownership access from local trusted mode.
|
||||
</p>
|
||||
|
||||
{claimMutation.error && (
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ function ClaimSuccess() {
|
|||
<div>
|
||||
<h1 className="text-xl font-semibold">You’re the instance admin</h1>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
Setup is complete. Taking you to onboarding to create your first company…
|
||||
Setup is complete. Taking you to onboarding to create your first organization…
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ export function CliAuthPage() {
|
|||
</div>
|
||||
{challenge.requestedCompanyName && (
|
||||
<div>
|
||||
<div className="text-muted-foreground">Requested company</div>
|
||||
<div className="text-muted-foreground">Requested organization</div>
|
||||
<div className="text-foreground">{challenge.requestedCompanyName}</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ describe("Companies page", () => {
|
|||
it("offers the company wizard when self-hosted", async () => {
|
||||
const root = await renderPage();
|
||||
|
||||
expect(container.textContent).toContain("New Company");
|
||||
expect(container.textContent).toContain("New Organization");
|
||||
|
||||
act(() => {
|
||||
root.unmount();
|
||||
|
|
@ -115,7 +115,7 @@ describe("Companies page", () => {
|
|||
|
||||
// Cloud stacks hold exactly one company and POST /companies is a 403 floor,
|
||||
// so the entry point must not be offered at all.
|
||||
expect(container.textContent).not.toContain("New Company");
|
||||
expect(container.textContent).not.toContain("New Organization");
|
||||
expect(container.textContent).toContain("Acme Labs");
|
||||
expect(mockOpenOnboarding).not.toHaveBeenCalled();
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export function Companies() {
|
|||
});
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([{ label: "Companies" }]);
|
||||
setBreadcrumbs([{ label: "Organizations" }]);
|
||||
}, [setBreadcrumbs]);
|
||||
|
||||
function startEdit(companyId: string, currentName: string) {
|
||||
|
|
@ -113,13 +113,13 @@ export function Companies() {
|
|||
{isCloud ? null : (
|
||||
<Button size="sm" onClick={() => openOnboarding()}>
|
||||
<Plus className="h-3.5 w-3.5 mr-1.5" />
|
||||
New Company
|
||||
New Organization
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="h-6">
|
||||
{loading && <p className="text-sm text-muted-foreground">Loading companies...</p>}
|
||||
{loading && <p className="text-sm text-muted-foreground">Loading organizations...</p>}
|
||||
{error && <p className="text-sm text-destructive">{error.message}</p>}
|
||||
</div>
|
||||
|
||||
|
|
@ -253,7 +253,7 @@ export function Companies() {
|
|||
onClick={() => setConfirmDeleteId(company.id)}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
Delete Company
|
||||
Delete Organization
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
|
@ -296,7 +296,7 @@ export function Companies() {
|
|||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<p className="text-sm text-destructive font-medium">
|
||||
Delete this company and all its data? This cannot be undone.
|
||||
Delete this organization and all its data? This cannot be undone.
|
||||
</p>
|
||||
<div className="flex items-center gap-2 ml-4 shrink-0">
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ describe("CompanyAccess", () => {
|
|||
});
|
||||
await flushReact();
|
||||
|
||||
expect(document.body.textContent).toContain("Update company role and membership status");
|
||||
expect(document.body.textContent).toContain("Update organization role and membership status");
|
||||
expect(document.body.textContent).not.toContain("Implicit grants from role");
|
||||
expect(document.body.textContent).not.toContain("permissionKey");
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export function CompanyAccess() {
|
|||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([
|
||||
{ label: selectedCompany?.name ?? "Company", href: "/dashboard" },
|
||||
{ label: selectedCompany?.name ?? "Organization", href: "/dashboard" },
|
||||
{ label: "Settings", href: "/company/settings" },
|
||||
{ label: "Members" },
|
||||
]);
|
||||
|
|
@ -202,20 +202,20 @@ export function CompanyAccess() {
|
|||
}, [removingMember]);
|
||||
|
||||
if (!selectedCompanyId) {
|
||||
return <div className="text-sm text-muted-foreground">Select a company to manage access.</div>;
|
||||
return <div className="text-sm text-muted-foreground">Select an organization to manage access.</div>;
|
||||
}
|
||||
|
||||
if (membersQuery.isLoading) {
|
||||
return <div className="text-sm text-muted-foreground">Loading company access…</div>;
|
||||
return <div className="text-sm text-muted-foreground">Loading organization access…</div>;
|
||||
}
|
||||
|
||||
if (membersQuery.error) {
|
||||
const message =
|
||||
membersQuery.error instanceof ApiError && membersQuery.error.status === 403
|
||||
? "You do not have permission to manage company members."
|
||||
? "You do not have permission to manage organization members."
|
||||
: membersQuery.error instanceof Error
|
||||
? membersQuery.error.message
|
||||
: "Failed to load company members.";
|
||||
: "Failed to load organization members.";
|
||||
return <div className="text-sm text-destructive">{message}</div>;
|
||||
}
|
||||
|
||||
|
|
@ -238,12 +238,12 @@ export function CompanyAccess() {
|
|||
<div className="max-w-6xl space-y-8">
|
||||
<div className="flex items-center gap-2">
|
||||
<ShieldCheck className="h-5 w-5 text-muted-foreground" />
|
||||
<h1 className="text-lg font-semibold">Company Members</h1>
|
||||
<h1 className="text-lg font-semibold">Organization Members</h1>
|
||||
</div>
|
||||
|
||||
{access && !access.currentUserRole && (
|
||||
<div className="rounded-xl bg-amber-500/10 px-4 py-3 text-sm text-amber-800 dark:text-amber-200">
|
||||
This account can manage access here through instance-admin privileges, but it does not currently hold an active company membership.
|
||||
This account can manage access here through instance-admin privileges, but it does not currently hold an active organization membership.
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -254,7 +254,7 @@ export function CompanyAccess() {
|
|||
<div>
|
||||
<h3 className="text-sm font-semibold">Pending human joins</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Review pending join requests before they become active company members.
|
||||
Review pending join requests before they become active organization members.
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="outline">{pendingHumanJoinRequests.length} pending</Badge>
|
||||
|
|
@ -307,7 +307,7 @@ export function CompanyAccess() {
|
|||
{members.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={5} className="px-3 py-8 text-muted-foreground">
|
||||
No user memberships found for this company yet.
|
||||
No user memberships found for this organization yet.
|
||||
</td>
|
||||
</tr>
|
||||
) : members.map((member) => {
|
||||
|
|
@ -373,14 +373,14 @@ export function CompanyAccess() {
|
|||
<DialogHeader>
|
||||
<DialogTitle>Edit member</DialogTitle>
|
||||
<DialogDescription>
|
||||
Update company role and membership status for {editingMember?.user?.name || editingMember?.user?.email || editingMember?.principalId}.
|
||||
Update organization role and membership status for {editingMember?.user?.name || editingMember?.user?.email || editingMember?.principalId}.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{editingMember && (
|
||||
<div className="space-y-5">
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<label className="space-y-2 text-sm">
|
||||
<span className="font-medium">Company role</span>
|
||||
<span className="font-medium">Organization role</span>
|
||||
<select
|
||||
className="w-full rounded-md border border-border bg-background px-3 py-2"
|
||||
value={draftRole ?? ""}
|
||||
|
|
@ -556,7 +556,7 @@ export function CompanyAccessLegacyRoute() {
|
|||
<h1 className="text-lg font-semibold">Advanced Permissions</h1>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Advanced access, scoped assignment, and explicit grant controls are provided by installed company settings extensions.
|
||||
Advanced access, scoped assignment, and explicit grant controls are provided by installed organization settings extensions.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -564,7 +564,7 @@ export function CompanyAccessLegacyRoute() {
|
|||
<div className="space-y-2">
|
||||
<h2 className="text-sm font-semibold">Advanced permissions unavailable</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Core Paperclip keeps enforcing company boundaries and any existing restrictive policy data, but editing advanced permissions requires an installed extension.
|
||||
Core Paperclip keeps enforcing organization boundaries and any existing restrictive policy data, but editing advanced permissions requires an installed extension.
|
||||
</p>
|
||||
{errorMessage ? (
|
||||
<p className="text-sm text-destructive">Plugin extensions unavailable: {errorMessage}</p>
|
||||
|
|
|
|||
|
|
@ -1409,7 +1409,7 @@ export function CompanyEnvironments({ mode = "list" }: CompanyEnvironmentsProps)
|
|||
});
|
||||
const createSecret = useMutation({
|
||||
mutationFn: (input: { name: string; value: string }) => {
|
||||
if (!selectedCompanyId) throw new Error("Select a company to create secrets");
|
||||
if (!selectedCompanyId) throw new Error("Select an organization to create secrets");
|
||||
return secretsApi.create(selectedCompanyId, input);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
|
|
@ -1888,7 +1888,7 @@ export function CompanyEnvironments({ mode = "list" }: CompanyEnvironmentsProps)
|
|||
if (deleteBlastRadius && !deleteBlockMessage) {
|
||||
if (deleteBlastRadius.staticReferences.agentDefaultCount > agentsUsingEnvironment.length) {
|
||||
deleteImpactNotes.push(
|
||||
"Other references to this environment (agents in other companies or terminated agents) fall back to the instance default.",
|
||||
"Other references to this environment (agents in other organizations or terminated agents) fall back to the instance default.",
|
||||
);
|
||||
}
|
||||
const selectionCount =
|
||||
|
|
@ -1940,7 +1940,7 @@ export function CompanyEnvironments({ mode = "list" }: CompanyEnvironmentsProps)
|
|||
})();
|
||||
|
||||
if (!selectedCompanyId) {
|
||||
return <div className="text-sm text-muted-foreground">Select a company context to manage environment secrets and bindings.</div>;
|
||||
return <div className="text-sm text-muted-foreground">Select an organization context to manage environment secrets and bindings.</div>;
|
||||
}
|
||||
|
||||
if (!environmentsEnabled) {
|
||||
|
|
@ -2102,7 +2102,7 @@ export function CompanyEnvironments({ mode = "list" }: CompanyEnvironmentsProps)
|
|||
{isEnvironmentFormPage && mode === "edit" && environments !== undefined && !editingEnvironment ? (
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="font-medium">Environment not found</div>
|
||||
<div className="text-muted-foreground">The environment may have been removed or is not available in this company.</div>
|
||||
<div className="text-muted-foreground">The environment may have been removed or is not available in this organization.</div>
|
||||
<Button size="sm" variant="outline" asChild>
|
||||
<Link to={ENVIRONMENTS_PATH}>Back to environments</Link>
|
||||
</Button>
|
||||
|
|
@ -2139,7 +2139,7 @@ export function CompanyEnvironments({ mode = "list" }: CompanyEnvironmentsProps)
|
|||
<div className="py-4">
|
||||
<Field
|
||||
label="Environment variables"
|
||||
hint="Injected into runs that resolve through this environment. Use plain values or company secrets."
|
||||
hint="Injected into runs that resolve through this environment. Use plain values or organization secrets."
|
||||
>
|
||||
<EnvironmentVariablesEditor
|
||||
ref={environmentVariablesEditorRef}
|
||||
|
|
@ -2427,7 +2427,7 @@ export function CompanyEnvironments({ mode = "list" }: CompanyEnvironmentsProps)
|
|||
|
||||
<Field
|
||||
label="Environment variables"
|
||||
hint="Injected into runs that resolve through this environment. Use plain values or company secrets."
|
||||
hint="Injected into runs that resolve through this environment. Use plain values or organization secrets."
|
||||
>
|
||||
<EnvironmentVariablesEditor
|
||||
ref={environmentVariablesEditorRef}
|
||||
|
|
|
|||
|
|
@ -733,7 +733,7 @@ export function CompanyExport() {
|
|||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([
|
||||
{ label: selectedCompany?.name ?? "Company", href: "/dashboard" },
|
||||
{ label: selectedCompany?.name ?? "Organization", href: "/dashboard" },
|
||||
{ label: "Settings", href: "/company/settings" },
|
||||
{ label: "Export" },
|
||||
]);
|
||||
|
|
@ -1024,7 +1024,7 @@ export function CompanyExport() {
|
|||
}
|
||||
|
||||
if (!selectedCompanyId) {
|
||||
return <EmptyState icon={Package} message="Select a company to export." />;
|
||||
return <EmptyState icon={Package} message="Select an organization to export." />;
|
||||
}
|
||||
|
||||
if (exportPreviewMutation.isPending && !exportData) {
|
||||
|
|
@ -1084,7 +1084,7 @@ export function CompanyExport() {
|
|||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="flex flex-wrap items-center gap-4 text-sm">
|
||||
<span className="font-medium">
|
||||
{selectedCompany?.name ?? "Company"} export
|
||||
{selectedCompany?.name ?? "Organization"} export
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
Exporting {selectedCount.toLocaleString()} of {totalFiles.toLocaleString()} file{totalFiles === 1 ? "" : "s"}
|
||||
|
|
|
|||
|
|
@ -714,7 +714,7 @@ describe("CompanyImport", () => {
|
|||
// Config edits while the import is in flight must not detach it from
|
||||
// the UI: the progress panel keeps reporting it until it settles.
|
||||
const midFlightNameInput = container.querySelector<HTMLInputElement>(
|
||||
'input[placeholder="Imported Company"]',
|
||||
'input[placeholder="Imported Organization"]',
|
||||
);
|
||||
expect(midFlightNameInput).toBeTruthy();
|
||||
await act(async () => {
|
||||
|
|
@ -738,7 +738,7 @@ describe("CompanyImport", () => {
|
|||
|
||||
// The new-company name feeds the request payload too: editing it clears
|
||||
// the stale error panel without discarding the rendered preview.
|
||||
const nameInput = container.querySelector<HTMLInputElement>('input[placeholder="Imported Company"]');
|
||||
const nameInput = container.querySelector<HTMLInputElement>('input[placeholder="Imported Organization"]');
|
||||
expect(nameInput).toBeTruthy();
|
||||
await act(async () => {
|
||||
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!;
|
||||
|
|
@ -950,7 +950,7 @@ describe("CompanyImport", () => {
|
|||
await renderPageAndImport();
|
||||
|
||||
expect(container.textContent).toContain("Import completed");
|
||||
expect(container.textContent).toContain("select it from the company switcher");
|
||||
expect(container.textContent).toContain("select it from the organization switcher");
|
||||
// No readable company, so no dashboard CTA — the switcher guidance stands in.
|
||||
expect(container.querySelector('[data-testid="import-expired-open-company"]')).toBeNull();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -697,7 +697,7 @@ async function readLocalPackageZip(file: File): Promise<{
|
|||
files: Record<string, CompanyPortabilityFileEntry>;
|
||||
}> {
|
||||
if (!/\.zip$/i.test(file.name)) {
|
||||
throw new Error("Select a .zip company package.");
|
||||
throw new Error("Select a .zip organization package.");
|
||||
}
|
||||
const archive = await readZipArchive(await file.arrayBuffer());
|
||||
if (Object.keys(archive.files).length === 0) {
|
||||
|
|
@ -1273,7 +1273,7 @@ export function CompanyImport() {
|
|||
pushToast({
|
||||
tone: "success",
|
||||
title: "Import completed",
|
||||
body: "Open the company to view it.",
|
||||
body: "Open the organization to view it.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -1643,7 +1643,7 @@ export function CompanyImport() {
|
|||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{importOutcome.companyName
|
||||
? <>The import finished and <span className="font-medium text-foreground">{importOutcome.companyName}</span> is ready. Its detailed summary is no longer available.</>
|
||||
: "The import finished and your company is ready. Its detailed summary is no longer available, but the company has been added — select it from the company switcher to view it."}
|
||||
: "The import finished and your organization is ready. Its detailed summary is no longer available, but the organization has been added — select it from the organization switcher to view it."}
|
||||
</p>
|
||||
{importOutcome.pausedAutomations ? (
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
|
|
@ -1661,7 +1661,7 @@ export function CompanyImport() {
|
|||
// immediately visible (same reason as the full-outcome CTA).
|
||||
onClick={() => window.location.assign(importOutcome.dashboardPath!)}
|
||||
>
|
||||
Open company dashboard
|
||||
Open organization dashboard
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
|
@ -1809,7 +1809,7 @@ export function CompanyImport() {
|
|||
}
|
||||
|
||||
if (!selectedCompanyId) {
|
||||
return <EmptyState icon={Download} message="Select a company to import into." />;
|
||||
return <EmptyState icon={Download} message="Select an organization to import into." />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -1906,7 +1906,7 @@ export function CompanyImport() {
|
|||
</Field>
|
||||
)}
|
||||
|
||||
<Field label="Target" hint="Import into this company or create a new one.">
|
||||
<Field label="Target" hint="Import into this organization or create a new one.">
|
||||
<select
|
||||
className="w-full rounded-md border border-border bg-transparent px-2.5 py-1.5 text-sm outline-none"
|
||||
value={targetMode}
|
||||
|
|
@ -1916,7 +1916,7 @@ export function CompanyImport() {
|
|||
resetImportFlowState();
|
||||
}}
|
||||
>
|
||||
<option value="new">Create new company</option>
|
||||
<option value="new">Create new organization</option>
|
||||
<option value="existing">
|
||||
Existing company: {selectedCompany?.name}
|
||||
</option>
|
||||
|
|
@ -1925,7 +1925,7 @@ export function CompanyImport() {
|
|||
|
||||
{targetMode === "new" && (
|
||||
<Field
|
||||
label="New company name"
|
||||
label="New organization name"
|
||||
hint="Optional override. Leave blank to use the package name."
|
||||
>
|
||||
<input
|
||||
|
|
@ -1936,14 +1936,14 @@ export function CompanyImport() {
|
|||
setNewCompanyName(e.target.value);
|
||||
resetMutationState();
|
||||
}}
|
||||
placeholder="Imported Company"
|
||||
placeholder="Imported Organization"
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
|
||||
<Field
|
||||
label="Collision strategy"
|
||||
hint="Board imports can rename, skip, or replace matching company content."
|
||||
hint="Board imports can rename, skip, or replace matching organization content."
|
||||
>
|
||||
<select
|
||||
className="w-full rounded-md border border-border bg-transparent px-2.5 py-1.5 text-sm outline-none"
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ describe("CompanyInvites", () => {
|
|||
await flushReact();
|
||||
await flushReact();
|
||||
|
||||
expect(container.textContent).toContain("Company Invites");
|
||||
expect(container.textContent).toContain("Organization Invites");
|
||||
expect(container.textContent).toContain("Invite a person");
|
||||
expect(container.textContent).not.toContain("Invite an agent");
|
||||
expect(container.textContent).not.toContain("Generate agent onboarding prompt");
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ const inviteRoleOptions = [
|
|||
{
|
||||
value: "viewer",
|
||||
label: "Viewer",
|
||||
description: "Can view company work and follow along.",
|
||||
gets: "View-only company membership.",
|
||||
description: "Can view organization work and follow along.",
|
||||
gets: "View-only organization membership.",
|
||||
},
|
||||
{
|
||||
value: "operator",
|
||||
|
|
@ -34,7 +34,7 @@ const inviteRoleOptions = [
|
|||
{
|
||||
value: "owner",
|
||||
label: "Owner",
|
||||
description: "Full company access, including membership management.",
|
||||
description: "Full organization access, including membership management.",
|
||||
gets: "Everything in Admin, plus managing members.",
|
||||
},
|
||||
] as const;
|
||||
|
|
@ -90,7 +90,7 @@ export function CompanyInvites() {
|
|||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([
|
||||
{ label: selectedCompany?.name ?? "Company", href: "/dashboard" },
|
||||
{ label: selectedCompany?.name ?? "Organization", href: "/dashboard" },
|
||||
{ label: "Settings", href: "/company/settings" },
|
||||
{ label: "Invites" },
|
||||
]);
|
||||
|
|
@ -160,7 +160,7 @@ export function CompanyInvites() {
|
|||
});
|
||||
|
||||
if (!selectedCompanyId) {
|
||||
return <div className="text-sm text-muted-foreground">Select a company to manage invites.</div>;
|
||||
return <div className="text-sm text-muted-foreground">Select an organization to manage invites.</div>;
|
||||
}
|
||||
|
||||
if (invitesQuery.isLoading) {
|
||||
|
|
@ -170,7 +170,7 @@ export function CompanyInvites() {
|
|||
if (invitesQuery.error) {
|
||||
const message =
|
||||
invitesQuery.error instanceof ApiError && invitesQuery.error.status === 403
|
||||
? "You do not have permission to manage company invites."
|
||||
? "You do not have permission to manage organization invites."
|
||||
: invitesQuery.error instanceof Error
|
||||
? invitesQuery.error.message
|
||||
: "Failed to load invites.";
|
||||
|
|
@ -182,10 +182,10 @@ export function CompanyInvites() {
|
|||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<MailPlus className="h-5 w-5 text-muted-foreground" />
|
||||
<h1 className="text-lg font-semibold">Company Invites</h1>
|
||||
<h1 className="text-lg font-semibold">Organization Invites</h1>
|
||||
</div>
|
||||
<p className="max-w-3xl text-sm text-muted-foreground">
|
||||
Invite people to request access to this company. New invite links are copied to your clipboard when they are generated.
|
||||
Invite people to request access to this organization. New invite links are copied to your clipboard when they are generated.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -311,7 +311,7 @@ export function CompanyInvites() {
|
|||
|
||||
{inviteHistory.length === 0 ? (
|
||||
<div className="border-t border-border px-5 py-8 text-sm text-muted-foreground">
|
||||
No invites have been created for this company yet.
|
||||
No invites have been created for this organization yet.
|
||||
</div>
|
||||
) : (
|
||||
<div className="border-t border-border">
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ export function CompanySettings() {
|
|||
if (!selectedCompany) {
|
||||
return (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
No company selected. Select a company from the switcher above.
|
||||
No organization selected. Select an organization from the switcher above.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -207,7 +207,7 @@ export function CompanySettings() {
|
|||
General
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<Field label="Company name" hint="The display name for your company.">
|
||||
<Field label="Organization name" hint="The display name for your organization.">
|
||||
<input
|
||||
className="w-full rounded-md border border-border bg-transparent px-2.5 py-1.5 text-sm outline-none"
|
||||
type="text"
|
||||
|
|
@ -217,13 +217,13 @@ export function CompanySettings() {
|
|||
</Field>
|
||||
<Field
|
||||
label="Description"
|
||||
hint="Optional description shown in the company profile."
|
||||
hint="Optional description shown in the organization profile."
|
||||
>
|
||||
<input
|
||||
className="w-full rounded-md border border-border bg-transparent px-2.5 py-1.5 text-sm outline-none"
|
||||
type="text"
|
||||
value={description}
|
||||
placeholder="Optional company description"
|
||||
placeholder="Optional organization description"
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
|
|
@ -289,7 +289,7 @@ export function CompanySettings() {
|
|||
</Field>
|
||||
<Field
|
||||
label="Brand color"
|
||||
hint="Sets the hue for the company icon. Leave empty for auto-generated color."
|
||||
hint="Sets the hue for the organization icon. Leave empty for auto-generated color."
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* token-extraction: allowlisted — <input type="color"> value must be a real hex string, not a var() reference. */}
|
||||
|
|
@ -414,7 +414,7 @@ export function CompanySettings() {
|
|||
</div>
|
||||
<div className="space-y-3 bg-destructive/5 px-4 py-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Archive this company to hide it from the sidebar. This persists in
|
||||
Archive this organization to hide it from the sidebar. This persists in
|
||||
the database.
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -428,7 +428,7 @@ export function CompanySettings() {
|
|||
onClick={() => {
|
||||
if (!selectedCompanyId) return;
|
||||
const confirmed = window.confirm(
|
||||
`Archive company "${selectedCompany.name}"? It will be hidden from the sidebar.`
|
||||
`Archive organization "${selectedCompany.name}"? It will be hidden from the sidebar.`
|
||||
);
|
||||
if (!confirmed) return;
|
||||
const nextCompanyId =
|
||||
|
|
@ -447,13 +447,13 @@ export function CompanySettings() {
|
|||
? "Archiving..."
|
||||
: selectedCompany.status === "archived"
|
||||
? "Already archived"
|
||||
: "Archive company"}
|
||||
: "Archive organization"}
|
||||
</Button>
|
||||
{archiveMutation.isError && (
|
||||
<span className="text-xs text-destructive">
|
||||
{archiveMutation.error instanceof Error
|
||||
? archiveMutation.error.message
|
||||
: "Failed to archive company"}
|
||||
: "Failed to archive organization"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export function CompanySettingsPluginPage() {
|
|||
if (hasInvalidCompanyPrefix) {
|
||||
return <NotFoundPage scope="invalid_company_prefix" requestedPrefix={routeCompanyPrefix} />;
|
||||
}
|
||||
return <div className="text-sm text-muted-foreground">Select a company to view this page.</div>;
|
||||
return <div className="text-sm text-muted-foreground">Select an organization to view this page.</div>;
|
||||
}
|
||||
|
||||
if (!settingsRoutePath || isLoading) {
|
||||
|
|
|
|||
|
|
@ -655,7 +655,7 @@ describe("SkillDetailPage settings", () => {
|
|||
detail: makeDetail(v1, { folderPath: "engineering/code-review" }),
|
||||
});
|
||||
|
||||
expect(node.textContent).toContain("Company / Engineering / Code Review");
|
||||
expect(node.textContent).toContain("Organization / Engineering / Code Review");
|
||||
});
|
||||
|
||||
it("shows a direct fork action for read-only skills", async () => {
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ type SourceFilter = "all" | "company" | "bundled" | "optional" | "external";
|
|||
|
||||
const SOURCE_FILTER_LABELS: Record<SourceFilter, string> = {
|
||||
all: "All",
|
||||
company: "Company",
|
||||
company: "Organization",
|
||||
bundled: "Bundled",
|
||||
optional: "Optional",
|
||||
external: "External",
|
||||
|
|
@ -654,7 +654,7 @@ function categorySetKey(categories: string[]) {
|
|||
}
|
||||
|
||||
function skillSettingsToastBody(skill: Pick<CompanySkillDetail, "categories" | "sharingScope">) {
|
||||
const sharing = skill.sharingScope === "private" ? "Sharing: private" : "Sharing: company";
|
||||
const sharing = skill.sharingScope === "private" ? "Sharing: private" : "Sharing: organization";
|
||||
const categories = skill.categories.length ? `Categories: ${skill.categories.join(", ")}` : "Categories: none";
|
||||
return `${sharing} | ${categories}`;
|
||||
}
|
||||
|
|
@ -1644,7 +1644,7 @@ function NewSkillWizard({
|
|||
<span className="text-muted-foreground">Slug</span>
|
||||
<span className="font-mono">{effectiveSlug || "skill"}</span>
|
||||
<span className="text-muted-foreground">Scope</span>
|
||||
<span>{draft.sharingScope === "private" ? "Private" : "Company"}</span>
|
||||
<span>{draft.sharingScope === "private" ? "Private" : "Organization"}</span>
|
||||
<span className="text-muted-foreground">Categories</span>
|
||||
<span>{draft.categories.length ? draft.categories.join(", ") : "none"}</span>
|
||||
</div>
|
||||
|
|
@ -1661,9 +1661,9 @@ function NewSkillWizard({
|
|||
draft.sharingScope === scope ? "border-foreground bg-accent/50" : "border-border",
|
||||
)}
|
||||
>
|
||||
<span className="block font-medium">{scope === "company" ? "Company" : "Private"}</span>
|
||||
<span className="block font-medium">{scope === "company" ? "Organization" : "Private"}</span>
|
||||
<span className="mt-1 block text-xs text-muted-foreground">
|
||||
{scope === "company" ? "Visible inside this company." : "Only visible in your library."}
|
||||
{scope === "company" ? "Visible inside this organization." : "Only visible in your library."}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
|
|
@ -2175,7 +2175,7 @@ export function InstallPreviewDialog({
|
|||
<div className="rounded-md border border-border p-3">
|
||||
<div className="mb-1 text-xs uppercase tracking-wide text-muted-foreground">Enable for agents</div>
|
||||
<p className="mb-2 text-xs text-muted-foreground">
|
||||
Installing adds the skill to the company library. Agents can only use it once it is enabled for them.
|
||||
Installing adds the skill to the organization library. Agents can only use it once it is enabled for them.
|
||||
</p>
|
||||
<AgentMultiSelect
|
||||
agents={agents}
|
||||
|
|
@ -2185,7 +2185,7 @@ export function InstallPreviewDialog({
|
|||
setSelectedAgentIds(next);
|
||||
}}
|
||||
showSelectionPreview={false}
|
||||
emptyMessage="No agents in this company support skills yet."
|
||||
emptyMessage="No agents in this organization support skills yet."
|
||||
isAgentDisabled={(agent) => {
|
||||
const option = agent as AttachAgentOption;
|
||||
return option.required || !option.supportsSkills;
|
||||
|
|
@ -2315,7 +2315,7 @@ function AttachAgentsPopover({
|
|||
</select>
|
||||
</div>
|
||||
) : null}
|
||||
emptyMessage={eligible.length === 0 ? "No agents in this company support skills yet." : "No agents yet."}
|
||||
emptyMessage={eligible.length === 0 ? "No agents in this organization support skills yet." : "No agents yet."}
|
||||
isAgentDisabled={(agent) => {
|
||||
const option = agent as AttachAgentOption;
|
||||
return option.required || !option.supportsSkills;
|
||||
|
|
@ -3367,7 +3367,7 @@ export function SkillDetailPage({
|
|||
<span className="hidden sm:inline">{detail.attachedAgentCount === 1 ? "install" : "installs"}</span>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Agents in this company that currently have this skill installed.</TooltipContent>
|
||||
<TooltipContent>Agents in this organization that currently have this skill installed.</TooltipContent>
|
||||
</Tooltip>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -3624,7 +3624,7 @@ export function SkillDetailPage({
|
|||
disabled={updateSettingsPending}
|
||||
className="h-9 w-full rounded-md border border-border bg-background px-2 text-sm text-foreground"
|
||||
>
|
||||
<option value="company">Company — visible inside this company</option>
|
||||
<option value="company">Organization — visible inside this organization</option>
|
||||
<option value="private">Private — only visible in your library</option>
|
||||
</select>
|
||||
<p className="text-xs text-muted-foreground">Public link sharing is coming later.</p>
|
||||
|
|
@ -3656,7 +3656,7 @@ export function SkillDetailPage({
|
|||
<div className="rounded-md border border-destructive/40 p-3">
|
||||
<div className="text-xs font-semibold uppercase tracking-wide text-destructive">Danger zone</div>
|
||||
<div className="mt-2 flex items-center justify-between gap-3">
|
||||
<p className="min-w-0 text-xs text-muted-foreground">Remove this skill from the company library.</p>
|
||||
<p className="min-w-0 text-xs text-muted-foreground">Remove this skill from the organization library.</p>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
|
|
@ -5063,7 +5063,7 @@ export function CompanySkills() {
|
|||
pushToast({
|
||||
tone: "success",
|
||||
title: "Skill removed",
|
||||
body: `${skill.name} was removed from the company skill library.`,
|
||||
body: `${skill.name} was removed from the organization skill library.`,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
|
|
@ -5107,7 +5107,7 @@ export function CompanySkills() {
|
|||
);
|
||||
|
||||
if (!selectedCompanyId) {
|
||||
return <EmptyState icon={Boxes} message="Select a company to manage skills." />;
|
||||
return <EmptyState icon={Boxes} message="Select an organization to manage skills." />;
|
||||
}
|
||||
|
||||
function handleAddSkillSource() {
|
||||
|
|
@ -5147,8 +5147,8 @@ export function CompanySkills() {
|
|||
const studioBackHref = studioForkDetailQuery.data ? routeForSkill(studioForkDetailQuery.data) : "/skills";
|
||||
const studioTitle = studioForkFromId ? "Fork skill" : "Create a new skill";
|
||||
const studioDescription = studioForkFromId
|
||||
? "Review the fork metadata and create an editable company copy."
|
||||
: "Create an editable company skill in the Paperclip workspace.";
|
||||
? "Review the fork metadata and create an editable organization copy."
|
||||
: "Create an editable organization skill in the Paperclip workspace.";
|
||||
return (
|
||||
<>
|
||||
{policyDenial.denial ? (
|
||||
|
|
@ -5161,7 +5161,7 @@ export function CompanySkills() {
|
|||
<DialogHeader>
|
||||
<DialogTitle>Remove skill</DialogTitle>
|
||||
<DialogDescription>
|
||||
Remove this skill from the company library. If any agents still use it, removal will be blocked until it is detached.
|
||||
Remove this skill from the organization library. If any agents still use it, removal will be blocked until it is detached.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3 text-sm">
|
||||
|
|
@ -5275,7 +5275,7 @@ export function CompanySkills() {
|
|||
<DialogHeader>
|
||||
<DialogTitle>Import a skill</DialogTitle>
|
||||
<DialogDescription>
|
||||
Paste a local path, GitHub URL, or `skills.sh` command to import a skill into this company.
|
||||
Paste a local path, GitHub URL, or `skills.sh` command to import a skill into this organization.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3">
|
||||
|
|
|
|||
|
|
@ -529,7 +529,7 @@ export function Costs() {
|
|||
}), [budgetPolicies]);
|
||||
|
||||
if (!selectedCompanyId) {
|
||||
return <EmptyState icon={DollarSign} message="Select a company to view costs." />;
|
||||
return <EmptyState icon={DollarSign} message="Select an organization to view costs." />;
|
||||
}
|
||||
|
||||
const showCustomPrompt = preset === "custom" && !customReady;
|
||||
|
|
@ -908,10 +908,10 @@ export function Costs() {
|
|||
return (
|
||||
<section key={scopeType} className="space-y-3">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold capitalize">{scopeType} budgets</h2>
|
||||
<h2 className="text-lg font-semibold capitalize">{scopeType === "company" ? "organization" : scopeType} budgets</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{scopeType === "company"
|
||||
? "Company-wide monthly policy."
|
||||
? "Organization-wide monthly policy."
|
||||
: scopeType === "agent"
|
||||
? "Recurring monthly spend policies for individual agents."
|
||||
: "Lifetime spend policies for execution-bound projects."}
|
||||
|
|
@ -940,7 +940,7 @@ export function Costs() {
|
|||
{budgetPolicies.length === 0 ? (
|
||||
<Card>
|
||||
<CardContent className="px-5 py-8 text-sm text-muted-foreground">
|
||||
No budget policies yet. Set agent and project budgets from their detail pages, or use the existing company monthly budget control.
|
||||
No budget policies yet. Set agent and project budgets from their detail pages, or use the existing organization monthly budget control.
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
|
|
|
|||
|
|
@ -293,14 +293,14 @@ export function Dashboard() {
|
|||
return (
|
||||
<EmptyState
|
||||
icon={LayoutDashboard}
|
||||
message="Welcome to Paperclip. Set up your first company and agent to get started."
|
||||
message="Welcome to Paperclip. Set up your first organization and agent to get started."
|
||||
action="Get Started"
|
||||
onAction={openOnboarding}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<EmptyState icon={LayoutDashboard} message="Create or select a company to view the dashboard." />
|
||||
<EmptyState icon={LayoutDashboard} message="Create or select an organization to view the dashboard." />
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -333,13 +333,13 @@ export function Dashboard() {
|
|||
</Button>
|
||||
}
|
||||
>
|
||||
Agents from a company import arrive paused as a safety default. Resume them so assigned tasks can start.
|
||||
Agents from an organization import arrive paused as a safety default. Resume them so assigned tasks can start.
|
||||
</InlineBanner>
|
||||
) : pausedBanner?.kind === "all-paused" ? (
|
||||
<InlineBanner
|
||||
tone="warning"
|
||||
icon={PauseCircle}
|
||||
title="All agents in this company are paused — nothing will run."
|
||||
title="All agents in this organization are paused — nothing will run."
|
||||
actions={
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
<Link to="/agents">Review agents</Link>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export function DashboardLive() {
|
|||
return (
|
||||
<EmptyState
|
||||
icon={RadioTower}
|
||||
message={companies.length === 0 ? "Create a company to view live runs." : "Select a company to view live runs."}
|
||||
message={companies.length === 0 ? "Create an organization to view live runs." : "Select an organization to view live runs."}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ export function DecisionQueuePage() {
|
|||
});
|
||||
|
||||
if (!selectedCompanyId) {
|
||||
return <p className="text-sm text-muted-foreground">Select a company first.</p>;
|
||||
return <p className="text-sm text-muted-foreground">Select an organization first.</p>;
|
||||
}
|
||||
if (isLoading) {
|
||||
return <PageSkeleton variant="approvals" />;
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ export function GoalDetail() {
|
|||
|
||||
const uploadImage = useMutation({
|
||||
mutationFn: async (file: File) => {
|
||||
if (!resolvedCompanyId) throw new Error("No company selected");
|
||||
if (!resolvedCompanyId) throw new Error("No organization selected");
|
||||
return assetsApi.uploadImage(
|
||||
resolvedCompanyId,
|
||||
file,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue