mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-17 01:25:26 +02:00
fix(ui): leave hidden task source defaults to the server (#16220)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The task dialog sends project and workspace choices to the control plane. > - Hidden workspace controls must leave automatic source choices to the server. > - The dialog still sent its computed source pointer while those controls were hidden. > - This bypassed the implicit default branch added in #16095 and saved an untyped source choice. > - This pull request omits that automatic pointer and preserves explicit launch context. > - The benefit is that fresh tasks can use the server's safe isolated default source path. ## Linked Issues or Issue Description Refs #16095. A search found no open duplicate for the hidden-selector payload bug. **What happened?** A fresh task created through the board with a project and hidden isolation controls failed setup with “Task workspace access is no longer available.” The dialog sent `projectWorkspaceId` even though the user had not selected a source. The saved task had no workspace-selection provenance. No provider turn started. **Expected behavior** Omit automatic or restored draft source pointers when the selector is hidden. Let the server select and record the project default. Preserve explicit source and execution-workspace context supplied by a launch action. **Steps to reproduce** Hide `workspaces.isolation` through the existing operator setting. Use a project with a default Git source and isolated workspace policy. Create a fresh task from the board. Inspect the creation payload: the hidden automatic source must be omitted. The server must then apply its existing default source contract. A restricted shared source must remain protected. **Paperclip version or commit** Observed on Core `c7f4b74111d16f0b57112fa6d0b6a59c2d63bfe8`, which includes #16095. **Deployment mode** Authenticated staging Cloud instance with a configured remote runner provider and operator-hidden workspace controls. ## What Changed - Omit the automatic `projectWorkspaceId` while isolation controls are hidden. - Keep explicit same-project launch source context, including reused workspaces and subtasks. - Add a fresh-default payload regression and strengthen stale-draft and explicit-context controls. - Keep server authorization, trust checks, source fallback rules, visible selectors, and stored old tasks unchanged. ## Verification - Before the repair, the hidden fresh/default and restored-source regressions failed: 2 failed, 3 passed, 79 other cases filtered. - Final rebased source: `pnpm exec vitest run --project @paperclipai/ui ui/src/components/NewIssueDialog.test.tsx ui/src/lib/project-workspace-defaults.test.ts` passed all 87 cases. - `pnpm --filter @paperclipai/ui typecheck` passed. - `pnpm --filter @paperclipai/ui build` passed. - `pnpm check:token-gates` passed. - Initial UI typecheck failed because this new checkout had not built the plugin SDK. Building that dependency and repeating the unchanged check passed. The failed attempt remains separate from the source regression. - Full repository typecheck, full repository tests, and full repository build have not been run locally for this two-file UI patch. Complete latest-head CI passed with 55 successful checks and two intentional skips. This includes the full repository typecheck, build, general/serialized tests and all eight browser shards. Fresh Apex is 5/5 on `f992823480311691469c6797d5cdff26d76899a2`, with no unresolved findings or recommendations. - No live recovery pass is claimed. The deployed baseline failed before provider startup. A fresh same-project task must pass after this patch is deployed. Existing tasks with untyped source pointers need a separate authorized recovery path. - The old Storybook maps were reviewed at the baseline, then master removed them. The rebase preserves that removal. The final Storybook structural check covers 262 files and 2,485 story IDs. No new Storybook browser, play, render, or visual proof is claimed. ## Risks - Hidden restored draft source choices are intentionally ignored. Explicit same-project launch defaults remain in the request and have controls for normal tasks and subtasks. - Visible workspace choices retain their existing behavior. - This patch fixes one creation path. It does not recover old tasks, relax private source restrictions, or establish a performance fix for slow historical runs. - No data model, endpoint, permission, runner, or Observability changes. ## Model Used OpenAI GPT-6 through Codex, with code and browser tools. The exact served model identifier and context-window size are not exposed in this session. ## 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 either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run 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
22d05644f4
commit
edcfcaf3f6
2 files changed
+30
-6
No files matched your search
@@ -1026,13 +1026,13 @@ describe("NewIssueDialog", () => {
|
||||
it("hides isolation choices and omits stale workspace draft overrides", async () => {
|
||||
mockInstanceSettingsApi.getExperimental.mockResolvedValue({ enableIsolatedWorkspaces: true });
|
||||
mockProjectsApi.list.mockResolvedValue([{
|
||||
id: "project-1", name: "Alpha", workspaces: [],
|
||||
id: "project-1", name: "Alpha", workspaces: [{ id: "source-default", isPrimary: true }],
|
||||
executionWorkspacePolicy: { enabled: true, defaultMode: "isolated_workspace" },
|
||||
}]);
|
||||
localStorage.setItem("paperclip:issue-draft", JSON.stringify({
|
||||
title: "Draft task", description: "", status: "todo", priority: "medium", assigneeValue: "",
|
||||
reviewerValue: "", approverValue: "", projectId: "project-1",
|
||||
selectedExecutionWorkspaceId: "stale-workspace", executionWorkspaceMode: "reuse_existing",
|
||||
projectWorkspaceId: "stale-source", selectedExecutionWorkspaceId: "stale-workspace", executionWorkspaceMode: "reuse_existing",
|
||||
assigneeModelOverride: "", assigneeThinkingEffort: "", assigneeChrome: false, workMode: "standard",
|
||||
}));
|
||||
const { root } = renderDialog(container, ["workspaces.isolation"]);
|
||||
@@ -1047,13 +1047,34 @@ describe("NewIssueDialog", () => {
|
||||
expect(payload).not.toHaveProperty("executionWorkspacePreference");
|
||||
expect(payload).not.toHaveProperty("executionWorkspaceSettings");
|
||||
expect(payload).not.toHaveProperty("executionWorkspaceId");
|
||||
expect(payload).not.toHaveProperty("projectWorkspaceId");
|
||||
act(() => root.unmount());
|
||||
});
|
||||
|
||||
it("leaves a fresh hidden project source choice to the server default", async () => {
|
||||
mockInstanceSettingsApi.getExperimental.mockResolvedValue({ enableIsolatedWorkspaces: true });
|
||||
mockProjectsApi.list.mockResolvedValue([{
|
||||
id: "project-1", name: "Alpha", workspaces: [{ id: "source-default", isPrimary: true }],
|
||||
executionWorkspacePolicy: { enabled: true, defaultMode: "isolated_workspace" },
|
||||
}]);
|
||||
dialogState.newIssueDefaults = { projectId: "project-1" };
|
||||
const { root } = renderDialog(container, ["workspaces.isolation"]);
|
||||
await flush();
|
||||
await typeTextareaValue(container.querySelector('textarea[aria-label="Describe a task…"]')!, "Managed task");
|
||||
const create = Array.from(container.querySelectorAll("button")).find((button) => button.getAttribute("aria-label") === "Create task");
|
||||
act(() => create!.click());
|
||||
await waitForAssertion(() => expect(mockIssuesApi.create).toHaveBeenCalled());
|
||||
const payload = mockIssuesApi.create.mock.calls[0][1];
|
||||
expect(payload.projectId).toBe("project-1");
|
||||
expect(payload).not.toHaveProperty("projectWorkspaceId");
|
||||
expect(payload).not.toHaveProperty("executionWorkspaceId");
|
||||
act(() => root.unmount());
|
||||
});
|
||||
|
||||
it.each([false, true])("keeps explicit workspace launch context when isolation controls are hidden (subtask: %s)", async (subtask) => {
|
||||
mockInstanceSettingsApi.getExperimental.mockResolvedValue({ enableIsolatedWorkspaces: true });
|
||||
dialogState.newIssueDefaults = {
|
||||
projectId: "project-1", executionWorkspaceId: "workspace-context",
|
||||
projectId: "project-1", projectWorkspaceId: "source-context", executionWorkspaceId: "workspace-context",
|
||||
...(subtask ? { parentId: "parent-task", parentIdentifier: "TEST-1" } : {}),
|
||||
};
|
||||
const { root } = renderDialog(container, ["workspaces.isolation"]);
|
||||
@@ -1065,6 +1086,7 @@ describe("NewIssueDialog", () => {
|
||||
await waitForAssertion(() => expect(mockIssuesApi.create).toHaveBeenCalled());
|
||||
expect(mockIssuesApi.create.mock.calls[0][1]).toMatchObject({
|
||||
executionWorkspaceId: "workspace-context", executionWorkspacePreference: "reuse_existing",
|
||||
projectWorkspaceId: "source-context",
|
||||
});
|
||||
act(() => root.unmount());
|
||||
});
|
||||
|
||||
@@ -816,9 +816,11 @@ export function NewIssueDialog() {
|
||||
const executionWorkspaceSettings = executionWorkspacePolicy?.enabled
|
||||
? { mode: requestedExecutionWorkspaceMode }
|
||||
: null;
|
||||
const requestedProjectWorkspaceId = canChooseWorktrees && executionWorkspaceMode === "reuse_existing" && selectedReusableExecutionWorkspace
|
||||
? selectedReusableExecutionWorkspace.projectWorkspaceId
|
||||
: projectWorkspaceId;
|
||||
const requestedProjectWorkspaceId = !workspaceIsolationControlsVisible
|
||||
? (newIssueDefaults.projectId === projectId ? newIssueDefaults.projectWorkspaceId : undefined)
|
||||
: canChooseWorktrees && executionWorkspaceMode === "reuse_existing" && selectedReusableExecutionWorkspace
|
||||
? selectedReusableExecutionWorkspace.projectWorkspaceId
|
||||
: projectWorkspaceId;
|
||||
// A task launched from a workspace (or its parent task) keeps that explicit
|
||||
// context. Draft-only choices are ignored while the selector is hidden.
|
||||
const contextualWorkspaceId =
|
||||
|
||||
Reference in new issue
Block a user