@striae-org/striae 5.5.0 → 5.5.2

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.
Files changed (32) hide show
  1. package/.env.example +9 -1
  2. package/app/components/actions/export-audit-pdf.ts +2 -3
  3. package/app/components/sidebar/notes/class-details/class-details-modal.tsx +3 -3
  4. package/app/components/sidebar/notes/notes-editor-form.tsx +244 -12
  5. package/app/components/sidebar/notes/notes-editor-modal.tsx +181 -2
  6. package/app/components/sidebar/notes/notes.module.css +77 -0
  7. package/app/components/user/user.module.css +1 -1
  8. package/app/routes/auth/login.example.tsx +17 -5
  9. package/functions/api/_shared/registration-allowlist.ts +38 -0
  10. package/functions/api/auth/can-register.ts +59 -0
  11. package/functions/api/user/[[path]].ts +34 -0
  12. package/members.emails.example +11 -0
  13. package/package.json +12 -12
  14. package/scripts/deploy-all.sh +2 -2
  15. package/scripts/deploy-members-emails.sh +102 -0
  16. package/scripts/deploy-pages-secrets.sh +13 -70
  17. package/scripts/deploy-primershear-emails.sh +7 -73
  18. package/scripts/update-markdown-versions.cjs +58 -1
  19. package/worker-configuration.d.ts +2 -1
  20. package/workers/audit-worker/package.json +13 -18
  21. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  22. package/workers/data-worker/package.json +13 -18
  23. package/workers/data-worker/wrangler.jsonc.example +1 -1
  24. package/workers/image-worker/package.json +13 -18
  25. package/workers/image-worker/wrangler.jsonc.example +1 -1
  26. package/workers/pdf-worker/package.json +14 -19
  27. package/workers/pdf-worker/src/audit-trail-report.ts +28 -6
  28. package/workers/pdf-worker/src/report-types.ts +1 -0
  29. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  30. package/workers/user-worker/package.json +13 -18
  31. package/workers/user-worker/wrangler.jsonc.example +1 -1
  32. package/wrangler.toml.example +1 -1
@@ -4,16 +4,7 @@
4
4
  # PRIMERSHEAR EMAIL LIST DEPLOYMENT SCRIPT
5
5
  # ============================================
6
6
  # Reads primershear.emails, updates PRIMERSHEAR_EMAILS in .env,
7
- # then deploys that secret directly to Cloudflare Pages.
8
- #
9
- # Usage:
10
- # bash ./scripts/deploy-primershear-emails.sh [--production-only|--preview-only|--env-only]
11
- #
12
- # Options:
13
- # --production-only Deploy to production Pages environment only
14
- # --preview-only Deploy to preview Pages environment only
15
- # --env-only Update .env only; do not deploy to Cloudflare
16
- # -h, --help Show this help message
7
+ # then deploys that secret directly to Cloudflare Pages (production).
17
8
 
18
9
  set -e
19
10
  set -o pipefail
@@ -33,43 +24,6 @@ cd "$PROJECT_ROOT"
33
24
 
34
25
  trap 'echo -e "\n${RED}❌ deploy-primershear-emails.sh failed near line ${LINENO}${NC}"' ERR
35
26
 
36
- # ── Argument parsing ─────────────────────────────────────────────────────────
37
-
38
- deploy_production=true
39
- deploy_preview=true
40
- env_only=false
41
-
42
- for arg in "$@"; do
43
- case "$arg" in
44
- -h|--help)
45
- echo "Usage: bash ./scripts/deploy-primershear-emails.sh [--production-only|--preview-only|--env-only]"
46
- echo ""
47
- echo "Options:"
48
- echo " --production-only Deploy to production Pages environment only"
49
- echo " --preview-only Deploy to preview Pages environment only"
50
- echo " --env-only Update .env only; do not deploy to Cloudflare"
51
- echo " -h, --help Show this help message"
52
- exit 0
53
- ;;
54
- --production-only)
55
- deploy_production=true
56
- deploy_preview=false
57
- ;;
58
- --preview-only)
59
- deploy_production=false
60
- deploy_preview=true
61
- ;;
62
- --env-only)
63
- env_only=true
64
- ;;
65
- *)
66
- echo -e "${RED}❌ Unknown option: $arg${NC}"
67
- echo "Use --help to see supported options."
68
- exit 1
69
- ;;
70
- esac
71
- done
72
-
73
27
  # ── Read emails file ──────────────────────────────────────────────────────────
74
28
 
75
29
  EMAILS_FILE="$PROJECT_ROOT/primershear.emails"
@@ -114,11 +68,6 @@ else
114
68
  echo -e "${GREEN}✅ Appended PRIMERSHEAR_EMAILS to .env${NC}"
115
69
  fi
116
70
 
