# CV Builder Product Teardown — Jobbjakt Internal Review

**Date:** 2026-07-12
**Scope:** Critical product, UX, and technical teardown of the current CV builder and related career features, ahead of the Career Workspace redesign.
**Companions:** `docs/cv-builder-competitor-deep-research.md` (market context) and the CV Builder Architecture Proposal artifact (target design). This document is the honest "as-is" audit both of those build on.
**Stance:** Written as a senior PM + UX designer + architect reviewing before a major redesign. Existing code is treated as evidence, not as precedent. Where something is wrong, it is called wrong.

---

## 1. Current Product Overview

### What the application is today

Jobbjakt is a self-hosted, single-tenant-ish (multi-user with admin) **job application tracker** with strong email intelligence, to which a CV builder has been progressively bolted on. Live at `https://jobs.cesnimda.uk`. Stack: ASP.NET Core 9 + EF Core (SQLite dev / MariaDB prod), Next.js 16 static-export frontend (MUI), FastAPI AI sidecar (Ollama/Gemini/Groq router), Playwright PDF rendering.

**Feature inventory (career-relevant):**

| Area | What exists | Where |
|---|---|---|
| Job tracking | CRUD, kanban pipeline, drag-drop, statuses, reminders, analytics/funnel | `JobApplicationsController` (~2,900 lines), `KanbanBoard`, `DashboardView` |
| Email intelligence | Gmail sync, thread↔job linking, review queue, suggested jobs, status suggestions | `GmailController`, `GmailReviewPage`, `CorrespondenceInboxPage` |
| Master CV | Upload (PDF/text) → parse → normalize → structured profile; rebuild/improve/reprocess; section rewrite | `ProfileCvController` (2,265 lines), `ProfilePage.tsx` |
| Tailored CV | Per-job draft: generate from master + job, edit, preview, PDF export | `JobApplicationsController:2465–2614`, `JobDetailsDialog.tsx` |
| Templates | 6 hardcoded templates (ats-minimal, harvard, auckland, edinburgh, monarch, fjord), curated accent palette, section order, page mode, bullet density | `CvTemplateRenderer` (448 lines), `TailoredCvRenderOptions` |
| Match scoring | CV↔JD keyword match with curated skill-tag synonyms | `JobCvMatchService`, `SkillTagger`, `match-score` endpoint |
| Per-job AI | Interview prep, candidate fit, focus plan, follow-up drafts, application package, readiness | `JobDetailsDialog.tsx` (all crammed into one dialog) |
| AI service | `/summarize`, `/cv/normalize`, `/cv/classify-block`, `/cv/rewrite` with prompt-injection hardening + provider router | `tools/summarizer/app.py` |

### Target user

Implicitly: **the developer himself** — a technically fluent, self-hosting job seeker running an active search. Nothing in the product contradicts this: no onboarding for novices, AI knobs exposed raw (tone/language/target-role fields), extraction "runs" and "reprocess" surfaced in end-user UI, admin pages in the same nav. The recent additions (onboarding checklist, empty states, OAuth signup) are the first genuine gestures toward a second user.

**This is the central product tension:** the backend is built like a multi-user SaaS (Identity, roles, registration, OAuth, per-owner scoping), but the UX is built like a personal tool. The redesign must pick: the Career Workspace vision implies real second users, which means the "developer-as-user" assumptions have to go.

### Current user journey

1. Land → sign in (email/Google/Microsoft) → dashboard.
2. Add jobs manually, via bookmarklet, or via Gmail suggested-jobs.
3. Track through kanban; Gmail sync auto-links correspondence; reminders fire.
4. Separately, on Profile page: upload a CV → extraction pipeline produces structured profile.
5. Inside a job's details dialog (a modal!): generate tailored CV draft → edit in text fields → preview → export PDF.
6. AI extras (interview prep, fit, follow-ups) live as tabs/sections in that same modal.

### Current relationships (the actual object model in practice)

