Skip to content

feat(jef-56): AI-generated company research briefing per application - #281

Merged
mankatcheung merged 3 commits into
mainfrom
feat/jef-56-company-research-briefing
Aug 8, 2026
Merged

mankatcheung merged 3 commits into
mainfrom
feat/jef-56-company-research-briefing

Conversation

@mankatcheung

@mankatcheung mankatcheung commented Aug 8, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Adds a fifth single-shot AI completion feature (following the same shape as cover letters, JD parsing, resume match, and chat) that generates a pre-interview company briefing from an application's company, role, and job description: company overview, likely culture signals, likely interview style, and talking points.
  • GenerateCompanyBriefingUseCase mirrors GenerateCoverLetterUseCase's error handling (NOT_FOUND/FORBIDDEN/AI_NOT_CONFIGURED) and the customAiPrompt system-message convention.
  • New generateCompanyBriefing mutation, wired into schema/index.ts and the DI container the same way every other AI mutation is.
  • New "Company Briefing" tab on the application detail page (between Documents and Cover Letter), same generate/copy UX as 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).
  • New use-case test suite (8 tests): returns LLM output, custom-AI-prompt inclusion/omission, "no recent news" instruction present in the system prompt, application context in the user prompt, NOT_FOUND/FORBIDDEN/AI_NOT_CONFIGURED error paths.
  • Full apps/api test suite (1027 tests) and apps/web test suite (243 tests) — both pass; hit intermittent timeouts in container.test.ts and one flaky waitFor in NotificationInbox.test.tsx during a heavily-loaded run, confirmed pre-existing and unrelated by isolating and rerunning both cleanly.
  • Production web build succeeds.
  • Manual click-through: open an application, generate a briefing, confirm the AI-not-configured error path shows the settings link when no API key is set.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added an AI-generated Company Briefing tab to application details.
    • Briefings include company and role context, with optional custom prompts.
    • Added loading, error, AI configuration guidance, and copy-to-clipboard support.
    • Added backend support for generating briefings through the application interface.
    • Added rate limiting to help manage briefing generation requests.
  • Tests

    • Added comprehensive coverage for successful generation, custom prompts, permissions, missing applications, rate limits, and AI configuration errors.

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.
@coderabbitai

coderabbitai Bot commented Aug 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1bfa8b8a-3d27-4aee-9eb4-9eb7445bcceb

📥 Commits

Reviewing files that changed from the base of the PR and between 45c67e7 and 757d674.

📒 Files selected for processing (4)
  • apps/api/src/__tests__/application/companyBriefing/GenerateCompanyBriefingUseCase.test.ts
  • apps/api/src/constants.ts
  • apps/api/src/http/container.ts
  • apps/api/src/use-cases/companyBriefing/GenerateCompanyBriefingUseCase.ts

Walkthrough

Adds 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.

Changes

Company briefing feature

Layer / File(s) Summary
Briefing generation and prompt construction
apps/api/src/use-cases/companyBriefing/GenerateCompanyBriefingUseCase.ts, apps/api/src/constants.ts, apps/api/src/__tests__/application/companyBriefing/GenerateCompanyBriefingUseCase.test.ts
The use case rate-limits requests, authorises applications, checks AI configuration, builds prompts, applies custom prompts, and requests plain-text output. Tests cover successful generation and typed failure cases.
GraphQL mutation and container wiring
apps/api/src/http/container.ts, apps/api/src/http/schema/index.ts, apps/api/src/http/schema/mutations/companyBriefingMutations.ts
The container registers the rate limiter and transient use case. The schema exposes the authenticated generateCompanyBriefing mutation and converts coded failures into GraphQL errors.
Application detail briefing interface
apps/web/src/routes/_authenticated/applications/$applicationId/-components/ApplicationDetailPage.tsx, apps/web/src/routes/_authenticated/applications/$applicationId/-components/CompanyBriefingTab.tsx
The application detail page adds a Company Briefing tab. The tab generates and displays briefings, handles loading and configuration errors, supports regeneration and copying, and labels the content as AI-generated.

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
Loading

Possibly related issues

Possibly related PRs

Poem

A rabbit checks the prompt with care,
Then sends a briefing through the air.
The tab displays the generated view,
With copy controls ready too.
Hop, review, and save the day. 🐇

🚥 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 title clearly and concisely describes the main change: an AI-generated company research briefing for each application.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/jef-56-company-research-briefing

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.

@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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 140200a and 45c67e7.

📒 Files selected for processing (7)
  • apps/api/src/__tests__/application/companyBriefing/GenerateCompanyBriefingUseCase.test.ts
  • apps/api/src/http/container.ts
  • apps/api/src/http/schema/index.ts
  • apps/api/src/http/schema/mutations/companyBriefingMutations.ts
  • apps/api/src/use-cases/companyBriefing/GenerateCompanyBriefingUseCase.ts
  • apps/web/src/routes/_authenticated/applications/$applicationId/-components/ApplicationDetailPage.tsx
  • apps/web/src/routes/_authenticated/applications/$applicationId/-components/CompanyBriefingTab.tsx

Comment on lines +8 to +12
const GENERATE_COMPANY_BRIEFING = `
mutation GenerateCompanyBriefing($applicationId: ID!) {
generateCompanyBriefing(applicationId: $applicationId)
}
`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 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

Comment on lines +29 to +33
const handleCopy = () => {
void navigator.clipboard.writeText(briefing);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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.

Suggested change
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.
@mankatcheung

Copy link
Copy Markdown
Owner Author

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).

  • Merge from `main` was clean — only `container.ts` needed auto-merging, no conflicts.
  • Added a dedicated rate limiter (20 attempts/5min, matching the other three AI mutations: cover letter, JD parsing, resume match) to `GenerateCompanyBriefingUseCase`, checked before any DB/LLM work, reusing the existing `RATE_LIMITED` error code — identical shape to what fix(jef-115): rate limit the AI-calling mutations that had none #285 already did for the others.

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.

@mankatcheung
mankatcheung merged commit e0592d5 into main Aug 8, 2026
11 checks passed
@mankatcheung
mankatcheung deleted the feat/jef-56-company-research-briefing branch August 18, 2026 13:16
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.

1 participant