mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-24 18:19:05 +02:00
fix(ui): preserve explicit document selections on refresh
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
7ba05522ec
commit
ddc9422e20
3 files changed
+49
-1
No files matched your search
@@ -236,6 +236,28 @@ describe("TaskSidePanel", () => {
|
||||
expect(routeFixture.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("preserves an explicitly linked document when artifacts arrive", async () => {
|
||||
fixture.documents = [issueDocument("agents", "AGENTS.md")];
|
||||
const documentDeepLink = { requestId: 1, documentKey: "agents" };
|
||||
await render(panel({ documentDeepLink, artifactsOpenRequestId: 1 }));
|
||||
expect(container.querySelector('[role="tab"][aria-selected="true"]')?.textContent).toContain("AGENTS.md");
|
||||
expect(container.querySelectorAll('[data-side-panel-tab-target="artifacts"]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("handles each document link once without overriding later manual selection", async () => {
|
||||
fixture.documents = [issueDocument("agents", "AGENTS.md")];
|
||||
const documentDeepLink = { requestId: 1, documentKey: "agents" };
|
||||
await render(panel({ documentDeepLink }));
|
||||
await act(async () => container.querySelector<HTMLButtonElement>("#side-panel-tab-properties")?.click());
|
||||
fixture.documents = [...fixture.documents, issueDocument("skill", "SKILL.md")];
|
||||
await render(panel({ documentDeepLink: { ...documentDeepLink }, artifactsOpenRequestId: 1 }));
|
||||
expect(container.querySelector('[role="tab"][aria-selected="true"]')?.textContent).toContain("Properties");
|
||||
expect(container.querySelectorAll('[data-side-panel-tab-target="artifacts"]')).toHaveLength(1);
|
||||
|
||||
await render(panel({ documentDeepLink: { ...documentDeepLink, requestId: 2 } }));
|
||||
expect(container.querySelector('[role="tab"][aria-selected="true"]')?.textContent).toContain("AGENTS.md");
|
||||
});
|
||||
|
||||
it("keeps a document selected when an artifact arrives", async () => {
|
||||
fixture.documents = [issueDocument("report", "Report")];
|
||||
const documentDeepLink = { documentKey: "report", requestId: 1 };
|
||||
|
||||
@@ -270,6 +270,7 @@ export function TaskSidePanel({
|
||||
const userInteractedRef = useRef(restoredRef.current?.userInteracted ?? false);
|
||||
const autoPlanHandledRef = useRef(restoredRef.current?.autoPlanHandled ?? false);
|
||||
const handledArtifactsRequestRef = useRef<number | undefined>(undefined);
|
||||
const handledDocumentRequestRef = useRef<number | undefined>(undefined);
|
||||
const initialState = useMemo(() => {
|
||||
const restored = restoredRef.current?.state;
|
||||
let tabs = restored?.tabs ?? (issue.conversationAgentId ? [taskPanelArtifactsTab()] : [taskPanelPropertiesTab()]);
|
||||
@@ -356,13 +357,15 @@ export function TaskSidePanel({
|
||||
}, [controller.openTab, planDocument]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!documentDeepLink) return;
|
||||
if (!documentDeepLink || handledDocumentRequestRef.current === documentDeepLink.requestId) return;
|
||||
if (
|
||||
documentDeepLink.documentKey === "plan" &&
|
||||
planDocument === null
|
||||
) return;
|
||||
const document = documents.find((candidate) => candidate.key === documentDeepLink.documentKey);
|
||||
const label = document ? documentDisplayTitle(document) : documentDeepLink.documentKey === "plan" ? "Plan" : documentDeepLink.documentKey;
|
||||
// A refresh must not replay a link after the user selects another tab.
|
||||
handledDocumentRequestRef.current = documentDeepLink.requestId;
|
||||
controller.openTab(taskPanelDocumentTab(documentDeepLink.documentKey, label));
|
||||
}, [controller.openTab, documentDeepLink, documents, planDocument]);
|
||||
|
||||
|
||||
@@ -2162,6 +2162,29 @@ describe("IssueDetail", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("opens Artifacts for existing output without discarding a document deep link", async () => {
|
||||
mockLocation.hash = "#document-agents";
|
||||
mockIssuesApi.get.mockResolvedValue(createIssue({
|
||||
documentSummaries: [{
|
||||
id: "agents-doc", companyId: "company-1", issueId: "issue-1",
|
||||
key: "agents", title: "AGENTS.md", format: "markdown",
|
||||
latestRevisionId: "revision-1", latestRevisionNumber: 1,
|
||||
createdByAgentId: "agent-1", createdByUserId: null,
|
||||
updatedByAgentId: "agent-1", updatedByUserId: null,
|
||||
lockedAt: null, lockedByAgentId: null, lockedByUserId: null,
|
||||
createdAt: new Date(), updatedAt: new Date(),
|
||||
}],
|
||||
}));
|
||||
await act(async () => {
|
||||
root.render(<QueryClientProvider client={queryClient}><IssueDetail /></QueryClientProvider>);
|
||||
});
|
||||
await waitForAssertion(() => {
|
||||
const props = mockOpenPanel.mock.calls.at(-1)?.[0]?.props.children?.props;
|
||||
expect(props?.artifactsOpenRequestId).toBe(1);
|
||||
expect(props?.documentDeepLink?.documentKey).toBe("agents");
|
||||
});
|
||||
});
|
||||
|
||||
it.each([false, true])("registers new artifacts without opening a closed panel (mobile: %s)", async (isMobile) => {
|
||||
mockSidebarState.isMobile = isMobile;
|
||||
mockLocation.state = createIssueDetailLocationState("Inbox", "/inbox/mine", "inbox");
|
||||
|
||||
Reference in new issue
Block a user