Skip to content

feat: [tools] CV optimizer + lettre de motivation (free taste + credit tier)#19

Open
AirKyzzZ wants to merge 18 commits into
mainfrom
feature/shelf-cv-lettre
Open

feat: [tools] CV optimizer + lettre de motivation (free taste + credit tier)#19
AirKyzzZ wants to merge 18 commits into
mainfrom
feature/shelf-cv-lettre

Conversation

@AirKyzzZ

@AirKyzzZ AirKyzzZ commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Closes #9

First slice of the provider-orchestration plan: a CV optimizer and a lettre de motivation writer, each with a free public taste on /outils (IP rate-limited, Haiku) and a 1-credit dashboard tier (Sonnet). The lettre rides the existing tool machinery as a fourth ToolMode, the CV analyzer is its own slice since it returns a structured analysis instead of an email. Both premium actions ground in profiles.cv_extracted and keep the Phase 4 billing invariant: a failed generation never costs a credit, and the consume_quota result is now acted on so a lost race returns quota_exceeded instead of a free result.

Also extracts LeadCapture out of the tool generator, adds the two pages to the hub and sitemap, two PostHog events, strings in all four locales, and a shelf-tools Playwright smoke. The playwright config now honors PORT so e2e can run next to another local dev server.

154 unit tests, typecheck, build and the 4 e2e all green.

Summary by CodeRabbit

  • New Features

    • Public CV Analyzer with score, strengths/gaps, keywords and lead capture flow.
    • CV Optimizer in dashboard with full analysis, credit/quota handling and rewritten excerpts.
    • Cover letter generator (public + dashboard) producing subject/body and copy-to-clipboard.
    • Sidebar navigation and sitemap updated with tool links; non‑French marketing pages set to noindex.
    • Translations added for FR/EN/DE/ES and new analytics events for tool usage.
  • Tests

    • Added E2E and unit tests covering tools, prompts, AI integrations and server actions.

@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cheatjob Ready Ready Preview, Comment Jun 10, 2026 8:08pm

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0a01865f-8abb-40b2-87eb-768a64e87253

📥 Commits

Reviewing files that changed from the base of the PR and between f4997e1 and 7837ebc.

📒 Files selected for processing (6)
  • web/e2e/shelf-tools.spec.ts
  • web/messages/de.json
  • web/messages/en.json
  • web/messages/es.json
  • web/messages/fr.json
  • web/src/app/[locale]/(marketing)/outils/_components/lead-capture.tsx
✅ Files skipped from review due to trivial changes (2)
  • web/messages/de.json
  • web/messages/fr.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • web/e2e/shelf-tools.spec.ts
  • web/messages/en.json
  • web/src/app/[locale]/(marketing)/outils/_components/lead-capture.tsx
  • web/messages/es.json

📝 Walkthrough

Walkthrough

Adds CV optimizer and cover-letter tools: prompt schemas, AI model wiring and mocks, server actions with quota/auth handling, marketing pages and public analyzer, dashboard forms, translations, tests, Playwright config, sitemap updates, and E2E checks.

Changes

CV Optimizer & Lettre Writer

