Merge Request — Wave 0 quick wins + Tier-1/Tier-2 features #1

Merged
cesnimda merged 26 commits from chore/wave0-quick-wins into main 2026-07-03 11:14:15 +02:00
Owner

Branch: chore/wave0-quick-wins → main Scope: 24 commits · 62 files · +3,165 / −489 Status: all tests green (backend 135, frontend 23 suites / 54 tests), production build compiles.

Prepared for human review. Do not auto-merge. One operator action is required after merge (DataProtection key rotation — see Known limitations).

Summary
Delivers the first two roadmap tiers plus the engineering-health groundwork, developed as small conventional commits. Two design principles run through it:

Deterministic over "AI-guessy." Match scoring, status suggestions, and pipeline logic are pure/deterministic — instant, reproducible, and safe (the user confirms every state change). This directly answers the market's most common complaint (hallucinated/generic AI output).
One pathway, not two. The bookmarklet and the PWA share-target feed a single /?add= capture flow rather than parallel implementations.
What's included
Engineering health (Wave 0)

security: untracked committed DataProtection keys + daily exports; removed dead legacy controllers.
feat: automated daily SQLite backups (VACUUM INTO, retention, startup catch-up) — prod previously had no automated backup on Linux.
ci: run the entire frontend suite (the old whitelist was hiding 3 broken suites, now fixed).
feat: dev-only OpenAPI at /openapi/v1.json; feat: structured salary fields.
Tier-1 features

Match score (GET /jobapplications/{id}/match-score) — deterministic CV↔job keyword coverage (0–100) + matched/missing keywords + section coverage. Instant panel on the Candidate Fit tab.
Canonical pipeline — JobPipeline single source of truth; status normalized on write (custom values preserved); UI deduped across 5 files; GET .../pipeline.
Analytics v2 — time-in-stage medians (from StatusChanged history) + funnel driven by the pipeline (fixes a bug that omitted the Waiting stage).
Status suggestions — deterministic email→status classifier surfaced as a human-confirmed banner.
Tier-2 features

Bookmarklet quick-capture (Settings) reusing jobimport/preview.
Installable PWA with a mobile share-target into the same capture flow.
Quality

Phase-6 security review (docs/SECURITY_REPORT.md): tenant isolation on new endpoints verified + regression-tested; no injection/ReDoS; dev-only OpenAPI.
Bug fixes: SkillTagger C#/.NET regex (silently missed those skills everywhere), a React stale-closure, a duplicated DB query, and 3 pre-existing hidden test failures.
Test coverage added
New pure/unit-tested services: JobCvMatchService (7), JobPipeline (14), StageAnalytics (4), EmailStatusClassifier (7). New endpoint integration + authorization tests (match-score, status-suggestion). New frontend tests: match-score panel, status-suggestion banner, pipeline, quick-capture, capture-url resolution.

Docs
New: docs/SYSTEM_OVERVIEW.md, docs/PRODUCT_RESEARCH.md, docs/ROADMAP.md, docs/SECURITY_REPORT.md. README updated with the new endpoints, backup/pipeline config, and quick-capture/PWA notes.

Known limitations / follow-ups
ACTION REQUIRED (security): the removed DataProtection key XMLs remain in git history. Rotate them on the production host after merge (see SECURITY_REPORT.md §6).
Per-user custom pipeline stages were deliberately deferred (unproven demand; large surface).
No offline service worker by design — the app deploys frequently and an aggressive cache would risk serving stale builds. The PWA is installable and share-capable without it.
Not yet done (future branches): interview hub (M3), contacts CRM (M4), god-controller decomposition, performance pass, Vite migration.
Reviewer notes
Repo quirk: controllers/services compile via the JobTrackerBackend library, not the JobTrackerApi host project (see docs/SYSTEM_OVERVIEW.md §2).
All AI-adjacent features are deterministic and make no model calls.

