102938c28b
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.
119 lines
9.4 KiB
Markdown
119 lines
9.4 KiB
Markdown
# 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.
|