@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
@@ -5,6 +5,7 @@ import {
5
5
  listReadOnlyCases,
6
6
  deleteReadOnlyCase
7
7
  } from '~/components/actions/case-review';
8
+ import { listCases } from '~/components/actions/case-manage';
8
9
  import {
9
10
  type ImportResult,
10
11
  type ConfirmationImportResult
@@ -52,8 +53,9 @@ export const CaseImport = ({
52
53
  } = useImportState();
53
54
  const canDismissOverlay = !importState.isImporting && !importState.isClearing;
54
55
  const {
55
- handleOverlayMouseDown,
56
- handleOverlayKeyDown
56
+ requestClose,
57
+ overlayProps,
58
+ getCloseButtonProps
57
59
  } = useOverlayDismiss({
58
60
  isOpen,
59
61
  onClose,
@@ -61,11 +63,13 @@ export const CaseImport = ({
61
63
  });
62
64
 
63
65
  const [existingReadOnlyCase, setExistingReadOnlyCase] = useState<string | null>(null);
66
+ const [showArchivedRegularCaseRiskWarning, setShowArchivedRegularCaseRiskWarning] = useState(false);
64
67
  const fileInputRef = useRef<HTMLInputElement>(null);
65
68
 
66
69
  // Clear import selection state (used by preview hook on validation failure)
67
70
  const clearImportSelection = useCallback(() => {
68
71
  updateImportState({ selectedFile: null, importType: null });
72
+ setShowArchivedRegularCaseRiskWarning(false);
69
73
  resetFileInput(fileInputRef);
70
74
  }, [updateImportState]);
71
75
 
@@ -123,10 +127,21 @@ export const CaseImport = ({
123
127
  updateImportState({ isClearing: true });
124
128
 
125
129
  try {
126
- await deleteReadOnlyCase(user, existingReadOnlyCase);
127
-
128
130
  const clearedCaseName = existingReadOnlyCase;
129
- setExistingReadOnlyCase(null);
131
+ const deleteSuccess = await deleteReadOnlyCase(user, clearedCaseName);
132
+ const remainingReadOnlyCases = await listReadOnlyCases(user);
133
+ const stillExists = remainingReadOnlyCases.some((caseMeta) => caseMeta.caseNumber === clearedCaseName);
134
+
135
+ setExistingReadOnlyCase(remainingReadOnlyCases.length > 0 ? remainingReadOnlyCases[0].caseNumber : null);
136
+
137
+ if (!deleteSuccess || stillExists) {
138
+ setError(
139
+ `Failed to fully clear read-only case "${clearedCaseName}". ` +
140
+ 'Please try again. If this was an archived import that overlaps a regular case, verify that all case images are accessible before retrying.'
141
+ );
142
+ return;
143
+ }
144
+
130
145
  setSuccess(`Removed read-only case "${clearedCaseName}"`);
131
146
 
132
147
  onImportComplete?.({
@@ -236,8 +251,8 @@ export const CaseImport = ({
236
251
 
237
252
  const handleModalCancel = useCallback(() => {
238
253
  clearImportData();
239
- onClose();
240
- }, [clearImportData, onClose]);
254
+ requestClose();
255
+ }, [clearImportData, requestClose]);
241
256
 
242
257
  // Effects
243
258
  useEffect(() => {
@@ -246,6 +261,42 @@ export const CaseImport = ({
246
261
  }
247
262
  }, [user, isOpen, checkForExistingReadOnlyCase]);
248
263
 
264
+ useEffect(() => {
265
+ let isMounted = true;
266
+
267
+ const checkArchivedRegularCaseRisk = async () => {
268
+ if (
269
+ !user ||
270
+ !isOpen ||
271
+ importState.importType !== 'case' ||
272
+ !casePreview?.archived ||
273
+ !casePreview.caseNumber
274
+ ) {
275
+ if (isMounted) {
276
+ setShowArchivedRegularCaseRiskWarning(false);
277
+ }
278
+ return;
279
+ }
280
+
281
+ try {
282
+ const regularCases = await listCases(user);
283
+ if (isMounted) {
284
+ setShowArchivedRegularCaseRiskWarning(regularCases.includes(casePreview.caseNumber));
285
+ }
286
+ } catch {
287
+ if (isMounted) {
288
+ setShowArchivedRegularCaseRiskWarning(false);
289
+ }
290
+ }
291
+ };
292
+
293
+ void checkArchivedRegularCaseRisk();
294
+
295
+ return () => {
296
+ isMounted = false;
297
+ };
298
+ }, [user, isOpen, importState.importType, casePreview?.archived, casePreview?.caseNumber]);
299
+
249
300
  // Reset state when modal closes
250
301
  useEffect(() => {
251
302
  if (!isOpen) {
@@ -265,20 +316,15 @@ export const CaseImport = ({
265
316
  <>
266
317
  <div
267
318
  className={styles.overlay}
268
- onMouseDown={handleOverlayMouseDown}
269
- onKeyDown={handleOverlayKeyDown}
270
- role="button"
271
- tabIndex={0}
272
319
  aria-label="Close case import dialog"
320
+ {...overlayProps}
273
321
  >
274
322
  <div className={styles.modal}>
275
323
  <div className={styles.header}>
276
- <h2 className={styles.title}>Import RO Case or Confirmations</h2>
277
- <button
324
+ <h2 className={styles.title}>Import Case or Confirmations</h2>
325
+ <button
278
326
  className={styles.closeButton}
279
- onClick={onClose}
280
- aria-label="Close modal"
281
- disabled={importState.isImporting || importState.isClearing}
327
+ {...getCloseButtonProps({ ariaLabel: 'Close case import dialog' })}
282
328
  >
283
329
  ×
284
330
  </button>
@@ -316,6 +362,7 @@ export const CaseImport = ({
316
362
  <CasePreviewSection
317
363
  casePreview={casePreview}
318
364
  isLoadingPreview={importState.isLoadingPreview}
365
+ showArchivedRegularCaseRiskWarning={showArchivedRegularCaseRiskWarning}
319
366
  />
320
367
  )}
321
368
 
@@ -407,6 +454,7 @@ export const CaseImport = ({
407
454
  <ConfirmationDialog
408
455
  showConfirmation={importState.showConfirmation}
409
456
  casePreview={casePreview}
457
+ showArchivedRegularCaseRiskWarning={showArchivedRegularCaseRiskWarning}
410
458
  onConfirm={handleConfirmImport}
411
459
  onCancel={handleCancelImport}
412
460
  />
@@ -4,9 +4,14 @@ import styles from '../case-import.module.css';
4
4
  interface CasePreviewSectionProps {
5
5
  casePreview: CaseImportPreview | null;
6
6
  isLoadingPreview: boolean;
7
+ showArchivedRegularCaseRiskWarning?: boolean;
7
8
  }
8
9
 
9
- export const CasePreviewSection = ({ casePreview, isLoadingPreview }: CasePreviewSectionProps) => {
10
+ export const CasePreviewSection = ({
11
+ casePreview,
12
+ isLoadingPreview,
13
+ showArchivedRegularCaseRiskWarning = false
14
+ }: CasePreviewSectionProps) => {
10
15
  if (isLoadingPreview) {
11
16
  return (
12
17
  <div className={styles.previewSection}>
@@ -24,6 +29,16 @@ export const CasePreviewSection = ({ casePreview, isLoadingPreview }: CasePrevie
24
29
  {/* Case Information - Always Blue */}
25
30
  <div className={styles.previewSection}>
26
31
  <h3 className={styles.previewTitle}>Case Information</h3>
32
+ {casePreview.archived && (
33
+ <div className={styles.archivedImportNote}>
34
+ Archived export detected. Original exporter imports are allowed for archived cases.
35
+ </div>
36
+ )}
37
+ {showArchivedRegularCaseRiskWarning && (
38
+ <div className={styles.archivedRegularCaseRiskNote}>
39
+ Warning: This archived import matches a case already in your regular case list. If you later clear the imported read-only case, the regular case images will be deleted and become inaccessible.
40
+ </div>
41
+ )}
27
42
  <div className={styles.previewGrid}>
28
43
  <div className={styles.previewItem}>
29
44
  <span className={styles.previewLabel}>Case Number:</span>
@@ -49,6 +64,10 @@ export const CasePreviewSection = ({ casePreview, isLoadingPreview }: CasePrevie
49
64
  <span className={styles.previewLabel}>Total Images:</span>
50
65
  <span className={styles.previewValue}>{casePreview.totalFiles}</span>
51
66
  </div>
67
+ <div className={styles.previewItem}>
68
+ <span className={styles.previewLabel}>Archived Export:</span>
69
+ <span className={styles.previewValue}>{casePreview.archived ? 'Yes' : 'No'}</span>
70
+ </div>
52
71
  </div>
53
72
  </div>
54
73
 
@@ -4,6 +4,7 @@ import styles from '../case-import.module.css';
4
4
  interface ConfirmationDialogProps {
5
5
  showConfirmation: boolean;
6
6
  casePreview: CaseImportPreview | null;
7
+ showArchivedRegularCaseRiskWarning?: boolean;
7
8
  onConfirm: () => void;
8
9
  onCancel: () => void;
9
10
  }
@@ -11,6 +12,7 @@ interface ConfirmationDialogProps {
11
12
  export const ConfirmationDialog = ({
12
13
  showConfirmation,
13
14
  casePreview,
15
+ showArchivedRegularCaseRiskWarning = false,
14
16
  onConfirm,
15
17
  onCancel
16
18
  }: ConfirmationDialogProps) => {
@@ -41,6 +43,19 @@ export const ConfirmationDialog = ({
41
43
  <div className={styles.confirmationItem}>
42
44
  <strong>Total Images:</strong> {casePreview.totalFiles}
43
45
  </div>
46
+ <div className={styles.confirmationItem}>
47
+ <strong>Archived Export:</strong> {casePreview.archived ? 'Yes' : 'No'}
48
+ </div>
49
+ {casePreview.archived && (
50
+ <div className={styles.archivedImportNote}>
51
+ Archived export detected. Original exporter imports are allowed for archived cases.
52
+ </div>
53
+ )}
54
+ {showArchivedRegularCaseRiskWarning && (
55
+ <div className={styles.archivedRegularCaseRiskNote}>
56
+ Warning: This archived import matches a case in your regular case list. If you clear the imported read-only case later, the regular case images will be deleted and inaccessible.
57
+ </div>
58
+ )}
44
59
  {casePreview.hashValid !== undefined && (
45
60
  <div className={`${styles.confirmationItem} ${casePreview.hashValid ? styles.confirmationItemValid : styles.confirmationItemInvalid}`}>
46
61
  <strong>Data Integrity:</strong>