diff --git a/docs/i18n/ATTACHMENT_SIZE_FORMATTING.md b/docs/i18n/ATTACHMENT_SIZE_FORMATTING.md new file mode 100644 index 00000000..1601832c --- /dev/null +++ b/docs/i18n/ATTACHMENT_SIZE_FORMATTING.md @@ -0,0 +1,17 @@ +# Attachment size localisation + +3 November 2026. Isolated `codex/i18n-attachment-sizes` from published `ca519cacbec6ee8e385927ad31356dacc18b2f2d`. Ordinary localisation work is authorised independently of the pending A3 increment gate. This branch does not replace the newer A3 application tree. + +`Attachments` and the frozen submitted-package attachment list displayed decimal points through `toFixed`, even with Norwegian UI text. They now use `Intl.NumberFormat` with the existing UI language. The attachment table keeps its existing 1024-based KB/MB threshold, zero KB decimals and one MB decimal. Frozen package labels keep one KB decimal. No stored file size, filename, snapshot, route, API, revision, download or generation language changes. + +Verification: + +- Red regression: two Norwegian-format cases failed, one English case passed; 58 cases were filtered, not executed. Native `attachment-size-red.json`. +- Full affected suites: **61 passed**, zero failed/skipped, two suites; `attachment-size-tests.json`. Includes EN/NB MB labels and changing the UI locale on a retained frozen package without a write. +- Typecheck, scoped ESLint for the four affected files, static-export build and `git diff --check`: PASSED. +- AST-only graph refresh PASSED:18,702 nodes/46,195 edges/1,119 communities. Optional SQL parser is absent and143 files yielded no symbols; these are graph coverage limits. Generated graph stays local. +- Independent static review found no actionable issues; verification reruns were not added to the61 cases. + +Rollback reverts these four source/test changes; no database rollback is required. The published branch remains separate until its narrow delta is reconciled against the newer A3 baseline. Existing physical PDF metrics and visual acceptance are unaffected; no renderer source changed. + +NOT RUN: new browser/device or physical PDF checks, complete European localisation, native-language review, backend/provider/live-model or release qualification. Existing tests and code remain responsible for file permissions and downloads; this display-only change adds no new access path. The 188 historical dynamic-call reviews remain accounted for and are not expanded by these three numeric-format regressions. diff --git a/docs/i18n/ATTACHMENT_SIZE_MANIFEST.json b/docs/i18n/ATTACHMENT_SIZE_MANIFEST.json new file mode 100644 index 00000000..32ea04a8 --- /dev/null +++ b/docs/i18n/ATTACHMENT_SIZE_MANIFEST.json @@ -0,0 +1,13 @@ +{ + "base": "ca519cacbec6ee8e385927ad31356dacc18b2f2d", + "policy": "LF-normalised text SHA256", + "files": { + "job-tracker-ui/src/components/Attachments.tsx": "02081be8282cb3d1a525b336bededb8aabe561cc3b73e1b98e924f9ef945add1", + "job-tracker-ui/src/components/ApplicationAssets.tsx": "effcee6cfa0a43ebe0d73a0da625410dc98a6580fddda225b843e5e00d4e45f6", + "job-tracker-ui/src/attachments.test.tsx": "89bb3e277ae7be652073eec6d3b6188bcbeaea8b29b07478ff5b21654be8e7c1", + "job-tracker-ui/src/application-assets.test.tsx": "980a346cb4452ac4b7e6b689a963e5a23fc6d77410e47e66a422f9cd9c704e7b", + "docs/i18n/ATTACHMENT_SIZE_FORMATTING.md": "4576ccfc3de3b72cd3e5591c40f697b827d724b8f4fe588b54567dbe75252332", + "docs/i18n/attachment-size-red.json": "872882cb7d2937bf1df7f7a89810158c5de831381f56c6ae8bb0263d26b84d8e", + "docs/i18n/attachment-size-tests.json": "8b8188d432b7788f951cf2c67f790c6cd4d24553ed9accbeb702645b3d4b2c6a" + } +} diff --git a/docs/i18n/attachment-size-red.json b/docs/i18n/attachment-size-red.json new file mode 100644 index 00000000..05945393 --- /dev/null +++ b/docs/i18n/attachment-size-red.json @@ -0,0 +1 @@ +{"numFailedTestSuites":2,"numFailedTests":2,"numPassedTestSuites":0,"numPassedTests":1,"numPendingTestSuites":0,"numPendingTests":58,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":2,"numTotalTests":61,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1793729792954,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":[],"duration":334,"failing":false,"failureDetails":[{"name":"TestingLibraryElementError"}],"failureMessages":["TestingLibraryElementError: Unable to find an accessible element with the role \"button\" and name `/recorded.pdf.*1,5 KB/`\n\nHere are the accessible roles:\n\n region:\n\n Name \"Innsendte pakker\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n banner:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n heading:\n\n Name \"Innsendte pakker\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n paragraph:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n group:\n\n Name \"Innsendte pakker\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n status:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n button:\n\n Name \"Frys gjeldende pakke\":\n \u001b[36m\u001b[39m\n\n Name \"Vis bevart pakke\":\n \u001b[36m\u001b[39m\n\n Name \"recorded.pdf · 1.5 KB\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mInnsendte pakker\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mFrys den nøyaktige CV-en, søknadsbrevet, søknadsmaterialet og vedleggene som ble sendt.\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mInnhold lastet.\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mFrys gjeldende pakke\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mInnsendt pakke v1\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m19.7.2026, 12:00:00\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mOriginal CV · v2\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mSøknadsbrev\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mVis bevart pakke\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mrecorded.pdf\u001b[0m\n \u001b[0m · \u001b[0m\n \u001b[0m1.5\u001b[0m\n \u001b[0m KB\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\u001b[36m\u001b[39m\n at Object.getElementError (C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\@testing-library\\dom\\dist\\config.js:37:19)\n at C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\@testing-library\\dom\\dist\\query-helpers.js:76:38\n at C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\@testing-library\\dom\\dist\\query-helpers.js:52:17\n at C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\@testing-library\\dom\\dist\\query-helpers.js:95:19\n at Object.getByRole (C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\src\\application-assets.test.tsx:114:17)"],"fullName":"frozen attachment size follows UI language without changing stored package data","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793729810310,"status":"failed","title":"frozen attachment size follows UI language without changing stored package data"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-11 tailoring] generated metadata and known history enums follow locale while evidence, unknown metadata and dirty prose stay verbatim","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"[P7-11 tailoring] generated metadata and known history enums follow locale while evidence, unknown metadata and dirty prose stay verbatim"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"document package history is read-only and separates captured notes from answers","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"document package history is read-only and separates captured notes from answers"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"cv section shows the attached variant and the ones available to attach","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"cv section shows the attached variant and the ones available to attach"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"attaching a different variant only re-points the application","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"attaching a different variant only re-points the application"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"cv section points at the builder when there are no variants","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"cv section points at the builder when there are no variants"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"tailoring renders suggestions grouped by kind","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"tailoring renders suggestions grouped by kind"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"tailoring asks for a career profile when there is none","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"tailoring asks for a career profile when there is none"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"submitted package capture appends an immutable history entry","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"submitted package capture appends an immutable history entry"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"cover letter loads the current text and its history","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"cover letter loads the current text and its history"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"editing marks the draft dirty and saving sends the new text","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"editing marks the draft dirty and saving sends the new text"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"discarding returns to the saved text","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"discarding returns to the saved text"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"restoring an old version calls the restore endpoint","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"restoring an old version calls the restore endpoint"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"an empty cover letter offers the template and an empty history","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"an empty cover letter offers the template and an empty history"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"the empty cover-letter template and rich-text controls follow Bokmål UI language","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"the empty cover-letter template and rich-text controls follow Bokmål UI language"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"AI letter results are discarded when the source changes during generation","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"AI letter results are discarded when the source changes during generation"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter Improve writing uses the existing reviewed action","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"letter Improve writing uses the existing reviewed action"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter Suggest content uses the existing reviewed action","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"letter Suggest content uses the existing reviewed action"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter Grammar check uses the existing reviewed action","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"letter Grammar check uses the existing reviewed action"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter Shorter uses the existing reviewed action","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"letter Shorter uses the existing reviewed action"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"cancelling letter generation ignores its late response","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"cancelling letter generation ignores its late response"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"AI cover letter suggestions use the linked CV and require explicit apply","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"AI cover letter suggestions use the linked CV and require explicit apply"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter AI revalidates its recorded source before applying and refuses a cross-tab source conflict","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"letter AI revalidates its recorded source before applying and refuses a cross-tab source conflict"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter AI language follows the saved document and updates the same local language draft","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"letter AI language follows the saved document and updates the same local language draft"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter AI ignores a late source validation after local edit","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"letter AI ignores a late source validation after local edit"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter AI ignores a late source validation after local cancel","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"letter AI ignores a late source validation after local cancel"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter AI ignores a late source validation after local job change","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"letter AI ignores a late source validation after local job change"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held letter Save prevents an in-flight AI validation from replacing the submitted snapshot","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"a held letter Save prevents an in-flight AI validation from replacing the submitted snapshot"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-W06] rejecting an AI cover letter preserves the saved draft without a write","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"[PRES-W06] rejecting an AI cover letter preserves the saved draft without a write"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a failed load surfaces an error","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"a failed load surfaces an error"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"application answer and recruiter drafts save from the dedicated workspace","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"application answer and recruiter drafts save from the dedicated workspace"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"application package drafts can be cleared without deleting ordinary notes","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810674,"status":"pending","title":"application package drafts can be cleared without deleting ordinary notes"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed cover-letter saving retains editable dirty text and original Save recovery across a live locale change","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"failed cover-letter saving retains editable dirty text and original Save recovery across a live locale change"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed application-answer saving keeps both dirty drafts editable and retries only the original save payload","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"failed application-answer saving keeps both dirty drafts editable and retries only the original save payload"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed CV attachment leaves the current association and selector available for the original explicit action","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"failed CV attachment leaves the current association and selector available for the original explicit action"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"dirty version restore cancels without a write and confirmed held restore appends history exactly once","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"dirty version restore cancels without a write and confirmed held restore appends history exactly once"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"held letter Save submits one snapshot and prevents an AI Apply from replacing its pending draft","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"held letter Save submits one snapshot and prevents an AI Apply from replacing its pending draft"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"held AI generation locks its controls and preserves captured action attribution after a later action selection","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"held AI generation locks its controls and preserves captured action attribution after a later action selection"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"capture, detail and download failures keep owned frozen history and recover only through their original explicit actions","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"capture, detail and download failures keep owned frozen history and recover only through their original explicit actions"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"all five asset read owners expose GET-only recovery without false empty history or locale refetch","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"all five asset read owners expose GET-only recovery without false empty history or locale refetch"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"keyed job changes stop a late CV copy from attaching and never reuse another job's saved-copy recovery","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"keyed job changes stop a late CV copy from attaching and never reuse another job's saved-copy recovery"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"keyed job changes keep job B's answer drafts and callbacks independent of job A's late save","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"keyed job changes keep job B's answer drafts and callbacks independent of job A's late save"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a parent refresh cannot attach a newer revision to dirty application drafts","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"a parent refresh cannot attach a newer revision to dirty application drafts"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"cover-letter rejection (409) retains draft and source revision without a fresh read","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"cover-letter rejection (409) retains draft and source revision without a fresh read"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"cover-letter rejection (0) retains draft and source revision without a fresh read","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"cover-letter rejection (0) retains draft and source revision without a fresh read"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter text preview shows unsaved formatting, hides the inactive narrow pane and never saves on switching","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"letter text preview shows unsaved formatting, hides the inactive narrow pane and never saves on switching"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter text preview renders unsafe Markdown as inert text","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"letter text preview renders unsafe Markdown as inert text"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter preview retains the draft after cancelled restore and failed save","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"letter preview retains the draft after cancelled restore and failed save"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"keyed letter job changes discard the old preview and ignore its late save","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"keyed letter job changes discard the old preview and ignore its late save"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"wide letter workspace keeps one editor beside its live preview","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"wide letter workspace keeps one editor beside its live preview"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter preview controls follow Bokmål without translating or saving the local draft","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"letter preview controls follow Bokmål without translating or saving the local draft"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"document settings are dirty local drafts until explicit Save, and Discard restores saved details","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"document settings are dirty local drafts until explicit Save, and Discard restores saved details"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter PDF awaits durable settings and blocks duplicate exports; failed save never exports","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"letter PDF awaits durable settings and blocks duplicate exports; failed save never exports"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter document preview ignores stale responses, keeps last good HTML on failure and retries without saving","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729810675,"status":"pending","title":"letter document preview ignores stale responses, keeps last good HTML on failure and retries without saving"}],"endTime":1793729810696,"message":" ● frozen attachment size follows UI language without changing stored package data\n\n TestingLibraryElementError: Unable to find an accessible element with the role \"button\" and name `/recorded.pdf.*1,5 KB/`\n\n Here are the accessible roles:\n\n region:\n\n Name \"Innsendte pakker\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n banner:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n heading:\n\n Name \"Innsendte pakker\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n paragraph:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n group:\n\n Name \"Innsendte pakker\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n status:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n button:\n\n Name \"Frys gjeldende pakke\":\n \u001b[36m\u001b[39m\n\n Name \"Vis bevart pakke\":\n \u001b[36m\u001b[39m\n\n Name \"recorded.pdf · 1.5 KB\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n\n Ignored nodes: comments, script, style\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mInnsendte pakker\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mFrys den nøyaktige CV-en, søknadsbrevet, søknadsmaterialet og vedleggene som ble sendt.\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mInnhold lastet.\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mFrys gjeldende pakke\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mInnsendt pakke v1\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m19.7.2026, 12:00:00\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mOriginal CV · v2\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mSøknadsbrev\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mVis bevart pakke\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mrecorded.pdf\u001b[0m\n \u001b[0m · \u001b[0m\n \u001b[0m1.5\u001b[0m\n \u001b[0m KB\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\n 112 | expect(await screen.findByRole('button', { name: /recorded.pdf.*1.5 KB/ })).toBeInTheDocument();\n 113 | changeLanguage('nb');\n > 114 | expect(screen.getByRole('button', { name: /recorded.pdf.*1,5 KB/ })).toBeInTheDocument();\n | ^\n 115 | expect(summary.attachments[0]).toEqual(attachment);\n 116 | expect(mockedApi.post).not.toHaveBeenCalled(); expect(mockedApi.put).not.toHaveBeenCalled();\n 117 | });\n\n at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:37:19)\n at node_modules/@testing-library/dom/dist/query-helpers.js:76:38\n at node_modules/@testing-library/dom/dist/query-helpers.js:52:17\n at node_modules/@testing-library/dom/dist/query-helpers.js:95:19\n at Object.getByRole (src/application-assets.test.tsx:114:17)\n","name":"C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\src\\application-assets.test.tsx","startTime":1793729796389,"status":"failed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":170,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"attachment size follows the UI locale (en)","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793729812304,"status":"passed","title":"attachment size follows the UI locale (en)"},{"ancestorTitles":[],"duration":4039,"failing":false,"failureDetails":[{"name":"TestingLibraryElementError"}],"failureMessages":["Error: Unable to find an element with the text: 1,5 MB. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mVedlegg (1)\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mLast opp CV-er, søknadsbrev, porteføljer og støttedokumenter for denne søknaden.\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m0 bilder\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m1 PDF-er\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mMaks 10 MB hver\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mLast opp\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mDra og slipp filer her\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0meller bruk opplastingsknappen for å velge dokumenter fra enheten din.\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mInnhold lastet.\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mNavn\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mType\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mFiltype\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mStørrelse\u001b[0m\n ...\n at waitForWrapper (C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\@testing-library\\dom\\dist\\wait-for.js:163:27)\n at C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\@testing-library\\dom\\dist\\query-helpers.js:86:33\n at findByText (C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\src\\attachments.test.tsx:30:29)\n at Object. (C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\jest-each\\build\\index.js:80:140)\n at Promise.finally.completed (C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1501:10)\n at _callCircusTest (C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1011:40)\n at _runTest (C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:951:3)\n at C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:866:11)\n at run (C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1993:21)\n at jestAdapter (C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\runner.js:111:19)\n at runTestInternal (C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\jest-runner\\build\\index.js:276:16)\n at runTest (C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\node_modules\\jest-runner\\build\\index.js:344:7)"],"fullName":"attachment size follows the UI locale (nb)","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729812474,"status":"failed","title":"attachment size follows the UI locale (nb)"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"PDF preview identifies the document in en","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729816513,"status":"pending","title":"PDF preview identifies the document in en"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"PDF preview identifies the document in nb","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729816513,"status":"pending","title":"PDF preview identifies the document in nb"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"attachments empty state renders drag and drop guidance","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729816513,"status":"pending","title":"attachments empty state renders drag and drop guidance"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"attachments metadata controls update purpose and ai usage","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729816513,"status":"pending","title":"attachments metadata controls update purpose and ai usage"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"attachment failures remain explicit and retry restores the real empty state","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793729816513,"status":"pending","title":"attachment failures remain explicit and retry restores the real empty state"}],"endTime":1793729816524,"message":" ● attachment size follows the UI locale (nb)\n\n Unable to find an element with the text: 1,5 MB. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.\n\n Ignored nodes: comments, script, style\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mVedlegg (1)\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mLast opp CV-er, søknadsbrev, porteføljer og støttedokumenter for denne søknaden.\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m0 bilder\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m1 PDF-er\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mMaks 10 MB hver\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mLast opp\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mDra og slipp filer her\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0meller bruk opplastingsknappen for å velge dokumenter fra enheten din.\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mInnhold lastet.\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mNavn\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mType\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mFiltype\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mStørrelse\u001b[0m\n ...\n\n 28 | mockedApi.get.mockResolvedValueOnce({ data: [{ id: 8, fileName: 'recorded.pdf', fileType: 'application/pdf', fileSize: 1572864, uploadDate: '2026-11-03T10:00:00Z' }] } as any);\n 29 | const view = render();\n > 30 | try { expect(await screen.findByText(size)).toBeInTheDocument(); expect(mockedApi.post).not.toHaveBeenCalled(); }\n | ^\n 31 | finally { view.unmount(); if (previous === null) localStorage.removeItem('uiLanguage'); else localStorage.setItem('uiLanguage', previous); }\n 32 | });\n 33 |\n\n at waitForWrapper (node_modules/@testing-library/dom/dist/wait-for.js:163:27)\n at node_modules/@testing-library/dom/dist/query-helpers.js:86:33\n at findByText (src/attachments.test.tsx:30:29)\n","name":"C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\src\\attachments.test.tsx","startTime":1793729810723,"status":"failed","summary":""}],"wasInterrupted":false} diff --git a/docs/i18n/attachment-size-tests.json b/docs/i18n/attachment-size-tests.json new file mode 100644 index 00000000..691f0b3a --- /dev/null +++ b/docs/i18n/attachment-size-tests.json @@ -0,0 +1 @@ +{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":2,"numPassedTests":61,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":2,"numTotalTests":61,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1793729832479,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":[],"duration":424,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"frozen attachment size follows UI language without changing stored package data","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793729838927,"status":"passed","title":"frozen attachment size follows UI language without changing stored package data"},{"ancestorTitles":[],"duration":968,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-11 tailoring] generated metadata and known history enums follow locale while evidence, unknown metadata and dirty prose stay verbatim","invocations":1,"location":null,"numPassingAsserts":13,"retryReasons":[],"startAt":1793729839351,"status":"passed","title":"[P7-11 tailoring] generated metadata and known history enums follow locale while evidence, unknown metadata and dirty prose stay verbatim"},{"ancestorTitles":[],"duration":191,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"document package history is read-only and separates captured notes from answers","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793729840319,"status":"passed","title":"document package history is read-only and separates captured notes from answers"},{"ancestorTitles":[],"duration":128,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"cv section shows the attached variant and the ones available to attach","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793729840510,"status":"passed","title":"cv section shows the attached variant and the ones available to attach"},{"ancestorTitles":[],"duration":277,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"attaching a different variant only re-points the application","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793729840639,"status":"passed","title":"attaching a different variant only re-points the application"},{"ancestorTitles":[],"duration":130,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"cv section points at the builder when there are no variants","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793729840916,"status":"passed","title":"cv section points at the builder when there are no variants"},{"ancestorTitles":[],"duration":133,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"tailoring renders suggestions grouped by kind","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793729841047,"status":"passed","title":"tailoring renders suggestions grouped by kind"},{"ancestorTitles":[],"duration":138,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"tailoring asks for a career profile when there is none","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793729841181,"status":"passed","title":"tailoring asks for a career profile when there is none"},{"ancestorTitles":[],"duration":202,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"submitted package capture appends an immutable history entry","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793729841319,"status":"passed","title":"submitted package capture appends an immutable history entry"},{"ancestorTitles":[],"duration":328,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"cover letter loads the current text and its history","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793729841522,"status":"passed","title":"cover letter loads the current text and its history"},{"ancestorTitles":[],"duration":683,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"editing marks the draft dirty and saving sends the new text","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793729841851,"status":"passed","title":"editing marks the draft dirty and saving sends the new text"},{"ancestorTitles":[],"duration":401,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"discarding returns to the saved text","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793729842534,"status":"passed","title":"discarding returns to the saved text"},{"ancestorTitles":[],"duration":433,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"restoring an old version calls the restore endpoint","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793729842935,"status":"passed","title":"restoring an old version calls the restore endpoint"},{"ancestorTitles":[],"duration":380,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"an empty cover letter offers the template and an empty history","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793729843368,"status":"passed","title":"an empty cover letter offers the template and an empty history"},{"ancestorTitles":[],"duration":393,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"the empty cover-letter template and rich-text controls follow Bokmål UI language","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793729843748,"status":"passed","title":"the empty cover-letter template and rich-text controls follow Bokmål UI language"},{"ancestorTitles":[],"duration":467,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"AI letter results are discarded when the source changes during generation","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793729844141,"status":"passed","title":"AI letter results are discarded when the source changes during generation"},{"ancestorTitles":[],"duration":474,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter Improve writing uses the existing reviewed action","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793729844608,"status":"passed","title":"letter Improve writing uses the existing reviewed action"},{"ancestorTitles":[],"duration":544,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter Suggest content uses the existing reviewed action","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793729845082,"status":"passed","title":"letter Suggest content uses the existing reviewed action"},{"ancestorTitles":[],"duration":402,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter Grammar check uses the existing reviewed action","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793729845626,"status":"passed","title":"letter Grammar check uses the existing reviewed action"},{"ancestorTitles":[],"duration":400,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter Shorter uses the existing reviewed action","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793729846028,"status":"passed","title":"letter Shorter uses the existing reviewed action"},{"ancestorTitles":[],"duration":431,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"cancelling letter generation ignores its late response","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793729846428,"status":"passed","title":"cancelling letter generation ignores its late response"},{"ancestorTitles":[],"duration":990,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"AI cover letter suggestions use the linked CV and require explicit apply","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793729846859,"status":"passed","title":"AI cover letter suggestions use the linked CV and require explicit apply"},{"ancestorTitles":[],"duration":630,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter AI revalidates its recorded source before applying and refuses a cross-tab source conflict","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793729847849,"status":"passed","title":"letter AI revalidates its recorded source before applying and refuses a cross-tab source conflict"},{"ancestorTitles":[],"duration":1106,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter AI language follows the saved document and updates the same local language draft","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793729848479,"status":"passed","title":"letter AI language follows the saved document and updates the same local language draft"},{"ancestorTitles":[],"duration":795,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter AI ignores a late source validation after local edit","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793729849585,"status":"passed","title":"letter AI ignores a late source validation after local edit"},{"ancestorTitles":[],"duration":685,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter AI ignores a late source validation after local cancel","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793729850380,"status":"passed","title":"letter AI ignores a late source validation after local cancel"},{"ancestorTitles":[],"duration":1647,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter AI ignores a late source validation after local job change","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793729851065,"status":"passed","title":"letter AI ignores a late source validation after local job change"},{"ancestorTitles":[],"duration":1462,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held letter Save prevents an in-flight AI validation from replacing the submitted snapshot","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793729852712,"status":"passed","title":"a held letter Save prevents an in-flight AI validation from replacing the submitted snapshot"},{"ancestorTitles":[],"duration":933,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-W06] rejecting an AI cover letter preserves the saved draft without a write","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793729854175,"status":"passed","title":"[PRES-W06] rejecting an AI cover letter preserves the saved draft without a write"},{"ancestorTitles":[],"duration":105,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a failed load surfaces an error","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793729855109,"status":"passed","title":"a failed load surfaces an error"},{"ancestorTitles":[],"duration":355,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"application answer and recruiter drafts save from the dedicated workspace","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793729855214,"status":"passed","title":"application answer and recruiter drafts save from the dedicated workspace"},{"ancestorTitles":[],"duration":681,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"application package drafts can be cleared without deleting ordinary notes","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793729855569,"status":"passed","title":"application package drafts can be cleared without deleting ordinary notes"},{"ancestorTitles":[],"duration":1496,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed cover-letter saving retains editable dirty text and original Save recovery across a live locale change","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793729856250,"status":"passed","title":"failed cover-letter saving retains editable dirty text and original Save recovery across a live locale change"},{"ancestorTitles":[],"duration":691,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed application-answer saving keeps both dirty drafts editable and retries only the original save payload","invocations":1,"location":null,"numPassingAsserts":14,"retryReasons":[],"startAt":1793729857746,"status":"passed","title":"failed application-answer saving keeps both dirty drafts editable and retries only the original save payload"},{"ancestorTitles":[],"duration":652,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed CV attachment leaves the current association and selector available for the original explicit action","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793729858437,"status":"passed","title":"failed CV attachment leaves the current association and selector available for the original explicit action"},{"ancestorTitles":[],"duration":1653,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"dirty version restore cancels without a write and confirmed held restore appends history exactly once","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793729859090,"status":"passed","title":"dirty version restore cancels without a write and confirmed held restore appends history exactly once"},{"ancestorTitles":[],"duration":1195,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"held letter Save submits one snapshot and prevents an AI Apply from replacing its pending draft","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793729860743,"status":"passed","title":"held letter Save submits one snapshot and prevents an AI Apply from replacing its pending draft"},{"ancestorTitles":[],"duration":1915,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"held AI generation locks its controls and preserves captured action attribution after a later action selection","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793729861939,"status":"passed","title":"held AI generation locks its controls and preserves captured action attribution after a later action selection"},{"ancestorTitles":[],"duration":1076,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"capture, detail and download failures keep owned frozen history and recover only through their original explicit actions","invocations":1,"location":null,"numPassingAsserts":23,"retryReasons":[],"startAt":1793729863854,"status":"passed","title":"capture, detail and download failures keep owned frozen history and recover only through their original explicit actions"},{"ancestorTitles":[],"duration":1330,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"all five asset read owners expose GET-only recovery without false empty history or locale refetch","invocations":1,"location":null,"numPassingAsserts":34,"retryReasons":[],"startAt":1793729864931,"status":"passed","title":"all five asset read owners expose GET-only recovery without false empty history or locale refetch"},{"ancestorTitles":[],"duration":632,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"keyed job changes stop a late CV copy from attaching and never reuse another job's saved-copy recovery","invocations":1,"location":null,"numPassingAsserts":19,"retryReasons":[],"startAt":1793729866261,"status":"passed","title":"keyed job changes stop a late CV copy from attaching and never reuse another job's saved-copy recovery"},{"ancestorTitles":[],"duration":822,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"keyed job changes keep job B's answer drafts and callbacks independent of job A's late save","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793729866893,"status":"passed","title":"keyed job changes keep job B's answer drafts and callbacks independent of job A's late save"},{"ancestorTitles":[],"duration":631,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a parent refresh cannot attach a newer revision to dirty application drafts","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793729867715,"status":"passed","title":"a parent refresh cannot attach a newer revision to dirty application drafts"},{"ancestorTitles":[],"duration":1003,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"cover-letter rejection (409) retains draft and source revision without a fresh read","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793729868346,"status":"passed","title":"cover-letter rejection (409) retains draft and source revision without a fresh read"},{"ancestorTitles":[],"duration":1046,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"cover-letter rejection (0) retains draft and source revision without a fresh read","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793729869350,"status":"passed","title":"cover-letter rejection (0) retains draft and source revision without a fresh read"},{"ancestorTitles":[],"duration":774,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter text preview shows unsaved formatting, hides the inactive narrow pane and never saves on switching","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793729870397,"status":"passed","title":"letter text preview shows unsaved formatting, hides the inactive narrow pane and never saves on switching"},{"ancestorTitles":[],"duration":882,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter text preview renders unsafe Markdown as inert text","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793729871171,"status":"passed","title":"letter text preview renders unsafe Markdown as inert text"},{"ancestorTitles":[],"duration":2205,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter preview retains the draft after cancelled restore and failed save","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793729872054,"status":"passed","title":"letter preview retains the draft after cancelled restore and failed save"},{"ancestorTitles":[],"duration":1746,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"keyed letter job changes discard the old preview and ignore its late save","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793729874259,"status":"passed","title":"keyed letter job changes discard the old preview and ignore its late save"},{"ancestorTitles":[],"duration":580,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"wide letter workspace keeps one editor beside its live preview","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793729876005,"status":"passed","title":"wide letter workspace keeps one editor beside its live preview"},{"ancestorTitles":[],"duration":1037,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter preview controls follow Bokmål without translating or saving the local draft","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793729876585,"status":"passed","title":"letter preview controls follow Bokmål without translating or saving the local draft"},{"ancestorTitles":[],"duration":2206,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"document settings are dirty local drafts until explicit Save, and Discard restores saved details","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793729877622,"status":"passed","title":"document settings are dirty local drafts until explicit Save, and Discard restores saved details"},{"ancestorTitles":[],"duration":1531,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter PDF awaits durable settings and blocks duplicate exports; failed save never exports","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793729879829,"status":"passed","title":"letter PDF awaits durable settings and blocks duplicate exports; failed save never exports"},{"ancestorTitles":[],"duration":3824,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"letter document preview ignores stale responses, keeps last good HTML on failure and retries without saving","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793729881360,"status":"passed","title":"letter document preview ignores stale responses, keeps last good HTML on failure and retries without saving"}],"endTime":1793729885187,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\src\\application-assets.test.tsx","startTime":1793729833997,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":162,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"attachment size follows the UI locale (en)","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793729887040,"status":"passed","title":"attachment size follows the UI locale (en)"},{"ancestorTitles":[],"duration":106,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"attachment size follows the UI locale (nb)","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793729887203,"status":"passed","title":"attachment size follows the UI locale (nb)"},{"ancestorTitles":[],"duration":248,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"PDF preview identifies the document in en","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793729887309,"status":"passed","title":"PDF preview identifies the document in en"},{"ancestorTitles":[],"duration":231,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"PDF preview identifies the document in nb","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793729887557,"status":"passed","title":"PDF preview identifies the document in nb"},{"ancestorTitles":[],"duration":64,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"attachments empty state renders drag and drop guidance","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793729887788,"status":"passed","title":"attachments empty state renders drag and drop guidance"},{"ancestorTitles":[],"duration":447,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"attachments metadata controls update purpose and ai usage","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793729887852,"status":"passed","title":"attachments metadata controls update purpose and ai usage"},{"ancestorTitles":[],"duration":108,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"attachment failures remain explicit and retry restores the real empty state","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793729888299,"status":"passed","title":"attachment failures remain explicit and retry restores the real empty state"}],"endTime":1793729888408,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\i18n-00-audit\\Job tracker\\job-tracker-ui\\src\\attachments.test.tsx","startTime":1793729885206,"status":"passed","summary":""}],"wasInterrupted":false} diff --git a/job-tracker-ui/src/application-assets.test.tsx b/job-tracker-ui/src/application-assets.test.tsx index 8190d53e..5eb30195 100644 --- a/job-tracker-ui/src/application-assets.test.tsx +++ b/job-tracker-ui/src/application-assets.test.tsx @@ -104,6 +104,18 @@ function changeLanguage(language: UiLanguage) { act(() => { window.dispatchEvent(new StorageEvent("storage", { key: "uiLanguage", newValue: language })); }); } +test('frozen attachment size follows UI language without changing stored package data', async () => { + const attachment = { id: 81, fileName: 'recorded.pdf', fileSize: 1536, fileType: 'application/pdf' }; + const summary = { id: 91, version: 1, cvVariantId: 3, cvVariantName: 'Original CV', cvVariantVersion: 2, cvThemeId: 'nordic', hasCv: true, hasCoverLetter: true, createdAtUtc: '2026-07-19T10:00:00Z', attachments: [attachment] }; + routeGet({ submittedPackages: [summary] }); + render(); + expect(await screen.findByRole('button', { name: /recorded.pdf.*1.5 KB/ })).toBeInTheDocument(); + changeLanguage('nb'); + expect(screen.getByRole('button', { name: /recorded.pdf.*1,5 KB/ })).toBeInTheDocument(); + expect(summary.attachments[0]).toEqual(attachment); + expect(mockedApi.post).not.toHaveBeenCalled(); expect(mockedApi.put).not.toHaveBeenCalled(); +}); + function deferred() { let resolve!: (value: T) => void; let reject!: (reason: unknown) => void; diff --git a/job-tracker-ui/src/attachments.test.tsx b/job-tracker-ui/src/attachments.test.tsx index 1e7df395..bce8e085 100644 --- a/job-tracker-ui/src/attachments.test.tsx +++ b/job-tracker-ui/src/attachments.test.tsx @@ -22,6 +22,15 @@ const mockedApi = api as jest.Mocked; beforeEach(() => jest.clearAllMocks()); +test.each([['en', '1.5 MB'], ['nb', '1,5 MB']])('attachment size follows the UI locale (%s)', async (language, size) => { + const previous = localStorage.getItem('uiLanguage'); + localStorage.setItem('uiLanguage', language); + mockedApi.get.mockResolvedValueOnce({ data: [{ id: 8, fileName: 'recorded.pdf', fileType: 'application/pdf', fileSize: 1572864, uploadDate: '2026-11-03T10:00:00Z' }] } as any); + const view = render(); + try { expect(await screen.findByText(size)).toBeInTheDocument(); expect(mockedApi.post).not.toHaveBeenCalled(); } + finally { view.unmount(); if (previous === null) localStorage.removeItem('uiLanguage'); else localStorage.setItem('uiLanguage', previous); } +}); + test.each([ ['en', 'Preview: resume.pdf', 'Preview: resume.pdf'], ['nb', 'Forhåndsvis: resume.pdf', 'Forhåndsvisning: resume.pdf'], diff --git a/job-tracker-ui/src/components/ApplicationAssets.tsx b/job-tracker-ui/src/components/ApplicationAssets.tsx index 6d45bcd5..a9ffa1b9 100644 --- a/job-tracker-ui/src/components/ApplicationAssets.tsx +++ b/job-tracker-ui/src/components/ApplicationAssets.tsx @@ -357,7 +357,7 @@ export function SubmittedPackagesSection({ jobId, allowCapture = true }: { jobId {item.attachments.map((attachment) => ( ))} diff --git a/job-tracker-ui/src/components/Attachments.tsx b/job-tracker-ui/src/components/Attachments.tsx index b9e0d17d..5168c67c 100644 --- a/job-tracker-ui/src/components/Attachments.tsx +++ b/job-tracker-ui/src/components/Attachments.tsx @@ -47,11 +47,12 @@ interface AttachmentItem { useForAi: boolean; } -function fmtSize(n: number) { +function fmtSize(n: number, language: string) { if (!n) return ""; const kb = n / 1024; - if (kb < 1024) return `${kb.toFixed(0)} KB`; - return `${(kb / 1024).toFixed(1)} MB`; + const digits = kb < 1024 ? 0 : 1; + const value = new Intl.NumberFormat(language === "nb" ? "nb-NO" : "en", { minimumFractionDigits: digits, maximumFractionDigits: digits }).format(kb < 1024 ? kb : kb / 1024); + return `${value} ${kb < 1024 ? "KB" : "MB"}`; } function isImageType(t: string) { @@ -303,7 +304,7 @@ export default function Attachments({ jobId }: { jobId: number }) { {kind} {a.fileType ? a.fileType.replace("application/", "") : ""} - {a.fileSize ? fmtSize(a.fileSize) : ""} + {a.fileSize ? fmtSize(a.fileSize, language) : ""} {a.uploadDate ? new Date(a.uploadDate).toLocaleString(language === "nb" ? "nb-NO" : "en") : ""}