diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e373faf..6776fbf0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -123,6 +123,17 @@ jobs: # this step, so the *.pg.test.ts files probe the unreachable default # and self-skip — same as today. They get real coverage in the # dedicated serial step below. + # + # `test` pins --test-concurrency=4 (issue #605). Node's default is + # derived from the core count, so a 16-core dev machine ran far more + # files at once than CI ever does — and the suite spins up many + # short-lived Express servers on localhost. Three consecutive full runs + # each failed a DIFFERENT test (runtimeSecretsRoute, + # conductorTemplateRoutes, inboundWebhookRoutes), two with a + # connection-level `fetch failed`; every one passed in isolation. That + # is contention, not logic. Pinning makes the number the same + # everywhere, so a red run means a real failure instead of training + # everyone to re-run — the habit that let #549 break main. - name: Test (node --test via tsx) run: npm run test diff --git a/middleware/package.json b/middleware/package.json index 3bedb686..c2783db4 100644 --- a/middleware/package.json +++ b/middleware/package.json @@ -41,7 +41,7 @@ "eval:golden": "node --import tsx test/golden/goldenSet.eval.ts", "setup:tigris-lifecycle": "tsx scripts/setup-tigris-lifecycle.ts", "pretest": "node scripts/check-node-version.mjs", - "test": "node --import tsx --test --test-timeout=120000 --test-reporter=spec 'test/**/*.test.ts'", + "test": "node --import tsx --test --test-timeout=120000 --test-concurrency=4 --test-reporter=spec 'test/**/*.test.ts'", "test:pg": "node --import tsx --test --test-timeout=120000 --test-concurrency=1 --test-reporter=spec 'test/**/*.pg.test.ts'" }, "engines": { diff --git a/web-ui/CLAUDE.md b/web-ui/CLAUDE.md index d0089ed0..90400a72 100644 --- a/web-ui/CLAUDE.md +++ b/web-ui/CLAUDE.md @@ -13,9 +13,12 @@ When you add or change any user-facing string: 1. Add the key to `messages/en.json` (English is the source of truth). 2. Mirror the **same key** in `messages/de.json` with the German translation. 3. Use it in components via `next-intl`: `const t = useTranslations('.')`. -4. Run `npm run i18n:check` — the parity test fails on missing/extra/empty keys, - forbidden HTML (`script/iframe/object/embed/link`), or mismatched ICU/tag - placeholders across locales. +4. Run `npm run i18n:check` — `scripts/i18n-validate.mjs` fails on missing/extra + keys, empty or non-string values, and forbidden HTML + (`script/iframe/object/embed/link`). +5. Run `npm test` — the parity suite (`app/_lib/i18n-parity.test.ts`) additionally + fails on **mismatched ICU arguments and rich-text tags** across locales. This + check is not part of `i18n:check`; it runs under vitest, and CI runs it. Do **not** hardcode user-facing strings in components. See `messages/README.md` for the full key-naming convention, ICU placeholders, and `t.rich` usage. diff --git a/web-ui/package-lock.json b/web-ui/package-lock.json index 065ae5c4..d7c0413d 100644 --- a/web-ui/package-lock.json +++ b/web-ui/package-lock.json @@ -31,6 +31,7 @@ "tailwind-merge": "^3.6.0" }, "devDependencies": { + "@formatjs/icu-messageformat-parser": "^3.5.16", "@tailwindcss/postcss": "^4.3.3", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^7.0.0", diff --git a/web-ui/package.json b/web-ui/package.json index 165d4362..89ec58c2 100644 --- a/web-ui/package.json +++ b/web-ui/package.json @@ -49,6 +49,7 @@ "tailwind-merge": "^3.6.0" }, "devDependencies": { + "@formatjs/icu-messageformat-parser": "^3.5.16", "@tailwindcss/postcss": "^4.3.3", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^7.0.0",