@striae-org/striae 6.0.1 → 6.1.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 (43) hide show
  1. package/app/components/actions/case-export/core-export.ts +11 -2
  2. package/app/components/actions/case-export/download-handlers.ts +3 -1
  3. package/app/components/canvas/canvas.module.css +1 -1
  4. package/app/components/canvas/canvas.tsx +32 -11
  5. package/app/components/colors/colors.module.css +19 -0
  6. package/app/components/colors/colors.tsx +5 -1
  7. package/app/components/icon/icons.svg +1 -1
  8. package/app/components/icon/manifest.json +1 -1
  9. package/app/components/navbar/navbar.tsx +32 -16
  10. package/app/components/sidebar/cases/case-sidebar.tsx +27 -25
  11. package/app/components/sidebar/files/files-modal.tsx +39 -15
  12. package/app/components/sidebar/notes/addl-notes-modal.tsx +9 -2
  13. package/app/components/sidebar/notes/{class-details/class-details-fields.tsx → item-details/item-details-fields.tsx} +10 -10
  14. package/app/components/sidebar/notes/{class-details/class-details-modal.tsx → item-details/item-details-modal.tsx} +20 -22
  15. package/app/components/sidebar/notes/{class-details/class-details-sections.tsx → item-details/item-details-sections.tsx} +16 -16
  16. package/app/components/sidebar/notes/{class-details/class-details-shared.ts → item-details/item-details-shared.ts} +4 -3
  17. package/app/components/sidebar/notes/{class-details/use-class-details-state.ts → item-details/use-item-details-state.ts} +4 -4
  18. package/app/components/sidebar/notes/notes-editor-form.tsx +357 -146
  19. package/app/components/sidebar/notes/notes-editor-modal.tsx +3 -0
  20. package/app/components/sidebar/notes/notes.module.css +40 -20
  21. package/app/components/sidebar/sidebar-container.tsx +1 -1
  22. package/app/components/sidebar/sidebar.tsx +3 -3
  23. package/app/components/toolbar/toolbar.tsx +5 -5
  24. package/app/hooks/useFileListPreferences.ts +22 -17
  25. package/app/routes/striae/striae.tsx +6 -13
  26. package/app/types/annotations.ts +29 -5
  27. package/app/utils/data/confirmation-summary/summary-core.ts +40 -8
  28. package/app/utils/data/file-filters.ts +39 -17
  29. package/app/utils/data/permissions.ts +123 -0
  30. package/package.json +12 -12
  31. package/workers/audit-worker/package.json +2 -2
  32. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  33. package/workers/data-worker/package.json +2 -2
  34. package/workers/data-worker/wrangler.jsonc.example +1 -1
  35. package/workers/image-worker/package.json +2 -2
  36. package/workers/image-worker/wrangler.jsonc.example +1 -1
  37. package/workers/pdf-worker/package.json +2 -2
  38. package/workers/pdf-worker/src/formats/format-striae.ts +65 -8
  39. package/workers/pdf-worker/src/report-types.ts +18 -4
  40. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  41. package/workers/user-worker/package.json +2 -2
  42. package/workers/user-worker/wrangler.jsonc.example +1 -1
  43. package/wrangler.toml.example +1 -1
@@ -26,6 +26,16 @@ export interface CaseMetadata {
26
26
  createdAt: string;
27
27
  }
28
28
 
29
+ /**
30
+ * Result for notes viewing permissions
31
+ * Determines if notes can be viewed and in what mode (edit or view-only)
32
+ */
33
+ export interface NotesViewPermission {
34
+ canOpen: boolean; // Can the notes panel be opened
35
+ isReadOnly: boolean; // Are notes in read-only mode (can view but not edit)
36
+ reason?: string; // Reason if notes cannot be opened
37
+ }
38
+
29
39
  /**
30
40
  * Get user data from KV store
31
41
  */
@@ -565,4 +575,117 @@ export const removeUserCase = async (user: User, caseNumber: string): Promise<vo
565
575
  console.error('Error removing case from user:', error);
566
576
  throw error;
567
577
  }