Branch: chore/wave0-quick-wins → main Scope: 24 commits · 62 files · +3,165 / −489 Status: all tests green (backend 135, frontend 23 suites / 54 tests), production build compiles. Prepared for human review. Do not auto-merge. One operator action is required after merge (DataProtection key rotation — see Known limitations). Summary Delivers the first two roadmap tiers plus the engineering-health groundwork, developed as small conventional commits. Two design principles run through it: Deterministic over "AI-guessy." Match scoring, status suggestions, and pipeline logic are pure/deterministic — instant, reproducible, and safe (the user confirms every state change). This directly answers the market's most common complaint (hallucinated/generic AI output). One pathway, not two. The bookmarklet and the PWA share-target feed a single /?add= capture flow rather than parallel implementations. What's included Engineering health (Wave 0) security: untracked committed DataProtection keys + daily exports; removed dead legacy controllers. feat: automated daily SQLite backups (VACUUM INTO, retention, startup catch-up) — prod previously had no automated backup on Linux. ci: run the entire frontend suite (the old whitelist was hiding 3 broken suites, now fixed). feat: dev-only OpenAPI at /openapi/v1.json; feat: structured salary fields. Tier-1 features Match score (GET /jobapplications/{id}/match-score) — deterministic CV↔job keyword coverage (0–100) + matched/missing keywords + section coverage. Instant panel on the Candidate Fit tab. Canonical pipeline — JobPipeline single source of truth; status normalized on write (custom values preserved); UI deduped across 5 files; GET .../pipeline. Analytics v2 — time-in-stage medians (from StatusChanged history) + funnel driven by the pipeline (fixes a bug that omitted the Waiting stage). Status suggestions — deterministic email→status classifier surfaced as a human-confirmed banner. Tier-2 features Bookmarklet quick-capture (Settings) reusing jobimport/preview. Installable PWA with a mobile share-target into the same capture flow. Quality Phase-6 security review (docs/SECURITY_REPORT.md): tenant isolation on new endpoints verified + regression-tested; no injection/ReDoS; dev-only OpenAPI. Bug fixes: SkillTagger C#/.NET regex (silently missed those skills everywhere), a React stale-closure, a duplicated DB query, and 3 pre-existing hidden test failures. Test coverage added New pure/unit-tested services: JobCvMatchService (7), JobPipeline (14), StageAnalytics (4), EmailStatusClassifier (7). New endpoint integration + authorization tests (match-score, status-suggestion). New frontend tests: match-score panel, status-suggestion banner, pipeline, quick-capture, capture-url resolution. Docs New: docs/SYSTEM_OVERVIEW.md, docs/PRODUCT_RESEARCH.md, docs/ROADMAP.md, docs/SECURITY_REPORT.md. README updated with the new endpoints, backup/pipeline config, and quick-capture/PWA notes. Known limitations / follow-ups ACTION REQUIRED (security): the removed DataProtection key XMLs remain in git history. Rotate them on the production host after merge (see SECURITY_REPORT.md §6). Per-user custom pipeline stages were deliberately deferred (unproven demand; large surface). No offline service worker by design — the app deploys frequently and an aggressive cache would risk serving stale builds. The PWA is installable and share-capable without it. Not yet done (future branches): interview hub (M3), contacts CRM (M4), god-controller decomposition, performance pass, Vite migration. Reviewer notes Repo quirk: controllers/services compile via the JobTrackerBackend library, not the JobTrackerApi host project (see docs/SYSTEM_OVERVIEW.md §2). All AI-adjacent features are deterministic and make no model calls.
cesnimda added 25 commits 2026-07-03 09:40:40 +02:00
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- git rm --cached on committed DataProtection key XMLs (keys/, JobTrackerApi/keys/)
  and daily export JSON snapshots; extend .gitignore so runtime data
  (keys, exports, CV artifacts/exports/benchmarks) can never be committed again.
