Files
jobtrackingapp/docs/implementation-roadmap.md
T
cesnimda efc9d83c25
CI and Deploy / test (push) Successful in 2m37s
CI and Deploy / deploy (push) Successful in 54s
fix: finish prospect workflow safeguards
2026-07-30 23:34:42 +02:00

269 lines
29 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.
# Jobjakt — Implementation Roadmap
Date: 2026-07-17 · **Updated after Phase 0** (see `docs/phase-0-foundation-report.md`)
Companion to `docs/application-discovery-report.md`. Every task below traces to a verified finding there.
> **Phase 0 is complete.** Architecture docs restored, **AI sidecar locked down to backend-only and verified live**, pipeline expanded beyond `Applied` (10 stages, 3 board groups), `Job` entity introduced. Phase 1 below has been re-scoped against the *actual* architecture rather than the assumptions the original plan carried. Product decisions from 2026-07-17 are folded in throughout.
>
> **Product decisions now settled** (were open questions):
> 1. **Career profile storage** — relational for Experience/Education/Skills/Projects; JSON blob for the long tail. *Phase 3 is unblocked.*
> 2. **AI providers** — fix the docs to match the code; do **not** build the multi-provider abstraction. *Task 5.1 is now S, not L.*
> 3. **Job discovery order** — manual URL import → browser extension → official APIs. **Scraping is not a starting point.** *Phase 6 re-ordered.*
> 4. **Geography** — Norway first, but **no hardcoding Norway**; market is a data dimension. *New task 6.6, and `Job.CountryCode`/`Job.Source` already exist.*
> 5. **Free vs Premium** — Free: job tracking, basic career profile, basic CV. Premium: advanced AI, more themes, automation, analytics, more storage. **Not count-based.** *Phase 7 scoped.*
> 6. **Mockups** — `F:\Pictures\website\jobtracker\new` is the source of truth **only where a mockup exists**; do not invent pages from them.
**Legend**
Priority: `P0` blocker · `P1` high · `P2` medium · `P3` later
Difficulty: `XS` <½day · `S` ~1day · `M` 24days · `L` ~12wk · `XL` 2wk+
**Ordering principle:** the guide names "Excellent Job Tracking" and "Excellent Application Workflow" as goals #1 and #2. Phase 1 exists because the data model currently cannot represent goal #2 at all. Nothing in Phases 37 is worth building before that is true.
---
## Phase 0 — Foundation corrections ✅ DONE (2026-07-17)
Delivered: architecture docs restored and corrected; AI sidecar authenticated + de-published; `JobPipeline` gained `Saved`/`Interested`/`Preparing` under a new `Prospect` category; `DateApplied` nullable + `SavedAt` added; `Job` entity introduced additively; ADR-002 written. Full record in `docs/phase-0-foundation-report.md`.
Original tasks 1.1, 1.2, 1.3 (backend half), 1.5, 1.7, 1.8, 1.9, 1.10 are complete and removed from Phase 1 below.
---
## Phase 1 — Critical fixes (re-scoped after Phase 0)
Goal: finish surfacing the pre-application workflow in the UI, and close the security findings that need an operator.
| # | Task | Priority | Difficulty | Dependencies | Expected value |
|---|---|---|---|---|---|
| 1.1 | ✅ **DONE**`Saved`, `Interested`, and `Preparing` are exposed through the shared pipeline model, grouped Kanban, filters, and status menus. | **P0** | **M** | Phase 0 | Prospect workflow is visible end to end. |
| 1.2 | ✅ **DONE (2026-07-30)** — new jobs default to `Saved`; users can choose any later stage in the wizard. | **P0** | **XS** | 1.1 | New opportunities no longer imply an application was already submitted. |
| 1.3 | **Drag-and-drop on the Kanban** | **P2** | **M** | 1.1 | Board is drop-target-only today. Table stakes at every competitor. Deferred: not blocking. |
| 1.4 | **Rotate DataProtection keys** | **P1** | **XS** | none | **Needs an operator — cannot be done from here.** Keys remain recoverable from git history (`519c32e`, `955cae6`). Open since 2026-07-03. |
| 1.5 | ✅ **DONE (2026-07-30)** — startup rejects wildcard CORS origins when credentialed requests are enabled. | **P1** | **XS** | none | Unsafe configuration now fails closed. |
| 1.6 | ✅ **DONE** — the dead `careerView` prop/tab is gone; the implemented CV Builder has its own routed workspace. | **P1** | **XS** | none | No dead navigation remains. |
| 1.7 | ✅ **DONE** — onboarding reads structured career-profile collections with raw CV text only as fallback. | **P2** | **XS** | none | Parsed profiles are recognized correctly. |
| 1.8 | ✅ **DONE** — full frontend suite is green (142 tests on 2026-07-30). | **P1** | **S** | none | CI has a clean regression baseline. |
| 1.9 | **Decide what to do with the `feature/career-workspace` branch.***Investigated 2026-07-17 — see below.* The orphan tables are not an abandoned prototype: they are the local footprint of **10 unmerged commits** (local + `origin`, last touched 2026-07-12) containing working, tested code and 1,100 lines of research/strategy. All 7 tables are **empty** in dev, so there is no data to migrate. Branch is **29 commits behind main**. | **P0** | **M** | none | **Blocks Phases 3 and 4 from being planned honestly.** Re-deriving this work would waste weeks; ignoring it leaves a rebase debt that grows every day main moves. See `docs/career-workspace-branch-assessment.md`. |
| 1.10 | **Dev DB is missing 2 migrations** (`AddCorrespondenceEmailFields`, `AddShortSummary`) yet has `SyncModelSnapshot` — the reconciler patched the columns instead. | **P2** | **S** | none | Local-only inconsistency, but it means the dev DB is not a faithful rehearsal for a prod migration. Worth reconciling before the Phase 1 cutover. |
**Phase 1 exit:** a user can save a job they have not applied to, walk the wizard, prepare materials, and *then* mark it applied — visibly, in the UI. CI is green.
---
## Phase 2 — UX improvements ✅ DONE (2026-07-30)
Goal: the guide's "users should always understand where they are, what they can do, what happens next."
| # | Task | Priority | Difficulty | Dependencies | Expected value |
|---|---|---|---|---|---|
| 2.1 | ~~Split `/profile` and `/career`~~**DONE (2026-07-17, commit `66cc6a7`)**`/profile` = identity + security + preferences; `/career` = master career profile. The two saves are now scoped (partial-update-safe `PUT /auth/profile`) so neither wipes the other; the inert CV-Builder tab and dead `careerView` prop are gone. **The master profile is the source of truth; generated docs reference snapshots (not built yet).** | **P1** | **M** | none | Fixed the guide's "everything should have one obvious place". Functional separation via the existing `careerOnly` fork + scoped saves. |
| 2.2 | ✅ **DONE (2026-07-30)** — decomposed identity/security and Career Workspace into separate `ProfilePage` and `CareerProfilePage` components, with career sections extracted into focused presentational components. | **P1** | **M** | 2.1 | Phase 2 delivered the *functional* split via the `careerOnly` fork + scoped saves, but it is still one file behind a boolean. Extracting `ProfilePage` (identity/security) and a `CareerWorkspace` content component (master profile) is the remaining cleanup — mechanical, deferred so Phase 2 stayed low-risk. Blocks nothing; do before heavy Phase 3/4 edits. |
| 2.3 | ✅ **DONE (2026-07-30)**`Signup → Verify → Profile → Import CV → Connect email → First job`; the dashboard progress flow reuses existing signup/verification screens, reads structured career data, detects Gmail/Outlook/IMAP connections, and disappears when complete. | **P1** | **M** | 2.1 | Currently a dismissible checkbox pair. **Surface the Gmail connect step** — the strongest differentiator is buried in `/settings/connected-accounts`. |
| 2.4 | ✅ **DONE (2026-07-30)** — dedicated `/register` route reuses the hardened auth form and clearly disables submission when registration is unavailable. | **P2** | **S** | none | Endpoint exists but returns 403 by default and has no route; signup is hidden inside `LoginPage`. Required for Phase 7; harmless now. |
| 2.5 | ✅ **DONE** — incremental cache equivalents ship through `useViewResource` for shared server state and `useWorkspaceTabCache` for expensive workspace tabs; no dependency added. | **P2** | **M** | none | Root cause of the 6001400-line components and the hand-rolled `refreshToken` prop-threading. Pays for itself across Phases 36. Adopt incrementally, not as a rewrite. |
| 2.6 | ✅ **DONE (2026-07-30)** — extracted shared presentation cards and four intelligence tab panels into `JobDetailsPanels` and `JobInsightTabs`; the dialog remains the orchestration boundary. | **P2** | **M** | 2.5 | A dialog carrying an entire workspace. |
| 2.7 | ✅ **DONE** — user-owned interview preparation is exposed in `ApplicationWorkspacePage`, reachable from workflow signals and reminders, with grouped editable prep items. | **P2** | **S** | none | `/jobapplications/{id}/interview-prep` already works and is invisible. Cheap win — a shipped feature nobody can reach. |
| 2.8 | ✅ **DONE (2026-07-30)** — direct Jest + Babel configuration replaces `react-scripts`; packages were promoted from the existing lockfile without a new download. | **P2** | **M** | none | Removes one of three frontend toolchains. Do not touch the router in the same change. |
| 2.9 | ✅ **DONE** — dashboard analytics include funnel, response rate by source, top companies, and time-in-stage backed by `AnalyticsService`. | **P3** | **M** | 1.3 | The paid feature at Teal/Huntr. Needs correct prospect-vs-applied accounting from 1.3 first. |
---
## Phase 3 — Career Workspace
Goal: one professional source of truth that can actually feed outputs.
> **Foundation SHIPPED 2026-07-18** (commits `9c8644e`…`a1dd447`). The relational master career
> profile is built, tested (23 backend + frontend tests), migrated (verified on the live DB), wired
> to `/career`, with completeness overview + version-history restore. See
> `docs/architecture/career-profile-model.md` (Implementation status). **Phase 3 completed 2026-07-30:**
> the workspace, Master CV separation, variants, long-tail sections, extraction retention, and
> file-backed avatars are delivered.
| # | Task | Priority | Difficulty | Dependencies | Expected value |
|---|---|---|---|---|---|
| 3.1 | ✅ **DONE** — Experience/Education/Skills/Projects/Certifications/Languages are relational children of `CareerProfile`; long tail is JSON. Projection service keeps the blob as a derived read-model; lazy backfill from existing data. | **P1** | **L** | 1.9 | Delivered. Queryable structured career data is now the source of truth; the blob is derived. |
| 3.2 | ✅ **DONE (2026-07-30)** — added Awards, Publications, Organisations, and References across extraction, review/merge, JSON storage, editing, and rendering. | **P1** | **S** | 3.1 | The guide names them; `StructuredCvProfile` has no home for them beyond generic `OtherSections`. These are the blob half of 3.1. |
| 3.3 | ✅ **DONE (2026-07-30)** — dedicated Career Workspace page and focused section components. | **P1** | **M** | 2.1, 2.2 | Currently a wrapper around `ProfilePage`. |
| 3.4 | ✅ **DONE (2026-07-30)** — Career Profile is the source of truth; Master CV is a generated representation. | **P1** | **M** | 3.1 | The glossary is explicit — "The career profile is NOT a CV"; Master CV is a *generated representation*. Code has one blob. Getting this wrong makes Phase 4 impossible. |
| 3.5 | ✅ **DONE (2026-07-30)** — named CV variants with stable profile-item references. | **P2** | **M** | 3.4 | In the glossary; no code. Distinct from per-application `TailoredCvDraft`, which works correctly and must not be disturbed. |
| 3.6 | ✅ **DONE (2026-07-30)** — retain the newest 20 completed extraction runs per user; queued/running work is protected. | **P2** | **S** | none | Three copies of every CV (raw/normalized/structured), unbounded. |
| 3.7 | ✅ **DONE (2026-07-30)** — new avatars live in persistent file storage; the DB stores only an internal file reference, with legacy data-URL compatibility. | **P3** | **S** | none | Base64 blob on the `/auth/me` hot path. |
**Do not disturb:** `TailoredCvDraft` correctly implements "the master CV must never be modified automatically" — the single most important documented invariant, and it already holds.
---
## Phase 4 — CV Builder
Goal: `Content Tab → Customise Tab → Preview → Export` (guide `:312`).
**Completed 2026-07-30.** The builder now covers content, customisation, preview, export, variants, and public sharing.
> **Foundation SHIPPED 2026-07-18** (commits `a3e18e4` backend, `158dd02` frontend). Data-driven
> theme engine + variant model + 3-tab builder + live preview + public CV, all consuming the master
> profile (never duplicating it). See `docs/architecture/cv-builder.md` and
> `docs/architecture/cv-theme-engine.md`. Status: 4.1 ✅ `CvTheme` model. 4.2 ✅ one
> `ThemedCvRenderer` (the old `CvTemplateRenderer` stays only for the legacy tailored-draft flow).
> 4.3 ✅ 8 themes as data. 4.4 ✅ Content tab reorder/hide/rename + custom sections (up/down
> controls with keyboard support; **native drag-and-drop deferred** — no dnd dependency added yet).
> 4.5 ✅ Customise tab (theme, accent, fonts, density, page size, photo/icons/page-numbers). 4.6 ✅
> live preview (server render on a 350 ms debounce for export fidelity). 4.7 ✅ PDF export wired into
> the builder. Plus: autosave + version history/restore, AI-assist (suggestion-only), and public CV at
> `/cv/{slug}`.
>
> **Phase 4.5 — Builder Polish SHIPPED 2026-07-18** (commits `585047d`, `e3b255f`, `582c4e0`).
> Public-CV deep links fixed (optional catch-all `app/[[...slug]]`; direct nav/refresh/shared links
> work, no infra change). Native drag-and-drop reorder for sections + entries (keyboard arrows kept).
> Per-item editing via `GET /api/cv/outline`: hide, title/subtitle override, rich-text bullets
> (`**bold** *italic* __underline__ [link]`, escaped-then-whitelisted server-side). `ItemOrder`
> reorders entries without touching the master profile. Preview: zoom presets, measured page count +
> page navigation + page-break indicators, "updating" state. Unsaved/Saving/Saved indicator, loading
> skeletons, better empty states, ATS-friendly theme badge, a11y (ARIA labels, keyboard theme cards,
> focus rings), print-quality page-break CSS, AA contrast fix. Docs: `cv-builder.md`,
> `cv-theme-engine.md`. Server-rendered preview remains intentional for export fidelity; the controller was
> split into endpoint, pipeline, and parsing partials on 2026-07-30.
| # | Task | Priority | Difficulty | Dependencies | Expected value |
|---|---|---|---|---|---|
| 4.1 | ✅ **DONE** — Design the `CvTheme` model — layout, columns, header position, font family, base size + per-element deltas, spacing, margins, accent + application targets, icon style, photo settings | **P1** | **M** | 3.4 | **The keystone.** Everything else in Phase 4 depends on themes being *data*. Modelled on FlowCV's proven control set (report §7), trimmed to ~12 controls per the guide's "avoid excessive configuration". |
| 4.2 | ✅ **DONE** — Replace `CvTemplateRenderer` with one parameterized renderer | **P1** | **L** | 4.1 | Current code is a C# `switch` over 6 hardcoded HTML-string functions with `roundedPhoto`/`curvedHeader` booleans (`Services/CvTemplateRenderer.cs:22`). **A structural dead end — do not extend it.** |
| 4.3 | ✅ **DONE** — Seed 35 themes as theme documents — ATS Professional, Modern Professional, Creative | **P1** | **M** | 4.2 | The guide's explicit target. Cheap once 4.1/4.2 land; impossible before. |
| 4.4 | ✅ **DONE** — Content tab — section add/remove/reorder, entry editing, drag-and-drop **with keyboard support** | **P1** | **L** | 3.4 | FlowCV's keyboard drag affordances are worth matching (report §7). |
| 4.5 | ✅ **DONE** — Customise tab — the ~12 controls from 4.1 | **P1** | **M** | 4.1, 4.2 | Currently **nothing** is customisable. |
| 4.6 | ✅ **DONE** — Live server-rendered preview | **P1** | **L** | 4.2 | Today: server round-trip. FlowCV: continuous, side-by-side. Hardest piece — the renderer must run client-side or stream fast enough to feel live. |
| 4.7 | ✅ **DONE** — Wire export into the builder | **P2** | **S** | 4.6 | `POST /profile-cv/export-pdf` + Playwright already work. Make Download persistent, not a mode. |
| 4.8 | ✅ **DONE (2026-07-30)** — Split `ProfileCvController` (split from 2,379 lines) | **P2** | **M** | — | Do it while working here, not as a standalone refactor. |
| 4.9 | ~~Research Reactive Resume / Novoresume / ElegantCV~~**ALREADY DONE** — on `feature/career-workspace`: `docs/cv-builder-competitor-deep-research.md` (327 lines, 8 teardowns incl. Novoresume + Reactive Resume, feature matrix, business-model analysis). **Recover it; do not redo it.** | **P1** | **XS** | 1.9 | Its conclusions independently match this plan's §7/§10 reasoning — structured-form + live preview beats canvas; client-side preview is a hard requirement; themes must be declarative data. It also carries the pricing intelligence Phase 7 needs (Resume.io's F BBB rating for billing traps; Novoresume blocking re-download of already-paid CVs), which independently supports the "never gate on count" decision. |
---
## Phase 5 — AI improvements
Goal: polish. This is the healthiest area — grounding in the structured profile is already right, and "AI never has final control" already holds.
> **AI Career Assistant SHIPPED 2026-07-18** (commits `f299d7b` backend, `bb0c0fe` frontend). A unified
> per-application **AI Workspace** tab with five suggestion modules — job-analysis, career-match,
> cover-letter (6 tones), interview, application-review — each running through the existing
> `ISummarizerService`/ai-service provider abstraction and stored as **append-only history**
> (`AiInteraction`) with reuse / compare / copy / delete. Suggestion-only throughout; nothing
> auto-applies; `Markdown` renders React nodes (no HTML-injection surface). See
> `docs/architecture/ai-career-assistant.md`. **Phase 5 completed 2026-07-30:** provider-neutral usage
> metering and visible monthly totals now close the Phase 7 cost-control prerequisite. Per-request
> provider choice remains deliberately deferred by ADR-004.
| # | Task | Priority | Difficulty | Dependencies | Expected value |
|---|---|---|---|---|---|
| 5.1 | ✅ **DONE (2026-07-30)** — fixed `docs/00-ai-context.md` to match the code. **Decided 2026-07-17: do NOT build the abstraction.** | **P1** | **S** | none | The doc describes a provider interface over OpenAI/Gemini/Claude/Ollama with admin control and per-user choice. Reality: one `AI_PROVIDER` env var over Ollama/Gemini/Groq. Multi-provider cloud AI also undermines the privacy moat (see `docs/research/competitors.md` §4). Revisit only if a customer asks. `docs/architecture/current.md` §9 already records the truth. |
| 5.2 | ✅ **DONE (2026-07-30)** — AI usage metering | **P1** | **M** | 1.5 | No quota, no tracking, no ceiling. Hard blocker for Phase 7; a cost risk today with `AI_PROVIDER=gemini`. |
| 5.3 | ✅ **DONE (2026-07-30)** — surfaced optional CV generation inside the add-job wizard | **P2** | **S** | 1.4 | The target workflow says "Generate CV if needed" at step 3. `POST /generate-tailored-cv-draft` exists but only post-save. |
| 5.4 | ✅ **DONE** — keyword-gap analysis on match score | **P2** | **M** | none | `JobCvMatchService` + `/match-score` exist. Gap analysis is the specific thing people pay Jobscan $49.95/mo for. |
| 5.5 | ✅ **DONE (2026-07-30)** — wrote ADR-004 (AI provider system) | **P2** | **S** | 5.1 | 0-byte file naming a real decision. |
---
## Phase 6 — Job discovery
Goal: help users find opportunities. **URL preview and bookmarklet/PWA capture are complete.** Explicitly an enhancement — the guide: "Job discovery supports tracking. It does not replace job boards."
**Order decided 2026-07-17:** (a) manual URL import → (b) browser extension → (c) official APIs where available. **Scraping is explicitly not the starting point.** This reorders the original plan: the extension now comes *before* a search backend.
| # | Task | Priority | Difficulty | Dependencies | Expected value |
|---|---|---|---|---|---|
| 6.1 | **DONE (2026-07-30)** — manual URL import previews into the existing reviewed add-job flow, which persists through the normal create endpoint and warns on duplicate URLs. No second import-history store was added. | **P2** | **M** | Phase 0 | The existing flow delivers the discovery win without another persistence model. |
| 6.2 | **DONE (2026-07-30)** — bookmarklet/PWA share capture reuses `jobimport/preview` and opens the reviewed add-job flow in `Saved`. A store extension remains deliberately unnecessary. | **P2** | **L** | 6.1, 1.1 | Capture is available without Chrome-store maintenance. |
| 6.3 | **DONE (2026-07-30)** — NAV Job Vacancy Feed integration uses the official authenticated API and rotating public experiment token; `NavJobs:Token` supports a stable private token later. FINN requires a business agreement, while Indeed and LinkedIn do not expose open discovery feeds. | **P3** | **L** | 6.2, 6.6 | Legal official Norway-first discovery without scraping. |
| 6.4 | **DONE (2026-07-30)** — discovery UI searches recent active NAV events by title/company and municipality. | **P3** | **M** | 6.3 | Provides a usable official-feed search surface. |
| 6.5 | **DONE (2026-07-30)** — “Save to tracker” routes discoveries through the existing reviewed URL-import wizard and lands them in `Saved`. | **P3** | **S** | 6.3 | Reuses the established import and duplicate-warning flow. |
| 6.6 | **PARTIAL**`Job.CountryCode` and `Job.Source` exist, but the live `JobApplication` create flow does not yet dual-write `Job`, and import results do not carry market metadata. **Decided: Norway first, but no hardcoding Norway.** | **P2** | **M** | none | Complete this with the first official API so its provider market contract drives the write path. |
| 6.7 | **Explicitly out of scope: scraping.** | — | — | — | Recorded so it is not re-proposed. Also out: auto-apply bots (ToS/ethics/quality; contradicts "apply to more *suitable* jobs"). |
---
## Phase 7 — SaaS preparation
Goal: commercialise. Last, per the guide's "do not over-engineer before needed. Excellent UX is more important."
**Good news:** the hard part — multi-tenancy — is already done and tested. Everything below is additive.
**Tiers decided 2026-07-17.** Free: job tracking, basic career profile, basic CV. Premium: advanced AI, more themes, automation, analytics, more storage.
**The gate is capability, never count.** No CV-count or job-count caps — that is Huntr's 100-job limit, Teal's AI credits and FlowCV's 1-resume limit, i.e. the exact "free tier caps at the point of seriousness" frustration in `docs/research/competitors.md` §1.4. Jobjakt's moat is privacy + self-hosting + free local AI; a count-based paywall surrenders the moat while inheriting the complaint.
| # | Task | Priority | Difficulty | Dependencies | Expected value |
|---|---|---|---|---|---|
| 7.1 | **IMPLEMENTED; configuration required** — password signup and sign-in use Cloudflare Turnstile with mandatory server-side Siteverify validation when keys are configured. Registration remains closed until `AUTH_ALLOW_REGISTRATION=true` and production widget keys are supplied. | **P2** | **M** | 2.4, 7.3 | Safe code path is ready without silently opening public registration. |
| 7.2 | **DONE (2026-07-30)** — existing Identity roles are the plan model: `Premium` (and `Admin`) receives `advancedAi`, `premiumThemes`, `automation`, `analytics`, and 5 GB storage capabilities; free accounts receive core features and 250 MB. `/auth/me` exposes plan and entitlements. | **P3** | **M** | none | Reuses the existing role system and avoids a second billing-state table before Stripe exists. |
| 7.3 | **DONE (2026-07-30)** — existing AI interaction metering now enforces monthly generation limits: 25 for free accounts and 250 for Premium/Admin. Usage responses expose the active plan and limit. | **P3** | **M** | 5.2, 7.2 | Cost-bearing AI now has a clear monthly ceiling before registration opens. |
| 7.4 | **DONE (2026-07-30)** — attachment uploads enforce total per-user storage entitlements (250 MB free, 5 GB Premium/Admin) in addition to the existing 10 MB per-file cap. | **P3** | **S** | 7.2 | Storage limits match the exposed capability model. |
| 7.5 | **Stripe billing** | **P3** | **L** | 7.2 | Still blocked on **Stripe keys** — the only remaining hard blocker. Tiers are now decided. |
| 7.6 | ✅ **DONE (2026-07-30)** — public CV (`/cv/{guid}`), privacy-first random links, revoke/rotate sharing | **P3** | **M** | 3.4, 4.2 | Anonymous rendering is isolated behind an explicit public flag, served with `noindex`, and revoked links cannot be restored accidentally. |
| 7.7 | ✅ **DONE (2026-07-30)** — three free CV themes plus five Premium themes, enforced by account entitlement and clearly locked in the picker | **P3** | **S** | 4.3, 7.2 | Existing Premium-theme CVs remain editable and exportable after downgrade so user data is never held hostage. |
| 7.8 | **DONE (2026-07-30)** — CI runs NuGet transitive vulnerability reporting and a production-only npm audit. The npm audit reports the existing no-fix advisory baseline without blocking unrelated deploys. | **P2** | **S** | none | Vulnerable dependencies are now visible before deployment. |
| 7.9 | ✅ **DONE (2026-07-30)** — per-user monthly AI token ceilings (100k free, 1M Premium/Admin) enforced alongside generation limits and exposed in usage totals | **P3** | **S** | 5.2, 7.2 | Existing metering is the single accounting source; paid-provider spend now has both request and token ceilings. |
---
## Critical path
```
[Phase 0 DONE] pipeline stages · DateApplied nullable · Job entity · sidecar secured · docs restored
1.1 Prospect stages in UI ── 1.2 Wizard target (finishes the workflow users can see)
1.8 Fix red CI (independent — do first; everything lands on this baseline)
1.4 Rotate DP keys · 1.5 CORS fix (independent security)
1.9 Orphan tables ── informs 3.1
2.1 Split profile/career ── 2.2 Decompose ProfilePage ── 3.3 Career Workspace
└─ 3.4 Profile ≠ Master CV ──┬─ 4.1 CvTheme model ── 4.2 Renderer ──┬─ 4.3 Themes
│ ├─ 4.5 Customise tab
│ └─ 4.6 Live preview ── 4.7 Export
└─ 4.4 Content tab
4.9 Reactive Resume research ── must precede 4.1
6.6 Market as data ── 6.3 Official APIs
5.2 AI metering ── 7.3 Quotas ── 7.1 Open registration
```
**Two things now gate everything:** `3.4` (profile ≠ CV) and `4.1` (themes as data). The third — the workflow — was unblocked by Phase 0; only its UI surface (1.1) remains.
---
## Effort summary
| Phase | Rough size | Note |
|---|---|---|
| 0 — Foundation | **done** | Delivered 2026-07-17. |
| 1 — Critical fixes | ~35 days | Shrunk: Phase 0 absorbed most of it. Mostly UI surfacing + two operator tasks. |
| 2 — UX | ~23 wk | 2.5 (data layer) is the multiplier. |
| 3 — Career Workspace | ~23 wk | Grew slightly: relational profile (3.1) is now L, not a coin-flip. |
| 4 — CV Builder | **~46 wk** | The big one. Do not underestimate because a tab exists. |
| 5 — AI | ~1 wk | Shrunk: 5.1 is a docs fix, not an abstraction build. |
| 6 — Job discovery | ~23 wk | Shrunk: no scraping, and (a) is mostly built. The extension (6.2) carries most of the value. |
| 7 — SaaS | ~34 wk | Tiers decided; needs Stripe keys. |
---
## Product decisions — settled 2026-07-17
All six of the original blocking questions are answered. Recorded here so they are not re-litigated.
| # | Question | Decision | Effect |
|---|---|---|---|
| 1 | Career profile storage | **Relational** for Experience/Education/Skills/Projects; **JSON blob** for the long tail | Phase 3 unblocked; 3.1 is now a task, not a question |
| 2 | AI providers | **Fix the docs, do not build the abstraction** | 5.1 drops from L to S |
| 3 | Job discovery | **(a) manual URL import → (b) browser extension → (c) official APIs. No scraping.** | Phase 6 reordered; extension promoted ahead of a search backend |
| 4 | Geography | **Norway first, no hardcoding Norway** | New task 6.6; `Job.CountryCode`/`Source` already added in Phase 0 |
| 5 | Free vs Premium | Free: tracking, basic profile, basic CV. Premium: advanced AI, more themes, automation, analytics, more storage. **Capability-gated, never count-gated** | Phase 7 scoped; 7.2/7.3 reshaped |
| 6 | Mockups | Source of truth **only where a mockup exists**; do not invent pages from them | Phases 2 and 4 constrained |
## Remaining blockers
Only two things now block work, and both need you rather than a decision:
1. **Stripe keys** (gates 7.5). Tiers are decided; the integration needs credentials.
2. **DataProtection key rotation** (task 1.4). Needs an operator with production access.
## Open questions raised *by* Phase 0
Not product decisions — engineering findings that need a call before the phase they touch:
1. **Orphan DB tables** (task 1.9). The dev database carries `CareerProfiles`, `CareerProfileVersions`, `CvVariants`, `CvVersions`, `TailoredApplications`, `InterviewPrepNotes`, `AiWorkspaceNotes` — in no model, no migration. An abandoned Career Workspace attempt. **Was there a previous design worth recovering, or is this dead weight to drop?** Answer before Phase 3 re-treads the same ground.
2. **Kanban column count** (task 1.1). The pipeline now has 9 stages; the board is built for 6. Do the three Prospect stages get their own columns, collapse into one "Not applied" column, or sit behind a toggle? A UI decision, needed to finish Phase 1.