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")}} onClick={() => set({ links: [...(value.links ?? []), { label: "", url: "" }] })}>{t("profileCvAddLink")}
{(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")}
onChange([...value, { name: "", level: "", notes: "" }])}>{t("profileCvStructuredAddLanguage")}
@@ -170,7 +170,7 @@ export function WorkExperienceSection({ value, onChange }: { value: StructuredCv
return (
- {t("profileCvStructuredJobs")}
+ {t("profileCvStructuredJobs")}
onChange([...value, { title: "", company: "", location: "", start: "", end: "", isCurrent: false, bullets: [], skills: [] }])}>{t("profileCvStructuredAddJob")}
{value.length === 0 ? {t("profileCvStructuredEmpty")} : null}
@@ -202,7 +202,7 @@ export function EducationSection({ value, onChange }: { value: StructuredCvEduca
return (
- {t("profileCvStructuredEducation")}
+ {t("profileCvStructuredEducation")}
onChange([...value, { qualification: "", institution: "", location: "", start: "", end: "", details: [] }])}>{t("profileCvStructuredAddEducation")}
{value.length === 0 ? {t("profileCvStructuredEmpty")} : null}
@@ -233,7 +233,7 @@ export function OtherSectionsSection({ value, onChange }: { value: StructuredCvO
return (
- {t("profileCvStructuredOtherSections")}
+ {t("profileCvStructuredOtherSections")}
onChange([...value, { title: "", items: [] }])}>{t("profileCvStructuredAddOtherSection")}
{value.length === 0 ? {t("profileCvStructuredEmpty")} : null}