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:
DottaandClaude Opus 4.8 committed 2026-07-10 20:09:29 +00:00
1 parent 042bb231d9
commit 8db9fa6a14
8 files changed
+86 -7

No files matched your search

+1
View File
@@ -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);
+2
View File
@@ -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,
+13 -7
View File
@@ -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">