Files
jobtrackingapp/docs/implementation-roadmap.md
T
cesnimda b176a44627 docs: reorganize tree, restore architecture + research from archive, add Phase 0 reports
Active docs/ was stub scaffolding while the real docs sat in docs/_archive/.
Restore and correct them, and record the Phase 0 work.

- docs/architecture/current.md: verified system map (from archived SYSTEM_OVERVIEW,
  9 corrections against code).
- docs/research/competitors.md: sourced competitor analysis (from archived
  PRODUCT_RESEARCH, feature matrix corrected).
- docs/decisions/ADR-002-job-application-model.md: the Job/JobApplication split.
- docs/application-discovery-report.md, docs/implementation-roadmap.md,
  docs/phase-0-foundation-report.md, docs/career-workspace-branch-assessment.md.
- Remove 10 zero-byte placeholder files that advertised content that never existed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:04:32 +02:00

228 lines
24 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 | **Surface prospect stages in the UI** — add `Saved`/`Interested`/`Preparing` to `src/pipeline.ts` (`PIPELINE_STATUSES`), the Kanban columns, the status filter and the status menus. | **P0** | **M** | Phase 0 | **The backend supports the workflow; the UI does not yet expose it.** Phase 0 deliberately changed no UI behaviour. Until this lands, users still cannot save an unapplied job. Note the Kanban already has 6 columns — 9 stages needs a layout decision. |
| 1.2 | **Point the add-job wizard at `Saved`/`Preparing`** | **P0** | **XS** | 1.1 | Completes the workflow end to end. The 6-step wizard already exists (`components/AddJobModal.tsx:368`); this is a default-value change once 1.1 makes the stage selectable. |
| 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 | **Fix the CORS wildcard landmine**`Cors:Origins="*"` triggers `SetIsOriginAllowed(_ => true)` **with** `AllowCredentials()` (`Program.cs:96-102`): any site could make authenticated requests with the user's session cookie. Not currently active (compose never sets `Cors__Origins`), but it is one config value away. Reject the `*`+credentials combination outright. | **P1** | **XS** | none | **Found during Phase 0.** Cheap fix, severe if ever triggered. Was out of Phase 0's 5-item scope. |
| 1.6 | **Resolve the dead `careerView` prop** — either wire it or remove the tab | **P1** | **XS** | none | Ships a visible "CV Builder" tab that does nothing (`views/ProfilePage.tsx:231`). Removing it until Phase 4 is honest; leaving it is not. **Both files are uncommitted WIP — confirm intent before touching.** |
| 1.7 | **Fix the onboarding CV check** — read the structured profile, not `profileCvText` | **P2** | **XS** | none | `components/OnboardingChecklist.tsx:31` tells users with a parsed profile to upload a CV again. |
| 1.8 | **Fix the 5 pre-existing frontend test failures** in `profile-page.test.tsx` and `settings-view.test.tsx` | **P1** | **S** | none | **Failing on `main` before Phase 0** (verified by stashing all changes and re-running). CI runs the whole suite, so `main` is red — every future change lands on a broken 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
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`** — Profile = identity + security; Career = career data. Stop rendering one 1368-line component behind a boolean. | **P1** | **M** | none | Fixes the guide's "everything should have one obvious place". Prerequisite for Phase 3 — the Career Workspace cannot be built inside `ProfilePage`. |
| 2.2 | **Decompose `ProfilePage.tsx` (1368 lines)** into profile / CV / security sections | **P1** | **M** | 2.1 | Worst file in the frontend. Blocks Phase 3/4 work. |
| 2.3 | **Real onboarding flow**`Signup → Verify → Profile → Import CV → Connect email → First job` replacing the 2-item checklist | **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 | **Dedicated `/register` screen** | **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 | **Introduce a server-cache layer** (React Query or equivalent) | **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 | **Decompose `JobDetailsDialog.tsx` (1400 lines)** | **P2** | **M** | 2.5 | A dialog carrying an entire workspace. |
| 2.7 | **Interview prep hub screen** | **P2** | **S** | none | `/jobapplications/{id}/interview-prep` already works and is invisible. Cheap win — a shipped feature nobody can reach. |
| 2.8 | **Retire `react-scripts` as test runner** (move to Jest+SWC or Vitest) | **P2** | **M** | none | Removes one of three frontend toolchains. Do not touch the router in the same change. |
| 2.9 | **Expand analytics** — funnel, response rate, time-in-stage | **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.
| # | Task | Priority | Difficulty | Dependencies | Expected value |
|---|---|---|---|---|---|
| 3.1 | **Model Experience/Education/Skills/Projects relationally**; keep the long tail (awards, publications, organisations, references, custom sections) as a JSON blob. **Decided 2026-07-17** — no longer an open question. | **P1** | **L** | 1.9 | Buys queryable skills (needed for real matching + keyword gaps), section-level history, and concurrent-edit safety. Hybrid avoids a full rewrite of `StructuredCvProfile`. **Check task 1.9 first** — orphan `CareerProfiles`/`CvVersions` tables suggest this was attempted before. |
| 3.2 | **Add the missing profile sections** — Awards, Publications, Organisations, References | **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 | **Real Career Workspace page** — replace the 36-line tab facade | **P1** | **M** | 2.1, 2.2 | Currently a wrapper around `ProfilePage`. |
| 3.4 | **Separate Career Profile from Master CV** | **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 | **CV variants** (Software Engineer CV / Management CV) | **P2** | **M** | 3.4 | In the glossary; no code. Distinct from per-application `TailoredCvDraft`, which works correctly and must not be disturbed. |
| 3.6 | **Retention policy for `CvExtractionRun`** | **P2** | **S** | none | Three copies of every CV (raw/normalized/structured), unbounded. |
| 3.7 | **Move avatars out of the DB column** | **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`).
**This is the largest item in the plan.** The existing inert tab makes it look nearly done; it is not started.
| # | Task | Priority | Difficulty | Dependencies | Expected value |
|---|---|---|---|---|---|
| 4.1 | **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 | **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 | **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 | **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 | **Customise tab** — the ~12 controls from 4.1 | **P1** | **M** | 4.1, 4.2 | Currently **nothing** is customisable. |
| 4.6 | **Live client-side 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 | **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 | **Split `ProfileCvController` (2249 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.
| # | Task | Priority | Difficulty | Dependencies | Expected value |
|---|---|---|---|---|---|
| 5.1 | **Fix `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 | **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 | **Surface 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 | **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 | **Write 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. **Zero code exists.** 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 | **(a) Harden manual URL import** — `POST /jobimport/preview` is preview-only (a 27-line controller, no persistence, no import history, no dedup at import time). | **P2** | **M** | Phase 0 | Step (a) of the decided order and **already mostly built**. Cheapest discovery win; the wizard already calls it. |
| 6.2 | **(b) Browser extension / bookmarklet capture** — reuse `jobimport/preview`; land captures in `Saved`. | **P2** | **L** | 6.1, 1.1 | Step (b). Teal/Huntr/Simplify all ship one; Jobjakt is server-side parse only. **Kills the single biggest friction (manual entry)** and needs no search backend. Start with a bookmarklet — a full Chrome-store extension is high maintenance. |
| 6.3 | **(c) Official job-board APIs where available** | **P3** | **L** | 6.2, 6.6 | Step (c). Legal and low-maintenance where an API exists. |
| 6.4 | **Search UI + filters** (title, location, remote, industry) | **P3** | **M** | 6.3 | Only worth it once (c) supplies real data. |
| 6.5 | **One-click import into tracker** | **P3** | **S** | 6.3 | Lands in `Saved` — which exists as of Phase 0. |
| 6.6 | **Make market a data dimension, not an assumption** — Finn/NAV/Jobbnorge/LinkedIn are Norway-specific. **Decided: Norway first, but no hardcoding Norway.** | **P2** | **M** | none | `Job.CountryCode` and `Job.Source` already exist (added in Phase 0) — the entity is ready. The *plugins*, locale and currency handling still assume NO. Doing this before 6.3 stops each new market being a rewrite. |
| 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 | **Open registration + CAPTCHA** | **P2** | **M** | 2.4, 7.3 | Registration is 403 by default; **no CAPTCHA exists** (verified). Rate limiting alone is not enough for public signup. |
| 7.2 | **Plan / tier / entitlement model** — capability flags (`advancedAi`, `premiumThemes`, `automation`, `analytics`, `storageBytes`), not counters. | **P3** | **M** | none | No concept of a plan exists anywhere. Shape it around the decided split so the free tier stays genuinely useful. |
| 7.3 | **Usage quotas — AI + storage only** | **P3** | **M** | 5.2, 7.2 | **Do not open registration before this lands.** AI and storage are unmetered and unbounded; these are real cost, so they are legitimate limits. Job/CV counts are not. |
| 7.4 | **Storage limits + attachment caps** | **P3** | **S** | 7.2 | The "more storage" premium lever. |
| 7.5 | **Stripe billing** | **P3** | **L** | 7.2 | Still blocked on **Stripe keys** — the only remaining hard blocker. Tiers are now decided. |
| 7.6 | **Public CV** (`/cv/{guid}`) | **P3** | **M** | 3.4, 4.2 | Documented in `docs/00-ai-context.md`; **zero code** — no route, no `IsPublic`, no slug. Privacy-first random GUID, no usernames. |
| 7.7 | **Premium themes** | **P3** | **S** | 4.3, 7.2 | Trivial once themes are data. Impossible while they are C# methods. A decided premium lever. |
| 7.8 | **Dependency CVE scanning in CI** | **P2** | **S** | none | CI explicitly disables audit (`npm_config_audit: 'false'`). No vulnerability scan runs anywhere. |
| 7.9 | **Per-user AI provider cost controls** | **P3** | **S** | 5.2, 7.2 | With `AI_PROVIDER=gemini` the "advanced AI" tier spends real money per call. Metering (5.2) measures; this enforces. |
---
## 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.