@striae-org/striae 6.0.1 → 6.1.0

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 (40) 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/icon/icons.svg +1 -1
  6. package/app/components/icon/manifest.json +1 -1
  7. package/app/components/navbar/navbar.tsx +10 -9
  8. package/app/components/sidebar/cases/case-sidebar.tsx +6 -1
  9. package/app/components/sidebar/files/files-modal.tsx +39 -15
  10. package/app/components/sidebar/notes/addl-notes-modal.tsx +9 -2
  11. package/app/components/sidebar/notes/{class-details/class-details-fields.tsx → item-details/item-details-fields.tsx} +10 -10
  12. package/app/components/sidebar/notes/{class-details/class-details-modal.tsx → item-details/item-details-modal.tsx} +20 -22
  13. package/app/components/sidebar/notes/{class-details/class-details-sections.tsx → item-details/item-details-sections.tsx} +16 -16
  14. package/app/components/sidebar/notes/{class-details/class-details-shared.ts → item-details/item-details-shared.ts} +4 -3
  15. package/app/components/sidebar/notes/{class-details/use-class-details-state.ts → item-details/use-item-details-state.ts} +4 -4
  16. package/app/components/sidebar/notes/notes-editor-form.tsx +333 -124
  17. package/app/components/sidebar/notes/notes-editor-modal.tsx +3 -0
  18. package/app/components/sidebar/notes/notes.module.css +40 -20
  19. package/app/components/sidebar/sidebar-container.tsx +1 -0
  20. package/app/components/sidebar/sidebar.tsx +3 -0
  21. package/app/components/toolbar/toolbar.tsx +5 -5
  22. package/app/hooks/useFileListPreferences.ts +22 -17
  23. package/app/routes/striae/striae.tsx +4 -10
  24. package/app/types/annotations.ts +28 -5
  25. package/app/utils/data/confirmation-summary/summary-core.ts +40 -8
  26. package/app/utils/data/file-filters.ts +39 -17
  27. package/package.json +139 -139
  28. package/workers/audit-worker/package.json +2 -2
  29. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  30. package/workers/data-worker/package.json +2 -2
  31. package/workers/data-worker/wrangler.jsonc.example +1 -1
  32. package/workers/image-worker/package.json +2 -2
  33. package/workers/image-worker/wrangler.jsonc.example +1 -1
  34. package/workers/pdf-worker/package.json +2 -2
  35. package/workers/pdf-worker/src/formats/format-striae.ts +65 -8
  36. package/workers/pdf-worker/src/report-types.ts +13 -1
  37. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  38. package/workers/user-worker/package.json +2 -2
  39. package/workers/user-worker/wrangler.jsonc.example +1 -1
  40. package/wrangler.toml.example +1 -1
