mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
feat(decisions): gate Decisions sidebar behind experimental flag (PAP-13481)
Add an `enableDecisions` instance experimental setting (default off), gate the Decisions sidebar item on it, and move Decisions below Inbox in the sidebar. Adds a toggle card on the Experimental settings page. Includes parity tests for the validator default/patch, service normalization, the settings toggle, and updates the full-object normalization assertion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
042bb231d9
commit
8db9fa6a14
8 files changed
+86
-7
No files matched your search
@@ -57,6 +57,7 @@ export interface InstanceExperimentalSettings {
|
||||
enableCloudSync: boolean;
|
||||
enableExternalObjects: boolean;
|
||||
enableBuiltInAgents: boolean;
|
||||
enableDecisions: boolean;
|
||||
enableGoalsSidebarLink: boolean;
|
||||
enableServerInfoDebugView: boolean;
|
||||
autoRestartDevServerWhenIdle: boolean;
|
||||
|
||||
@@ -46,6 +46,22 @@ describe("instance experimental settings validators", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("defaults the decisions sidebar link off", () => {
|
||||
const settings = instanceExperimentalSettingsSchema.parse({});
|
||||
|
||||
expect(settings.enableDecisions).toBe(false);
|
||||
});
|
||||
|
||||
it("accepts decisions patches", () => {
|
||||
expect(
|
||||
patchInstanceExperimentalSettingsSchema.parse({
|
||||
enableDecisions: true,
|
||||
}),
|
||||
).toEqual({
|
||||
enableDecisions: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts server info debug view patches", () => {
|
||||
expect(
|
||||
patchInstanceExperimentalSettingsSchema.parse({
|
||||
|
||||
@@ -51,6 +51,7 @@ export const instanceExperimentalSettingsSchema = z.object({
|
||||
enableCloudSync: z.boolean().default(false),
|
||||
enableExternalObjects: z.boolean().default(false),
|
||||
enableBuiltInAgents: z.boolean().default(false),
|
||||
enableDecisions: z.boolean().default(false),
|
||||
enableGoalsSidebarLink: z.boolean().default(false),
|
||||
enableServerInfoDebugView: z.boolean().default(false),
|
||||
autoRestartDevServerWhenIdle: z.boolean().default(false),
|
||||
|
||||
@@ -32,6 +32,7 @@ describe("instance settings service", () => {
|
||||
enableTaskWatchdogs: true,
|
||||
enableCloudSync: true,
|
||||
enableBuiltInAgents: true,
|
||||
enableDecisions: false,
|
||||
enableGoalsSidebarLink: true,
|
||||
enableServerInfoDebugView: true,
|
||||
autoRestartDevServerWhenIdle: true,
|
||||
@@ -76,6 +77,14 @@ describe("instance settings service", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("defaults enableDecisions to false for empty and legacy stored settings", () => {
|
||||
expect(normalizeExperimentalSettings(undefined).enableDecisions).toBe(false);
|
||||
expect(normalizeExperimentalSettings({}).enableDecisions).toBe(false);
|
||||
expect(
|
||||
normalizeExperimentalSettings({ enableStreamlinedLeftNavigation: true }).enableDecisions,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("defaults workspace branch repair settings to true for empty and legacy stored settings", () => {
|
||||
expect(normalizeExperimentalSettings(undefined).enableWorkspaceBranchReconcileForward).toBe(true);
|
||||
expect(normalizeExperimentalSettings({}).enableWorkspaceBranchReconcileForward).toBe(true);
|
||||
|
||||
@@ -56,6 +56,7 @@ export function normalizeExperimentalSettings(raw: unknown): InstanceExperimenta
|
||||
enableCloudSync: parsed.data.enableCloudSync ?? false,
|
||||
enableExternalObjects: parsed.data.enableExternalObjects ?? false,
|
||||
enableBuiltInAgents: parsed.data.enableBuiltInAgents ?? false,
|
||||
enableDecisions: parsed.data.enableDecisions ?? false,
|
||||
enableGoalsSidebarLink: parsed.data.enableGoalsSidebarLink ?? false,
|
||||
enableServerInfoDebugView: parsed.data.enableServerInfoDebugView ?? false,
|
||||
autoRestartDevServerWhenIdle: parsed.data.autoRestartDevServerWhenIdle ?? false,
|
||||
@@ -81,6 +82,7 @@ export function normalizeExperimentalSettings(raw: unknown): InstanceExperimenta
|
||||
enableCloudSync: false,
|
||||
enableExternalObjects: false,
|
||||
enableBuiltInAgents: false,
|
||||
enableDecisions: false,
|
||||
enableGoalsSidebarLink: false,
|
||||
enableServerInfoDebugView: false,
|
||||
autoRestartDevServerWhenIdle: false,
|
||||
|
||||
@@ -89,6 +89,10 @@ export function Sidebar() {
|
||||
const showPipelines = experimentalSettings?.enablePipelines === true;
|
||||
const goalsLinkPending = experimentalSettings === undefined;
|
||||
const showGoalsLink = experimentalSettings?.enableGoalsSidebarLink === true;
|
||||
// Decisions (attention home) is an experimental surface (PAP-13481): the nav
|
||||
// item is hidden entirely until the flag is enabled (same no-flash pattern as
|
||||
// showWorkspacesLink — it defaults hidden, so no placeholder is needed).
|
||||
const showDecisions = experimentalSettings?.enableDecisions === true;
|
||||
const showCases = experimentalSettings?.enableCases === true;
|
||||
// Streamlined left navigation (top-level Projects link + starred children) is
|
||||
// now the standard product sidebar (PAP-12472). The former experimental
|
||||
@@ -191,13 +195,6 @@ export function Sidebar() {
|
||||
);
|
||||
})()}
|
||||
<SidebarNavItem to="/dashboard" label="Dashboard" icon={LayoutDashboard} liveCount={liveRunCount} />
|
||||
<SidebarNavItem
|
||||
to="/decisions"
|
||||
label="Decisions"
|
||||
icon={ListChecks}
|
||||
badge={attentionCount}
|
||||
badgeLabel="decisions"
|
||||
/>
|
||||
<SidebarNavItem
|
||||
to="/inbox"
|
||||
label="Inbox"
|
||||
@@ -207,6 +204,15 @@ export function Sidebar() {
|
||||
badgeTone={inboxBadge.failedRuns > 0 ? "danger" : "default"}
|
||||
alert={inboxBadge.failedRuns > 0}
|
||||
/>
|
||||
{showDecisions ? (
|
||||
<SidebarNavItem
|
||||
to="/decisions"
|
||||
label="Decisions"
|
||||
icon={ListChecks}
|
||||
badge={attentionCount}
|
||||
badgeLabel="decisions"
|
||||
/>
|
||||
) : null}
|
||||
{conferenceRoomChatEnabled ? (
|
||||
<SidebarNavItem to="/board-chat" label="Conference Room" icon={MessagesSquare} />
|
||||
) : null}
|
||||
|
||||
@@ -46,6 +46,8 @@ const TASK_WATCHDOGS_TOGGLE_SELECTOR =
|
||||
'button[aria-label="Toggle task watchdogs experimental setting"]';
|
||||
const GOALS_SIDEBAR_LINK_TOGGLE_SELECTOR =
|
||||
'button[aria-label="Toggle goals sidebar link experimental setting"]';
|
||||
const DECISIONS_TOGGLE_SELECTOR =
|
||||
'button[aria-label="Toggle decisions experimental setting"]';
|
||||
const SERVER_INFO_TOGGLE_SELECTOR =
|
||||
'button[aria-label="Toggle server info debug view experimental setting"]';
|
||||
const BUILT_IN_AGENTS_TOGGLE_SELECTOR =
|
||||
@@ -63,6 +65,7 @@ function defaultExperimentalSettings(): InstanceExperimentalSettingsPayload {
|
||||
enableExperimentalFileViewer: false,
|
||||
enableExternalObjects: false,
|
||||
enableBuiltInAgents: false,
|
||||
enableDecisions: false,
|
||||
enableGoalsSidebarLink: false,
|
||||
enableTaskWatchdogs: false,
|
||||
enableCloudSync: false,
|
||||
@@ -225,6 +228,28 @@ describe("InstanceExperimentalSettings — Conference Room Chat card (PAP-11233)
|
||||
});
|
||||
});
|
||||
|
||||
it("renders and patches the Decisions experimental toggle", async () => {
|
||||
await renderPage();
|
||||
|
||||
expect(container.textContent).toContain("Decisions");
|
||||
expect(container.textContent).toContain(
|
||||
"Show the Decisions item in the main sidebar",
|
||||
);
|
||||
|
||||
const toggle = container.querySelector<HTMLButtonElement>(DECISIONS_TOGGLE_SELECTOR);
|
||||
expect(toggle?.getAttribute("aria-checked")).toBe("false");
|
||||
|
||||
await act(async () => {
|
||||
toggle?.click();
|
||||
});
|
||||
await flushReact();
|
||||
|
||||
expect(mockInstanceSettingsApi.updateExperimental).toHaveBeenCalledWith({
|
||||
enableDecisions: true,
|
||||
});
|
||||
expect(toggle?.getAttribute("aria-checked")).toBe("true");
|
||||
});
|
||||
|
||||
it("renders and patches the Goals Sidebar Link experimental toggle", async () => {
|
||||
await renderPage();
|
||||
|
||||
|
||||
@@ -249,6 +249,7 @@ export function InstanceExperimentalSettings() {
|
||||
const enableCloudSync = experimentalQuery.data?.enableCloudSync === true;
|
||||
const enableExternalObjects = experimentalQuery.data?.enableExternalObjects === true;
|
||||
const enableBuiltInAgents = experimentalQuery.data?.enableBuiltInAgents === true;
|
||||
const enableDecisions = experimentalQuery.data?.enableDecisions === true;
|
||||
const enableGoalsSidebarLink = experimentalQuery.data?.enableGoalsSidebarLink === true;
|
||||
const enableCases = experimentalQuery.data?.enableCases === true;
|
||||
const enableServerInfoDebugView = experimentalQuery.data?.enableServerInfoDebugView === true;
|
||||
@@ -447,6 +448,24 @@ export function InstanceExperimentalSettings() {
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="block p-5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<h2 className="text-sm font-semibold">Decisions</h2>
|
||||
<p className="max-w-2xl text-sm text-muted-foreground">
|
||||
Show the Decisions item in the main sidebar — the attention home that surfaces the tasks awaiting your
|
||||
input — while the surface is still being evaluated.
|
||||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
checked={enableDecisions}
|
||||
onCheckedChange={() => toggleMutation.mutate({ enableDecisions: !enableDecisions })}
|
||||
disabled={toggleMutation.isPending}
|
||||
aria-label="Toggle decisions experimental setting"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="block p-5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1.5">
|
||||
|
||||
Reference in new issue
Block a user