From aedd6e32ad665fe01448b4101d577c0756ab2f79 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Fri, 17 Jul 2026 17:06:46 +0200 Subject: [PATCH] docs: recover Career Workspace research + strategy from feature/career-workspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .gitignore | 3 + docs/MASTER_IMPLEMENTATION_GUIDE.md | 53 ++- ...career-workspace-implementation-roadmap.md | 124 +++++++ docs/career-workspace-product-strategy.md | 328 ++++++++++++++++++ docs/cv-builder-competitor-deep-research.md | 327 +++++++++++++++++ docs/cv-builder-product-teardown.md | 321 +++++++++++++++++ 6 files changed, 1151 insertions(+), 5 deletions(-) create mode 100644 docs/career-workspace-implementation-roadmap.md create mode 100644 docs/career-workspace-product-strategy.md create mode 100644 docs/cv-builder-competitor-deep-research.md create mode 100644 docs/cv-builder-product-teardown.md diff --git a/.gitignore b/.gitignore index 54c58d1..6a14381 100644 --- a/.gitignore +++ b/.gitignore @@ -93,3 +93,6 @@ target/ # ── GSD baseline (auto-generated) ── .gsd-id .gsd/ + +# TS incremental build info +*.tsbuildinfo diff --git a/docs/MASTER_IMPLEMENTATION_GUIDE.md b/docs/MASTER_IMPLEMENTATION_GUIDE.md index 8931e63..6ba8f7a 100644 --- a/docs/MASTER_IMPLEMENTATION_GUIDE.md +++ b/docs/MASTER_IMPLEMENTATION_GUIDE.md @@ -1,18 +1,21 @@ # Jobjakt Master Implementation Guide -Version: 1.0 +Version: 1.1 --- # Purpose Of This Document -This document is the primary source of truth for understanding and improving Jobjakt. +This document is the primary source of truth for the product *direction*. For the +*current system* and the *recovered Career Workspace architecture*, read the documents +linked below — they are verified against the code and take precedence over any stub +elsewhere in `docs/`. Before making significant changes: -1. Read this document. -2. Read the relevant linked documentation. -3. Understand existing architecture. +1. Read this document (direction and non-negotiables). +2. Read the relevant linked documentation (below). +3. Understand existing architecture — verify against the actual codebase. 4. Make incremental improvements. Do not redesign based on assumptions. @@ -21,6 +24,46 @@ Always verify against the actual codebase. --- +# Source-Of-Truth Documents + +Read these before touching the areas they cover. Where one disagrees with the code, +the code wins and the document is a bug to fix. + +## Current system (verified) + +- `docs/architecture/current.md` — the system as it actually is: projects, controllers, + data model, auth, AI pipeline, CI/CD, security posture. **Start here.** +- `docs/application-discovery-report.md` — full audit and feature inventory. +- `docs/phase-0-foundation-report.md` — what Phase 0 changed (Job entity, expanded + pipeline, AI-service lockdown, DateApplied history) and how it was verified. +- `docs/implementation-roadmap.md` — the phased plan and settled product decisions. + +## Decisions + +- `docs/decisions/ADR-002-job-application-model.md` — Job (opportunity) separated from + JobApplication (pursuit); the reference-not-ownership rule. + +## Career Workspace — the recovered architecture (governs Phase 2–4) + +These four documents were recovered from the `feature/career-workspace` branch and are +the **target architecture** for the Career Workspace. They, plus ADR-002, define how +career data is stored and how generated documents reference it. + +- `docs/career-workspace-product-strategy.md` — vision, positioning, personas. +- `docs/career-workspace-implementation-roadmap.md` — the F0–F5 execution plan. +- `docs/cv-builder-competitor-deep-research.md` — Novoresume, Reactive Resume, FlowCV, + Teal, Enhancv, Canva, Resume.io, Kickresume; feature matrix; pricing intelligence. +- `docs/cv-builder-product-teardown.md` — product teardown. +- `docs/career-workspace-branch-assessment.md` — per-table recover/migrate/replace verdict + for the recovered Career/CV tables. + +**Non-negotiable, restated from those documents:** the master career profile is the single +source of truth. Career data is never duplicated into CVs, applications, or variants — a +generated document *references* a snapshot; it does not own the data. Job tracking remains +the primary product; the Career Workspace is a bounded supporting domain. + +--- + # Product Identity ## Application Name diff --git a/docs/career-workspace-implementation-roadmap.md b/docs/career-workspace-implementation-roadmap.md new file mode 100644 index 0000000..3e21362 --- /dev/null +++ b/docs/career-workspace-implementation-roadmap.md @@ -0,0 +1,124 @@ +# Career Workspace — Implementation Roadmap (ADR + sequencing) + +**Date:** 2026-07-12 +**Status:** Active. F0–F2 shipped; F3 and F5 partially shipped (see phase sections below for exactly what landed vs. what's still open). This is the execution plan that turns the three strategy docs into code, incrementally, without breaking the live app. +**Source of truth:** `cv-builder-competitor-deep-research.md`, `cv-builder-product-teardown.md`, `career-workspace-product-strategy.md`. + +--- + +## Product boundary (non-negotiable, per owner directive 2026-07-12) + +The **primary product stays Job Search & Application Management.** The Career Workspace is a **bounded supporting domain** that makes applications better. Rules that constrain every change below: + +- Job applications **reference** career outputs; they do **not own** them. +- Career profile data is **independent of any single job**. +- Themes/templates are **independent of application workflow**. +- Do **not** remove, replace, or redesign job-tracking functionality. The goal is **integration**, not replacement. + +``` +Main product: Job Search & Application Management + └─ tracking · applications · company research · interviews · matching +Supporting: Career Workspace (bounded domain) + └─ profile · CV variants · tailored CVs · cover letters · outputs · AI assist +Integration: JobApplication ──references──▶ TailoredApplication ──uses──▶ CvVariant ──inherits──▶ CareerProfile +``` + +The tracker references a career output by id; deleting a job never deletes profile/variant data; a variant exists with zero jobs attached. + +--- + +## Migration mechanics (how this codebase actually changes schema) + +**No EF migrations in practice.** Schema is provisioned by the idempotent raw-SQL reconciler in `StartupInitializationExtensions.InitializeJobTrackerAsync`. Both dialects are hand-maintained: +- **SQLite (dev):** `Ensure*Table(conn)` helpers with `CREATE TABLE IF NOT EXISTS` + `EnsureColumn` guards, called in the `useSqliteBootstrap` branch (near `EnsureCvTables`). +- **MySQL/MariaDB (prod):** `if (!HasMySqlTable(...))` blocks + `EnsureMySqlColumn`, in the `else` branch. + +**Every new table therefore needs BOTH dialect blocks + a `DbSet` + `OnModelCreating` config** (query filter + indexes). The EF `ModelSnapshot` is known-stale; do not rely on `Migrate()` to create Career tables — add them to the reconciler. + +**Rule for this roadmap:** all new tables are **additive**. Nothing drops `ApplicationUser.ProfileCvStructureJson` or `TailoredCvDrafts` until the new path is proven in production and dual-read has run clean. Backwards-compatible at every step. + +--- + +## Target entities (only what earns its place) + +| Entity | Purpose | Replaces / relates | When | +|---|---|---|---| +| `CareerProfile` | The durable source of truth; one per user (N later). Structure canonical, text derived. | Lifts `ApplicationUser.ProfileCvStructureJson` off the Identity row | Phase F1 | +| `CareerProfileVersion` | Append-only history of the profile; diff/restore | New (teardown gap) | F1 (table) / F4 (UI) | +| `CvVariant` | Persistent lens on the profile: selections + per-item overrides, named ("Backend focus") | Generalizes today's single implicit CV | F2 | +| `CvVersion` | Append-only history of a variant (every generation/save) | New; kills overwrite-anxiety | F2 | +| `CvTheme` (data, not table yet) | Declarative theme descriptor; sibling input to renderer, never welded to content | Formalizes `CvTemplateRenderer` catalog | F3 | +| `TailoredApplication` | Variant × JobApplication event; gap-driven tweaks; **the integration seam** | Reworks `TailoredCvDraft`'s job-lock into a reference | F2/F5 | + +**Schema amendments forced by the teardown (apply at table-creation time — cheap now, brutal later):** +1. **Stable item IDs** on every profile item (jobs, bullets, skills, education, projects). Without them, variant lineage / "update everywhere" / inherit-with-override are unimplementable. +2. **Normalized dates** (`YYYY-MM` + `isCurrent`) — backfilled during migration by best-effort parse of the free-string `Start`/`End`. Timeline / tenure / skills-recency depend on it. +3. **Structure is canonical; text is derived** — stated in code. Match scoring reads structure (F5), not raw text. + +--- + +## Phased sequence (each phase ships independently, app stays green) + +### Phase F0 — Immediate fixes (no architecture) ✅ SHIPPED +- **OAuth CV lockout fixed** (`ProfilePage.tsx`): CV controls gated on a new `canEditCv` (any authenticated user) instead of `isLocal`. Identity/password fields remain local-only. Unblocks every Google/Microsoft user. + - Commit: `fix: unlock CV builder for Google/Microsoft-authenticated users` + +### Phase F1 — Career Profile as first-class data (backwards-compatible seam) ✅ SHIPPED +1. ✅ `CareerProfile` + `CareerProfileVersion` tables in the reconciler (both dialects), `DbSet`s, query filters, indexes. +2. ✅ `ICareerProfileService` — dual-writes: persists to `CareerProfiles`/`CareerProfileVersions` on every structured-profile save (upload/rebuild/improve/reprocess/parse) while `ApplicationUser.ProfileCvStructureJson` stays the column every existing read path uses. +3. ✅ Stable item IDs (jobs/education/certifications/projects) + normalized `YYYY-MM` dates (`CvDateNormalizer`) assigned on save. +4. **Not done:** full cutover of `ProfileCvController` read paths to the service (still reads `user.ProfileCvStructureJson` directly). The service is invoked at every write site but reads haven't moved yet — deliberate: F1's own exit criteria says "new table is authoritative; column is a mirror" implies read cutover is a *later* step once the table's been proven, not this pass. +5. ✅ 11 tests (stable IDs, date normalization incl. `IsCurrent` guard, version history, dual-write). Verified against the real dev DB. + - Commit: `feat: add career profile foundation with versioned history` + +### Phase F2 — Variants + versions (additive, opt-in) ✅ SHIPPED +1. ✅ `CvVariant` + `CvVersion` tables. `CvVariant.CareerProfileId` links to the user's current `CareerProfile` (nullable, `SetNull` on delete — not owned, not cascaded). +2. ✅ `TailoredApplication` table: `(CvVariantId, JobApplicationId)`, unique per `(OwnerUserId, JobApplicationId)`. Job references the tailored output; does not own the variant. Both FKs cascade (the link is meaningless without either side). +3. ✅ Dual-write (not a one-time backfill): both `TailoredCvDraft` save paths (`SaveTailoredCvDraft`, `UpsertGeneratedTailoredCvDraftAsync`) now also upsert the variant, bump its version, append a `CvVersion` snapshot, and ensure the `TailoredApplication` link — via `SyncCvVariantFromDraftAsync`. Reuses `TailoredCvDocument` as `ContentJson` (zero new data shape). `TailoredCvDrafts` remains authoritative for every existing read path. +4. **Not done:** a one-time backfill of *pre-existing* `TailoredCvDraft` rows that predate this change (only rows saved *after* this ships get synced). **Not done:** `/career/*` endpoints — nothing reads `CvVariant`/`CvVersion` yet; this phase is pure write-side foundation, same "populate before UI" strategy as F1. +5. ✅ 2 tests (variant/version/link created on first save; same variant reused + version incremented on resave, not duplicated). Verified against the real dev DB — FK dependency order (`CareerProfiles` → `CvVariants` → `CvVersions`/`TailoredApplications`) holds in both dialects. + - Commit: `feat: introduce CV variant schema, dual-written from tailored CV saves` + +**Next actions on this phase (not started):** (a) backfill script for pre-existing `TailoredCvDraft` rows if the table shouldn't have a "before my change" gap; (b) a read endpoint exposing `CvVariant` list — the actual precondition for F4's "reuse a variant across jobs" UI to mean anything. + +### Phase F3 — Rendering as data (theme catalog) — PARTIAL +1. ✅ `CvTemplateDescriptor` (backend, `ProfileCvController.GetCvTemplateDescriptors`) extended with `LayoutFamily` + `AtsRating`; surfaced as a badge in the frontend template picker (`ProfilePage.tsx`). +2. ✅ 14-test regression suite (`CvTemplateRendererTests`) locking in current renderer output — the prerequisite for a safe future extraction — landed *before* touching the renderer, per the golden-test discipline this phase calls for. +3. **Not done:** the actual extraction (renderer consumes `(document, theme)` as data; layout shells as a fixed set; theme = shell + tokens). The six `RenderXxx` HTML-string methods in `CvTemplateRenderer` are unchanged. Real work, real PDF-regression risk, correctly *not* attempted in the same pass as unrelated feature work. +4. **Known gap surfaced this pass:** the frontend never calls `GET /profile-cv/templates` — it duplicates the template catalog in a hardcoded `REWRITE_TEMPLATES` array in `ProfilePage.tsx`. Two sources of truth for template metadata. Worth fixing *as part of* the F3 extraction (single source becomes the natural output), not before. +5. **Deferred:** external template engine (Scriban) + user/marketplace themes — only when a marketplace is real (strategy §9). + +**Next action on this phase:** the extraction itself (item 3) — budget a dedicated pass; the regression suite (item 2) is what makes it safe to attempt. + +### Phase F4 — CV Builder UX (structured editor + tailoring workspace) +- Structured profile editor route (`/career/profile`): section forms, per-bullet reorder, provenance-flagged review queue for low-confidence fields. +- From-scratch + paste-text entry paths (removes import-only dead end). +- Tailoring workspace route (`/jobs/:id/tailor`): JD gap chips ↔ variant editor ↔ live themed preview ↔ rescore. Retire the modal editor. **Reached from a job** (integration), full page. +- **Career** becomes a top-level nav pillar (Profile · Variants); "CV" ceases to be a nav noun. Tracker nav untouched. + +### Phase F5 — AI depth + integration — PARTIAL +- ✅ **Diff view for AI rewrites**: `TextDiff` component (word-level `diffWords`), wired into the master-CV rewrite preview behind a "Show changes" toggle (default off — an existing test proved diff-by-default breaks the plain-text read). Scoped to the master-CV rewrite surface only; the tailored-CV draft regenerate flow already had a confirm+reset safety net and wasn't a good fit for the same treatment (would mean diffing structured fields, which is F4 tailoring-workspace scope). + - Commit: `feat: show diff view for AI CV rewrites` +- ✅ **Persist interview prep** (`InterviewPrepNote`, one table, per-field columns — the DTO is flat) and **candidate fit + focus plan** (`AiWorkspaceNote`, one generic table keyed by `NoteType` — those DTOs are irregular/nested, so per-field columns would've been unreasonable; introduced the generalization on the 2nd/3rd occurrence, not the 1st). All three: reuse across tab-opens, regenerate on attachment-context change, explicit "Regenerate" button as the escape hatch. Collectively these three tabs fired 9 AI calls on every single re-open before this; now 0 unless something changed. + - Commits: `feat: persist interview prep instead of regenerating on every open`, `feat: persist candidate fit and focus plan, stop re-running on every open` +- ✅ **ATS-safety badge** on the template picker (folded into the F3 entry above — same commit touched both, since `AtsRating` is a field on the template descriptor). +- **Not done:** `JobCvMatchService` retarget — checked the live code this pass and found `BuildCvSearchCorpus` already reads structured profile *and* raw text as a hybrid (better than the teardown assumed); no change needed. Closing this line item as **resolved, not deferred**. +- **Not done:** fact-constraint validator (novel named-entity/number flagging on AI generation). Real remaining trust gap — the diff view lets a user *see* a fabrication, but nothing stops the model from producing one. Good next F5 slice. + +### Phase F6+ — Career Workspace horizons (architecture-ready, not built now) +Cover letters (profile+JD+thread) · ATS plain-text view · skills-gap analytics · public profile (theme over live profile) · DOCX adapter · portfolio/LinkedIn adapters. Each ≈ one `IOutputAdapter` + optional theme. Keep the adapter boundary swap-clean (Reactive Resume abandoned server-Chromium for cost — our Playwright PDF adapter must stay replaceable without touching themes). + +--- + +## Risk register + +| Risk | Mitigation | +|---|---| +| Live-DB schema migration breaks prod | Additive-only tables; dual-write/dual-read; never drop legacy until proven; test reconciler against a prod DB copy | +| Backfill mis-parses free-string dates | Best-effort normalize, keep original string alongside normalized fields; never lose data | +| PDF output regression in theme refactor | Golden byte-identical test before merge | +| Scope creep into job-tracker redesign | Boundary rules above; tracker code out of scope | +| Two sources of truth diverge (F1 interim) | Time-box the dual-write window; F5 retargets the last consumer (match scoring) then column is dropped | + +## Working-summary convention +Each session updates **## Completed / ## Current / ## Next / ## Decisions** in the response. This file is the durable plan; the running summary is the session delta. diff --git a/docs/career-workspace-product-strategy.md b/docs/career-workspace-product-strategy.md new file mode 100644 index 0000000..0457f2f --- /dev/null +++ b/docs/career-workspace-product-strategy.md @@ -0,0 +1,328 @@ +# Career Workspace — Product Strategy & Roadmap + +**Date:** 2026-07-12 +**Inputs:** `docs/cv-builder-competitor-deep-research.md` (market), `docs/cv-builder-product-teardown.md` (as-is audit), CV Builder Architecture Proposal (target design). +**Nature:** This is a *decision document*, not a summary. Where the research allowed multiple directions, this document picks one and says why. Intended as the foundation for Fable 5 product planning, the engineering roadmap, UX design, and architecture decisions. + +--- + +## 1. Product Vision + +### The one-sentence vision + +> **Jobbjakt is the workspace where your career data lives once and works everywhere — every CV, cover letter, application, and interview prep session is generated from, and feeds back into, one structured career profile.** + +### Evaluating "the CV is not the product; the profile is" + +The premise is **correct but incomplete**. Correct: the teardown proved every current defect traces to documents-as-primary-objects, and the research proved no competitor owns the profile-first position. Incomplete: a profile sitting still is a database, not a product. What users pay attention to (and would pay money for) is what the profile *does under pressure* — during an active job search. + +**Refined thesis:** the product is the **loop**, and the profile is its engine: + +``` +Career Profile → tailored application (CV + letter, gap-driven) + ↑ ↓ + learns from ← application outcome (tracked, email-observed) +``` + +Every competitor owns at most one arc of this loop. Teal has profile→tailor but weak rendering and stops at "Applied." Design-led builders have rendering but no career data. Reactive Resume has clean documents but zero context. **Nobody closes the loop — and we already own its rarest arc: the outcome-observation side (Gmail intelligence, status suggestions, tracking).** That asymmetry is the strategy. We don't build a CV builder and add tracking; we already have the tracking-and-intelligence layer competitors can't cheaply replicate, and we're adding the one commodity piece (a good CV builder) they all have. + +### The problems we solve + +1. **Fragmentation:** the modern search runs across 5–7 tools (builder, tracker, spreadsheet, keyword checker, letter writer, prep notes) with career data re-typed into each. One profile, one workspace. +2. **Tailoring cost:** serious applicants need 3–5 tailored variants; today that means duplicated documents that rot independently. Variants inherit from the profile; the profile updates everywhere. +3. **Blind applications:** applicants don't know what the ATS sees or what the JD wants. Match scoring + gap-driven tailoring + ATS-view make it visible. +4. **Career amnesia:** achievements evaporate between searches. A durable, versioned profile means the next search starts warm. +5. **Trust:** the incumbent market monetises desperation (subscription traps, resume hostage-taking, secret AI caps). We are structurally incapable of it (self-hostable, export-always-free) and say so. + +### Unique value proposition + +> "Other tools build you a document. Jobbjakt runs your search: one career profile that generates every tailored CV and cover letter, scores you against every job you track, reads the recruiter's reply, and preps you for the interview it lands — without ever holding your data hostage." + +--- + +## 2. Product Positioning + +### Landscape verdict (from the research, compressed to decisions) + +| Cluster | Their strength | Their structural weakness | Our exploit | +|---|---|---|---| +| Design-led (Novoresume, Resume.io, Kickresume, Enhancv) | Template quality, polish, distribution | Predatory billing = trust vacuum; zero career context; PDF-only lock-ins | Fair-exit guarantee + career context they'd have to rebuild their revenue model to match | +| Free/OSS (Reactive Resume, FlowCV) | Trust, editor UX, price | Document-centric; no job context; thin AI | Match their fairness, exceed them on the loop | +| Workspace (Teal) | The loop's left half; tracker distribution | Weak rendering; stops at the application; shallow profile | Deeper profile + real rendering + post-application intelligence | +| Canvas (Canva) | Design freedom | 72% ATS failure — architecturally unfixable | "ATS-safe by construction," provable | + +### Why choose us over each + +- **Over Novoresume/Resume.io:** same-or-better output quality, no watermark, no hostage, and your CV knows about your job search. +- **Over FlowCV:** everything FlowCV's editor does, plus the variant you're editing is scored against the actual job and updated from your profile. +- **Over Reactive Resume:** same data-ownership ethos (self-hostable, schema published), plus tailoring, tracking, and email intelligence a document tool can't have. +- **Over Teal:** your tailored CV actually looks professional, your profile is deep (provenance, versions), and the workspace doesn't go silent after you click Apply — it reads the interview invite and preps you for it. +- **Over Canva:** structured data → deterministic parse order → every theme ATS-safe by construction, with a "view as ATS" proof. + +### Positioning statement + +> **For active job seekers who apply to many roles, Jobbjakt is the AI career workspace that turns one structured career profile into every tailored application — unlike resume builders that produce disconnected documents and trackers that abandon you after you apply, Jobbjakt closes the loop from profile to interview, and never holds your data hostage.** + +Two positioning disciplines that follow: +1. **Never market as "resume builder."** In that category we have 6 templates against Novoresume's brand; we'd be comparison-shopped on our weakest axis. The category is *career workspace* — young enough (GigForge/ResumeTrakr-tier entrants only) that a quality product can define it. +2. **Trust is a feature with a spec:** export always free (PDF+JSON minimum), no watermark, visible AI quotas, published profile schema, one-click full data export. Each is cheap for us and revenue-model-breaking for incumbents — the definition of a durable wedge. + +--- + +## 3. Target User Personas + +Priority order is a decision, not a list: build for P1/P2 first — they exercise the loop hardest and match the product's existing DNA (the current user *is* P2). + +### P1 — "Marcus," the high-volume applicant (primary) +Mid-career, applying to 30–80 roles across 2–3 title families. **Goals:** volume without quality collapse; know which applications are alive. **Frustrations:** re-tailoring is an hour per application so he stops tailoring; loses track of threads; every builder wants $25/mo at his most broke moment. **Workflow:** job boards → save → tailor (or guiltily don't) → apply → chaos of follow-ups. **Needs most:** variants with cheap tailoring (gap chips, one-click fixes), tracker + email auto-linking (exists), follow-up nudges (exists), match score triage ("which of these 12 saved jobs am I actually competitive for?"). + +### P2 — "Dana," the technical professional (primary; the current user) +Developer/engineer, deliberate search, 5–15 applications. **Goals:** precision-target roles; CV that survives both ATS and senior-engineer skim; own her data. **Frustrations:** builders dumb down technical content; canvas tools break ATS; distrusts SaaS lock-in. **Workflow:** deep JD reading → heavy per-role tailoring → tracked follow-ups. **Needs most:** structured editor with real skill taxonomy, ATS-view, self-hosting/schema/JSON export, constrained AI that never invents seniority, diff-on-every-AI-edit. + +### P3 — "Sofia," the career changer (secondary) +Moving between fields; same history must tell two different stories. **Goals:** reframe, not fabricate. **Frustrations:** single-CV tools force one narrative; AI rewrites drift into fiction. **Needs most:** multiple *free-standing* variants from one profile (the sharpest validation of profile/variant separation), transferable-skills surfacing, fact-constrained rewriting with visible diffs, cover letters that carry the reframing story. + +### P4 — "Tom," the recent graduate (secondary) +First real CV, thin content, no existing document. **Goals:** credible one-pager fast. **Frustrations:** import-only tools (our current dead end — teardown §2) assume a CV exists; blank-page paralysis. **Needs most:** from-scratch guided creation, content prompts ("what did you build at university?"), one great simple theme, honest bullet suggestions. *Strategic note: Tom is the acquisition persona (students share tools; Kickresume's student program precedent) but must not drive architecture — his needs are a subset of P1–P3's editor.* + +### P5 — "Priya," the international applicant (secondary) +Cross-border applications; conventions differ (photo/no-photo, page norms, spelling, market-specific sections). **Needs most:** per-variant conventions (photo toggle exists; page mode exists), multilingual content support (rewrite `language` knob exists — rare head start), theme conventions per market, visa/eligibility custom sections. Mostly configuration breadth on top of the variant model, not new architecture. + +### P6 — "Elena," the experienced executive (tertiary) +20+ years, 3-page history, selective applications. **Needs most:** selective inclusion per variant (long profile, curated projections — again the variant model), discreet public profile, quality typography. Buys or self-hosts on trust and polish. Serve architecturally, don't design for first. + +**Pattern worth noticing:** P3, P5, P6 all reduce to "one deep profile, many selective projections." The personas independently re-derive the core architecture — good sign the architecture is right. + +--- + +## 4. Core Product Model + +``` +CareerProfile ← the durable asset (one per user; N later) + │ everything you've ever done: jobs, bullets, skills, education, projects, + │ certs, languages; stable item IDs; normalized dates; provenance metadata + │ (confidence, source, review state); versioned; structure canonical + │ + ├──< CvVariant ← a persistent *lens* on the profile + │ │ "Backend-focused" / "Team-lead-focused" / "Career-change: data" + │ │ selections + overrides by item ID (inherits; profile edits flow in; + │ │ overrides tracked, revertible) + │ ├──< CvVersion ← history: every save/generation; diff/restore + │ └── ThemeRef → Theme ← SIBLING input: declarative manifest + │ (tokens, layout, section styles, page-break + │ rules, ATS rating). Never welded to content. + │ + ├──< TailoredApplication ← the *event*: variant × JobApplication + │ │ gap analysis (match score), per-application tweaks, its own versions + │ └── outcome feedback ← tracker status + Gmail intelligence + │ + └──> Outputs (adapters; each = projection of profile/variant/application + theme) + PDF CV · DOCX CV (scoped) · ATS plain-text view · Cover letter + Public profile (live, not snapshot) · Portfolio/personal site + LinkedIn content · Interview prep (persisted; email-thread-aware) +``` + +**Relationships, precisely:** +- **Profile → Variant** is *inheritance with selection*: a variant names which profile items appear, in what order, with what per-item overrides. Edit a job title in the profile → every non-overridden variant reflects it. This single mechanism serves Marcus (volume), Sofia (two narratives), Elena (selective depth). +- **Variant → TailoredApplication** is *instantiation against a job*: the variant is durable and reusable; the tailored application is per-job, driven by the match-score gap list, and versioned so regeneration never destroys manual work (fixes the teardown's overwrite-anxiety root cause). +- **Theme** is orthogonal to all content. Any variant/application renders in any theme; switching is non-destructive by construction. +- **Outputs** are stateless projections through `IOutputAdapter`. New output type ≈ one adapter + (sometimes) one theme. This is what makes Horizon-3 features (portfolio, LinkedIn, site) *cheap* instead of new products. +- **The loop closes** at TailoredApplication ← outcome: tracked status + email signals accumulate on the application, feeding analytics ("last 15 rejections wanted Kubernetes") and, eventually, tailoring suggestions. + +Cover letters hang off TailoredApplication (they're per-job by nature), generated from profile + JD + (uniquely) the email thread context. + +--- + +## 5. Feature Prioritisation + +### MVP — "the loop works" (next major version) + +| Feature | User value | Business value | Complexity | MoSCoW | +|---|---|---|---|---| +| Fix OAuth CV lockout (`isLocal` bug) | Unblocks all Google/MS users | Removes a dead-end for every new OAuth signup | Trivial | **Must (ship now, pre-MVP)** | +| CareerProfile + CvVariant + CvVersion migration (stable IDs, normalized dates) | Invisible now; everything later | The architecture bet | Large | **Must** | +| Structured profile editor (+ extraction review queue) | Career data becomes maintainable; provenance visible | Core differentiator vs. document tools | Large | **Must** | +| Theme engine port — best 3–4 of 6 templates as manifests | Non-destructive theme switching; quality floor | Unblocks marketplace/premium later | Large | **Must** | +| Tailoring workspace (JD gaps ↔ variant ↔ live preview ↔ rescore, full page) | The killer screen; ends modal editing | The demo that sells the product | Large | **Must** | +| Import (upload exists + paste-text) AND from-scratch creation | No user dead-ends at entry | Doubles addressable entry funnel (P4) | Medium | **Must** | +| Diff + accept/reject on every AI mutation | Trust; no silent fabrication | Anti-hallucination brand plank | Small–Med | **Must** | +| Fair-exit set: free PDF+JSON export, no watermark | Table stakes vs. OSS; trust wedge | Positioning proof | Small | **Must** | +| Live paginated preview (<300ms on 3-page CV) | FlowCV-bar editor feel | Retention; perceived quality | Medium | **Should** | +| Template gallery w/ visual previews + ATS rating per theme | Informed choice; ATS trust | Marketing surface | Small | **Should** | +| Persist interview prep / fit outputs | Work stops evaporating | Cheap retention | Small | **Should** | +| UI vocabulary cleanup (kill "reprocess/runs") | Comprehensibility | Polish | Trivial | **Should** | +| DOCX export (scoped single-column) | Loudest market complaint | Checklist parity | Medium | **Could** | +| Cover letter v1 (profile + JD) | Completes the application | Expected feature | Medium | **Could** | +| LinkedIn import | Onboarding speed | Funnel | Medium | **Not now** (parse fragility; paste-text covers 80%) | + +### Version 2 — "the loop compounds" + +| Feature | User value | Business value | Complexity | MoSCoW | +|---|---|---|---|---| +| Cover letters w/ email-thread context | Letters that reference the actual conversation | Unique-data moat begins | Medium | **Must (V2)** | +| ATS plain-text "what the parser sees" view | Anxiety-killer; provable claim | Marketing weapon vs. Canva/Enhancv | Small | **Must (V2)** | +| Skills-gap analytics across tracked JDs | "Your market wants X; you lack it" | Nobody has it; pure aggregation | Small–Med | **Must (V2)** | +| Email-aware interview prep (persisted, thread-fed) | Preps you for *this* interview | The post-application moat | Medium | **Should** | +| Public profile / share link (theme over live profile) | Always-current link for recruiters | Viral surface; premium candidate | Medium | **Should** | +| Fact-constraint validator (novel-entity flagging) on generation | Career integrity guarantee | Trust plank #2 | Medium | **Should** | +| Variant refresh/diff when profile changed (staleness UX on `CanonicalProfileVersion`) | Safe propagation | Completes inheritance story | Medium | **Should** | +| Published profile schema + JSON Resume import/export | Interop; technical-user trust | OSS goodwill (P2) | Small | **Should** | +| User-defined theme tokens (fonts, spacing) | Personalization | Premium candidate | Medium | **Could** | +| Multi-language variant support (leans on existing rewrite lang) | P5 unlock | Market breadth | Medium | **Could** | + +### Future Vision — "the platform" + +| Feature | User value | Business value | Complexity | MoSCoW | +|---|---|---|---|---| +| Portfolio / personal-site generation (adapters + themes) | Whole web presence from one profile | Category-defining | Large | **Should (V3)** | +| LinkedIn content generation (summary, about, posts) | Consistency across surfaces | Engagement between searches | Small–Med | **Should (V3)** | +| Career timeline & skills matrix visualizations | Self-knowledge; review prep | Differentiator; needs normalized dates (done in MVP) | Medium | **Could** | +| Theme marketplace (manifest sandbox enables it) | Choice explosion w/o our design time | Revenue share model | Large | **Could** | +| MCP/agent endpoint over profile | User's own AI agents read/write career data | Agent-native future (RR precedent) | Medium | **Could** | +| Multiple CareerProfiles per user | Portfolio careers, consultants | Niche but architecture-ready | Small (post-M1) | **Not now** | +| Coaching / marketplace of humans | — | Off-mission; different business | — | **Not now** | +| Auto-apply / one-click mass application | — | Reputation poison (research: spam arms race) | — | **Never** | + +--- + +## 6. UX Strategy + +### First-time user: "I need a CV" → professional PDF, one session + +Target: **first rendered PDF < 10 minutes** (FlowCV bar), while quietly building a *profile*, not a document. + +``` +Sign up (OAuth, one click — bug fixed) + → "How do you want to start?" [Upload CV] [Paste text] [Start fresh] + → Import path: extraction runs live; user lands in REVIEW flow: + confidence-flagged cards ("We read this as… confirm/fix") — + provenance metadata finally earns its keep as visible trust + → Fresh path: guided mini-wizard (contact → most recent job w/ bullet + prompts → education → skills) — enough for a one-pager, expandable later + → Theme picker: 3–4 quality themes, visual gallery, ATS badge on each + → Live preview appears WITH the user's real content immediately + → Download PDF (free, no watermark) + nudge: "Track a job you're + applying to — we'll score this CV against it." +``` + +That final nudge is the workspace conversion moment: the PDF is the hook; the score-against-a-real-job is the "oh, this is different" beat. Teal's lesson (research §2.4): workspace-first onboarding with no artifact feels disorienting — so we produce the artifact first and reveal the system second. + +### Returning user + +- **Update profile:** Profile is a top-level destination (never again a settings card). Structured sections, inline edit, per-item provenance. Edits show a "3 variants use this item" ripple indicator. +- **New variant:** from profile or by cloning: pick items, name the lens ("Platform-eng focus"), pick theme. Variants list shows which jobs each has been used for. +- **Apply to a job:** job gets a Tailor action → tailoring workspace: JD + gap chips left, variant editor center, live preview right; one-click gap fixes (constrained rewrite + diff); export/attach; tracker updates. +- **Track progress:** existing dashboard/kanban continues; applications now show attached tailored version + match score at application time. + +### Navigation (recommended) + +``` +Dashboard · Jobs (kanban/table) · Inbox (correspondence + review queue) +Career [NEW: Profile · Variants · (later) Public profile] · Settings +``` + +CV ceases to exist as a noun in the nav. **Career** is the pillar; documents are things you export from it. Tailoring workspace is a *route* (`/jobs/:id/tailor`), reached from a job — full page, never a modal (teardown's hardest UX finding). + +### Dashboard design + +Keep tracker widgets (pipeline, reminders, analytics). Add two career widgets: **Profile health** (completeness, unreviewed low-confidence fields, variants stale vs. profile) and **Match radar** (saved jobs ranked by score — Marcus's triage). Dashboard answers "what should I do next in my search?", not "here are your documents." + +--- + +## 7. AI Strategy + +Doctrine, from research §5's useful-vs-gimmick line: **AI operates on the user's real data (profile, JD, email thread), shows its work (diff, source), and never invents facts.** Anything that generates from nothing, or hides its edit, is out. + +| Feature | User problem | AI solution | Complexity | Priority | +|---|---|---|---|---| +| Extraction + confidence review | Getting existing CV into structure is tedious | Parse → normalize → classify w/ per-field confidence; user confirms flagged fields (exists; needs the review UI) | UI only | **P0 (MVP)** | +| Gap-driven tailoring | Tailoring is an hour per job | Match gaps → one-click constrained rewrite per gap → live rescore (wires existing scorer + rewrite) | Medium (UI + orchestration) | **P0 (MVP) — the flagship** | +| Bullet improve w/ diff | Weak bullets; distrust of rewrites | Per-bullet improve, before/after diff, accept/reject; select-and-rephrase constraint | Small–Med | **P0 (MVP)** | +| Fact-constraint validator | Hallucinated seniority/numbers = career damage | Generation limited to profile facts; novel named-entity/number flagging | Medium | **P1 (V2)** | +| Cover letter from profile+JD+thread | Blank-page letters; generic AI letters | Grounded generation citing actual profile items and, in V2, the actual recruiter conversation | Medium | **P1 (V2)** | +| Skills-gap analytics | "Why am I being rejected?" | Aggregate JD demands across tracked jobs vs. profile skills | Small–Med | **P1 (V2)** | +| Email-aware interview prep | Prep is generic; the invite says what the panel covers | Prep generated from profile + JD + thread; persisted; STAR stories from user's own bullets | Medium | **P1 (V2) — the moat** | +| From-scratch content prompts | Blank-page paralysis (P4) | Section-aware questions ("what did you build?") → drafted bullets user edits | Small | **P2** | +| LinkedIn summary generation | Surface consistency | Another projection of the profile | Small | **P3** | +| Career advice chat | — | **Rejected as flagship**: unbounded scope, generic output, hallucination surface; revisit only as thin UI over the grounded features above | — | **Not now** | +| Resume "score out of 100" theater | — | **Rejected**: research shows these are engagement gimmicks; our match score is per-job and actionable instead | — | **Never** | + +Operational: provider router stays (Gemini/Groq cloud default in prod, Ollama local fallback); **visible quota** in UI from day one of any metering (Kickresume's secret-cap backlash is the cautionary tale); all CV-content calls remain through the delimiter-fenced, injection-hardened sidecar. + +--- + +## 8. Technical Direction + +Confirms the Architecture Proposal with the teardown's amendments. Decision summary: + +**Rebuild (replace):** +- Master-CV storage: `ApplicationUser.ProfileCvText/ProfileCvStructureJson` columns → `CareerProfile` table (+ `CareerProfileVersion`). Raw text demotes to derived artifact (search corpus, export). **Structure is canonical** — one truth, stated in code. +- `TailoredCvDraft` → `CvVariant` + `CvVersion` + `TailoredApplication`(job-linked variant use). Presentation leaves the content row: `ThemeRef` + per-variant render tokens. +- `CvTemplateRenderer`'s six C# string-builders → Scriban theme manifests (declarative: tokens, layout slots, section styles, explicit page-break rules, ATS rating). Port best 3–4; retire the rest. +- Tailored-CV UI out of `JobDetailsDialog` → dedicated routes. + +**Keep (assets, per teardown §3):** +- Extraction pipeline (artifacts, versioned runs, provenance metadata) — becomes the review-queue engine. +- `JobCvMatchService` + `SkillTagger` — becomes the tailoring loop's engine (retarget corpus to read from structure, not raw text). +- Playwright PDF exporter — stays as the PDF adapter behind `IOutputAdapter`; RR's Chromium-cost lesson says keep the boundary swap-clean, not swap now. +- FastAPI sidecar with fencing + provider router. +- All tracker/Gmail/analytics infrastructure — untouched; it's the moat. + +**Migrate gradually:** +- Schema via the raw-SQL reconciler in additive steps: create new tables → backfill from user columns + drafts (assign stable item IDs, normalize dates *during* backfill — one-time cost, teardown amendment) → dual-read period → cut over → drop columns last. +- Endpoints: new `/career/*` API grows beside `/profile-cv/*`; old routes proxy then deprecate. Frontend adopts per-screen (profile editor first, tailoring workspace second). +- Versioning: append-only version rows (profile + variant), content-hash deduped; diff computed, not stored. + +**Scalability posture:** current scale is single-server self-hosted; don't over-build. The two future-proofing investments that are cheap now and brutal later: stable item IDs and normalized dates (schema), and the adapter/theme boundaries (code). Everything else (queue-based rendering, multi-tenant sharding) is **Not now**. + +--- + +## 9. Monetisation Opportunities + +**Context decision first:** this is currently a self-hosted personal/OSS-style product; Stripe work (Wave 5) is deferred pending product decisions. Monetisation strategy is therefore designed now, implemented only if/when the product goes multi-user SaaS. Design it now anyway — pricing architecture shapes feature boundaries. + +**The model, if/when SaaS: FlowCV's seam, our loop.** Free = full quality, singular. Paid = multiplicity + intelligence depth. + +| | Free forever | Plus (~£4–6/mo — undercut Teal/Novoresume 3–5×) | +|---|---|---| +| Profile | Full, versioned, provenance | Same | +| Variants | 1 | Unlimited | +| Tailored applications | 3 active | Unlimited | +| Themes | All core themes | Same (+ future marketplace) | +| Export | PDF+JSON, unlimited, no watermark — **always** | + DOCX | +| AI | Metered monthly allowance, **visible quota** | High allowance, still visible | +| Tracker + Gmail | Full | Full | +| Public profile | — | Custom-slug live profile | + +**Monetise:** variant multiplicity (the proven seam — value scales with search intensity, exactly when willingness-to-pay peaks, without degrading free quality), AI volume (real marginal cost; honest metering), public profile (ongoing hosted value), later marketplace themes (rev-share). + +**Never monetise (the trust spec):** export of your own data, watermark removal (never watermark), the tracker (Teal proved free-tracker acquisition; ours feeds the loop), re-access to documents after cancellation (the anti-Novoresume guarantee — put it on the pricing page verbatim: *"Cancel and keep everything you made."*), secret AI caps (Kickresume's one-star engine). + +**Not now:** coaching/human services (different business), auto-apply (never), premium template *tiers* before a marketplace exists (6 themes is too thin to split). + +--- + +## 10. Final Recommendation + +### Executive summary + +**What it becomes:** the AI Career Workspace — one structured, versioned, provenance-aware career profile that generates every output of a job search (tailored CVs, cover letters, ATS views, public profile, interview prep) and learns from every outcome (tracking, email intelligence, match analytics). The CV builder is the visible front door; the loop is the product. + +**Why:** the research shows a market that is huge (55k reviews on a single incumbent), broken on trust (F BBB ratings, hostage patterns), and architecturally stuck — design-led tools can't add career context without rebuilding their revenue model, and the one workspace player (Teal) has weak rendering and stops at the application. The teardown shows our codebase already owns the hardest, least-replicable half: tracking, Gmail intelligence, match scoring, hardened AI, extraction provenance. Every audited defect shares one root cause (documents-as-primary), fixable with one architecture (profile-as-source-of-truth) that four competitor architectures each validate a quarter of. + +**How we win:** (1) close the loop nobody closes — profile → gap-driven tailoring → tracked outcome → interview prep from the actual recruiter thread; (2) make trust a spec, not a slogan — export-always-free, diff-on-every-AI-edit, visible quotas, ATS-view proof, published schema; (3) sequence ruthlessly — invisible data migration first, then the structured editor, then the tailoring workspace as the demo that defines the category. + +### Recommended next steps — first 10 tasks + +1. **Fix the OAuth `isLocal` CV lockout** (`ProfilePage.tsx:356`) — split identity-gates from feature-gates. Ship immediately; P0 bug independent of redesign. +2. **Freeze the CareerProfile schema v1** — profile/variant/version entities, stable item IDs, normalized dates (`YYYY-MM` + `isCurrent`), provenance carried over; publish as JSON Schema doc in `docs/`. (Design task; gates everything.) +3. **Write the migration + backfill** in the raw-SQL reconciler: new tables, backfill from `ProfileCvStructureJson` + `TailoredCvDraft` rows (IDs + date normalization during backfill), dual-read flag. Test against a prod DB copy. +4. **Retarget `JobCvMatchService` to read from structure** (not raw text) — kills the dual-truth divergence and validates the new model with an existing consumer. +5. **Author the Scriban theme-manifest schema** (tokens, layout, section styles, page-break rules, ATS rating) and **port ats-minimal** end-to-end through `IOutputAdapter` as the proving thread. Then port the next 2–3 best templates; retire the rest. +6. **Build the structured profile editor** (new `/career/profile` route): section forms, per-bullet rows w/ reorder, provenance-flagged review queue for low-confidence fields. +7. **Add from-scratch + paste-text entry paths** feeding the same editor/normalize pipeline; wire the onboarding flow of §6 (first PDF < 10 min). +8. **Ship diff/accept-reject on all AI mutations** (rewrite, improve, generation) — the trust primitive, small enough to land while 5–6 are in flight. +9. **Build the tailoring workspace route** (`/jobs/:id/tailor`): gap chips ↔ variant editor ↔ live themed preview ↔ rescore; retire the modal editor. +10. **Ship the fair-exit set + template gallery:** free PDF+JSON export everywhere, no-watermark guarantee stated in UI, visual theme gallery with ATS badges — the positioning made tangible in the product. + +Tasks 1–4 are backend-quiet and parallelizable with 5; 6–9 are the visible product; 10 is polish that carries the strategy. This sequence matches the Architecture Proposal's phases with the teardown's amendments applied, and lands the MVP of §5 in full. diff --git a/docs/cv-builder-competitor-deep-research.md b/docs/cv-builder-competitor-deep-research.md new file mode 100644 index 0000000..61ffdbd --- /dev/null +++ b/docs/cv-builder-competitor-deep-research.md @@ -0,0 +1,327 @@ +# CV Builder Competitor Deep Research + +**Date:** 2026-07-12 +**Purpose:** Product strategy input for the Jobbjakt CV Builder redesign and the long-term Career Workspace vision. Companion to the CV Builder Architecture Proposal (published artifact) — this document grounds those decisions in what the market actually does, where it fails, and where we can win. +**Audience:** Product/engineering discovery and planning (written to be consumable by AI planning agents and humans alike). + +--- + +## 1. Executive Summary + +The resume-builder market splits into four clusters: + +| Cluster | Examples | Core bet | Core weakness | +|---|---|---|---| +| **Design-led builders** | Novoresume, Enhancv, Kickresume, Resume.io | Beautiful templates sell | Subscription-trap billing, resume-hostage lock-in, ATS-hostile layouts | +| **Free/OSS builders** | Reactive Resume, FlowCV | Trust + generosity win users | No career context; document-centric, not career-centric | +| **Workspace tools** | Teal | The job *search* is the product; resume is one artifact | Stops at the application; no interview/offer support; weak template design | +| **Generic design tools** | Canva | Design freedom | 72% of tested templates fail ATS parsing; wrong tool for the job | + +**Five load-bearing findings:** + +1. **Nobody owns the "career profile as source of truth" position.** Teal comes closest (structured fields → adapt per job) but its profile is shallow and its outputs stop at resume + cover letter. Every competitor is document-first: the resume file is the primary object, and career data is trapped inside it. Our planned architecture (CareerProfile → many outputs) is genuinely differentiated, and a crop of small 2025–26 entrants (Joberney's "one career profile," ResumeTrakr's "one source of truth") confirms the market has noticed the gap — but none has our integrated job-tracking + email-intelligence + match-scoring base. + +2. **Billing malpractice is the #1 trust destroyer in this market.** Resume.io's $2.95-trial→$29.95/4-weeks auto-convert has produced an **F BBB rating** despite a 4.3 Trustpilot score; Novoresume blocks re-download of *already-paid-for* resumes when the subscription lapses; Kickresume cuts off "unlimited" AI mid-billing-cycle with undisclosed caps. The single cheapest differentiation available to us: **never hold the user's data hostage.** Free export, always, in every format we support. + +3. **ATS anxiety is the dominant purchase driver.** Every top-ranked review criterion in 2026 is ATS pass rate. Canva's failure mode (floating text boxes → no guaranteed reading order) and Enhancv's (pretty multi-column layouts that parsers mangle) prove design and parseability pull in opposite directions unless the architecture enforces both. Because we render from structured data (not a canvas), **every theme we ship can be ATS-safe by construction** — a claim Canva/Enhancv architecturally cannot make. + +4. **The winning editor model is structured-form + live preview** (FlowCV, Teal, Reactive Resume), not canvas editing (Canva) and not rigid wizard flows (Resume.io). Users tolerate — and actually prefer — forms when the preview updates instantly and section reorder is drag-and-drop. This validates our proposed editor architecture and warns against investing in inline/canvas editing. + +5. **AI is table stakes but mostly shallow.** Every paid competitor has "AI bullet rewrite" and "cover letter generation." What reviewers consistently score as *genuinely useful* is narrower: job-description keyword gap analysis with actionable diffs (Teal, Rezi), and bullet-level improvement with before/after scoring (BeamJobs). What's scored as fluff: generic "improve my resume" rewrites and AI-generated content that needs heavy editing. We already have the hard part (job data + match scoring in the same system); competitors bolt keyword analysis onto a resume tool, we bolt a resume tool onto keyword analysis. + +**Strategic posture in one line:** Don't compete as a resume builder. Compete as the only tool where the resume is *generated from* the same career data that tracks your applications, scores your matches, and reads your recruiter email — and be radically fair on pricing where incumbents are predatory. + +--- + +## 2. Individual Product Teardowns + +### 2.1 Novoresume (design-led incumbent) + +**Positioning:** "The Best Online Resume Builder" — 18M+ users claimed, 4.3/5 aggregate rating, FAANG-name-dropping social proof, "94% of users get more interviews" (unverifiable marketing stat). + +**First-time experience:** Landing page pushes two CTAs: "Create Your Resume" (→ template gallery first) and "Get Your Resume Analysis" (ATS checker as lead magnet). Template-first onboarding: pick a template, then fill content. "No credit card required" prominent. The ATS-checker funnel is smart — it meets anxious users at their anxiety, then upsells. + +**CV creation workflow:** Template → guided form sections → live preview. Free tier is deliberately crippled: **1 document, 1-page max**, watermark on free output ($19.99/mo to remove per third-party reviews). Premium unlocks 10-page docs, 72 documents, AI assistant, custom layout, cover letters. + +**Editor:** Structured-form with a strong live preview; customization is curated (8 templates, 30 color themes, 3 fonts on premium) rather than free-form. Reviewers praise template quality as "genuinely better than most alternatives"; AI described as "a guided assistant, not an autonomous builder." + +**Template system:** Categories: Free / Modern / AI-Powered / Creative / Traditional / Simple / Executive. Curated design tokens (color themes, font sets) over free customization — content and styling separated enough to switch templates without re-entering data. This is the mainstream commercial pattern and matches our proposed theme model. + +**AI:** Content suggestions, AI assistant on premium, ATS resume checker. Marketed as "AI-powered" but reviews consistently frame it as assistive-only. + +**Pain points (from reviews):** +- **PDF-only export** — no DOCX; recurring dealbreaker complaint. +- **Resume hostage:** subscription lapses → can't re-download resumes you already paid to create. Re-download of paid resumes after minor edits triggered *new* payment demands. +- Support: slow, canned responses. +- ~$24/mo single-month price — steep for a DIY tool. + +**Business model:** Freemium with hard free-tier limits + watermark. Interesting wrinkle: homepage now says "Pay once, no recurring billing" for some plans — a direct response to the market's subscription-trap backlash. Also upsells "Novocareer" courses and an "AI Coach" — evidence incumbents are also creeping toward the career-platform position. + +**Lesson for us:** Template quality is their moat and it's real. But the monetization model actively destroys trust at the exact moment users are most stressed. Their pivot to "pay once" pricing confirms subscription fatigue is a competitive lever. + +--- + +### 2.2 Reactive Resume (OSS benchmark — deepest technical relevance) + +**Positioning:** "A one-of-a-kind resume builder that keeps your privacy in mind. Completely secure, customizable, portable, open-source and free forever." **39.5k GitHub stars, 4.5k forks, 5,622 commits.** MIT licensed. The trust-position king: no paywalls, no premium tiers, self-hostable, one-click permanent data deletion. + +**First-time experience:** Sign up (passkey/2FA supported) → dashboard → create resume → structured editor with real-time preview. No onboarding wizard, no AI hand-holding for beginners — assumes a technical-ish, self-directed user. Import from JSON Resume format supported. + +**Workflow:** Unlimited resumes, no limits anywhere. Create from scratch or import JSON Resume. Share via unique public link. Export PDF/JSON/DOCX. + +**Editor:** Structured forms + Tiptap rich-text editor for content blocks, drag-and-drop section ordering, custom sections "for any content type," real-time preview. Dark mode, multi-language. + +**Template system (most instructive for us):** +- ~12–15 named templates (Pokémon names: Azurill, Bronzor, … Onyx, Pikachu, Rhyhorn), each a distinct layout. +- A4 + Letter support; customizable colors, fonts, spacing. +- **"Structured Style Rules for section and text styling"** — styling as data, not code. Same direction as our Scriban theme-manifest proposal. +- **The whole document is a JSON Schema** (draft-07, published at `rxresu.me/schema.json`): all sections (basics, summary, experience, education, projects, skills, languages, interests, awards, certifications, publications, volunteer, references) + custom sections + **metadata block carrying template, layout, typography, colors, page settings, custom CSS.** Content and presentation live in one document but in cleanly separated subtrees. + +**Technical architecture (v5.x, 2026):** +- **Stack:** TanStack Start (React 19 + Vite), Node.js, TypeScript, PostgreSQL + Drizzle ORM, ORPC type-safe RPC, Better Auth, Tailwind + Base UI, Zustand + TanStack Query. Single Node process mounts auth, RPC, **MCP**, OpenAPI, uploads, schema JSON, SEO endpoints, and the built web app. Optional SeaweedFS for S3-compatible uploads. +- **Export pipeline — the headline lesson:** v5.1.0 moved PDF generation **entirely client-side via @react-pdf/renderer**, eliminating Browserless/Chromium as a server dependency. Explicit `@reactive-resume/pdf/browser` and `/server` adapters; PDF.js for preview/thumbnails. They ran the Chromium-print architecture (same family as our Playwright exporter) for years and *migrated away from it* for operational cost reasons. +- **AI:** BYO-key integration (OpenAI, Gemini, Claude) — AI as optional enhancement, never a dependency or upsell. +- Ships an **MCP endpoint** — resumes as agent-accessible data. Directionally aligned with our AI-workspace ambitions. + +**Pain points:** Template variety is thin vs. commercial rivals (12 vs 40–300+); design quality is clean-functional, not designer-grade; no job tracking, no tailoring, no career context at all; hosted-version reliability historically depends on one maintainer's donations. + +**Business model:** None (donations). Exists as proof of what users get for free — which means **anything we charge for must exceed the Reactive Resume baseline**: unlimited resumes, all templates, PDF/DOCX/JSON export, share links, BYO-AI. + +**Lessons for us:** +1. Their schema (content sections + metadata subtree) validates our content/presentation separation — but we go further by making the profile independent of any document. +2. Their Chromium→client-side PDF migration is a signal: keep our Playwright exporter (we already run it, and server-side gives typographic control), but keep the renderer behind an interface so a lighter backend can replace it without touching themes. Our proposed `IOutputAdapter` already does this. +3. Their "Structured Style Rules" is the same insight as our theme manifests: **themes as data**. +4. Free-forever + privacy is a durable trust position; we can't out-free them, but we can match the fairness (no hostage-taking) while offering what they structurally can't: career context. + +--- + +### 2.3 FlowCV (free-first commercial — best-in-class first-run UX) + +**Positioning:** "Your first resume is 100% free forever. Unlimited downloads. No hidden fees. Yes, really 🚀" — the "yes, really" acknowledges market-wide distrust. 5.3M users; 4.9/5 Trustpilot, 4.8 Google, 4.9 Product Hunt — **the highest satisfaction ratings in the category.** + +**First-time experience:** Four-step promise on the landing page: choose template → add experience (guided; or import existing resume) → customize layout/design → download unlimited PDFs. Fast time-to-first-resume, no credit card, no watermark, auto-save. GDPR/privacy trust markers. Free plan includes **three imports** of an existing resume. + +**Editor (their crown jewel per reviews):** Structured forms with an instant, lag-free live preview; drag-and-drop reordering of whole sections; full design control (spacing, colors, fonts) even on free. "Incredibly intuitive" is the recurring review phrase. This is the editor experience bar we should aim to meet. + +**Template system:** 50+ templates, ATS-oriented, plus "design your own template and save it" — user-defined templates as a first-class feature (a marketplace seed we also plan via theme manifests). + +**AI:** Present but lightly weighted — positioning is speed + control, not AI magic. + +**Pain points:** The **1-free-resume limit** is the single recurring complaint: active seekers need 3–5 tailored variants, which forces Basic ($36/yr). Paid plans auto-renew (soft friction, but 14-day money-back and student discounts blunt it). + +**Business model — the fairest in the category and the one to copy:** +- Free: 1 resume, ALL design features, unlimited watermark-free PDFs. +- Pro from **$5/mo**: unlimited resumes. +- The paywall sits exactly on **multiplicity (variants), not quality**. Free output is never sabotaged. + +**Lesson for us:** The paywall placement is the masterstroke: free users are walking advertisements (their resume looks great), and the upgrade trigger (need a second tailored variant) arrives precisely when the user is most engaged. Our CvVariant concept maps 1:1 onto this monetization seam if we ever charge. + +--- + +### 2.4 Teal (secondary — closest to our Career Workspace thesis) + +**Positioning:** "Land interviews 6x faster" — a job *search* platform where the resume builder is one module. Free tier is a real product: unlimited job tracking, Chrome extension, basic resume builder. + +**First-time experience (instructively different):** Sign-up required → you land in a **job-tracking dashboard, not a resume editor**. Onboarding asks you to import career history via three routes: resume file upload (parsed into structured fields), LinkedIn URL, or paste text. Reviewers note this "can feel disorienting — there's no 'build resume' moment; you're building a system." That's both their genius and their friction: high ceiling, slower first-win. + +**Workflow:** Career data lives in structured fields (experience, skills, summaries) → resumes are assembled *from* it → per-job tailoring driven by a job-description match score that flags keyword gaps in real time and suggests concrete improvements. Chrome extension (4.9/5, ~200k users, Chrome Store "Favorites of 2023") bookmarks jobs from 40+ boards with salary and keyword breakdowns, saves LinkedIn contacts. + +**Editor:** Structured fields + "design mode" for template/look-and-feel. Reviewers rate the tracker and match scoring far above the resume *design* output — templates "feel pricey and sometimes don't work well with common systems like Workday"; formatting inconsistencies reported. + +**AI:** The match-score → keyword-gap → suggested-fix loop is the most consistently praised AI feature in the entire market. AI cover letters on paid. + +**Pain points:** Premium pricing anxiety (~$9/wk, $29/mo, ~$179/yr — sources vary, weekly billing reads as churn-farming); template design quality below the design-led cluster; and the most strategically interesting gap — **"Teal stops at the application stage."** No interview prep, no STAR coaching, nothing after the tracker says "Interview." + +**Business model:** Generous free core (the tracker) drives acquisition; paid unlocks unlimited AI + advanced analysis. 4.3 Trustpilot, 4.1/5 editorial consensus. + +**Lessons for us:** (1) Teal validates the entire career-workspace thesis — structured profile → tailored outputs → tracking loop. (2) Their two weaknesses are exactly our strengths-in-waiting: template/rendering quality (our themed Playwright pipeline) and post-application support (our email intelligence already sees interview invitations arrive). (3) Their onboarding teaches the trade-off: workspace-first onboarding needs a fast first win bolted on, or users bounce before the system pays off. + +--- + +### 2.5 Enhancv (secondary — design-differentiation ceiling) + +**Positioning:** Visually distinctive, personality-forward resumes ("modern, human" templates). 4.6 Trustpilot (892 reviews). + +**Workflow & editor:** Drag-and-drop section rearrangement, custom content blocks, AI content suggestions inline. Templates have fixed structure — "not a blank canvas," which frustrates users paying for uniqueness but protects ATS-sensible formatting. Content analyzer gives structure/wording feedback. + +**AI:** Paste a JD → keyword suggestions, bullet rewrites, tailored summaries, cover letters, multilingual. Reviewers' caveat: it *suggests*, you still do the tailoring manually. + +**Pain points:** No DOCX export; steep pricing (Pro Weekly **$24.99 with auto-renewing 7-day trial** — trap-adjacent; ~$20/mo monthly); the distinctive multi-column/graphic templates are exactly the ones **ATS parsers struggle with**; editor lags on long resumes. + +**Lesson for us:** Enhancv is the cautionary tale on the design/ATS tension: their differentiation (visual flair) directly fights the market's #1 anxiety (parseability). Any theme system we ship should carry an explicit per-theme ATS-safety rating so users make that trade-off knowingly. + +--- + +### 2.6 Canva Resume Builder (secondary — the anti-pattern) + +**Positioning:** 300+ free designer templates inside a general design tool. + +**Why it matters as a negative benchmark:** Independent testing found **72% of 50 popular Canva resume templates failed basic ATS parsing.** Root cause is architectural: Canva positions **floating text boxes on a canvas** — there is no semantic structure, so no guaranteed machine reading order (skills read before name, sidebar merged into headings). Multi-column layouts, icons-as-labels, text-in-shapes compound it. Canva's own answer is a 9-template "WissCreative" ATS-safe subset — an admission the core product is wrong for this job. + +**Lesson for us:** This is the strongest possible argument for our structured-data → theme-render pipeline. Canvas freedom and ATS safety are architecturally incompatible. We should never build canvas editing; and "ATS-safe by construction — unlike Canva" is a marketable, *provable* claim (structured data → we can emit the parse order deterministically, and even offer a "what the ATS sees" plain-text preview). + +--- + +### 2.7 Resume.io (secondary — dark-pattern monetization at scale) + +**Positioning:** High-volume mainstream builder; ~30 templates in Professional/Modern/Simple/Creative/ATS collections; 4.3 Trustpilot from **55,000+ reviews**. + +**Product:** Genuinely competent — templates are well-designed for ATS (single-column, standard headers, clean formatting); wizard-style guided flow with prewritten phrase suggestions; fast time-to-first-resume. + +**The dark pattern:** "$2.95" download headline → auto-converts to **$29.95/4-weeks after 7 days** (~$389/yr). Complaint mass: unexpected charges, hard-to-complete cancellations, billing after cancellation, refused refunds. Parent company (Talent Worldwide) holds an **F BBB rating** for ignoring complaints. Product Hunt reviews are "almost entirely about billing." Also accused of presenting AI-generated "expert reviews" as human. + +**Lesson for us:** Proof that a good product with predatory billing still prints money at scale — but leaves a giant trust vacuum. Their 55k reviews show the market size; their F rating shows the opening. Any pricing we ever ship must be the exact inverse: visible price, easy cancel, export always free. + +--- + +### 2.8 Kickresume (secondary — AI-writing leader) + +**Positioning:** "AI resume builder" — GPT-4.1-backed writer, 40+ designer templates organized by industry, 4.6 Trustpilot. Best-rated AI *writing quality* in the category per editorial roundups. + +**Product:** Free tier: 4 templates, no AI. Premium $8/mo annual ($96/yr), $24/mo monthly. Students/teachers get 6 months free. AI resume writer, cover letter generator, ATS checker on paid. + +**Pain points:** The most consistent 1-star theme: **undisclosed AI usage caps on "unlimited" plans** — users blocked mid-billing-cycle ("I only made 16 resumes and they turned off my AI"). Support slow, refund guarantee restricted in ways not disclosed at purchase. AI output inconsistent for niche roles. + +**Lesson for us:** If we meter AI (we must — cloud LLM costs are real), **publish the limits.** "Unlimited*" with a secret cap is a review-score time bomb. Our Gemini/Groq router already gives us cost control; surface remaining quota in the UI instead of hiding it. + +--- + +## 3. Feature Comparison Matrix + +| Capability | Novoresume | Reactive Resume | FlowCV | Teal | Enhancv | Canva | Resume.io | Kickresume | **Jobbjakt (planned)** | +|---|---|---|---|---|---|---|---|---|---| +| Structured career profile (doc-independent) | ✗ | ✗ (per-doc JSON) | ✗ | ◐ (shallow) | ✗ | ✗ | ✗ | ✗ | **✓ core** | +| Multiple CV variants | Paid (72 docs) | ✓ unlimited | Paid | ✓ | Paid | ✓ | Paid | Paid | **✓ (CvVariant)** | +| Per-job tailoring w/ match score | ✗ | ✗ | ✗ | ✓ best-in-class | ◐ manual | ✗ | ✗ | ◐ checker | **✓ (already live)** | +| Job application tracking | ✗ | ✗ | ✗ | ✓ best-in-class | ✗ | ✗ | ✗ | ✗ | **✓ (already live)** | +| Email/recruiter intelligence | ✗ | ✗ | ✗ | ◐ (contacts ext.) | ✗ | ✗ | ✗ | ✗ | **✓ unique (Gmail sync)** | +| Import existing CV (parse to structure) | ◐ | ◐ (JSON only) | ✓ (3 free) | ✓ (file/LinkedIn/paste) | ✓ | ✗ | ✓ | ✓ | **✓ (extraction runs live)** | +| Template count / quality | 8–16 / high | ~15 / clean | 50+ / high | modest / mid | 20+ / distinctive | 300+ / ATS-broken | ~30 / high | 40+ / high | 6 / mid → theme engine | +| User-defined templates | ✗ | ◐ (style rules, CSS) | ✓ | ✗ | ✗ | ✓ (canvas) | ✗ | ✗ | **✓ planned (manifests)** | +| ATS-safe by construction | ◐ | ✓ | ✓ | ✓ | ✗ (flagship themes) | ✗✗ | ✓ | ◐ | **✓ provable** | +| DOCX export | ✗ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✓ | planned (scoped) | +| Public share link / web CV | ✗ | ✓ | ✓ | ✗ | ◐ | ✗ | ✗ | ◐ | planned (Career Workspace) | +| Cover letters | Paid | ✗ | ✓ | Paid | Paid | ✓ | Paid | Paid | **✓ planned (same profile)** | +| Interview prep | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ◐ | **planned — open market gap** | +| Self-host / data ownership | ✗ | ✓ MIT | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ (self-hosted by design) | +| Fair billing (no hostage/trap) | ✗ | ✓ (free) | ✓ | ◐ | ✗ | ✓ | ✗✗ | ✗ (AI caps) | **✓ non-negotiable** | + +◐ = partial. **Bold** = our differentiation. + +Read across the bottom three rows of the left cluster: no commercial competitor combines fair billing + data ownership + career context. Read the "already live" cells: our moat is that the hardest parts (tracking, match scoring, email intelligence, CV extraction) are built — competitors would have to build *toward* our position while we only have to build a better renderer. + +--- + +## 4. UX Patterns Worth Copying + +1. **FlowCV's 4-step first-run promise** (template → content → design → download) with time-to-first-PDF under ~10 minutes. Guided but skippable. Our onboarding should deliver a rendered PDF in the first session, even from a thin profile. +2. **Teal's three-way import** (upload file / LinkedIn URL / paste text) as the *first* onboarding question. We already have upload+extraction; add paste-text (cheap — same pipeline) and evaluate LinkedIn later. +3. **Match-score-driven tailoring loop** (Teal): JD on one side, resume on the other, live keyword-gap chips with one-click "address this." We have the scoring service; the missing piece is the side-by-side tailoring UI. +4. **Instant live preview with zero lag** (FlowCV, Reactive Resume). Debounced re-render of the themed preview is a hard UX requirement, not a nice-to-have. Client-side preview render (even if final PDF stays server-side) is worth the investment. +5. **Drag-and-drop section reorder** (everyone good has it). Already partially in our RenderOptions.SectionOrder — expose it as direct manipulation on the preview, not a settings list. +6. **ATS checker as lead magnet** (Novoresume): a free "paste your CV, see what the ATS sees" tool is both acquisition funnel and an honest product feature. We can render our parse of *their* uploaded CV — we already run extraction. +7. **Curated design tokens over free-form styling** (Novoresume's 30 color themes / font sets): protects output quality, keeps themes swappable. Matches our accent-palette approach. +8. **"What you'd normally need five apps for" messaging** (RoleReady, ResumeTrakr): the fragmentation pain is now recognized enough that small entrants lead with it. When we market the workspace, lead with the *one profile, many outputs* story, not feature lists. +9. **Reactive Resume's published JSON schema + export-everything.** Publishing our profile schema (and JSON Resume import/export compatibility) buys trust with technical users at near-zero cost and future-proofs migrations. +10. **Kickresume's student program** (6 months free): cheap goodwill in the highest-need, lowest-income segment; worth copying if we ever monetize. + +## 5. UX Patterns To Avoid + +1. **Subscription traps** — $2.95-that-becomes-$29.95 (Resume.io), auto-renewing weekly trials (Enhancv). Category-defining trust destroyers. +2. **Resume hostage-taking** (Novoresume): blocking download of already-created documents. Export is free, forever, full stop. +3. **Secret AI caps on "unlimited" plans** (Kickresume). Meter openly with a visible quota. +4. **Watermarks on free output** (Novoresume): turns your free users into anti-advertisements. +5. **Canvas/floating-text editing** (Canva): architecturally incompatible with ATS parseability. +6. **Design flair that breaks parsing without warning** (Enhancv): if a theme is ATS-risky, label it. +7. **Workspace-first onboarding with no quick win** (Teal's "disorienting" first-run): our onboarding must produce a tangible artifact in session one even though we're profile-first underneath. +8. **Wizard rigidity** (Resume.io): a linear wizard that's hard to exit frustrates returning users; guided flow must degrade gracefully into free editing. +9. **Editor lag on long documents** (Enhancv): performance is a UX feature; test with 3-page, 10-year CVs. +10. **PDF-only export** (Novoresume, Enhancv): DOCX absence is a top-3 complaint across the design-led cluster. Even a scoped, simplified DOCX beats none. + +--- + +## 6. Technical Architecture Insights + +**From Reactive Resume (primary technical reference):** +- **Document = content sections + metadata subtree** (template, layout, typography, colors, page, custom CSS) in one JSON Schema. Clean separation *within* a document — but content is still trapped per-document. Our CareerProfile → CvVariant split is one abstraction better: their model can't answer "update my job title everywhere." +- **Styling as data** ("Structured Style Rules") — converges with our Scriban theme-manifest plan. Themes as declarative artifacts enable user themes and a marketplace without code execution. Keep manifests declarative (tokens, layout slots, section styles) and let Scriban only fill content into layout — don't let themes run logic. +- **PDF pipeline migration** (Chromium/Browserless → client-side @react-pdf/renderer in v5.1): server-side browser rendering is operationally heavy at scale. We keep Playwright (already built, gives full CSS typography, and our scale is modest) but the `IOutputAdapter` boundary must stay clean enough that swapping the PDF backend never touches themes. Their history proves this boundary earns its keep. +- **Type-safe RPC + published schema + MCP endpoint**: resumes as machine-readable, agent-accessible data. Cheap for us to mirror later (we already expose OpenAPI): a `/career-profile` schema endpoint and eventually MCP makes the workspace AI-agent-native. + +**From JSON Resume (jsonresume.org):** a de-facto community interchange schema with an ecosystem of themes/tools. Supporting JSON Resume import/export makes us interoperable with the OSS world for roughly one mapping function per direction. + +**From Canva (negative):** semantic structure must be the source of truth; presentation must be a *projection*. Never store positioned boxes. + +**From Teal:** structured fields → per-job adaptation works, but their weak rendering shows the two halves (data model vs. render quality) are separate competencies — most competitors are good at exactly one. We have a real shot at both: structured data is our heritage (tracker, extraction), and the theme engine is a bounded, well-understood build. + +**Export formats observed:** PDF universal; DOCX in ~60% (and its absence loudly punished); JSON only in OSS; public web link in OSS + FlowCV. Confirms roadmap priority: PDF (done) → DOCX (scoped) → public link (Career Workspace phase) → JSON (cheap, do early for trust). + +--- + +## 7. Market Gaps + +1. **Career profile as single source of truth, with outputs as projections.** Everyone stores documents; nobody (at real quality) stores a career. Confirmed by the wave of tiny 2025–26 entrants marketing exactly this ("one career profile powering every tool" — Joberney; "one source of truth" — ResumeTrakr). The position is being discovered but is not yet occupied by anyone with distribution. +2. **Post-application support.** Teal — the best workspace — "stops at the application button." Interview prep from *your actual profile + the actual JD + the actual recruiter email thread* is a genuinely empty space. Our Gmail intelligence sees the interview invitation arrive; no competitor even has that signal. +3. **Trustworthy billing as a product feature.** The gap between Resume.io's product quality and its F BBB rating is the market's largest arbitrage. "We never hold your CV hostage" is a positioning statement no design-led incumbent can copy without abandoning their revenue model. +4. **ATS transparency.** Everyone claims "ATS-friendly"; nobody *shows* it. A "view as ATS" plain-text projection with per-theme safety ratings is cheap for us (structured data) and impossible for canvas tools. +5. **Email-aware career automation.** Auto-updating application status from recruiter email, linking threads to applications, suggesting follow-ups — we already do this; no resume-builder competitor does. It's our most defensible moat because it requires infrastructure none of them have. +6. **Fair variant pricing.** FlowCV monetizes variants at $5/mo and is beloved; design-led rivals charge $20–30/mo and are resented. If we ever monetize, the variant/tailoring axis at FlowCV-like pricing is the proven seam. + +**Where competitors are strong (respect these):** template design quality (Novoresume/Kickresume/Resume.io — years of design investment); onboarding speed (FlowCV); tracker + extension distribution (Teal); free-forever trust + OSS community (Reactive Resume). We should not expect to beat template quality on day one — 6 excellent themes beat 40 mediocre ones. + +--- + +## 8. Recommended Product Strategy + +1. **Position as the Career Workspace, not a resume builder.** The resume builder is the *demo* of the workspace: one profile → tailored CVs, cover letters, interview prep, and eventually public profile/portfolio. Sequence per the existing 9-phase roadmap: profile/variant data model first (invisible), theme engine second, then the tailoring loop UI, then post-application features. +2. **Editor model: structured forms + instant themed preview + drag-and-drop section order.** No canvas. No inline-editing-on-the-document as primary model (nice later as sugar over the same fields). This is both the market-validated pattern and the one our data model wants. +3. **Make ATS safety a visible, provable feature:** per-theme ATS rating + "what the ATS sees" plain-text view + (later) free public ATS-check tool as acquisition funnel. +4. **AI: depth over breadth.** Ship the tailoring loop (JD ↔ CV keyword gaps with one-click fixes — we have the scorer) and bullet-level improve-with-diff (we have rewrite-section). Skip: generic "AI writes your whole resume," AI "review scores" theater. Metered AI with a *visible* quota. +5. **Trust as strategy:** free export always (PDF/JSON at minimum), no watermarks, published profile schema, JSON Resume interop, self-hosted data ownership story. Every one of these is cheap for us and structurally expensive for incumbents. +6. **Theme system:** curated first-party themes (quality bar: Novoresume) on declarative manifests; user-customizable tokens (accent, fonts, spacing, section order); user-defined themes later — the manifest architecture is the marketplace seed, but the marketplace itself is deferred. +7. **DOCX: scoped, not parity** (as proposed): a clean single-column DOCX rendition of any variant answers the actual complaint ("an employer asked for Word") without chasing pixel parity across six themes. + +## 9. MVP Recommendation + +Aligned with the architecture proposal's phases; competitor evidence adjusts emphasis: + +**MVP = the tailoring loop on a solid profile, with 3–4 excellent themes.** + +1. **CareerProfile + CvVariant data model** (invisible migration) — the foundation nothing else works without. +2. **Theme engine port** of existing 6 templates to manifests; cull to the best 3–4 if any are weak (quality over count — FlowCV won on 50 good, not 300 broken). +3. **Editor v1:** structured forms + live themed preview + drag-drop section order + accent/font tokens. Performance budget: preview update < 300ms on a 3-page CV. +4. **Import-first onboarding:** upload (exists) + paste-text, straight into a rendered first PDF in session one. +5. **Tailoring view:** JD keyword gaps ↔ variant side-by-side with one-click apply (wires existing match scorer + rewrite endpoints into one screen). This is the screen no design-led competitor has and Teal has without our rendering quality. +6. **Fair-exit guarantees from day one:** PDF + JSON export free, no watermark. + +Defer from MVP: DOCX (fast follow), public share links, cover-letter generation UI, user-defined themes, interview prep. Each is validated, none is needed to prove the core loop. + +**MVP success metric candidates:** time-to-first-rendered-PDF (< 10 min from signup); % of applications with a tailored variant attached; match-score delta before/after tailoring session. + +## 10. Long-Term Career Workspace Vision + +The end state (per the user's stated vision, now market-validated): **one structured career profile powering every artifact of a career.** + +- **Phase horizon 1 — the loop closes:** profile → variants → tailored CV per application → application tracked → recruiter email auto-linked → status auto-suggested. (Mostly exists; CV side lands with MVP.) +- **Horizon 2 — outputs multiply:** cover letters from the same profile + JD + thread context; scoped DOCX; public profile page (Reactive-Resume-style share link, but backed by the live profile, not a document snapshot); JSON Resume interop. +- **Horizon 3 — the gap nobody fills:** interview prep generated from profile + JD + the actual email thread ("they said the panel covers system design — here are your relevant stories, in STAR form, from your own experience entries"); skills matrix and gap analysis across all tracked JDs ("last 20 rejections wanted Kubernetes; you don't have it"); LinkedIn summary generation; portfolio/personal-site generation as another theme over the same profile. +- **Horizon 4 — agent-native:** published schema + MCP endpoint so the user's own AI agents read/write the profile (Reactive Resume already ships MCP — the pattern is proven). + +The strategic through-line: every horizon reuses the same profile and the same theme/output architecture. Competitors adding "career platform" features (Novoresume's courses, Teal's extension) are bolting products together; our architecture makes each new output roughly *one adapter + one theme*, which is the compounding advantage the data model buys. + +--- + +## Appendix: Sources + +**Novoresume:** [soundcv.com review](https://www.soundcv.com/blog/novoresume-review-2026) · [resumejudge 14-day test](https://resumejudge.com/blog/novoresume-review/) · [resumegenius review](https://resumegenius.com/reviews/novoresume-reviews) · [stylingcv review](https://stylingcv.com/blog/novoresume-review-2026-features-pricing-pros-cons-worth-it/) · [novoresume.com](https://novoresume.com/) · [Trustpilot](https://www.trustpilot.com/review/novoresume.com) +**Reactive Resume:** [GitHub repo](https://github.com/amruthpillai/reactive-resume) · [docs.rxresu.me](https://docs.rxresu.me/) · [JSON schema guide](https://docs.rxresu.me/guides/json-resume-schema) · [v5.1.5 release notes](https://github.com/amruthpillai/reactive-resume/releases/tag/v5.1.5) · [rxresu.me](https://rxresu.me/) · [JSON Resume](https://jsonresume.org/) +**FlowCV:** [flowcv.com](https://flowcv.com/) · [resumehog review](https://resumehog.com/blog/posts/flowcv-review-2026-is-it-still-the-best-free-resume-builder.html) · [resumearena](https://resumearena.com/tool/flowcv/) · [resufit review](https://resufit.com/blog/flowcv-review-free-resume-builder-worth-trying/) · [jobsolv](https://jobsolv.com/directory/flowcv) +**Teal:** [tealhq.com](https://www.tealhq.com/) · [pricing](https://www.tealhq.com/pricing) · [import help doc](https://help.tealhq.com/en/articles/9457699-import-existing-resume-or-linkedin-profile) · [resume builder help](https://help.tealhq.com/en/articles/9508933-getting-started-resume-builder) · [loopcv review](https://blog.loopcv.pro/teal-hq-review/) · [resumehog verdict](https://resumehog.com/blog/posts/teal-hq-review-2026-is-this-job-search-tool-worth-it.html) · [remotejobassistant](https://www.remotejobassistant.com/blog/teal-resume-review) · [enhancv's Teal review](https://enhancv.com/blog/teal-review/) +**Enhancv:** [resumegenius review](https://resumegenius.com/reviews/enhancv-reviews) · [pitchmeai full review](https://pitchmeai.com/blog/enhancv-review-pros-cons) · [pricing analysis](https://pitchmeai.com/blog/enhancv-worth-it-review-features-pricing) · [G2](https://www.g2.com/products/enhancv/reviews) +**Canva:** [resufit ATS analysis](https://resufit.com/blog/canva-resume-builder-templates-ats-compatibility/) · [rezi.ai review](https://www.rezi.ai/posts/canva-ai-resume-builder-review) · [resumegencv ATS failures](https://resumegencv.com/blog/why-canva-resumes-fail-ats-scans) · [jobscoutly 2026 test](https://jobscoutly.com/canva-resume-builder) +**Resume.io:** [resufit review](https://resufit.com/blog/resumeio-review-pricing-templates-worth-it/) · [Product Hunt reviews](https://www.producthunt.com/products/resume-io/reviews) · [boxresume](https://boxresume.com/comparison/resume-io-review-the-good-the-bad/) · [Trustpilot](https://www.trustpilot.com/review/resume.io) · [pitchmeai](https://pitchmeai.com/blog/resume-io-review) +**Kickresume:** [remotejobassistant review](https://www.remotejobassistant.com/blog/kickresume-review) · [firebear review](https://firebearstudio.com/blog/kickresume-review.html) · [resumegenius](https://resumegenius.com/reviews/kickresume-review) · [resumeoptimizerpro](https://resumeoptimizerpro.com/blog/kickresume-review) +**AI features / market:** [jobscan best AI builders](https://www.jobscan.co/blog/best-ai-resume-builders/) · [medium 8-builder test](https://medium.com/@vemasanihareesh/i-tested-8-ai-resume-builders-in-2026-heres-what-actually-works-334f07619431) · [resumeoptimizerpro ranking](https://resumeoptimizerpro.com/blog/best-ai-powered-resume-builders-2026) +**Workspace entrants (gap validation):** [GigForge](https://gigforge.io/) · [ResumeTrakr](https://resumetrakr.com/) · [Joberney](https://joberney.com/career) · [RoleReady](https://www.roleready.me/) · [Seekario](https://seekario.ai/) + +*Method note: primary sites fetched directly (FlowCV, Novoresume, Reactive Resume repo + docs); Teal's site blocks fetching (403), covered via its help-center docs and multiple independent reviews. Review claims cross-checked across ≥2 independent sources where possible; single-source claims (e.g., Canva 72% ATS failure figure) attributed to their origin.* diff --git a/docs/cv-builder-product-teardown.md b/docs/cv-builder-product-teardown.md new file mode 100644 index 0000000..a8fe50d --- /dev/null +++ b/docs/cv-builder-product-teardown.md @@ -0,0 +1,321 @@ +# 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` 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 (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.