```
ApplicationUser
 ├─ ProfileCvText          (raw CV text — a column on the Identity user row)
 ├─ ProfileCvStructureJson (StructuredCvProfile serialized — also a user column)
 ├─ CvUploadArtifact ──< CvExtractionRun (parser/normalizer/prompt versions, run history)
 └─ JobApplication ──1:1── TailoredCvDraft
                             ├─ TemplateId + RenderOptionsJson  (presentation welded to content)
                             ├─ SummaryJson / SelectedSkillsJson / ExperienceJson / ... (per-section JSON blobs)
                             ├─ CanonicalProfileVersion (staleness pointer to master)
                             └─ Status ("generated" / edited)
Cover letters: DO NOT EXIST as entities. Interview prep etc.: transient AI responses, not persisted as documents.
```

### Current product philosophy (inferred, since none is written down)

- **Tracker-first:** the CV is an attachment to a job application, not a first-class product. The tailored CV lives *inside the job details modal* — the clearest possible statement of the current hierarchy.
- **One master CV, ephemeral derivatives:** exactly one profile per user; tailored drafts are per-job satellites; nothing else is durable.
- **AI as pipeline, not as assistant:** AI does batch transforms (parse this, rewrite that) with exposed machinery, rather than conversational or inline assistance.
- **Provenance-conscious:** field-level confidence/review-state metadata shows real care about "where did this claim come from" — unusually mature for this product stage.

The Career Workspace vision inverts the first two tenets. The last two are worth keeping.

---

## 2. User Experience Review

### New user

**How does a user create their first CV?** They can't, in any meaningful sense — they can only *import* one. `ProfilePage` offers "Upload CV" (parse an existing document). There is no from-scratch path: no guided form, no "add your first job" flow for the profile. A user without an existing CV document is stuck. Every competitor in the research offers from-scratch creation; we are import-only.

**Is onboarding clear?** The new onboarding checklist (add CV / import job / check match) is a good spine, but step one drops the user on a Profile page where the CV feature is a card among password/avatar/email-connection cards. CV building is presented as an *account setting*. That framing is wrong for what is supposed to become the product's centerpiece.

**Critical bug, found during this audit:** [`ProfilePage.tsx:356`](../job-tracker-ui/src/views/ProfilePage.tsx) computes `isLocal = me?.provider === "local"` and disables CV upload/rebuild/improve (among other controls) for OAuth users. The gate was presumably meant for identity fields (can't change password on a Google account) and was blanket-applied to the CV card. **Google/Microsoft users — the exact accounts we just built auto-signup for — get a disabled CV builder.** A Google-first new user's journey dead-ends at step one of the checklist. Must-fix regardless of redesign timing.

**Where do new users get confused?**
- "Reprocess," "Rebuild," "Improve," "Runs" — four adjacent buttons whose distinction (re-run extraction vs. regenerate structure vs. AI-rewrite text vs. view pipeline history) is developer vocabulary. No user knows which to press.
- The structured profile (the actual output of extraction) has no real editing UI — the raw text and the structure are shown, but correcting a mis-parsed date means fighting JSON or re-uploading.
- Nothing explains that the master CV feeds match scores and tailored drafts; the causal chain that makes the product coherent is invisible.

### Existing user

**Editing the CV:** the master CV is edited as *raw text* (`ProfileCvText` in a textarea) with AI rewrite assistance per-section. The structured profile is a *derived artifact* the user can't directly maintain. This is backwards relative to both competitors (structured forms are the primary surface everywhere) and our own architecture proposal. Consequence: every text edit desynchronizes text from structure until a rebuild; the "which is the truth?" question has no good answer today (see §4).

**Creating tailored CVs:** open a job → details modal → tailored CV section → "Generate" → edit. Real problems:
- It's in a **modal**. A document editor competing for space with interview prep, fit analysis, follow-ups, readiness, notes — inside a dialog over the jobs table. No room for the side-by-side JD↔CV tailoring view that the competitor research identified as the killer screen (Teal's core loop).
- Bullets are edited as newline-joined blobs (`splitLines`/`joinLines` in `tailoredCvDraft.ts`) — plain textareas, no per-bullet operations, no drag-reorder, no AI-improve-this-bullet affordance at the point of editing.
- **One draft per job, no variants, no history.** Regenerate overwrites; a good manual edit lost to a regenerate is unrecoverable. `Status` ("generated"/edited) and `GenerationContextHash` exist precisely because overwrite-anxiety is real — they mitigate instead of solving.

