@striae-org/striae 5.4.5 → 5.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@striae-org/striae",
3
- "version": "5.4.5",
3
+ "version": "5.5.1",
4
4
  "private": false,
5
5
  "description": "Striae is a specialized, cloud-native platform designed to streamline forensic firearms identification by providing an intuitive environment for digital comparison image annotation, authenticated confirmations, and automated report generation.",
6
6
  "license": "Apache-2.0",
@@ -105,8 +105,8 @@
105
105
  "isbot": "^5.1.37",
106
106
  "jszip": "^3.10.1",
107
107
  "qrcode": "^1.5.4",
108
- "react": "^19.2.4",
109
- "react-dom": "^19.2.4",
108
+ "react": "^19.2.5",
109
+ "react-dom": "^19.2.5",
110
110
  "react-router": "^7.14.0"
111
111
  },
112
112
  "devDependencies": {
@@ -115,8 +115,8 @@
115
115
  "@types/qrcode": "^1.5.6",
116
116
  "@types/react": "^19.2.14",
117
117
  "@types/react-dom": "^19.2.3",
118
- "@typescript-eslint/eslint-plugin": "^8.58.0",
119
- "@typescript-eslint/parser": "^8.58.0",
118
+ "@typescript-eslint/eslint-plugin": "^8.58.1",
119
+ "@typescript-eslint/parser": "^8.58.1",
120
120
  "eslint": "^9.39.4",
121
121
  "eslint-import-resolver-typescript": "^4.4.4",
122
122
  "eslint-plugin-import": "^2.32.0",
@@ -126,9 +126,9 @@
126
126
  "firebase-admin": "^13.7.0",
127
127
  "modern-normalize": "^3.0.1",
128
128
  "typescript": "^5.9.3",
129
- "vite": "^6.4.1",
129
+ "vite": "^7.3.2",
130
130
  "vite-tsconfig-paths": "^6.1.1",
131
- "wrangler": "^4.80.0"
131
+ "wrangler": "^4.81.1"
132
132
  },
133
133
  "overrides": {
134
134
  "@tootallnate/once": "3.0.1",
@@ -136,7 +136,7 @@
136
136
  "undici": "7.24.1"
137
137
  },
138
138
  "engines": {
139
- "node": ">=20.0.0"
139
+ "node": ">=20.19.0"
140
140
  },
141
141
  "packageManager": "npm@11.11.0"
142
142
  }
@@ -7,6 +7,14 @@ const markdownFiles = [
7
7
  // Add other markdown files that need version updates
8
8
  ];
9
9
 
10
+ const workerDirs = [
11
+ 'workers/audit-worker',
12
+ 'workers/data-worker',
13
+ 'workers/image-worker',
14
+ 'workers/pdf-worker',
15
+ 'workers/user-worker',
16
+ ];
17
+
10
18
  function updateMarkdownVersions() {
11
19
  console.log(`📝 Updating markdown files with version ${packageJson.version}...`);
12
20
 
@@ -31,8 +39,57 @@ function updateMarkdownVersions() {
31
39
  console.error(`❌ Error updating ${filePath}:`, error.message);
32
40
  }
33
41
  });
42
+
43
+ console.log(`📦 Updating worker package.json files with version ${packageJson.version}...`);
44
+
45
+ workerDirs.forEach(workerDir => {
46
+ const pkgPath = path.join(__dirname, '..', workerDir, 'package.json');
47
+ const lockPath = path.join(__dirname, '..', workerDir, 'package-lock.json');
48
+
49
+ // --- Update package.json ---
50
+ if (!fs.existsSync(pkgPath)) {
51
+ console.log(`⚠️ Skipping ${workerDir}/package.json (file not found)`);
52
+ } else {
53
+ try {
54
+ const workerPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
55
+ workerPkg.version = packageJson.version;
56
+ fs.writeFileSync(pkgPath, JSON.stringify(workerPkg, null, 2) + '\n');
57
+ console.log(`✅ Updated ${workerDir}/package.json`);
58
+ } catch (error) {
59
+ console.error(`❌ Error updating ${workerDir}/package.json:`, error.message);
60
+ }
61
+ }
62
+
63
+ // --- Update package-lock.json ---
64
+ // Lockfile v2/v3 stores the version in two places:
65
+ // - Top-level `version` field
66
+ // - `packages[""].version` (the self-referencing root entry)
67
+ // Both must match package.json to pass `npm ci` consistency checks.
68
+ if (!fs.existsSync(lockPath)) {
69
+ console.log(
70
+ `⚠️ No package-lock.json found in ${workerDir} — run \`npm install\` there to generate one.`
71
+ );
72
+ } else {
73
+ try {
74
+ const lockData = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
75
+
76
+ if ('version' in lockData) {
77
+ lockData.version = packageJson.version;
78
+ }
79
+
80
+ if (lockData.packages && '' in lockData.packages) {
81
+ lockData.packages[''].version = packageJson.version;
82
+ }
83
+
84
+ fs.writeFileSync(lockPath, JSON.stringify(lockData, null, 2) + '\n');
85
+ console.log(`✅ Updated ${workerDir}/package-lock.json`);
86
+ } catch (error) {
87
+ console.error(`❌ Error updating ${workerDir}/package-lock.json:`, error.message);
88
+ }
89
+ }
90
+ });
34
91
 
