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

9.4 KiB
Raw Blame History

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), DbSets, 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.