**Reusing information:** the master→tailored generation is the only reuse mechanism. No way to reuse a great tailored summary across jobs, no library of alternative bullets, no second master for a different career track. `CanonicalProfileVersion` at least detects when a draft is stale relative to the master — good instinct, minimal payoff without a refresh/diff flow.

**Maintaining career information over time:** effectively unsupported. Adding a new job to your history = edit raw text + rebuild, or re-upload a new document. For a product whose vision is "the structured career profile is the single source of truth," today's truth is a text blob on the user table.

**Friction inventory (ranked):**
1. OAuth users locked out of CV features (bug).
2. No from-scratch creation path.
3. No structured-profile editor — raw text is the editing surface.
4. Tailored CV editor trapped in a modal.
5. Single draft, overwrite-on-regenerate, no history.
6. Pipeline vocabulary (runs/reprocess/rebuild) in end-user UI.
7. Preview is HTML-in-a-box, not a paginated document preview; template switching is a dropdown with no visual gallery.
8. CV features split across two distant locations (Profile page ↔ job modal) with no navigational thread connecting them.

---

## 3. CV Builder Analysis (vs. competitor research)

### Editing model

Current: **raw-text-primary with derived structure** (master) and **form-ish JSON blob editing** (tailored). The market-winning model per the research: **structured forms + instant themed preview + drag-drop sections** (FlowCV, Reactive Resume, Teal). We have the *data model* for that (StructuredCvProfile is section-granular) but not the UI. Verdict: the editing surface must be rebuilt around structure; the raw text demotes to an import artifact and export view.

### Preview experience

Rendered HTML returned by the server per-request, displayed inline. No client-side re-render on keystroke, no pagination fidelity, no zoom, no "what the ATS sees" view (which our structured pipeline could produce almost for free — competitive claim identified in research §7.4). Against FlowCV's lag-free live preview this is a clear generation behind.

### Template switching & customization

Genuinely decent bones: `TemplateId` swaps freely over the same content (content/presentation separation *within* the draft works); render options offer curated accent palette, section order, page mode, bullet density, photo toggle. This matches the curated-token pattern Novoresume uses (research §4.7) — the right instinct. Falls short on: no visual template gallery, no font choice, no spacing control, six templates whose design quality is mid-tier vs. the design-led cluster, and per-CV theme settings can't be saved/reused as a named style.

### What we already do well (protect these in the redesign)

1. **Extraction pipeline with provenance** — versioned runs (parser/normalizer/prompt versions), field-level confidence + review-state + source snippet. No competitor surfaces provenance at all. This is a differentiating asset the moment a review UI exposes it ("we're 60% sure about this date — confirm?").
2. **Match scoring with curated skill synonyms** in the same system as tracking — Teal's premium feature, already ours.
3. **Prompt-injection-hardened AI pipeline** with delimiter fencing and instruction-ignoring rules; provider router with local fallback. More mature than the market's bolt-on AI.
4. **Server-side Playwright rendering** — full CSS typography control; matches the print quality of the design-led cluster.
5. **Staleness detection** (`CanonicalProfileVersion`, `GenerationContextHash`) — the primitive that version-aware tailoring needs.

### Where we fall behind

| Dimension | Market bar | Us |
|---|---|---|
| From-scratch creation | Universal | Absent |
| Structured editing UI | FlowCV/RR/Teal forms + preview | Raw text + JSON blobs |
| Live preview | Instant, paginated | Server round-trip HTML |
| Variants | Unlimited (RR) / paid tiers | One per job, zero free-standing |
| Version history | Rare in market (opportunity) | None (also our gap) |
| Template count/quality | 30–50 good | 6 mid |
| DOCX export | ~60% of market | None |
| Public share link | RR, FlowCV | None |
| Onboarding to first PDF | <10 min (FlowCV) | Not achievable without an existing CV document |

