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 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user