Store opted-in report screenshots as validated local artifacts rather than GitHub attachments. - Add multipart report attachment upload with PNG/JPEG validation and provenance metadata. - Carry validated artifact references through report drafts and filing flows without binary egress. - Update report UI, tests, documentation, and release metadata. Files changed: .changeset/report-screenshot-artifacts.md | 7 + docs/dashboard-guide.md | 2 +- packages/core/src/index.gate.ts | 2 +- packages/core/src/index.ts | 2 +- packages/core/src/types.ts | 7 + packages/dashboard/app/api/report.ts | 14 +- packages/dashboard/app/components/ReportModal.css | 3 +- packages/dashboard/app/components/ReportModal.tsx | 41 ++--- .../app/components/__tests__/ReportModal.test.tsx | 58 +++++-- packages/dashboard/app/utils/report-capture.ts | 47 ++---- .../src/__tests__/report-pipeline.test.ts | 94 ------------ .../dashboard/src/__tests__/report-routes.test.ts | 123 +++++++++++---- packages/dashboard/src/report-pipeline.ts | 128 +++------------- .../dashboard/src/routes/register-report-routes.ts | 169 +++++++-------------- 14 files changed, 279 insertions(+), 418 deletions(-) Fusion-Task-Id: FN-8371 Fusion-Task-Lineage: 48d0e94d-602f-43e2-a63f-cb497221ac29 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
23 lines
1.3 KiB
TypeScript
23 lines
1.3 KiB
TypeScript
const MAX_ACTIVITY = 20;
|
|
const activity: string[] = [];
|
|
|
|
/**
|
|
* FNXC:ReportPipeline 2026-07-19-10:00:
|
|
* Capture returns a local PNG blob solely for immediate artifact upload. The
|
|
* caller must not serialize it into a report payload or display it as a draft.
|
|
*/
|
|
export async function captureScreenshot(): Promise<Blob | undefined> {
|
|
if (!navigator.mediaDevices?.getDisplayMedia) return undefined;
|
|
let stream: MediaStream | undefined;
|
|
try {
|
|
stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false });
|
|
const video = document.createElement("video"); video.srcObject = stream; await video.play();
|
|
const canvas = document.createElement("canvas"); canvas.width = video.videoWidth; canvas.height = video.videoHeight;
|
|
canvas.getContext("2d")?.drawImage(video, 0, 0);
|
|
return await new Promise<Blob | undefined>((resolve) => canvas.toBlob((blob) => resolve(blob ?? undefined), "image/png"));
|
|
} catch { return undefined; } finally { stream?.getTracks().forEach((track) => track.stop()); }
|
|
}
|
|
export function recordActivity(label: string): void { activity.push(label.slice(0, 80)); while (activity.length > MAX_ACTIVITY) activity.shift(); }
|
|
export function getRecentActivity(): string[] { return [...activity]; }
|
|
export function clearReportActivityForTests(): void { activity.length = 0; }
|