---

## 4. Current Data Model Review

### Entities and storage

- **`ApplicationUser.ProfileCvText` + `ProfileCvStructureJson`** — the master CV as two nullable string columns *on the Identity user row*. Sins: (a) fat blobs on the most-fetched row in the system (auth reads drag CV bytes along unless carefully projected); (b) exactly-one-profile hard-coded into the schema — the CvVariant/second-career-track future requires a migration by definition; (c) no versioning — every rebuild silently destroys the previous structure (extraction *runs* are versioned; the *applied profile* is not); (d) dual representation with no single source of truth — text and structure coexist, edits touch one, rebuilds overwrite the other, and different features read different ones (match scoring builds a corpus from raw text; tailoring generates from structure).
- **`StructuredCvProfile`** (JSON shape) — good: section-granular, extensible (`OtherSections`, generic `Sections`), already covers certifications/projects/languages/interests, and `Metadata.Fields` carries per-field provenance. Weak: date fields are free-strings (`Start`/`End`) so no reliable timeline math (career-timeline feature will choke); `Skills` is `List<string>` with no proficiency/category/years; no stable IDs on items, so "this bullet in the tailored draft came from job #2 bullet #3" is unexpressible — lineage between master and tailored content is lost at generation time.
- **`TailoredCvDraft`** — one row per job (`JobApplicationId` FK, effectively 1:1), section JSON blobs, `TemplateId` + `RenderOptionsJson` inline. Good: `CanonicalProfileVersion` + `GenerationContextHash` staleness primitives; JSON-blob sections are pragmatic for a document-shaped payload. Bad: **presentation welded to content** (can't render one draft in two themes without mutating it — the architecture proposal's core criticism, confirmed); no variant concept; no history; `Status` is a two-state string doing lifecycle work.
- **`CvUploadArtifact` / `CvExtractionRun`** — the best-designed corner: artifacts retained, runs versioned by parser/normalizer/prompt versions, `StructuredProfileJson` snapshot per run. This IS a version history — but only for imports, and nothing lets a user diff or restore from it.
- **Cover letters, interview prep, portfolios, public profiles** — no entities. Interview prep/fit/focus outputs are transient API responses; a user's best interview-prep notes evaporate.

### Verdict against the Career Workspace target

The proposal's target model (CareerProfile → CvVariant → CvVersion, Theme as sibling reference, TailoredCvVersion, output entities) is confirmed necessary by this audit, and the migration is *tractable*: `ProfileCvStructureJson` lifts into a `CareerProfile` table nearly verbatim; each `TailoredCvDraft` becomes a job-linked CvVariant with its render options extracted to a theme reference. Two additions this audit forces onto the proposal:
1. **Stable item IDs** in the profile schema (jobs, bullets, skills) — without them, variant/tailoring lineage, "update everywhere," and inheritance-with-overrides are all unimplementable.
2. **Normalize dates** (`YYYY-MM` + `isCurrent`) at migration time — timeline, tenure math, and skills-recency all depend on it; migrating free-strings later means re-parsing every profile again.

Also settle the source-of-truth rule explicitly: **structure is canonical; text is derived** (an export format and search corpus, regenerated on change) — and make match scoring read from structure so the two consumers stop diverging.

---

## 5. AI Workflow Review

### What exists

| Feature | Flow | Persistence |
|---|---|---|
| CV parse/normalize | upload → PDF text → `/cv/normalize` + `/cv/classify-block` (delimiter-fenced) → StructuredCvProfile | Run snapshots ✓ |
| Section rewrite / improve | section text + tone/language/target-role knobs → `/cv/rewrite` → replace text | Overwrites |
| Tailored generation | master profile + job context → draft sections | Overwrites draft |
| Match score | curated skill tags (synonym regex) + keyword corpus | Computed |
| Job-ad summary | local distilbart `/summarize` | Stored on job |
| Interview prep / fit / focus / follow-ups / package / readiness | per-job LLM calls from modal | **Transient** |

