Cut Settings from six tabs to four and stop drowning it in chrome

General (identity, mode, workspaces), Providers, Skills, and Advanced
(Composio key, the operator request console, billing, migration) replace
the six-tab sprawl. Card-in-card nesting and every decorative subtitle are
gone; row actions - provider Check/Manage/Revoke, workspace Add, skill
Import, migration Report - reveal on hover or focus instead of shouting all
the time. The Composio panel loses its hardcoded org-wide toolkit grid,
which the Connectors grants model had already made dead weight, and keeps
only the API key. Fixing the flow also surfaced a real bug: provider policy
rows remounted on a snapshot-derived key, so any refresh mid-edit wiped the
operator's unsaved choices - rows now keep stable identity and edits
survive. E2e follows the new layout.
This commit is contained in:
ZacharyZhang-NY committed 2026-07-06 23:48:31 -04:00
1 parent 2f95fc2f2d
commit d64cf22feb
11 files changed
+186 -216

No files matched your search

@@ -2,13 +2,11 @@ import { useMemo, useState } from "react";
import { ProvidersPanel } from "./settings/ProvidersPanel";
import { SkillsPanel } from "./settings/SkillsPanel";
import { WorkspacePanel } from "./settings/WorkspacePanel";
import { BillingPanel } from "./settings/BillingPanel";
import { MigrationPanel } from "./settings/MigrationPanel";
import { ComposioPanel } from "./settings/ComposioPanel";
import { AdvancedPanel } from "./settings/AdvancedPanel";
import type { PageProps } from "./types";
import { activeOrgId, requireActiveEmployee, requireActiveOrganization } from "./types";
type SettingsTab = "workspace" | "billing" | "providers" | "tools" | "skills" | "migration";
type SettingsTab = "general" | "providers" | "skills" | "advanced";
interface NavSpec {
id: SettingsTab;
@@ -16,19 +14,17 @@ interface NavSpec {
}
const TABS: NavSpec[] = [
{ id: "workspace", label: "Workspace" },
{ id: "billing", label: "Billing" },
{ id: "general", label: "General" },
{ id: "providers", label: "Providers" },
{ id: "tools", label: "Tools" },
{ id: "skills", label: "Skills" },
{ id: "migration", label: "Migration" },
{ id: "advanced", label: "Advanced" },
];
export function SettingsPage({ snapshot, actions }: PageProps) {
const orgId = activeOrgId(snapshot);
const organization = requireActiveOrganization(snapshot);
const employee = requireActiveEmployee(snapshot);
const [tab, setTab] = useState<SettingsTab>("providers");
const [tab, setTab] = useState<SettingsTab>("general");
const agents = useMemo(() => snapshot.agents.filter((item) => item.orgId === orgId), [snapshot.agents, orgId]);
const providers = useMemo(() => snapshot.providers.filter((item) => item.orgId === orgId), [snapshot.providers, orgId]);
@@ -60,8 +56,7 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
</aside>
<div className="main" style={{ flex: 1, minWidth: 0 }}>
<div className="content">
{tab === "workspace" && <WorkspacePanel organization={organization} workspaces={workspaces} projects={snapshot.projects.filter((p) => p.orgId === orgId)} orgId={orgId} actions={actions} />}
{tab === "billing" && <BillingPanel agents={agents} providers={providers} providerRequests={providerRequests} hiringPolicies={hiringPolicies} />}
{tab === "general" && <WorkspacePanel organization={organization} workspaces={workspaces} projects={snapshot.projects.filter((p) => p.orgId === orgId)} orgId={orgId} actions={actions} />}
{tab === "providers" && (
<ProvidersPanel
orgId={orgId}
@@ -70,13 +65,10 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
authProfiles={providerAuthProfiles}
catalog={snapshot.providerCatalog}
agents={agents}
workspaces={workspaces}
providerRateLimits={providerRateLimits}
providerRequests={providerRequests}
actions={actions}
/>
)}
{tab === "tools" && <ComposioPanel orgId={orgId} settings={snapshot.composioSettings} actions={actions} />}
{tab === "skills" && (
<SkillsPanel
orgId={orgId}
@@ -88,11 +80,18 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
actions={actions}
/>
)}
{tab === "migration" && (
<MigrationPanel
{tab === "advanced" && (
<AdvancedPanel
orgId={orgId}
employeeId={employee.id}
reports={migrationReports}
agents={agents}
providers={providers}
catalog={snapshot.providerCatalog}
workspaces={workspaces}
providerRequests={providerRequests}
hiringPolicies={hiringPolicies}
composioSettings={snapshot.composioSettings}
migrationReports={migrationReports}
rollbackSnapshots={rollbackSnapshots}
reconciliationItems={snapshot.migrationReconciliationItems.filter((item) => item.orgId === orgId)}
actions={actions}
@@ -0,0 +1,86 @@
import type {
AgentRecord,
HiringPolicyRecord,
MigrationReportRecord,
ProviderCatalogEntry,
ProviderRecord,
ProviderRequestRecord,
RollbackSnapshotRecord,
WorkspaceRecord,
} from "../../../../shared/domain";
import type { MigrationReconciliationItemRecord } from "../../../../shared/rollback-domain";
import type { ComposioSettingRecord } from "../../../../shared/snapshot-domain";
import type { SnapshotActions } from "../../app/useSnapshot";
import { BillingPanel } from "./BillingPanel";
import { ComposioPanel } from "./ComposioPanel";
import { MigrationPanel } from "./MigrationPanel";
import { ProviderRequestPanel } from "./ProviderRequestPanel";
/** The low-frequency operator surfaces, one section each. */
export function AdvancedPanel({
orgId,
employeeId,
agents,
providers,
catalog,
workspaces,
providerRequests,
hiringPolicies,
composioSettings,
migrationReports,
rollbackSnapshots,
reconciliationItems,
actions,
}: {
orgId: string;
employeeId: string;
agents: AgentRecord[];
providers: ProviderRecord[];
catalog: ProviderCatalogEntry[];
workspaces: WorkspaceRecord[];
providerRequests: ProviderRequestRecord[];
hiringPolicies: HiringPolicyRecord[];
composioSettings: ComposioSettingRecord[];
migrationReports: MigrationReportRecord[];
rollbackSnapshots: RollbackSnapshotRecord[];
reconciliationItems: MigrationReconciliationItemRecord[];
actions: SnapshotActions;
}) {
const connected = providers.filter((provider) => provider.enabled);
return (
<div className="page">
<h1 className="page-h1">Advanced</h1>
<div className="section-label" style={{ margin: "16px 0 10px" }}>Composio</div>
<ComposioPanel orgId={orgId} settings={composioSettings} actions={actions} />
{connected.length > 0 && (
<div style={{ marginTop: 28 }}>
<ProviderRequestPanel
orgId={orgId}
providers={connected}
catalog={catalog}
workspaces={workspaces}
providerRequests={providerRequests}
actions={actions}
/>
</div>
)}
<div style={{ marginTop: 28 }}>
<BillingPanel agents={agents} providers={providers} providerRequests={providerRequests} hiringPolicies={hiringPolicies} />
</div>
<div style={{ marginTop: 28 }}>
<MigrationPanel
orgId={orgId}
employeeId={employeeId}
reports={migrationReports}
rollbackSnapshots={rollbackSnapshots}
reconciliationItems={reconciliationItems}
actions={actions}
/>
</div>
</div>
);
}
@@ -22,9 +22,8 @@ export function BillingPanel({
const agentSpend = buildAgentSpend(agents, providers, monthRequests);
return (
<div className="page">
<h1 className="page-h1">Billing</h1>
<p className="page-sub">Allocations, spend, and cost breakdown.</p>
<div>
<div className="section-label" style={{ marginBottom: 10 }}>Billing</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(4, minmax(0, 1fr))", gap: 12, marginBottom: 20 }}>
<StatTile label="Monthly allocation" value={money(totalBudget)} />
@@ -3,44 +3,14 @@ import { Button } from "../../shared/ui";
import type { SnapshotActions } from "../../app/useSnapshot";
import type { ComposioSettingRecord } from "../../../../shared/snapshot-domain";
const AVAILABLE_TOOLKITS = [
{ id: "github", label: "GitHub" },
{ id: "gmail", label: "Gmail" },
{ id: "slack", label: "Slack" },
{ id: "notion", label: "Notion" },
{ id: "linear", label: "Linear" },
{ id: "jira", label: "Jira" },
{ id: "google_calendar", label: "Google Calendar" },
{ id: "google_drive", label: "Google Drive" },
{ id: "google_docs", label: "Google Docs" },
{ id: "google_sheets", label: "Google Sheets" },
{ id: "trello", label: "Trello" },
{ id: "asana", label: "Asana" },
{ id: "discord", label: "Discord" },
{ id: "twitter", label: "Twitter / X" },
{ id: "youtube", label: "YouTube" },
{ id: "hubspot", label: "HubSpot" },
{ id: "salesforce", label: "Salesforce" },
{ id: "zendesk", label: "Zendesk" },
{ id: "figma", label: "Figma" },
{ id: "airtable", label: "Airtable" },
];
function parseSavedToolkits(settings: ComposioSettingRecord[], orgId: string): Set<string> {
const row = settings.find((s) => s.orgId === orgId && s.key === "toolkits");
if (!row?.value) return new Set();
try {
const parsed = JSON.parse(row.value);
return new Set(Array.isArray(parsed) ? parsed.filter((v): v is string => typeof v === "string") : []);
} catch {
return new Set();
}
}
function hasSavedKey(settings: ComposioSettingRecord[], orgId: string): boolean {
return settings.some((s) => s.orgId === orgId && s.key === "api_key" && s.value.length > 0);
}
/**
* The Composio project API key. Which toolkits agents may use is decided by
* connections and per-agent grants on the Connectors page, not here.
*/
export function ComposioPanel({
orgId,
settings,
@@ -50,98 +20,33 @@ export function ComposioPanel({
settings: ComposioSettingRecord[];
actions: SnapshotActions;
}) {
const savedToolkits = useMemo(() => parseSavedToolkits(settings, orgId), [settings, orgId]);
const keyConfigured = useMemo(() => hasSavedKey(settings, orgId), [settings, orgId]);
const [apiKey, setApiKey] = useState("");
const [selected, setSelected] = useState<Set<string>>(savedToolkits);
const [saved, setSaved] = useState(false);
const toggle = (id: string) => {
setSelected((prev) => {
const next = new Set(prev);
if (next.has(id)) next.delete(id);
else next.add(id);
return next;
});
};
const handleSave = async (e: FormEvent) => {
e.preventDefault();
const handleSave = (event: FormEvent) => {
event.preventDefault();
const key = apiKey.trim();
if (!key && !keyConfigured) return;
const row = settings.find((s) => s.orgId === orgId && s.key === "api_key");
const effectiveKey = key || row?.value || "";
if (!effectiveKey) return;
await actions.run(() => window.agentCompany.saveComposioSettings({
if (!key) return;
void actions.run(() => window.agentCompany.saveComposioSettings({
orgId,
apiKey: effectiveKey,
toolkits: [...selected],
}));
setApiKey("");
setSaved(true);
setTimeout(() => setSaved(false), 2000);
apiKey: key,
toolkits: [],
})).then(() => setApiKey(""));
};
return (
<div style={{ maxWidth: 640 }}>
<h2 style={{ fontSize: 18, fontWeight: 600, marginBottom: 4 }}>External Tools</h2>
<p style={{ fontSize: 13, color: "var(--text-3)", marginBottom: 24 }}>
Connect your agents to external services via Composio.
</p>
<form onSubmit={handleSave}>
<label style={{ display: "block", marginBottom: 16 }}>
<span style={{ fontSize: 13, fontWeight: 500, display: "block", marginBottom: 6 }}>API Key</span>
<input
type="password"
className="input"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder={keyConfigured ? "••••••••" : undefined}
autoComplete="off"
style={{ width: "100%", maxWidth: 400 }}
/>
</label>
<div style={{ marginBottom: 20 }}>
<span style={{ fontSize: 13, fontWeight: 500, display: "block", marginBottom: 8 }}>Toolkits</span>
<div style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(140px, 1fr))",
gap: 6,
}}>
{AVAILABLE_TOOLKITS.map((toolkit) => (
<label
key={toolkit.id}
style={{
display: "flex",
alignItems: "center",
gap: 8,
padding: "6px 10px",
borderRadius: 8,
cursor: "pointer",
fontSize: 13,
background: selected.has(toolkit.id) ? "var(--accent-soft)" : "transparent",
transition: "background 0.12s",
}}
>
<input
type="checkbox"
checked={selected.has(toolkit.id)}
onChange={() => toggle(toolkit.id)}
style={{ accentColor: "var(--accent)" }}
/>
{toolkit.label}
</label>
))}
</div>
</div>
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
<Button tone="primary" type="submit" disabled={!apiKey.trim() && !keyConfigured}>
Save
</Button>
{saved && <span style={{ fontSize: 13, color: "var(--green)" }}>Saved</span>}
</div>
</form>
</div>
<form onSubmit={handleSave} className="row" style={{ gap: 8, maxWidth: 480 }}>
<input
type="password"
className="input"
aria-label="Composio API key"
value={apiKey}
onChange={(event) => setApiKey(event.target.value)}
placeholder={keyConfigured ? "••••••••" : "ak_..."}
autoComplete="off"
style={{ flex: 1 }}
/>
<Button tone="primary" type="submit" disabled={!apiKey.trim()}>Save</Button>
</form>
);
}
@@ -40,13 +40,10 @@ export function MigrationPanel({
};
return (
<div className="page">
<div className="row" style={{ marginBottom: 24 }}>
<div>
<h1 className="page-h1">Migration</h1>
<p className="page-sub" style={{ marginBottom: 0 }}>{reports.length} migration {reports.length === 1 ? "report" : "reports"}.</p>
</div>
<div style={{ marginLeft: "auto" }}>
<div className="reveal-row">
<div className="row" style={{ marginBottom: 10 }}>
<div className="section-label">Migration</div>
<div className="reveal" style={{ marginLeft: "auto" }}>
<Button tone="primary" onClick={() => setOpen(true)}><Icon name="plus" size={16} /> Report</Button>
</div>
</div>
@@ -7,12 +7,9 @@ import type {
ProviderAuthProfileRecord,
ProviderRateLimitStateRecord,
ProviderRecord,
ProviderRequestRecord,
WorkspaceRecord,
} from "../../../../shared/domain";
import { PROVIDER_DATA_POLICY } from "../../../../shared/domain";
import { ProviderLogo } from "./ProviderLogo";
import { ProviderRequestPanel } from "./ProviderRequestPanel";
import { ProviderConnectFields, ProviderOAuthFields } from "./ProviderConnectionDialog";
import { composeSecret } from "./provider-secret";
import type { SnapshotActions } from "../../app/useSnapshot";
@@ -24,16 +21,14 @@ interface ProvidersPanelProps {
authProfiles: ProviderAuthProfileRecord[];
catalog: ProviderCatalogEntry[];
agents: AgentRecord[];
workspaces: WorkspaceRecord[];
providerRateLimits: ProviderRateLimitStateRecord[];
providerRequests: ProviderRequestRecord[];
actions: SnapshotActions;
}
const POLICY_GRID = "minmax(150px, 1.2fr) minmax(118px, 0.8fr) minmax(140px, 1fr) 112px 124px 92px";
type WorkspaceScope = "organization" | "workspace";
export function ProvidersPanel({ orgId, employeeId, providers, authProfiles, catalog, agents, workspaces, providerRateLimits, providerRequests, actions }: ProvidersPanelProps) {
export function ProvidersPanel({ orgId, 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);
@@ -62,11 +57,10 @@ export function ProvidersPanel({ orgId, employeeId, providers, authProfiles, cat
return (
<div className="page">
<h1 className="page-h1">Providers</h1>
<p className="page-sub">Connect model providers — keys, accounts, OAuth.</p>
{connected.length > 0 && (
<>
<div className="section-label" style={{ marginBottom: 10 }}>Connected</div>
<div className="section-label" style={{ margin: "16px 0 10px" }}>Connected</div>
<div style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: 28 }}>
{connected.map((provider) => (
<ProviderRow
@@ -86,14 +80,6 @@ export function ProvidersPanel({ orgId, employeeId, providers, authProfiles, cat
providerRateLimits={providerRateLimits}
actions={actions}
/>
<ProviderRequestPanel
orgId={orgId}
providers={connected}
catalog={catalog}
workspaces={workspaces}
providerRequests={providerRequests}
actions={actions}
/>
</>
)}
@@ -159,7 +145,7 @@ function ProviderPolicyTable({
const rateLimit = requireProviderRateLimit(provider, providerRateLimits);
return (
<ProviderPolicyRow
key={`${provider.id}:${provider.dataPolicy}:${provider.backgroundEnabled}:${provider.monthlyRequestLimit}`}
key={provider.id}
orgId={orgId}
provider={provider}
rateLimit={rateLimit}
@@ -283,6 +269,7 @@ function ProviderRow({
const boundCount = agents.filter((agent) => agent.modelProviderId === provider.id).length;
return (
<div
className="reveal-row"
style={{
display: "flex",
alignItems: "center",
@@ -301,13 +288,14 @@ function ProviderRow({
</div>
</div>
<ProviderHealth provider={provider} />
<div style={{ textAlign: "right", minWidth: 120 }}>
<div className="num mono" style={{ fontSize: 14, fontWeight: 540 }}>{boundCount} {boundCount === 1 ? "agent" : "agents"}</div>
<div className="mono" style={{ fontSize: 12, color: "var(--text-3)" }}>bound</div>
<div className="num mono" style={{ fontSize: 13, minWidth: 72, textAlign: "right" }}>
{boundCount} {boundCount === 1 ? "agent" : "agents"}
</div>
<Button onClick={onCheck}>Check</Button>
<Button onClick={onManage}>Manage</Button>
<Button tone="ghost" onClick={onRevoke}>Revoke</Button>
<span className="reveal row" style={{ gap: 6 }}>
<Button onClick={onCheck}>Check</Button>
<Button onClick={onManage}>Manage</Button>
<Button tone="ghost" onClick={onRevoke}>Revoke</Button>
</span>
</div>
);
}
@@ -44,15 +44,10 @@ export function SkillsPanel({ orgId, skills, revisions, bindings, tests, agents,
};
return (
<div className="page">
<div className="page reveal-row">
<div className="row" style={{ marginBottom: 24 }}>
<div>
<h1 className="page-h1">Skills</h1>
<p className="page-sub" style={{ marginBottom: 0 }}>
{skills.length} {skills.length === 1 ? "definition" : "definitions"} · {revisions.length} revisions · {bindings.filter((b) => b.enabled).length} bindings
</p>
</div>
<div style={{ marginLeft: "auto" }}>
<h1 className="page-h1" style={{ margin: 0 }}>Skills</h1>
<div className="reveal" style={{ marginLeft: "auto" }}>
<Button tone="primary" onClick={() => setImportOpen(true)}><Icon name="plus" size={16} /> Import</Button>
</div>
</div>
@@ -48,44 +48,38 @@ export function WorkspacePanel({
return (
<div className="page">
<h1 className="page-h1">Workspace</h1>
<p className="page-sub">Identity, mission, and execution mode for {organization.name}.</p>
<h1 className="page-h1">General</h1>
<div className="card" style={{ padding: 18, marginBottom: 20 }}>
<div className="section-label" style={{ marginBottom: 12 }}>Identity</div>
<div className="row" style={{ gap: 8, margin: "16px 0 24px" }}>
<Pill tone="violet" dot>{organization.executionMode === "ai_ceo" ? "AI CEO" : "AI Co-Founder"}</Pill>
{organization.autoApproveHires ? <Pill tone="green" dot>Auto-approve hires</Pill> : <Pill>Hires require approval</Pill>}
{organization.reviewDeliverables ? <Pill tone="amber" dot>Reviews deliverables</Pill> : <Pill>Ships without review</Pill>}
</div>
<div style={{ marginBottom: 28 }}>
<KeyValue label="Name" value={organization.name} />
<KeyValue label="Mission" value={organization.mission || "—"} />
<KeyValue label="Goal" value={organization.goal || "—"} />
<KeyValue label="Description" value={organization.description || "—"} />
</div>
<div className="card" style={{ padding: 18, marginBottom: 20 }}>
<div className="section-label" style={{ marginBottom: 12 }}>Execution mode</div>
<div className="row" style={{ gap: 8 }}>
<Pill tone="violet" dot>{organization.executionMode === "ai_ceo" ? "AI CEO" : "AI Co-Founder"}</Pill>
{organization.autoApproveHires ? <Pill tone="green" dot>Auto-approve hires</Pill> : <Pill>Hires require approval</Pill>}
{organization.reviewDeliverables ? <Pill tone="amber" dot>Reviews deliverables</Pill> : <Pill>Deliverables ship without review</Pill>}
</div>
<div className="row reveal-row" style={{ marginBottom: 8 }}>
<div className="section-label">Workspaces</div>
<span className="reveal" style={{ marginLeft: "auto" }}>
<Button tone="ghost" onClick={() => setCreateOpen(true)}><Icon name="plus" size={16} /> Add</Button>
</span>
</div>
<div className="card" style={{ padding: 18 }}>
<div className="row" style={{ marginBottom: 12 }}>
<div className="section-label">Workspaces</div>
<span style={{ marginLeft: "auto" }}><Button tone="ghost" onClick={() => setCreateOpen(true)}><Icon name="plus" size={16} /> Add</Button></span>
</div>
{workspaces.map((workspace) => (
<div key={workspace.id} className="row" style={{ padding: "8px 0", borderBottom: "1px solid var(--border-soft)" }}>
<div style={{ minWidth: 0 }}>
<div style={{ fontWeight: 540 }}>{workspace.name}</div>
<div style={{ fontSize: 12, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{workspace.localPath}</div>
</div>
<div style={{ marginLeft: "auto" }}>
{workspace.isPrimary ? <Pill tone="violet">Primary</Pill> : <Pill>Secondary</Pill>}
</div>
{workspaces.map((workspace) => (
<div key={workspace.id} className="row" style={{ padding: "8px 0", borderBottom: "1px solid var(--border-soft)" }}>
<div style={{ minWidth: 0 }}>
<div style={{ fontWeight: 540 }}>{workspace.name}</div>
<div style={{ fontSize: 12, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{workspace.localPath}</div>
</div>
))}
{workspaces.length === 0 && <div style={{ fontSize: 14, color: "var(--text-3)" }}>No workspaces configured</div>}
</div>
<div style={{ marginLeft: "auto" }}>
{workspace.isPrimary && <Pill tone="violet">Primary</Pill>}
</div>
</div>
))}
<Dialog title="Add workspace" open={createOpen} submitLabel="Create" onClose={() => setCreateOpen(false)} onSubmit={submit}>
<Field label="Name"><input className="input" name="name" required /></Field>
@@ -226,6 +226,11 @@ a { color: inherit; }
.convo-item:hover { background: var(--hover); color: var(--text-1); }
.convo-item.active { background: var(--panel-3); color: var(--text-1); }
/* Controls that appear on intent: hidden until the row is hovered or focused. */
.reveal-row .reveal { opacity: 0; transition: opacity var(--motion-fast) var(--ease-out); }
.reveal-row:hover .reveal,
.reveal-row:focus-within .reveal { opacity: 1; }
/* Selectable list row used by master-detail panes (Runs, etc.). width:100%
+ min-width:0 lets the inner flex child ellipsize instead of pushing the
row past the pane and forcing a horizontal scrollbar. */
+11 -9
View File
@@ -113,19 +113,27 @@ test("onboards, hires, and creates work in the Electron shell", async () => {
await expect(page.getByRole("button", { name: "Refresh" })).toBeVisible();
await page.getByRole("button", { name: "Settings" }).click();
await expect(page.getByText("AI CEO", { exact: true })).toBeVisible();
await page.getByRole("button", { name: "Providers" }).click();
await page.getByLabel("OpenAI background execution").check();
await page.getByRole("button", { name: "Save OpenAI policy" }).click();
await expect(page.getByText("Requests", { exact: true })).toBeVisible();
// The operator request console lives under Advanced.
await page.getByRole("button", { name: "Advanced" }).click();
await expect(page.getByText("Requests", { exact: true }).first()).toBeVisible();
await expect(page.getByLabel("Request provider")).toBeVisible();
await expect(page.getByRole("button", { name: "Background" })).toBeVisible();
await page.getByLabel("Request prompt").fill("Draft a provider control memo.");
await page.getByLabel("Request instructions").fill("Use direct language.");
await expect(page.getByLabel("Composio API key")).toBeVisible();
await page.getByRole("button", { name: "Providers" }).click();
await page.getByLabel("OpenAI data policy").selectOption("zero_data_retention");
await expect(page.getByLabel("OpenAI background execution")).not.toBeChecked();
await page.getByLabel("OpenAI monthly request limit").fill("25");
await page.getByRole("button", { name: "Save OpenAI policy" }).click();
await expect(page.getByText("0 / 25").first()).toBeVisible();
await page.getByRole("button", { name: "Skills" }).click();
await page.getByRole("button", { name: "Import" }).click();
await page.getByLabel("Directory path").fill(createSkillDirectory());
@@ -135,7 +143,7 @@ test("onboards, hires, and creates work in the Electron shell", async () => {
await page.getByRole("button", { name: "Run tests" }).click();
await expect(page.getByText("1/1 passed").first()).toBeVisible();
const legacyPath = createLegacyDatabase();
await page.getByRole("button", { name: "Migration" }).click();
await page.getByRole("button", { name: "Advanced" }).click();
await page.getByRole("button", { name: "Report" }).click();
await page.getByLabel("Source database path").fill(legacyPath);
await page.locator(".modal").getByRole("button", { name: "Create" }).click();
@@ -150,12 +158,6 @@ test("onboards, hires, and creates work in the Electron shell", async () => {
await page.getByRole("button", { name: "Office" }).click();
await expect(page.locator("canvas")).toBeVisible();
// Composio settings panel
await page.getByRole("button", { name: "Settings" }).click();
await page.getByRole("button", { name: "Tools", exact: true }).click();
await expect(page.getByText("External Tools")).toBeVisible();
await expect(page.getByText("GitHub")).toBeVisible();
// @mention with canonical handle in chat
await page.getByRole("button", { name: "Messages" }).click();
await page.getByText("general").first().click();
@@ -204,7 +206,7 @@ async function verifyPagesDoNotOverflow(page: Page): Promise<void> {
await page.getByRole("button", { name: nav, exact: true }).click();
await expectPageToFit(page);
}
for (const tab of ["Workspace", "Billing", "Providers", "Tools", "Skills", "Migration"]) {
for (const tab of ["General", "Providers", "Skills", "Advanced"]) {
await page.getByRole("button", { name: "Settings" }).click();
await page.getByRole("button", { name: tab, exact: true }).click();
await expectPageToFit(page);
@@ -50,7 +50,7 @@ test("captures every page for visual review", async () => {
await go(page, "Settings", outDir, "17-settings");
await go(page, "Office", outDir, "18-office");
for (const tab of ["Workspace", "Billing", "Skills", "Migration"]) {
for (const tab of ["General", "Providers", "Skills", "Advanced"]) {
await page.getByRole("button", { name: "Settings" }).click();
await page.getByRole("button", { name: tab }).first().click();
await snap(page, outDir, `settings-${tab.toLowerCase()}`);