FN-8309: add report screenshot and activity capture

Add opt-in local screenshot attachments and scrubbed activity traces to reports.

- Capture, preview, confirm, and locally retain report screenshots without GitHub egress.
- Include bounded activity traces in draft and filing context with server-side validation.
- Add report pipeline coverage, documentation, and release metadata.

Files changed:
 .changeset/fn-8309-report-capture.md               |   7 ++
 docs/dashboard-guide.md                            |   2 +
 packages/dashboard/app/App.tsx                     |  10 ++
 packages/dashboard/app/api/report.ts               |  16 ++-
 packages/dashboard/app/components/ReportModal.css  |   3 +
 packages/dashboard/app/components/ReportModal.tsx  |  45 ++++++-
 .../app/utils/__tests__/activity-trace.test.ts     |  19 +++
 packages/dashboard/app/utils/activity-trace.ts     |  37 ++++++
 packages/dashboard/app/utils/capture-screenshot.ts |  22 ++++
 packages/dashboard/package.json                    |   5 +-
 .../src/__tests__/report-pipeline.test.ts          |  13 +++
 packages/dashboard/src/report-pipeline.ts          |  29 ++++-
 packages/dashboard/src/routes.ts                   |   2 +-
 .../dashboard/src/routes/register-report-routes.ts |  85 +++++++++++++-
 packages/dashboard/src/routes/types.ts             |   4 +-
 pnpm-lock.yaml                                     | 129 ++++++++++-----------
 16 files changed, 341 insertions(+), 87 deletions(-)

Fusion-Task-Id: FN-8309

Fusion-Task-Lineage: db9c14a6-1721-4e81-8891-cfd301473cd0

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-18 12:41:42 -07:00
parent d5db5fd58d
commit 88b0db0f4d
16 changed files with 343 additions and 89 deletions

View File

@@ -6,6 +6,18 @@ async function post(path: string, body: unknown) {
return response.json();
}
export function reportDraft(input: { actionType: ReportActionType; userPrompt: string; contextRefs?: { taskId?: string; agentId?: string } }) { return post("/api/report/draft", input); }
export function reportFile(input: { actionType: ReportActionType; report: unknown; endorseIssueNumber?: number; endorseDiscussionId?: string }) { return post("/api/report/file", input); }
export interface ReportActivityTraceEntry { ts: string; kind: string; label: string; }
export interface ReportContextInput { actionType: ReportActionType; userPrompt: string; contextRefs?: { taskId?: string; agentId?: string }; activityTrace?: ReportActivityTraceEntry[]; screenshotArtifactId?: string; }
export async function uploadReportScreenshot(blob: Blob, contextRefs?: { taskId?: string; agentId?: string }): Promise<{ artifactId: string; uri?: string }> {
const form = new FormData();
form.append("screenshot", blob, "report-screenshot.jpg");
if (contextRefs) form.append("contextRefs", JSON.stringify(contextRefs));
const response = await fetch("/api/report/attachment", { method: "POST", body: form });
if (!response.ok) throw new Error((await response.json().catch(() => ({ error: response.statusText }))).error ?? response.statusText);
return response.json();
}
export function reportDraft(input: ReportContextInput) { return post("/api/report/draft", input); }
export function reportFile(input: { actionType: ReportActionType; report: unknown; endorseIssueNumber?: number; endorseDiscussionId?: string; activityTrace?: ReportActivityTraceEntry[]; screenshotArtifactId?: string }) { return post("/api/report/file", input); }
export function reportHelp(question: string) { return post("/api/report/help", { question }); }