fix(ui): preserve explicit document selections on refresh

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-27 16:49:05 -05:00
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]);
+23
View File
@@ -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");