Files
jobtrackingapp/docs/cv-builder-product-teardown.md
cesnimda aedd6e32ad docs: recover Career Workspace research + strategy from feature/career-workspace
Bring the four Career Workspace documents onto main as the target architecture
for Phases 2-4, and point MASTER_IMPLEMENTATION_GUIDE.md at them. Taken from the
branch tip (later commits refined them). Pure additions — none previously existed
on main.

- cv-builder-competitor-deep-research.md (Novoresume, Reactive Resume, FlowCV,
  Teal, Enhancv, Canva, Resume.io, Kickresume; matrix; pricing intelligence).
- cv-builder-product-teardown.md
- career-workspace-product-strategy.md
- career-workspace-implementation-roadmap.md (F0-F5)

MASTER_IMPLEMENTATION_GUIDE.md v1.1: adds a Source-Of-Truth Documents section and
restates the "profile is the source of truth; documents reference snapshots" rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:06:46 +02:00

322 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:24652614`, `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 | 3050 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 14 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 34 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.