@striae-org/striae 4.1.0 → 4.2.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.
Files changed (124) hide show
  1. package/.env.example +8 -0
  2. package/LICENSE +1 -1
  3. package/app/components/actions/case-export/core-export.ts +14 -8
  4. package/app/components/actions/case-export/data-processing.ts +1 -0
  5. package/app/components/actions/case-export/download-handlers.ts +7 -0
  6. package/app/components/actions/case-export/metadata-helpers.ts +2 -1
  7. package/app/components/actions/case-import/confirmation-import.ts +12 -2
  8. package/app/components/actions/case-import/orchestrator.ts +78 -32
  9. package/app/components/actions/case-import/storage-operations.ts +97 -8
  10. package/app/components/actions/case-import/zip-processing.ts +159 -86
  11. package/app/components/actions/case-manage.ts +463 -8
  12. package/app/components/actions/confirm-export.ts +9 -2
  13. package/app/components/actions/image-manage.ts +77 -44
  14. package/app/components/audit/user-audit-viewer.tsx +19 -8
  15. package/app/components/audit/user-audit.module.css +21 -0
  16. package/app/components/audit/viewer/audit-entries-list.tsx +12 -2
  17. package/app/components/audit/viewer/audit-filters-panel.tsx +1 -0
  18. package/app/components/audit/viewer/audit-viewer-utils.ts +2 -0
  19. package/app/components/audit/viewer/use-audit-viewer-data.ts +24 -1
  20. package/app/components/audit/viewer/use-audit-viewer-export.ts +1 -1
  21. package/app/components/canvas/box-annotations/box-annotations.module.css +22 -18
  22. package/app/components/canvas/box-annotations/box-annotations.tsx +15 -0
  23. package/app/components/canvas/canvas.module.css +64 -54
  24. package/app/components/canvas/canvas.tsx +14 -16
  25. package/app/components/canvas/confirmation/confirmation.module.css +1 -0
  26. package/app/components/canvas/confirmation/confirmation.tsx +12 -14
  27. package/app/components/colors/colors.module.css +4 -3
  28. package/app/components/navbar/case-modals/archive-case-modal.module.css +110 -0
  29. package/app/components/navbar/case-modals/archive-case-modal.tsx +129 -0
  30. package/app/components/navbar/case-modals/open-case-modal.module.css +81 -0
  31. package/app/components/navbar/case-modals/open-case-modal.tsx +120 -0
  32. package/app/components/navbar/case-modals/rename-case-modal.module.css +81 -0
  33. package/app/components/navbar/case-modals/rename-case-modal.tsx +107 -0
  34. package/app/components/navbar/navbar.module.css +447 -0
  35. package/app/components/navbar/navbar.tsx +402 -0
  36. package/app/components/public-signing-key-modal/public-signing-key-modal.module.css +1 -0
  37. package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +15 -16
  38. package/app/components/sidebar/case-export/case-export.module.css +1 -0
  39. package/app/components/sidebar/case-export/case-export.tsx +8 -46
  40. package/app/components/sidebar/case-import/case-import.module.css +23 -0
  41. package/app/components/sidebar/case-import/case-import.tsx +64 -16
  42. package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +20 -1
  43. package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +15 -0
  44. package/app/components/sidebar/cases/case-sidebar.tsx +68 -588
  45. package/app/components/sidebar/cases/cases-modal.module.css +1 -0
  46. package/app/components/sidebar/cases/cases-modal.tsx +82 -43
  47. package/app/components/sidebar/cases/cases.module.css +82 -21
  48. package/app/components/sidebar/files/files-modal.module.css +1 -0
  49. package/app/components/sidebar/files/files-modal.tsx +49 -52
  50. package/app/components/sidebar/notes/addl-notes-modal.tsx +82 -0
  51. package/app/components/sidebar/notes/{notes-sidebar.tsx → notes-editor-form.tsx} +187 -138
  52. package/app/components/sidebar/notes/notes-editor-modal.module.css +49 -0
  53. package/app/components/sidebar/notes/notes-editor-modal.tsx +64 -0
  54. package/app/components/sidebar/notes/notes.module.css +170 -1
  55. package/app/components/sidebar/sidebar-container.tsx +16 -28
  56. package/app/components/sidebar/sidebar.module.css +5 -69
  57. package/app/components/sidebar/sidebar.tsx +27 -125
  58. package/app/components/sidebar/upload/image-upload-zone.module.css +13 -13
  59. package/app/components/user/inactivity-warning.module.css +1 -0
  60. package/app/components/user/inactivity-warning.tsx +15 -2
  61. package/app/components/user/manage-profile.tsx +23 -10
  62. package/app/{tailwind.css → global.css} +1 -3
  63. package/app/hooks/useOverlayDismiss.ts +54 -4
  64. package/app/root.tsx +1 -1
  65. package/app/routes/auth/login.tsx +785 -774
  66. package/app/routes/striae/striae.module.css +10 -3
  67. package/app/routes/striae/striae.tsx +475 -30
  68. package/app/services/audit/audit.service.ts +173 -27
  69. package/app/services/audit/builders/audit-event-builders-case-file.ts +43 -0
  70. package/app/services/audit/builders/audit-event-builders-workflow.ts +2 -0
  71. package/app/services/audit/builders/index.ts +1 -0
  72. package/app/types/audit.ts +4 -1
  73. package/app/types/case.ts +29 -0
  74. package/app/types/import.ts +3 -0
  75. package/app/utils/data/confirmation-summary/summary-core.ts +279 -0
  76. package/app/utils/data/data-operations.ts +17 -861
  77. package/app/utils/data/index.ts +11 -1
  78. package/app/utils/data/operations/batch-operations.ts +113 -0
  79. package/app/utils/data/operations/case-operations.ts +168 -0
  80. package/app/utils/data/operations/confirmation-summary-operations.ts +301 -0
  81. package/app/utils/data/operations/file-annotation-operations.ts +196 -0
  82. package/app/utils/data/operations/index.ts +7 -0
  83. package/app/utils/data/operations/signing-operations.ts +225 -0
  84. package/app/utils/data/operations/types.ts +42 -0
  85. package/app/utils/data/operations/validation-operations.ts +48 -0
  86. package/app/utils/data/permissions.ts +16 -1
  87. package/app/utils/forensics/audit-export-signature.ts +5 -1
  88. package/app/utils/forensics/confirmation-signature.ts +3 -0
  89. package/app/utils/forensics/export-verification.ts +426 -22
  90. package/functions/api/_shared/firebase-auth.ts +2 -7
  91. package/functions/api/image/[[path]].ts +20 -23
  92. package/functions/api/pdf/[[path]].ts +27 -8
  93. package/package.json +7 -12
  94. package/scripts/deploy-primershear-emails.sh +2 -1
  95. package/worker-configuration.d.ts +3 -3
  96. package/workers/audit-worker/package.json +1 -1
  97. package/workers/audit-worker/worker-configuration.d.ts +7448 -11323
  98. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  99. package/workers/data-worker/package.json +1 -1
  100. package/workers/data-worker/worker-configuration.d.ts +7448 -11323
  101. package/workers/data-worker/wrangler.jsonc.example +1 -1
  102. package/workers/image-worker/package.json +1 -1
  103. package/workers/image-worker/src/image-worker.example.ts +16 -5
  104. package/workers/image-worker/worker-configuration.d.ts +7447 -11322
  105. package/workers/image-worker/wrangler.jsonc.example +1 -1
  106. package/workers/keys-worker/package.json +1 -1
  107. package/workers/keys-worker/worker-configuration.d.ts +7447 -11322
  108. package/workers/keys-worker/wrangler.jsonc.example +1 -1
  109. package/workers/pdf-worker/package.json +1 -1
  110. package/workers/pdf-worker/src/formats/format-striae.ts +9 -14
  111. package/workers/pdf-worker/src/pdf-worker.example.ts +37 -58
  112. package/workers/pdf-worker/src/report-types.ts +3 -3
  113. package/workers/pdf-worker/worker-configuration.d.ts +7448 -11323
  114. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  115. package/workers/user-worker/package.json +1 -1
  116. package/workers/user-worker/src/user-worker.example.ts +17 -0
  117. package/workers/user-worker/worker-configuration.d.ts +7448 -11323
  118. package/workers/user-worker/wrangler.jsonc.example +1 -1
  119. package/wrangler.toml.example +1 -1
  120. package/NOTICE +0 -13
  121. package/app/components/sidebar/notes/notes-modal.tsx +0 -53
  122. package/postcss.config.js +0 -6
  123. package/public/.well-known/keybase.txt +0 -56
  124. package/tailwind.config.ts +0 -22
