Update company creation copy (#8653)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The onboarding and company switcher UI are the first places users
create or select an organization
> - Some of that surface still used older team/workspace wording even
though the product model is company-centric
> - Mixed wording makes the setup path feel inconsistent and can make
users wonder whether they are creating a team, workspace, or company
> - This pull request updates the affected UI copy to consistently say
company
> - The benefit is a clearer first-run and navigation experience without
changing behavior

## Linked Issues or Issue Description

No public issue exists for this small UI polish change.

### Problem or motivation

The company creation and switcher surfaces used mixed
team/workspace/company wording for the same concept, which makes the
setup path feel inconsistent.

### Proposed solution

Update the visible copy, accessibility label, inline comment, e2e
expectations, and matching test expectations to use company-centric
language consistently.

### Alternatives considered

Leave the existing wording alone, but that preserves inconsistent
terminology in a high-traffic setup path.

### Roadmap alignment

This is focused UI polish and does not overlap with a roadmap-level core
feature.

### Additional context

The create action keeps its trailing ellipsis because it opens the
onboarding wizard rather than completing immediately.

## What Changed

- Updated front door and onboarding wizard labels from team-oriented
copy to company-oriented copy.
- Updated the sidebar company menu from workspace/team wording to
company wording, including the trigger accessibility label and empty
fallback text.
- Kept the sidebar create action ellipsis for the dialog/wizard
affordance.
- Updated component and Playwright test expectations for the new copy.

## Verification

- `pnpm exec vitest run ui/src/components/SidebarCompanyMenu.test.tsx`
- Attempted `npx playwright test --config tests/e2e/playwright.config.ts
tests/e2e/onboarding.spec.ts tests/e2e/nux-phase4-screenshots.spec.ts
tests/e2e/planning-mode-visual-verification.spec.ts
tests/e2e/conference-room-typing-intro.spec.ts`; local browser launch is
blocked by missing host Chromium dependencies (`libatk1.0-0t64`,
`libatspi2.0-0t64`, `libxcomposite1`, `libxdamage1`, `libxfixes3`,
`libxrandr2`, `libgbm1`, `libasound2t64`).
- Screenshots intentionally omitted because this is a copy-only change
and no design screenshots are needed for review.

## Risks

Low risk. This is copy-only UI polish plus matching test updates; no
data model, API, migration, workflow, lockfile, or behavior changes are
included.

## Model Used

OpenAI GPT-5 Codex (`gpt-5`) via the Paperclip Codex agent, with
tool-assisted repository inspection, GitHub CLI usage, and local command
execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-06-26 12:01:02 -05:00 committed by GitHub
parent b3c0fadd63
commit c79d347abe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 38 additions and 38 deletions

View File

@ -60,10 +60,10 @@ test.describe("Dashboard launch after onboarding wizard", () => {
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 team");
const frontDoor = page.getByText("Build a new company");
if (await frontDoor.count()) await frontDoor.first().click();
// Step 1: team name.
// Step 1: company name.
await page.getByPlaceholder("Acme Corp").fill(COMPANY_NAME);
await page.getByRole("button", { name: /^Next/ }).click();

View File

@ -10,7 +10,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
*
* Boots a throwaway local_trusted instance (see playwright.config.ts webServer)
* and captures screenshots of every surface integrated by NUX Phases 13:
* - "Build a new team" step 1 (team name) + step 2 (mission)
* - "Build a new company" step 1 (company name) + step 2 (mission)
* - Team-lead hire step (capsule wizard, PAP-125)
* - Onboarding front door (path picker)
* - "Add agents to your org" growth intake
@ -57,16 +57,16 @@ test.describe("NUX Phase 4 visual QA", () => {
const baseUrl =
"http://127.0.0.1:" + (process.env.PAPERCLIP_E2E_PORT ?? "3199");
// ── Section A: create-team path (name → mission → hire) ───────────────
// ── Section A: create-company path (name → mission → hire) ────────────
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 team/ });
const createCard = page.getByRole("button", { name: /Build a new company/ });
if (await createCard.count()) {
await createCard.first().click();
}
await expect(
page.getByRole("heading", { name: "Name your team" }),
page.getByRole("heading", { name: "Name your company" }),
).toBeVisible({ timeout: 15_000 });
await page.getByPlaceholder("Acme Corp").fill("QA Robotics");
await page.screenshot({ path: shot("02-create-name.png") });
@ -110,7 +110,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 team" }),
page.getByRole("heading", { name: "Build a new company" }),
).toBeVisible();
await expect(
page.getByRole("heading", { name: "Add agents to your org" }),
@ -118,9 +118,9 @@ test.describe("NUX Phase 4 visual QA", () => {
await page.screenshot({ path: shot("01-front-door.png") });
await page.getByRole("button", { name: /Add agents to your org/ }).click();
// The grow path shares step 1 (team name) before its step-2 intake.
// The grow path shares step 1 (company name) before its step-2 intake.
await expect(
page.getByRole("heading", { name: "Name your team" }),
page.getByRole("heading", { name: "Name your company" }),
).toBeVisible({ timeout: 10_000 });
await page.getByPlaceholder("Acme Corp").fill("QA Robotics Grow");
await page.getByRole("button", { name: /^Next/ }).click();

View File

@ -48,14 +48,14 @@ test.describe("Onboarding wizard", () => {
if (await startBtn.count()) {
await startBtn.first().click();
}
const createCard = page.getByRole("button", { name: /Build a new team/ });
const createCard = page.getByRole("button", { name: /Build a new company/ });
if (await createCard.count()) {
await createCard.first().click();
}
// Step 1 — Name your team.
// Step 1 — Name your company.
await expect(
page.getByRole("heading", { name: "Name your team" }),
page.getByRole("heading", { name: "Name your company" }),
).toBeVisible({ timeout: 15_000 });
await page.getByPlaceholder("Acme Corp").fill(COMPANY_NAME);
await page.getByRole("button", { name: /^Next/ }).click();

View File

@ -44,10 +44,10 @@ test("captures planning mode UI for desktop and mobile", async ({ page }) => {
const startBtn = page.getByRole("button", { name: /Start Onboarding|New Company|Add Agent/ });
if (await startBtn.count()) await startBtn.first().click();
const createCard = page.getByRole("button", { name: /Build a new team/ });
const createCard = page.getByRole("button", { name: /Build a new company/ });
if (await createCard.count()) await createCard.first().click();
await expect(page.getByRole("heading", { name: "Name your team" })).toBeVisible({ timeout: 15_000 });
await expect(page.getByRole("heading", { name: "Name your company" })).toBeVisible({ timeout: 15_000 });
await page.locator('input[placeholder="Acme Corp"]').fill(companyName);
await page.getByRole("button", { name: /^Next/ }).click();

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 team</h3>
<h3 className="font-semibold text-sm">Build a new company</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

@ -964,7 +964,7 @@ export function OnboardingWizard() {
</div>
)}
{/* Step 1: Name your team (both paths) */}
{/* Step 1: Name your company (both paths) */}
{step === 1 && (
<div className="space-y-5">
<div className="flex items-center gap-3 mb-1">
@ -972,9 +972,9 @@ export function OnboardingWizard() {
<Building2 className="h-5 w-5 text-muted-foreground" />
</div>
<div>
<h3 className="font-medium">Name your team</h3>
<h3 className="font-medium">Name your company</h3>
<p className="text-xs text-muted-foreground">
What should we call your team?
What should we call your company?
</p>
</div>
</div>
@ -987,7 +987,7 @@ export function OnboardingWizard() {
: "text-muted-foreground group-focus-within:text-foreground"
)}
>
Team name
Company name
</label>
<input
className="w-full rounded-md border border-border bg-transparent px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-ring placeholder:text-muted-foreground/50"
@ -1206,7 +1206,7 @@ export function OnboardingWizard() {
className="text-[11px] text-muted-foreground hover:text-foreground transition-colors"
onClick={() => setStep(1)}
>
Change team name
Change company name
</button>
</div>
)}
@ -1589,7 +1589,7 @@ export function OnboardingWizard() {
{/* Review checklist — everything that's now set up */}
<div className="space-y-1.5">
{[
{ label: "Team name", done: Boolean(companyName.trim()) },
{ label: "Company name", done: Boolean(companyName.trim()) },
{ label: "Mission", done: Boolean(companyGoal.trim()) },
{ label: "Agent created", done: Boolean(createdAgentId) },
{ label: "Model connected", done: Boolean(createdAgentId) },

View File

@ -410,7 +410,7 @@ describe("Sidebar", () => {
expect(container.querySelector('button[aria-label="Expand sidebar"]')).toBeNull();
expect(container.querySelector('a[aria-label="Open search"]')).toBeNull();
// The company menu (workspace switcher / logo) is still present in the rail.
// The company menu (company switcher / logo) is still present in the rail.
expect(container.textContent).toContain("Company menu");
flushSync(() => {

View File

@ -140,7 +140,7 @@ describe("SidebarCompanyMenu", () => {
vi.clearAllMocks();
});
it("uses team-centric create copy without the chat flag", async () => {
it("uses company-centric create copy without the chat flag", async () => {
const root = createRoot(container);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
@ -156,7 +156,7 @@ describe("SidebarCompanyMenu", () => {
await flushReact();
await flushReact();
const trigger = container.querySelector('button[aria-label="Open Acme Labs workspace switcher"]');
const trigger = container.querySelector('button[aria-label="Open Acme Labs company switcher"]');
expect(trigger).not.toBeNull();
act(() => {
trigger?.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, button: 0 }));
@ -164,7 +164,7 @@ describe("SidebarCompanyMenu", () => {
});
await flushReact();
expect(document.body.textContent).toContain("Create new team...");
expect(document.body.textContent).toContain("Create new company...");
expect(document.body.textContent).not.toContain("Add company...");
act(() => {
@ -190,7 +190,7 @@ describe("SidebarCompanyMenu", () => {
expect(container.textContent).toContain("Acme Labs");
const trigger = container.querySelector('button[aria-label="Open Acme Labs workspace switcher"]');
const trigger = container.querySelector('button[aria-label="Open Acme Labs company switcher"]');
expect(trigger).not.toBeNull();
act(() => {
@ -199,11 +199,11 @@ describe("SidebarCompanyMenu", () => {
});
await flushReact();
expect(document.body.textContent).toContain("Switch workspace");
expect(document.body.textContent).toContain("Switch company");
expect(document.body.textContent).toContain("Edit");
expect(document.body.textContent).toContain("Strata");
expect(document.body.textContent).toContain("ANA");
expect(document.body.textContent).toContain("Create new team...");
expect(document.body.textContent).toContain("Create new company...");
expect(document.body.textContent).toContain("Invite people to Acme Labs");
expect(document.body.textContent).toContain("Company settings");
expect(document.body.textContent).toContain("Sign out");
@ -224,7 +224,7 @@ describe("SidebarCompanyMenu", () => {
});
});
it("toggles company order editing without selecting a workspace", async () => {
it("toggles company order editing without selecting a company", async () => {
const root = createRoot(container);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
@ -240,7 +240,7 @@ describe("SidebarCompanyMenu", () => {
await flushReact();
await flushReact();
const trigger = container.querySelector('button[aria-label="Open Acme Labs workspace switcher"]');
const trigger = container.querySelector('button[aria-label="Open Acme Labs company switcher"]');
expect(trigger).not.toBeNull();
act(() => {
@ -280,7 +280,7 @@ describe("SidebarCompanyMenu", () => {
});
});
it("navigates to the selected workspace dashboard from company-prefixed routes", async () => {
it("navigates to the selected company dashboard from company-prefixed routes", async () => {
mockLocation.pathname = "/PAP/issues";
const root = createRoot(container);
const queryClient = new QueryClient({
@ -297,7 +297,7 @@ describe("SidebarCompanyMenu", () => {
await flushReact();
await flushReact();
const trigger = container.querySelector('button[aria-label="Open Acme Labs workspace switcher"]');
const trigger = container.querySelector('button[aria-label="Open Acme Labs company switcher"]');
expect(trigger).not.toBeNull();
act(() => {

View File

@ -231,12 +231,12 @@ export function SidebarCompanyMenu({ open: controlledOpen, onOpenChange }: Sideb
// svg present (expanded) it was already 12px but without it (rail) it fell
// back to 8px — a 4px horizontal jump on collapse (PAP-10676).
className="h-9 flex-1 justify-start gap-2 px-3 text-left"
aria-label={selectedCompany ? `Open ${selectedCompany.name} workspace switcher` : "Open workspace switcher"}
aria-label={selectedCompany ? `Open ${selectedCompany.name} company switcher` : "Open company switcher"}
>
<span className="flex min-w-0 flex-1 items-center gap-2">
{selectedCompany ? <WorkspaceIcon company={selectedCompany} /> : null}
<span className={cn("truncate text-sm font-bold text-foreground", rail && SIDEBAR_RAIL_HIDDEN_LABEL)}>
{selectedCompany?.name ?? "Select workspace"}
{selectedCompany?.name ?? "Select company"}
</span>
</span>
{!rail && <ChevronsUpDown className="size-3.5 shrink-0 text-muted-foreground" />}
@ -245,7 +245,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-[11px] font-semibold uppercase text-muted-foreground">
Switch workspace
Switch company
</DropdownMenuLabel>
<button
type="button"
@ -281,7 +281,7 @@ export function SidebarCompanyMenu({ open: controlledOpen, onOpenChange }: Sideb
</SortableContext>
</DndContext>
{orderedCompanies.length === 0 ? (
<DropdownMenuItem disabled>No workspaces</DropdownMenuItem>
<DropdownMenuItem disabled>No companies</DropdownMenuItem>
) : null}
</div>
<DropdownMenuSeparator />
@ -291,7 +291,7 @@ export function SidebarCompanyMenu({ open: controlledOpen, onOpenChange }: Sideb
disabled={isEditingOrder}
>
<Plus className="size-4" />
<span>Create new team...</span>
<span>Create new company...</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem asChild disabled={isEditingOrder}>

View File

@ -381,7 +381,7 @@ export function BoardChat() {
}, [selectedCompanyId]);
// The onboarding wizard renders as an overlay above an already-mounted
// Conference Room (sidebar "Create new team..." path). Holding the reveal
// Conference Room (sidebar "Create new company..." path). Holding the reveal
// timer while it's open guarantees the dots window can't burn off behind
// the wizard before the user ever sees the chat (PAP-134).
const { onboardingOpen } = useDialogState();