mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 10:30:37 +09:00
Show workspace changes and stale notices in issue threads (#5356)
## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies > - The issue thread is the operator's durable audit trail for what changed and why > - Workspace changes and stale disposition notices need to be visible in that same timeline without noisy or misleading rendering > - The local branch already contained backend activity details, timeline conversion, and UI rendering work for those events > - This pull request isolates the issue-thread activity work into a standalone branch against `origin/master` > - The benefit is a focused audit-trail PR that can merge independently of the sidebar/operator UI polish branch ## What Changed - Adds readable workspace-change activity details to issue update activity events. - Surfaces workspace-change events in issue chat/timeline rendering. - Makes the existing issue comment migration idempotent. - Folds and renders stale disposition notices inline so they match activity-log styling and spacing. - Adds focused route, timeline, and issue-thread system notice coverage. ## Verification - `pnpm install --frozen-lockfile` - `pnpm exec vitest run server/src/__tests__/issue-activity-events-routes.test.ts ui/src/lib/issue-timeline-events.test.ts ui/src/components/IssueChatThreadSystemNotice.test.tsx` — 3 files passed, 22 tests passed. - Confirmed the PR changes 9 files and does not include `pnpm-lock.yaml` or `.github/workflows/*`. - `pnpm exec vitest run server/src/__tests__/issue-closed-workspace-routes.test.ts` — 1 file passed, 4 tests passed. - `pnpm exec vitest run server/src/__tests__/issue-activity-events-routes.test.ts ui/src/lib/issue-timeline-events.test.ts ui/src/components/IssueChatThreadSystemNotice.test.tsx server/src/services/recovery/successful-run-handoff.test.ts packages/shared/src/validators/issue.test.ts` — 5 files passed, 54 tests passed. - `pnpm --filter @paperclipai/shared typecheck && pnpm --filter @paperclipai/server typecheck && pnpm --filter @paperclipai/ui typecheck`. - `pnpm --filter @paperclipai/ui typecheck` after adding the Storybook screenshot fixture. - Captured Storybook screenshots for the new UI rendering paths: - Collapsed stale notice + workspace-change row: `docs/pr-screenshots/pr-5356/issue-thread-notices-collapsed.png` - Expanded stale notice details: `docs/pr-screenshots/pr-5356/issue-thread-notices-expanded.png` ### Screenshots Collapsed stale notice with workspace-change row:  Expanded stale notice details:  ## Risks - Moderate risk: this touches issue activity serialization and issue-thread rendering, both of which are central operator surfaces. - Migration risk is low: the only migration change makes an existing migration idempotent. - No new migrations are introduced, so there is no cross-PR migration ordering requirement. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, GPT-5 coding agent, shell/tool-use enabled, used to split the existing branch, verify the isolated PR branch, and create this PR. ## 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 run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
4103978578
commit
d0e9cc76f2
17 changed files with 852 additions and 36 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import express from "express";
|
||||
import request from "supertest";
|
||||
import { getTableName } from "drizzle-orm";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { normalizeIssueExecutionPolicy } from "../services/issue-execution-policy.ts";
|
||||
|
||||
|
|
@ -266,6 +267,76 @@ describe("issue activity event routes", () => {
|
|||
});
|
||||
}, 15_000);
|
||||
|
||||
it("logs readable workspace change activity details for issue updates", async () => {
|
||||
const previousProjectWorkspaceId = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
|
||||
const nextExecutionWorkspaceId = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb";
|
||||
const issue = {
|
||||
...makeIssue(),
|
||||
projectId: "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
|
||||
projectWorkspaceId: previousProjectWorkspaceId,
|
||||
executionWorkspaceId: null,
|
||||
executionWorkspacePreference: "shared_workspace",
|
||||
executionWorkspaceSettings: { mode: "shared_workspace" },
|
||||
};
|
||||
mockIssueService.getById.mockResolvedValue(issue);
|
||||
mockIssueService.update.mockImplementation(async (_id: string, patch: Record<string, unknown>) => ({
|
||||
...issue,
|
||||
...patch,
|
||||
updatedAt: new Date(),
|
||||
}));
|
||||
|
||||
const dbMock = {
|
||||
select: vi.fn(() => ({
|
||||
from: (table: unknown) => ({
|
||||
where: async () => {
|
||||
const tableName = getTableName(table as Parameters<typeof getTableName>[0]);
|
||||
if (tableName === "project_workspaces") {
|
||||
return [{ id: previousProjectWorkspaceId, name: "Main workspace" }];
|
||||
}
|
||||
if (tableName === "execution_workspaces") {
|
||||
return [{ id: nextExecutionWorkspaceId, name: "Feature workspace" }];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
}),
|
||||
})),
|
||||
};
|
||||
|
||||
const res = await request(await createApp(dbMock))
|
||||
.patch(`/api/issues/${issue.id}`)
|
||||
.send({ executionWorkspaceId: nextExecutionWorkspaceId });
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
await vi.waitFor(() => {
|
||||
expect(mockLogActivity).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
action: "issue.updated",
|
||||
details: expect.objectContaining({
|
||||
executionWorkspaceId: nextExecutionWorkspaceId,
|
||||
workspaceChange: {
|
||||
from: {
|
||||
label: "Main workspace",
|
||||
projectWorkspaceId: previousProjectWorkspaceId,
|
||||
executionWorkspaceId: null,
|
||||
mode: "shared_workspace",
|
||||
},
|
||||
to: {
|
||||
label: "Feature workspace",
|
||||
projectWorkspaceId: previousProjectWorkspaceId,
|
||||
executionWorkspaceId: nextExecutionWorkspaceId,
|
||||
mode: "shared_workspace",
|
||||
},
|
||||
},
|
||||
_previous: expect.objectContaining({
|
||||
executionWorkspaceId: null,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("logs successful_run_handoff_resolved when an in_progress issue transitions to done with a pending required handoff", async () => {
|
||||
const issue = { ...makeIssue(), status: "in_progress" };
|
||||
mockIssueService.getById.mockResolvedValue(issue);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue