mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 19:35:13 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - People use New task to send work to an agent. > - The board currently downloads the task dialog code after the first click. > - This shows a loading window before people can enter their request. > - The form already loads agent and project choices after it opens. > - This pull request loads the task dialog code with the board. > - People can start writing immediately and keep their text as choices arrive. ## Linked Issues or Issue Description Related change: #16849. Commit `fa50cab5685b9cdbb26ad9c610cc5b6c373d3ff4` added the deferred dialog and its loading window on October 19, 2026. **What happened?** The first New task click shows “Loading new task…” while the browser downloads the dialog code. **Expected behavior** The form opens on click. Agent and project choices load in place. Text entered during those requests stays in the form. **Steps to reproduce** 1. Open the board in a fresh browser session. 2. Select New task before its code is cached. 3. Observe the loading window before the form appears. **Paperclip version or commit** Source baseline: `8b4ab65b1c3d863d282752e83307943720a5c85c`. **Deployment mode** The browser code applies to local and hosted boards. Verification used a source checkout. ## What Changed - Import NewIssueDialog with the board. Keep the existing mount and draft lifecycle. - Add a first-open test that fails if the form waits for an asynchronous render. - Cover text entry during delayed agent and project requests and defaults that arrive later. - Document the behavior and its verification recipe. ## Verification - Regression proof: the first-open test failed before the import change and passed after it. - All 125 tests in BoardDialogs, DeferredOverlay, NewIssueDialog and Layout passed. - The production UI build and UI TypeScript check passed. The typecheck passed with `NODE_OPTIONS=--max-old-space-size=2400` after the competing test process was stopped. - Token gates, map validation and Git diff checks passed. - The first UI typecheck could not resolve the unbuilt local plugin SDK. The SDK was then built. - Full typecheck and build attempts hit the sandbox's 4 GB memory limit when run with the full test suite. Two later UI typecheck attempts also hit that limit during other validation. The cgroup recorded four OOM kills in total. The full repository test attempt and a broader UI test attempt were stopped. No complete suite result is claimed. Serial repository typecheck reached the missing Rust `cargo` executable. Full CI passed on head `9dde4bc`; local limits remain recorded here. - First CI run at `e5a99b8`: typecheck, build, all unit-test shards and seven browser shards passed. Browser shard 2 failed at `chat-adapters-ui-providers.spec.ts:185`, waiting for Slack setup Success after reload. The saved page showed the working connection and Continue button. The cause is not confirmed; this failure is retained for follow-up. [Failed job](https://github.com/paperclipai/paperclip/actions/runs/39522214527/job/118968542923). - Removed the added import comment after Greptile review. Head `9dde4bc` changes only that comment. The review thread is resolved. Fresh CI passed on head `9dde4bc`. Greptile reviewed that exact head at 5/5 with no open findings. All eight browser shards passed, including the earlier Slack failure. The earlier failure remains unexplained; the only intervening code change removed a source comment. A passing retry establishes recovery, not its cause. - Component tests use mocked directory requests. No deployed latency or browser persistence claim is made. Latest CI: [run 39523376437](https://github.com/paperclipai/paperclip/actions/runs/39523376437). Reviewed head: `9dde4bc0caa3556e315a2fc197268b7c9879efa4`. ## Risks - Task dialog code is part of board startup again. This adds its dependencies to the initial code graph. No startup latency benchmark is claimed. - Other creation dialogs retain their existing lazy imports and loading behavior. ## Model Used OpenAI Codex, GPT-6, with reasoning, repository tools and code execution. The runtime does not expose a more precise model ID or context-window size. ## 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>
55 lines
2.5 KiB
TypeScript
55 lines
2.5 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { act } from "react";
|
|
import { flushSync } from "react-dom";
|
|
import { createRoot } from "react-dom/client";
|
|
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
import { BoardDialogs } from "./BoardDialogs";
|
|
|
|
const state = vi.hoisted(() => ({ newIssueOpen: false, newProjectOpen: false, newGoalOpen: false, newAgentOpen: false }));
|
|
vi.mock("../context/DialogContext", () => ({ useDialogState: () => state, useDialogActions: () => ({ closeNewIssue: vi.fn(), closeNewProject: vi.fn(), closeNewGoal: vi.fn(), closeNewAgent: vi.fn() }) }));
|
|
vi.mock("./NewIssueDialog", () => ({ NewIssueDialog: () => <input aria-label="Task draft" /> }));
|
|
vi.mock("./NewProjectDialog", () => ({ NewProjectDialog: () => <div>Project dialog</div> }));
|
|
vi.mock("./NewGoalDialog", () => ({ NewGoalDialog: () => <div>Goal dialog</div> }));
|
|
vi.mock("./NewAgentDialog", () => ({ NewAgentDialog: () => <div>Agent dialog</div> }));
|
|
let container: HTMLDivElement;
|
|
let root: ReturnType<typeof createRoot>;
|
|
beforeEach(() => {
|
|
Object.assign(state, { newIssueOpen: false, newProjectOpen: false, newGoalOpen: false, newAgentOpen: false });
|
|
container = document.createElement("div");
|
|
document.body.appendChild(container);
|
|
root = createRoot(container);
|
|
});
|
|
afterEach(async () => {
|
|
await act(async () => root.unmount());
|
|
container.remove();
|
|
});
|
|
async function render() { await act(async () => { root.render(<BoardDialogs />); }); }
|
|
it("renders the task form on the first open without an asynchronous loading frame", () => {
|
|
act(() => flushSync(() => root.render(<BoardDialogs />)));
|
|
state.newIssueOpen = true;
|
|
act(() => {
|
|
flushSync(() => root.render(<BoardDialogs />));
|
|
expect(container.querySelector('[aria-label="Task draft"]')).not.toBeNull();
|
|
expect(document.body.textContent).not.toContain("Loading new task");
|
|
});
|
|
});
|
|
it("leaves unopened dialog trees unmounted", async () => {
|
|
await render();
|
|
expect(container.innerHTML).toBe("");
|
|
});
|
|
it("loads a requested dialog and retains its draft across close and reopen", async () => {
|
|
await render();
|
|
state.newIssueOpen = true;
|
|
await render();
|
|
const draft = container.querySelector("input")!;
|
|
expect(draft).not.toBeNull();
|
|
draft.value = "Keep my draft";
|
|
expect(container.textContent).not.toContain("Project dialog");
|
|
state.newIssueOpen = false;
|
|
await render();
|
|
state.newIssueOpen = true;
|
|
await render();
|
|
expect(container.querySelector("input")).toBe(draft);
|
|
expect(container.querySelector("input")?.value).toBe("Keep my draft");
|
|
});
|