@striae-org/striae 6.0.0 → 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.
- package/README.md +3 -1
- package/app/components/actions/case-export/core-export.ts +11 -2
- package/app/components/actions/case-export/download-handlers.ts +3 -1
- package/app/components/canvas/canvas.module.css +1 -1
- package/app/components/canvas/canvas.tsx +32 -11
- package/app/components/icon/icons.svg +1 -1
- package/app/components/icon/manifest.json +1 -1
- package/app/components/navbar/navbar.tsx +10 -9
- package/app/components/sidebar/cases/case-sidebar.tsx +6 -1
- package/app/components/sidebar/files/files-modal.tsx +39 -15
- package/app/components/sidebar/notes/addl-notes-modal.tsx +9 -2
- package/app/components/sidebar/notes/{class-details/class-details-fields.tsx → item-details/item-details-fields.tsx} +10 -10
- package/app/components/sidebar/notes/{class-details/class-details-modal.tsx → item-details/item-details-modal.tsx} +20 -22
- package/app/components/sidebar/notes/{class-details/class-details-sections.tsx → item-details/item-details-sections.tsx} +16 -16
- package/app/components/sidebar/notes/{class-details/class-details-shared.ts → item-details/item-details-shared.ts} +4 -3
- package/app/components/sidebar/notes/{class-details/use-class-details-state.ts → item-details/use-item-details-state.ts} +4 -4
- package/app/components/sidebar/notes/notes-editor-form.tsx +333 -124
- package/app/components/sidebar/notes/notes-editor-modal.tsx +3 -0
- package/app/components/sidebar/notes/notes.module.css +40 -20
- package/app/components/sidebar/sidebar-container.tsx +8 -0
- package/app/components/sidebar/sidebar.tsx +3 -0
- package/app/components/toolbar/toolbar.tsx +5 -5
- package/{members.emails.example → app/config-example/members.emails} +1 -1
- package/{primershear.emails.example → app/config-example/primershear.emails} +1 -1
- package/app/hooks/useFileListPreferences.ts +22 -17
- package/app/routes/striae/striae.tsx +4 -10
- package/app/types/annotations.ts +28 -5
- package/app/utils/data/confirmation-summary/summary-core.ts +40 -8
- package/app/utils/data/file-filters.ts +39 -17
- package/package.json +139 -141
- package/scripts/deploy-config.sh +33 -0
- package/scripts/deploy-members-emails.sh +4 -4
- package/scripts/deploy-primershear-emails.sh +3 -3
- package/workers/audit-worker/package.json +2 -2
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/package.json +2 -2
- package/workers/data-worker/wrangler.jsonc.example +1 -1
- package/workers/image-worker/package.json +2 -2
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/pdf-worker/package.json +2 -2
- package/workers/pdf-worker/src/formats/format-striae.ts +65 -8
- package/workers/pdf-worker/src/report-types.ts +13 -1
- package/workers/pdf-worker/wrangler.jsonc.example +1 -1
- package/workers/user-worker/package.json +2 -2
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +1 -1
package/package.json
CHANGED
|
@@ -1,142 +1,140 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@striae-org/striae",
|
|
3
|
-
"version": "6.
|
|
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
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"build": "
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"publish:
|
|
74
|
-
"publish:
|
|
75
|
-
"publish:
|
|
76
|
-
"publish:
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"typegen": "wrangler types",
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"update-
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"deploy-
|
|
93
|
-
"deploy-
|
|
94
|
-
"deploy-
|
|
95
|
-
"deploy-
|
|
96
|
-
"deploy-
|
|
97
|
-
"deploy-
|
|
98
|
-
"deploy-workers:
|
|
99
|
-
"deploy-workers:
|
|
100
|
-
"deploy-workers:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
110
|
-
"react": "^
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"@
|
|
116
|
-
"@react
|
|
117
|
-
"@types/
|
|
118
|
-
"@
|
|
119
|
-
"@
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"eslint": "^
|
|
123
|
-
"eslint-
|
|
124
|
-
"eslint-plugin-
|
|
125
|
-
"eslint-plugin-
|
|
126
|
-
"
|
|
127
|
-
"
|
|
128
|
-
"
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
},
|
|
141
|
-
"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"
|
|
142
140
|
}
|
package/scripts/deploy-config.sh
CHANGED
|
@@ -200,6 +200,36 @@ source "$DEPLOY_CONFIG_VALIDATION_MODULE"
|
|
|
200
200
|
source "$DEPLOY_CONFIG_SCAFFOLDING_MODULE"
|
|
201
201
|
source "$DEPLOY_CONFIG_PROMPT_MODULE"
|
|
202
202
|
|
|
203
|
+
EMAIL_LIST_CONFIG_DIR="app/config"
|
|
204
|
+
MEMBERS_EMAILS_FILE="$EMAIL_LIST_CONFIG_DIR/members.emails"
|
|
205
|
+
PRIMERSHEAR_EMAILS_FILE="$EMAIL_LIST_CONFIG_DIR/primershear.emails"
|
|
206
|
+
|
|
207
|
+
sync_env_var_from_email_list_file() {
|
|
208
|
+
local env_var_name=$1
|
|
209
|
+
local file_path=$2
|
|
210
|
+
local loaded_values=""
|
|
211
|
+
|
|
212
|
+
if [ ! -f "$file_path" ]; then
|
|
213
|
+
echo -e "${YELLOW}⚠️ $file_path not found; keeping existing $env_var_name value in .env${NC}"
|
|
214
|
+
return 0
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
loaded_values=$(grep -v '^[[:space:]]*#' "$file_path" | grep -v '^[[:space:]]*$' | sed -e 's/\r$//' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | paste -sd ',' - || true)
|
|
218
|
+
|
|
219
|
+
write_env_var "$env_var_name" "$loaded_values"
|
|
220
|
+
export "$env_var_name=$loaded_values"
|
|
221
|
+
|
|
222
|
+
local loaded_count
|
|
223
|
+
loaded_count=$(echo "$loaded_values" | tr ',' '\n' | grep -c '[^[:space:]]' || true)
|
|
224
|
+
echo -e "${GREEN}✅ Synced $env_var_name from $file_path ($loaded_count entry/entries)${NC}"
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
sync_email_list_env_vars_from_config() {
|
|
228
|
+
echo -e "${YELLOW}📧 Syncing optional email list env vars from app/config...${NC}"
|
|
229
|
+
sync_env_var_from_email_list_file "REGISTRATION_EMAILS" "$MEMBERS_EMAILS_FILE"
|
|
230
|
+
sync_env_var_from_email_list_file "PRIMERSHEAR_EMAILS" "$PRIMERSHEAR_EMAILS_FILE"
|
|
231
|
+
}
|
|
232
|
+
|
|
203
233
|
if [ "$validate_only" = "true" ]; then
|
|
204
234
|
echo -e "\n${BLUE}🧪 Validate-only mode enabled${NC}"
|
|
205
235
|
run_validation_checkpoint
|
|
@@ -217,6 +247,9 @@ load_admin_service_credentials
|
|
|
217
247
|
# Always prompt for secrets to ensure configuration
|
|
218
248
|
prompt_for_secrets
|
|
219
249
|
|
|
250
|
+
# Keep optional email list env vars aligned with app/config source files.
|
|
251
|
+
sync_email_list_env_vars_from_config
|
|
252
|
+
|
|
220
253
|
# Validate after secrets have been configured
|
|
221
254
|
validate_required_vars
|
|
222
255
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# ============================================
|
|
4
4
|
# MEMBERS EMAIL LIST DEPLOYMENT SCRIPT
|
|
5
5
|
# ============================================
|
|
6
|
-
# Reads members.emails, updates REGISTRATION_EMAILS in .env,
|
|
6
|
+
# Reads app/config/members.emails, updates REGISTRATION_EMAILS in .env,
|
|
7
7
|
# then deploys that secret directly to Cloudflare Pages (production).
|
|
8
8
|
|
|
9
9
|
set -e
|
|
@@ -26,12 +26,12 @@ trap 'echo -e "\n${RED}❌ deploy-members-emails.sh failed near line ${LINENO}${
|
|
|
26
26
|
|
|
27
27
|
# ── Read emails file ──────────────────────────────────────────────────────────
|
|
28
28
|
|
|
29
|
-
EMAILS_FILE="$PROJECT_ROOT/members.emails"
|
|
29
|
+
EMAILS_FILE="$PROJECT_ROOT/app/config/members.emails"
|
|
30
30
|
|
|
31
31
|
if [ ! -f "$EMAILS_FILE" ]; then
|
|
32
32
|
echo -e "${RED}❌ members.emails not found at: $EMAILS_FILE${NC}"
|
|
33
33
|
echo -e "${YELLOW} Create it with one email address or @domain.com wildcard per line.${NC}"
|
|
34
|
-
echo -e "${YELLOW} See members.emails
|
|
34
|
+
echo -e "${YELLOW} See app/config-example/members.emails for the format.${NC}"
|
|
35
35
|
exit 1
|
|
36
36
|
fi
|
|
37
37
|
|
|
@@ -45,7 +45,7 @@ if [ -z "$REGISTRATION_EMAILS" ]; then
|
|
|
45
45
|
fi
|
|
46
46
|
|
|
47
47
|
ENTRY_COUNT=$(echo "$REGISTRATION_EMAILS" | tr ',' '\n' | grep -c '[^[:space:]]' || true)
|
|
48
|
-
echo -e "${GREEN}✅ Loaded $ENTRY_COUNT entry(ies) from members.emails${NC}"
|
|
48
|
+
echo -e "${GREEN}✅ Loaded $ENTRY_COUNT entry(ies) from app/config/members.emails${NC}"
|
|
49
49
|
|
|
50
50
|
# ── Update .env ───────────────────────────────────────────────────────────────
|
|
51
51
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# ============================================
|
|
4
4
|
# PRIMERSHEAR EMAIL LIST DEPLOYMENT SCRIPT
|
|
5
5
|
# ============================================
|
|
6
|
-
# Reads primershear.emails, updates PRIMERSHEAR_EMAILS in .env,
|
|
6
|
+
# Reads app/config/primershear.emails, updates PRIMERSHEAR_EMAILS in .env,
|
|
7
7
|
# then deploys that secret directly to Cloudflare Pages (production).
|
|
8
8
|
|
|
9
9
|
set -e
|
|
@@ -26,7 +26,7 @@ trap 'echo -e "\n${RED}❌ deploy-primershear-emails.sh failed near line ${LINEN
|
|
|
26
26
|
|
|
27
27
|
# ── Read emails file ──────────────────────────────────────────────────────────
|
|
28
28
|
|
|
29
|
-
EMAILS_FILE="$PROJECT_ROOT/primershear.emails"
|
|
29
|
+
EMAILS_FILE="$PROJECT_ROOT/app/config/primershear.emails"
|
|
30
30
|
|
|
31
31
|
if [ ! -f "$EMAILS_FILE" ]; then
|
|
32
32
|
echo -e "${RED}❌ primershear.emails not found at: $EMAILS_FILE${NC}"
|
|
@@ -44,7 +44,7 @@ if [ -z "$PRIMERSHEAR_EMAILS" ]; then
|
|
|
44
44
|
fi
|
|
45
45
|
|
|
46
46
|
EMAIL_COUNT=$(echo "$PRIMERSHEAR_EMAILS" | tr ',' '\n' | grep -c '[^[:space:]]' || true)
|
|
47
|
-
echo -e "${GREEN}✅ Loaded $EMAIL_COUNT email address(es) from primershear.emails${NC}"
|
|
47
|
+
echo -e "${GREEN}✅ Loaded $EMAIL_COUNT email address(es) from app/config/primershear.emails${NC}"
|
|
48
48
|
|
|
49
49
|
# ── Update .env ───────────────────────────────────────────────────────────────
|
|
50
50
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "audit-worker",
|
|
3
|
-
"version": "6.
|
|
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.
|
|
11
|
+
"wrangler": "^4.82.2"
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "data-worker",
|
|
3
|
-
"version": "6.
|
|
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.
|
|
11
|
+
"wrangler": "^4.82.2"
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "image-worker",
|
|
3
|
-
"version": "6.
|
|
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.
|
|
11
|
+
"wrangler": "^4.82.2"
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pdf-worker",
|
|
3
|
-
"version": "6.
|
|
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.
|
|
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('
|
|
435
|
+
${annotationData && annotationsSet?.has('item') ? `
|
|
419
436
|
<div class="class-annotation">
|
|
420
437
|
<div class="class-text-annotation">
|
|
421
|
-
${
|
|
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('
|
|
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
|
-
${
|
|
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') &&
|
|
469
|
-
<section class="
|
|
470
|
-
|
|
471
|
-
|
|
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
|
|