From 36db9322356d31ae3ebd80af4020f8bc15be043a Mon Sep 17 00:00:00 2001 From: cesnimda Date: Mon, 2 Nov 2026 22:25:09 +0100 Subject: [PATCH] fix(i18n): preserve custom statuses matching inherited alias names --- docs/i18n/PIPELINE_ALIAS_FALLBACK.md | 11 +++++++++++ job-tracker-ui/src/pipeline.test.ts | 6 ++++++ job-tracker-ui/src/pipeline.ts | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 docs/i18n/PIPELINE_ALIAS_FALLBACK.md diff --git a/docs/i18n/PIPELINE_ALIAS_FALLBACK.md b/docs/i18n/PIPELINE_ALIAS_FALLBACK.md new file mode 100644 index 00000000..892aafd8 --- /dev/null +++ b/docs/i18n/PIPELINE_ALIAS_FALLBACK.md @@ -0,0 +1,11 @@ +# Pipeline alias fallback — 2 November 2026 + +CONFIRMED functional defect: normalizeStatus('constructor') returned the inherited Object function and '__proto__' returned an object instead of the declared pipeline status or Other. statusLabel could consequently lose a custom status. No prototype mutation, account access or persisted-data corruption is asserted. + +Minimal fix: only accept own properties of the existing ALIASES map. Existing trimming, case-insensitive canonical stages, known aliases, empty-as-Applied and custom-as-Other behavior are retained. Shared callers include JobFlowBar, KanbanBoard and ApplicationWorkspacePage. No statuses or stored records were renamed; no routes, migrations or dependencies changed. + +RED diagnostic: constructor and __proto__ cases failed; toString control passed because the input is lowercased. Eleven unrelated cases were not selected in that diagnostic run. GREEN final: pipeline and grouped-board suites, 29 tests, zero failures/pending, native exit 0. New cases verify raw custom labels bypass translation; existing cases cover aliases, group membership/order, valid stage labels and board interactions. Scoped lint and full typecheck passed. Native ignored report: job-tracker-ui/.artifacts/pipeline-alias-fallback.json. + +This resolves historical pipeline.ts:149 `t(LABEL_KEYS[normalized])`: the canonical result is one of the ten explicit LABEL_KEYS or Other, which returns raw status. The original implementation's assumed finite return domain was not true for inherited names. Latest dynamic review count: 123 resolved / 65 pending of 188. Full catalogue/native-language context acceptance is separate. + +Rollback: revert the one-line guard and regression additions. Production build from the preceding label/session package predates this patch and is not claimed as a fresh pipeline build. Final integrated browser/security qualification remains open. diff --git a/job-tracker-ui/src/pipeline.test.ts b/job-tracker-ui/src/pipeline.test.ts index 93fdf27f..992a3a29 100644 --- a/job-tracker-ui/src/pipeline.test.ts +++ b/job-tracker-ui/src/pipeline.test.ts @@ -10,6 +10,12 @@ import { } from './pipeline'; describe('pipeline', () => { + test.each(['constructor', '__proto__', 'toString'])('keeps inherited alias name %s as a custom status', status => { + const translate = jest.fn(() => 'translated'); + expect(normalizeStatus(status)).toBe('Other'); + expect(statusLabel(translate, status)).toBe(status); + expect(translate).not.toHaveBeenCalled(); + }); test('normalizeStatus canonicalizes casing and synonyms', () => { expect(normalizeStatus('applied')).toBe('Applied'); expect(normalizeStatus(' OFFER ')).toBe('Offer'); diff --git a/job-tracker-ui/src/pipeline.ts b/job-tracker-ui/src/pipeline.ts index 1302a1c3..2a594983 100644 --- a/job-tracker-ui/src/pipeline.ts +++ b/job-tracker-ui/src/pipeline.ts @@ -102,7 +102,7 @@ export function normalizeStatus(status?: string | null): PipelineStatus | "Other const exact = PIPELINE_STATUSES.find((s) => s.toLowerCase() === trimmed.toLowerCase()); if (exact) return exact; const alias = ALIASES[trimmed.toLowerCase()]; - return alias ?? "Other"; + return Object.hasOwn(ALIASES, trimmed.toLowerCase()) ? alias : "Other"; } /** MUI palette key for a status; both chip color and board accent derive from this. */