mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 10:30:37 +09:00
## Thinking Path > - Paperclip gives operators a live view of agent work across dashboards, transcripts, and run activity charts > - Those views consume live run updates and aggregate run activity from backend dashboard data > - Missing or partial run data could make charts brittle, and live transcript updates were heavier than needed > - Operators need dashboard data to stay stable even when recent run payloads are incomplete > - This pull request hardens dashboard run aggregation, guards chart rendering, and lightens live run update handling > - The benefit is a more reliable dashboard during active agent execution ## What Changed - Added dashboard run activity types and backend aggregation coverage. - Guarded activity chart rendering when run data is missing or partial. - Reduced live transcript update churn in active agent and run chat surfaces. - Fixed issue chat avatar alignment in the thread renderer. - Added focused dashboard, activity chart, and live transcript tests. ## Verification - `pnpm install --frozen-lockfile --ignore-scripts` - `pnpm exec vitest run server/src/__tests__/dashboard-service.test.ts ui/src/components/ActivityCharts.test.tsx ui/src/components/transcript/useLiveRunTranscripts.test.tsx` - Result: 8 tests passed, 1 skipped. The embedded Postgres dashboard service test skipped on this host with the existing PGlite/Postgres init warning; UI chart and transcript tests passed. ## Risks - Medium-low risk: aggregation semantics changed, but the UI remains guarded around incomplete data. - The dashboard service test is host-skipped here, so CI should confirm the embedded database path. > 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 coding agent based on GPT-5, tool-enabled local shell and GitHub workflow, exact runtime context window not exposed in this session. ## 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, or documented why targeted component tests are sufficient here - [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>
102 lines
2.8 KiB
TypeScript
102 lines
2.8 KiB
TypeScript
// @vitest-environment jsdom
|
|
|
|
import { act } from "react";
|
|
import type { ReactNode } from "react";
|
|
import { createRoot, type Root } from "react-dom/client";
|
|
import type { HeartbeatRun } from "@paperclipai/shared";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { RunActivityChart, SuccessRateChart } from "./ActivityCharts";
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
|
|
|
let container: HTMLDivElement;
|
|
let root: Root;
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(new Date("2026-04-20T12:00:00.000Z"));
|
|
container = document.createElement("div");
|
|
document.body.appendChild(container);
|
|
root = createRoot(container);
|
|
});
|
|
|
|
afterEach(() => {
|
|
act(() => root.unmount());
|
|
container.remove();
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
function render(ui: ReactNode) {
|
|
act(() => {
|
|
root.render(ui);
|
|
});
|
|
}
|
|
|
|
function createRun(overrides: Partial<HeartbeatRun> = {}): HeartbeatRun {
|
|
return {
|
|
id: "run-1",
|
|
companyId: "company-1",
|
|
agentId: "agent-1",
|
|
invocationSource: "on_demand",
|
|
triggerDetail: "manual",
|
|
status: "succeeded",
|
|
startedAt: new Date("2026-04-20T11:58:00.000Z"),
|
|
finishedAt: new Date("2026-04-20T11:59:00.000Z"),
|
|
error: null,
|
|
wakeupRequestId: null,
|
|
exitCode: 0,
|
|
signal: null,
|
|
usageJson: null,
|
|
resultJson: null,
|
|
sessionIdBefore: null,
|
|
sessionIdAfter: null,
|
|
logStore: null,
|
|
logRef: null,
|
|
logBytes: null,
|
|
logSha256: null,
|
|
logCompressed: false,
|
|
stdoutExcerpt: null,
|
|
stderrExcerpt: null,
|
|
errorCode: null,
|
|
externalRunId: null,
|
|
processPid: null,
|
|
processGroupId: null,
|
|
processStartedAt: null,
|
|
retryOfRunId: null,
|
|
processLossRetryCount: 0,
|
|
livenessState: null,
|
|
livenessReason: null,
|
|
continuationAttempt: 0,
|
|
lastUsefulActionAt: null,
|
|
nextAction: null,
|
|
contextSnapshot: null,
|
|
createdAt: new Date("2026-04-20T11:58:00.000Z"),
|
|
updatedAt: new Date("2026-04-20T11:59:00.000Z"),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("ActivityCharts", () => {
|
|
it("renders empty run charts when dashboard aggregate data is temporarily missing", () => {
|
|
render(<RunActivityChart activity={undefined} />);
|
|
expect(container.textContent).toContain("No runs yet");
|
|
|
|
render(<SuccessRateChart activity={undefined} />);
|
|
expect(container.textContent).toContain("No runs yet");
|
|
});
|
|
|
|
it("still aggregates raw agent runs for detail charts", () => {
|
|
render(
|
|
<RunActivityChart
|
|
runs={[
|
|
createRun({ id: "run-success", status: "succeeded" }),
|
|
createRun({ id: "run-failed", status: "failed" }),
|
|
]}
|
|
/>,
|
|
);
|
|
|
|
expect(container.textContent).not.toContain("No runs yet");
|
|
expect(container.querySelector("[title='2026-04-20: 2 runs']")).not.toBeNull();
|
|
});
|
|
});
|