From 4a3da3319e59719800a9f2044b59b589a1d8df64 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Mon, 2 Nov 2026 22:55:38 +0100 Subject: [PATCH] fix(i18n): retain historical checklist text for unknown system keys --- docs/i18n/CHECKLIST_UNKNOWN_SYSTEM_KEYS.md | 13 +++ docs/i18n/DYNAMIC_RESOLUTION_REGISTER.json | 93 ++++++++++++++++++- .../src/application-checklist.test.tsx | 8 ++ job-tracker-ui/src/applicationWorkspace.ts | 5 +- .../src/checklist-translation-token.test.ts | 11 +++ 5 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 docs/i18n/CHECKLIST_UNKNOWN_SYSTEM_KEYS.md create mode 100644 job-tracker-ui/src/checklist-translation-token.test.ts diff --git a/docs/i18n/CHECKLIST_UNKNOWN_SYSTEM_KEYS.md b/docs/i18n/CHECKLIST_UNKNOWN_SYSTEM_KEYS.md new file mode 100644 index 00000000..a2d06359 --- /dev/null +++ b/docs/i18n/CHECKLIST_UNKNOWN_SYSTEM_KEYS.md @@ -0,0 +1,13 @@ +# Historical checklist text fallback + +2 November 2026. CONFIRMED functional compatibility defect: an unknown/retired persisted system key was converted into nonexistent catalogue keys, hiding the supplied historical task text. Diagnostic RED: three unknown-token cases and EN/NB UI cases failed; nine unselected cases were not run in that diagnostic execution. + +Minimal shared fix in checklistSystemTranslationToken: retain kebab-to-camel conversion, but return a token only when the English fallback catalogue owns both title and description keys. Otherwise return null, activating the existing original-text fallback in ApplicationChecklist and workspace next-step presentation. Learning/custom boundaries remain intact. This does not delete tasks, rewrite stored keys/text, change backend generation, enable AI, or change revision/ownership/API contracts. English is the existing reliable translation fallback; current 14 system title/description pairs also exist in Bokmål. + +PASSED: 37 tests in checklist-translation-token, application-checklist and application-workspace-overlay, zero failures/pending, exit 0. New tests prove unknown/inherited-name keys do not manufacture labels, historical text survives EN/NB, and known/learning/null behavior remains. Full typecheck and scoped three-file ESLint passed. Native local report: job-tracker-ui/.artifacts/checklist-unknown-system-keys.json. Full build/browser and integrated security acceptance are not claimed here. Rollback: revert the helper/import and new tests; no database rollback. + +Four dynamic checklist/next-step sites are now bounded by actual catalogue membership, not merely a presumed backend key list. The machine register binds applicationWorkspace.ts as their owner dependency. + +Two further unchanged sites reviewed: ApplicationWorkspacePage.tsx:505 receives only WORKSPACE_SECTIONS.map(s.key), a fixed seven-key label domain; CareerEvidenceBank.tsx:83 first checks a six-value literal category array before constructing the prefixed key, leaving unknown category text untouched. All 41 distinct system/workspace/evidence keys are defined in both catalogues. This is key-domain evidence, not native language quality or complete every-string context. + +Reconciliation: 108 original + 62 supplemental sites = 170 resolved / 18 pending of 188. This supersedes the earlier open checklist gap without reclassifying broader i18n/security acceptance. diff --git a/docs/i18n/DYNAMIC_RESOLUTION_REGISTER.json b/docs/i18n/DYNAMIC_RESOLUTION_REGISTER.json index 3bc3b7e1..6f9daf51 100644 --- a/docs/i18n/DYNAMIC_RESOLUTION_REGISTER.json +++ b/docs/i18n/DYNAMIC_RESOLUTION_REGISTER.json @@ -3,9 +3,9 @@ "inventorySha256": "79928688a7b1a4961286aaaf48c028eb13ae8286d72d132561d6b5fdd192e323", "historicalCalls": 188, "previouslyResolved": 108, - "additionalResolved": 56, - "resolved": 164, - "pending": 24, + "additionalResolved": 62, + "resolved": 170, + "pending": 18, "evidence": [ "CHECKLIST_DYNAMIC_REVIEW.md", "FINITE_OWNER_MAP_REVIEW.md", @@ -15,7 +15,8 @@ "REMINDER_INTERVIEW_KEY_REVIEW.md", "ASSET_KEY_REVIEW.md", "LANDING_DOCUMENT_KEY_REVIEW.md", - "CV_BOARD_KEY_REVIEW.md" + "CV_BOARD_KEY_REVIEW.md", + "CHECKLIST_UNKNOWN_SYSTEM_KEYS.md" ], "entries": [ { @@ -575,6 +576,90 @@ "reviewedSourceSha256": "0c772feb6bde8324676b0884aaf343283465c5cd3b3318128363063df4ea22ab", "status": "finite-key-domain-reviewed", "runtimeCoverage": "static control-flow review; no PDF metric or model rerun" + }, + { + "file": "job-tracker-ui/src/components/ApplicationChecklist.tsx", + "historicalLine": 214, + "expression": "t(`checklistItem_${token}`)", + "historicalSourceSha256": "88fdcd24914cf074aedee72a94b0c420c96a16dfe7194142daf22ec0ad8370ff", + "reviewedSourceSha256": "88fdcd24914cf074aedee72a94b0c420c96a16dfe7194142daf22ec0ad8370ff", + "status": "finite-key-domain-reviewed", + "runtimeCoverage": "CHECKLIST_UNKNOWN_SYSTEM_KEYS.md; 37 focused cases; other finite labels statically reviewed", + "ownerDependencies": [ + { + "file": "job-tracker-ui/src/applicationWorkspace.ts", + "sha256": "286c5f6b5c360dddaebcf1eaaaa1157b8dfb750e8a204b7863d59e8fd33ecf2f" + } + ] + }, + { + "file": "job-tracker-ui/src/components/ApplicationChecklist.tsx", + "historicalLine": 215, + "expression": "t(`checklistItem_${token}Description`)", + "historicalSourceSha256": "88fdcd24914cf074aedee72a94b0c420c96a16dfe7194142daf22ec0ad8370ff", + "reviewedSourceSha256": "88fdcd24914cf074aedee72a94b0c420c96a16dfe7194142daf22ec0ad8370ff", + "status": "finite-key-domain-reviewed", + "runtimeCoverage": "CHECKLIST_UNKNOWN_SYSTEM_KEYS.md; 37 focused cases; other finite labels statically reviewed", + "ownerDependencies": [ + { + "file": "job-tracker-ui/src/applicationWorkspace.ts", + "sha256": "286c5f6b5c360dddaebcf1eaaaa1157b8dfb750e8a204b7863d59e8fd33ecf2f" + } + ] + }, + { + "file": "job-tracker-ui/src/views/ApplicationWorkspacePage.tsx", + "historicalLine": 392, + "expression": "t(`checklistItem_${nextStepToken}` as any)", + "historicalSourceSha256": "107b4db54ef15d88254e01bc404136b987755588c1d171b800c2a47f93488b50", + "reviewedSourceSha256": "107b4db54ef15d88254e01bc404136b987755588c1d171b800c2a47f93488b50", + "status": "finite-key-domain-reviewed", + "runtimeCoverage": "CHECKLIST_UNKNOWN_SYSTEM_KEYS.md; 37 focused cases; other finite labels statically reviewed", + "ownerDependencies": [ + { + "file": "job-tracker-ui/src/applicationWorkspace.ts", + "sha256": "286c5f6b5c360dddaebcf1eaaaa1157b8dfb750e8a204b7863d59e8fd33ecf2f" + } + ] + }, + { + "file": "job-tracker-ui/src/views/ApplicationWorkspacePage.tsx", + "historicalLine": 395, + "expression": "t(`checklistItem_${nextStepToken}Description` as any)", + "historicalSourceSha256": "107b4db54ef15d88254e01bc404136b987755588c1d171b800c2a47f93488b50", + "reviewedSourceSha256": "107b4db54ef15d88254e01bc404136b987755588c1d171b800c2a47f93488b50", + "status": "finite-key-domain-reviewed", + "runtimeCoverage": "CHECKLIST_UNKNOWN_SYSTEM_KEYS.md; 37 focused cases; other finite labels statically reviewed", + "ownerDependencies": [ + { + "file": "job-tracker-ui/src/applicationWorkspace.ts", + "sha256": "286c5f6b5c360dddaebcf1eaaaa1157b8dfb750e8a204b7863d59e8fd33ecf2f" + } + ] + }, + { + "file": "job-tracker-ui/src/views/ApplicationWorkspacePage.tsx", + "historicalLine": 505, + "expression": "t(keys[section])", + "historicalSourceSha256": "107b4db54ef15d88254e01bc404136b987755588c1d171b800c2a47f93488b50", + "reviewedSourceSha256": "107b4db54ef15d88254e01bc404136b987755588c1d171b800c2a47f93488b50", + "status": "finite-key-domain-reviewed", + "runtimeCoverage": "CHECKLIST_UNKNOWN_SYSTEM_KEYS.md; 37 focused cases; other finite labels statically reviewed", + "ownerDependencies": [ + { + "file": "job-tracker-ui/src/applicationWorkspace.ts", + "sha256": "286c5f6b5c360dddaebcf1eaaaa1157b8dfb750e8a204b7863d59e8fd33ecf2f" + } + ] + }, + { + "file": "job-tracker-ui/src/views/career/CareerEvidenceBank.tsx", + "historicalLine": 83, + "expression": "t(`careerEvidenceCategory_${value}` as \"careerEvidenceCategory_achievement\")", + "historicalSourceSha256": "fc85ccfc1addd48af3a29bc263b089858765b2ac3ec7db3b66dedc55ddfab625", + "reviewedSourceSha256": "fc85ccfc1addd48af3a29bc263b089858765b2ac3ec7db3b66dedc55ddfab625", + "status": "finite-key-domain-reviewed", + "runtimeCoverage": "CHECKLIST_UNKNOWN_SYSTEM_KEYS.md; 37 focused cases; other finite labels statically reviewed" } ], "limitations": [ diff --git a/job-tracker-ui/src/application-checklist.test.tsx b/job-tracker-ui/src/application-checklist.test.tsx index 33d77973..ce4dca28 100644 --- a/job-tracker-ui/src/application-checklist.test.tsx +++ b/job-tracker-ui/src/application-checklist.test.tsx @@ -68,6 +68,14 @@ test("localizes system checklist content without changing user-authored tasks", expect(screen.getByText("Ask Sara for a referral")).toBeInTheDocument(); }); +test.each(['en', 'nb'])('preserves unknown historical system task text in %s', async language => { + window.localStorage.setItem('uiLanguage', language); + mockedApi.get.mockResolvedValue({ data: checklist([item({ systemKey: 'retired-system-task', title: 'Reviewed historical task', description: 'Original task details' })]) } as any); + renderChecklist(); + expect(await screen.findByText('Reviewed historical task')).toBeInTheDocument(); + expect(screen.getByText('Original task details')).toBeInTheDocument(); +}); + test("renders the checklist grouped by category with progress", async () => { renderChecklist(); diff --git a/job-tracker-ui/src/applicationWorkspace.ts b/job-tracker-ui/src/applicationWorkspace.ts index 2bd1ebbd..c051d763 100644 --- a/job-tracker-ui/src/applicationWorkspace.ts +++ b/job-tracker-ui/src/applicationWorkspace.ts @@ -1,4 +1,5 @@ import { api } from "./api"; +import { translations } from "./i18n/translations"; // Mirrors WorkspaceOverviewDto. Read-only aggregate — the workspace owns no data. export type WorkspaceCv = { @@ -93,7 +94,9 @@ export const WORKSPACE_SECTIONS: { key: WorkspaceSectionKey; label: string }[] = export function checklistSystemTranslationToken(systemKey?: string | null): string | null { if (!systemKey || systemKey.startsWith("learning:")) return null; - return systemKey.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase()); + const token = systemKey.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase()); + return Object.hasOwn(translations.en, `checklistItem_${token}`) + && Object.hasOwn(translations.en, `checklistItem_${token}Description`) ? token : null; } const LEGACY_WORKSPACE_SECTIONS: Record = { diff --git a/job-tracker-ui/src/checklist-translation-token.test.ts b/job-tracker-ui/src/checklist-translation-token.test.ts new file mode 100644 index 00000000..3e0cb269 --- /dev/null +++ b/job-tracker-ui/src/checklist-translation-token.test.ts @@ -0,0 +1,11 @@ +import { checklistSystemTranslationToken } from './applicationWorkspace'; + +test.each(['retired-system-task', 'constructor', '__proto__'])('does not manufacture translation keys for %s', key => { + expect(checklistSystemTranslationToken(key)).toBeNull(); +}); + +test('retains the known system translation and learning/custom boundaries', () => { + expect(checklistSystemTranslationToken('prepare-cv')).toBe('prepareCv'); + expect(checklistSystemTranslationToken('learning:prepare-cv')).toBeNull(); + expect(checklistSystemTranslationToken(null)).toBeNull(); +});