chore(M001/S01): auto-commit after research-slice
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
# S01 — Research
|
||||
|
||||
**Date:** 2026-03-24
|
||||
|
||||
## Summary
|
||||
|
||||
S01 owns **R001** and **R002** directly, and it materially supports **R010** because imported correspondence becomes part of the job timeline and later follow-up context. The codebase already has a complete Gmail OAuth path, a Gmail message listing endpoint, single-message and thread import endpoints, and a per-job correspondence UI. The gap is not missing Gmail plumbing; it is that matching is still mostly manual. The backend returns raw Gmail search results, while the frontend (`job-tracker-ui/src/components/Correspondence.tsx`) applies a lightweight client-side score based only on the freeform query, snippet text, and already-imported subjects. That does not use the actual job/company context strongly enough to satisfy the “less manual cleanup” bar in R002.
|
||||
|
||||
The best approach is to keep the existing OAuth/import flow and add a **job-aware matching layer** rather than replacing the Gmail integration. In practice that means: enrich backend candidate discovery around a specific `JobApplication`, return grouped thread/message suggestions with explicit match reasons/confidence inputs, and then update the correspondence dialog to present those suggestions first. This follows the current architecture cleanly and preserves the no-auto-send boundary from D002. The React side should keep async work consolidated instead of scattering additional fetches across effects; the loaded `react-best-practices` skill is relevant here, especially `async-parallel` and `client-event-listeners`.
|
||||
|
||||
## Recommendation
|
||||
|
||||
Add a dedicated **job-scoped Gmail matching surface** on top of the existing endpoints instead of trying to make the current generic `/api/gmail/messages` search UI smarter only in the browser.
|
||||
|
||||
Recommended shape:
|
||||
- Backend: add a job-aware endpoint in `JobTrackerApi/Controllers/GmailController.cs` that accepts `jobApplicationId` and optional overrides, loads the job + company context, builds Gmail queries from `JobTitle`, `Company.Name`, `Company.RecruiterEmail`, recruiter name, and recent imported correspondence, then returns ranked message/thread candidates with **match reasons** and enough metadata for import decisions.
|
||||
- Persistence: if the planner wants durable thread-aware behavior, extend `Models/Correspondence.cs` beyond `ExternalMessageId` to also persist at least `ExternalThreadId` and raw sender/recipient metadata. This is the cleanest way to support downstream S03 reply/follow-up context without re-deriving it later.
|
||||
- Frontend: refactor `job-tracker-ui/src/components/Correspondence.tsx` so the Gmail tab consumes enriched API data instead of doing primary ranking locally. `JobDetailsDialog.tsx` already loads the full job; passing the job or a reduced job-context prop into `Correspondence` is cheaper than forcing the Gmail tab to rediscover job facts.
|
||||
|
||||
Why this approach:
|
||||
- It uses the existing Gmail OAuth/token flow unchanged.
|
||||
- It moves matching logic to the backend where job context, dedupe checks, and future heuristics are easier to test.
|
||||
- It avoids over-investing in fragile client-only heuristics.
|
||||
- It creates a natural seam for S03, where better thread metadata and message provenance will matter again.
|
||||
|
||||
## Implementation Landscape
|
||||
|
||||
### Key Files
|
||||
|
||||
- `JobTrackerApi/Controllers/GmailController.cs` — current Gmail API surface. Has `/status`, `/connect-url`, `/messages`, `/import`, and `/import-thread`. Import endpoints already attach messages to `JobApplication` and dedupe by `Correspondence.ExternalMessageId`, but discovery is still generic and not job-aware.
|
||||
- `JobTrackerApi/Services/GmailOAuthService.cs` — Gmail OAuth/token refresh and Gmail API access. `ListMessagesAsync` currently calls the Gmail list endpoint and then does an **N+1** sequence of `GetMessageAsync` calls to hydrate summaries. There is no thread-specific fetch API, no job-aware query builder, and no ranking/match-reason contract here yet.
|
||||
- `job-tracker-ui/src/components/Correspondence.tsx` — the main S01 frontend surface. It opens the Gmail dialog, loads `/gmail/status`, loads `/gmail/messages`, groups by `threadId`, and sorts using `scoreMessage(...)`. Current suggestions come from existing correspondence subjects, not from job/company/recruiter context.
|
||||
- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — already loads the full `JobApplication` record and hosts the Correspondence tab. This is the easiest place to pass job context into `Correspondence` instead of refetching it inside the Gmail tab.
|
||||
- `job-tracker-ui/src/types.ts` — frontend contracts for `GmailStatus`, `GmailMessageSummary`, and `CorrespondenceMessage`. Any enriched matching response or persisted metadata expansion needs updates here.
|
||||
- `Models/Correspondence.cs` — currently stores `From`, `Subject`, `Channel`, `ExternalMessageId`, `Content`, and `Date`. No `ThreadId`, no original Gmail sender/recipient fields, and no match/debug metadata.
|
||||
- `Data/JobTrackerContext.cs` — EF relationships and ownership filters. `JobApplication`, `Company`, and `GmailConnection` are user-scoped via query filters; new Gmail-matching endpoints should continue loading jobs through this context rather than bypassing ownership.
|
||||
- `Models/Company.cs` and `Models/JobApplication.cs` — hold the matching signals that the current Gmail UI ignores: `Company.Name`, `RecruiterEmail`, `RecruiterName`, `JobTitle`, `JobUrl`, `ShortSummary`, and existing correspondence/timeline relationships.
|
||||
- `JobTrackerApi/Controllers/CorrespondenceController.cs` — current create/list/delete API for job-linked messages. If S01 persists extra Gmail metadata, this contract may need to expose it to the UI and timeline.
|
||||
- `JobTrackerApi/Controllers/JobApplicationsController.cs` — downstream dependency surface. It already reads `Correspondences` for follow-up drafting and timeline assembly, so better message/thread metadata here directly helps S03.
|
||||
- `JobTrackerApi/Program.cs` — important migration/backfill guardrail. The app manually ensures legacy SQLite/MySQL columns such as `Correspondences.Subject`, `Channel`, and `ExternalMessageId`. If S01 adds new persistence columns, these compatibility blocks must be updated alongside the EF migration.
|
||||
- `JobTrackerApi.Tests/GmailControllerTests.cs` — only covers the empty-thread import validation case today. Good starting point, but far below the verification level needed for S01.
|
||||
- `job-tracker-ui/src/job-details-generated-drafts.test.tsx` — representative React test style: mock `api`, render `JobDetailsDialog`, assert visible tab behavior. Follow this pattern for new Gmail suggestion/import UI tests.
|
||||
|
||||
### Build Order
|
||||
|
||||
1. **Decide and lock the backend contract first.**
|
||||
- Prove what a “smarter match” response looks like: message/thread grouping, rank/confidence, reasons, imported/already-linked flags, and import actions.
|
||||
- This is the riskiest part and unblocks everything else.
|
||||
|
||||
2. **Implement job-aware matching in `GmailController` + supporting service/helpers.**
|
||||
- Load `JobApplication` with `Company`.
|
||||
- Build candidate Gmail queries from job/company/recruiter data.
|
||||
- Merge/dedupe results by message id or thread id.
|
||||
- Compute match reasons server-side.
|
||||
- Keep existing `/import` and `/import-thread` behavior unless the new contract proves they need richer return payloads.
|
||||
|
||||
3. **Only after the contract is stable, refactor `Correspondence.tsx`.**
|
||||
- Replace `scoreMessage(...)` as the primary ranking engine with server-provided ranking/reasons.
|
||||
- Pass job context from `JobDetailsDialog.tsx` rather than introducing another job fetch in the correspondence tab.
|
||||
- Keep manual query override/search available as a fallback, not the primary UX.
|
||||
|
||||
4. **Then extend persistence if needed for thread continuity.**
|
||||
- Add correspondence metadata only if the chosen backend contract needs it for dedupe, import clarity, or future reply context.
|
||||
- If added, update model, migration, and `Program.cs` compatibility shims together.
|
||||
|
||||
5. **Finish with tests.**
|
||||
- Backend tests for matching/import behavior first.
|
||||
- Frontend tests for the new Gmail suggestion UI second.
|
||||
|
||||
### Verification Approach
|
||||
|
||||
- Backend unit/integration tests:
|
||||
- `dotnet test JobTrackerApi.Tests`
|
||||
- Add tests for: job-aware candidate endpoint contract, dedupe behavior for already-imported messages, ownership-scoped job lookup, and thread import summary results.
|
||||
- Frontend tests:
|
||||
- `npm test -- --watch=false` from `job-tracker-ui`
|
||||
- Add React tests covering: Gmail tab rendering ranked suggestions, showing match reasons, import button states, and fallback/manual search behavior.
|
||||
- Contract/manual verification:
|
||||
- Open a job in `JobDetailsDialog` → Correspondence tab.
|
||||
- Confirm Gmail connection state still works.
|
||||
- Confirm the Gmail tab now shows job-relevant suggestions before freeform searching.
|
||||
- Import a single message and a full thread; verify the job timeline/correspondence list updates and duplicates are skipped.
|
||||
- If persistence changes land:
|
||||
- Verify schema startup still succeeds on existing dev DBs because `JobTrackerApi/Program.cs` legacy `EnsureColumn(...)` blocks are easy to miss.
|
||||
|
||||
## Constraints
|
||||
|
||||
- `Data/JobTrackerContext.cs` applies ownership query filters to `Company`, `JobApplication`, and `GmailConnection`. Any new Gmail matching endpoint must keep loading through EF-scoped entities, not raw unfiltered ids.
|
||||
- `JobTrackerApi/Services/GmailOAuthService.cs` currently exposes only message list/detail methods. There is no reusable thread-fetch or search-aggregation abstraction yet.
|
||||
- `JobTrackerApi/Program.cs` contains manual schema repair code for SQLite/MySQL. Adding correspondence metadata requires updating both EF migration artifacts and these runtime compatibility paths.
|
||||
- `Models/Correspondence.cs` does not currently preserve Gmail thread identity or raw sender/recipient fields, which limits downstream thread-aware UX.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- **Leaving ranking in the browser** — `Correspondence.tsx` can polish server results, but if the primary intelligence stays in `scoreMessage(...)`, S01 will remain query-driven and fragile.
|
||||
- **Adding columns without updating `Program.cs`** — this repo relies on startup-time `EnsureColumn(...)` logic for legacy/dev databases; migration-only changes are incomplete here.
|
||||
- **Duplicating job fetches in the dialog tree** — `JobDetailsDialog.tsx` already owns the job record. Per the `react-best-practices` guidance (`async-parallel`, `client-event-listeners`), keep async fetching consolidated and avoid adding more dialog-level waterfalls or duplicated global listeners.
|
||||
- **Treating thread import as enough without thread metadata** — importing all messages in a thread helps today, but without persisting thread identity the app still cannot reason clearly about thread continuity later.
|
||||
|
||||
## Open Risks
|
||||
|
||||
- Gmail search quality may still be noisy even after better query construction; the planner should expect one iteration on ranking heuristics once real data is exercised.
|
||||
- `ListMessagesAsync` is sequential and could become noticeably slow if the new matching flow issues multiple Gmail searches per job. If that happens, batching/parallelization inside the Gmail service becomes part of S01, not a later optimization.
|
||||
|
||||
## Skills Discovered
|
||||
|
||||
| Technology | Skill | Status |
|
||||
|------------|-------|--------|
|
||||
| React | `react-best-practices` | available |
|
||||
| ASP.NET Core | `openai/skills@aspnet-core` | installed |
|
||||
Reference in New Issue
Block a user