### What's genuinely valuable
- The **hardened pipeline** (fencing, ignore-embedded-instructions, provider router with graceful local fallback) — infrastructure competitors lack.
- **Match scoring** — the research's verdict was that JD-gap analysis is the one universally-praised AI feature; ours is real (curated synonyms beat naive keyword matching) and already wired to job data.
- **Structured extraction with confidence** — the input side of every future feature.

### What's limited
- **Rewrite is fire-and-forget:** no diff view, no accept/reject, no before/after. The research flagged "generic rewrite" as the gimmick tier and "improvement with visible diff/scoring" as the useful tier (BeamJobs pattern). We're on the wrong side of that line purely for lack of UI.
- **Tailoring is disconnected from scoring:** generation doesn't take the match-score gaps as input, and the score doesn't update live as the user edits the draft. The two halves of the killer loop exist and don't talk.
- **AI knobs are raw:** tone/language/target-role as form fields instead of intent-level actions ("make this more senior," "address this missing keyword").
- **Transient outputs:** interview prep and fit analyses regenerate (cost + latency + inconsistency) instead of persisting as reviewable documents.

### Hallucination / factual-accuracy risk — currently the biggest unmanaged AI risk
Rewrites and tailored generation can fabricate: a rewrite that upgrades "assisted with migration" to "led migration" is a *career integrity* failure, invisible today because nothing constrains generation to source facts or shows the user a diff. The provenance metadata (source snippets, confidence) exists on extraction but is **not enforced on generation**. Recommendation, in priority order:
1. Every AI mutation renders as a **diff with accept/reject** (also solves the limited-rewrite problem).
2. Generation prompts constrained to *select/rephrase* profile content, never invent quantities, employers, titles, or dates; validator pass flags novel named entities/numbers that don't appear in the source profile.
3. Tailored content carries source-item references (needs the stable IDs from §4) so "where did this claim come from" is answerable per bullet.

### Missing AI opportunities (ranked by leverage of existing assets)
1. **Tailoring loop screen:** match gaps ↔ draft side-by-side, one-click "address this gap" → constrained rewrite → live rescore. Wires three existing services into the screen no competitor has with our rendering quality.
2. **Extraction review queue:** low-confidence fields surfaced as confirm/fix cards — turns existing provenance metadata into visible trust.
3. **Email-aware prep:** interview prep that reads the actual recruiter thread (unique data no competitor holds).
4. **Skills-gap analytics across tracked JDs:** "your last 15 rejections wanted X" — pure aggregation over data already stored.

---

## 6. Template & Rendering Review

### Pipeline
`TailoredCvDocument` → `CvTemplateRenderer.Render(templateId, …)` → C# switch over 6 template methods building HTML strings (~450 lines total) → `PlaywrightCvPdfExporter` (headless Chromium print) → PDF. Accent resolved via `ResolveAccent` (slate/blue/emerald/plum/brick → hex). Same renderer drives HTML preview and PDF (single source of visual truth — good).

### Evaluation
- **Add a new template:** write a new C# method, recompile, redeploy. Designer-inaccessible, review-heavy, untestable in isolation. Cost is why there are six.
- **User customization:** limited to the curated render options; anything more means more C# branches.
- **Premium/marketplace templates:** impossible — templates are compiled code; third-party code in the renderer is a non-starter (research §6 confirmed themes-as-declarative-data is how RR solves this: "Structured Style Rules").
- **Career Workspace outputs:** each new output type (public profile page, portfolio, DOCX) would today mean another hardcoded renderer. The proposal's `IOutputAdapter` + Scriban theme manifests directly answers this; this audit adds one guardrail from RR's history (they abandoned server-Chromium for cost): **keep the PDF backend swappable behind the adapter interface** — Playwright is right for us now (already built, small scale, full CSS), but the boundary must let a lighter renderer replace it without touching themes.
- **Print fidelity risks present today:** page-break control is CSS-implicit (no explicit widow/orphan handling per section); "one-page" mode is a squeeze heuristic rather than a layout contract. Fine at 6 templates; codify break rules in the theme manifest schema when porting.

---

## 7. Feature Gap Analysis