@@ -2,7 +2,7 @@
2
2
  "name": "KEYS_WORKER_NAME",
3
3
  "account_id": "ACCOUNT_ID",
4
4
  "main": "src/keys.ts",
5
- "compatibility_date": "2026-03-19",
5
+ "compatibility_date": "2026-03-21",
6
6
  "compatibility_flags": [
7
7
  "nodejs_compat"
8
8
  ],
@@ -13,7 +13,7 @@
13
13
  "@cloudflare/puppeteer": "^1.0.6",
14
14
  "@cloudflare/vitest-pool-workers": "^0.13.0",
15
15
  "vitest": "~4.1.0",
16
- "wrangler": "^4.73.0"
16
+ "wrangler": "^4.76.0"
17
17
  },
18
18
  "overrides": {
19
19
  "undici": "7.24.1",
@@ -63,12 +63,6 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
63
63
  return luminance < 0.5;
64
64
  };
65
65
 
66
- // Use passed currentDate or generate fallback
67
- const displayDate = currentDate || (() => {
68
- const now = new Date();
69
- return `${(now.getMonth() + 1).toString().padStart(2, '0')}/${now.getDate().toString().padStart(2, '0')}/${now.getFullYear()}`;
70
- })();
71
-
72
66
  return `
73
67
  <!DOCTYPE html>
74
68
  <html lang="en">
@@ -146,7 +140,7 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
146
140
  .left-annotation,
147
141
  .right-annotation {
148
142
  position: absolute;
149
- padding: 12px 16px;
143
+ padding: 0.75rem 1rem;
150
144
  background: rgba(0, 0, 0, 0.7);
151
145
  border-radius: 6px;
152
146
  backdrop-filter: blur(4px);
@@ -154,16 +148,16 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
154
148
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
155
149
  }
156
150
  .left-annotation {
157
- top: 2%;
158
- left: 4%;
151
+ top: 1rem;
152
+ left: 1rem;
159
153
  }
160
154
  .right-annotation {
161
- top: 2%;
162
- right: 4%;
155
+ top: 1rem;
156
+ right: 1rem;
163
157
  }
164
158
  .case-text {
165
159
  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
166
- font-size: 18px;
160
+ font-size: 1rem;
167
161
  font-weight: 700;
168
162
  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
169
163
  white-space: nowrap;
@@ -242,8 +236,9 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
242
236
  .confirmation-section {
243
237
  margin-top: 20px;
244
238
  display: flex;
245
- justify-content: space-between;
239
+ justify-content: flex-start;
246
240
  align-items: flex-start;
241
+ gap: 20px;
247
242
  }
248
243
  .confirmation-box {
249
244
  background: #ffffff;
@@ -404,7 +399,7 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
404
399
  <div class="content-wrapper">
405
400
  <div class="header">
406
401
  <div class="header-content">
407
- <div class="date">${displayDate}</div>
402
+ <div class="date">${currentDate}</div>
408
403
  ${caseNumber ? `<div class="case-number">${caseNumber}</div>` : '<div class="case-number"></div>'}
409
404
  </div>
410
405
  </div>
@@ -3,13 +3,10 @@ import type { PDFGenerationData, PDFGenerationRequest, ReportModule } from './re
3
3
  interface Env {
4
4
  BROWSER: Fetcher;
5
5
  PDF_WORKER_AUTH: string;
6
- ACCOUNT_ID?: string;
7
- CLOUDFLARE_ACCOUNT_ID?: string;
8
- BROWSER_API_TOKEN?: string;
9
- API_TOKEN?: string;
6
+ ACCOUNT_ID: string;
7
+ BROWSER_API_TOKEN: string;
10
8
  }
11
9
 
12
- const DEFAULT_REPORT_FORMAT = 'striae';
13
10
  const BROWSER_PDF_TIMEOUT_MS = 90_000;
14
11
  const BROWSER_RENDERING_API_BASE = 'https://api.cloudflare.com/client/v4/accounts';
15
12
 
@@ -56,62 +53,47 @@ function jsonResponse(body: unknown, status: number): Response {
56
53
  });
57
54
  }
58
55
 
59
- function resolveBrowserApiToken(env: Env): string {
60
- const candidates = [env.BROWSER_API_TOKEN, env.API_TOKEN];
56
+ class MissingSecretError extends Error {
57
+ readonly secretKey: string;
61
58
 
62
- for (const candidate of candidates) {
63
- if (typeof candidate === 'string' && candidate.trim().length > 0) {
64
- return candidate.trim();
65
- }
59
+ constructor(key: string) {
60
+ super(`Worker is missing required secret: ${key}`);
61
+ this.name = 'MissingSecretError';
62
+ this.secretKey = key;
66
63
  }
67
-
68
- return '';
69
64
  }
70
65
 
71
- function resolveAccountId(env: Env): string {
72
- const candidates = [env.ACCOUNT_ID, env.CLOUDFLARE_ACCOUNT_ID];
73
-
74
- for (const candidate of candidates) {
75
- if (typeof candidate === 'string' && candidate.trim().length > 0) {
76
- return candidate.trim();
77
- }
66
+ function getRequiredSecret(value: string, name: string): string {
67
+ const normalized = value.trim();
68
+ if (!normalized) {
69
+ throw new MissingSecretError(name);
78
70
  }
79
71
 
80
- return '';
72
+ return normalized;
81
73
  }
82
74
 
83
- function normalizeReportFormat(format: unknown): string {
84
- if (typeof format !== 'string') {
85
- return DEFAULT_REPORT_FORMAT;
86
- }
87
-
88
- const normalized = format.trim().toLowerCase();
89
- return normalized || DEFAULT_REPORT_FORMAT;
90
- }
91
-
92
- function resolveReportRequest(payload: unknown): { reportFormat: string; data: PDFGenerationData } {
93
- if (!payload || typeof payload !== 'object') {
75
+ function resolveReportRequest(payload: unknown): PDFGenerationRequest {
76
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
94
77
  throw new Error('Request body must be a JSON object');
95
78
  }
96
79
 
97
80
  const record = payload as Record<string, unknown>;
98
- const reportFormat = normalizeReportFormat(record.reportFormat);
81
+ if (typeof record.reportFormat !== 'string' || record.reportFormat.trim().length === 0) {
82
+ throw new Error('Request body must include a non-empty reportFormat');
83
+ }
99
84
 
100
- if (record.data && typeof record.data === 'object') {
101
- return {
102
- reportFormat,
103
- data: record.data as PDFGenerationData,
104
- };
85
+ if (!record.data || typeof record.data !== 'object' || Array.isArray(record.data)) {
86
+ throw new Error('Request body must include a data object');
105
87
  }
106
88
 
107
- // Backward compatibility: accept legacy top-level payload shape.
108
- const legacyData: Record<string, unknown> = { ...record };
109
- delete legacyData.reportFormat;
110
- delete legacyData.data;
89
+ const data = record.data as Record<string, unknown>;
90
+ if (typeof data.currentDate !== 'string' || data.currentDate.trim().length === 0) {
91
+ throw new Error('Request body data must include a non-empty currentDate');
92
+ }
111
93
 
112
94
  return {
113
- reportFormat,
114
- data: legacyData as PDFGenerationData,
95
+ reportFormat: record.reportFormat.trim().toLowerCase(),
96
+ data: record.data as PDFGenerationData,
115
97
  };
116
98
  }
117
99
 
@@ -128,19 +110,8 @@ async function renderReport(reportFormat: string, data: PDFGenerationData): Prom
128
110
  }
129
111
 
130
112
  async function renderPdfViaRestEndpoint(env: Env, html: string): Promise<Response> {
131
- const accountId = resolveAccountId(env);
132
- const browserApiToken = resolveBrowserApiToken(env);
133
-
134
- if (!accountId || !browserApiToken) {
135
- return jsonResponse(
136
- {
137
- error: 'Missing required Browser Rendering credentials',
138
- requiredSecrets: ['ACCOUNT_ID', 'BROWSER_API_TOKEN'],
139
- note: 'Set ACCOUNT_ID and a Browser Rendering - Edit token (BROWSER_API_TOKEN) on this worker.',
140
- },
141
- 502
142
- );
143
- }
113
+ const accountId = getRequiredSecret(env.ACCOUNT_ID, 'ACCOUNT_ID');
114
+ const browserApiToken = getRequiredSecret(env.BROWSER_API_TOKEN, 'BROWSER_API_TOKEN');
144
115
 
145
116
  const endpoint = `${BROWSER_RENDERING_API_BASE}/${accountId}/browser-rendering/pdf`;
146
117
  const requestBody = JSON.stringify({
@@ -217,12 +188,20 @@ export default {
217
188
 
218
189
  if (request.method === 'POST') {
219
190
  try {
220
- const payload = await request.json() as PDFGenerationData | PDFGenerationRequest;
191
+ const payload = await request.json() as unknown;
221
192
  const { reportFormat, data } = resolveReportRequest(payload);
222
193
  const document = await renderReport(reportFormat, data);
223
194
 
224
195
  return await renderPdfViaRestEndpoint(env, document);
225
196
  } catch (error) {
197
+ if (error instanceof MissingSecretError) {
198
+ console.error(`[pdf-worker] Configuration error: ${error.message}`);
199
+ return jsonResponse(
200
+ { error: 'Worker configuration error', missing_secret: error.secretKey },
201
+ 502
202
+ );
203
+ }
204
+
226
205
  if (isTimeoutError(error)) {
227
206
  const timeoutMessage = error instanceof Error ? error.message : 'PDF generation timed out';
228
207
  return jsonResponse({ error: timeoutMessage }, 504);
@@ -52,7 +52,7 @@ export interface PDFGenerationData {
52
52
  caseNumber?: string;
53
53
  annotationData?: AnnotationData;
54
54
  activeAnnotations?: string[];
55
- currentDate?: string;
55
+ currentDate: string;
56
56
  notesUpdatedFormatted?: string;
57
57
  userCompany?: string;
58
58
  userFirstName?: string;
@@ -61,8 +61,8 @@ export interface PDFGenerationData {
61
61
  }
62
62
 
63
63
  export interface PDFGenerationRequest {
64
- reportFormat?: string;
65
- data?: PDFGenerationData;
64
+ reportFormat: string;
65
+ data: PDFGenerationData;
66
66
  }
67
67
 
68
68
  export type ReportRenderer = (data: PDFGenerationData) => string;