578
+ };
579
+
580
+ // ============================================================================
581
+ // NOTES VIEW PERMISSIONS
582
+ // ============================================================================
583
+
584
+ /**
585
+ * Determine if notes can be opened and viewed, and whether they should be in read-only mode
586
+ *
587
+ * Notes can be viewed in the following scenarios:
588
+ * - Normal active case with unconfirmed image: Can edit and save
589
+ * - Active case with confirmed image: Can view only (read-only)
590
+ * - Read-only case (review/confirmation case): Can view only (read-only)
591
+ * - Archived case: Can view only (read-only)
592
+ *
593
+ * Notes cannot be opened when:
594
+ * - Files are uploading
595
+ * - Confirmation status is still being checked
596
+ * - No image is loaded
597
+ *
598
+ * @param config Configuration object with state flags
599
+ * @returns NotesViewPermission object indicating if notes can be opened and if they're read-only
600
+ */
601
+ export const getNotesViewPermission = (config: {
602
+ imageLoaded: boolean;
603
+ isUploading: boolean;
604
+ isCheckingConfirmation: boolean;
605
+ isReadOnlyCase?: boolean;
606
+ isArchivedCase?: boolean;
607
+ isConfirmedImage?: boolean;
608
+ }): NotesViewPermission => {
609
+ const {
610
+ imageLoaded,
611
+ isUploading,
612
+ isCheckingConfirmation,
613
+ isReadOnlyCase = false,
614
+ isArchivedCase = false,
615
+ isConfirmedImage = false
616
+ } = config;
617
+
618
+ // Cannot open if uploading files
619
+ if (isUploading) {
620
+ return {
621
+ canOpen: false,
622
+ isReadOnly: false,
623
+ reason: 'Cannot open notes while uploading'
624
+ };
625
+ }
626
+
627
+ // Cannot open if checking confirmation status
628
+ if (isCheckingConfirmation) {
629
+ return {
630
+ canOpen: false,
631
+ isReadOnly: false,
632
+ reason: 'Checking confirmation status...'
633
+ };
634
+ }
635
+
636
+ // Cannot open if no image is loaded
637
+ if (!imageLoaded) {
638
+ return {
639
+ canOpen: false,
640
+ isReadOnly: false,
641
+ reason: 'Select an image first'
642
+ };
643
+ }
644
+
645
+ // Can open, determine if read-only
646
+ const isReadOnly = isConfirmedImage || isReadOnlyCase || isArchivedCase;
647
+
648
+ return {
649
+ canOpen: true,
650
+ isReadOnly
651
+ };
652
+ };
653
+
654
+ /**
655
+ * Get a user-friendly tooltip message for the Image Notes button
656
+ *
657
+ * This centralizes all the tooltip logic that appears in the navbar and sidebar
658
+ *
659
+ * @param permission NotesViewPermission object from getNotesViewPermission
660
+ * @param additionalContext Optional context for more specific messages
661
+ * @returns Tooltip string, or undefined if button has no specific tooltip
662
+ */
663
+ export const getNotesButtonTooltip = (
664
+ permission: NotesViewPermission,
665
+ additionalContext?: {
666
+ isReadOnlyCase?: boolean;
667
+ isArchivedCase?: boolean;
668
+ isConfirmedImage?: boolean;
669
+ }
670
+ ): string | undefined => {
671
+ // If cannot open, return the reason
672
+ if (!permission.canOpen) {
673
+ return permission.reason;
674
+ }
675
+
676
+ // Can open - provide context-specific read-only messages
677
+ if (permission.isReadOnly && additionalContext) {
678
+ if (additionalContext.isConfirmedImage) {
679
+ return 'Image notes: viewing only (image is confirmed)';
680
+ }
681
+ if (additionalContext.isReadOnlyCase) {
682
+ return 'Image notes: viewing only (case is read-only)';
683
+ }
684
+ if (additionalContext.isArchivedCase) {
685
+ return 'Image notes: viewing only (case is archived)';
686
+ }
687
+ }
688
+
689
+ // No tooltip needed for normal edit mode
690
+ return undefined;
568
691
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@striae-org/striae",
3
- "version": "6.0.1",
3
+ "version": "6.1.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",
@@ -50,12 +50,12 @@
50
50
  "workers/pdf-worker/src/assets/generated-assets.example.ts",
51
51
  "!workers/pdf-worker/src/formats/**/*",
52
52
  "workers/pdf-worker/src/formats/format-striae.ts",
53
- ".env.example",
53
+ ".env.example",
54
54
  "firebase.json",
55
55
  "tsconfig.json",
56
56
  "vite.config.ts",
57
57
  "/worker-configuration.d.ts",
58
- "wrangler.toml.example",
58
+ "wrangler.toml.example",
59
59
  "LICENSE"
60
60
  ],
61
61
  "sideEffects": false,
@@ -100,23 +100,23 @@
100
100
  "deploy-workers:user": "cd workers/user-worker && npm run deploy"
