mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-03 04:49:26 +00:00
Trim release UI: simpler Settings, mode menu, session subtitle
Mode menu offers Discuss/Ask for approval/Full access only. Topbar subtitle is model-only and inert (persona page hidden this release).
This commit is contained in:
@@ -63,23 +63,19 @@ test("signed in: account row shows the name; one-click appears; sign out from th
|
||||
await expect(page.getByTestId("account-row")).toContainText("Not signed in");
|
||||
});
|
||||
|
||||
test("telemetry toggle: lives in Settings, signed-in only, default on, opt-out round-trips", async ({
|
||||
test("telemetry/Privacy card is gone from Settings (owner ask 2026-07-22), signed in or out", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Signed out: Settings has no toggle at all — nothing is sent, nothing to configure.
|
||||
await page.goto("/");
|
||||
await page.getByTestId("account-row").click();
|
||||
await page.getByTestId("account-menu").getByRole("button", { name: "Settings" }).click();
|
||||
await expect(page.getByRole("heading", { name: "General" })).toBeVisible();
|
||||
await expect(page.getByTestId("telemetry-toggle")).toHaveCount(0);
|
||||
await expect(page.getByText("Privacy", { exact: true })).toHaveCount(0);
|
||||
|
||||
await signIn(page);
|
||||
await page.getByTestId("account-row").click();
|
||||
await page.getByTestId("account-menu").getByRole("button", { name: "Settings" }).click();
|
||||
const toggle = page.getByTestId("telemetry-toggle");
|
||||
await expect(toggle).toBeChecked({ timeout: 10_000 }); // default-on when signed in
|
||||
await expect(page.getByText("never your prompts, files, or connector")).toBeVisible();
|
||||
|
||||
await toggle.uncheck();
|
||||
await expect(toggle).not.toBeChecked(); // survives the status re-fetch (persisted)
|
||||
await expect(page.getByTestId("telemetry-toggle")).toHaveCount(0);
|
||||
await expect(page.getByText("Privacy", { exact: true })).toHaveCount(0);
|
||||
});
|
||||
|
||||
@@ -25,12 +25,13 @@ test("composer: send-gating, + attach menu, Mode menu", async ({ page }) => {
|
||||
await page.locator(".fixed.inset-0.z-30").click();
|
||||
await expect(page.getByRole("button", { name: "Photo or image" })).toHaveCount(0);
|
||||
|
||||
// Mode menu (workspace personas only): the five permission options with the current one
|
||||
// marked, plus the Unattended/send-to-Inbox toggle at the bottom (§22).
|
||||
// Mode menu: the three shipped permission options with the current one marked, plus the
|
||||
// Unattended/send-to-Inbox toggle (§22). Plan + Custom hidden for this release (2026-07-22).
|
||||
await page.getByRole("button", { name: "Mode", exact: true }).click();
|
||||
const menu = page.getByTestId("mode-menu");
|
||||
await expect(menu.getByText("Discuss")).toBeVisible();
|
||||
await expect(menu.getByText("Explore read-only, propose a plan")).toBeVisible();
|
||||
await expect(menu.getByText("Plan", { exact: true })).toHaveCount(0);
|
||||
await expect(menu.getByText("Custom", { exact: true })).toHaveCount(0);
|
||||
// The current mode is marked with a ✓.
|
||||
await expect(menu.locator("button").filter({ hasText: "Ask for approval" })).toContainText("✓");
|
||||
await expect(menu.getByRole("switch", { name: "Send approvals to the Inbox" })).toBeVisible();
|
||||
|
||||
@@ -33,7 +33,7 @@ test("top-left cluster renders only while the sidebar is collapsed", async ({ pa
|
||||
await expect(page.getByTestId("topbar-cluster")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("facts subtitle: absent on a fresh session, persona · model after the first turn; click → persona page", async ({
|
||||
test("facts subtitle: absent on a fresh session, model-only after the first turn, inert", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/");
|
||||
@@ -51,14 +51,13 @@ test("facts subtitle: absent on a fresh session, persona · model after the firs
|
||||
await page.getByRole("button", { name: "Send" }).click();
|
||||
await expect(page.getByText(/Echo: hello/)).toBeVisible();
|
||||
|
||||
// Model only — no persona name (owner ask 2026-07-22: personas are hidden this release),
|
||||
// and the subtitle is a plain fact line, not a button to the persona page.
|
||||
const sub = page.getByTestId("session-subtitle");
|
||||
await expect(sub).toContainText("Coworker · Claude Opus 4.8");
|
||||
await expect(sub).toHaveText("Claude Opus 4.8");
|
||||
await expect(page.locator(".dd").filter({ hasText: "Claude Opus 4.8" })).toBeVisible();
|
||||
|
||||
// The subtitle is the session's fixed facts — clicking it opens the coworker (persona) page,
|
||||
// replacing the old topbar sliders button.
|
||||
await sub.click();
|
||||
await expect(page.getByRole("button", { name: "Back", exact: true })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Back", exact: true })).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("composer is three controls (+ attach · Mode · send); folder and branch chips are gone", async ({
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
type SurfaceVisibility,
|
||||
} from "./api";
|
||||
import type { ApprovalDecision, Attachment, Item, SessionInfo, TodoItem, WsEvent } from "./types";
|
||||
import { isProjectScoped, shortPersonaName } from "./personaScope";
|
||||
import { isProjectScoped } from "./personaScope";
|
||||
import { baseName } from "./paths";
|
||||
import { itemsFromMessages } from "./itemsFromMessages";
|
||||
import { streamMode } from "./streamGate";
|
||||
@@ -1076,9 +1076,7 @@ export function App() {
|
||||
const pendingDirReq = [...items].reverse().find((i) => i.kind === "dirreq" && !i.resolved);
|
||||
const pendingPlan = [...items].reverse().find((i) => i.kind === "planreq" && !i.resolved);
|
||||
const pendingQuestion = [...items].reverse().find((i) => i.kind === "question" && !i.resolved);
|
||||
// Topbar trim: the active persona's short display name (mock's "· SRE persona").
|
||||
const personaName = shortPersonaName(personaOf(agent)?.name, agent);
|
||||
// Facts subtitle (§22): the session's FIXED facts, not controls — persona · model (+ the
|
||||
// Facts subtitle (§22): the session's FIXED facts, not controls — model (+ the
|
||||
// workspace folder for project-scoped sessions). Renders only once the session has history;
|
||||
// until then the model is still choosable in the composer, so there's no locked fact to state.
|
||||
const hasHistory = items.length > 0;
|
||||
@@ -1087,7 +1085,9 @@ export function App() {
|
||||
const modelDisplay =
|
||||
modelLabels[model]?.split(" · ")[0] ||
|
||||
(model.includes(":") ? model.split(":").slice(1).join(":") : model);
|
||||
const subtitleParts = [personaName, modelDisplay];
|
||||
// Persona name dropped for this release (owner ask 2026-07-22): personas are hidden,
|
||||
// so "Coworker" read as noise. The model (+ project folder) are the real fixed facts.
|
||||
const subtitleParts = [modelDisplay];
|
||||
if (isProjectScoped(personaOf(agent)) && workspace) subtitleParts.push(baseName(workspace));
|
||||
const activeInfo = sessions.find((s) => s.session_id === sessionId);
|
||||
const activeTitle = activeInfo?.title || "New session";
|
||||
@@ -1343,16 +1343,12 @@ export function App() {
|
||||
>
|
||||
{activeTitle}
|
||||
</span>
|
||||
{/* Plain facts, no affordance: the persona page it used to open is hidden for
|
||||
this release (owner ask 2026-07-22). */}
|
||||
{hasHistory && (
|
||||
<button
|
||||
className="title-sub"
|
||||
data-testid="session-subtitle"
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onClick={agent !== "chat" ? () => openPersona(agent, "session") : undefined}
|
||||
title={agent !== "chat" ? "About this coworker" : undefined}
|
||||
>
|
||||
<span className="title-sub" data-testid="session-subtitle">
|
||||
{subtitleParts.join(" · ")}
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* Right: session-settings icon (§23) + panel toggle. Model/mode/persona chrome is
|
||||
|
||||
@@ -15,12 +15,14 @@ import {
|
||||
type DictationStatus,
|
||||
} from "../tauri";
|
||||
|
||||
// Plan + Custom hidden for this release (owner ask 2026-07-22): Plan's approval flow isn't
|
||||
// polished enough to ship, and Custom (config.toml auto-allow rules) is a power-user mode
|
||||
// with no in-app explanation. The server still honors both — a session already in one of
|
||||
// those modes keeps working; the picker just doesn't offer them.
|
||||
const PERMISSION_OPTIONS: Option[] = [
|
||||
{ value: "discuss", label: "Discuss", description: "Chat and explore — no edits or commands" },
|
||||
{ value: "plan", label: "Plan", description: "Explore read-only, propose a plan for approval, then build" },
|
||||
{ value: "interactive", label: "Ask for approval", description: "Ask before edits and commands" },
|
||||
{ value: "auto", label: "Full access", description: "Run everything without asking" },
|
||||
{ value: "custom", label: "Custom", description: "Use auto-allow rules from config.toml" },
|
||||
];
|
||||
|
||||
// No hardcoded model fallback: until the server supplies the list (a few seconds after a
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
CLOUD_CHANGED,
|
||||
getCloudStatus,
|
||||
getSettings,
|
||||
setCloudTelemetry,
|
||||
setOnboarded,
|
||||
setPdfSettings,
|
||||
setScratchBase,
|
||||
setSessionsPeek,
|
||||
type CloudStatus,
|
||||
type ModelSettings,
|
||||
type PdfSettings,
|
||||
} from "../api";
|
||||
@@ -428,8 +424,6 @@ function AppearanceSection() {
|
||||
|
||||
<FilesCard />
|
||||
|
||||
<TelemetryCard />
|
||||
|
||||
{desktop && (
|
||||
<div className={CARD + " p-4"}>
|
||||
<div className={FIELD_LABEL + " mb-2.5"}>Always-on</div>
|
||||
@@ -524,46 +518,8 @@ function UpdateInline() {
|
||||
);
|
||||
}
|
||||
|
||||
// -- Telemetry (moved here from the retired Connectors-page cloud strip, §26) -----
|
||||
function TelemetryCard() {
|
||||
const [cloud, setCloud] = useState<CloudStatus | null>(null);
|
||||
const [telemetry, setTelemetry] = useState<boolean | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const load = () => getCloudStatus().then(setCloud).catch(() => {});
|
||||
load();
|
||||
// Sign-in/out lands out-of-band (browser flow, account row) — refetch on the announce.
|
||||
window.addEventListener(CLOUD_CHANGED, load);
|
||||
return () => window.removeEventListener(CLOUD_CHANGED, load);
|
||||
}, []);
|
||||
|
||||
// Signed out there is nothing to toggle — telemetry is a no-op without a cloud session.
|
||||
if (!cloud?.signed_in) return null;
|
||||
return (
|
||||
<div className={CARD + " p-4 mb-4"}>
|
||||
<div className={FIELD_LABEL + " mb-2.5"}>Privacy</div>
|
||||
<label className="flex items-start gap-3 py-1 select-none">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="mt-0.5"
|
||||
checked={telemetry ?? cloud.telemetry_enabled !== false}
|
||||
data-testid="telemetry-toggle"
|
||||
onChange={async (e) => {
|
||||
setTelemetry(e.target.checked);
|
||||
await setCloudTelemetry(e.target.checked);
|
||||
}}
|
||||
/>
|
||||
<span>
|
||||
<span className="block text-[13px] text-ink">Help improve OpenWorker</span>
|
||||
<span className="block text-[12px] text-muted">
|
||||
Which coworker type was started and when; never your prompts, files, or connector
|
||||
data. Signed-out installs send nothing regardless.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// Telemetry/Privacy card removed for this release (owner ask 2026-07-22); the
|
||||
// setCloudTelemetry API stays for a future opt-out surface.
|
||||
|
||||
// -- Sidebar density -------------------------------------------------------------
|
||||
// -- Token savings (PDF attachments; owner ask, 2026-07-17) ---------------------
|
||||
|
||||
Reference in New Issue
Block a user