mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-13 07:12:00 +01:00
fix(tasks): retain linked comments across document navigation
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
e6166108a5
commit
5a1bb2c1c4
4 files changed
+50
-4
No files matched your search
@@ -23,9 +23,8 @@ visible cards after reveal, and thread height grew from 690 to 1,432 pixels.
|
||||
The first candidate keeps all 7 saved cards present and height constant. A
|
||||
second regression showed delayed PR counts changing card heights from 62 to
|
||||
82 pixels; reserving that statistics line makes both states 82 pixels.
|
||||
Focused UI tests: 507 pass. Focused server tests: 16 pass. Token and map checks pass.
|
||||
Final browser checks, full workspace typecheck/tests/build and hosted review are
|
||||
in progress. One early candidate browser run exceeded the initial 5-second dev
|
||||
Focused UI tests: 551 pass before the added retention regression. Focused server tests: 16 pass. Token and map checks pass.
|
||||
Eight initial browser checks, full workspace typecheck and build pass. Full local tests and hosted CI remain in progress. Review found that document navigation dropped a separately fetched older comment. The fix retains target comments in the observed compact window without advancing its cursor. Its added unit and browser checks pass, including row position. Hosted acceptance is recorded in PR #18092. One early candidate browser run exceeded the initial 5-second dev
|
||||
readiness deadline; later runs use 10 seconds. No reliable latency gain is claimed.
|
||||
The initial 503 linked-comment fixture exercised the app's global reconnect and
|
||||
30-second retry policy. The target-specific failure check uses a terminal 404.
|
||||
@@ -33,7 +32,7 @@ The initial 503 linked-comment fixture exercised the app's global reconnect and
|
||||
Local reproduction: http://127.0.0.1:3223/TAS/issues/TAS-1#document-investigation.
|
||||
Synthetic task data only; no live provider execution. Restart with
|
||||
`PAPERCLIP_HOME=/tmp/paperclip-stable-task-loading PAPERCLIP_INSTANCE_ID=task-loading PAPERCLIP_UI_DEV_MIDDLEWARE=true node cli/node_modules/tsx/dist/cli.mjs cli/src/index.ts run`.
|
||||
Evidence is retained under `/tmp/task-loading-proof/` until final attachment.
|
||||
Evidence is retained under `/tmp/task-loading-proof/`; the generated report is also attached to the disposable local instance. The canonical PR is https://github.com/paperclipai/paperclip/pull/18092.
|
||||
|
||||
State: branch `codex/stable-task-anchor-loading`; baseline `063cfd4381`.
|
||||
Dependencies installed locally; the existing lockfile was stale and will not be committed.
|
||||
@@ -132,6 +132,21 @@ test("opening documents and older comments keeps the mounted composer and compac
|
||||
await expect(editor).toHaveAttribute("data-mount-proof", "retained");
|
||||
});
|
||||
|
||||
test("opening a document from an older linked comment retains that row and its position", async ({ page, request }) => {
|
||||
// Give the real saved target a document link without inventing a browser row.
|
||||
const target = await (await request.get(`/api/issues/${fixture.id}/comments/${fixture.oldestCommentId}`)).json();
|
||||
await page.route(`**/api/issues/${fixture.id}/comments/${fixture.oldestCommentId}`, route => route.fulfill({ json: { ...target, body: `${target.body}\n\n[Inspect saved evidence](#document-investigation)` } }));
|
||||
await page.goto(`/${fixture.prefix}/issues/${fixture.identifier}#comment-${fixture.oldestCommentId}`);
|
||||
await ready(page);
|
||||
const row = page.locator(`[id="comment-${fixture.oldestCommentId}"]`);
|
||||
const before = (await row.boundingBox())!.y;
|
||||
await row.getByRole("link", { name: "Inspect saved evidence", exact: true }).click();
|
||||
await expect(page.locator('[id="side-panel-content-document:investigation"]')).toBeVisible();
|
||||
await expect(row).toBeVisible();
|
||||
await expect.poll(async () => Math.abs((await row.boundingBox())!.y - before)).toBeLessThanOrEqual(2);
|
||||
await expect(page.getByRole("button", { name: "More messages", exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test("a failed linked comment keeps recent messages readable and retries without expanding history", async ({ page }) => {
|
||||
const targetPath = `**/api/issues/${fixture.id}/comments/${fixture.oldestCommentId}`;
|
||||
await page.route(targetPath, route => route.fulfill({ status: 404, contentType: "application/json", body: '{"error":"Comment not found"}' }));
|
||||
|
||||
@@ -1857,6 +1857,25 @@ describe("IssueDetail", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("retains a linked older comment when opening a document without moving the preview cursor", async () => {
|
||||
queryClient.setQueryData(["issues", "history-expanded", "PAP-1"], false);
|
||||
mockLocation.hash = "#comment-old-comment";
|
||||
mockIssuesApi.get.mockResolvedValue(createIssue());
|
||||
const recent = createIssueComment({ id: "recent-comment", body: "Latest reply" });
|
||||
const older = createIssueComment({ id: "old-comment", body: "Read my saved evidence" });
|
||||
mockIssuesApi.getComment.mockResolvedValue(older);
|
||||
mockIssuesApi.recentComments.mockResolvedValue({ comments: [recent], latestCommentId: recent.id, hasMore: true });
|
||||
await act(async () => renderIssueView());
|
||||
const props = () => mockIssueChatThreadRender.mock.calls.at(-1)![0];
|
||||
await waitForAssertion(() => expect(props().comments).toEqual(expect.arrayContaining([expect.objectContaining({ id: older.id })])));
|
||||
mockLocation.hash = "#document-qa-evidence";
|
||||
await act(async () => renderIssueView());
|
||||
await waitForAssertion(() => expect(props().comments).toEqual(expect.arrayContaining([expect.objectContaining({ id: older.id }), expect.objectContaining({ id: recent.id })])));
|
||||
expect(queryClient.getQueryData<IssueRecentComments>([...queryKeys.issues.comments("PAP-1"), "recent"])?.latestCommentId).toBe(recent.id);
|
||||
expect(mockIssuesApi.listComments).not.toHaveBeenCalled();
|
||||
expect(mockActivityApi.forIssue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([false, true])("loads older history after sending from a cold view (reassign=%s)", async (reassign) => {
|
||||
queryClient.setDefaultOptions({ queries: { retry: false, staleTime: 30_000 } });
|
||||
queryClient.setQueryData(["issues", "history-expanded", "PAP-1"], false);
|
||||
|
||||
@@ -3452,6 +3452,19 @@ export function TaskDetailSurface({ conversation, tasksTab }: { tasksTab?: TaskS
|
||||
queryFn: ({ signal }) => issuesApi.getComment(issue!.id, linkedCommentId!, { signal }),
|
||||
enabled: !!issue && !!linkedCommentId && !historyExpanded && !windowComments.some(comment => comment.id === linkedCommentId),
|
||||
});
|
||||
useEffect(() => {
|
||||
const target = linkedComment.data;
|
||||
if (!target || !recentComments.data) return;
|
||||
// A document click changes the hash, not the reader's conversation. Keep
|
||||
// every fetched target in the observed window so that click cannot remove
|
||||
// its source row. The preview cursor still belongs to the latest reply.
|
||||
queryClient.setQueryData<IssueRecentComments>(
|
||||
[...queryKeys.issues.comments(issueId!), "recent"],
|
||||
current => !current || current.comments.some(comment => comment.id === target.id)
|
||||
? current
|
||||
: { ...current, comments: mergeWindowComments([target], current.comments) },
|
||||
);
|
||||
}, [issueId, linkedComment.data, recentComments.data, queryClient]);
|
||||
const comments = useMemo(
|
||||
() => mergeWindowComments(linkedComment.data ? [linkedComment.data] : [], windowComments),
|
||||
[linkedComment.data, windowComments],
|
||||
|
||||
Reference in new issue
Block a user