101
101
  },
102
102
  "dependencies": {
103
- "@react-router/cloudflare": "^7.14.0",
103
+ "@react-router/cloudflare": "^7.14.1",
104
104
  "firebase": "^12.12.0",
105
- "isbot": "^5.1.37",
105
+ "isbot": "^5.1.38",
106
106
  "jszip": "^3.10.1",
107
107
  "qrcode": "^1.5.4",
108
108
  "react": "^19.2.5",
109
109
  "react-dom": "^19.2.5",
110
- "react-router": "^7.14.0"
110
+ "react-router": "^7.14.1"
111
111
  },
112
112
  "devDependencies": {
113
- "@react-router/dev": "^7.14.0",
114
- "@react-router/fs-routes": "^7.14.0",
113
+ "@react-router/dev": "^7.14.1",
114
+ "@react-router/fs-routes": "^7.14.1",
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.1",
119
- "@typescript-eslint/parser": "^8.58.1",
118
+ "@typescript-eslint/eslint-plugin": "^8.58.2",
119
+ "@typescript-eslint/parser": "^8.58.2",
120
120
  "eslint": "^9.39.4",
121
121
  "eslint-import-resolver-typescript": "^4.4.4",
122
122
  "eslint-plugin-import": "^2.32.0",
@@ -128,7 +128,7 @@
128
128
  "typescript": "^5.9.3",
129
129
  "vite": "^7.3.2",
130
130
  "vite-tsconfig-paths": "^6.1.1",
131
- "wrangler": "^4.81.1"
131
+ "wrangler": "^4.83.0"
132
132
  },
133
133
  "overrides": {
134
134
  "@tootallnate/once": "3.0.1"
@@ -136,5 +136,5 @@
136
136
  "engines": {
137
137
  "node": ">=20.19.0"
138
138
  },
139
- "packageManager": "npm@11.11.0"
139
+ "packageManager": "npm@11.12.0"
140
140
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "audit-worker",
3
- "version": "6.0.1",
3
+ "version": "6.1.1",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "deploy": "wrangler deploy",
@@ -8,6 +8,6 @@
8
8
  "start": "wrangler dev"
9
9
  },
10
10
  "devDependencies": {
11
- "wrangler": "^4.81.1"
11
+ "wrangler": "^4.83.0"
12
12
  }
13
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-12",
10
+ "compatibility_date": "2026-04-15",
11
11
  "compatibility_flags": [
12
12
  "nodejs_compat"
13
13
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "data-worker",
3
- "version": "6.0.1",
3
+ "version": "6.1.1",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "deploy": "wrangler deploy",
@@ -8,6 +8,6 @@
8
8
  "start": "wrangler dev"
9
9
  },
10
10
  "devDependencies": {
11
- "wrangler": "^4.81.1"
11
+ "wrangler": "^4.83.0"
12
12
  }
13
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-12",
8
+ "compatibility_date": "2026-04-15",
9
9
  "compatibility_flags": [
10
10
  "nodejs_compat"
11
11
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "image-worker",
3
- "version": "6.0.1",
3
+ "version": "6.1.1",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "deploy": "wrangler deploy",
@@ -8,6 +8,6 @@
8
8
  "start": "wrangler dev"
9
9
  },
10
10
  "devDependencies": {
11
- "wrangler": "^4.81.1"
11
+ "wrangler": "^4.83.0"
12
12
  }
13
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-12",
5
+ "compatibility_date": "2026-04-15",
6
6
  "compatibility_flags": [
7
7
  "nodejs_compat"
8
8
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdf-worker",
3
- "version": "6.0.1",
3
+ "version": "6.1.1",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "generate:assets": "node scripts/generate-assets.js",
@@ -9,6 +9,6 @@
9
9
  "start": "wrangler dev"
10
10
  },
11
11
  "devDependencies": {
12
- "wrangler": "^4.81.1"
12
+ "wrangler": "^4.83.0"
13
13
  }
14
14
  }
@@ -7,6 +7,14 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
7
7
  const annotationsSet = new Set(activeAnnotations);
8
8
  const hasImage = Boolean(imageUrl && imageUrl !== '/clear.jpg');
9
9
  const safeText = (value: unknown): string => escapeHtml(String(value ?? ''));