117
- if [ "$env_only" = "true" ]; then
118
- echo -e "\n${GREEN}🎉 .env updated. Skipping Cloudflare deployment (--env-only).${NC}"
119
- exit 0
120
- fi
121
-
122
71
  # ── Deploy to Cloudflare Pages ────────────────────────────────────────────────
123
72
 
124
73
  if ! command -v wrangler > /dev/null 2>&1; then
@@ -134,31 +83,16 @@ if [ -z "$PAGES_PROJECT_NAME" ]; then
134
83
  exit 1
135
84
  fi
136
85
 
137
- set_secret() {
138
- local pages_env=$1
139
- echo -e "${YELLOW} Setting PRIMERSHEAR_EMAILS for $pages_env...${NC}"
140
- if [ "$pages_env" = "production" ]; then
141
- printf '%s' "$PRIMERSHEAR_EMAILS" | wrangler pages secret put PRIMERSHEAR_EMAILS \
142
- --project-name "$PAGES_PROJECT_NAME"
143
- else
144
- printf '%s' "$PRIMERSHEAR_EMAILS" | wrangler pages secret put PRIMERSHEAR_EMAILS \
145
- --project-name "$PAGES_PROJECT_NAME" --env "$pages_env"
146
- fi
147
- }
148
-
149
- if [ "$deploy_production" = "true" ]; then
150
- set_secret "production"
151
- echo -e "${GREEN}✅ PRIMERSHEAR_EMAILS deployed to production${NC}"
152
- fi
86
+ echo -e "${YELLOW} Setting PRIMERSHEAR_EMAILS for production...${NC}"
87
+ printf '%s' "$PRIMERSHEAR_EMAILS" | wrangler pages secret put PRIMERSHEAR_EMAILS \
88
+ --project-name "$PAGES_PROJECT_NAME"
153
89
 
154
- if [ "$deploy_preview" = "true" ]; then
155
- set_secret "preview"
156
- echo -e "${GREEN}✅ PRIMERSHEAR_EMAILS deployed to preview${NC}"
157
- fi
90
+ echo -e "${GREEN}✅ PRIMERSHEAR_EMAILS deployed to production${NC}"
158
91
 
159
92
  # Deploy Pages so the new secret takes effect immediately
160
93
  echo -e "\n${YELLOW}🚀 Building and deploying Pages to activate new secret...${NC}"
161
- if ! npm run deploy; then
94
+
95
+ if ! npm run deploy-pages; then
162
96
  echo -e "${RED}❌ Pages deployment failed${NC}"
163
97
  exit 1
164
98
  fi
@@ -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
@@ -58,6 +58,7 @@ declare namespace Cloudflare {
58
58
  PDF_WORKER_AUTH: string;
59
59
  BROWSER_API_TOKEN: string;
60
60
  PRIMERSHEAR_EMAILS: string;
61
+ REGISTRATION_EMAILS: string;
61
62
  }
62
63
  }
63
64
  interface Env extends Cloudflare.Env {}
