Files
jobtrackingapp/docs/career-workspace-implementation-roadmap.md
T
cesnimda 102938c28b docs: add Career Workspace research, teardown, strategy, and roadmap
Four discovery documents backing the Career Workspace redesign:

- cv-builder-competitor-deep-research.md: teardown of Novoresume,
  Reactive Resume, FlowCV, Teal, Enhancv, Canva, Resume.io, Kickresume
  -- positioning, UX patterns, pricing/trust failures, technical
  architecture lessons (esp. Reactive Resume's content/theme
  separation and PDF pipeline history).
- cv-builder-product-teardown.md: critical as-is audit of this app's
  CV builder -- data model, editor UX, AI workflow, rendering
  pipeline, feature gaps -- including the OAuth CV lockout bug fixed
  in a prior commit.
- career-workspace-product-strategy.md: product vision, positioning,
  personas, core object model, feature roadmap (MVP/V2/future),
  AI/monetization strategy, and the first 10 engineering tasks.
- career-workspace-implementation-roadmap.md: the execution plan --
  product boundary (Career Workspace is a bounded domain supporting
  job tracking, not replacing it), phased sequencing (F0-F6), and the
  migration mechanics specific to this repo's raw-SQL schema
  reconciler.
2026-07-12 15:21:17 +02:00

119 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Career Workspace — Implementation Roadmap (ADR + sequencing)
**Date:** 2026-07-12
**Status:** Active. 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 this session
- **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.
### Phase F1 — Career Profile as first-class data (backwards-compatible seam)
1. Add `CareerProfile` + `CareerProfileVersion` tables to the reconciler (both dialects), `DbSet`s, query filters, indexes.
2. Introduce `ICareerProfileService` — the single accessor for the user's structured profile. Initially **dual-writes**: persists to the new `CareerProfiles` table **and** keeps `ApplicationUser.ProfileCvStructureJson` in sync (so nothing that still reads the column breaks).
3. Assign stable item IDs + normalize dates when materializing a profile into the new table (one-time backfill on first read/write per user).
4. Point `ProfileCvController` read/write paths at the service (behavior identical).
5. Test: round-trip a profile through the service; assert IDs stable across saves, dates normalized, legacy column still mirrored.
**Exit:** new table is authoritative; column is a mirror. Zero user-visible change.
### Phase F2 — Variants + versions (additive, opt-in)
1. `CvVariant` + `CvVersion` tables. A variant references a `CareerProfile` and holds selection/override JSON keyed by item ID.
2. `TailoredApplication` table: `(CvVariantId, JobApplicationId)` — the reference seam. Job references the tailored output; does not own the variant.
3. Backfill: each existing `TailoredCvDraft` → one `CvVariant` (job-linked) + its render options extracted toward a theme ref, wrapped in a `TailoredApplication`. Legacy `TailoredCvDrafts` retained (dual-read) until proven.
4. Endpoints under `/career/*` grow beside legacy `/profile-cv/*` and the job-scoped tailored routes.
**Exit:** variants exist; regeneration writes a new `CvVersion` instead of overwriting.
### Phase F3 — Rendering as data (theme catalog)
1. Extract the `CvTemplateRenderer` template catalog into a `CvTheme` descriptor set (id, label, layout shell, font stack, palette, heading style, default accent, ATS rating). Content pipeline (`RenderMainSections` + section renderers) already theme-agnostic — formalize the boundary: **renderer consumes `(document, theme)`; theme carries no CV logic.**
2. Layout **shells** stay a small fixed set (single-column, sidebar, rail, bordered); a theme selects a shell + tokens. Adding a theme that reuses a shell = a data entry, no code.
3. Golden test: render each existing template id before/after; assert byte-identical output (pure refactor).
4. **Deferred:** external template engine (Scriban) + user/marketplace themes — only when a marketplace is real (strategy §9). Do not add the dependency now.
**Exit:** adding a theme on an existing layout is data-only; PDF output unchanged.
### 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
- Diff / accept-reject on every AI mutation (rewrite, improve, generation). Trust primitive; also kills silent hallucination.
- Retarget `JobCvMatchService` to read from structured profile (not raw text) — removes the dual-truth divergence; validates F1's model with an existing consumer.
- Fact-constraint validator (novel named-entity/number flagging) on generation.
- Persist interview-prep / fit outputs (stop regenerating).
### 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.