feat(06-01): fix named terminology straggler requirements (TERM-10 through TERM-17)

- TERM-10: Companies.tsx breadcrumb uses VOCAB.companies, loading/delete text uses VOCAB
- TERM-11: InstanceSettings.tsx adds VOCAB import, uses VOCAB.company/companies
- TERM-12: Costs.tsx adds VOCAB import and SCOPE_LABELS map, replaces hardcoded company strings
- TERM-13: CompanyImport.tsx uses VOCAB.appName, VOCAB.company, VOCAB.board throughout
- TERM-17: IssuesList.tsx (component) title='Board view' -> 'Kanban view'
- Dashboard.tsx: 'awaiting board review' -> 'awaiting owner review'
- CompanySettings.tsx: 'No company selected' uses VOCAB.company
- ReportsToPicker.tsx: adds VOCAB import, default label uses VOCAB.ceo not hardcoded 'CEO'
This commit is contained in:
Mikkel Georgsen 2026-03-31 13:25:00 +02:00
parent f4df47089a
commit ccc8ead357
8 changed files with 34 additions and 24 deletions

View file

@ -391,7 +391,7 @@ export function IssuesList({
<button <button
className={`p-1.5 transition-colors ${viewState.viewMode === "board" ? "bg-accent text-foreground" : "text-muted-foreground hover:text-foreground"}`} className={`p-1.5 transition-colors ${viewState.viewMode === "board" ? "bg-accent text-foreground" : "text-muted-foreground hover:text-foreground"}`}
onClick={() => updateView({ viewMode: "board" })} onClick={() => updateView({ viewMode: "board" })}
title="Board view" title="Kanban view"
> >
<Columns3 className="h-3.5 w-3.5" /> <Columns3 className="h-3.5 w-3.5" />
</button> </button>

View file

@ -1,4 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import { VOCAB } from "@paperclipai/branding";
import type { Agent } from "@paperclipai/shared"; import type { Agent } from "@paperclipai/shared";
import { import {
Popover, Popover,
@ -16,7 +17,7 @@ export function ReportsToPicker({
onChange, onChange,
disabled = false, disabled = false,
excludeAgentIds = [], excludeAgentIds = [],
disabledEmptyLabel = "Reports to: N/A (CEO)", disabledEmptyLabel,
chooseLabel = "Reports to...", chooseLabel = "Reports to...",
}: { }: {
agents: Agent[]; agents: Agent[];
@ -27,6 +28,7 @@ export function ReportsToPicker({
disabledEmptyLabel?: string; disabledEmptyLabel?: string;
chooseLabel?: string; chooseLabel?: string;
}) { }) {
const label = disabledEmptyLabel ?? `Reports to: N/A (${VOCAB.ceo})`;
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const exclude = new Set(excludeAgentIds); const exclude = new Set(excludeAgentIds);
const rows = agents.filter( const rows = agents.filter(
@ -69,7 +71,7 @@ export function ReportsToPicker({
<> <>
<User className="h-3 w-3 shrink-0 text-muted-foreground" /> <User className="h-3 w-3 shrink-0 text-muted-foreground" />
<span className="min-w-0 truncate"> <span className="min-w-0 truncate">
{disabled ? disabledEmptyLabel : chooseLabel} {disabled ? label : chooseLabel}
</span> </span>
</> </>
)} )}

View file

@ -70,7 +70,7 @@ export function Companies() {
}); });
useEffect(() => { useEffect(() => {
setBreadcrumbs([{ label: "Companies" }]); setBreadcrumbs([{ label: VOCAB.companies }]);
}, [setBreadcrumbs]); }, [setBreadcrumbs]);
function startEdit(companyId: string, currentName: string) { function startEdit(companyId: string, currentName: string) {
@ -98,7 +98,7 @@ export function Companies() {
</div> </div>
<div className="h-6"> <div className="h-6">
{loading && <p className="text-sm text-muted-foreground">Loading companies...</p>} {loading && <p className="text-sm text-muted-foreground">{`Loading ${VOCAB.companies.toLowerCase()}...`}</p>}
{error && <p className="text-sm text-destructive">{error.message}</p>} {error && <p className="text-sm text-destructive">{error.message}</p>}
</div> </div>
@ -267,7 +267,7 @@ export function Companies() {
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<p className="text-sm text-destructive font-medium"> <p className="text-sm text-destructive font-medium">
Delete this company and all its data? This cannot be undone. {`Delete this ${VOCAB.company.toLowerCase()} and all its data? This cannot be undone.`}
</p> </p>
<div className="flex items-center gap-2 ml-4 shrink-0"> <div className="flex items-center gap-2 ml-4 shrink-0">
<Button <Button

View file

@ -704,7 +704,7 @@ export function CompanyImport() {
}, [companyAgents]); }, [companyAgents]);
const localZipHelpText = const localZipHelpText =
"Upload a .zip exported directly from Paperclip. Re-zipped archives created by Finder, Explorer, or other zip tools may not import correctly."; `Upload a .zip exported directly from ${VOCAB.appName}. Re-zipped archives created by Finder, Explorer, or other zip tools may not import correctly.`;
useEffect(() => { useEffect(() => {
setBreadcrumbs([ setBreadcrumbs([
@ -1086,7 +1086,7 @@ export function CompanyImport() {
const selectedAction = selectedFile ? (actionMap.get(selectedFile) ?? null) : null; const selectedAction = selectedFile ? (actionMap.get(selectedFile) ?? null) : null;
if (!selectedCompanyId) { if (!selectedCompanyId) {
return <EmptyState icon={Download} message="Select a company to import into." />; return <EmptyState icon={Download} message={`Select a ${VOCAB.company.toLowerCase()} to import into.`} />;
} }
return ( return (
@ -1096,7 +1096,7 @@ export function CompanyImport() {
<div> <div>
<h2 className="text-base font-semibold">Import source</h2> <h2 className="text-base font-semibold">Import source</h2>
<p className="text-xs text-muted-foreground mt-1"> <p className="text-xs text-muted-foreground mt-1">
Choose a GitHub repo or upload a local Paperclip zip package. {`Choose a GitHub repo or upload a local ${VOCAB.appName} zip package.`}
</p> </p>
</div> </div>
@ -1178,7 +1178,7 @@ export function CompanyImport() {
</Field> </Field>
)} )}
<Field label="Target" hint="Import into this company or create a new one."> <Field label="Target" hint={`Import into this ${VOCAB.company.toLowerCase()} or create a new one.`}>
<select <select
className="w-full rounded-md border border-border bg-transparent px-2.5 py-1.5 text-sm outline-none" className="w-full rounded-md border border-border bg-transparent px-2.5 py-1.5 text-sm outline-none"
value={targetMode} value={targetMode}
@ -1187,16 +1187,16 @@ export function CompanyImport() {
setImportPreview(null); setImportPreview(null);
}} }}
> >
<option value="new">Create new company</option> <option value="new">{`Create new ${VOCAB.company.toLowerCase()}`}</option>
<option value="existing"> <option value="existing">
Existing company: {selectedCompany?.name} {`Existing ${VOCAB.company.toLowerCase()}: ${selectedCompany?.name}`}
</option> </option>
</select> </select>
</Field> </Field>
{targetMode === "new" && ( {targetMode === "new" && (
<Field <Field
label="New company name" label={`New ${VOCAB.company.toLowerCase()} name`}
hint="Optional override. Leave blank to use the package name." hint="Optional override. Leave blank to use the package name."
> >
<input <input
@ -1211,7 +1211,7 @@ export function CompanyImport() {
<Field <Field
label="Collision strategy" label="Collision strategy"
hint="Board imports can rename, skip, or replace matching company content." hint={`${VOCAB.board} imports can rename, skip, or replace matching ${VOCAB.company.toLowerCase()} content.`}
> >
<select <select
className="w-full rounded-md border border-border bg-transparent px-2.5 py-1.5 text-sm outline-none" className="w-full rounded-md border border-border bg-transparent px-2.5 py-1.5 text-sm outline-none"

View file

@ -208,7 +208,7 @@ export function CompanySettings() {
if (!selectedCompany) { if (!selectedCompany) {
return ( return (
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
No company selected. Select a company from the switcher above. {`No ${VOCAB.company.toLowerCase()} selected. Select a ${VOCAB.company.toLowerCase()} from the switcher above.`}
</div> </div>
); );
} }

View file

@ -1,4 +1,5 @@
import { useEffect, useMemo, useRef, useState, type ComponentType } from "react"; import { useEffect, useMemo, useRef, useState, type ComponentType } from "react";
import { VOCAB } from "@paperclipai/branding";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { import type {
BudgetPolicySummary, BudgetPolicySummary,
@ -35,6 +36,12 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
const NO_COMPANY = "__none__"; const NO_COMPANY = "__none__";
const SCOPE_LABELS: Record<string, string> = {
company: VOCAB.company,
agent: "Agent",
project: "Project",
};
function currentWeekRange(): { from: string; to: string } { function currentWeekRange(): { from: string; to: string } {
const now = new Date(); const now = new Date();
const day = now.getDay(); const day = now.getDay();
@ -529,7 +536,7 @@ export function Costs() {
}), [budgetPolicies]); }), [budgetPolicies]);
if (!selectedCompanyId) { if (!selectedCompanyId) {
return <EmptyState icon={DollarSign} message="Select a company to view costs." />; return <EmptyState icon={DollarSign} message={`Select a ${VOCAB.company.toLowerCase()} to view costs.`} />;
} }
const showCustomPrompt = preset === "custom" && !customReady; const showCustomPrompt = preset === "custom" && !customReady;
@ -855,7 +862,7 @@ export function Costs() {
<MetricTile <MetricTile
label="Pending approvals" label="Pending approvals"
value={String(budgetData?.pendingApprovalCount ?? 0)} value={String(budgetData?.pendingApprovalCount ?? 0)}
subtitle="Budget override approvals awaiting board action" subtitle="Budget override approvals awaiting owner action"
icon={ArrowUpRight} icon={ArrowUpRight}
/> />
<MetricTile <MetricTile
@ -907,10 +914,10 @@ export function Costs() {
return ( return (
<section key={scopeType} className="space-y-3"> <section key={scopeType} className="space-y-3">
<div> <div>
<h2 className="text-lg font-semibold capitalize">{scopeType} budgets</h2> <h2 className="text-lg font-semibold">{SCOPE_LABELS[scopeType] ?? scopeType} budgets</h2>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
{scopeType === "company" {scopeType === "company"
? "Company-wide monthly policy." ? `${VOCAB.company}-wide monthly policy.`
: scopeType === "agent" : scopeType === "agent"
? "Recurring monthly spend policies for individual agents." ? "Recurring monthly spend policies for individual agents."
: "Lifetime spend policies for execution-bound projects."} : "Lifetime spend policies for execution-bound projects."}
@ -939,7 +946,7 @@ export function Costs() {
{budgetPolicies.length === 0 ? ( {budgetPolicies.length === 0 ? (
<Card> <Card>
<CardContent className="px-5 py-8 text-sm text-muted-foreground"> <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 ${VOCAB.company.toLowerCase()} monthly budget control.`}
</CardContent> </CardContent>
</Card> </Card>
) : null} ) : null}

View file

@ -277,8 +277,8 @@ export function Dashboard() {
description={ description={
<span> <span>
{data.budgets.pendingApprovals > 0 {data.budgets.pendingApprovals > 0
? `${data.budgets.pendingApprovals} budget overrides awaiting board review` ? `${data.budgets.pendingApprovals} budget overrides awaiting owner review`
: "Awaiting board review"} : "Awaiting owner review"}
</span> </span>
} }
/> />

View file

@ -1,4 +1,5 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { VOCAB } from "@paperclipai/branding";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Clock3, ExternalLink, Settings } from "lucide-react"; import { Clock3, ExternalLink, Settings } from "lucide-react";
import type { InstanceSchedulerHeartbeatAgent } from "@paperclipai/shared"; import type { InstanceSchedulerHeartbeatAgent } from "@paperclipai/shared";
@ -171,14 +172,14 @@ export function InstanceSettings() {
<h1 className="text-lg font-semibold">Scheduler Heartbeats</h1> <h1 className="text-lg font-semibold">Scheduler Heartbeats</h1>
</div> </div>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Agents with a timer heartbeat enabled across all of your companies. {`Agents with a timer heartbeat enabled across all of your ${VOCAB.companies.toLowerCase()}.`}
</p> </p>
</div> </div>
<div className="flex items-center gap-4 text-sm text-muted-foreground"> <div className="flex items-center gap-4 text-sm text-muted-foreground">
<span><span className="font-semibold text-foreground">{activeCount}</span> active</span> <span><span className="font-semibold text-foreground">{activeCount}</span> active</span>
<span><span className="font-semibold text-foreground">{disabledCount}</span> disabled</span> <span><span className="font-semibold text-foreground">{disabledCount}</span> disabled</span>
<span><span className="font-semibold text-foreground">{grouped.length}</span> {grouped.length === 1 ? "company" : "companies"}</span> <span><span className="font-semibold text-foreground">{grouped.length}</span> {grouped.length === 1 ? VOCAB.company.toLowerCase() : VOCAB.companies.toLowerCase()}</span>
{anyEnabled && ( {anyEnabled && (
<Button <Button
variant="destructive" variant="destructive"