Files
jobtrackingapp/docs/implementation-roadmap.md
T
cesnimda ce76046a29 feat: complete release readiness work
- consolidate API ownership and remove dead vendor code

- add Stripe billing, learning paths, and public CV hardening

- add migration, recovery, security, audit, and browser gates
2026-07-31 16:54:16 +02:00

28 KiB
Raw Blame History

Jobjakt — Implementation Roadmap

Date: 2026-07-31 · Reconciled after the technical-debt pass 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. MockupsF:\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+

Current state: Phases 07 are repository-complete. Opening registration, activating Stripe, and deploying the release require operator configuration/access. See BLOCKERS.md.


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 CODE COMPLETE

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 DONESaved, 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 DONE — draggable cards move between grouped Kanban columns, persist the destination entry stage, and retain precise stage selection in the card menu. P2 M 1.1 Covered by grouped-board drag/drop regression tests.
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 (145 tests on 2026-07-31). P1 S none CI has a clean regression baseline.
1.9 DONE — the useful Career Workspace design/code was recovered and superseded by the completed Phase 3/4 implementation. The old branch is historical and is not a merge target. P0 M none No longer blocks later phases. See docs/career-workspace-branch-assessment.md for the original assessment.
1.10 DONE (2026-07-30) — restored EF identity metadata for the two orphan migrations as safe no-op history markers and brought the development database fully current. P2 S none The idempotent reconciler remains the schema owner for those legacy columns; EF migration history is now complete with no pending migrations.

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 9c8644ea1dd447). 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 DONE (2026-07-30) — application creation dual-writes the Job opportunity; importer results carry provider source and market metadata, with NAV/FINN/Jobbnorge declaring NO. P2 M none Norway-first provider data now drives the write path without making Norway a global default.
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 AND CONFIGURED; browser verification required — password signup and sign-in use Cloudflare Turnstile with mandatory server-side Siteverify validation. Production reports registration and Turnstile enabled; a real signup still needs interactive verification. P2 M 2.4, 7.3 The code and configuration are active without weakening abuse controls.
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 IMPLEMENTED; configuration required (2026-07-31) — hosted subscription Checkout, customer portal, signed subscription webhooks, persisted Stripe state, and idempotent Premium-role provisioning. P3 L 7.2 Activation needs the operator-created monthly price, portal, webhook registration, and three deployment secrets in BLOCKERS.md.
7.6 DONE (2026-07-31) — public CV (/cv/{guid}), privacy-first random links, revoke/rotate sharing, recruiter PDF download P3 M 3.4, 4.2 Anonymous rendering is isolated behind an explicit public flag, served with noindex; revoked links cannot be restored accidentally, and rate-limited PDF export uses the same visibility check.
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-31) — CI runs NuGet transitive vulnerability reporting and blocks high/critical npm findings across production and test/browser tooling. The documented React Router baseline is moderate. P2 S none Vulnerable dependencies are visible and high-severity regressions stop 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.

Maintenance milestone — Technical-debt reduction DONE (2026-07-31)

Removed the dead 232 MB vendor snapshot and transitional link-compilation project; consolidated API source ownership; added Problem Details, traceable structured logs, trusted proxy handling, durable CV queue restart recovery, CV artifact cleanup, PDF retention, and warning-free frontend tests; replaced placeholder docs and reconciled stale architecture records. Remaining conditional debt and its trigger conditions are tracked in docs/architecture/technical-debt.md.


Critical path

All implementation dependencies through Phase 6 are closed. The remaining release path is:

production key rotation → backup/restore rehearsal → deploy/smoke test → registration verification

Stripe code is complete; activation can follow once the product, monthly price, portal, webhook, and credentials exist.


Effort summary

Phase Rough size Note
0 — Foundation done Delivered 2026-07-17.
1 — Critical fixes code complete DataProtection rotation remains an operator task.
2 — UX done Delivered 2026-07-30.
3 — Career Workspace done Delivered 2026-07-30.
4 — CV Builder done Delivered 2026-07-30; public PDF completed 2026-07-31.
5 — AI done Metering and quotas included.
6 — Job discovery done Official NAV feed plus reviewed save flow.
7 — SaaS operationally blocked Remote CI, Stripe activation, and interactive production signup/OAuth verification remain.
Maintenance — Technical debt done Conditional future debt remains trigger-based, not current blocking work.

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

The authoritative list is BLOCKERS.md. Repository implementation is currently blocked only by external systems, credentials/configuration, or production access: remote CI, Stripe activation, production signup/OAuth verification, DataProtection key rotation, production verification/deployment, and the production-backed legacy job/application cutover. Job-specific learning paths are implemented, and portfolio content stays in public CVs.

Open questions raised by Phase 0

None remain. Career Workspace data was recovered into the relational model and the Kanban groups prospect stages explicitly.