copy: unify user-facing "company" wording to "organization" (#12243)

This commit is contained in:
scotttong 2026-08-27 01:04:55 -07:00 committed by GitHub
parent d785b19213
commit 4d82f5eaea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
165 changed files with 442 additions and 442 deletions

View File

@ -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/>

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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") });

View File

@ -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();
}

View File

@ -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 });

View File

@ -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());
});
});

View File

@ -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());
});
});

View File

@ -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);
});

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>
)}

View File

@ -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" />

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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, " ");

View File

@ -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>

View File

@ -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

View File

@ -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;

View File

@ -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.";

View File

@ -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", () => {

View File

@ -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>
);

View File

@ -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}

View File

@ -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",

View File

@ -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>

View File

@ -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");
},
});

View File

@ -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());
});

View File

@ -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}

View File

@ -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");
},
});

View File

@ -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");
});
});
});

View File

@ -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);

View File

@ -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: () => {

View File

@ -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) => (

View File

@ -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");
});

View File

@ -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">

View File

@ -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");

View File

@ -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} />

View File

@ -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");

View File

@ -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&apos;t load companies</DropdownMenuItem>
<DropdownMenuItem disabled>Couldn&apos;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}
</>

View File

@ -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

View File

@ -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) {

View File

@ -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}

View File

@ -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

View File

@ -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>

View File

@ -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"));

View File

@ -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">

View File

@ -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)}>

View File

@ -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();

View File

@ -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(" / ");
}

View File

@ -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",

View File

@ -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");
});

View File

@ -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",

View File

@ -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)

View File

@ -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"
}
}
}

View File

@ -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",

View File

@ -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.",
);
});

View File

@ -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;

View File

@ -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");
});

View File

@ -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,

View File

@ -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.",
};

View File

@ -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", () => {

View File

@ -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.",

View File

@ -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" },
]);

View File

@ -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);
},
});

View File

@ -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.

View File

@ -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}

View File

@ -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) {

View File

@ -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) {

View File

@ -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;

View File

@ -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", () => {

View File

@ -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}

View File

@ -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 && (

View File

@ -162,7 +162,7 @@ function ClaimSuccess() {
<div>
<h1 className="text-xl font-semibold">You&rsquo;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&hellip;
Setup is complete. Taking you to onboarding to create your first organization&hellip;
</p>
</div>
</div>

View File

@ -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>
)}

View File

@ -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();

View File

@ -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

View File

@ -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");

View File

@ -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>

View File

@ -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}

View File

@ -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"}

View File

@ -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();
});

View File

@ -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"

View File

@ -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");

View File

@ -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">

View File

@ -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>

View File

@ -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) {

View File

@ -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 () => {

View File

@ -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">

View File

@ -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}

View File

@ -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>

View File

@ -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."}
/>
);
}

View File

@ -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" />;

View File

@ -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