fix(i18n): retain historical checklist text for unknown system keys

This commit is contained in:
cesnimda
2026-11-02 22:55:38 +01:00
parent fae2f760cd
commit 4a3da3319e
5 changed files with 125 additions and 5 deletions
@@ -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.
+89 -4
View File
@@ -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": [
@@ -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();
+4 -1
View File
@@ -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<string, WorkspaceSectionKey> = {
@@ -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();
});