From a94ea0a4a4695d1c196366ac39793a901485e919 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Sun, 11 Oct 2026 03:05:08 +0200 Subject: [PATCH] fix(ui): align semantic headings contrast and Settings panels --- docs/redesign/phase-6/p6-01/CORRECTIONS.md | 47 +++++++++++++++++++ docs/redesign/phase-6/p6-01/RESULTS.md | 11 +++++ job-tracker-ui/src/admin-audit-page.test.tsx | 2 + job-tracker-ui/src/admin-system-page.test.tsx | 14 ++++++ job-tracker-ui/src/admin-users-page.test.tsx | 2 + .../src/components/SettingsView.tsx | 20 ++++---- .../src/cv-builder-deep-link.test.tsx | 9 ++++ job-tracker-ui/src/login-page.test.tsx | 7 +++ job-tracker-ui/src/profile-page.test.tsx | 31 ++++++++++++ job-tracker-ui/src/settings-view.test.tsx | 16 +++++++ job-tracker-ui/src/views/AdminAuditPage.tsx | 2 +- job-tracker-ui/src/views/AdminSystemPage.tsx | 42 ++++++++--------- job-tracker-ui/src/views/AdminUsersPage.tsx | 2 +- .../src/views/CareerProfilePage.tsx | 16 +++---- job-tracker-ui/src/views/CvBuilderEditor.tsx | 2 +- job-tracker-ui/src/views/LoginPage.tsx | 3 +- job-tracker-ui/src/views/ProfilePage.tsx | 8 ++-- .../views/career/CareerProfileSections.tsx | 10 ++-- 18 files changed, 192 insertions(+), 52 deletions(-) create mode 100644 docs/redesign/phase-6/p6-01/CORRECTIONS.md create mode 100644 docs/redesign/phase-6/p6-01/RESULTS.md diff --git a/docs/redesign/phase-6/p6-01/CORRECTIONS.md b/docs/redesign/phase-6/p6-01/CORRECTIONS.md new file mode 100644 index 0000000..249aaf3 --- /dev/null +++ b/docs/redesign/phase-6/p6-01/CORRECTIONS.md @@ -0,0 +1,47 @@ +# P6-01 — Shared design-system corrections + +Current owner's P6-01 follows [P6-00 audit](../p6-00/README.md). The earlier README/FINDINGS in this folder retain their historical checkpoint meaning. Baseline application source is `9d6d29d`; all existing dirty company/i18n/toast/browser changes were preserved. Parent execution log owns final commit hashes, checkpoint pushes and remote verification. + +## Bounded changes + +| Audit finding | Existing owners / correction | Protected behavior and focused evidence | +|---|---|---| +| P6-DC01 | CareerProfilePage and ProfilePage Avatar use existing primary.main/primary.contrastText instead of default grey/white. | Image upload/crop/remove and identity source unchanged. Two initials tests prove rendered default-theme contrast reaches 4.5; real app themes are separately measured by parent's native evidence. | +| P6-DC02 | Login root gains main; existing h5-style title explicitly renders h1. | Sign-in/register/provider/verification/2FA/remember-me payloads and routes unchanged. Two mode cases identify title inside main. | +| P6-DC03 | Career/Profile/Admin contextual titles/sections get explicit h2/h3/h4; metric values remain styled text. CV Content Sections becomes h3 under existing h2 editing region. Career field-group headings are h4; raw accordion subtitle is span inside existing MUI heading. | Styling/font/spacing remain approved. No global variant mapping or print renderer edit. Career/Admin actual heading sequence, account hierarchy and CV no-write test preserve route-specific context. Existing Admin audit/users cases now require contextual level-two title. | +| P6-DC04 | Settings existing tabs link settings-tab-0…3 to settings-panel-0…3. Existing active-only TabPanel gets role, labelledby and translated label fallback. | Inactive panel unmount behavior, Rules session/draft owner, save/discard and mobile selector unchanged. Test traverses all four sections and phone selector. | + +Every changed presentational owner uses MUI/Emotion and existing translations/tokens. No new component framework, font, option, form model, save model, navigation or information architecture was introduced. AppShell is unchanged: the parent's settled native naming proof must determine whether historical transitional flags represent a defect. P5-13 stays withheld/deferred; no model run, AI activation or renderer/pagination modification occurs. + +## Test-first record and current evidence + +Local raw logs and exact Jest JSON remain under `.artifacts/phase6/current/design/`. They are execution artifacts, not portable browser/human qualification. + +- `red.json`: initial eight new cases fail. Initial test setup also used an incorrect CV label and expected DU instead of actual unchanged DD initials; these setup errors are retained rather than called product regressions. +- `red-corrected.json` / `red-initials.json`: corrected CV semantics and both Avatar rendered pairs fail for the intended missing feature; initials default pair is 1.878 in the default MUI/JSDOM test environment, distinct from historical real-app ratios. +- `red-admin.json`: contextual title assertions fail before Admin source changes. +- `red-hierarchy.json`: after actual Career data loads, descendant h6 skips h3; explicit contextual field-group semantics repair it. Earlier wrong Career label and early empty field setup are retained in intermediate logs. +- `green.json`: first seven-suite run has 117 passing / 2 failures. One was the corrected Career test setup; the new Admin test also required its own jest-dom matcher import. No product assertion was relaxed. +- `green-final.json` and `green-semantic.json`: detect a remaining genuine Admin latest-summary subtitle2=h6 after h4. Its semantic component is corrected to h4. +- **`green-race-semantic.json`: 30/30 across complete Profile, AdminSystem and I18n suites, zero failed/skipped/todo.** Other six complete suites in the prior focused run pass their unchanged tests; parent's fresh integrated full-suite run is authoritative final acceptance after the last source edit. +- Whitespace check passes. Final type/lint/build, native theme/locale/reflow, complete browser and full regression evidence belong to the parent execution manifest, not an inferred PASS here. + +Added semantic tests: Login 2, Settings 1, meaningful initials 2, Career hierarchy 1, account hierarchy 1, CV hierarchy/no-write 1, AdminSystem hierarchy 1 = **9**; AdminUsers/Audit existing cases were strengthened without changing counts. Existing dirty feedback tests and final full-suite count reconciliation are separately owned by P6-02/final acceptance. + +## P6-02 dependency discovered during independent review + +The existing dirty language-feedback repair could deliver an older failure after a newer successful choice, or keep an earlier Retry that reapplied an obsolete language. Independent review reproduced both cases before fixing them. `red-language-race.json` records two actual failures: obsolete alert delivery and EN reverting to NB through stale Retry. + +I18nProvider now keeps the current choice and latest preference-request sequence at the existing owner. Both failure delivery and Retry check those values; ordinary account save still uses the same endpoint/payload, local choice still changes immediately, server hydration still avoids a write, and storage changes update the current-choice guard. `green-race-semantic.json` includes both passing regressions in the complete six-case I18n suite. **Two additional race tests** are counted in P6-02, separate from nine semantic cases. This is a frontend stale-feedback repair, not a diagnosis or repair of historical database preference500 contention. + +Company failed-save Alert/draft/retry and consistent list/card date locales were independently reviewed as bounded existing-owner corrections. Their final focused/browser results are parent-owned; no duplicate source edits were made here. + +## Preservation and outcome + +Applicable traces: PRES-E02/E06/E07/E08, C06–C09, S01/S03/S04; canonical profile/CV override/save owners and authorization remain untouched. All 57 inherited groups remain contractual; no group is promoted to full PASS from finite semantic tests. P2-D01 must remain ordinary passing in final native regression. + +Bounded corrections are ready for integrated validation. Human screen-reader, physical touch/software keyboard, production-equivalent font/photo, contextual preview main/incomplete axe and operational release evidence remain unverified. Full Phase 7 visual migration and Phase 8 customization were not attempted; Production remains NO-GO. + +## Native error localisation correction + +The first native company error cases actually rendered an Alert and retained the draft, but exposed `Request failed with status code 500` in both languages instead of the caller's translated fallback. The unit API stub had concealed this shared-helper difference. Current source has126 `getApiErrorMessage` call sites; the root is corrected once after existing server-validation/message extraction: ordinary Axios transport failures use the caller's fallback, while structured server explanations, sanitisation, explicit cancellation/session and ordinary application errors are retained. Five real-helper cases cover both locales, HTTP/network/timeout, validation, HTML, cancellation and application errors:2RED/3already passing→5GREEN. Complete-suite/native affected-route validation is repeated after this final helper edit; no assertion is relaxed to accept mixed-language transport text. diff --git a/docs/redesign/phase-6/p6-01/RESULTS.md b/docs/redesign/phase-6/p6-01/RESULTS.md new file mode 100644 index 0000000..ad2dd95 --- /dev/null +++ b/docs/redesign/phase-6/p6-01/RESULTS.md @@ -0,0 +1,11 @@ +# Shared correction checkpoint result + +Current source after the final transport helper repair: complete frontend **521/521 in85 suites**, no failed/pending/todo tests. TypeScript, browser-coverage TypeScript, lint and production build pass. The native Settings association/reflow subset passes4/4 (1920/360/1280/390, both themes/languages), and native company/language failure→retained draft/local choice→retry→real save/reload passes4/4. Exact commands/zero retry outcomes are in [semantic evidence](../p6-05/evidence/semantics-results.json) and [feedback evidence](../p6-05/evidence/feedback-results.json); the final aggregate manifest supplements full cross-route regression. + +The authoritative Phase5 unit baseline498 grows by23:9 semantic cases,4 retained feedback cases,2 stale-language races,2 company locale cases,1 toast/fetch baseline case and5 real API-helper transport/protection cases. Existing AdminUsers/Audit cases are strengthened without increasing counts. Two new test files produce83→85 suites. Focused subset cases are not added again to521. + +Independent source/caller review found no remaining application regression. Review findings in the performance diagnostic were corrected before final native execution: wait for every visible CV page, derive actual serving/reuse metadata, and describe count/API-to-DOM parity precisely. No speculative toast refetch optimisation was justified by the passing real-consumer baseline. + +The early full attempt ran while final test/source corrections were still being completed and recorded4 failures (test matcher/fixture and two intended stale-language REDs). Its raw result and every focused RED remain retained; the source-frozen final521 run supersedes it. First native feedback run recorded2 real generic-English500 failures, motivating the shared helper correction; final4/4 passes exact localized messages. No assertion was relaxed, skip introduced or failure reclassified as expected. + +Outcome: **bounded corrections verified; conditional whole-application acceptance remains for P6-05**. Human/device/production and deferred domain gates remain open. Commit/push hash is recorded by the successor in [COMMITS](../COMMITS.md). diff --git a/job-tracker-ui/src/admin-audit-page.test.tsx b/job-tracker-ui/src/admin-audit-page.test.tsx index dcc253d..658db40 100644 --- a/job-tracker-ui/src/admin-audit-page.test.tsx +++ b/job-tracker-ui/src/admin-audit-page.test.tsx @@ -27,6 +27,8 @@ test("distinguishes an unavailable audit log from an empty audit log and retries mockedApi.get.mockRejectedValueOnce(new Error("offline")); renderPage(); + expect(screen.getByRole("heading", { name: "Audit log", level: 2 })).toBeInTheDocument(); + expect(await screen.findByText("Unable to load audit events")).toBeInTheDocument(); expect(screen.queryByText("No events.")).not.toBeInTheDocument(); diff --git a/job-tracker-ui/src/admin-system-page.test.tsx b/job-tracker-ui/src/admin-system-page.test.tsx index c14e2a6..cc7e476 100644 --- a/job-tracker-ui/src/admin-system-page.test.tsx +++ b/job-tracker-ui/src/admin-system-page.test.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import '@testing-library/jest-dom'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import AdminSystemPage from './views/AdminSystemPage'; @@ -166,6 +167,19 @@ describe('AdminSystemPage', () => { jest.clearAllMocks(); }); + it('uses contextual system headings and keeps metric values out of heading navigation', async () => { + render(); + await screen.findByText('Production'); + expect(screen.getByRole('heading', { name: 'System status', level: 2 })).toBeInTheDocument(); + expect(screen.queryByRole('heading', { name: 'Production' })).not.toBeInTheDocument(); + const levels = screen.getAllByRole('heading').map(heading => Number(heading.tagName.slice(1))); + let previous = 1; + for (const level of levels) { + expect(level).toBeLessThanOrEqual(previous + 1); + previous = level; + } + }); + it('renders AI service health, latency, and OCR readiness', async () => { render( diff --git a/job-tracker-ui/src/admin-users-page.test.tsx b/job-tracker-ui/src/admin-users-page.test.tsx index 629a0a8..e2a4740 100644 --- a/job-tracker-ui/src/admin-users-page.test.tsx +++ b/job-tracker-ui/src/admin-users-page.test.tsx @@ -54,6 +54,8 @@ beforeEach(() => { test("requires explicit confirmation before removing your own admin role", async () => { renderPage([{ id: "me", email: "me@example.com", userName: "me", roles: ["Admin"], emailConfirmed: true, isCurrentUser: true, canRemoveAdmin: true }]); + expect(screen.getByRole("heading", { name: "Users", level: 2 })).toBeInTheDocument(); + fireEvent.click(await screen.findByRole("button", { name: "Remove admin" })); expect(await screen.findByText(/you will immediately lose access/i)).toBeInTheDocument(); diff --git a/job-tracker-ui/src/components/SettingsView.tsx b/job-tracker-ui/src/components/SettingsView.tsx index 5bce33c..1ffb944 100644 --- a/job-tracker-ui/src/components/SettingsView.tsx +++ b/job-tracker-ui/src/components/SettingsView.tsx @@ -40,9 +40,9 @@ interface Props { onThemeModeChange: (v: ThemeModePref) => void; } -function TabPanel({ value, index, children }: { value: number; index: number; children: React.ReactNode }) { +function TabPanel({ value, index, label, children }: { value: number; index: number; label: string; children: React.ReactNode }) { if (value !== index) return null; - return {children}; + return {children}; } type NotificationPrefs = { @@ -175,15 +175,15 @@ export default function SettingsView({ selectSection(v)} variant="scrollable" scrollButtons="auto" sx={{ display: { xs: "none", sm: "flex" }, mb: 1 }}> - - - - + + + + {rulesDirty ? {t("assetsUnsavedChanges")}. {t("rulesDraftScope")} : null} - + @@ -273,7 +273,7 @@ export default function SettingsView({ - + void saveRules()} onRetry={() => void loadRules()} onDiscard={discardRules} onChange={(field, value) => { @@ -282,7 +282,7 @@ export default function SettingsView({ }} /> - + {notificationError ? {notificationError} : null} {!notificationPrefs ? : @@ -302,7 +302,7 @@ export default function SettingsView({ - + diff --git a/job-tracker-ui/src/cv-builder-deep-link.test.tsx b/job-tracker-ui/src/cv-builder-deep-link.test.tsx index df0243c..56cb8a7 100644 --- a/job-tracker-ui/src/cv-builder-deep-link.test.tsx +++ b/job-tracker-ui/src/cv-builder-deep-link.test.tsx @@ -81,6 +81,15 @@ async function openContentTab() { fireEvent.click(screen.getByRole('tab', { name: 'Content' })); } +test('content sections follow the editing-region heading without mutating CV settings', async () => { + routeGet(() => Promise.resolve({ data: variant })); + renderAt(3); + await openContentTab(); + expect(screen.getByRole('heading', { name: 'CV editing controls', level: 2 })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Sections', level: 3 })).toBeInTheDocument(); + expect(mockedApi.put).not.toHaveBeenCalled(); +}); + test('section navigation opens and focuses omitted/custom sections without changing their data', async () => { const settings = { ...variant.settings, sections: [{ key: 'skills', hidden: true }, { key: 'custom:sample', hidden: false }], diff --git a/job-tracker-ui/src/login-page.test.tsx b/job-tracker-ui/src/login-page.test.tsx index 5159da6..3bb427e 100644 --- a/job-tracker-ui/src/login-page.test.tsx +++ b/job-tracker-ui/src/login-page.test.tsx @@ -48,6 +48,13 @@ async function click(element: Element) { } describe('LoginPage', () => { + it.each(['login', 'register'] as const)('provides a main landmark and level-one heading for %s', async (mode) => { + renderLoginPage(mode); + await screen.findByLabelText(mode === 'login' ? 'Username or email' : 'Email'); + const title = screen.getByRole('heading', { name: mode === 'login' ? 'Sign in' : 'Create account', level: 1 }); + expect(screen.getByRole('main')).toContainElement(title); + }); + it('renders a dedicated registration screen and explains when registration is disabled', async () => { mockedApi.get.mockResolvedValueOnce({ data: { requireAuth: true, googleEnabled: false, microsoftEnabled: false, localEnabled: true, allowRegistration: false, requireEmailVerification: true }, diff --git a/job-tracker-ui/src/profile-page.test.tsx b/job-tracker-ui/src/profile-page.test.tsx index 5b9d382..8014e55 100644 --- a/job-tracker-ui/src/profile-page.test.tsx +++ b/job-tracker-ui/src/profile-page.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import '@testing-library/jest-dom'; import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { getContrastRatio } from '@mui/material/styles'; import { ToastProvider } from './toast'; import { I18nProvider } from './i18n/I18nProvider'; import ProfilePage from './views/ProfilePage'; @@ -192,6 +193,36 @@ afterEach(() => { jest.clearAllMocks(); }); +test.each([CareerProfilePage, ProfilePage])('meaningful identity initials use a readable foreground/background pair in %p', async (Component) => { + window.history.replaceState({}, '', Component === CareerProfilePage ? '/career?section=profile' : '/profile'); + renderWith(Component); + const avatar = await screen.findByText('DD'); + const style = window.getComputedStyle(avatar); + expect(getContrastRatio(style.color, style.backgroundColor)).toBeGreaterThanOrEqual(4.5); +}); + +test('Career Profile section headings describe the visible hierarchy without changing editing fields', async () => { + renderPage(); + await waitFor(() => expect(screen.getByLabelText(/full name/i)).toHaveValue('Demo User')); + expect(screen.getByRole('heading', { name: 'Career profile', level: 2 })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Career information', level: 3 })).toBeInTheDocument(); + expect(screen.getByLabelText(/full name/i)).toHaveValue('Demo User'); + let previous = 1; + for (const heading of screen.getAllByRole('heading')) { + const level = Number(heading.tagName.slice(1)); + expect(level).toBeLessThanOrEqual(previous + 1); + previous = level; + } +}); + +test('account headings separate identity from security sections', async () => { + renderWith(ProfilePage); + await screen.findByText('DD'); + expect(screen.getByRole('heading', { name: 'Profile', level: 2 })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Account', level: 3 })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Change password', level: 3 })).toBeInTheDocument(); +}); + test('profile page loads persisted structured cv and can re-parse it', async () => { renderPage(); diff --git a/job-tracker-ui/src/settings-view.test.tsx b/job-tracker-ui/src/settings-view.test.tsx index 7355f79..6c43302 100644 --- a/job-tracker-ui/src/settings-view.test.tsx +++ b/job-tracker-ui/src/settings-view.test.tsx @@ -102,6 +102,22 @@ afterEach(() => { jest.clearAllMocks(); }); +test('each selected Settings section has a named panel linked to its tab and remains selectable on phones', async () => { + renderView(); + await screen.findByText(/AI privacy/i); + const tabs = screen.getAllByRole('tab'); + for (const tab of tabs) { + await act(async () => { fireEvent.click(tab); }); + const panel = screen.getByRole('tabpanel', { name: tab.textContent! }); + expect(tab.id).not.toBe(''); + expect(tab).toHaveAttribute('aria-controls', panel.id); + expect(panel).toHaveAttribute('aria-labelledby', tab.id); + } + fireEvent.mouseDown(screen.getByRole('combobox', { name: 'Settings section' })); + fireEvent.click(screen.getByRole('option', { name: 'General' })); + expect(screen.getByRole('tabpanel', { name: 'General' })).toBeInTheDocument(); +}); + test('settings view has no accent picker and uses one follow-up section, one notification section', async () => { renderView(); diff --git a/job-tracker-ui/src/views/AdminAuditPage.tsx b/job-tracker-ui/src/views/AdminAuditPage.tsx index 37af0d3..dea0c10 100644 --- a/job-tracker-ui/src/views/AdminAuditPage.tsx +++ b/job-tracker-ui/src/views/AdminAuditPage.tsx @@ -85,7 +85,7 @@ export default function AdminAuditPage() { return ( - + {t("adminAuditTitle")} diff --git a/job-tracker-ui/src/views/AdminSystemPage.tsx b/job-tracker-ui/src/views/AdminSystemPage.tsx index bb97bdc..4102703 100644 --- a/job-tracker-ui/src/views/AdminSystemPage.tsx +++ b/job-tracker-ui/src/views/AdminSystemPage.tsx @@ -203,7 +203,7 @@ function SummaryCard({ title, value, subtitle, tone = "default" }: { title: stri return ( {title} - {value} + {value} {subtitle || "-"} ); @@ -368,7 +368,7 @@ export default function AdminSystemPage() { - {t("adminSystemTitle")} + {t("adminSystemTitle")} {t("adminSystemSubtitle")} @@ -439,7 +439,7 @@ export default function AdminSystemPage() { - {t("adminSystemDatabaseStorage")} + {t("adminSystemDatabaseStorage")} @@ -457,7 +457,7 @@ export default function AdminSystemPage() { - {t("adminSystemRuntimeAuth")} + {t("adminSystemRuntimeAuth")} @@ -475,7 +475,7 @@ export default function AdminSystemPage() { - {t("adminSystemEmailConfig")} + {t("adminSystemEmailConfig")} @@ -487,7 +487,7 @@ export default function AdminSystemPage() { - {t("adminSystemSummarizerRuntime")} + {t("adminSystemSummarizerRuntime")} @@ -509,16 +509,16 @@ export default function AdminSystemPage() { - {t("adminSystemSummarizerTelemetry")} + {t("adminSystemSummarizerTelemetry")} - {t("adminSystemRequests")}{status?.ai.requests ?? 0} - {t("adminSystemCacheHits")}{status?.ai.cacheHits ?? 0} - {t("adminSystemCacheMisses")}{status?.ai.cacheMisses ?? 0} - {t("adminSystemFailures")}{status?.ai.failures ?? 0} - {t("adminSystemProbeFailures")}{status?.ai.probeFailures ?? 0} - {t("adminSystemAvgLatency")}{status?.ai.averageLatencyMs != null ? `${status.ai.averageLatencyMs} ms` : "-"} - {t("adminSystemOcrRequests")}{status?.ai.ocrRequests ?? 0} - {t("adminSystemOcrAvgLatency")}{status?.ai.averageOcrLatencyMs != null ? `${status.ai.averageOcrLatencyMs} ms` : "-"} + {t("adminSystemRequests")}{status?.ai.requests ?? 0} + {t("adminSystemCacheHits")}{status?.ai.cacheHits ?? 0} + {t("adminSystemCacheMisses")}{status?.ai.cacheMisses ?? 0} + {t("adminSystemFailures")}{status?.ai.failures ?? 0} + {t("adminSystemProbeFailures")}{status?.ai.probeFailures ?? 0} + {t("adminSystemAvgLatency")}{status?.ai.averageLatencyMs != null ? `${status.ai.averageLatencyMs} ms` : "-"} + {t("adminSystemOcrRequests")}{status?.ai.ocrRequests ?? 0} + {t("adminSystemOcrAvgLatency")}{status?.ai.averageOcrLatencyMs != null ? `${status.ai.averageOcrLatencyMs} ms` : "-"} @@ -539,7 +539,7 @@ export default function AdminSystemPage() { - {t("adminBenchmarkTitle")} + {t("adminBenchmarkTitle")} {cvBenchmarkJob?.description || t("adminBenchmarkRunScript")} @@ -579,7 +579,7 @@ export default function AdminSystemPage() { - {t("adminBenchmarkTopFindings")} + {t("adminBenchmarkTopFindings")} {benchmarkFindings.length > 0 ? benchmarkFindings.map((finding) => ( @@ -591,7 +591,7 @@ export default function AdminSystemPage() { - {t("adminBenchmarkWeakest")} + {t("adminBenchmarkWeakest")} {weakestEntries.map((entry) => ( @@ -609,7 +609,7 @@ export default function AdminSystemPage() { - {t("adminBenchmarkLatestSummary")} + {t("adminBenchmarkLatestSummary")} {benchmarkStatus?.reportMarkdown || "-"} @@ -627,7 +627,7 @@ export default function AdminSystemPage() { ) : ( - {t("adminSystemEmailSettingsTitle")} + {t("adminSystemEmailSettingsTitle")} {t("adminSystemEmailSettingsBody")} - {t("adminSystemSmtpTest")} + {t("adminSystemSmtpTest")} {t("adminSystemSmtpTestBody")} diff --git a/job-tracker-ui/src/views/AdminUsersPage.tsx b/job-tracker-ui/src/views/AdminUsersPage.tsx index 55598fa..3350e8d 100644 --- a/job-tracker-ui/src/views/AdminUsersPage.tsx +++ b/job-tracker-ui/src/views/AdminUsersPage.tsx @@ -192,7 +192,7 @@ export default function AdminUsersPage() { return ( - + {t("adminUsersTitle")} {t("adminUsersSubtitle")} diff --git a/job-tracker-ui/src/views/CareerProfilePage.tsx b/job-tracker-ui/src/views/CareerProfilePage.tsx index db5e009..52dc883 100644 --- a/job-tracker-ui/src/views/CareerProfilePage.tsx +++ b/job-tracker-ui/src/views/CareerProfilePage.tsx @@ -430,7 +430,7 @@ export default function CareerProfilePage() { {workspaceSection === "profile" ? - {t("careerProfileImportTitle")} + {t("careerProfileImportTitle")} {t("careerProfileImportBody")} + {t("profileCvOtherLinks")} {(value.links ?? []).map((link, index) => set({ links: (value.links ?? []).map((item, itemIndex) => itemIndex === index ? { ...item, label: event.target.value || undefined } : item) })} sx={{ flex: "0 1 180px" }} /> set({ links: (value.links ?? []).map((item, itemIndex) => itemIndex === index ? { ...item, url: event.target.value || undefined } : item) })} fullWidth /> set({ links: (value.links ?? []).filter((_, itemIndex) => itemIndex !== index) })}>)} @@ -145,7 +145,7 @@ export function LanguagesSection({ value, onChange, getMetadata }: { value: Stru return ( - {t("profileCvStructuredLanguages")} + {t("profileCvStructuredLanguages")} @@ -170,7 +170,7 @@ export function WorkExperienceSection({ value, onChange }: { value: StructuredCv return ( - {t("profileCvStructuredJobs")} + {t("profileCvStructuredJobs")} {value.length === 0 ? {t("profileCvStructuredEmpty")} : null} @@ -202,7 +202,7 @@ export function EducationSection({ value, onChange }: { value: StructuredCvEduca return ( - {t("profileCvStructuredEducation")} + {t("profileCvStructuredEducation")} {value.length === 0 ? {t("profileCvStructuredEmpty")} : null} @@ -233,7 +233,7 @@ export function OtherSectionsSection({ value, onChange }: { value: StructuredCvO return ( - {t("profileCvStructuredOtherSections")} + {t("profileCvStructuredOtherSections")} {value.length === 0 ? {t("profileCvStructuredEmpty")} : null}