### Missing entirely (vs. competitor research + vision)

| Feature | Competitor bar | Vision need | Cost given our architecture |
|---|---|---|---|
| From-scratch CV creation | Universal | Yes | Medium (structured editor is the prerequisite) |
| Multiple CV variants | RR unlimited; FlowCV's paywall seam | Core | Schema migration (planned) |
| CV version history | Market gap — differentiator | Core | Medium (CvVersion planned) |
| Structured profile editor + review UI | Universal (forms) | Core | Large — the main UI build |
| Live paginated preview | FlowCV bar | Yes | Medium |
| DOCX export | ~60% of market, loud complaints | Yes | Medium, scoped (per proposal) |
| Public profile / share link | RR, FlowCV | Core (Horizon 2) | Medium (adapter + theme) |
| Cover letter entity + generation | All paid competitors | Core | Small once profile model lands |
| Career timeline / skills matrix | Nobody good | Differentiator | Small *after* date normalization |
| Persisted interview prep | Nobody (market gap) | Differentiator | Small (persist what exists) |
| ATS-view ("what the parser sees") | Nobody shows it | Trust play | Small (we have structure) |
| JSON Resume interop / published schema | RR | Trust play | Small |

### Exists but needs improvement
1. **OAuth CV lockout bug** — fix now (one-line frontend condition).
2. Tailored editor out of the modal into a full-page workspace route.
3. Rewrite → diff/accept/reject.
4. Match score ↔ tailoring connection (the loop).
5. Template gallery with visual previews (data exists in `templates` endpoint descriptors).
6. Pipeline vocabulary → user vocabulary ("Update from new CV," not "Reprocess run").
7. Section editing: per-bullet rows with reorder + inline AI, not newline blobs.
8. Extraction confidence → visible review flow instead of buried metadata.

---

## 8. Product Recommendations

### Immediate (low effort / high impact — do before or alongside Phase 1)

| # | Recommendation | Problem | User value | Technical impact | Complexity | Priority |
|---|---|---|---|---|---|---|
| I1 | Fix OAuth `isLocal` CV lockout | Google/MS users can't use CV features | Unblocks all OAuth users | One condition split (identity-gates vs. feature-gates) | Trivial | **P0 — bug** |
| I2 | Diff + accept/reject on every AI rewrite | Silent overwrites; hallucination invisible | Trust in AI edits; recoverability | Frontend diff view; keep previous text | Small | P1 |
| I3 | Persist interview prep / fit outputs | Regeneration cost; lost work | Notes survive; consistent prep | One table or JSON column per job | Small | P1 |
| I4 | Rename pipeline vocabulary in UI | Developer jargon confuses | Comprehensible actions | i18n strings only | Trivial | P1 |
| I5 | Template gallery with thumbnails | Blind dropdown | Informed template choice | Render 6 previews once; static images | Small | P2 |
| I6 | Export JSON of profile + drafts | No data-ownership story | Trust (research: fair-exit is strategy) | One endpoint, serializers exist | Small | P2 |

### Medium-term (requires the architectural work — Phases 1–4 of proposal)

| # | Recommendation | Problem | User value | Technical impact | Complexity | Priority |
|---|---|---|---|---|---|---|
| M1 | CareerProfile + CvVariant + CvVersion migration, **with stable item IDs and normalized dates** | One profile, no variants, no history, blobs on user row | Multiple CVs, safe regeneration, update-everywhere | The Phase-1 schema migration; raw-SQL reconciler steps | Large | P0 of redesign |
| M2 | Structured profile editor + extraction review queue | Raw text is the editing surface | Maintainable career data; visible trust | New primary UI; confidence metadata already present | Large | P0 of redesign |
| M3 | Theme engine port (Scriban manifests, 6→best 3–4 templates) | Templates are compiled code | Theme switching, future marketplace | Per proposal; add explicit page-break rules to manifest schema | Large | P1 |
| M4 | Tailoring workspace route (JD gaps ↔ draft ↔ live preview + rescore) | Modal editor; disconnected scoring | The killer screen (research MVP §9.5) | New route; wires existing services | Large | P1 |
| M5 | Fact-constrained generation + novel-entity validator | Hallucinated seniority/numbers | Career integrity | Prompt + validator in FastAPI sidecar | Medium | P1 |
| M6 | From-scratch creation + paste-text import | Import-only onboarding | New-grad / no-CV users can start | Falls out of M2 + existing normalize path | Medium | P2 |
| M7 | Scoped DOCX adapter | Loudest export complaint in market | "Employer wants Word" solved | OpenXML `IOutputAdapter` (per proposal: scoped, not parity) | Medium | P2 |