@@ -65,7 +66,7 @@ type StringifyValues<EnvType extends Record<string, unknown>> = {
65
66
  [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string;
66
67
  };
67
68
  declare namespace NodeJS {
68
- interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "ACCOUNT_ID" | "USER_DB_AUTH" | "R2_KEY_SECRET" | "IMAGES_API_TOKEN" | "API_KEY" | "AUTH_DOMAIN" | "PROJECT_ID" | "STORAGE_BUCKET" | "MESSAGING_SENDER_ID" | "APP_ID" | "MEASUREMENT_ID" | "FIREBASE_SERVICE_ACCOUNT_EMAIL" | "FIREBASE_SERVICE_ACCOUNT_PRIVATE_KEY" | "USER_KV_ENCRYPTION_PRIVATE_KEY" | "USER_KV_ENCRYPTION_KEY_ID" | "USER_KV_ENCRYPTION_PUBLIC_KEY" | "USER_KV_WRITE_ENDPOINTS_ENABLED" | "USER_KV_ENCRYPTION_KEYS_JSON" | "USER_KV_ENCRYPTION_ACTIVE_KEY_ID" | "MANIFEST_SIGNING_PRIVATE_KEY" | "MANIFEST_SIGNING_KEY_ID" | "MANIFEST_SIGNING_PUBLIC_KEY" | "EXPORT_ENCRYPTION_PRIVATE_KEY" | "EXPORT_ENCRYPTION_KEY_ID" | "EXPORT_ENCRYPTION_PUBLIC_KEY" | "EXPORT_ENCRYPTION_KEYS_JSON" | "EXPORT_ENCRYPTION_ACTIVE_KEY_ID" | "DATA_AT_REST_ENCRYPTION_ENABLED" | "DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" | "DATA_AT_REST_ENCRYPTION_KEY_ID" | "DATA_AT_REST_ENCRYPTION_PUBLIC_KEY" | "DATA_AT_REST_ENCRYPTION_KEYS_JSON" | "DATA_AT_REST_ENCRYPTION_ACTIVE_KEY_ID" | "PAGES_PROJECT_NAME" | "PAGES_CUSTOM_DOMAIN" | "USER_WORKER_NAME" | "USER_WORKER_DOMAIN" | "KV_STORE_ID" | "DATA_WORKER_NAME" | "DATA_BUCKET_NAME" | "FILES_BUCKET_NAME" | "DATA_WORKER_DOMAIN" | "AUDIT_WORKER_NAME" | "AUDIT_BUCKET_NAME" | "AUDIT_WORKER_DOMAIN" | "IMAGES_WORKER_NAME" | "IMAGES_WORKER_DOMAIN" | "IMAGE_SIGNED_URL_SECRET" | "IMAGE_SIGNED_URL_TTL_SECONDS" | "IMAGE_SIGNED_URL_BASE_URL" | "PDF_WORKER_NAME" | "PDF_WORKER_DOMAIN" | "PDF_WORKER_AUTH" | "BROWSER_API_TOKEN" | "PRIMERSHEAR_EMAILS">> {}
69
+ interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "ACCOUNT_ID" | "USER_DB_AUTH" | "R2_KEY_SECRET" | "IMAGES_API_TOKEN" | "API_KEY" | "AUTH_DOMAIN" | "PROJECT_ID" | "STORAGE_BUCKET" | "MESSAGING_SENDER_ID" | "APP_ID" | "MEASUREMENT_ID" | "FIREBASE_SERVICE_ACCOUNT_EMAIL" | "FIREBASE_SERVICE_ACCOUNT_PRIVATE_KEY" | "USER_KV_ENCRYPTION_PRIVATE_KEY" | "USER_KV_ENCRYPTION_KEY_ID" | "USER_KV_ENCRYPTION_PUBLIC_KEY" | "USER_KV_WRITE_ENDPOINTS_ENABLED" | "USER_KV_ENCRYPTION_KEYS_JSON" | "USER_KV_ENCRYPTION_ACTIVE_KEY_ID" | "MANIFEST_SIGNING_PRIVATE_KEY" | "MANIFEST_SIGNING_KEY_ID" | "MANIFEST_SIGNING_PUBLIC_KEY" | "EXPORT_ENCRYPTION_PRIVATE_KEY" | "EXPORT_ENCRYPTION_KEY_ID" | "EXPORT_ENCRYPTION_PUBLIC_KEY" | "EXPORT_ENCRYPTION_KEYS_JSON" | "EXPORT_ENCRYPTION_ACTIVE_KEY_ID" | "DATA_AT_REST_ENCRYPTION_ENABLED" | "DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" | "DATA_AT_REST_ENCRYPTION_KEY_ID" | "DATA_AT_REST_ENCRYPTION_PUBLIC_KEY" | "DATA_AT_REST_ENCRYPTION_KEYS_JSON" | "DATA_AT_REST_ENCRYPTION_ACTIVE_KEY_ID" | "PAGES_PROJECT_NAME" | "PAGES_CUSTOM_DOMAIN" | "USER_WORKER_NAME" | "USER_WORKER_DOMAIN" | "KV_STORE_ID" | "DATA_WORKER_NAME" | "DATA_BUCKET_NAME" | "FILES_BUCKET_NAME" | "DATA_WORKER_DOMAIN" | "AUDIT_WORKER_NAME" | "AUDIT_BUCKET_NAME" | "AUDIT_WORKER_DOMAIN" | "IMAGES_WORKER_NAME" | "IMAGES_WORKER_DOMAIN" | "IMAGE_SIGNED_URL_SECRET" | "IMAGE_SIGNED_URL_TTL_SECONDS" | "IMAGE_SIGNED_URL_BASE_URL" | "PDF_WORKER_NAME" | "PDF_WORKER_DOMAIN" | "PDF_WORKER_AUTH" | "BROWSER_API_TOKEN" | "PRIMERSHEAR_EMAILS" | "REGISTRATION_EMAILS">> {}
69
70
  }
70
71
 
71
72
  // Begin runtime types
@@ -1,18 +1,13 @@
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.81.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.2",
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
+ }
@@ -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-08",
10
+ "compatibility_date": "2026-04-10",
11
11
  "compatibility_flags": [
12
12
  "nodejs_compat"
13
13
  ],
@@ -1,18 +1,13 @@
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.81.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.2",
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
+ }
@@ -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-08",
8
+ "compatibility_date": "2026-04-10",
9
9
  "compatibility_flags": [
10
10
  "nodejs_compat"
11
11
  ],
@@ -1,18 +1,13 @@
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.81.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.2",
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
+ }
@@ -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-08",
5
+ "compatibility_date": "2026-04-10",
6
6
  "compatibility_flags": [
7
7
  "nodejs_compat"
8
8
  ],