35
- console.log('🎉 Markdown version update complete!');
92
+ console.log('🎉 Version update complete!');
36
93
  }
37
94
 
38
95
  // Run if called directly
@@ -1,18 +1,17 @@
1
- {
2
- "name": "audit-worker",
3
- "version": "0.0.0",
4
- "private": true,
5
- "scripts": {
6
- "deploy": "wrangler deploy",
7
- "dev": "wrangler dev",
8
- "start": "wrangler dev"
9
- },
10
- "devDependencies": {
11
- "@cloudflare/puppeteer": "^1.0.6",
12
- "wrangler": "^4.80.0"
13
- },
14
- "overrides": {
15
- "undici": "7.24.1",
16
- "yauzl": "3.2.1"
17
- }
18
- }
1
+ {
2
+ "name": "audit-worker",
3
+ "version": "5.5.1",
4
+ "private": true,
5
+ "scripts": {
6
+ "deploy": "wrangler deploy",
7
+ "dev": "wrangler dev",
8
+ "start": "wrangler dev"
9
+ },
10
+ "devDependencies": {
11
+ "wrangler": "^4.81.1"
12
+ },
13
+ "overrides": {
14
+ "undici": "7.24.1",
15
+ "yauzl": "3.2.1"
16
+ }
17
+ }
@@ -7,7 +7,7 @@
7
7
  "name": "AUDIT_WORKER_NAME",
8
8
  "account_id": "ACCOUNT_ID",
9
9
  "main": "src/audit-worker.ts",
10
- "compatibility_date": "2026-04-03",
10
+ "compatibility_date": "2026-04-09",
11
11
  "compatibility_flags": [
12
12
  "nodejs_compat"
13
13
  ],
@@ -1,18 +1,17 @@
1
- {
2
- "name": "data-worker",
3
- "version": "0.0.0",
4
- "private": true,
5
- "scripts": {
6
- "deploy": "wrangler deploy",
7
- "dev": "wrangler dev",
8
- "start": "wrangler dev"
9
- },
10
- "devDependencies": {
11
- "@cloudflare/puppeteer": "^1.0.6",
12
- "wrangler": "^4.80.0"
13
- },
14
- "overrides": {
15
- "undici": "7.24.1",
16
- "yauzl": "3.2.1"
17
- }
18
- }
1
+ {
2
+ "name": "data-worker",
3
+ "version": "5.5.1",
4
+ "private": true,
5
+ "scripts": {
6
+ "deploy": "wrangler deploy",
7
+ "dev": "wrangler dev",
8
+ "start": "wrangler dev"
9
+ },
10
+ "devDependencies": {
11
+ "wrangler": "^4.81.1"
12
+ },
13
+ "overrides": {
14
+ "undici": "7.24.1",
15
+ "yauzl": "3.2.1"
16
+ }
17
+ }
@@ -5,7 +5,7 @@
5
5
  "name": "DATA_WORKER_NAME",
6
6
  "account_id": "ACCOUNT_ID",
7
7
  "main": "src/data-worker.ts",
8
- "compatibility_date": "2026-04-03",
8
+ "compatibility_date": "2026-04-09",
9
9
  "compatibility_flags": [
10
10
  "nodejs_compat"
11
11
  ],