### Long-term (Career Workspace horizons)

| # | Recommendation | Value | Complexity | Priority |
|---|---|---|---|---|
| L1 | Cover letters as profile-derived entities | Completes application package | Small post-M1 | P1 of Horizon 2 |
| L2 | Public profile / share link (theme over live profile, not doc snapshot) | RR/FlowCV parity + our live-data twist | Medium | P2 |
| L3 | Email-aware interview prep (thread context) | Unique-data moat; market's empty space | Medium | P1 of Horizon 3 |
| L4 | Skills matrix + gap analytics across tracked JDs | Nobody has it; pure aggregation for us | Small post-M1 | P2 |
| L5 | Portfolio / personal site as output adapters | Vision endgame; each ≈ adapter + theme | Medium each | P3 |
| L6 | Published profile schema + JSON Resume interop (+ MCP later) | Trust + agent-native future (RR precedent) | Small | P3 |

---

## 9. Proposed Future Architecture

Confirms the architecture proposal, with this audit's amendments folded in:

```
CareerProfile  (one per user now, N later; stable item IDs; normalized dates;
 │              provenance metadata retained; STRUCTURE canonical, text derived)
 │  sources: upload/extraction runs (kept), from-scratch editor, paste-text
 │
 ├──< CvVariant          (free-standing OR job-linked; inherits profile, holds
 │      │                 selections/overrides by item ID — lineage preserved)
 │      ├──< CvVersion   (history: every generation & manual save; diff/restore)
 │      └── ThemeRef ────→ Theme (SIBLING input, never welded into content;
 │                          declarative Scriban manifest: tokens, layout,
 │                          section styles, page-break rules; ATS rating)
 │
 ├── Tailoring loop      (JobApplication + MatchScore gaps ↔ variant edits ↔
 │                        constrained AI rewrites w/ diff ↔ live rescore)
 │
 └──> IOutputAdapter<T>  (renderer boundary; PDF backend swappable — RR lesson)
        ├─ PDF CV            (Playwright, today)
        ├─ DOCX CV           (OpenXML, scoped)
        ├─ ATS plain-text    (trust view — near-free)
        ├─ Cover letter      (profile + job + thread context)
        ├─ Public profile    (theme over live profile)
        ├─ Portfolio / site  (Horizon 3+)
        ├─ LinkedIn content  (Horizon 3)
        └─ Interview prep    (persisted; email-thread-aware — unique moat)
```

**Why this redesign is right (one paragraph):** every weakness this teardown found — single profile as user-row blobs, text/structure truth conflict, presentation welded to drafts, overwrite-anxiety mitigations, compiled templates, modal-trapped editing, transient AI outputs — is a symptom of the same root cause: *documents are the primary objects and career data is trapped inside them*. The competitor research shows the market leader in each dimension solved exactly one symptom (RR: schema; FlowCV: editor; Teal: career data; Novoresume: themes) and none solved the root. Inverting the model — profile as source of truth, every artifact a themed projection — fixes all symptoms with one architecture, reuses our real assets (extraction provenance, match scoring, email intelligence, hardened AI pipeline, Playwright rendering), and each subsequent output costs one adapter + one theme instead of one product.

**Sequencing note:** I1 (OAuth bug) ships now. M1+M2 before any visible redesign — the migration is invisible and everything depends on it. M3/M4 are the visible payoff. The proposal's phase plan stands; this audit adds stable IDs + date normalization as Phase-1 requirements and the diff-everywhere rule as a design principle from day one.
