diff --git a/docs/ARCHITECTURE_EVOLUTION.md b/docs/ARCHITECTURE_EVOLUTION.md
new file mode 100644
index 0000000..686b051
--- /dev/null
+++ b/docs/ARCHITECTURE_EVOLUTION.md
@@ -0,0 +1,53 @@
+# Jobbjakt Architecture Evolution
+
+## Guiding constraints
+
+- Preserve the existing single-master-profile model.
+- Keep job applications and their workflow at the centre of the domain.
+- Make AI providers replaceable and optional.
+- Add SaaS foundations without prematurely building a billing platform or team product.
+- Keep feature slices small while reducing the current controller concentration.
+
+## Target boundaries
+
+```text
+Web UI
+ -> Application APIs
+ -> Application workflow domain
+ -> Career profile and document domain
+ -> Discovery provider adapters
+ -> Connected-account adapters
+ -> AI provider boundary
+ -> Entitlements and usage policy
+```
+
+### Career profile and documents
+
+`ApplicationUser.ProfileCvStructureJson` remains the canonical master profile during the transition. A later migration may normalize high-change sections (projects and portfolio items) into user-owned tables, but generated CVs and application-specific drafts must remain immutable copies with a profile-version reference.
+
+### Discovery
+
+Create an `IJobDiscoveryProvider` contract with normalized query and result types. Providers own authentication, source-specific filters, terms-aware attribution, and mapping. The UI and import flow must not know provider payload shapes. Cache public results briefly, do not persist an external job until the user imports it.
+
+### AI
+
+Extract AI prompts and model transport from controllers behind task-focused interfaces: CV extraction, CV tailoring, draft generation, interview preparation, and summarization. Each request records provider, model, input profile version, job context, and user review state. Local and online providers implement the same boundary.
+
+### Entitlements
+
+Introduce account-level plan, entitlement, and usage-ledger records before payment integration. Product code checks named capabilities (for example `ai.tailor_cv`, `cv.premium_theme`, `storage.attachments`) rather than payment-provider state. This supports trials, manual access, subscriptions, and future organisation plans.
+
+## Incremental engineering work
+
+1. Add targeted feature services beside the existing controllers for every touched feature; do not attempt a big-bang controller rewrite.
+2. Replace local-only preferences that affect behaviour with user-owned persisted settings.
+3. Replace the in-memory CV queue with a database-backed work item before horizontal hosting.
+4. Use PostgreSQL for hosted multi-tenant deployment; retain SQLite for self-hosted/single-user deployments.
+5. Add provider health, job processing, AI usage, and entitlement audit telemetry before paid hosting.
+
+## Explicit non-goals for the first SaaS release
+
+- Teams/organisations and shared candidate data.
+- Auto-apply automation or scraping-proxy infrastructure.
+- A browser-extension marketplace release.
+- A billing-provider integration before commercial policy is decided.
diff --git a/docs/PRODUCT_EVOLUTION_PLAN.md b/docs/PRODUCT_EVOLUTION_PLAN.md
new file mode 100644
index 0000000..0d35147
--- /dev/null
+++ b/docs/PRODUCT_EVOLUTION_PLAN.md
@@ -0,0 +1,81 @@
+# Jobbjakt Product Evolution Plan
+
+> Confirmed 2026-07-15. This document is the product decision record for the career-management evolution.
+
+## Product position
+
+Jobbjakt is a job-tracking and career-management product. The application workflow remains the core loop:
+
+```text
+Discover opportunity -> Track application -> Prepare application -> Follow up -> Learn and progress
+```
+
+Career profile, CV generation, cover letters, portfolio, interview preparation, and discovery support this loop. They must not displace it or turn Jobbjakt into a CV-builder-first product.
+
+## Confirmed decisions
+
+| Decision | Direction |
+| --- | --- |
+| Primary discovery market | Nordics and Europe; global later |
+| Discovery sources | Permitted provider APIs and existing URL capture; no scraping or browser extension in the first release |
+| Career workspace | Dedicated top-level navigation item |
+| Add-job flow | Wizard; create the application after job review, then keep preparation steps optional and resumable |
+| Portfolio | Lightweight links and project items connected to the master career profile |
+| AI | Retain local and online capability behind a provider boundary and explicit user controls |
+| SaaS priority | Hosted subscriptions and feature entitlements before organisations/teams |
+| Brand | Keep the Jobbjakt name |
+
+## Target information architecture
+
+| Area | Purpose | Initial destinations |
+| --- | --- | --- |
+| Dashboard | Daily direction | priorities, follow-ups, recent activity |
+| Applications | Core tracker | list, board, companies, application workspace |
+| Career Workspace | Reusable career source of truth | master profile, CV builder, portfolio, interview preparation |
+| Discover Jobs | Find then act | provider search, saved searches later, import into tracker |
+| Settings | Product preferences | general, follow-ups, notifications, connected accounts, data |
+| Account | Identity and security | personal details, password, providers, sessions, 2FA |
+
+## Delivery roadmap
+
+### Phase 1 — Workflow clarity
+
+1. Create the Career Workspace and move CV-facing functionality out of Account Profile.
+2. Move Gmail, Outlook, and IMAP management to Settings > Connected Accounts.
+3. Reorganise Settings: remove normal-user export/import controls, hide admin-only status controls, and improve follow-up explanations.
+4. Simplify application workspace navigation into task-oriented sections.
+
+### Phase 2 — Application preparation
+
+1. Replace the dense Add Job modal with a six-step wizard.
+2. URL capture and extraction lead the flow; manual details remain an easy fallback.
+3. Save the application after review; CV, cover letter, portfolio, and documents are explicitly optional.
+4. Link job-specific CV copies and draft material to the application without mutating the master profile.
+
+### Phase 3 — Career workspace completion
+
+1. Strengthen the structured master career profile: experience, education, skills, projects, certifications, languages, achievements, and portfolio links.
+2. Turn the existing template system into a guided three-step CV builder: theme, preview/customise, save defaults.
+3. Keep five professional templates and persisted visual preferences; add drag-and-drop ordering after the underlying configuration is durable.
+
+### Phase 4 — Nordic/European discovery
+
+1. Introduce a provider-adapter contract and a normalized result model.
+2. Implement approved public/provider APIs beginning with Nordic sources; add European sources only when their terms permit product use.
+3. Provide search, filters, source attribution, map/distance enrichment where data permits, and one-click import to the wizard.
+4. Add fuzzy role matching locally through a role/skill synonym catalog before any external AI ranking.
+
+### Phase 5 — Hosted SaaS foundations
+
+1. Add account-level entitlement and usage records independent of a billing provider.
+2. Gate premium themes, AI allowances, storage, and discovery quotas through policy checks.
+3. Integrate a billing provider only after pricing, tax, trial, cancellation, and support policy decisions are recorded.
+4. Move restart-sensitive CV processing to durable storage before multi-instance hosting.
+
+## Success measures
+
+- A new user can create a tracked application from a URL in under two minutes.
+- A user can find their master profile, create a CV, and manage email connections without visiting Account Profile.
+- Optional preparation never blocks capture of an opportunity.
+- Every AI-generated output visibly states its source profile/job context and requires user review before it is saved or sent.
+- Discovery always preserves source attribution and sends users to the original job advert to apply.
diff --git a/job-tracker-ui/src/App.tsx b/job-tracker-ui/src/App.tsx
index 18407a2..81ea16c 100644
--- a/job-tracker-ui/src/App.tsx
+++ b/job-tracker-ui/src/App.tsx
@@ -16,6 +16,7 @@ import ShieldIcon from "@mui/icons-material/Shield";
import SearchIcon from "@mui/icons-material/Search";
import MailOutlineIcon from "@mui/icons-material/MailOutline";
import MemoryIcon from "@mui/icons-material/Memory";
+import DescriptionOutlinedIcon from "@mui/icons-material/DescriptionOutlined";
import { Navigate, Route, Routes, useLocation, useNavigate, createBrowserRouter, RouterProvider } from "react-router-dom";
@@ -47,6 +48,8 @@ const SettingsView = lazy(() => import("./components/SettingsView"));
const RemindersView = lazy(() => import("./components/RemindersView"));
const QuickCommandDialog = lazy(() => import("./components/QuickCommandDialog"));
const ProfilePage = lazy(() => import("./views/ProfilePage"));
+const CareerWorkspacePage = lazy(() => import("./views/CareerWorkspacePage"));
+const ConnectedAccountsPage = lazy(() => import("./views/ConnectedAccountsPage"));
const AdminAuditPage = lazy(() => import("./views/AdminAuditPage"));
const AdminUsersPage = lazy(() => import("./views/AdminUsersPage"));
const AdminSystemPage = lazy(() => import("./views/AdminSystemPage"));
@@ -78,6 +81,8 @@ function breadcrumbsFor(path: string, t: (k: any) => string): string[] {
if (path.startsWith("/trash")) return [t("home"), t("trash")];
if (path.startsWith("/settings")) return [t("home"), t("settings")];
if (path.startsWith("/profile")) return [t("home"), t("account"), t("profile")];
+ if (path.startsWith("/career")) return [t("home"), "Career Workspace"];
+ if (path.startsWith("/settings/connected-accounts")) return [t("home"), t("settings"), "Connected accounts"];
if (path.startsWith("/admin/audit")) return [t("home"), t("admin"), t("auditLog")];
if (path.startsWith("/admin/users")) return [t("home"), t("admin"), t("users")];
if (path.startsWith("/admin/system")) return [t("home"), t("admin"), t("system")];
@@ -95,6 +100,8 @@ function titleFor(path: string, t: (k: any) => string): string {
if (path.startsWith("/trash")) return t("trash");
if (path.startsWith("/settings")) return t("settings");
if (path.startsWith("/profile")) return t("profile");
+ if (path.startsWith("/career")) return "Career Workspace";
+ if (path.startsWith("/settings/connected-accounts")) return "Connected accounts";
if (path.startsWith("/admin/audit")) return t("auditLog");
if (path.startsWith("/admin/users")) return t("users");
if (path.startsWith("/admin/system")) return t("systemStatus");
@@ -234,6 +241,7 @@ function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMo
{ to: "/companies", label: t("companies"), icon: , section: t("manage") },
{ to: "/correspondence", label: "Correspondence", icon: , section: t("manage") },
{ to: "/correspondence/review", label: "Gmail review", icon: , section: t("manage") },
+ { to: "/career", label: "Career Workspace", icon: , section: t("manage") },
{ to: "/trash", label: t("trash"), icon: , section: t("manage") },
];
@@ -314,11 +322,13 @@ function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMo
} />
} />
} />
+ } />
} />
} />
} />
} />
} />
+ } />
} />
diff --git a/job-tracker-ui/src/components/SettingsView.tsx b/job-tracker-ui/src/components/SettingsView.tsx
index 9215806..bd8a310 100644
--- a/job-tracker-ui/src/components/SettingsView.tsx
+++ b/job-tracker-ui/src/components/SettingsView.tsx
@@ -198,6 +198,9 @@ export default function SettingsView({
+
+
+
@@ -226,7 +229,6 @@ export default function SettingsView({
-
diff --git a/job-tracker-ui/src/views/CareerWorkspacePage.tsx b/job-tracker-ui/src/views/CareerWorkspacePage.tsx
new file mode 100644
index 0000000..bd7a16e
--- /dev/null
+++ b/job-tracker-ui/src/views/CareerWorkspacePage.tsx
@@ -0,0 +1,22 @@
+import React from "react";
+
+import { Alert, Box, Paper, Typography } from "@mui/material";
+
+import ProfilePage from "./ProfilePage";
+
+export default function CareerWorkspacePage() {
+ return (
+
+
+ Career Workspace
+
+ Maintain the master career profile that powers your CVs, tailored application material, and future portfolio outputs.
+
+
+
+ Your master profile is the source of truth. Job-specific CV drafts remain separate and never overwrite it.
+
+
+
+ );
+}
diff --git a/job-tracker-ui/src/views/ConnectedAccountsPage.tsx b/job-tracker-ui/src/views/ConnectedAccountsPage.tsx
new file mode 100644
index 0000000..d5fed1c
--- /dev/null
+++ b/job-tracker-ui/src/views/ConnectedAccountsPage.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+
+import { Box, Button, Paper, Typography } from "@mui/material";
+import { useNavigate } from "react-router-dom";
+
+import EmailProviderConnections from "../components/EmailProviderConnections";
+
+export default function ConnectedAccountsPage() {
+ const navigate = useNavigate();
+
+ return (
+
+
+ Connected accounts
+
+ Connect the inboxes Jobbjakt may use to identify recruiter correspondence. Connecting an inbox never sends a message on your behalf.
+
+
+
+
+
+ );
+}
diff --git a/job-tracker-ui/src/views/ProfilePage.tsx b/job-tracker-ui/src/views/ProfilePage.tsx
index a651ebc..3d204ce 100644
--- a/job-tracker-ui/src/views/ProfilePage.tsx
+++ b/job-tracker-ui/src/views/ProfilePage.tsx
@@ -13,7 +13,6 @@ import MicrosoftAuthCard from "../components/MicrosoftAuthCard";
import AuthStatusCard from "../components/AuthStatusCard";
import TwoFactorSettingsCard from "../components/TwoFactorSettingsCard";
import SessionsSettingsCard from "../components/SessionsSettingsCard";
-import EmailProviderConnections from "../components/EmailProviderConnections";
import CropImageDialog from "../components/CropImageDialog";
import { useToast } from "../toast";
import { useI18n } from "../i18n/I18nProvider";
@@ -227,7 +226,7 @@ function FieldReviewNote({ metadata }: { metadata?: StructuredCvFieldMetadata })
);
}
-export default function ProfilePage() {
+export default function ProfilePage({ careerOnly = false }: { careerOnly?: boolean }) {
const { toast } = useToast();
const { t } = useI18n();
const cvInputRef = useRef(null);
@@ -553,7 +552,7 @@ export default function ProfilePage() {
- {t("profileTitle")}
+ {careerOnly ? "Master career profile" : t("profileTitle")}
{me?.userName || me?.displayName || fullName || me?.email || "-"}
{headline || t("profileHeadlinePlaceholder")}
@@ -566,37 +565,38 @@ export default function ProfilePage() {
-
-
-
-
-
-
+ {!careerOnly ? <>
+
+
+
+ > : null}
-
+ {!careerOnly ?
{t("profileAccountSection")}
{!isLocal ? (
{t("profileReadOnlyInfo")}
) : null}
-
+ : null}
- setDisplayName(e.target.value)} disabled={!isLocal} fullWidth />
- setUserName(e.target.value)} disabled={!isLocal} fullWidth />
- setFirstName(e.target.value)} disabled={!isLocal} fullWidth />
- setLastName(e.target.value)} disabled={!isLocal} fullWidth />
- setEmail(e.target.value)} disabled={!isLocal} fullWidth />
-
+ setDisplayName(e.target.value)} disabled={!isLocal} fullWidth />
+ setUserName(e.target.value)} disabled={!isLocal} fullWidth />
+ setFirstName(e.target.value)} disabled={!isLocal} fullWidth />
+ setLastName(e.target.value)} disabled={!isLocal} fullWidth />
+ setEmail(e.target.value)} disabled={!isLocal} fullWidth />
+ setHeadline(e.target.value)}
helperText={t("profileHeadlineHelp")}
fullWidth
- />
+ />
+ > : null}
-
+
{t("profileMasterCv")}
@@ -1318,16 +1318,17 @@ export default function ProfilePage() {
-
+ {!careerOnly ?
{t("profileChangePassword")}
{!isLocal ? {t("profilePasswordLocalOnly")} : null}
-
+ : null}
- setCurrentPassword(e.target.value)} disabled={!isLocal} fullWidth />
- setNewPassword(e.target.value)} disabled={!isLocal} fullWidth />
+ {!careerOnly ? <>
+ setCurrentPassword(e.target.value)} disabled={!isLocal} fullWidth />
+ setNewPassword(e.target.value)} disabled={!isLocal} fullWidth />
-
+
-
+
+ > : null}
- {isLocal ? : null}
- {isLocal ? : null}
+ {!careerOnly && isLocal ? : null}
+ {!careerOnly && isLocal ? : null}
);
}