- Delete root Controller/ stubs: an early prototype compiled by no project
  (JobTrackerApi excludes them; JobTrackerBackend globs only JobTrackerApi/Controllers).
- NOTE: the removed key XMLs remain in git history; rotating DataProtection
  keys on the server is recommended.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 1-3 deliverables: full architecture/security/tech-debt map,
2026 market research with feature matrix, and tiered execution roadmap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keep the latest load callback in a ref so reload() always invokes the
current fetcher without changing its own identity on every render.
Reduces full-suite test failures from 5 to 3 (remaining are pre-existing).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New DatabaseBackupHostedService + SqliteDatabaseBackupRunner:
- daily VACUUM INTO snapshot to <Data:Root>/backups (safe with WAL)
- catch-up backup at startup when none exists from the last 24h
- retention pruning (Backups:RetainCount, default 14)
- warns and stays idle on MySQL/MariaDB where external backups apply

Production previously had no automated database backup on Linux
(the /api/backup endpoint is Windows-DPAPI-only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Raise testing-library asyncUtilTimeout to 4s and jest timeout to 30s:
  heavy MUI views exceeded the 1s default on slower machines
  (profile-page, daily-control-loop double-mount).
- end-to-end-trust-loop: mock the /tailored-cv-draft endpoint the
  redesigned Tailored CV tab now loads, and assert on the structured
  draft instead of the removed legacy tailoredCvText textarea.

Full suite now green locally: 18/18 suites, 39/39 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The whitelist silently skipped new suites; two regressions in
non-whitelisted suites reached main unnoticed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- AddOpenApi/MapOpenApi (anonymous, Development environment only).
- security: mark ProfileCvController.ProcessQueuedRunAsync [NonAction] -
  the controller-level [Route] exposed this background-service hook as a
  routable any-verb endpoint, which also broke OpenAPI generation.

96 endpoint paths documented.

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>
New JobCvMatchService: a pure, AI-free keyword-coverage scorer that
returns a stable, reproducible 0-100 match score plus matched/missing
keyword lists and per-CV-section coverage. Unlike candidate-fit (AI
narrative), it makes no model calls, so results are instant and
identical for identical inputs - the Jobscan-style differentiator.

- GET /api/jobapplications/{id}/match-score
- keywords = curated SkillTagger tags (high weight) + salient posting
  terms (title terms boosted); word-boundary matching avoids false hits
- section coverage shows where CV evidence is concentrated
- fix(SkillTagger): punctuation-tolerant C#/.NET patterns; the old \b
  boundaries silently missed 'C#,' and '.NET,' everywhere they are used
- 7 unit tests on the pure scorer; full backend suite green (104)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a MatchScoreCard at the top of the Candidate Fit tab that loads the
deterministic /match-score endpoint independently of the slow AI
narrative, so users see a reproducible score, matched/missing keyword
chips, and per-section coverage immediately.

- MatchScore types + cached, attachment-independent load effect
- graceful 'not enough signal' state
- EN/NB translations
- frontend panel test (matched/missing/section + degraded state)
- backend integration tests for GetMatchScore (happy path + missing CV)
- README endpoint reference

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New JobPipeline: ordered canonical stages (Applied, Waiting, Interview,
Offer, Rejected, Ghosted) with category grouping and a Normalize() that
canonicalizes casing and known synonyms (Interviewing->Interview,
declined->Rejected, ...) while preserving unknown custom statuses.

- normalize status on every write path (Create/Update/PATCH status) so
  the stored value stays canonical without destroying custom values
- GET /api/jobapplications/pipeline exposes the ordered stages so the UI
  renders from one source instead of duplicated hardcoded lists
- 14 unit tests; full backend suite green (120)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
- New pure StageAnalytics.TimeInStage: median days jobs have spent in
  each active pipeline stage (entry time from the last StatusChanged
  event into that stage, else applied date). Closed/success stages
  excluded since 'how long stuck' only applies to actionable stages.
- analytics-overview now derives the funnel from JobPipeline (includes
  the previously-omitted Waiting stage, normalizes legacy spellings) and
  returns TimeInStage.
- 4 unit tests; full backend suite green (124).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Adds a 'Median time in stage' block to the conversion-funnel card
  showing median days and active count per stage from the enriched
  analytics-overview endpoint.
- Funnel bar labels and stage names now render through the shared
  pipeline statusLabel (localized; the funnel also now includes Waiting).
- EN/NB translations. Full frontend suite green (20 suites / 46 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New EmailStatusClassifier scans a message subject/body for outcome
signals (interview invite, offer, rejection) and suggests a canonical
pipeline status. Priority-ordered so a rejection that mentions the prior
interview still classifies as Rejected. Deterministic - no AI - so it is
instant, reproducible, and safe.

- GET /api/jobapplications/{id}/status-suggestion reads the job's latest
  inbound correspondence (incl. Gmail imports) and suggests a forward
  status move, suppressed when already in/past that stage
- always human-confirmed via the existing PATCH .../status
- 7 classifier unit tests + 2 endpoint integration tests; backend green (133)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When a job workspace opens, loads /status-suggestion and shows a
dismissible banner when a recent inbound email implies a status move
("This email looks like a move to Interview"). Applying it PATCHes the
status; nothing changes without the user's click.

- StatusSuggestion type + load-on-open effect + apply handler
- warning-toned banner shown above tab content on any tab
- EN/NB translations; README endpoint docs
- 2 frontend tests; full suite green (21 suites / 48 tests)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cross-user access to the new endpoints returns NotFound (carried by the
JobTrackerContext global query filters). Regression guard for the class
of tenant-leak bugs found in the M013-M015 assessments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The create path ran the same Companies.AnyAsync existence check twice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Scoped security review of Wave 0 + H1-H4: confirms tenant isolation on
new endpoints (query filters + tests), no injection/ReDoS, dev-only
OpenAPI. Flags DataProtection key rotation as the operator action item.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One-click job capture from any posting, reusing the existing
jobimport/preview parser.

- AddJobModal accepts initialUrl and auto-imports once on open
- App reads a /?add=<encoded url> param, opens Add Job pre-filled, and
  strips the param from the address bar
- QuickCaptureCard in Settings offers a draggable bookmarklet (href set
  via ref since React blocks javascript: URLs) plus copyable code
- EN/NB translations; README feature note
- 2 frontend tests; full suite green (22 suites / 50 tests)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Corrected manifest (Jobbjakt branding, matching green theme, maskable
  icons, description/categories/scope/id).
- share_target (GET) maps a shared url/link into the same /?add= capture
  flow the bookmarklet uses, so mobile 'Share -> Jobbjakt' pre-fills Add
  Job.
- resolveCaptureUrl helper (tested) extracts the link from add or from a
  link embedded in shared text; App uses it and strips the params.
- Deliberately no offline service worker: the app deploys frequently and
  an aggressive cache would risk stale builds (documented in README).
- 4 unit tests; build compiles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docs: add merge-request summary for review
CI and Deploy / test (pull_request) Successful in 2m10s
CI and Deploy / deploy (pull_request) Has been skipped
3bd7b4b7e4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cesnimda added 1 commit 2026-07-03 10:53:11 +02:00
fix(build): give the frontend build 1GB /dev/shm
CI and Deploy / test (pull_request) Successful in 2m2s
CI and Deploy / deploy (pull_request) Has been skipped
d61dd6310b
CRA's build runs fork-ts-checker in a forked process whose IPC needs
more than Docker's default 64MB /dev/shm; too little segfaults
'npm run build' (RpcIpcMessagePortClosedError / SIGSEGV) with no compile
error. Set shm_size on the frontend image build so production deploys
don't hit this. (CI runners need the same via their container options.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cesnimda merged commit 316ef9ac1a into main 2026-07-03 11:14:15 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: cesnimda/jobtrackingapp#1