mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 00:53:38 +02:00
fix(ui): alphabetize experimental settings and hide empty groups (#13905)
## Thinking Path Paperclip operators use Experimental settings to find and manage optional controls. New cards have accumulated outside alphabetical order, and operator-hidden cards leave empty developer and legacy sections. Sort the displayed controls within their existing groups and remove groups with no visible controls. ## Related Issues **What existing behavior does this improve?** The Instance Settings → Experimental page. **Current behavior** Agent Chat follows Cases, MCP aggregators precedes External Objects, and hidden developer/legacy controls leave empty headings. The worktree execution card also ignores its operator visibility key. **Proposed behavior** Cards appear alphabetically by their displayed title within each section. Empty developer and legacy sections disappear. The worktree execution card follows the same operator visibility policy as other experimental controls. **Reason and benefit** Operators can scan the list predictably. Hosted installations show only the controls their operator permits, without empty sections or a worktree-only exception. No matching sorting PR was found in the duplicate search. This is a small improvement to an existing settings page and does not add a roadmap feature. ## What Changed - Reorder existing cards without changing their values or mutation handlers. - Hide empty developer/legacy sections and respect the hidden worktree-execution key. - Cover alphabetical ordering, conditional isolated-workspace controls, a restricted three-control policy, and partly visible sections. - Document sorting and operator visibility. ## Verification - `pnpm --dir ui exec vitest run src/pages/InstanceExperimentalSettings.test.tsx`: 45 tests passed. - `pnpm check:token-gates`: passed. - `pnpm -r typecheck`: passed. - `pnpm build`: passed. - `pnpm --filter @paperclipai/ui typecheck`: passed. - All PR CI checks passed on `88011049be`, including the chat integration shards, full typecheck, build, browser tests, and policy checks. Greptile is 5/5 with no review threads. - Local `pnpm test:run` reported eight failures in unchanged chat, company-skills, email-channel, and workspace exposure suites. Stopped the remaining local run after CI completed successfully. Isolated chat rechecks were skipped by the host database support gate and do not count as passes. All matching CI shards passed; the full local suite is not claimed as green. - Frozen installation is blocked on the base branch by existing overrides/patch configuration drift from the lockfile. Local validation uses `pnpm@9.15.4 install --no-frozen-lockfile`; the original lockfile and manifests are unchanged in this PR. - Diff reviewed for secrets, private references, and run artifacts. ## Risks Settings only change position or visibility. Existing values, managed locks, API contracts, and feature dependencies are unchanged. Each section keeps its own alphabetical list. Reverting this change restores the previous presentation. ## Model Used OpenAI GPT-6 (Codex), with reasoning, repository tools, and test 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 described the issue in-PR following the enhancement template - [x] I have not referenced internal/instance-local Paperclip issues or links - [x] My branch name describes the change and contains no internal ticket id - [x] I have run the relevant 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:
1 parent
fff410dfe7
commit
794b09f834
3 files changed
+288
-218
No files matched your search
@@ -18,6 +18,9 @@ If you need stable behavior for an important workflow, do not rely on an experim
|
||||
## Where you enable them
|
||||
|
||||
Board operators enable or disable experiments from **Instance Settings > Experimental** in the app.
|
||||
Controls are listed alphabetically within each section. Hosting operators can hide
|
||||
controls they manage; empty developer and legacy sections are omitted. Hidden
|
||||
controls retain their configured values.
|
||||
|
||||
The CLI exposes the same surface:
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { flushSync } from "react-dom";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { INSTANCE_FEATURE_KEYS } from "@paperclipai/shared";
|
||||
import type {
|
||||
InstanceExperimentalSettings as InstanceExperimentalSettingsPayload,
|
||||
InstanceExperimentalSettingsWithManaged,
|
||||
@@ -968,6 +969,17 @@ describe("InstanceExperimentalSettings — card ordering and headings (PAP-393)"
|
||||
expect(sections.at(-1)?.textContent).toContain("Goals Sidebar Link");
|
||||
});
|
||||
|
||||
it("alphabetizes cards by their displayed title within each section", async () => {
|
||||
setWorktreeRuntimeMeta(true);
|
||||
await renderPage({ ...defaultExperimentalSettings(), enableIsolatedWorkspaces: true });
|
||||
|
||||
for (const section of container.querySelectorAll("section")) {
|
||||
const titles = [...section.querySelectorAll("h3")].map((heading) => heading.textContent ?? "");
|
||||
expect(titles.length).toBeGreaterThan(1);
|
||||
expect(titles).toEqual([...titles].sort((a, b) => a.localeCompare(b, "en", { sensitivity: "base" })));
|
||||
}
|
||||
});
|
||||
|
||||
it("renders setting cards without a background color", async () => {
|
||||
await renderPage(defaultExperimentalSettings());
|
||||
|
||||
@@ -1001,11 +1013,15 @@ describe("InstanceExperimentalSettings — operator-hidden cards", () => {
|
||||
root = null;
|
||||
queryClient?.clear();
|
||||
container.remove();
|
||||
setWorktreeRuntimeMeta(false);
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
async function renderPage(hiddenSettings?: string[]) {
|
||||
mockInstanceSettingsApi.getExperimental.mockResolvedValue(defaultExperimentalSettings());
|
||||
async function renderPage(
|
||||
hiddenSettings?: string[],
|
||||
settings: InstanceExperimentalSettingsWithManaged = defaultExperimentalSettings(),
|
||||
) {
|
||||
mockInstanceSettingsApi.getExperimental.mockResolvedValue(settings);
|
||||
root = createRoot(container);
|
||||
queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
queryClient.setQueryData(queryKeys.health, {
|
||||
@@ -1030,6 +1046,41 @@ describe("InstanceExperimentalSettings — operator-hidden cards", () => {
|
||||
expect(container.textContent).not.toContain("Show the Apps navigation");
|
||||
});
|
||||
|
||||
it("keeps only permitted controls in alphabetical order, including when hidden features are enabled", async () => {
|
||||
setWorktreeRuntimeMeta(true);
|
||||
const visible = new Set(["enableExternalObjects", "enableMcpAggregators", "enableSimplifiedEnglishInteractions"]);
|
||||
await renderPage(
|
||||
INSTANCE_FEATURE_KEYS.filter((key) => !visible.has(key)).map((key) => `instance.experimental.${key}`),
|
||||
{
|
||||
...defaultExperimentalSettings(),
|
||||
enableIsolatedWorkspaces: true,
|
||||
enableIsolatedWorkspacesByDefault: true,
|
||||
enablePaperclipDeveloperMode: true,
|
||||
managedKeys: {
|
||||
enableIsolatedWorkspacesByDefault: { managed: true, managedBy: "paperclip-cloud" },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect([...container.querySelectorAll("h3")].map((heading) => heading.textContent)).toEqual([
|
||||
"Enable External Objects",
|
||||
"MCP aggregators",
|
||||
"Simplified English Interactions",
|
||||
]);
|
||||
expect([...container.querySelectorAll("section h2")].map((heading) => heading.textContent)).toEqual([
|
||||
"Experimental features",
|
||||
]);
|
||||
expect(container.querySelectorAll('button[role="switch"]')).toHaveLength(3);
|
||||
expect(mockInstanceSettingsApi.updateExperimental).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("retains a section when one of its controls is visible", async () => {
|
||||
const visible = new Set(["enablePaperclipDeveloperMode", "enableGoalsSidebarLink"]);
|
||||
await renderPage(INSTANCE_FEATURE_KEYS.filter((key) => !visible.has(key)).map((key) => `instance.experimental.${key}`));
|
||||
expect(container.querySelector('[aria-labelledby="developer-mode-heading"] h3')?.textContent).toBe("Paperclip Developer Mode");
|
||||
expect(container.querySelector('[aria-labelledby="legacy-heading"] h3')?.textContent).toBe("Goals Sidebar Link");
|
||||
});
|
||||
|
||||
it("shows every toggle when nothing is hidden", async () => {
|
||||
await renderPage();
|
||||
|
||||
|
||||
@@ -121,6 +121,7 @@ function ExperimentalToggleCard({
|
||||
export function InstanceExperimentalSettings() {
|
||||
const { setBreadcrumbs } = useBreadcrumbs();
|
||||
const queryClient = useQueryClient();
|
||||
const { hidden: hiddenSettings } = useHiddenSettings();
|
||||
const [actionError, setActionError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -237,6 +238,17 @@ export function InstanceExperimentalSettings() {
|
||||
experimentalQuery.data?.enableFirstTaskPlanProposal === true;
|
||||
const enableSmokeLab = experimentalQuery.data?.enableSmokeLab === true;
|
||||
const autoRestartDevServerWhenIdle = experimentalQuery.data?.autoRestartDevServerWhenIdle === true;
|
||||
const isVisible = (key: InstanceFeatureKey) => !hiddenSettings.has(experimentalSettingKey(key));
|
||||
const showWorktreeRunExecution = inWorktree && isVisible("enableWorktreeRunExecution");
|
||||
const showDeveloperSection = showWorktreeRunExecution || ([
|
||||
"autoRestartDevServerWhenIdle",
|
||||
"enableManagedSandboxOnly",
|
||||
"enablePaperclipDeveloperMode",
|
||||
"enableServerInfoDebugView",
|
||||
"enableSmokeLab",
|
||||
"enableIssuePlanDecompositions",
|
||||
] satisfies InstanceFeatureKey[]).some(isVisible);
|
||||
const showLegacySection = isVisible("enableClassicTaskInterface") || isVisible("enableGoalsSidebarLink");
|
||||
return (
|
||||
<div className="max-w-6xl space-y-6">
|
||||
<div className="space-y-2">
|
||||
@@ -281,6 +293,18 @@ export function InstanceExperimentalSettings() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="Agent Chat"
|
||||
description="Talk to each agent in one ongoing conversation. Clarify goals and create tasks for execution."
|
||||
footnote="Turning this off preserves conversations and lets active runs finish, but prevents new messages."
|
||||
checked={experimentalQuery.data?.enableAgentChat ?? false}
|
||||
onCheckedChange={(checked) => toggleMutation.mutate({ enableAgentChat: checked })}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableAgentChat"
|
||||
managed={managedKeys.enableAgentChat}
|
||||
ariaLabel="Toggle agent chat experimental setting"
|
||||
/>
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="Beta skills"
|
||||
description="Allow agents to pin beta releases of the Paperclip core skill. Disabling this returns every agent to the default live skill without removing saved pins."
|
||||
@@ -315,17 +339,6 @@ export function InstanceExperimentalSettings() {
|
||||
ariaLabel="Toggle cases experimental setting"
|
||||
/>
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="Agent Chat"
|
||||
description="Talk to each agent in one ongoing conversation. Clarify goals and create tasks for execution."
|
||||
footnote="Turning this off preserves conversations and lets active runs finish, but prevents new messages."
|
||||
checked={experimentalQuery.data?.enableAgentChat ?? false}
|
||||
onCheckedChange={(checked) => toggleMutation.mutate({ enableAgentChat: checked })}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableAgentChat"
|
||||
managed={managedKeys.enableAgentChat}
|
||||
ariaLabel="Toggle agent chat experimental setting"
|
||||
/>
|
||||
<ExperimentalToggleCard
|
||||
title="Chat connectors"
|
||||
description="Connect agents to Slack, GitHub, Discord, Microsoft Teams, and Telegram conversations."
|
||||
@@ -338,18 +351,6 @@ export function InstanceExperimentalSettings() {
|
||||
ariaLabel="Toggle chat connectors experimental setting"
|
||||
/>
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="MCP aggregators"
|
||||
description="Connect Zapier, Arcade, Composio Connect, and Executor through their MCP servers."
|
||||
footnote="Turning this off hides setup for these connectors. Existing MCP connections keep running."
|
||||
checked={experimentalQuery.data?.enableMcpAggregators === true}
|
||||
onCheckedChange={(checked) => toggleMutation.mutate({ enableMcpAggregators: checked })}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableMcpAggregators"
|
||||
managed={managedKeys.enableMcpAggregators}
|
||||
ariaLabel="Toggle MCP aggregators experimental setting"
|
||||
/>
|
||||
|
||||
{SHOW_CONFERENCE_ROOM_EXPERIMENTAL_SETTING ? (
|
||||
<ExperimentalToggleCard
|
||||
title="Conference Room Chat"
|
||||
@@ -407,21 +408,6 @@ export function InstanceExperimentalSettings() {
|
||||
ariaLabel="Toggle isolated workspaces experimental setting"
|
||||
/>
|
||||
|
||||
{enableIsolatedWorkspaces && (
|
||||
<ExperimentalToggleCard
|
||||
title="Use Isolated Workspaces By Default"
|
||||
description="Treat a project that has no execution workspace policy of its own as if it selected isolated workspaces, so its tasks get a per-task worktree instead of sharing the project checkout. A project that carries its own policy keeps it."
|
||||
checked={enableIsolatedWorkspacesByDefault}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enableIsolatedWorkspacesByDefault: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableIsolatedWorkspacesByDefault"
|
||||
managed={managedKeys.enableIsolatedWorkspacesByDefault}
|
||||
ariaLabel="Toggle isolated workspaces by default experimental setting"
|
||||
/>
|
||||
)}
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="Experimental File Viewer"
|
||||
description="Show task detail controls for browsing and previewing workspace files relative to a task."
|
||||
@@ -433,6 +419,31 @@ export function InstanceExperimentalSettings() {
|
||||
ariaLabel="Toggle experimental file viewer setting"
|
||||
/>
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="First task: propose with a plan document"
|
||||
description="When the user's first request is a single task, the chief of staff writes a short plan document and a checkbox card instead of a one-card confirmation. Applies to organizations created after the toggle is flipped."
|
||||
checked={enableFirstTaskPlanProposal}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enableFirstTaskPlanProposal: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableFirstTaskPlanProposal"
|
||||
managed={managedKeys.enableFirstTaskPlanProposal}
|
||||
ariaLabel="Toggle first task plan proposal experimental setting"
|
||||
/>
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="MCP aggregators"
|
||||
description="Connect Zapier, Arcade, Composio Connect, and Executor through their MCP servers."
|
||||
footnote="Turning this off hides setup for these connectors. Existing MCP connections keep running."
|
||||
checked={experimentalQuery.data?.enableMcpAggregators === true}
|
||||
onCheckedChange={(checked) => toggleMutation.mutate({ enableMcpAggregators: checked })}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableMcpAggregators"
|
||||
managed={managedKeys.enableMcpAggregators}
|
||||
ariaLabel="Toggle MCP aggregators experimental setting"
|
||||
/>
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="Paperclip Runner"
|
||||
description="Allow new Codex agents to select the experimental Rust Paperclip Runner, including authenticated runner ingress when a sandbox requires it. Onboarding continues to use legacy adapters. Turning this off hides the choice without affecting existing native runs."
|
||||
@@ -459,19 +470,6 @@ export function InstanceExperimentalSettings() {
|
||||
ariaLabel="Toggle simplified english interactions experimental setting"
|
||||
/>
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="First task: propose with a plan document"
|
||||
description="When the user's first request is a single task, the chief of staff writes a short plan document and a checkbox card instead of a one-card confirmation. Applies to organizations created after the toggle is flipped."
|
||||
checked={enableFirstTaskPlanProposal}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enableFirstTaskPlanProposal: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableFirstTaskPlanProposal"
|
||||
managed={managedKeys.enableFirstTaskPlanProposal}
|
||||
ariaLabel="Toggle first task plan proposal experimental setting"
|
||||
/>
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="Status Cards"
|
||||
description="Enable the experimental shared status-card board and its gated API. Existing card data is kept when this is disabled."
|
||||
@@ -520,187 +518,205 @@ export function InstanceExperimentalSettings() {
|
||||
ariaLabel="Toggle summaries experimental setting"
|
||||
/>
|
||||
|
||||
{enableIsolatedWorkspaces && (
|
||||
<ExperimentalToggleCard
|
||||
title="Use Isolated Workspaces By Default"
|
||||
description="Treat a project that has no execution workspace policy of its own as if it selected isolated workspaces, so its tasks get a per-task worktree instead of sharing the project checkout. A project that carries its own policy keeps it."
|
||||
checked={enableIsolatedWorkspacesByDefault}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enableIsolatedWorkspacesByDefault: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableIsolatedWorkspacesByDefault"
|
||||
managed={managedKeys.enableIsolatedWorkspacesByDefault}
|
||||
ariaLabel="Toggle isolated workspaces by default experimental setting"
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="space-y-3" aria-labelledby="developer-mode-heading">
|
||||
<div className="space-y-1">
|
||||
<h2 id="developer-mode-heading" className="text-sm font-semibold">
|
||||
Paperclip Developer Mode
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Internal tools for developing, testing, and debugging Paperclip.
|
||||
</p>
|
||||
</div>
|
||||
{showDeveloperSection ? (
|
||||
<section className="space-y-3" aria-labelledby="developer-mode-heading">
|
||||
<div className="space-y-1">
|
||||
<h2 id="developer-mode-heading" className="text-sm font-semibold">
|
||||
Paperclip Developer Mode
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Internal tools for developing, testing, and debugging Paperclip.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="Paperclip Developer Mode"
|
||||
description="Show internal Paperclip maintainer tools and observability links, including Honeycomb trace queries on run pages."
|
||||
checked={enablePaperclipDeveloperMode}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enablePaperclipDeveloperMode: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enablePaperclipDeveloperMode"
|
||||
managed={managedKeys.enablePaperclipDeveloperMode}
|
||||
ariaLabel="Toggle Paperclip developer mode experimental setting"
|
||||
/>
|
||||
<ExperimentalToggleCard
|
||||
title="Auto-Restart Dev Server When Idle"
|
||||
description="In `pnpm dev:once`, wait for all queued and running local agent runs to finish, then restart the server automatically when backend changes or migrations make the current boot stale."
|
||||
checked={autoRestartDevServerWhenIdle}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ autoRestartDevServerWhenIdle: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="autoRestartDevServerWhenIdle"
|
||||
managed={managedKeys.autoRestartDevServerWhenIdle}
|
||||
ariaLabel="Toggle guarded dev-server auto-restart"
|
||||
/>
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="Managed Environment Only"
|
||||
description="Hide the local environment and run all agents in the platform-managed environment."
|
||||
checked={enableManagedSandboxOnly}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enableManagedSandboxOnly: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableManagedSandboxOnly"
|
||||
managed={managedKeys.enableManagedSandboxOnly}
|
||||
ariaLabel="Toggle managed environment only experimental setting"
|
||||
/>
|
||||
<ExperimentalToggleCard
|
||||
title="Managed Environment Only"
|
||||
description="Hide the local environment and run all agents in the platform-managed environment."
|
||||
checked={enableManagedSandboxOnly}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enableManagedSandboxOnly: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableManagedSandboxOnly"
|
||||
managed={managedKeys.enableManagedSandboxOnly}
|
||||
ariaLabel="Toggle managed environment only experimental setting"
|
||||
/>
|
||||
|
||||
{inWorktree ? (
|
||||
<Card className="block bg-transparent p-5">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<h3 className="text-sm font-semibold">Run tasks in this worktree</h3>
|
||||
{worktreeRunExecutionManaged ? <ManagedByCloudBadge /> : null}
|
||||
</div>
|
||||
<p className="max-w-2xl text-sm text-muted-foreground">
|
||||
This is an isolated git-worktree preview instance. Turn this on to let the scheduler execute runs
|
||||
here. Only tasks created after enabling will run automatically — copied/pre-existing tasks stay
|
||||
parked. Toggling off and on resets the cutoff.
|
||||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
checked={enableWorktreeRunExecution}
|
||||
onCheckedChange={(checked) => {
|
||||
if (worktreeRunExecutionManaged) return;
|
||||
toggleMutation.mutate({ enableWorktreeRunExecution: checked });
|
||||
}}
|
||||
disabled={toggleMutation.isPending || worktreeRunExecutionManaged}
|
||||
aria-label="Toggle worktree run execution setting"
|
||||
/>
|
||||
</div>
|
||||
<ExperimentalToggleCard
|
||||
title="Paperclip Developer Mode"
|
||||
description="Show internal Paperclip maintainer tools and observability links, including Honeycomb trace queries on run pages."
|
||||
checked={enablePaperclipDeveloperMode}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enablePaperclipDeveloperMode: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enablePaperclipDeveloperMode"
|
||||
managed={managedKeys.enablePaperclipDeveloperMode}
|
||||
ariaLabel="Toggle Paperclip developer mode experimental setting"
|
||||
/>
|
||||
|
||||
{worktreeRunExecutionState.kind === "armed" ? (
|
||||
<div className="flex items-center gap-2 rounded-md border border-emerald-500/30 bg-emerald-500/5 px-3 py-2 text-sm text-foreground">
|
||||
<Play className="h-4 w-4 shrink-0 text-emerald-600" />
|
||||
<span>
|
||||
Running tasks created after{" "}
|
||||
<span className="font-medium">
|
||||
{formatActivationTimestamp(worktreeRunExecutionState.activatedAt)}
|
||||
</span>
|
||||
.
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{worktreeRunExecutionState.kind === "fail_closed" ? (
|
||||
<div className="flex items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/5 px-3 py-2 text-sm">
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-700" />
|
||||
<div className="space-y-0.5">
|
||||
<p className="font-medium text-foreground">Execution is suppressed — effectively off.</p>
|
||||
<p className="text-muted-foreground">
|
||||
{worktreeRunExecutionState.reason === "instance_mismatch"
|
||||
? "This setting was armed in a different instance and copied here, so no tasks run automatically."
|
||||
: "This setting is missing its activation cutoff, so no tasks run automatically."}{" "}
|
||||
Toggle it off and back on to arm execution for tasks created here.
|
||||
{showWorktreeRunExecution ? (
|
||||
<Card className="block bg-transparent p-5">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<h3 className="text-sm font-semibold">Run tasks in this worktree</h3>
|
||||
{worktreeRunExecutionManaged ? <ManagedByCloudBadge /> : null}
|
||||
</div>
|
||||
<p className="max-w-2xl text-sm text-muted-foreground">
|
||||
This is an isolated git-worktree preview instance. Turn this on to let the scheduler execute runs
|
||||
here. Only tasks created after enabling will run automatically — copied/pre-existing tasks stay
|
||||
parked. Toggling off and on resets the cutoff.
|
||||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
checked={enableWorktreeRunExecution}
|
||||
onCheckedChange={(checked) => {
|
||||
if (worktreeRunExecutionManaged) return;
|
||||
toggleMutation.mutate({ enableWorktreeRunExecution: checked });
|
||||
}}
|
||||
disabled={toggleMutation.isPending || worktreeRunExecutionManaged}
|
||||
aria-label="Toggle worktree run execution setting"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="Auto-Restart Dev Server When Idle"
|
||||
description="In `pnpm dev:once`, wait for all queued and running local agent runs to finish, then restart the server automatically when backend changes or migrations make the current boot stale."
|
||||
checked={autoRestartDevServerWhenIdle}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ autoRestartDevServerWhenIdle: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="autoRestartDevServerWhenIdle"
|
||||
managed={managedKeys.autoRestartDevServerWhenIdle}
|
||||
ariaLabel="Toggle guarded dev-server auto-restart"
|
||||
/>
|
||||
{worktreeRunExecutionState.kind === "armed" ? (
|
||||
<div className="flex items-center gap-2 rounded-md border border-emerald-500/30 bg-emerald-500/5 px-3 py-2 text-sm text-foreground">
|
||||
<Play className="h-4 w-4 shrink-0 text-emerald-600" />
|
||||
<span>
|
||||
Running tasks created after{" "}
|
||||
<span className="font-medium">
|
||||
{formatActivationTimestamp(worktreeRunExecutionState.activatedAt)}
|
||||
</span>
|
||||
.
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="Server Info Debug View"
|
||||
description='Show a "Server" section in the account drawer with the current server restart time and running commit.'
|
||||
checked={enableServerInfoDebugView}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enableServerInfoDebugView: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableServerInfoDebugView"
|
||||
managed={managedKeys.enableServerInfoDebugView}
|
||||
ariaLabel="Toggle server info debug view experimental setting"
|
||||
/>
|
||||
{worktreeRunExecutionState.kind === "fail_closed" ? (
|
||||
<div className="flex items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/5 px-3 py-2 text-sm">
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-700" />
|
||||
<div className="space-y-0.5">
|
||||
<p className="font-medium text-foreground">Execution is suppressed — effectively off.</p>
|
||||
<p className="text-muted-foreground">
|
||||
{worktreeRunExecutionState.reason === "instance_mismatch"
|
||||
? "This setting was armed in a different instance and copied here, so no tasks run automatically."
|
||||
: "This setting is missing its activation cutoff, so no tasks run automatically."}{" "}
|
||||
Toggle it off and back on to arm execution for tasks created here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="Smoke Lab"
|
||||
description='Add a "Smoke Lab" tab under Apps → Developer and an "Integration smoke" card on the dashboard for exercising every integration path against deterministic local fixtures (fake OAuth provider + loopback MCP servers). Private (non-public) deployments only.'
|
||||
checked={enableSmokeLab}
|
||||
onCheckedChange={(checked) => toggleMutation.mutate({ enableSmokeLab: checked })}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableSmokeLab"
|
||||
managed={managedKeys.enableSmokeLab}
|
||||
ariaLabel="Toggle smoke lab experimental setting"
|
||||
/>
|
||||
<ExperimentalToggleCard
|
||||
title="Server Info Debug View"
|
||||
description='Show a "Server" section in the account drawer with the current server restart time and running commit.'
|
||||
checked={enableServerInfoDebugView}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enableServerInfoDebugView: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableServerInfoDebugView"
|
||||
managed={managedKeys.enableServerInfoDebugView}
|
||||
ariaLabel="Toggle server info debug view experimental setting"
|
||||
/>
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="Task Plan Decomposition"
|
||||
description="Show accepted-plan decomposition history on task detail pages. Intended for debugging and validating subtask creation behavior while the presentation is still being refined."
|
||||
checked={enableIssuePlanDecompositions}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enableIssuePlanDecompositions: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableIssuePlanDecompositions"
|
||||
managed={managedKeys.enableIssuePlanDecompositions}
|
||||
ariaLabel="Toggle task plan decomposition panel experimental setting"
|
||||
/>
|
||||
</section>
|
||||
<ExperimentalToggleCard
|
||||
title="Smoke Lab"
|
||||
description='Add a "Smoke Lab" tab under Apps → Developer and an "Integration smoke" card on the dashboard for exercising every integration path against deterministic local fixtures (fake OAuth provider + loopback MCP servers). Private (non-public) deployments only.'
|
||||
checked={enableSmokeLab}
|
||||
onCheckedChange={(checked) => toggleMutation.mutate({ enableSmokeLab: checked })}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableSmokeLab"
|
||||
managed={managedKeys.enableSmokeLab}
|
||||
ariaLabel="Toggle smoke lab experimental setting"
|
||||
/>
|
||||
|
||||
<section className="space-y-3" aria-labelledby="legacy-heading">
|
||||
<div className="space-y-1">
|
||||
<h2 id="legacy-heading" className="text-sm font-semibold">
|
||||
Legacy
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">These features are going to be removed.</p>
|
||||
</div>
|
||||
<ExperimentalToggleCard
|
||||
title="Task Plan Decomposition"
|
||||
description="Show accepted-plan decomposition history on task detail pages. Intended for debugging and validating subtask creation behavior while the presentation is still being refined."
|
||||
checked={enableIssuePlanDecompositions}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enableIssuePlanDecompositions: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableIssuePlanDecompositions"
|
||||
managed={managedKeys.enableIssuePlanDecompositions}
|
||||
ariaLabel="Toggle task plan decomposition panel experimental setting"
|
||||
/>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="Classic Task Interface"
|
||||
description="Restores the previous task detail page: the page-level header with inline description editing, the plain comment thread, and the fixed Properties sidebar. Chat-only features — streaming activity folding, inline plan and question cards, the three-mode composer — are unavailable in the classic view."
|
||||
footnote="Switching takes effect immediately. No task data is affected."
|
||||
checked={enableClassicTaskInterface}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enableClassicTaskInterface: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableClassicTaskInterface"
|
||||
managed={managedKeys.enableClassicTaskInterface}
|
||||
ariaLabel="Toggle classic task interface experimental setting"
|
||||
/>
|
||||
{showLegacySection ? (
|
||||
<section className="space-y-3" aria-labelledby="legacy-heading">
|
||||
<div className="space-y-1">
|
||||
<h2 id="legacy-heading" className="text-sm font-semibold">
|
||||
Legacy
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">These features are going to be removed.</p>
|
||||
</div>
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="Goals Sidebar Link"
|
||||
description="Restore the Goals item in the main sidebar while the goals surface is being evaluated."
|
||||
checked={enableGoalsSidebarLink}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enableGoalsSidebarLink: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableGoalsSidebarLink"
|
||||
managed={managedKeys.enableGoalsSidebarLink}
|
||||
ariaLabel="Toggle goals sidebar link experimental setting"
|
||||
/>
|
||||
</section>
|
||||
<ExperimentalToggleCard
|
||||
title="Classic Task Interface"
|
||||
description="Restores the previous task detail page: the page-level header with inline description editing, the plain comment thread, and the fixed Properties sidebar. Chat-only features — streaming activity folding, inline plan and question cards, the three-mode composer — are unavailable in the classic view."
|
||||
footnote="Switching takes effect immediately. No task data is affected."
|
||||
checked={enableClassicTaskInterface}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enableClassicTaskInterface: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableClassicTaskInterface"
|
||||
managed={managedKeys.enableClassicTaskInterface}
|
||||
ariaLabel="Toggle classic task interface experimental setting"
|
||||
/>
|
||||
|
||||
<ExperimentalToggleCard
|
||||
title="Goals Sidebar Link"
|
||||
description="Restore the Goals item in the main sidebar while the goals surface is being evaluated."
|
||||
checked={enableGoalsSidebarLink}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleMutation.mutate({ enableGoalsSidebarLink: checked })
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
settingKey="enableGoalsSidebarLink"
|
||||
managed={managedKeys.enableGoalsSidebarLink}
|
||||
ariaLabel="Toggle goals sidebar link experimental setting"
|
||||
/>
|
||||
</section>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user