mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
Gate plan decomposition panel behind experimental flag
This commit is contained in:
1 parent
41b08c4255
commit
32dbcc1da4
8 files changed
+126
-5
No files matched your search
@@ -205,6 +205,8 @@ export function InstanceExperimentalSettings() {
|
||||
|
||||
const enableEnvironments = experimentalQuery.data?.enableEnvironments === true;
|
||||
const enableIsolatedWorkspaces = experimentalQuery.data?.enableIsolatedWorkspaces === true;
|
||||
const enableIssuePlanDecompositions =
|
||||
experimentalQuery.data?.enableIssuePlanDecompositions === true;
|
||||
const enableCloudSync = experimentalQuery.data?.enableCloudSync === true;
|
||||
const autoRestartDevServerWhenIdle = experimentalQuery.data?.autoRestartDevServerWhenIdle === true;
|
||||
const enableIssueGraphLivenessAutoRecovery =
|
||||
@@ -299,6 +301,28 @@ export function InstanceExperimentalSettings() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rounded-xl border border-border bg-card p-5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<h2 className="text-sm font-semibold">Issue Plan Decomposition Panel</h2>
|
||||
<p className="max-w-2xl text-sm text-muted-foreground">
|
||||
Show accepted-plan decomposition history on issue detail pages. Intended for debugging and validating
|
||||
subtask creation behavior while the presentation is still being refined.
|
||||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
checked={enableIssuePlanDecompositions}
|
||||
onCheckedChange={() =>
|
||||
toggleMutation.mutate({
|
||||
enableIssuePlanDecompositions: !enableIssuePlanDecompositions,
|
||||
})
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
aria-label="Toggle issue plan decomposition panel experimental setting"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rounded-xl border border-border bg-card p-5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1.5">
|
||||
|
||||
@@ -10,6 +10,7 @@ import { canBoardResolveRecoveryAction, IssueDetail } from "./IssueDetail";
|
||||
const mockIssuesApi = vi.hoisted(() => ({
|
||||
get: vi.fn(),
|
||||
list: vi.fn(),
|
||||
listAcceptedPlanDecompositions: vi.fn(),
|
||||
listComments: vi.fn(),
|
||||
listAttachments: vi.fn(),
|
||||
listFeedbackVotes: vi.fn(),
|
||||
@@ -59,6 +60,7 @@ const mockProjectsApi = vi.hoisted(() => ({
|
||||
|
||||
const mockInstanceSettingsApi = vi.hoisted(() => ({
|
||||
getGeneral: vi.fn(),
|
||||
getExperimental: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockNavigate = vi.hoisted(() => vi.fn());
|
||||
@@ -823,6 +825,10 @@ describe("IssueDetail", () => {
|
||||
keyboardShortcuts: false,
|
||||
feedbackDataSharingPreference: "prompt",
|
||||
});
|
||||
mockInstanceSettingsApi.getExperimental.mockResolvedValue({
|
||||
enableIssuePlanDecompositions: false,
|
||||
});
|
||||
mockIssuesApi.listAcceptedPlanDecompositions.mockResolvedValue([]);
|
||||
mockIssuesListRender.mockClear();
|
||||
mockIssueChatThreadRender.mockClear();
|
||||
});
|
||||
@@ -858,6 +864,78 @@ describe("IssueDetail", () => {
|
||||
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("hides the plan decomposition panel by default", async () => {
|
||||
mockIssuesApi.get.mockResolvedValue(createIssue());
|
||||
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<IssueDetail />
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
});
|
||||
|
||||
await flushReact();
|
||||
await flushReact();
|
||||
|
||||
expect(container.textContent).not.toContain("Plan decomposition");
|
||||
expect(mockIssuesApi.listAcceptedPlanDecompositions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows the plan decomposition panel when the experimental flag is enabled", async () => {
|
||||
mockIssuesApi.get.mockResolvedValue(createIssue());
|
||||
mockInstanceSettingsApi.getExperimental.mockResolvedValue({
|
||||
enableIssuePlanDecompositions: true,
|
||||
});
|
||||
mockIssuesApi.listAcceptedPlanDecompositions.mockResolvedValue([
|
||||
{
|
||||
id: "decomp-1",
|
||||
companyId: "company-1",
|
||||
sourceIssueId: "issue-1",
|
||||
acceptedPlanRevisionId: "plan-rev-1",
|
||||
acceptedPlanRevisionNumber: 2,
|
||||
acceptedInteractionId: null,
|
||||
status: "completed",
|
||||
requestFingerprint: "fingerprint-1",
|
||||
requestedChildCount: 1,
|
||||
childIssueIds: ["issue-2"],
|
||||
childIssues: [
|
||||
{
|
||||
id: "issue-2",
|
||||
identifier: "PAP-2",
|
||||
title: "First child issue",
|
||||
status: "todo",
|
||||
priority: "medium",
|
||||
assigneeAgentId: null,
|
||||
assigneeUserId: null,
|
||||
},
|
||||
],
|
||||
ownerAgentId: null,
|
||||
ownerUserId: null,
|
||||
ownerRunId: null,
|
||||
completedAt: "2026-05-28T06:00:00.000Z",
|
||||
createdAt: "2026-05-28T05:50:00.000Z",
|
||||
updatedAt: "2026-05-28T06:00:00.000Z",
|
||||
},
|
||||
]);
|
||||
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<IssueDetail />
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
});
|
||||
|
||||
await flushReact();
|
||||
await flushReact();
|
||||
|
||||
expect(container.textContent).toContain("Plan decomposition");
|
||||
expect(container.textContent).toContain("Plan revision 2");
|
||||
expect(container.textContent).toContain("First child issue");
|
||||
expect(mockIssuesApi.listAcceptedPlanDecompositions).toHaveBeenCalledWith("issue-1");
|
||||
});
|
||||
|
||||
it("renders sibling previous and next navigation at the chat footer", async () => {
|
||||
const issue = createIssue({
|
||||
id: "issue-2",
|
||||
|
||||
@@ -1441,8 +1441,16 @@ export function IssueDetail() {
|
||||
enabled: !!issueId,
|
||||
retry: false,
|
||||
});
|
||||
const { data: instanceExperimentalSettings } = useQuery({
|
||||
queryKey: queryKeys.instance.experimentalSettings,
|
||||
queryFn: () => instanceSettingsApi.getExperimental(),
|
||||
enabled: !!issueId,
|
||||
retry: false,
|
||||
});
|
||||
const keyboardShortcutsEnabled = instanceGeneralSettings?.keyboardShortcuts === true;
|
||||
const feedbackDataSharingPreference = instanceGeneralSettings?.feedbackDataSharingPreference ?? "prompt";
|
||||
const showPlanDecompositionsSection =
|
||||
instanceExperimentalSettings?.enableIssuePlanDecompositions === true;
|
||||
const { orderedProjects } = useProjectOrder({
|
||||
projects: projects ?? [],
|
||||
companyId: selectedCompanyId,
|
||||
@@ -3714,11 +3722,13 @@ export function IssueDetail() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<IssuePlanDecompositionsSection
|
||||
issueId={issue.id}
|
||||
issueIdentifier={issue.identifier}
|
||||
agentMap={agentMap}
|
||||
/>
|
||||
{showPlanDecompositionsSection ? (
|
||||
<IssuePlanDecompositionsSection
|
||||
issueId={issue.id}
|
||||
issueIdentifier={issue.identifier}
|
||||
agentMap={agentMap}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<IssueDocumentsSection
|
||||
issue={issue}
|
||||
|
||||
Reference in new issue
Block a user