10
+ const leftAdditionalNotes = annotationData?.leftAdditionalNotes?.trim() || '';
11
+ const rightAdditionalNotes = annotationData?.rightAdditionalNotes?.trim() || '';
12
+ const generalAdditionalNotes = annotationData?.additionalNotes?.trim() || '';
13
+ const hasSideAdditionalNotes = Boolean(leftAdditionalNotes || rightAdditionalNotes);
14
+ const hasGeneralAdditionalNotes = Boolean(generalAdditionalNotes);
15
+ const hasAdditionalNotes = hasSideAdditionalNotes || hasGeneralAdditionalNotes;
16
+ const hasConfirmationOrNotes = Boolean(annotationData && ((annotationData.includeConfirmation === true) || hasAdditionalNotes));
17
+ const notesShouldStartNewPage = hasImage || annotationData?.includeConfirmation === true;
10
18
 
11
19
  // Programmatically determine if a color is dark and needs a light background
12
20
  const needsLightBackground = (color: string | undefined): boolean => {
@@ -323,6 +331,15 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
323
331
  font-family: 'Inter', Arial, sans-serif;
324
332
  color: #333;
325
333
  }
334
+ .additional-notes-grid {
335
+ display: flex;
336
+ align-items: stretch;
337
+ gap: 12px;
338
+ }
339
+ .additional-notes-section--half {
340
+ flex: 1 1 50%;
341
+ width: 50%;
342
+ }
326
343
  .additional-notes-title {
327
344
  margin: 0 0 10px;
328
345
  font-size: 12px;
@@ -415,15 +432,31 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
415
432
  </div>
416
433
  ` : '<div class="support-level-annotation"></div>'}
417
434
 
418
- ${annotationData && annotationsSet?.has('class') ? `
435
+ ${annotationData && annotationsSet?.has('item') ? `
419
436
  <div class="class-annotation">
420
437
  <div class="class-text-annotation">
421
- ${safeText(annotationData.customClass || annotationData.classType)}${annotationData.classNote ? ` (${safeText(annotationData.classNote)})` : ''}
438
+ ${(() => {
439
+ const leftValue = annotationData.leftCustomClass || annotationData.leftItemType;
440
+ const rightValue = annotationData.rightCustomClass || annotationData.rightItemType;
441
+ const legacyValue = annotationData.customClass || annotationData.itemType || annotationData.classType;
442
+ const displayValue =
443
+ leftValue && rightValue && leftValue !== rightValue
444
+ ? `${leftValue} / ${rightValue}`
445
+ : leftValue || rightValue || legacyValue;
446
+ const leftClassNote = annotationData.leftClassNote?.trim();
447
+ const rightClassNote = annotationData.rightClassNote?.trim();
448
+ const legacyClassNote = annotationData.classNote?.trim();
449
+ const displayClassNote =
450
+ leftClassNote && rightClassNote && leftClassNote !== rightClassNote
451
+ ? `${leftClassNote} / ${rightClassNote}`
452
+ : leftClassNote || rightClassNote || legacyClassNote;
453
+ return safeText(displayValue || '') + (displayClassNote ? ` (${safeText(displayClassNote)})` : '');
454
+ })()}
422
455
  </div>
423
456
  </div>
424
457
  ` : '<div class="class-annotation"></div>'}
425
458
 
426
- ${annotationData && annotationsSet?.has('class') && annotationData.hasSubclass ? `
459
+ ${annotationData && annotationsSet?.has('item') && (annotationData.leftHasSubclass || annotationData.rightHasSubclass || annotationData.hasSubclass) ? `
427
460
  <div class="subclass-annotation">
428
461
  <div class="subclass-text">
429
462
  POTENTIAL SUBCLASS
@@ -434,7 +467,7 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
434
467
  </div>
435
468
  ` : ''}
436
469
 
437
- ${annotationData && ((annotationData.includeConfirmation === true) || annotationData.additionalNotes) ? `
470
+ ${hasConfirmationOrNotes ? `
438
471
  <div class="confirmation-section">
439
472
  ${annotationData && (annotationData.includeConfirmation === true) ? `
440
473
  <div class="confirmation-summary">
@@ -465,10 +498,34 @@ export const renderReport: ReportRenderer = (data: PDFGenerationData): string =>
465
498
  </div>
466
499
  ` : ''}
467
500
 
468
- ${annotationData && annotationsSet?.has('notes') && annotationData.additionalNotes && annotationData.additionalNotes.trim() ? `
469
- <section class="additional-notes-section ${hasImage || annotationData.includeConfirmation === true ? 'notes-page' : ''}">
470
- <h2 class="additional-notes-title">Additional Notes</h2>
471
- <p class="additional-notes-body">${escapeHtml(annotationData.additionalNotes.trim())}</p>
501
+ ${annotationData && annotationsSet?.has('notes') && hasAdditionalNotes ? `
502
+ <section class="${notesShouldStartNewPage ? 'notes-page' : ''}">
503
+ ${hasSideAdditionalNotes ? `
504
+ ${leftAdditionalNotes && rightAdditionalNotes ? `
505
+ <div class="additional-notes-grid">
506
+ <div class="additional-notes-section additional-notes-section--half">
507
+ <h2 class="additional-notes-title">Additional Notes (L)</h2>
508
+ <p class="additional-notes-body">${escapeHtml(leftAdditionalNotes)}</p>
509
+ </div>
510
+ <div class="additional-notes-section additional-notes-section--half">
511
+ <h2 class="additional-notes-title">Additional Notes (R)</h2>
512
+ <p class="additional-notes-body">${escapeHtml(rightAdditionalNotes)}</p>
513
+ </div>
514
+ </div>
515
+ ` : `
516
+ <div class="additional-notes-section">
517
+ <h2 class="additional-notes-title">Additional Notes (${leftAdditionalNotes ? 'L' : 'R'})</h2>
518
+ <p class="additional-notes-body">${escapeHtml(leftAdditionalNotes || rightAdditionalNotes)}</p>
519
+ </div>
520
+ `}
521
+ ` : ''}
522
+
523
+ ${hasGeneralAdditionalNotes ? `
524
+ <div class="additional-notes-section" style="margin-top: ${hasSideAdditionalNotes ? '12px' : '0'};">
525
+ <h2 class="additional-notes-title">Additional Notes (General)</h2>
526
+ <p class="additional-notes-body">${escapeHtml(generalAdditionalNotes)}</p>
527
+ </div>
528
+ ` : ''}
472
529
  </section>
473
530
  ` : ''}
474
531
  </div>
@@ -1,3 +1,5 @@
1
+ import type { IndexType, SupportLevel } from '../../../shared/types/annotation-literals';
2
+
1
3
  export interface BoxAnnotation {
2
4
  x: number;
3
5
  y: number;
@@ -16,7 +18,7 @@ export interface ConfirmationData {
16
18
 
17
19
  export interface AnnotationData {
18
20
  // Index annotations
19
- indexType?: 'number' | 'color';
21
+ indexType?: IndexType;
20
22
  indexNumber?: string;
21
23
  indexColor?: string;
22
24
 
@@ -31,9 +33,19 @@ export interface AnnotationData {
31
33
  boxAnnotations?: BoxAnnotation[];
32
34
 
33
35
  // ID/Support level annotations
34
- supportLevel?: 'ID' | 'Exclusion' | 'Inconclusive';
35
-
36
- // Class annotations
36
+ supportLevel?: SupportLevel;
37
+
38
+ // Class annotations (left/right per-item)
39
+ leftItemType?: string;
40
+ leftCustomClass?: string;
41
+ leftClassNote?: string;
42
+ leftHasSubclass?: boolean;
43
+ rightItemType?: string;
44
+ rightCustomClass?: string;
45
+ rightClassNote?: string;
46
+ rightHasSubclass?: boolean;
47
+ // Legacy (kept for backward compatibility)
48
+ itemType?: string;
37
49
  classType?: string;
38
50
  customClass?: string;
39
51
  classNote?: string;
@@ -44,6 +56,8 @@ export interface AnnotationData {
44
56
  confirmationData?: ConfirmationData;
45
57
 
46
58
  // Notes
59
+ leftAdditionalNotes?: string;
60
+ rightAdditionalNotes?: string;
47
61
  additionalNotes?: string;
48
62
  }
49
63
 
@@ -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-12",
5
+ "compatibility_date": "2026-04-15",
6
6
  "compatibility_flags": [
7
7
  "nodejs_compat"
8
8
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "user-worker",
3
- "version": "6.0.1",
3
+ "version": "6.1.1",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "deploy": "wrangler deploy",
@@ -8,6 +8,6 @@
8
8
  "start": "wrangler dev"
9
9
  },
10
10
  "devDependencies": {
11
- "wrangler": "^4.81.1"
11
+ "wrangler": "^4.83.0"
12
12
  }
13
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-12",
5
+ "compatibility_date": "2026-04-15",
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-12"
3
+ compatibility_date = "2026-04-15"
4
4
  compatibility_flags = ["nodejs_compat"]
5
5
  pages_build_output_dir = "./build/client"
6
6