package/package.json CHANGED
@@ -1,140 +1,140 @@
1
- {
2
- "name": "@striae-org/striae",
3
- "version": "6.0.1",
4
- "private": false,
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
- "license": "Apache-2.0",
7
- "homepage": "https://github.com/striae-org/striae/wiki",
8
- "repository": {
9
- "type": "git",
10
- "url": "https://github.com/striae-org/striae.git"
11
- },
12
- "funding": {
13
- "type": "github",
14
- "url": "https://github.com/sponsors/striae-org"
15
- },
16
- "bugs": {
17
- "url": "https://github.com/striae-org/striae/issues"
18
- },
19
- "keywords": [
20
- "forensics",
21
- "firearms",
22
- "annotation",
23
- "react",
24
- "cloudflare-workers",
25
- "authenticated",
26
- "confirmations",
27
- "chain-of-custody",
28
- "audit-trail"
29
- ],
30
- "publishConfig": {
31
- "access": "public"
32
- },
33
- "files": [
34
- "app/",
35
- "!app/config",
36
- "!app/routes/auth/login.tsx",
37
- "!app/routes/auth/login.module.css",
38
- "react-router.config.ts",
39
- "load-context.ts",
40
- "scripts/",
41
- "functions/",
42
- "public/",
43
- "workers/",
44
- "!workers/*/.wrangler",
45
- "!workers/*/package-lock.json",
46
- "!workers/*/worker-configuration.d.ts",
47
- "!workers/*/wrangler.jsonc",
48
- "!workers/*/src/**/*worker.ts",
49
- "!workers/pdf-worker/src/assets/**/*",
50
- "workers/pdf-worker/src/assets/generated-assets.example.ts",
51
- "!workers/pdf-worker/src/formats/**/*",
52
- "workers/pdf-worker/src/formats/format-striae.ts",
53
- ".env.example",
54
- "firebase.json",
55
- "tsconfig.json",
56
- "vite.config.ts",
57
- "/worker-configuration.d.ts",
58
- "wrangler.toml.example",
59
- "LICENSE"
60
- ],
61
- "sideEffects": false,
62
- "type": "module",
63
- "scripts": {
64
- "deploy:all": "bash ./scripts/deploy-all.sh",
65
- "emulators": "firebase emulators:start --only auth",
66
- "dev": "node ./scripts/dev.cjs && react-router dev",
67
- "build": "node ./scripts/dev.cjs && react-router build",
68
- "clean": "rm -rf build node_modules/.cache .cache",
69
- "clean:build": "npm run clean && npm run build",
70
- "deploy": "npm run build && wrangler pages deploy",
71
- "publish:npm": "npm publish --access public --registry=https://registry.npmjs.org --@striae-org:registry=https://registry.npmjs.org",
72
- "publish:npm:dry-run": "npm publish --dry-run --access public --registry=https://registry.npmjs.org --@striae-org:registry=https://registry.npmjs.org",
73
- "publish:github": "npm publish --registry=https://npm.pkg.github.com --@striae-org:registry=https://npm.pkg.github.com",
74
- "publish:github:dry-run": "npm publish --dry-run --registry=https://npm.pkg.github.com --@striae-org:registry=https://npm.pkg.github.com",
75
- "publish:all": "npm run publish:npm && npm run publish:github",
76
- "publish:all:dry-run": "npm run publish:npm:dry-run && npm run publish:github:dry-run",
77
- "lint": "node ./scripts/run-eslint.cjs",
78
- "start": "node ./scripts/dev.cjs && wrangler pages dev",
79
- "typecheck": "react-router typegen && tsc",
80
- "typegen": "wrangler types",
81
- "preview": "npm run build && wrangler pages dev",
82
- "cf-typegen": "wrangler types",
83
- "enable-totp-mfa": "node ./scripts/enable-totp-mfa.mjs",
84
- "unenroll-totp-mfa": "node ./scripts/unenroll-totp-mfa.mjs",
85
- "update-versions": "node ./scripts/update-markdown-versions.cjs",
86
- "update-compatibility-dates": "node ./scripts/update-compatibility-dates.cjs",
87
- "deploy-config": "bash ./scripts/deploy-config.sh",
88
- "update-env": "bash ./scripts/deploy-config.sh --update-env",
89
- "install-workers": "bash ./scripts/install-workers.sh",
90
- "deploy-workers": "npm run deploy-workers:audit && npm run deploy-workers:data && npm run deploy-workers:image && npm run deploy-workers:pdf && npm run deploy-workers:user",
91
- "deploy-workers:secrets": "bash ./scripts/deploy-worker-secrets.sh",
92
- "deploy-pages:secrets": "bash ./scripts/deploy-pages-secrets.sh",
93
- "deploy-pages": "bash ./scripts/deploy-pages.sh",
94
- "deploy-primershear": "bash ./scripts/deploy-primershear-emails.sh",
95
- "deploy-members": "bash ./scripts/deploy-members-emails.sh",
96
- "deploy-workers:audit": "cd workers/audit-worker && npm run deploy",
97
- "deploy-workers:data": "cd workers/data-worker && npm run deploy",
98
- "deploy-workers:image": "cd workers/image-worker && npm run deploy",
99
- "deploy-workers:pdf": "cd workers/pdf-worker && npm run deploy",
100
- "deploy-workers:user": "cd workers/user-worker && npm run deploy"
101
- },
102
- "dependencies": {
103
- "@react-router/cloudflare": "^7.14.0",
104
- "firebase": "^12.12.0",
105
- "isbot": "^5.1.37",
106
- "jszip": "^3.10.1",
107
- "qrcode": "^1.5.4",
108
- "react": "^19.2.5",
109
- "react-dom": "^19.2.5",
110
- "react-router": "^7.14.0"
111
- },
112
- "devDependencies": {
113
- "@react-router/dev": "^7.14.0",
114
- "@react-router/fs-routes": "^7.14.0",
115
- "@types/qrcode": "^1.5.6",
116
- "@types/react": "^19.2.14",
117
- "@types/react-dom": "^19.2.3",
118
- "@typescript-eslint/eslint-plugin": "^8.58.1",
119
- "@typescript-eslint/parser": "^8.58.1",
120
- "eslint": "^9.39.4",
121
- "eslint-import-resolver-typescript": "^4.4.4",
122
- "eslint-plugin-import": "^2.32.0",
123
- "eslint-plugin-jsx-a11y": "^6.10.2",
124
- "eslint-plugin-react": "^7.37.5",
125
- "eslint-plugin-react-hooks": "^7.0.1",
126
- "firebase-admin": "^13.8.0",
127
- "modern-normalize": "^3.0.1",
128
- "typescript": "^5.9.3",
129
- "vite": "^7.3.2",
130
- "vite-tsconfig-paths": "^6.1.1",
131
- "wrangler": "^4.81.1"
132
- },
133
- "overrides": {
134
- "@tootallnate/once": "3.0.1"
135
- },
136
- "engines": {
137
- "node": ">=20.19.0"
138
- },
139
- "packageManager": "npm@11.11.0"
1
+ {
2
+ "name": "@striae-org/striae",
3
+ "version": "6.1.0",
4
+ "private": false,
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
+ "license": "Apache-2.0",
7
+ "homepage": "https://github.com/striae-org/striae/wiki",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/striae-org/striae.git"
11
+ },
12
+ "funding": {
13
+ "type": "github",
14
+ "url": "https://github.com/sponsors/striae-org"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/striae-org/striae/issues"
18
+ },
19
+ "keywords": [
20
+ "forensics",
21
+ "firearms",
22
+ "annotation",
23
+ "react",
24
+ "cloudflare-workers",
25
+ "authenticated",
26
+ "confirmations",
27
+ "chain-of-custody",
28
+ "audit-trail"
29
+ ],
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "files": [
34
+ "app/",
35
+ "!app/config",
36
+ "!app/routes/auth/login.tsx",
37
+ "!app/routes/auth/login.module.css",
38
+ "react-router.config.ts",
39
+ "load-context.ts",
40
+ "scripts/",
41
+ "functions/",
42
+ "public/",
43
+ "workers/",
44
+ "!workers/*/.wrangler",
45
+ "!workers/*/package-lock.json",
46
+ "!workers/*/worker-configuration.d.ts",
47
+ "!workers/*/wrangler.jsonc",
48
+ "!workers/*/src/**/*worker.ts",
49
+ "!workers/pdf-worker/src/assets/**/*",
50
+ "workers/pdf-worker/src/assets/generated-assets.example.ts",
51
+ "!workers/pdf-worker/src/formats/**/*",
52
+ "workers/pdf-worker/src/formats/format-striae.ts",
53
+ ".env.example",
54
+ "firebase.json",
55
+ "tsconfig.json",
56
+ "vite.config.ts",
57
+ "/worker-configuration.d.ts",
58
+ "wrangler.toml.example",
59
+ "LICENSE"
60
+ ],
61
+ "sideEffects": false,
62
+ "type": "module",
63
+ "scripts": {
64
+ "deploy:all": "bash ./scripts/deploy-all.sh",
65
+ "emulators": "firebase emulators:start --only auth",
66
+ "dev": "node ./scripts/dev.cjs && react-router dev",
67
+ "build": "node ./scripts/dev.cjs && react-router build",
68
+ "clean": "rm -rf build node_modules/.cache .cache",
69
+ "clean:build": "npm run clean && npm run build",
70
+ "deploy": "npm run build && wrangler pages deploy",
71
+ "publish:npm": "npm publish --access public --registry=https://registry.npmjs.org --@striae-org:registry=https://registry.npmjs.org",
72
+ "publish:npm:dry-run": "npm publish --dry-run --access public --registry=https://registry.npmjs.org --@striae-org:registry=https://registry.npmjs.org",
73
+ "publish:github": "npm publish --registry=https://npm.pkg.github.com --@striae-org:registry=https://npm.pkg.github.com",
74
+ "publish:github:dry-run": "npm publish --dry-run --registry=https://npm.pkg.github.com --@striae-org:registry=https://npm.pkg.github.com",
75
+ "publish:all": "npm run publish:npm && npm run publish:github",
76
+ "publish:all:dry-run": "npm run publish:npm:dry-run && npm run publish:github:dry-run",
77
+ "lint": "node ./scripts/run-eslint.cjs",
78
+ "start": "node ./scripts/dev.cjs && wrangler pages dev",
79
+ "typecheck": "react-router typegen && tsc",
80
+ "typegen": "wrangler types",
81
+ "preview": "npm run build && wrangler pages dev",
82
+ "cf-typegen": "wrangler types",
83
+ "enable-totp-mfa": "node ./scripts/enable-totp-mfa.mjs",
84
+ "unenroll-totp-mfa": "node ./scripts/unenroll-totp-mfa.mjs",
85
+ "update-versions": "node ./scripts/update-markdown-versions.cjs",
86
+ "update-compatibility-dates": "node ./scripts/update-compatibility-dates.cjs",
87
+ "deploy-config": "bash ./scripts/deploy-config.sh",
88
+ "update-env": "bash ./scripts/deploy-config.sh --update-env",
89
+ "install-workers": "bash ./scripts/install-workers.sh",
90
+ "deploy-workers": "npm run deploy-workers:audit && npm run deploy-workers:data && npm run deploy-workers:image && npm run deploy-workers:pdf && npm run deploy-workers:user",
91
+ "deploy-workers:secrets": "bash ./scripts/deploy-worker-secrets.sh",
92
+ "deploy-pages:secrets": "bash ./scripts/deploy-pages-secrets.sh",
93
+ "deploy-pages": "bash ./scripts/deploy-pages.sh",
94
+ "deploy-primershear": "bash ./scripts/deploy-primershear-emails.sh",
95
+ "deploy-members": "bash ./scripts/deploy-members-emails.sh",
96
+ "deploy-workers:audit": "cd workers/audit-worker && npm run deploy",
97
+ "deploy-workers:data": "cd workers/data-worker && npm run deploy",
98
+ "deploy-workers:image": "cd workers/image-worker && npm run deploy",
99
+ "deploy-workers:pdf": "cd workers/pdf-worker && npm run deploy",
100
+ "deploy-workers:user": "cd workers/user-worker && npm run deploy"
101
+ },
102
+ "dependencies": {
103
+ "@react-router/cloudflare": "^7.14.1",
104
+ "firebase": "^12.12.0",
105
+ "isbot": "^5.1.38",
106
+ "jszip": "^3.10.1",
107
+ "qrcode": "^1.5.4",
108
+ "react": "^19.2.5",
109
+ "react-dom": "^19.2.5",
110
+ "react-router": "^7.14.1"
111
+ },
112
+ "devDependencies": {
113
+ "@react-router/dev": "^7.14.1",
114
+ "@react-router/fs-routes": "^7.14.1",
115
+ "@types/qrcode": "^1.5.6",
116
+ "@types/react": "^19.2.14",
117
+ "@types/react-dom": "^19.2.3",
118
+ "@typescript-eslint/eslint-plugin": "^8.58.2",
119
+ "@typescript-eslint/parser": "^8.58.2",
120
+ "eslint": "^9.39.4",
121
+ "eslint-import-resolver-typescript": "^4.4.4",
122
+ "eslint-plugin-import": "^2.32.0",
123
+ "eslint-plugin-jsx-a11y": "^6.10.2",
124
+ "eslint-plugin-react": "^7.37.5",
125
+ "eslint-plugin-react-hooks": "^7.0.1",
126
+ "firebase-admin": "^13.8.0",
127
+ "modern-normalize": "^3.0.1",
128
+ "typescript": "^5.9.3",
129
+ "vite": "^7.3.2",
130
+ "vite-tsconfig-paths": "^6.1.1",
131
+ "wrangler": "^4.82.2"
132
+ },
133
+ "overrides": {
134
+ "@tootallnate/once": "3.0.1"
135
+ },
136
+ "engines": {
137
+ "node": ">=20.19.0"
138
+ },
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.0",
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.82.2"
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-14",
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.0",
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.82.2"
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-14",
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.0",
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.82.2"
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-14",
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.0",
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.82.2"
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>
@@ -33,7 +33,17 @@ export interface AnnotationData {
33
33
  // ID/Support level annotations
34
34
  supportLevel?: 'ID' | 'Exclusion' | 'Inconclusive';
35
35
 
36
- // Class annotations
36
+ // Class annotations (left/right per-item)
37
+ leftItemType?: string;
38
+ leftCustomClass?: string;
39
+ leftClassNote?: string;
40
+ leftHasSubclass?: boolean;
41
+ rightItemType?: string;
42
+ rightCustomClass?: string;
43
+ rightClassNote?: string;
44
+ rightHasSubclass?: boolean;
45
+ // Legacy (kept for backward compatibility)
46
+ itemType?: string;
37
47
  classType?: string;
38
48
  customClass?: string;
39
49
  classNote?: string;
@@ -44,6 +54,8 @@ export interface AnnotationData {
44
54
  confirmationData?: ConfirmationData;
45
55
 
46
56
  // Notes
57
+ leftAdditionalNotes?: string;
58
+ rightAdditionalNotes?: string;
47
59
  additionalNotes?: string;
48
60
  }
49
61
 
@@ -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-14",
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.0",
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.82.2"
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-14",
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-14"
4
4
  compatibility_flags = ["nodejs_compat"]
5
5
  pages_build_output_dir = "./build/client"
6
6