@@ -1,18 +1,17 @@
1
- {
2
- "name": "image-worker",
3
- "version": "0.0.0",
4
- "private": true,
5
- "scripts": {
6
- "deploy": "wrangler deploy",
7
- "dev": "wrangler dev",
8
- "start": "wrangler dev"
9
- },
10
- "devDependencies": {
11
- "@cloudflare/puppeteer": "^1.0.6",
12
- "wrangler": "^4.80.0"
13
- },
14
- "overrides": {
15
- "undici": "7.24.1",
16
- "yauzl": "3.2.1"
17
- }
18
- }
1
+ {
2
+ "name": "image-worker",
3
+ "version": "5.5.1",
4
+ "private": true,
5
+ "scripts": {
6
+ "deploy": "wrangler deploy",
7
+ "dev": "wrangler dev",
8
+ "start": "wrangler dev"
9
+ },
10
+ "devDependencies": {
11
+ "wrangler": "^4.81.1"
12
+ },
13
+ "overrides": {
14
+ "undici": "7.24.1",
15
+ "yauzl": "3.2.1"
16
+ }
17
+ }
@@ -2,7 +2,7 @@
2
2
  "name": "IMAGES_WORKER_NAME",
3
3
  "account_id": "ACCOUNT_ID",
4
4
  "main": "src/image-worker.ts",
5
- "compatibility_date": "2026-04-03",
5
+ "compatibility_date": "2026-04-09",
6
6
  "compatibility_flags": [
7
7
  "nodejs_compat"
8
8
  ],
