chore(M001/S01): auto-commit after complete-slice
This commit is contained in:
@@ -3,122 +3,168 @@ id: S01
|
||||
parent: M001
|
||||
milestone: M001
|
||||
provides:
|
||||
- Job-aware Gmail matching and import inside the job workspace, with ranked thread suggestions, duplicate-aware imports, and persisted Gmail message/thread metadata.
|
||||
requires: []
|
||||
- Job-scoped Gmail matching and import now run from the job workspace with backend-owned ranking reasons, duplicate-aware import contracts, persisted Gmail thread metadata, and linked-thread refresh that imports later replies into the same job.
|
||||
affects:
|
||||
- S02
|
||||
- S03
|
||||
- S04
|
||||
- S05
|
||||
key_files:
|
||||
- `JobTrackerApi/Controllers/GmailController.cs`
|
||||
- `JobTrackerApi/Services/GmailOAuthService.cs`
|
||||
- `Models/Correspondence.cs`
|
||||
- `JobTrackerApi/Program.cs`
|
||||
- `job-tracker-ui/src/components/Correspondence.tsx`
|
||||
- `job-tracker-ui/src/components/JobDetailsDialog.tsx`
|
||||
- `job-tracker-ui/src/types.ts`
|
||||
- `job-tracker-ui/src/correspondence-gmail-import.test.tsx`
|
||||
- JobTrackerApi/Controllers/GmailController.cs
|
||||
- JobTrackerApi/Services/GmailOAuthService.cs
|
||||
- JobTrackerApi.Tests/GmailControllerTests.cs
|
||||
- Models/Correspondence.cs
|
||||
- JobTrackerApi/Controllers/CorrespondenceController.cs
|
||||
- job-tracker-ui/src/components/Correspondence.tsx
|
||||
- job-tracker-ui/src/components/JobDetailsDialog.tsx
|
||||
- job-tracker-ui/src/types.ts
|
||||
- job-tracker-ui/src/correspondence-gmail-import.test.tsx
|
||||
key_decisions:
|
||||
- Backend, not the browser, is now the source of truth for Gmail candidate ranking.
|
||||
- Gmail-imported correspondence now persists thread identity plus raw sender/recipient metadata for downstream context reuse.
|
||||
- Keep Gmail continuity bounded to known `ExternalThreadId` values for one job via `POST /api/gmail/refresh-linked-threads` instead of inbox-wide Gmail watch/history infrastructure.
|
||||
- Treat the backend as the source of truth for Gmail candidate ranking, duplicate visibility, and linked-thread refresh counts so the workspace UI stays explanatory without re-implementing Gmail heuristics in React.
|
||||
patterns_established:
|
||||
- Job-scoped Gmail endpoints that load owned `JobApplication` context before querying Gmail.
|
||||
- Explicit import result payloads with imported/skipped counts instead of ambiguous duplicate behavior.
|
||||
- Workspace UI consumes ranked backend suggestions and only uses manual search as an override.
|
||||
- Gmail-derived correspondence is first-class job history: imported rows persist external message/thread ids plus sender/recipient labels and can be rendered directly in the timeline/workspace.
|
||||
- Linked-thread continuity is pull-based and duplicate-safe: refresh reads already-linked thread ids for one owned job, skips known external message ids, and imports only new Gmail replies into that same job.
|
||||
- The workspace distinguishes ranked import suggestions from already-linked live threads, with automatic one-shot refresh per loaded job/thread-set and an explicit manual refresh action.
|
||||
observability_surfaces:
|
||||
- `GET /api/gmail/job-candidates`
|
||||
- `GET /api/gmail/status`
|
||||
- `POST /api/gmail/import`
|
||||
- `POST /api/gmail/import-thread`
|
||||
- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx`
|
||||
- isolated `GmailControllerTests`
|
||||
- GET /api/gmail/status
|
||||
- GET /api/gmail/job-candidates
|
||||
- POST /api/gmail/import
|
||||
- POST /api/gmail/import-thread
|
||||
- POST /api/gmail/refresh-linked-threads
|
||||
- persisted Correspondence.ExternalMessageId / ExternalThreadId / ExternalFrom / ExternalTo
|
||||
- JobTrackerApi.Tests/GmailControllerTests.cs
|
||||
- job-tracker-ui/src/correspondence-gmail-import.test.tsx
|
||||
drill_down_paths:
|
||||
- `.gsd/milestones/M001/slices/S01/tasks/T01-SUMMARY.md`
|
||||
- `.gsd/milestones/M001/slices/S01/tasks/T02-SUMMARY.md`
|
||||
- `.gsd/milestones/M001/slices/S01/tasks/T03-SUMMARY.md`
|
||||
duration: one execution pass
|
||||
- .gsd/milestones/M001/slices/S01/tasks/T01-SUMMARY.md
|
||||
- .gsd/milestones/M001/slices/S01/tasks/T02-SUMMARY.md
|
||||
- .gsd/milestones/M001/slices/S01/tasks/T03-SUMMARY.md
|
||||
duration: ~1 slice closure session + prior executor task work
|
||||
verification_result: passed
|
||||
completed_at: 2026-03-24
|
||||
completed_at: 2026-03-24T11:54:52+01:00
|
||||
---
|
||||
|
||||
# S01: Smarter Gmail import and matching
|
||||
|
||||
**Shipped a job-aware Gmail import loop that ranks likely correspondence for a specific job, imports it with clearer duplicate behavior, and persists thread-aware metadata for later slices.**
|
||||
## Outcome
|
||||
|
||||
## What Happened
|
||||
S01 now delivers a job-aware Gmail import loop that is materially closer to the milestone trust bar than the pre-slice state. The workspace can:
|
||||
|
||||
S01 moved Gmail import from a generic inbox search experience toward a job-scoped workflow. On the backend, `GmailController` now exposes `GET /api/gmail/job-candidates`, which builds queries from the owned job, company, recruiter, and prior correspondence state, then returns ranked threads/messages with confidence and match reasons. `GmailOAuthService` gained multi-query candidate retrieval so Gmail request plumbing stays in the service layer rather than leaking into the UI.
|
||||
- connect Gmail and load ranked candidate messages/threads for one owned job
|
||||
- explain why each Gmail candidate matched via score/confidence/match reasons
|
||||
- import either a single message or an entire thread with duplicate-safe result counts
|
||||
- persist Gmail thread identity plus raw sender/recipient labels on correspondence rows
|
||||
- refresh already-linked Gmail threads for that same job and import only later unseen replies
|
||||
- surface that linked-thread state back in the workspace without making the user manually re-import the thread
|
||||
|
||||
The slice also hardened import continuity. `Correspondence` records now store `ExternalThreadId`, `ExternalFrom`, and `ExternalTo` in addition to `ExternalMessageId`, and import responses explicitly report imported vs skipped work. `Program.cs` was updated so the runtime schema repair path adds those columns for older SQLite and MySQL databases instead of breaking startup.
|
||||
The biggest slice change is that Gmail import is no longer just “pick a message and save a snapshot.” It is now a bounded continuity loop around stored `ExternalThreadId` values.
|
||||
|
||||
On the frontend, the job workspace now passes the loaded `job` into `Correspondence`, and the Gmail tab consumes the new backend match contract directly. The tab shows ranked thread/message suggestions, confidence, reasons, already-linked state, and still supports manual query override through the same job-aware endpoint. After import, the correspondence list and Gmail suggestions both refresh so the user can see what changed immediately.
|
||||
## What actually shipped
|
||||
|
||||
## Verification
|
||||
### Backend contract
|
||||
|
||||
- Backend API compiled successfully with `$HOME/.dotnet/dotnet build JobTrackerApi/JobTrackerApi.csproj`.
|
||||
- Focused Gmail backend coverage passed in an isolated harness after the repo-wide test project proved to have unrelated compile drift.
|
||||
- Focused frontend Gmail UI coverage passed:
|
||||
- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx`
|
||||
- Manual/UAT verification is still required for the roadmap’s live Gmail trust bar: connect Gmail, review ranked suggestions for a real job, import a message and a thread, and confirm the result feels materially cleaner than the old manual search flow.
|
||||
`JobTrackerApi/Controllers/GmailController.cs` and `JobTrackerApi/Services/GmailOAuthService.cs` now cover three distinct Gmail workspace behaviors:
|
||||
|
||||
## Requirements Advanced
|
||||
1. **Job-aware candidate ranking** via `GET /api/gmail/job-candidates`
|
||||
- backend aggregates Gmail hits per job query
|
||||
- duplicate Gmail hits are merged server-side
|
||||
- response includes weighted match reasons, matched queries, imported flags, confidence, and thread/message counts
|
||||
|
||||
- R001 — The imported job is now the anchor for Gmail discovery and correspondence import inside the workspace rather than a detached generic inbox search.
|
||||
- R002 — Gmail matching/import now uses job/company/recruiter context, exposes confidence/reasons, skips duplicates explicitly, and persists thread-aware metadata for lower-cleanup correspondence import.
|
||||
- R010 — The slice now preserves more job-linked correspondence continuity by storing message/thread identity and sender/recipient metadata that later timeline/follow-up work can reuse.
|
||||
2. **Duplicate-safe import** via `POST /api/gmail/import` and `POST /api/gmail/import-thread`
|
||||
- single-message imports return `Imported`/`Skipped` plus the imported or existing correspondence row
|
||||
- thread imports report imported/skipped counts instead of silently duplicating rows
|
||||
- imported correspondence persists `ExternalMessageId`, `ExternalThreadId`, `ExternalFrom`, and `ExternalTo`
|
||||
|
||||
## Requirements Validated
|
||||
3. **Linked-thread continuity** via `POST /api/gmail/refresh-linked-threads`
|
||||
- reads the owned job’s existing linked Gmail thread ids
|
||||
- fetches those threads from Gmail
|
||||
- skips already-known external message ids
|
||||
- imports only new messages into the same job
|
||||
- returns refresh counts per job and per thread
|
||||
- distinguishes invalid job, disconnected Gmail, empty linked-thread set, and already-current outcomes
|
||||
|
||||
- R001 — S01 proved the job-specific Gmail import loop is wired into the actual job workspace and uses the job as the context boundary for import actions.
|
||||
- R002 — Backend and frontend verification now prove ranked job-aware suggestions, explicit duplicate handling, and correspondence refresh after import.
|
||||
### Persistence and compatibility
|
||||
|
||||
## New Requirements Surfaced
|
||||
`Models/Correspondence.cs`, `JobTrackerApi/Controllers/CorrespondenceController.cs`, and `JobTrackerApi/Program.cs` now treat Gmail metadata as part of the durable correspondence model, including compatibility guards for the extra columns.
|
||||
|
||||
- none
|
||||
### Workspace UI
|
||||
|
||||
## Requirements Invalidated or Re-scoped
|
||||
`job-tracker-ui/src/components/Correspondence.tsx` now makes the job workspace the real Gmail import surface instead of a generic search-first flow.
|
||||
|
||||
- none — the slice matched the planned requirement shape.
|
||||
It now:
|
||||
|
||||
## Deviations
|
||||
- loads backend-ranked Gmail candidates for the current job
|
||||
- shows confidence/score/match reasons/already-linked status
|
||||
- preserves manual query override through the same endpoint
|
||||
- renders persisted Gmail thread/from/to metadata in the correspondence list
|
||||
- automatically refreshes linked Gmail threads once per loaded job/thread-set
|
||||
- exposes a manual “Refresh linked threads” action for another bounded pull in the same session
|
||||
- updates the workspace after message import, thread import, and linked-thread refresh
|
||||
|
||||
The planned backend verification command (`dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests`) could not run cleanly because the broader `JobTrackerApi.Tests` project already contains unrelated compile errors outside Gmail. Instead of expanding slice scope to repair unrelated tests, Gmail controller coverage was verified in an isolated harness and the frontend path was verified with a focused React test.
|
||||
`job-tracker-ui/src/correspondence-gmail-import.test.tsx` now covers both the ranked import path and the no-manual-reimport continuity path.
|
||||
|
||||
## Known Limitations
|
||||
## Verification run in this closure session
|
||||
|
||||
- The roadmap’s live Gmail trust bar still needs real-account UAT; automated tests prove the contract and UI behavior, not inbox quality on actual mail.
|
||||
- The main `JobTrackerApi.Tests` project still has unrelated compile drift that prevents direct filtered execution of Gmail tests without isolation.
|
||||
- Draft-generation and follow-up slices still need to consume the new correspondence metadata before the full milestone loop is truly integrated.
|
||||
### Automated checks
|
||||
|
||||
## Follow-ups
|
||||
| Command | Result |
|
||||
|---|---|
|
||||
| `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` | ✅ passed |
|
||||
| `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx` | ✅ passed |
|
||||
| `dotnet build JobTrackerApi/JobTrackerApi.csproj` | ✅ passed |
|
||||
|
||||
- S02 should consume the imported correspondence and persisted Gmail metadata as trusted application-context input instead of re-deriving it from raw snippets.
|
||||
- S03 should use `ExternalThreadId`, `ExternalFrom`, and `ExternalTo` when assembling reply/follow-up context so thread continuity stays explicit.
|
||||
- After S02/S03 wiring, run live Gmail UAT again to confirm ranking heuristics still feel trustworthy once downstream flows depend on them.
|
||||
The backend verification command originally described in project knowledge was blocked by unrelated test-project drift earlier in the milestone, but that drift was fixed during this closure pass, and the exact filtered command now passes.
|
||||
|
||||
## Files Created/Modified
|
||||
### Observability confirmed
|
||||
|
||||
- `JobTrackerApi/Controllers/GmailController.cs` — added job-aware candidate discovery, ranked match DTOs, and explicit import result payloads.
|
||||
- `JobTrackerApi/Services/GmailOAuthService.cs` — added multi-query Gmail candidate retrieval support.
|
||||
- `Models/Correspondence.cs` — added persisted Gmail thread and sender/recipient metadata.
|
||||
- `JobTrackerApi/Controllers/CorrespondenceController.cs` — exposed optional external Gmail metadata on correspondence creation.
|
||||
- `JobTrackerApi/Program.cs` — added SQLite/MySQL compatibility guards for the new correspondence fields.
|
||||
- `JobTrackerApi.Tests/GmailControllerTests.cs` — expanded Gmail controller coverage for ranking, ownership scope, duplicate imports, and import payloads.
|
||||
- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — passes job context into the correspondence tab.
|
||||
- `job-tracker-ui/src/components/Correspondence.tsx` — consumes ranked backend Gmail suggestions and refreshes correspondence after import.
|
||||
- `job-tracker-ui/src/types.ts` — added contracts for Gmail match/import payloads and enriched correspondence data.
|
||||
- `job-tracker-ui/src/correspondence-gmail-import.test.tsx` — verifies ranked suggestion rendering, manual override, and post-import refresh.
|
||||
The slice’s durable inspection surfaces are now coherent:
|
||||
|
||||
## Forward Intelligence
|
||||
- `GET /api/gmail/status` exposes Gmail connection freshness (`lastSyncedAt`)
|
||||
- `GET /api/gmail/job-candidates` exposes job-scoped ranking details and duplicate visibility
|
||||
- `POST /api/gmail/refresh-linked-threads` exposes refresh counts and per-thread status
|
||||
- persisted correspondence rows carry external Gmail thread/message/from/to metadata
|
||||
- focused backend/frontend tests encode the intended behavior for future refactors
|
||||
|
||||
### What the next slice should know
|
||||
- S02 can treat imported correspondence as job-trusted context now; the Gmail tab already returns ranked candidates and the stored correspondence now preserves message/thread identity cleanly enough for downstream draft context assembly.
|
||||
### Human / live Gmail UAT status
|
||||
|
||||
### What's fragile
|
||||
- Repo-wide backend test health is fragile — unrelated test drift in `JobTrackerApi.Tests` still blocks direct filtered test execution, so future slices should either repair that debt deliberately or keep focused verification isolated.
|
||||
This auto-mode session did **not** have a live Gmail account wired for a real-account browser pass, so the required human/live UAT was prepared as a concrete script in `S01-UAT.md` rather than executed here. The implementation and automated slice gates passed; the live-account trust check remains a human runbook item.
|
||||
|
||||
### Authoritative diagnostics
|
||||
- `GET /api/gmail/job-candidates` plus `job-tracker-ui/src/correspondence-gmail-import.test.tsx` are the fastest trustworthy checks for whether Gmail matching and UI wiring are still aligned.
|
||||
## Requirement impact
|
||||
|
||||
### What assumptions changed
|
||||
- Original assumption: the existing Gmail integration mostly needed UI polish. Actually, the trust problem sat in backend ranking ownership and persistence continuity, so the slice had to move matching logic server-side and enrich stored correspondence metadata.
|
||||
- **R002** moved to **validated** based on the shipped linked-thread refresh contract plus focused backend/frontend verification.
|
||||
- **R010** remains active, but S01 materially advances it by making Gmail correspondence continuity part of the same job history instead of a one-off import snapshot.
|
||||
|
||||
## Key downstream implications
|
||||
|
||||
### For S02
|
||||
|
||||
Imported Gmail correspondence is now trustworthy enough to use as drafting context. Downstream draft generation should consume job-linked correspondence rows directly, including sender/recipient/thread metadata when useful for tone or recipient context.
|
||||
|
||||
### For S03
|
||||
|
||||
Reply/follow-up drafting can now assume two important invariants:
|
||||
|
||||
- Gmail correspondence is attached to a specific job
|
||||
- later Gmail replies can be pulled into that same job without user re-import
|
||||
|
||||
That means reply/follow-up context assembly should build from the persisted job correspondence set, not from transient Gmail candidate data.
|
||||
|
||||
### For S04/S05
|
||||
|
||||
Daily-loop surfaces can now rely on a clearer distinction between:
|
||||
|
||||
- jobs that only have candidate Gmail matches
|
||||
- jobs with already-linked live Gmail threads
|
||||
- jobs whose linked threads were refreshed and imported new correspondence
|
||||
|
||||
Those are useful action/readiness signals for dashboards, follow-up surfaces, and final end-to-end trust checks.
|
||||
|
||||
## Notable lessons / non-obvious details
|
||||
|
||||
- The bounded sync model is deliberate: refresh is over known linked thread ids for one job, not inbox-wide Gmail watch/history state.
|
||||
- The React workspace auto-refresh is intentionally one-shot per `jobId + linked thread set`; repeated pulls in the same session require the explicit refresh action.
|
||||
- The filtered Gmail backend test command compiles the whole `JobTrackerApi.Tests` project before filtering, so unrelated test drift can still block slice verification if future work breaks test signatures again.
|
||||
|
||||
## Slice verdict
|
||||
|
||||
S01 now establishes the milestone’s Gmail foundation: smarter matching, clearer import trust signals, persisted Gmail metadata, and real linked-thread continuity in the job workspace.
|
||||
|
||||
Reference in New Issue
Block a user