Layer / File(s) Summary
AI models, prompts & mock data
web/src/server/integrations/ai/index.ts, web/src/server/integrations/ai/openrouter.ts, web/src/server/integrations/ai/openrouter.test.ts, web/src/server/integrations/ai/prompts/cv-optimizer.ts, web/src/server/integrations/ai/prompts/cv-optimizer.test.ts, web/src/server/integrations/ai/prompts/lettre-full.ts, web/src/server/integrations/ai/prompts/lettre-full.test.ts, web/src/server/integrations/ai/prompts/tool-generator.ts, web/src/server/integrations/ai/prompts/tool-generator.test.ts
Adds cvOptimize and lettre model entries, prompt builders (buildCvOptimizerPrompt, buildLettrePrompt), parsing schemas (CvAnalysisSchema, LettreOutputSchema), and OpenRouter E2E mock response for CV-analysis; includes unit tests for prompts and parsing.
CV optimizer server actions & tests
web/src/server/actions/cv-optimizer.ts, web/src/server/actions/cv-optimizer.test.ts
Exports analyzeCvPublic (honeypot, validation, rate-limit RPC, public AI path) and analyzeCvFull (auth, profile CV requirement, quota checks, full model call, quota decrement on success). Tests cover validation, rate-limit/quota, parse success, and generation failures.
Lettre writer server action & tests
web/src/server/actions/lettre-writer.ts, web/src/server/actions/lettre-writer.test.ts
Exports writeLettre with input validation, auth/profile/quota checks, AI generation and strict JSON parsing; decrements quota only after successful parse. Tests cover auth, validation, quota shortcuts, success, generation failures, and concurrent quota conflicts.
Public marketing pages & lead capture
web/src/app/[locale]/(marketing)/outils/_components/cv-analyzer.tsx, web/src/app/[locale]/(marketing)/outils/_components/cv-tool-page.tsx, web/src/app/[locale]/(marketing)/outils/_components/lead-capture.tsx, web/src/app/[locale]/(marketing)/outils/_components/tool-generator.tsx, web/src/app/[locale]/(marketing)/outils/_content.ts, web/src/app/[locale]/(marketing)/outils/optimiser-son-cv/page.tsx, web/src/app/[locale]/(marketing)/outils/lettre-de-motivation/page.tsx, web/src/app/[locale]/(marketing)/outils/page.tsx, web/src/app/sitemap.ts
Adds CvAnalyzer public form calling analyzeCvPublic, CvToolPage with JSON-LD, new LeadCapture component, refactors ToolGenerator to use LeadCapture, registers lettre content & CV tool content, adds public pages with locale-aware metadata and sitemap entries.
Authenticated dashboard: CV optimizer & lettre forms
web/src/app/[locale]/dashboard/cv-optimizer/page.tsx, web/src/app/[locale]/dashboard/cv-optimizer/_components/cv-optimizer-form.tsx, web/src/app/[locale]/dashboard/lettre/page.tsx, web/src/app/[locale]/dashboard/lettre/_components/lettre-form.tsx, web/src/app/[locale]/dashboard/_components/sidebar.tsx
Adds dashboard pages/components gated by Supabase auth, using profile-derived hasCv/quotaRemaining, forms to call analyzeCvFull/writeLettre, result/error UIs, and sidebar nav entries.
Localization, Playwright wiring & E2E
web/messages/{de,en,es,fr}.json, web/playwright.config.ts, web/e2e/shelf-tools.spec.ts, web/src/lib/analytics/events.ts
Adds translation keys for sidebar/tool strings and tools UI, extracts PORT into Playwright config for baseURL/webServer, adds E2E tests for hub/tools pages and analyzer flow, and registers analytics events CvOptimized and LettreGenerated.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 I nibbled prompts and hopped through code, delight—
CVs sharpened, lettres drafted in moonlight,
Quota checks thump like tiny drum beats,
Two tools now help job hunts find their feets.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main changes: implementing two new AI tools (CV optimizer and lettre de motivation) with both free and credit-based tiers.
Linked Issues check ✅ Passed The PR fully implements the objectives from issue #9 [#9]: CV optimizer for analyzing CVs against job postings [#9], lettre de motivation cover letter generator [#9], reusing AIProvider and profiles.cv_extracted [#9], and following the planned workflow [#9].
Out of Scope Changes check ✅ Passed All changes directly support the CV optimizer and lettre de motivation implementation: E2E tests, i18n strings, server actions, UI components, analytics events, AI prompts, and configuration updates are all in scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/shelf-cv-lettre

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
web/src/app/[locale]/dashboard/lettre/_components/lettre-form.tsx (1)

63-68: 💤 Low value

Consider adding error handling for clipboard write.

navigator.clipboard.writeText can fail if clipboard permissions are denied or if the API is unavailable. While rare in modern HTTPS contexts, handling the error would improve UX by showing feedback when copy fails.

