From cf8b2fa01476e1b65d1210db444974c5aea72a4b Mon Sep 17 00:00:00 2001 From: cesnimda Date: Sat, 18 Jul 2026 00:13:06 +0200 Subject: [PATCH] docs(architecture): document profile and career ownership Add section 4a to docs/architecture/current.md: request flow, data ownership, API responsibilities, and future extension points for the /profile vs /career separation completed in Phase 2/2.2. Co-Authored-By: Claude Opus 4.8 --- docs/architecture/current.md | 75 +++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/docs/architecture/current.md b/docs/architecture/current.md index 1723e3b..c3b553d 100644 --- a/docs/architecture/current.md +++ b/docs/architecture/current.md @@ -120,9 +120,80 @@ Known consequence: a **dev-only 404 on deep links** follows directly from the Ne **Styling:** MUI `sx` + custom `src/theme.ts` (439 lines), light/dark. Design tokens live inline in component `sx` props rather than in the theme (e.g. the same `boxShadow: "0px 8px 24px -12px rgba(15,23,42,0.12)"` is repeated across pages). There is no component primitives layer and no Storybook. -**Oversized components** (refactor targets, in order): `JobDetailsDialog.tsx` (1400), `ProfilePage.tsx` (1368), `JobTable.tsx` (786), `Correspondence.tsx` (732), `DashboardView.tsx` (666), `AdminSystemPage.tsx` (623), `AddJobModal.tsx` (618). +**Oversized components** (refactor targets, in order): `JobDetailsDialog.tsx` (1400), `CareerProfilePage.tsx` (1293), `JobTable.tsx` (786), `Correspondence.tsx` (732), `DashboardView.tsx` (666), `AdminSystemPage.tsx` (623), `AddJobModal.tsx` (618). (`ProfilePage.tsx` was 1368; Phase 2.2 split it — see §4a.) -`ProfilePage.tsx` serves **both** `/profile` and `/career`, forked by a `careerOnly` boolean — two nav destinations rendering one component. +--- + +## 4a. Profile / Career separation (Phase 2, 2026-07-17) + +`/profile` and `/career` were one 1368-line component (`ProfilePage`) forked by a `careerOnly` +boolean. Phase 2 scoped their saves; Phase 2.2 split them into two dedicated components. This is +the reference model for how account identity and the master career profile relate. + +### Components & routes + +| Route | Component | Owns | +|---|---|---| +| `/profile` | `views/ProfilePage.tsx` (~490 lines) | Account identity + security + preferences | +| `/career` | `views/CareerWorkspacePage.tsx` → `views/CareerProfilePage.tsx` (~1293 lines) | The **master career profile** — the single editable source of truth | + +`CareerWorkspacePage` is a thin shell (heading + source-of-truth notice) around `CareerProfilePage`. +The CV Builder is **not** built yet (Phase 4); `CareerProfilePage` is where it will live. + +### Request flow + +``` +/profile → ProfilePage + load: GET /auth/me (account row only) + save: PUT /auth/profile { email, userName, firstName, lastName, displayName } + +/career → CareerWorkspacePage → CareerProfilePage + load: GET /auth/me + GET /profile-cv/runs (extraction history) + GET /jobapplications?… (for per-job CV tailoring context) + save: PUT /auth/profile { profileCvText, profileCvStructureJson } + (ProfileCvController paths additionally dual-write CareerProfileService — + CareerProfiles / CareerProfileVersions — see §9 / §16) +``` + +### Data ownership (the invariant) + +Both surfaces persist through the **same** endpoint, `PUT /auth/profile`, which does **partial +updates** (`AuthController.UpdateProfile`): a field is touched only if the request carries it — +`null`/omitted leaves it unchanged, `""` clears it, a value sets it. Email and UserName are never +cleared (login identifiers). + +| Field(s) on `ApplicationUser` | Owner (only surface that writes them) | +|---|---| +| `Email`, `UserName`, `FirstName`, `LastName`, `DisplayName`, `AvatarImageDataUrl`, password, TOTP/2FA, linked OAuth accounts | **/profile** | +| `ProfileCvText`, `ProfileCvStructureJson` (the master career profile) | **/career** | + +Because the endpoint is partial, `/profile` saving identity does **not** null the master profile, +and `/career` saving the profile does **not** null identity. This is enforced by tests +(`AuthAndSystemControllerTests`: identity-save-keeps-CV, career-save-keeps-identity, empty-clears, +null-leaves). + +### API responsibilities + +- `AuthController.UpdateProfile` (`PUT /auth/profile`) — partial update of the account row; the + single write path for both surfaces. Local accounts only. +- `AuthController` (`GET /auth/me`) — returns the whole account row; each surface reads the fields + it owns. +- `ProfileCvController` — CV ingest/parse/rewrite/export and the `CareerProfileService` dual-write. + Called from `/career`. +- `JobApplicationsController` — `/career` reads job list for tailoring context only. + +### Future extension points + +- **`CareerProfilePage` is the foundation for all future career outputs** (Phase 3/4): CV Builder, + tailored CVs, cover letters, portfolio, interview prep. They attach here, referencing the master + profile — never duplicating it (per `docs/MASTER_IMPLEMENTATION_GUIDE.md`). +- **Source-of-truth flip (F5):** today `ProfileCvStructureJson` is authoritative and + `CareerProfileService` mirrors it. A later phase makes `CareerProfiles`/`CareerProfileVersions` + authoritative; the `/career` save would then route through `CareerProfileService` rather than the + blob column. The partial-update endpoint and the ownership split above do not change. +- **Full decomposition (roadmap 2.2 residue):** `CareerProfilePage` is still large because it owns + the whole master-CV surface; the CV Builder work will extract sub-components from it. ---