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>
38 lines
1.2 KiB
TypeScript
38 lines
1.2 KiB
TypeScript
export interface ActivityTraceEntry {
|
|
ts: string;
|
|
kind: string;
|
|
label: string;
|
|
}
|
|
|
|
const MAX_ENTRIES = 20;
|
|
const MAX_CHARS = 4_000;
|
|
const entries: ActivityTraceEntry[] = [];
|
|
|
|
function size(entry: ActivityTraceEntry): number {
|
|
return entry.ts.length + entry.kind.length + entry.label.length;
|
|
}
|
|
|
|
/**
|
|
* FNXC:ReportPipeline 2026-07-16-09:00:
|
|
* Reports may include a small user-facing activity trace by default. Keep it
|
|
* local, bounded text so it receives the same server-side scrub as all report
|
|
* context before any GitHub egress.
|
|
*/
|
|
export function recordActivity(entry: Omit<ActivityTraceEntry, "ts"> & { ts?: string }): void {
|
|
const next: ActivityTraceEntry = {
|
|
ts: typeof entry.ts === "string" ? entry.ts.slice(0, 64) : new Date().toISOString(),
|
|
kind: String(entry.kind).slice(0, 80),
|
|
label: String(entry.label).slice(0, 1_000),
|
|
};
|
|
entries.push(next);
|
|
while (entries.length > MAX_ENTRIES || entries.reduce((total, item) => total + size(item), 0) > MAX_CHARS) entries.shift();
|
|
}
|
|
|
|
export function snapshotActivityTrace(): ActivityTraceEntry[] {
|
|
return entries.map((entry) => ({ ...entry }));
|
|
}
|
|
|
|
export function clearActivityTraceForTests(): void {
|
|
entries.length = 0;
|
|
}
|