feat(FN-3785): add standalone HTML rendering and export routes for reports

Adds a complete HTML rendering and export pipeline to the fusion-plugin-reports plugin, including a standalone HTML renderer with template and stylesheet support, new export routes (`/api/reports/:id/export/html`) that persist rendered HTML to the store, and plugin-type support for non-JSON route re

Fusion-Task-Id: FN-3785
This commit is contained in:
Fusion
2026-05-10 10:05:07 -07:00
committed by gsxdsm
parent 4b54a7ddbc
commit d0a2d90182
23 changed files with 911 additions and 3 deletions

View File

@@ -29,6 +29,8 @@ interface ReportRow {
failureReason: string | null;
draftMarkdown: string | null;
renderedHtmlPath: string | null;
rendered_html: string | null;
rendered_html_generated_at: string | null;
metadataJson: string;
combinedReviewJson: string | null;
createdAt: string;
@@ -76,6 +78,8 @@ export class ReportStore extends EventEmitter<ReportStoreEvents> {
failureReason: null,
draftMarkdown: input.draftMarkdown ?? null,
renderedHtmlPath: null,
renderedHtml: null,
renderedHtmlGeneratedAt: null,
metadata: input.metadata ?? {},
combinedReview: null,
createdAt: now,
@@ -88,12 +92,12 @@ export class ReportStore extends EventEmitter<ReportStoreEvents> {
id, cadence, periodStart, periodEnd, title, status,
generationStartedAt, generationCompletedAt, reviewStartedAt, reviewCompletedAt,
approvedAt, approvedBy, publishedAt, archivedAt, failureReason,
draftMarkdown, renderedHtmlPath, metadataJson, combinedReviewJson, createdAt, updatedAt
draftMarkdown, renderedHtmlPath, rendered_html, rendered_html_generated_at, metadataJson, combinedReviewJson, createdAt, updatedAt
) VALUES (
@id, @cadence, @periodStart, @periodEnd, @title, @status,
@generationStartedAt, @generationCompletedAt, @reviewStartedAt, @reviewCompletedAt,
@approvedAt, @approvedBy, @publishedAt, @archivedAt, @failureReason,
@draftMarkdown, @renderedHtmlPath, @metadataJson, @combinedReviewJson, @createdAt, @updatedAt
@draftMarkdown, @renderedHtmlPath, @renderedHtml, @renderedHtmlGeneratedAt, @metadataJson, @combinedReviewJson, @createdAt, @updatedAt
)
`).run(this.toDbParams(report, true));
});
@@ -157,6 +161,8 @@ export class ReportStore extends EventEmitter<ReportStoreEvents> {
draftMarkdown: patch.draftMarkdown ?? current.draftMarkdown,
renderedHtmlPath: patch.renderedHtmlPath ?? current.renderedHtmlPath,
metadata: patch.metadata ?? current.metadata,
renderedHtml: patch.renderedHtml ?? current.renderedHtml,
renderedHtmlGeneratedAt: patch.renderedHtmlGeneratedAt ?? current.renderedHtmlGeneratedAt,
failureReason: patch.failureReason ?? current.failureReason,
updatedAt: new Date().toISOString(),
};
@@ -224,6 +230,13 @@ export class ReportStore extends EventEmitter<ReportStoreEvents> {
return this.updateReport(id, { renderedHtmlPath: htmlPath });
}
setRenderedHtml(id: string, html: string): Report {
return this.updateReport(id, {
renderedHtml: html,
renderedHtmlGeneratedAt: new Date().toISOString(),
});
}
deleteReport(id: string): void {
this.requireReport(id);
this.db.transaction(() => {
@@ -258,6 +271,8 @@ export class ReportStore extends EventEmitter<ReportStoreEvents> {
failureReason: row.failureReason,
draftMarkdown: row.draftMarkdown,
renderedHtmlPath: row.renderedHtmlPath,
renderedHtml: row.rendered_html,
renderedHtmlGeneratedAt: row.rendered_html_generated_at,
metadata: this.parseMetadata(row.metadataJson),
combinedReview: this.parseCombinedReview(row.combinedReviewJson),
createdAt: row.createdAt,
@@ -284,6 +299,8 @@ export class ReportStore extends EventEmitter<ReportStoreEvents> {
failureReason = @failureReason,
draftMarkdown = @draftMarkdown,
renderedHtmlPath = @renderedHtmlPath,
rendered_html = @renderedHtml,
rendered_html_generated_at = @renderedHtmlGeneratedAt,
metadataJson = @metadataJson,
combinedReviewJson = @combinedReviewJson,
updatedAt = @updatedAt
@@ -314,6 +331,8 @@ export class ReportStore extends EventEmitter<ReportStoreEvents> {
failureReason: report.failureReason,
draftMarkdown: report.draftMarkdown,
renderedHtmlPath: report.renderedHtmlPath,
renderedHtml: report.renderedHtml,
renderedHtmlGeneratedAt: report.renderedHtmlGeneratedAt,
metadataJson: JSON.stringify(report.metadata ?? {}),
combinedReviewJson: report.combinedReview ? JSON.stringify(report.combinedReview) : null,
...(includeCreatedAt ? { createdAt: report.createdAt } : {}),

View File

@@ -30,6 +30,8 @@ export interface Report {
failureReason: string | null;
draftMarkdown: string | null;
renderedHtmlPath: string | null;
renderedHtml: string | null;
renderedHtmlGeneratedAt: string | null;
metadata: Record<string, unknown>;
combinedReview: CombinedReview | null;
createdAt: string;
@@ -45,7 +47,7 @@ export interface ReportCreateInput {
draftMarkdown?: string;
}
export type ReportUpdateInput = Partial<Pick<Report, "title" | "draftMarkdown" | "renderedHtmlPath" | "metadata" | "failureReason">>;
export type ReportUpdateInput = Partial<Pick<Report, "title" | "draftMarkdown" | "renderedHtmlPath" | "renderedHtml" | "renderedHtmlGeneratedAt" | "metadata" | "failureReason">>;
export interface ReportListFilter {
cadence?: ReportCadence;