@@ -1,19 +1,18 @@
1
- {
2
- "name": "pdf-worker",
3
- "version": "0.0.0",
4
- "private": true,
5
- "scripts": {
6
- "generate:assets": "node scripts/generate-assets.js",
7
- "deploy": "wrangler deploy",
8
- "dev": "wrangler dev",
9
- "start": "wrangler dev"
10
- },
11
- "devDependencies": {
12
- "@cloudflare/puppeteer": "^1.0.6",
13
- "wrangler": "^4.80.0"
14
- },
15
- "overrides": {
16
- "undici": "7.24.1",
17
- "yauzl": "3.2.1"
18
- }
19
- }
1
+ {
2
+ "name": "pdf-worker",
3
+ "version": "5.5.1",
4
+ "private": true,
5
+ "scripts": {
6
+ "generate:assets": "node scripts/generate-assets.js",
7
+ "deploy": "wrangler deploy",
8
+ "dev": "wrangler dev",
9
+ "start": "wrangler dev"
10
+ },
11
+ "devDependencies": {
12
+ "wrangler": "^4.81.1"
13
+ },
14
+ "overrides": {
15
+ "undici": "7.24.1",
16
+ "yauzl": "3.2.1"
17
+ }
18
+ }
@@ -0,0 +1,253 @@
1
+ import type { AuditTrailReportPayload, PDFGenerationData, ReportPdfOptions } from './report-types';
2
+ import { buildRepeatedChromePdfOptions, escapeHtml } from './report-layout';
3
+
4
+ const safeText = (value: unknown): string => escapeHtml(String(value ?? ''));
5
+
6
+ const formatTimestamp = (timestamp: string, timezone?: string): string => {
7
+ const parsed = new Date(timestamp);
8
+ if (Number.isNaN(parsed.getTime())) {
9
+ return timestamp;
10
+ }
11
+
12
+ if (!timezone) {
13
+ return parsed.toISOString();
14
+ }
15
+
16
+ try {
17
+ const parts = new Intl.DateTimeFormat('en-US', {
18
+ timeZone: timezone,
19
+ month: '2-digit',
20
+ day: '2-digit',
21
+ year: 'numeric',
22
+ hour: '2-digit',
23
+ minute: '2-digit',
24
+ second: '2-digit',
25
+ hour12: false,
26
+ timeZoneName: 'short'
27
+ }).formatToParts(parsed);
28
+
29
+ const get = (type: string): string => parts.find(p => p.type === type)?.value ?? '';
30
+ return `${get('month')}/${get('day')}/${get('year')} ${get('hour')}:${get('minute')}:${get('second')} ${get('timeZoneName')}`;
31
+ } catch {
32
+ return parsed.toISOString();
33
+ }
34
+ };
35
+
36
+ const renderEntryDetailsSummary = (entry: Record<string, unknown>): string => {
37
+ const details = (entry.details ?? {}) as Record<string, unknown>;
38
+ const caseNumber = typeof details.caseNumber === 'string' ? details.caseNumber : '';
39
+ const workflowPhase = typeof details.workflowPhase === 'string' ? details.workflowPhase : '';
40
+ const fileName = typeof details.fileName === 'string' ? details.fileName : '';
41
+
42
+ const summaryRows = [
43
+ caseNumber ? `<div><strong>Case:</strong> ${safeText(caseNumber)}</div>` : '',
44
+ workflowPhase ? `<div><strong>Phase:</strong> ${safeText(workflowPhase)}</div>` : '',
45
+ fileName ? `<div><strong>File:</strong> ${safeText(fileName)}</div>` : ''
46
+ ].filter(Boolean);
47
+
48
+ if (summaryRows.length === 0) {
49
+ return '<div class="entry-meta-muted">No structured summary fields available.</div>';
50
+ }
51
+
52
+ return summaryRows.join('');
53
+ };
54
+
55
+ const renderRawJsonAppendix = (entry: unknown): string => {
56
+ if (entry === null || entry === undefined) {
57
+ return '';
58
+ }
59
+
60
+ let rawJson: string;
61
+ try {
62
+ rawJson = JSON.stringify(entry, null, 2) ?? '';
63
+ } catch {
64
+ return '';
65
+ }
66
+
67
+ if (!rawJson) {
68
+ return '';
69
+ }
70
+
71
+ return `
72
+ <div class="entry-raw-json">
73
+ <div class="entry-raw-label">Raw JSON Entry</div>
74
+ <pre>${escapeHtml(rawJson)}</pre>
75
+ </div>
76
+ `;
77
+ };
78
+
79
+ export const isAuditTrailReportMode = (data: PDFGenerationData): boolean =>
80
+ data.reportMode === 'audit-trail';
81
+
82
+ export const getAuditTrailPayload = (data: PDFGenerationData): AuditTrailReportPayload => {
83
+ const payload = data.auditTrailReport;
84
+
85
+ if (!payload) {
86
+ throw new Error('Audit trail report payload is required when reportMode is audit-trail');
87
+ }
88
+
89
+ return payload;
90
+ };
91
+
92
+ export const renderAuditTrailReport = (data: PDFGenerationData): string => {
93
+ const payload = getAuditTrailPayload(data);
94
+ const entries = payload.entries || [];
95
+ const timezone = data.userTimezone;
96
+
97
+ const entrySections = entries.map((entry, index) => {
98
+ const entryRecord = entry as Record<string, unknown>;
99
+ const timestamp = typeof entryRecord.timestamp === 'string' ? entryRecord.timestamp : 'unknown';
100
+ const action = typeof entryRecord.action === 'string' ? entryRecord.action : 'unknown';
101
+ const result = typeof entryRecord.result === 'string' ? entryRecord.result : 'unknown';
102
+ const userEmail = typeof entryRecord.userEmail === 'string' ? entryRecord.userEmail : 'unknown';
103
+ const userId = typeof entryRecord.userId === 'string' ? entryRecord.userId : 'unknown';
104
+
105
+ return `
106
+ <section class="entry-section">
107
+ <h3 class="entry-title">Entry ${index + 1} of ${entries.length}</h3>
108
+ <div class="entry-core-grid">
109
+ <div><strong>Timestamp:</strong> ${safeText(formatTimestamp(timestamp, timezone))}</div>
110
+ <div><strong>Action:</strong> ${safeText(action)}</div>
111
+ <div><strong>Result:</strong> ${safeText(result)}</div>
112
+ <div><strong>User Email:</strong> ${safeText(userEmail)}</div>
113
+ <div><strong>User ID:</strong> ${safeText(userId)}</div>
114
+ </div>
115
+ <div class="entry-meta">
116
+ ${renderEntryDetailsSummary(entryRecord)}
117
+ </div>
118
+ ${renderRawJsonAppendix(entry)}
119
+ </section>
120
+ `;
121
+ }).join('');
122
+
123
+ return `
124
+ <!DOCTYPE html>
125
+ <html lang="en">
126
+ <head>
127
+ <meta charset="utf-8" />
128
+ <style>
129
+ html, body {
130
+ margin: 0;
131
+ padding: 0;
132
+ width: 100%;
133
+ font-family: Arial, sans-serif;
134
+ color: #1f2933;
135
+ background: #ffffff;
136
+ }
137
+ body {
138
+ box-sizing: border-box;
139
+ }
140
+ .report-body {
141
+ width: 100%;
142
+ box-sizing: border-box;
143
+ }
144
+ .summary {
145
+ border: 1px solid #d0d7de;
146
+ border-radius: 8px;
147
+ padding: 16px;
148
+ margin-bottom: 16px;
149
+ page-break-inside: avoid;
150
+ }
151
+ .summary h1 {
152
+ margin: 0 0 8px;
153
+ font-size: 22px;
154
+ }
155
+ .summary-grid {
156
+ display: grid;
157
+ grid-template-columns: repeat(2, minmax(0, 1fr));
158
+ gap: 6px 12px;
159
+ font-size: 12px;
160
+ }
161
+ .entry-section {
162
+ border: 1px solid #d0d7de;
163
+ border-radius: 8px;
164
+ padding: 12px;
165
+ margin-bottom: 12px;
166
+ page-break-inside: avoid;
167
+ }
168
+ .entry-title {
169
+ margin: 0 0 8px;
170
+ font-size: 14px;
171
+ }
172
+ .entry-core-grid {
173
+ display: grid;
174
+ grid-template-columns: repeat(2, minmax(0, 1fr));
175
+ gap: 6px 12px;
176
+ font-size: 11px;
177
+ }
178
+ .entry-meta {
179
+ margin-top: 10px;
180
+ font-size: 11px;
181
+ color: #334155;
182
+ }
183
+ .entry-meta-muted {
184
+ color: #64748b;
185
+ font-style: italic;
186
+ }
187
+ .entry-raw-json {
188
+ margin-top: 12px;
189
+ border-top: 1px dashed #c5ced8;
190
+ padding-top: 10px;
191
+ }
192
+ .entry-raw-label {
193
+ font-size: 10px;
194
+ letter-spacing: 0.05em;
195
+ text-transform: uppercase;
196
+ color: #475569;
197
+ margin-bottom: 6px;
198
+ font-weight: 700;
199
+ }
200
+ .entry-raw-json pre {
201
+ margin: 0;
202
+ padding: 10px;
203
+ border: 1px solid #d0d7de;
204
+ border-radius: 6px;
205
+ background: #f8fafc;
206
+ color: #0f172a;
207
+ font-size: 10px;
208
+ line-height: 1.5;
209
+ white-space: pre-wrap;
210
+ word-break: break-word;
211
+ }
212
+ </style>
213
+ </head>
214
+ <body>
215
+ <div class="report-body">
216
+ <section class="summary">
217
+ <h1>Case Audit Trail Report</h1>
218
+ <div class="summary-grid">
219
+ <div><strong>Case Number:</strong> ${safeText(payload.caseNumber)}</div>
220
+ <div><strong>Exported At:</strong> ${safeText(formatTimestamp(payload.exportedAt, timezone))}</div>
221
+ <div><strong>Range Start:</strong> ${safeText(formatTimestamp(payload.exportRangeStart, timezone))}</div>
222
+ <div><strong>Range End:</strong> ${safeText(formatTimestamp(payload.exportRangeEnd, timezone))}</div>
223
+ <div><strong>Total Entries (All Parts):</strong> ${safeText(payload.totalEntries)}</div>
224
+ <div><strong>This Part:</strong> ${safeText(payload.chunkIndex)} of ${safeText(payload.totalChunks)}</div>
225
+ <div><strong>Entries in Part:</strong> ${safeText(entries.length)}</div>
226
+ <div><strong>Raw JSON Appendix:</strong> Included when available</div>
227
+ </div>
228
+ </section>
229
+ ${entrySections}
230
+ </div>
231
+ </body>
232
+ </html>
233
+ `;
234
+ };
235
+
236
+ export const getAuditTrailPdfOptions = (data: PDFGenerationData): Partial<ReportPdfOptions> => {
237
+ const payload = getAuditTrailPayload(data);
238
+
239
+ return {
240
+ format: 'letter',
241
+ ...buildRepeatedChromePdfOptions({
242
+ headerLeft: data.currentDate,
243
+ headerCenter: 'Case Audit Trail Report',
244
+ headerRight: `Case ${payload.caseNumber}`,
245
+ headerDetailLeft: `Entries ${payload.totalEntries}`,
246
+ headerDetailRight: `Part ${payload.chunkIndex}/${payload.totalChunks}`,
247
+ footerLeft: 'Striae Audit Export',
248
+ footerCenter: payload.exportedAt,
249
+ footerRight: `Case ${payload.caseNumber}`,
250
+ includePageNumbers: true
251
+ })
252
+ };
253
+ };
@@ -480,17 +480,19 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
480
480
  `;
481
481
  };
482
482
 
483
- export const getPdfOptions: ReportPdfOptionsBuilder = (data: PDFGenerationData) => buildRepeatedChromePdfOptions({
484
- headerLeft: data.currentDate,
485
- headerRight: data.caseNumber,
486
- headerDetailLeft: [data.annotationData?.leftCase, data.annotationData?.leftItem].filter(Boolean).join(' / ')
487
- ? `Left Case / Item: ${[data.annotationData?.leftCase, data.annotationData?.leftItem].filter(Boolean).join(' / ')}`
488
- : undefined,
489
- headerDetailRight: [data.annotationData?.rightCase, data.annotationData?.rightItem].filter(Boolean).join(' / ')
490
- ? `Right Case / Item: ${[data.annotationData?.rightCase, data.annotationData?.rightItem].filter(Boolean).join(' / ')}`
491
- : undefined,
492
- footerLeft: 'Notes formatted by Striae',
493
- footerCenter: data.userCompany,
494
- footerRight: data.notesUpdatedFormatted ? `Notes updated ${data.notesUpdatedFormatted}` : undefined,
495
- footerLeftImageSrc: ICON_256,
496
- });
483
+ export const getPdfOptions: ReportPdfOptionsBuilder = (data: PDFGenerationData) => {
484
+ return buildRepeatedChromePdfOptions({
485
+ headerLeft: data.currentDate,
486
+ headerRight: data.caseNumber,
487
+ headerDetailLeft: [data.annotationData?.leftCase, data.annotationData?.leftItem].filter(Boolean).join(' / ')
488
+ ? `Left Case / Item: ${[data.annotationData?.leftCase, data.annotationData?.leftItem].filter(Boolean).join(' / ')}`
489
+ : undefined,
490
+ headerDetailRight: [data.annotationData?.rightCase, data.annotationData?.rightItem].filter(Boolean).join(' / ')
491
+ ? `Right Case / Item: ${[data.annotationData?.rightCase, data.annotationData?.rightItem].filter(Boolean).join(' / ')}`
492
+ : undefined,
493
+ footerLeft: 'Notes formatted by Striae',
494
+ footerCenter: data.userCompany,
495
+ footerRight: data.notesUpdatedFormatted ? `Notes updated ${data.notesUpdatedFormatted}` : undefined,
496
+ footerLeftImageSrc: ICON_256,
497
+ });
498
+ };
@@ -1,4 +1,5 @@
1
1
  import type { PDFGenerationData, PDFGenerationRequest, ReportModule, ReportPdfOptions } from './report-types';
2
+ import { getAuditTrailPdfOptions, isAuditTrailReportMode, renderAuditTrailReport } from './audit-trail-report';
2
3
 
3
4
  interface Env {
4
5
  PDF_WORKER_AUTH: string;
@@ -112,6 +113,13 @@ function resolveReportRequest(payload: unknown): PDFGenerationRequest {
112
113
  }
113
114
 
114
115
  async function renderReport(reportFormat: string, data: PDFGenerationData): Promise<{ html: string; pdfOptions: ReportPdfOptions }> {
116
+ if (isAuditTrailReportMode(data)) {
117
+ return {
118
+ html: renderAuditTrailReport(data),
119
+ pdfOptions: resolvePdfOptions(getAuditTrailPdfOptions(data)),
120
+ };
121
+ }
122
+
115
123
  const loader = reportModuleLoaders[reportFormat];
116
124
 
117
125
  if (!loader) {
@@ -58,6 +58,21 @@ export interface PDFGenerationData {
58
58
  userFirstName?: string;
59
59
  userLastName?: string;
60
60
  userBadgeId?: string;
61
+ reportMode?: 'audit-trail';
62
+ auditTrailReport?: AuditTrailReportPayload;
63
+ userTimezone?: string;
64
+ }
65
+
66
+ export interface AuditTrailReportPayload {
67
+ caseNumber: string;
68
+ exportedAt: string;
69
+ exportRangeStart: string;
70
+ exportRangeEnd: string;
71
+ chunkIndex: number;
72
+ totalChunks: number;
73
+ totalEntries: number;
74
+ includeRawJsonAppendix: boolean;
75
+ entries: unknown[];
61
76
  }
62
77
 
63
78
  export interface PDFGenerationRequest {
@@ -2,7 +2,7 @@
2
2
  "name": "PDF_WORKER_NAME",
3
3
  "account_id": "ACCOUNT_ID",
4
4
  "main": "src/pdf-worker.ts",
5
- "compatibility_date": "2026-04-03",
5
+ "compatibility_date": "2026-04-09",
6
6
  "compatibility_flags": [
7
7
  "nodejs_compat"
8
8
  ],