♻️ Proposed enhancement
  async function onCopy() {
    if (!result) return;
-   await navigator.clipboard.writeText(`${result.subject}\n\n${result.body}`);
-   setCopied(true);
-   setTimeout(() => setCopied(false), 2000);
+   try {
+     await navigator.clipboard.writeText(`${result.subject}\n\n${result.body}`);
+     setCopied(true);
+     setTimeout(() => setCopied(false), 2000);
+   } catch {
+     // Optionally set an error state to show "Copy failed" feedback
+   }
  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/app/`[locale]/dashboard/lettre/_components/lettre-form.tsx around
lines 63 - 68, Wrap the navigator.clipboard.writeText call in a try/catch inside
onCopy: attempt the write, setCopied(true) and schedule reset only on success,
and in the catch set an error state (e.g., setCopyError or setCopied(false) and
setCopyErrorMessage) so the UI can show a failure message; keep onCopy as the
single place handling copy and update any relevant UI to display the error when
that state is set.
web/src/app/[locale]/(marketing)/outils/_components/cv-analyzer.tsx (1)

28-48: ⚡ Quick win

Premature analytics tracking spans both tool components.

Both CvAnalyzer and ToolGenerator track EVENTS.ToolGenerated immediately on form submit (before their respective async server actions complete). This means the event fires even when the action fails due to rate limiting, invalid input, or server errors. The event name implies successful generation, making analytics data misleading.

Consider one of these approaches across both components:

  • Track the event only in the success path (after successful response)
  • Rename to EVENTS.ToolGenerationAttempted or EVENTS.ToolSubmitted
  • Emit separate events for attempts vs. successes
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/app/`[locale]/(marketing)/outils/_components/cv-analyzer.tsx around
lines 28 - 48, The submit handler currently calls track(EVENTS.ToolGenerated, {
tool: TOOL }) immediately in onSubmit, which logs success even when
analyzeCvPublic fails; to fix, remove that initial track call and instead call
track(EVENTS.ToolGenerated, { tool: TOOL }) only inside the success branch where
res.ok is true (right after setAnalysis(res.analysis)), or alternatively emit a
distinct event (e.g., EVENTS.ToolGenerationAttempted) at submit and keep
EVENTS.ToolGenerated for the success path; update references in onSubmit,
analyzeCvPublic success branch, and any related tests to reflect the chosen
approach while leaving setAnalysis and setErrorKind logic intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/e2e/shelf-tools.spec.ts`:
- Around line 4-8: The test named "hub lists five tools" in the test block
(test("hub lists five tools", async ({ page }) => { ... })) only asserts two
links; either rename the test to reflect the actual assertions (e.g., "hub lists
CV and lettre tools") or add expectations for the other three expected tool
links by adding additional expect(page.getByRole("link", { name: /.../
})).toBeVisible() calls after the existing ones (keep the existing
page.goto("/fr/outils") and match the visible link names for the three missing
tools).

In `@web/src/app/`[locale]/(marketing)/outils/_components/cv-tool-page.tsx:
- Around line 6-32: CV_TOOL currently hardcodes French strings so the page
ignores locale; replace the hardcoded values in the CV_TOOL object with
translation lookups using the existing next-intl translations (use the
getTranslations("tools") result and t(...) calls) — e.g. map metaTitle,
metaDescription, h1, intro and each faq.q/a to translation keys like
"cv.metaTitle", "cv.metaDescription", "cv.h1", "cv.intro", "cv.faq.0.q",
"cv.faq.0.a", etc.; update the component that imports/uses CV_TOOL to build or
hydrate CV_TOOL from t(...) after awaiting getTranslations("tools") so content
is locale-aware and remove the French literals from the file.

In `@web/src/app/`[locale]/(marketing)/outils/_components/lead-capture.tsx:
- Around line 14-23: The onCapture handler lacks error handling when
captureToolLead returns ok: false; add a local error state (e.g., captureError)
and set it when res.ok is false inside onCapture so the UI can show a failure
message and optionally allow retry; update the JSX that currently uses
setCaptureSent to also render the error message when captureError is set and
clear captureError when the user modifies the email or retries. Target symbols:
onCapture, startCapture, captureToolLead, setCaptureSent, track, email, tool.

---

Nitpick comments:
In `@web/src/app/`[locale]/(marketing)/outils/_components/cv-analyzer.tsx:
- Around line 28-48: The submit handler currently calls
track(EVENTS.ToolGenerated, { tool: TOOL }) immediately in onSubmit, which logs
success even when analyzeCvPublic fails; to fix, remove that initial track call
and instead call track(EVENTS.ToolGenerated, { tool: TOOL }) only inside the
success branch where res.ok is true (right after setAnalysis(res.analysis)), or
alternatively emit a distinct event (e.g., EVENTS.ToolGenerationAttempted) at
submit and keep EVENTS.ToolGenerated for the success path; update references in
onSubmit, analyzeCvPublic success branch, and any related tests to reflect the
chosen approach while leaving setAnalysis and setErrorKind logic intact.

In `@web/src/app/`[locale]/dashboard/lettre/_components/lettre-form.tsx:
- Around line 63-68: Wrap the navigator.clipboard.writeText call in a try/catch
inside onCopy: attempt the write, setCopied(true) and schedule reset only on
success, and in the catch set an error state (e.g., setCopyError or
setCopied(false) and setCopyErrorMessage) so the UI can show a failure message;
keep onCopy as the single place handling copy and update any relevant UI to
display the error when that state is set.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 93617891-5c8e-417c-938e-72463e1514de

📥 Commits

Reviewing files that changed from the base of the PR and between 1d3da1b and f4997e1.

📒 Files selected for processing (34)
  • web/e2e/shelf-tools.spec.ts
  • web/messages/de.json
  • web/messages/en.json
  • web/messages/es.json
  • web/messages/fr.json
  • web/playwright.config.ts
  • web/src/app/[locale]/(marketing)/outils/_components/cv-analyzer.tsx
  • web/src/app/[locale]/(marketing)/outils/_components/cv-tool-page.tsx
  • web/src/app/[locale]/(marketing)/outils/_components/lead-capture.tsx
  • web/src/app/[locale]/(marketing)/outils/_components/tool-generator.tsx
  • web/src/app/[locale]/(marketing)/outils/_content.ts
  • web/src/app/[locale]/(marketing)/outils/lettre-de-motivation/page.tsx
  • web/src/app/[locale]/(marketing)/outils/optimiser-son-cv/page.tsx
  • web/src/app/[locale]/(marketing)/outils/page.tsx
  • web/src/app/[locale]/dashboard/_components/sidebar.tsx
  • web/src/app/[locale]/dashboard/cv-optimizer/_components/cv-optimizer-form.tsx
  • web/src/app/[locale]/dashboard/cv-optimizer/page.tsx
  • web/src/app/[locale]/dashboard/lettre/_components/lettre-form.tsx
  • web/src/app/[locale]/dashboard/lettre/page.tsx
  • web/src/app/sitemap.ts
  • web/src/lib/analytics/events.ts
  • web/src/server/actions/cv-optimizer.test.ts
  • web/src/server/actions/cv-optimizer.ts
  • web/src/server/actions/lettre-writer.test.ts
  • web/src/server/actions/lettre-writer.ts
  • web/src/server/integrations/ai/index.ts
  • web/src/server/integrations/ai/openrouter.test.ts
  • web/src/server/integrations/ai/openrouter.ts
  • web/src/server/integrations/ai/prompts/cv-optimizer.test.ts
  • web/src/server/integrations/ai/prompts/cv-optimizer.ts
  • web/src/server/integrations/ai/prompts/lettre-full.test.ts
  • web/src/server/integrations/ai/prompts/lettre-full.ts
  • web/src/server/integrations/ai/prompts/tool-generator.test.ts
  • web/src/server/integrations/ai/prompts/tool-generator.ts

Comment thread web/e2e/shelf-tools.spec.ts
Comment on lines +6 to +32
export const CV_TOOL = {
slug: "optimiser-son-cv",
metaTitle: "Optimiser son CV pour une offre : analyse gratuite · Cheatjob",
metaDescription:
"Colle ton CV et l'offre visée : score d'adéquation, points forts, lacunes, mots-clés manquants et conseils concrets. Gratuit, sans inscription.",
h1: "Optimise ton CV pour l'offre que tu vises",
intro:
"Un CV n'est pas bon dans l'absolu, il est bon pour une offre. Colle ton CV et l'annonce : on te dit ce qui colle, ce qui manque et ce qu'il faut réécrire, en trente secondes.",
faq: [
{
q: "Comment adapter son CV à une offre d'emploi ?",
a: "Tu reprends les mots de l'offre, tu remontes les expériences qui y répondent, tu coupes le reste. Un CV adapté se lit comme une réponse à l'annonce, pas comme un historique exhaustif.",
},
{
q: "C'est quoi les mots-clés ATS ?",
a: "Les termes que les logiciels de tri et les recruteurs cherchent en premier : intitulés de poste, compétences, outils. S'ils sont dans l'offre mais pas dans ton CV, tu passes sous le radar.",
},
{
q: "Quelle longueur pour un CV de jeune diplômé ?",
a: "Une page. Pas par dogme : parce qu'un recruteur y passe trente secondes et qu'une page bien hiérarchisée se scanne mieux que deux pages remplies.",
},
{
q: "Faut-il chiffrer ses expériences sur un CV ?",
a: "Oui, dès que c'est possible. « Pilotage de 3 projets » dit quelque chose, « participation à des projets » ne dit rien. Les chiffres rendent une expérience vérifiable et mémorisable.",
},
],
} as const;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Hardcoded French content breaks multi-locale support.

The CV_TOOL constant contains French-only strings for metaTitle, metaDescription, h1, intro, and all FAQ entries. The PR summary states "adds strings for four locales" and the layer depends on expanded message catalogs, yet this component will render French content regardless of the locale parameter.

You should migrate these strings to use next-intl translations, similar to how the component already uses await getTranslations("tools") on line 35.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/app/`[locale]/(marketing)/outils/_components/cv-tool-page.tsx around
lines 6 - 32, CV_TOOL currently hardcodes French strings so the page ignores
locale; replace the hardcoded values in the CV_TOOL object with translation
lookups using the existing next-intl translations (use the
getTranslations("tools") result and t(...) calls) — e.g. map metaTitle,
metaDescription, h1, intro and each faq.q/a to translation keys like
"cv.metaTitle", "cv.metaDescription", "cv.h1", "cv.intro", "cv.faq.0.q",
"cv.faq.0.a", etc.; update the component that imports/uses CV_TOOL to build or
hydrate CV_TOOL from t(...) after awaiting getTranslations("tools") so content
is locale-aware and remove the French literals from the file.

@AirKyzzZ

Copy link
Copy Markdown
Owner Author

Fixes Applied Successfully

Fixed 6 file(s) based on 2 of 3 CodeRabbit feedback item(s).

Applied:

  • web/e2e/shelf-tools.spec.ts: the hub test now asserts all five tool cards, matching its name
  • web/src/app/[locale]/(marketing)/outils/_components/lead-capture.tsx: failed captures now surface a visible error (tools.captureError, added in all 4 message files)

Deferred (intentional): the hardcoded French content in cv-tool-page.tsx matches the existing /outils pattern, these are FR-targeted SEO pages with non-FR locales noindexed; EN/ES/DE localization of tool content is an explicit deferred follow-up.

Commit: 7837ebc on feature/shelf-cv-lettre. 154 unit tests + 4 e2e still green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 2 — CV optimizer + cover letter writer

1 participant