Repository navigation
feat(jef-56): AI-generated company research briefing per application - #281
Conversation
Adds a fifth single-shot AI completion feature (following the same pattern as cover letters, JD parsing, resume match, and chat) that generates a pre-interview briefing from an application's company, role, and job description: company overview, likely culture signals, likely interview style, and talking points. Deliberately scopes out a "recent news" section for v1, per the issue's own open question — without live web search, an LLM's notion of "recent" is bounded by its training cutoff, so presenting that as current would be misleading. The system prompt explicitly instructs the model not to fabricate time-sensitive claims, and the UI carries a visible "not live data" caveat next to the generated output. - GenerateCompanyBriefingUseCase: same ownership/AI-not-configured error handling as GenerateCoverLetterUseCase, reuses the user's customAiPrompt system message convention. - generateCompanyBriefing mutation, registered in schema/index.ts and the DI container following the existing use-case pattern. - CompanyBriefingTab: new tab on the application detail page between Documents and Cover Letter, same generate/copy UX as CoverLetterTab. Verified: typecheck and lint clean (0 errors) for both apps, full test suite passes for apps/web (243 tests) and apps/api (1027 tests — hit intermittent container.test.ts timeouts under load during verification, confirmed pre-existing and unrelated by isolating and rerunning with a longer timeout), plus a production web build.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
WalkthroughAdds AI-generated company briefings to application details. The feature includes prompt construction, rate limiting, authorisation and AI configuration checks, a GraphQL mutation, dependency registration, UI states, clipboard copying, and use-case tests. ChangesCompany briefing feature
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
actor User
participant CompanyBriefingTab
participant generateCompanyBriefing
participant GenerateCompanyBriefingUseCase
participant ApplicationRepository
participant LLMProvider
User->>CompanyBriefingTab: Open Company Briefing tab
CompanyBriefingTab->>generateCompanyBriefing: Submit applicationId
generateCompanyBriefing->>GenerateCompanyBriefingUseCase: Execute with applicationId and userId
GenerateCompanyBriefingUseCase->>ApplicationRepository: Load and authorise application
GenerateCompanyBriefingUseCase->>LLMProvider: Request briefing
LLMProvider-->>GenerateCompanyBriefingUseCase: Return plain-text briefing
GenerateCompanyBriefingUseCase-->>CompanyBriefingTab: Return briefing
CompanyBriefingTab-->>User: Display briefing and copy control
Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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
`@apps/web/src/routes/_authenticated/applications/`$applicationId/-components/CompanyBriefingTab.tsx:
- Around line 8-12: Move the GenerateCompanyBriefing operation out of the inline
GENERATE_COMPANY_BRIEFING constant into the web GraphQL .graphql workflow, run
code generation, and update CompanyBriefingTab to use the generated operation
document and response types instead of a manually defined mutation shape.
- Around line 29-33: Update handleCopy so it awaits
navigator.clipboard.writeText(briefing) before calling setCopied(true), ensuring
the copied state changes only after a successful write. Preserve the existing
timeout reset behavior and avoid setting copied when the clipboard operation
rejects.
🪄 Autofix
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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: fb036b13-cb83-4a53-8ffb-2adc33d0ca49
📒 Files selected for processing (7)
apps/api/src/__tests__/application/companyBriefing/GenerateCompanyBriefingUseCase.test.tsapps/api/src/http/container.tsapps/api/src/http/schema/index.tsapps/api/src/http/schema/mutations/companyBriefingMutations.tsapps/api/src/use-cases/companyBriefing/GenerateCompanyBriefingUseCase.tsapps/web/src/routes/_authenticated/applications/$applicationId/-components/ApplicationDetailPage.tsxapps/web/src/routes/_authenticated/applications/$applicationId/-components/CompanyBriefingTab.tsx
| const GENERATE_COMPANY_BRIEFING = ` | ||
| mutation GenerateCompanyBriefing($applicationId: ID!) { | ||
| generateCompanyBriefing(applicationId: $applicationId) | ||
| } | ||
| `; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Use the web GraphQL code-generation flow.
This inline operation bypasses the required web .graphql file and generated operation types. Move GenerateCompanyBriefing to a .graphql file, run code generation, and use the generated operation type instead of the manual response shape.
As per coding guidelines, when adding a feature, follow the layer order through the web .graphql file and codegen.
🤖 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
`@apps/web/src/routes/_authenticated/applications/`$applicationId/-components/CompanyBriefingTab.tsx
around lines 8 - 12, Move the GenerateCompanyBriefing operation out of the
inline GENERATE_COMPANY_BRIEFING constant into the web GraphQL .graphql
workflow, run code generation, and update CompanyBriefingTab to use the
generated operation document and response types instead of a manually defined
mutation shape.
Source: Coding guidelines
| const handleCopy = () => { | ||
| void navigator.clipboard.writeText(briefing); | ||
| setCopied(true); | ||
| setTimeout(() => setCopied(false), 2000); | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Set copied state only after the clipboard write succeeds.
navigator.clipboard.writeText() can reject when clipboard access is unavailable or denied. The current handler still displays Copied when no text was copied. Await the write before setting copied.
Proposed fix
- const handleCopy = () => {
- void navigator.clipboard.writeText(briefing);
- setCopied(true);
- setTimeout(() => setCopied(false), 2000);
+ const handleCopy = async () => {
+ try {
+ await navigator.clipboard.writeText(briefing);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch {
+ setCopied(false);
+ }
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleCopy = () => { | |
| void navigator.clipboard.writeText(briefing); | |
| setCopied(true); | |
| setTimeout(() => setCopied(false), 2000); | |
| }; | |
| const handleCopy = async () => { | |
| try { | |
| await navigator.clipboard.writeText(briefing); | |
| setCopied(true); | |
| setTimeout(() => setCopied(false), 2000); | |
| } catch { | |
| setCopied(false); | |
| } | |
| }; |
🤖 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
`@apps/web/src/routes/_authenticated/applications/`$applicationId/-components/CompanyBriefingTab.tsx
around lines 29 - 33, Update handleCopy so it awaits
navigator.clipboard.writeText(briefing) before calling setCopied(true), ensuring
the copied state changes only after a successful write. Preserve the existing
timeout reset behavior and avoid setting copied when the clipboard operation
rejects.
Follow-up to JEF-115 (#285, merged) — that PR intentionally left this use-case out since its own PR (#281) hadn't merged to main yet, so it didn't exist on that branch. Now that main has caught up (merged here), applying the identical pattern: a dedicated rate limiter (20 attempts/ 5min, matching the other three AI mutations), checked before any DB/ LLM work, reusing the existing RATE_LIMITED error code. Verified: typecheck and lint clean (0 errors, same 1 pre-existing warning), full apps/api suite passes (1032 tests, including a new RATE_LIMITED test case), apps/web typecheck and production build succeed.
|
Merged `main` into this branch and applied JEF-115's rate-limiting pattern to `GenerateCompanyBriefingUseCase`, per the follow-up noted on that PR (#285, already merged).
Verified: typecheck/lint clean on both apps, full `apps/api` suite passes (1032 tests, including a new dedicated `RATE_LIMITED` test), `apps/web` typecheck and production build succeed. |
Summary
GenerateCompanyBriefingUseCasemirrorsGenerateCoverLetterUseCase's error handling (NOT_FOUND/FORBIDDEN/AI_NOT_CONFIGURED) and thecustomAiPromptsystem-message convention.generateCompanyBriefingmutation, wired intoschema/index.tsand the DI container the same way every other AI mutation is.CoverLetterTab.A deliberate scoping call
The issue's own "Open questions" section flagged the risk: without live web search, an LLM's "recent news" is bounded by its training cutoff, so presenting that as current would be misleading. I scoped v1 to skip "recent news" entirely rather than label it — the system prompt explicitly instructs the model not to fabricate time-sensitive claims (funding, layoffs, leadership changes), and the UI carries a visible "not live data — verify anything time-sensitive" caveat next to the generated output.
Test plan
pnpm --filter @job-finder/api typecheck/pnpm --filter @job-finder/web typecheck— both pass.pnpm --filter @job-finder/api lint/pnpm --filter @job-finder/web lint— 0 errors on both (pre-existing warnings only, unrelated to this change).NOT_FOUND/FORBIDDEN/AI_NOT_CONFIGUREDerror paths.apps/apitest suite (1027 tests) andapps/webtest suite (243 tests) — both pass; hit intermittent timeouts incontainer.test.tsand one flakywaitForinNotificationInbox.test.tsxduring a heavily-loaded run, confirmed pre-existing and unrelated by isolating and rerunning both cleanly.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests