Q164: every settings section says whether it affects this company, every company, or this computer
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9826b163a9
commit
93c5eb2d71
8 files changed
+39
-5
No files matched your search
@@ -54,6 +54,9 @@ export const zh: Record<string, string> = {
|
||||
"Cancel run": "取消运行",
|
||||
"Show older runs": "显示更早的运行",
|
||||
"Target date": "目标日期",
|
||||
"Applies to every company in this app.": "适用于本应用中的所有公司。",
|
||||
"Applies to {company} only; the folders are on this computer.": "仅适用于 {company};文件夹位于这台电脑上。",
|
||||
"Applies to {company} only.": "仅适用于 {company}。",
|
||||
"{n} others": "其余 {n} 人",
|
||||
"Work accepted (7 days)": "工作验收(近 7 天)",
|
||||
"{accepted} done · {returned} sent back · sessions: {ok} ended, {failed} failed": "{accepted} 项完成 · {returned} 项退回 · 会话:{ok} 次结束,{failed} 次失败",
|
||||
|
||||
@@ -50,6 +50,7 @@ export function SettingsPage({ snapshot, actions, focus }: PageProps) {
|
||||
{tab === "providers" && (
|
||||
<ProvidersPanel
|
||||
orgId={orgId}
|
||||
companyName={organization.name}
|
||||
employeeId={employee.id}
|
||||
providers={providers}
|
||||
authProfiles={providerAuthProfiles}
|
||||
@@ -59,7 +60,7 @@ export function SettingsPage({ snapshot, actions, focus }: PageProps) {
|
||||
actions={actions}
|
||||
/>
|
||||
)}
|
||||
{tab === "integrations" && <IntegrationKeysPanel keys={snapshot.integrationKeys} actions={actions} />}
|
||||
{tab === "integrations" && <IntegrationKeysPanel keys={snapshot.integrationKeys} companyName={organization.name} actions={actions} />}
|
||||
{tab === "advanced" && (
|
||||
<AdvancedPanel
|
||||
orgId={orgId}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ScopeNote } from "./ScopeNote";
|
||||
import type { UsageStatsRecord } from "../../../../shared/usage-stats";
|
||||
import type { ProviderRateLimitStateRecord } from "../../../../shared/provider-runtime-domain";
|
||||
import type {
|
||||
@@ -45,6 +46,7 @@ export function AdvancedPanel({
|
||||
return (
|
||||
<div className="page">
|
||||
<h1 className="page-h1">{t("Advanced")}</h1>
|
||||
<ScopeNote scope="company" company={organization.name} />
|
||||
|
||||
{connected.length > 0 && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ScopeNote } from "./ScopeNote";
|
||||
import { useState, type FormEvent } from "react";
|
||||
import type { IntegrationKeyName, IntegrationKeyStatus } from "../../../../shared/commands";
|
||||
import { Button, Field, Pill } from "../../shared/ui";
|
||||
@@ -22,11 +23,12 @@ const KEY_COPY: Record<IntegrationKeyName, { label: string; hint: string; placeh
|
||||
},
|
||||
};
|
||||
|
||||
export function IntegrationKeysPanel({ keys, actions }: { keys: IntegrationKeyStatus[]; actions: SnapshotActions }) {
|
||||
export function IntegrationKeysPanel({ keys, companyName, actions }: { keys: IntegrationKeyStatus[]; companyName: string; actions: SnapshotActions }) {
|
||||
return (
|
||||
<div className="page">
|
||||
<h1 className="page-h1">{t("Integrations")}</h1>
|
||||
<p style={{ margin: "8px 0 24px", color: "var(--text-2)" }}>
|
||||
<ScopeNote scope="app" company={companyName} />
|
||||
<p style={{ margin: "0 0 24px", color: "var(--text-2)" }}>
|
||||
{t("Keys are stored on this device, encrypted by the operating system, and shared by every company.")}
|
||||
</p>
|
||||
{keys.map((status) => <KeyRow key={status.name} status={status} actions={actions} />)}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ScopeNote } from "./ScopeNote";
|
||||
import { useState, type FormEvent } from "react";
|
||||
import { Button, Pill } from "../../shared/ui";
|
||||
import { Dialog } from "../../shared/Dialog";
|
||||
@@ -16,6 +17,7 @@ import { composeSecret } from "./provider-secret";
|
||||
import type { SnapshotActions } from "../../app/useSnapshot";
|
||||
|
||||
interface ProvidersPanelProps {
|
||||
companyName: string;
|
||||
orgId: string;
|
||||
employeeId: string;
|
||||
providers: ProviderRecord[];
|
||||
@@ -30,7 +32,7 @@ const PRICE_GRID = "minmax(150px, 1.4fr) 110px 110px 110px 92px";
|
||||
const POLICY_GRID = "minmax(150px, 1.2fr) minmax(118px, 0.8fr) minmax(140px, 1fr) 112px 124px 124px 92px";
|
||||
type WorkspaceScope = "organization" | "workspace";
|
||||
|
||||
export function ProvidersPanel({ orgId, employeeId, providers, authProfiles, catalog, agents, providerRateLimits, actions }: ProvidersPanelProps) {
|
||||
export function ProvidersPanel({ orgId, companyName, employeeId, providers, authProfiles, catalog, agents, providerRateLimits, actions }: ProvidersPanelProps) {
|
||||
const [active, setActive] = useState<ProviderRecord | null>(null);
|
||||
const connected = providers.filter((provider) => provider.enabled);
|
||||
const available = providers.filter((provider) => !provider.enabled);
|
||||
@@ -58,6 +60,7 @@ export function ProvidersPanel({ orgId, employeeId, providers, authProfiles, cat
|
||||
return (
|
||||
<div className="page">
|
||||
<h1 className="page-h1">{t("Providers")}</h1>
|
||||
<ScopeNote scope="company" company={companyName} />
|
||||
|
||||
{connected.length > 0 && (
|
||||
<>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { t } from "../../i18n/i18n";
|
||||
|
||||
/** Where a setting takes effect: only the current company, every company in this app, or the company with folders on this computer. */
|
||||
export type SettingScope = "company" | "app" | "company_folders";
|
||||
|
||||
/** Says, under a settings section, what a change there affects. */
|
||||
export function ScopeNote({ scope, company }: { scope: SettingScope; company: string }) {
|
||||
const text = scope === "app"
|
||||
? t("Applies to every company in this app.")
|
||||
: scope === "company_folders"
|
||||
? t("Applies to {company} only; the folders are on this computer.", { company })
|
||||
: t("Applies to {company} only.", { company });
|
||||
return <div role="note" className="scope-note" style={{ fontSize: 12, color: "var(--text-3)", margin: "4px 0 10px" }}>{text}</div>;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ScopeNote } from "./ScopeNote";
|
||||
import { useState, type FormEvent } from "react";
|
||||
import type {
|
||||
OrganizationRecord,
|
||||
@@ -62,6 +63,7 @@ export function WorkspacePanel({
|
||||
return (
|
||||
<div className="page">
|
||||
<h1 className="page-h1">{t("General")}</h1>
|
||||
<ScopeNote scope="company" company={organization.name} />
|
||||
|
||||
<div className="row" style={{ gap: 8, margin: "16px 0 24px" }}>
|
||||
<Pill tone="violet" dot>{organization.executionMode === "ai_ceo" ? t("AI CEO") : t("AI Co-Founder")}</Pill>
|
||||
@@ -76,7 +78,8 @@ export function WorkspacePanel({
|
||||
<KeyValue label={t("Description")} value={organization.description || "—"} />
|
||||
</div>
|
||||
|
||||
<div className="section-label" style={{ marginBottom: 8 }}>{t("Appearance")}</div>
|
||||
<div className="section-label">{t("Appearance")}</div>
|
||||
<ScopeNote scope="app" company={organization.name} />
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(2, minmax(0, 240px))", gap: 12, marginBottom: 28 }}>
|
||||
<Field label={t("Theme")}>
|
||||
<select
|
||||
@@ -113,6 +116,7 @@ export function WorkspacePanel({
|
||||
<Button tone="ghost" onClick={() => setCreateOpen(true)}><Icon name="plus" size={16} /> {t("Add")}</Button>
|
||||
</span>
|
||||
</div>
|
||||
<ScopeNote scope="company_folders" company={organization.name} />
|
||||
{workspaces.map((workspace) => (
|
||||
<div key={workspace.id} className="row" aria-current={workspace.id === focusedWorkspaceId ? "true" : undefined} style={{ padding: "8px 0", borderBottom: "1px solid var(--border-soft)", background: workspace.id === focusedWorkspaceId ? "var(--accent-soft)" : undefined }}>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
|
||||
@@ -72,6 +72,11 @@ test("the pinned CEO conversation is the Messages front door", async () => {
|
||||
// The message itself says where the reply stands.
|
||||
await expect(chatLog.locator(".chat-message", { hasText: "How are we tracking against the goal?" }).first()).toContainText("CEO will reply; queued.");
|
||||
|
||||
// Each settings section says what a change there affects.
|
||||
await page.getByRole("button", { name: "Settings" }).click();
|
||||
await expect(page.getByRole("note").filter({ hasText: "Applies to FlowPilot only." })).toBeVisible();
|
||||
await expect(page.getByRole("note").filter({ hasText: "Applies to every company in this app." })).toBeVisible();
|
||||
|
||||
// A goal's pace is measured against its target date; without one, no pace is claimed.
|
||||
await page.getByRole("button", { name: "Home" }).click();
|
||||
await expect(page.getByText("no target date")).toBeVisible();
|
||||
|
||||
Reference in new issue
Block a user