Unblocks the documented core workflow and closes the AI-service exposure,
without changing existing behaviour.
Job/JobApplication split (additive; see ADR-002):
- New Job entity (the opportunity) with owner-scoped query filter; nullable
JobApplication.JobId FK. Nothing reads Job yet.
- Migration AddJobEntityAndProspectStages, hand-edited to drop reconciler-owned
tables the scaffolder re-emitted; verified against the real dev DB.
Pipeline: 10 internal stages across three concerns kept separate —
PipelineStage (workflow) / PipelineGroup (UI: NotApplied/Active/Closed) /
PipelineCategory (analytics). Adds Saved/Interested/Preparing/Withdrawn;
keeps Waiting and Ghosted. Kanban shows 3 grouped columns; cards keep a stage
chip and full transitions; drag applies only safe transitions (never infers
Ghosted/Withdrawn).
DateApplied nullable + SavedAt. Cleared when leaving Applied so analytics stay
accurate; the discarded date is preserved as an AppliedDateCleared JobEvent.
AI service lockdown: no host port; private ai_internal network (backend is the
only other member); X-Ai-Service-Token required on all non-/health endpoints;
AI_SERVICE_TOKEN mandatory via compose. Verified backend-only against the live
stack.
Also carries two pre-existing working-tree files (views/ProfilePage.tsx,
views/CareerWorkspacePage.tsx) so the tree is clean for the branch integration.
Tests: +40 backend (247 total), +5 sidecar (16), +15 frontend.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
No mockup exists for 404/500 pages, so these follow the visual
language already established elsewhere this session: floating-shadow
card, big bold status number (matching the dashboard stat tiles'
bold-number treatment) instead of a small overline.
JobTable's first-run empty state gets an icon chip matching the
landing page's feature-card icon treatment (rounded square, tinted
primary background) instead of plain text -- the filtered "no results"
one-liner stays as-is, that's a different, correctly minimal case.
Main table Paper wrapper gets the same floating-shadow treatment as
every other card this session.
ViewStateNotice (the shared loading/error component used across the
app) reviewed and left untouched -- it's an MUI Alert used as an inline
banner, which is the correct pattern; it was never a "fake card" to
begin with.
Implements the six "propose first" items from the product/UX review:
- "/" now redirects to /dashboard instead of the empty /jobs table --
a new user's first screen is now an overview with orientation, not
a data table with zero rows and four filter dropdowns.
- Jobs table gets a real first-time empty state (distinct from "no
results match your filters") pointing at Add Job and the bookmarklet,
instead of a bare "No jobs found."
- Match Score card and Candidate Fit tab now each get a one-line
caption explaining what they are and how they differ (deterministic
keyword coverage vs. AI opinion) -- they previously sat side by side
with no explanation of why there are two.
- Google sign-in hint now reflects self-serve signup when
Auth:AllowRegistration is on, instead of always implying you need an
existing linked account.
- Quick Search button now shows its keyboard shortcut (Ctrl+K / ⌘K)
inline instead of being undiscoverable.
Introduces pipeline.ts (mirrors backend JobPipeline) as the one frontend
source of truth for canonical stages, synonym normalization, tone, and
localized labels. Replaces the status list/logic previously duplicated
across KanbanBoard, JobTable, AddJobModal and EditJobDialog.
- KanbanBoard/AddJobModal/EditJobDialog render from PIPELINE_STATUSES
- JobTable uses shared statusTone + statusLabel (status chips now
localized; NB gets proper labels, English unchanged)
- Edit dialog status dropdown is now localized too
- 5 unit tests; full frontend suite green (19 suites / 41 tests)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds SalaryMin/SalaryMax/SalaryCurrency/SalaryPeriod alongside the
existing free-text Salary field (kept for back-compat and display).
- JobApplication model + idempotent column bridging for SQLite and MySQL
- Create/Update DTOs with NormalizeSalary (clamps negatives, swaps
inverted min/max, uppercases currency, whitelists period)
- JobApplicationDto exposes the fields; CSV export gains 4 columns
- UI: add/edit dialogs get min/max/currency/period inputs; job table
renders a formatted range via shared salary.ts formatter (falls back
to free-text when structured values are absent)
- EN/NB translations; backend + full frontend suites green
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>