@@ -1,19 +1,14 @@
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.81.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.2",
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
+ }
@@ -3,13 +3,34 @@ import { buildRepeatedChromePdfOptions, escapeHtml } from './report-layout';
3
3
 
4
4
  const safeText = (value: unknown): string => escapeHtml(String(value ?? ''));
5
5
 
6
- const formatTimestamp = (timestamp: string): string => {
6
+ const formatTimestamp = (timestamp: string, timezone?: string): string => {
7
7
  const parsed = new Date(timestamp);
8
8
  if (Number.isNaN(parsed.getTime())) {
9
9
  return timestamp;
10
10
  }
11
11
 
12
- return parsed.toISOString();
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
+ }
13
34
  };
14
35
 
15
36
  const renderEntryDetailsSummary = (entry: Record<string, unknown>): string => {
@@ -71,6 +92,7 @@ export const getAuditTrailPayload = (data: PDFGenerationData): AuditTrailReportP
71
92
  export const renderAuditTrailReport = (data: PDFGenerationData): string => {
72
93
  const payload = getAuditTrailPayload(data);
73
94
  const entries = payload.entries || [];
95
+ const timezone = data.userTimezone;
74
96
 
75
97
  const entrySections = entries.map((entry, index) => {
76
98
  const entryRecord = entry as Record<string, unknown>;
@@ -84,7 +106,7 @@ export const renderAuditTrailReport = (data: PDFGenerationData): string => {
84
106
  <section class="entry-section">
85
107
  <h3 class="entry-title">Entry ${index + 1} of ${entries.length}</h3>
86
108
  <div class="entry-core-grid">
87
- <div><strong>Timestamp:</strong> ${safeText(formatTimestamp(timestamp))}</div>
109
+ <div><strong>Timestamp:</strong> ${safeText(formatTimestamp(timestamp, timezone))}</div>
88
110
  <div><strong>Action:</strong> ${safeText(action)}</div>
89
111
  <div><strong>Result:</strong> ${safeText(result)}</div>
90
112
  <div><strong>User Email:</strong> ${safeText(userEmail)}</div>
@@ -195,9 +217,9 @@ export const renderAuditTrailReport = (data: PDFGenerationData): string => {
195
217
  <h1>Case Audit Trail Report</h1>
196
218
  <div class="summary-grid">
197
219
  <div><strong>Case Number:</strong> ${safeText(payload.caseNumber)}</div>
198
- <div><strong>Exported At:</strong> ${safeText(formatTimestamp(payload.exportedAt))}</div>
199
- <div><strong>Range Start:</strong> ${safeText(formatTimestamp(payload.exportRangeStart))}</div>
200
- <div><strong>Range End:</strong> ${safeText(formatTimestamp(payload.exportRangeEnd))}</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>
201
223
  <div><strong>Total Entries (All Parts):</strong> ${safeText(payload.totalEntries)}</div>
202
224
  <div><strong>This Part:</strong> ${safeText(payload.chunkIndex)} of ${safeText(payload.totalChunks)}</div>
203
225
  <div><strong>Entries in Part:</strong> ${safeText(entries.length)}</div>
@@ -60,6 +60,7 @@ export interface PDFGenerationData {
60
60
  userBadgeId?: string;
61
61
  reportMode?: 'audit-trail';
62
62
  auditTrailReport?: AuditTrailReportPayload;
63
+ userTimezone?: string;
63
64
  }
64
65
 
65
66
  export interface AuditTrailReportPayload {
@@ -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-08",
5
+ "compatibility_date": "2026-04-10",
6
6
  "compatibility_flags": [
7
7
  "nodejs_compat"
8
8
  ],
@@ -1,18 +1,13 @@
1
- {
2
- "name": "user-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.81.0"
13
- },
14
- "overrides": {
15
- "undici": "7.24.1",
16
- "yauzl": "3.2.1"
17
- }
18
- }
1
+ {
2
+ "name": "user-worker",
3
+ "version": "5.5.2",
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
+ }
@@ -2,7 +2,7 @@
2
2
  "name": "USER_WORKER_NAME",
3
3
  "account_id": "ACCOUNT_ID",
4
4
  "main": "src/user-worker.ts",
5
- "compatibility_date": "2026-04-08",
5
+ "compatibility_date": "2026-04-10",
6
6
  "compatibility_flags": [
7
7
  "nodejs_compat"
8
8
  ],
@@ -1,6 +1,6 @@
1
1
  #:schema node_modules/wrangler/config-schema.json
2
2
  name = "PAGES_PROJECT_NAME"
3
- compatibility_date = "2026-04-08"
3
+ compatibility_date = "2026-04-10"
4
4
  compatibility_flags = ["nodejs_compat"]
5
5
  pages_build_output_dir = "./build/client"
6
6