chore(M001/S01): auto-commit after complete-slice
This commit is contained in:
@@ -14,3 +14,4 @@
|
||||
| D006 | M001/S02 | workspace-persistence | How the saved application answer draft should persist inside the job workspace before a dedicated field exists | Store the application answer draft in a replaceable notes block and make SaveApplicationDrafts overwrite notes when notes are explicitly provided | The existing append-only notes behavior made the Tailored CV workspace untrustworthy because repeated saves duplicated the application answer indefinitely. A replaceable notes block preserves current schema compatibility while giving the workspace a stable saved/read-back loop for later slices. | Yes | agent |
|
||||
| D007 | M001/S01 | gmail-continuity | What “good Gmail import” now means for M001 | Treat Gmail import as full-thread continuity: the user must be able to import the whole relevant thread, and already-linked Gmail threads must refresh automatically so later inbound messages and user-sent replies appear on the job without manual re-import. This supersedes the narrower one-time-import interpretation inside D005’s Gmail-import focus. | The user explicitly asked whether the app can bring the whole email thread and automatically show their reply later without re-pulling/importing again. That changes the trust bar from “find and import the right message” to “keep the linked thread current over time,” while still preserving the no-auto-send boundary from D002. | Yes — if Gmail API or product constraints later require a different sync model | human |
|
||||
| D008 | M001/S01 planning | gmail-sync | How linked Gmail threads stay current in S01 | Use a job-scoped refresh flow over already-imported Gmail thread IDs, triggered from the job workspace/API, instead of building inbox-wide Gmail watch/history cursor infrastructure in M001. | The codebase already persists ExternalThreadId per correspondence and lacks Gmail history/watch infrastructure. Fetching known linked threads for one job keeps scope bounded, fits the single-user workspace model, supports duplicate-safe import of new inbound and sent replies, and creates a trustworthy continuity loop without adding brittle webhook/cursor state before the milestone proves value. | Yes — if real usage shows job-scoped pull refresh is too slow or misses important continuity cases. | agent |
|
||||
| D009 | M001/S01 closure | gmail-matching | Where Gmail candidate aggregation and ranking logic should live for job-aware import | Keep Gmail query-hit aggregation, dedupe, matched-query traces, and ranking reasons in the backend contract instead of recreating that logic in the React workspace. | The correspondence workspace needs explanatory candidate ranking plus duplicate visibility, and putting the logic in the API keeps one source of truth for scoring/import state while preventing browser-side heuristic drift. | Yes | agent |
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Project Knowledge
|
||||
|
||||
- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` still compiles the entire `JobTrackerApi.Tests` project before filtering execution. If unrelated controller tests drift from production signatures, the Gmail slice verification command will fail at compile time even when `GmailControllerTests` itself is correct.
|
||||
- The correspondence workspace auto-refreshes linked Gmail threads once per `jobId + ExternalThreadId set` using `POST /api/gmail/refresh-linked-threads`; if you need another pull in the same UI session without changing linked threads, use the explicit "Refresh linked threads" action.
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ The product must let one person run a real job search without losing the thread:
|
||||
|
||||
## Current State
|
||||
|
||||
A substantial brownfield app already exists. The repo has a React frontend, an ASP.NET Core API, and a local FastAPI AI service. Current capabilities already include job tracking, companies, attachments, correspondence, reminders, job import preview, Gmail connection/import, profile CV upload/parsing/rewrite flows, AI-assisted tailored CV and cover-letter generation, candidate-fit/focus-plan/interview-prep/readiness endpoints, and dashboard/system surfaces. S01 moved Gmail import from a generic search surface to a job-aware workspace flow: the backend ranks likely Gmail threads/messages for a specific job, imports report duplicate state explicitly, correspondence persists Gmail thread and sender/recipient metadata, and the job workspace renders those ranked suggestions directly. The new milestone direction raises that bar further: Gmail import must now support trustworthy whole-thread continuity, so already-linked Gmail threads need to refresh automatically and show later inbound messages or user-sent replies on the job without requiring manual re-import. S02 then tightened the package loop around that context: backend package generation now consumes imported correspondence plus recruiter/job state, and the Tailored CV tab treats tailored CV, cover letter, recruiter message, and saved application-answer material as one editable workspace tied back to the job instead of a throwaway preview pane. S03 turned follow-up drafting into a real thread-aware workspace flow, and S04 connected the dashboard, reminders, and table into one routed daily control loop that opens the same per-job workspace state. The next phase is not greenfield feature invention; it is milestone revalidation plus Gmail thread-continuity polish so the correspondence loop stays live after first import.
|
||||
A substantial brownfield app already exists. The repo has a React frontend, an ASP.NET Core API, and a local FastAPI AI service. Current capabilities already include job tracking, companies, attachments, correspondence, reminders, job import preview, Gmail connection/import, profile CV upload/parsing/rewrite flows, AI-assisted tailored CV and cover-letter generation, candidate-fit/focus-plan/interview-prep/readiness endpoints, and dashboard/system surfaces. S01 is now complete: the Gmail workspace is job-aware, backend ranking happens server-side, Gmail imports persist thread/from/to metadata, duplicate-safe single-message and thread imports are explicit, and already-linked Gmail threads refresh back into the same job automatically via a bounded `ExternalThreadId` pull. S02 tightened the package loop around that context: backend package generation now consumes imported correspondence plus recruiter/job state, and the Tailored CV tab treats tailored CV, cover letter, recruiter message, and saved application-answer material as one editable workspace tied back to the job instead of a throwaway preview pane. S03 turned follow-up drafting into a real thread-aware workspace flow, and S04 connected the dashboard, reminders, and table into one routed daily control loop that opens the same per-job workspace state. The next phase is milestone revalidation plus end-to-end trust/polish on top of the now-live Gmail continuity loop.
|
||||
|
||||
## Architecture / Key Patterns
|
||||
|
||||
|
||||
+15
-15
@@ -4,17 +4,6 @@ This file is the explicit capability and coverage contract for the project.
|
||||
|
||||
## Active
|
||||
|
||||
### R002 — Gmail connection, message retrieval, thread import, and linked-thread refresh must help the user pull real correspondence into the right job and keep it current with materially less manual cleanup.
|
||||
- Class: integration
|
||||
- Status: active
|
||||
- Description: Gmail connection, message retrieval, single-message/thread import, and linked-thread refresh must help the user pull real correspondence into the right job, preserve full thread continuity, and automatically surface later inbound or user-sent replies without requiring manual re-import of the thread.
|
||||
- Why it matters: Gmail import is one of the two clearest current weaknesses and a major trust surface for daily use; one-time import alone is not enough if the thread immediately goes stale.
|
||||
- Source: user
|
||||
- Primary owning slice: M001/S01
|
||||
- Supporting slices: M001/S03, M001/S05
|
||||
- Validation: remapped after override; previous S01 validation covered ranked import and persisted thread metadata, but the requirement now also includes full-thread continuity and automatic refresh of linked Gmail threads.
|
||||
- Notes: This requirement was previously treated as validated for one-time import quality. The 2026-03-24 override re-opened it by raising the bar to include ongoing thread sync.
|
||||
|
||||
### R008 — The app may draft application, reply, and follow-up content, but it must not auto-send emails or auto-apply to jobs.
|
||||
- Class: constraint
|
||||
- Status: active
|
||||
@@ -61,6 +50,17 @@ This file is the explicit capability and coverage contract for the project.
|
||||
- Validation: S01 completed with a job-scoped Gmail import loop wired into the job workspace: backend `GET /api/gmail/job-candidates` uses the owned job as context, imports target that job directly, and focused UI verification passed in `job-tracker-ui/src/correspondence-gmail-import.test.tsx`.
|
||||
- Notes: Validation is contract/UI-level plus workspace integration. Live user UAT of the broader milestone loop still remains for later slices.
|
||||
|
||||
### R002 — Gmail connection, message retrieval, single-message/thread import, and linked-thread refresh must help the user pull real correspondence into the right job, preserve full thread continuity, and automatically surface later inbound or user-sent replies without requiring manual re-import of the thread.
|
||||
- Class: integration
|
||||
- Status: validated
|
||||
- Description: Gmail connection, message retrieval, single-message/thread import, and linked-thread refresh must help the user pull real correspondence into the right job, preserve full thread continuity, and automatically surface later inbound or user-sent replies without requiring manual re-import of the thread.
|
||||
- Why it matters: Gmail import is one of the two clearest current weaknesses and a major trust surface for daily use; one-time import alone is not enough if the thread immediately goes stale.
|
||||
- Source: user
|
||||
- Primary owning slice: M001/S01
|
||||
- Supporting slices: M001/S03, M001/S05
|
||||
- Validation: Validated by M001/S01: backend `POST /api/gmail/refresh-linked-threads` now refreshes already-linked Gmail thread ids for one owned job, imports only unseen replies into the same job with duplicate-safe counts, focused `GmailControllerTests` pass via `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests`, and `job-tracker-ui/src/correspondence-gmail-import.test.tsx` proves the workspace auto-refresh path shows a later Gmail reply without manual re-import.
|
||||
- Notes: S01 now covers job-aware Gmail candidate ranking, duplicate-safe single-message/thread import, persisted Gmail thread/from/to metadata, and automatic linked-thread continuity inside the correspondence workspace.
|
||||
|
||||
### R003 — Tailored CV and cover-letter drafts must feel specific, credible, and good enough that the user wants to start from them.
|
||||
- Class: differentiator
|
||||
- Status: validated
|
||||
@@ -202,7 +202,7 @@ This file is the explicit capability and coverage contract for the project.
|
||||
| ID | Class | Status | Primary owner | Supporting | Proof |
|
||||
|---|---|---|---|---|---|
|
||||
| R001 | primary-user-loop | validated | M001/S01 | M001/S05 | S01 completed with a job-scoped Gmail import loop wired into the job workspace: backend `GET /api/gmail/job-candidates` uses the owned job as context, imports target that job directly, and focused UI verification passed in `job-tracker-ui/src/correspondence-gmail-import.test.tsx`. |
|
||||
| R002 | integration | active | M001/S01 | M001/S03, M001/S05 | Remapped after the 2026-03-24 override: ranked import and metadata persistence exist, but the requirement now also includes full-thread continuity and automatic refresh of linked Gmail threads so later inbound/user-sent replies appear without manual re-import. |
|
||||
| R002 | integration | validated | M001/S01 | M001/S03, M001/S05 | Validated by M001/S01: backend `POST /api/gmail/refresh-linked-threads` now refreshes already-linked Gmail thread ids for one owned job, imports only unseen replies into the same job with duplicate-safe counts, focused `GmailControllerTests` pass via `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests`, and `job-tracker-ui/src/correspondence-gmail-import.test.tsx` proves the workspace auto-refresh path shows a later Gmail reply without manual re-import. |
|
||||
| R003 | differentiator | validated | M001/S02 | M001/S05 | Validated by M001/S02: backend application-package generation now uses recruiter/job/profile/attachment/imported-correspondence context, focused backend tests pass in an isolated harness, and the Tailored CV workspace test proves generation, editing, save, and saved-state redisplay behavior. |
|
||||
| R004 | primary-user-loop | validated | M001/S03 | M001/S01, M001/S02, M001/S05 | Validated by M001/S03: follow-up draft generation now consumes imported correspondence plus saved application package material, focused backend follow-up tests pass in an isolated harness, the focused React follow-up test passes, and browser verification on the built branch UI proved the grounded follow-up draft plus manual send/log flow back into correspondence. |
|
||||
| R005 | continuity | validated | M001/S04 | M001/S05 | Validated by M001/S04: the job table now exposes actionable urgency chips that route directly into the relevant job workspace tab, focused daily-loop UI tests pass, and browser verification confirmed the table/dashboard/reminders flow routes into the same workspace model. |
|
||||
@@ -221,7 +221,7 @@ This file is the explicit capability and coverage contract for the project.
|
||||
|
||||
## Coverage Summary
|
||||
|
||||
- Active requirements: 4
|
||||
- Mapped to slices: 4
|
||||
- Validated: 6 (R001, R003, R004, R005, R006, R007)
|
||||
- Active requirements: 3
|
||||
- Mapped to slices: 3
|
||||
- Validated: 7 (R001, R002, R003, R004, R005, R006, R007)
|
||||
- Unmapped active requirements: 0
|
||||
|
||||
@@ -28,3 +28,8 @@
|
||||
{"ts":"2026-03-24T11:11:32.900Z","flowId":"a8498eb1-9aa2-41cf-98d5-c908c31835fb","seq":2,"eventType":"dispatch-match","rule":"executing → execute-task (recover missing task plan → plan-slice)","data":{"unitType":"plan-slice","unitId":"M001/S01"}}
|
||||
{"ts":"2026-03-24T11:11:32.919Z","flowId":"a8498eb1-9aa2-41cf-98d5-c908c31835fb","seq":3,"eventType":"unit-start","data":{"unitType":"plan-slice","unitId":"M001/S01"}}
|
||||
{"ts":"2026-03-24T11:14:30.779Z","flowId":"a8498eb1-9aa2-41cf-98d5-c908c31835fb","seq":4,"eventType":"unit-end","data":{"unitType":"plan-slice","unitId":"M001/S01","status":"completed","artifactVerified":true},"causedBy":{"flowId":"a8498eb1-9aa2-41cf-98d5-c908c31835fb","seq":3}}
|
||||
{"ts":"2026-03-24T11:14:31.436Z","flowId":"a8498eb1-9aa2-41cf-98d5-c908c31835fb","seq":5,"eventType":"iteration-end","data":{"iteration":3}}
|
||||
{"ts":"2026-03-24T11:14:31.437Z","flowId":"76338692-ac8b-4668-98c6-5062fdd544ff","seq":1,"eventType":"iteration-start","data":{"iteration":4}}
|
||||
{"ts":"2026-03-24T11:14:31.524Z","flowId":"76338692-ac8b-4668-98c6-5062fdd544ff","seq":2,"eventType":"dispatch-match","rule":"summarizing → complete-slice","data":{"unitType":"complete-slice","unitId":"M001/S01"}}
|
||||
{"ts":"2026-03-24T11:14:31.532Z","flowId":"76338692-ac8b-4668-98c6-5062fdd544ff","seq":3,"eventType":"unit-start","data":{"unitType":"complete-slice","unitId":"M001/S01"}}
|
||||
{"ts":"2026-03-24T11:27:04.508Z","flowId":"76338692-ac8b-4668-98c6-5062fdd544ff","seq":4,"eventType":"unit-end","data":{"unitType":"complete-slice","unitId":"M001/S01","status":"completed","artifactVerified":true},"causedBy":{"flowId":"76338692-ac8b-4668-98c6-5062fdd544ff","seq":3}}
|
||||
|
||||
@@ -52,7 +52,7 @@ This milestone is complete only when all are true:
|
||||
|
||||
## Slices
|
||||
|
||||
- [ ] **S01: Smarter Gmail import and matching** `risk:high` `depends:[]`
|
||||
- [x] **S01: Smarter Gmail import and matching** `risk:high` `depends:[]`
|
||||
> After this: User can connect Gmail, review likely messages or threads for a job, import a message or full thread, and trust linked Gmail threads to stay current on that job without manual re-import.
|
||||
|
||||
- [ ] **S02: Stronger AI application package drafting** `risk:high` `depends:[S01]`
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
# S01 UAT: Smarter Gmail import and matching
|
||||
|
||||
## Scope
|
||||
|
||||
Validate that one real job workspace can:
|
||||
|
||||
- connect Gmail
|
||||
- show ranked Gmail message/thread suggestions for that job
|
||||
- import a single message or full thread into the job
|
||||
- preserve Gmail thread/sender/recipient metadata in correspondence
|
||||
- refresh already-linked Gmail threads and surface later inbound or user-sent replies without manual re-import
|
||||
|
||||
## Preconditions
|
||||
|
||||
1. Run the app with a build that includes S01 changes.
|
||||
2. Sign in as a normal local user who owns at least one job application record.
|
||||
3. Choose one job with a realistic company name, recruiter email, or recruiter name so Gmail matching has meaningful context.
|
||||
4. Use a Gmail account that contains at least one relevant thread for that job.
|
||||
5. For the continuity test, ensure that thread can receive a new inbound reply or that you can send a reply from your Gmail account during the test.
|
||||
6. Start with no browser extensions that block Google OAuth popups.
|
||||
|
||||
---
|
||||
|
||||
## Test Case 1 — Connect Gmail from the job workspace
|
||||
|
||||
**Goal:** Confirm Gmail connection starts from the job correspondence workspace and returns visible connection state.
|
||||
|
||||
### Steps
|
||||
|
||||
1. Open the chosen job in the job workspace/dialog.
|
||||
2. Go to the **Correspondence** area.
|
||||
3. Click **Import email**.
|
||||
4. Open the **Google** tab.
|
||||
5. If Gmail is not connected, click **Connect Gmail**.
|
||||
6. Complete the Google OAuth flow in the popup.
|
||||
7. Return to the job workspace.
|
||||
|
||||
### Expected results
|
||||
|
||||
- The popup closes or reports success.
|
||||
- The Gmail section shows the connected Gmail address.
|
||||
- The Gmail tab becomes usable without a page reload.
|
||||
- A `Last synced` timestamp or connected-state indicator is visible.
|
||||
|
||||
---
|
||||
|
||||
## Test Case 2 — Ranked Gmail suggestions are job-aware and explanatory
|
||||
|
||||
**Goal:** Confirm the workspace suggests likely Gmail threads/messages for the current job and explains why.
|
||||
|
||||
### Steps
|
||||
|
||||
1. Stay in the same job’s **Correspondence → Google** tab.
|
||||
2. Wait for candidate Gmail suggestions to load automatically.
|
||||
3. Review the first 3 suggested threads/messages.
|
||||
4. Use the manual search box with a more specific override such as the recruiter email or a subject fragment.
|
||||
5. Click **Search**.
|
||||
|
||||
### Expected results
|
||||
|
||||
- Suggestions are scoped to the current job rather than a generic inbox list.
|
||||
- Each suggested thread/message shows visible ranking context such as confidence, score, and match reasons.
|
||||
- Already-linked content, if any, is marked as already linked/imported rather than presented as a fresh import with no explanation.
|
||||
- Manual search override changes the candidate list without breaking the job-aware context.
|
||||
|
||||
### Edge checks
|
||||
|
||||
- If the job has no good recruiter/company data, the UI should still show either fallback queries or a clear empty state rather than a broken panel.
|
||||
- If no Gmail matches exist, the UI should clearly say there are no matches yet for this job/search override.
|
||||
|
||||
---
|
||||
|
||||
## Test Case 3 — Import a single Gmail message into the job
|
||||
|
||||
**Goal:** Confirm single-message import attaches Gmail correspondence to the correct job and preserves metadata.
|
||||
|
||||
### Steps
|
||||
|
||||
1. In the Google tab, locate a suggested Gmail message that belongs to the chosen job.
|
||||
2. Click **Import email** for that message.
|
||||
3. Return focus to the correspondence list.
|
||||
4. Inspect the newly imported correspondence row.
|
||||
5. Without changing jobs, try importing the exact same Gmail message again if it is still visible.
|
||||
|
||||
### Expected results
|
||||
|
||||
- The imported message appears in the chosen job’s correspondence history immediately.
|
||||
- The correspondence row shows the imported subject/body content.
|
||||
- Gmail metadata is visible on that row, including thread id and sender/recipient labels when available.
|
||||
- Re-importing the same message does **not** create a duplicate correspondence entry.
|
||||
- The UI reports that the message was already linked or skipped on the second attempt.
|
||||
|
||||
---
|
||||
|
||||
## Test Case 4 — Import an entire Gmail thread into the job
|
||||
|
||||
**Goal:** Confirm thread import brings multiple related Gmail messages into the same job and stays duplicate-safe on repeat.
|
||||
|
||||
### Steps
|
||||
|
||||
1. In the Google tab, choose a suggested thread with at least 2 messages.
|
||||
2. Click **Import thread**.
|
||||
3. Inspect the correspondence list after import.
|
||||
4. Count how many entries from that thread now appear on the job.
|
||||
5. Trigger **Import thread** again for the same thread if it remains listed.
|
||||
|
||||
### Expected results
|
||||
|
||||
- Multiple correspondence entries from the selected Gmail thread are attached to the same job.
|
||||
- Each imported entry preserves Gmail metadata (`Thread`, `From`, `To`) where present.
|
||||
- The import result reports imported/skipped counts.
|
||||
- Repeating the thread import does not duplicate messages already attached to the job.
|
||||
|
||||
### Edge checks
|
||||
|
||||
- If some messages from the thread were already imported before the thread import, only the missing messages should be added.
|
||||
- If the thread is fully imported already, the result should show skipped-only behavior rather than silently doing nothing.
|
||||
|
||||
---
|
||||
|
||||
## Test Case 5 — Automatic linked-thread refresh imports a later reply without manual re-import
|
||||
|
||||
**Goal:** Validate the core slice promise: once a Gmail thread is linked to a job, later replies appear on that job through refresh rather than a fresh import action.
|
||||
|
||||
### Steps
|
||||
|
||||
1. Start from a job that already has at least one imported Gmail message/thread with a visible linked thread id.
|
||||
2. In Gmail, send a new reply on that same thread **or** wait for a real inbound reply from the recruiter.
|
||||
3. Return to the job workspace.
|
||||
4. Reopen the job if needed, but do **not** use **Import email** or **Import thread** for the new reply.
|
||||
5. Wait for the workspace to settle.
|
||||
6. If the new reply does not appear automatically after the initial load, click **Refresh linked threads** once.
|
||||
7. Inspect the correspondence list.
|
||||
|
||||
### Expected results
|
||||
|
||||
- The workspace recognizes that the job has linked Gmail threads.
|
||||
- The new reply appears under the same job without the user manually selecting the thread again from Gmail candidates.
|
||||
- The new reply preserves thread continuity and sender/recipient metadata.
|
||||
- The Gmail area shows a refresh summary such as imported count, linked thread count, or already-current state.
|
||||
- Running refresh again immediately after a successful import should not create duplicates.
|
||||
|
||||
### Edge checks
|
||||
|
||||
- Test both directions if possible:
|
||||
- recruiter → user inbound reply
|
||||
- user → recruiter sent reply from Gmail
|
||||
- If there are no new messages, refresh should report that linked threads are already current rather than pretending new content arrived.
|
||||
|
||||
---
|
||||
|
||||
## Test Case 6 — Failure visibility is explicit
|
||||
|
||||
**Goal:** Confirm the slice does not fail as a silent no-op.
|
||||
|
||||
### Steps
|
||||
|
||||
1. Disconnect Gmail from the workspace.
|
||||
2. Reopen the Gmail tab for the same job.
|
||||
3. Attempt to use the Gmail area again.
|
||||
4. Reconnect Gmail.
|
||||
5. Open a different job with no imported Gmail thread ids.
|
||||
6. Check whether linked-thread refresh controls and status remain understandable.
|
||||
|
||||
### Expected results
|
||||
|
||||
- Disconnected Gmail state is explicit and actionable.
|
||||
- The workspace does not pretend linked-thread refresh succeeded while disconnected.
|
||||
- A job with no linked Gmail threads shows a clear “no linked threads yet” state rather than a hidden failure.
|
||||
- Invalid/empty states remain distinct from successful refreshes.
|
||||
|
||||
---
|
||||
|
||||
## Acceptance summary
|
||||
|
||||
S01 passes UAT when all of the following are true for at least one real job:
|
||||
|
||||
- Gmail can be connected from the correspondence workspace.
|
||||
- The workspace shows sensible ranked Gmail suggestions for that job.
|
||||
- Single-message and full-thread imports attach correspondence to that job and stay duplicate-safe.
|
||||
- Imported correspondence visibly preserves Gmail metadata.
|
||||
- A later reply on an already-linked Gmail thread appears on the job through the linked-thread refresh path without requiring the user to manually re-import the thread.
|
||||
- Empty/disconnected/already-current states are visible and understandable.
|
||||
@@ -7,6 +7,11 @@ files:
|
||||
- JobTrackerApi/Controllers/CorrespondenceController.cs
|
||||
- JobTrackerApi/Program.cs
|
||||
- JobTrackerApi.Tests/GmailControllerTests.cs
|
||||
observability_surfaces:
|
||||
- POST /api/gmail/import
|
||||
- POST /api/gmail/import-thread
|
||||
- persisted Correspondence.ExternalThreadId / ExternalFrom / ExternalTo fields
|
||||
- JobTrackerApi.Tests/GmailControllerTests.cs repeat-import coverage
|
||||
verification:
|
||||
- $HOME/.dotnet/dotnet build JobTrackerApi/JobTrackerApi.csproj
|
||||
- docker run --rm -v "$PWD":/src -w /src mcr.microsoft.com/dotnet/sdk:9.0 bash -lc '...dotnet test /tmp/gmailtests/GmailTests.csproj...'
|
||||
@@ -14,7 +19,8 @@ verification:
|
||||
|
||||
Extended correspondence persistence and Gmail import continuity for S01.
|
||||
|
||||
What changed:
|
||||
## What changed
|
||||
|
||||
- `Models/Correspondence.cs`
|
||||
- added `ExternalThreadId`
|
||||
- added `ExternalFrom`
|
||||
@@ -33,9 +39,25 @@ What changed:
|
||||
- added repeat thread import coverage
|
||||
- retained ranking and owned-job scope coverage from T01
|
||||
|
||||
Verification:
|
||||
## Verification
|
||||
|
||||
- Native API build passed with `$HOME/.dotnet/dotnet build JobTrackerApi/JobTrackerApi.csproj`
|
||||
- Isolated Gmail controller tests passed in Docker (`5 passed`)
|
||||
|
||||
Important caveat:
|
||||
- The repository’s main `JobTrackerApi.Tests` project still has unrelated pre-existing compile failures outside Gmail tests, so the exact planned command `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` remains blocked by broader test drift. Gmail coverage itself passes when isolated.
|
||||
## Verification Evidence
|
||||
|
||||
| # | Command | Exit Code | Verdict | Duration |
|
||||
|---|---------|-----------|---------|----------|
|
||||
| 1 | `$HOME/.dotnet/dotnet build JobTrackerApi/JobTrackerApi.csproj` | 0 | ✅ pass | ~1.2s |
|
||||
| 2 | `docker run --rm -v "$PWD":/src -w /src mcr.microsoft.com/dotnet/sdk:9.0 bash -lc '...dotnet test /tmp/gmailtests/GmailTests.csproj...'` | 0 | ✅ pass | not recorded |
|
||||
|
||||
## Diagnostics
|
||||
|
||||
- Inspect persisted `Correspondence` rows for `ExternalMessageId`, `ExternalThreadId`, `ExternalFrom`, and `ExternalTo` to confirm Gmail imports keep thread identity plus sender/recipient labels.
|
||||
- Hit `POST /api/gmail/import` twice with the same Gmail message id to confirm the duplicate-safe `Imported`/`Skipped` contract.
|
||||
- Hit `POST /api/gmail/import-thread` twice with the same thread payload to confirm repeat imports skip already-linked message ids instead of duplicating correspondence.
|
||||
- Read `JobTrackerApi.Tests/GmailControllerTests.cs` for the durable expected behavior around repeat single-message and repeat thread imports.
|
||||
|
||||
## Important caveat
|
||||
|
||||
The repository’s main `JobTrackerApi.Tests` project still had unrelated pre-existing compile failures outside Gmail tests when this task finished, so the exact planned command `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` remained blocked at that point by broader test drift. Gmail coverage itself passed when isolated.
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
estimated_steps: 4
|
||||
estimated_files: 4
|
||||
skills_used:
|
||||
- react-best-practices
|
||||
- test
|
||||
---
|
||||
|
||||
# T03: Wire ranked Gmail suggestions into the job workspace UI
|
||||
|
||||
**Slice:** S01 — Smarter Gmail import and matching
|
||||
**Milestone:** M001
|
||||
|
||||
## Description
|
||||
|
||||
Deliver the user-facing part of the slice in the actual job workspace. The Correspondence tab should stop acting like a generic Gmail search box and instead open with job-aware ranked suggestions from the backend, explain why each candidate is relevant, let the user override with manual search when needed, and refresh the job-linked correspondence view after import.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Update `job-tracker-ui/src/types.ts` to reflect the new backend candidate contract and enriched correspondence metadata from T01 and T02.
|
||||
2. Pass job context from `job-tracker-ui/src/components/JobDetailsDialog.tsx` into `job-tracker-ui/src/components/Correspondence.tsx` so the Gmail tab can request job-aware suggestions without duplicating the job fetch.
|
||||
3. Refactor `job-tracker-ui/src/components/Correspondence.tsx` to consume the backend-ranked suggestions, show thread/message match reasons and import state, keep manual Gmail query override/search available, and refresh the rendered correspondence list after successful imports.
|
||||
4. Add `job-tracker-ui/src/correspondence-gmail-import.test.tsx` covering ranked suggestion rendering, reason/confidence display, thread vs single-message import actions, and refresh after import.
|
||||
|
||||
## Must-Haves
|
||||
|
||||
- [ ] The Gmail tab opens on job-aware ranked suggestions instead of using `scoreMessage(...)` as the primary intelligence.
|
||||
- [ ] The UI still supports manual Gmail searching as a fallback override, but it no longer depends on freeform query heuristics for the core experience.
|
||||
- [ ] The React test proves the user can see ranked suggestions and that importing updates the same job’s correspondence surface.
|
||||
|
||||
## Verification
|
||||
|
||||
- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx`
|
||||
- Manually inspect the dialog to confirm ranked reasons/import state are visible and the correspondence list refreshes after import.
|
||||
|
||||
## Observability Impact
|
||||
|
||||
- Signals added/changed: visible match reasons/confidence/import state in the Gmail tab and clearer import success/duplicate feedback toasts.
|
||||
- How a future agent inspects this: read `job-tracker-ui/src/correspondence-gmail-import.test.tsx` and open the Correspondence dialog in the running app.
|
||||
- Failure state exposed: the UI should distinguish no matches, already-imported candidates, loading states, and import failures instead of collapsing them into a generic empty list.
|
||||
|
||||
## Inputs
|
||||
|
||||
- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — host dialog that already owns the job record.
|
||||
- `job-tracker-ui/src/components/Correspondence.tsx` — current Gmail import UI with client-side ranking.
|
||||
- `job-tracker-ui/src/types.ts` — frontend API contracts.
|
||||
- `JobTrackerApi/Controllers/GmailController.cs` — T01/T02 backend Gmail candidate and import contract.
|
||||
|
||||
## Expected Output
|
||||
|
||||
- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — passes job context into the correspondence tab.
|
||||
- `job-tracker-ui/src/components/Correspondence.tsx` — renders job-aware Gmail suggestions and refreshed import behavior.
|
||||
- `job-tracker-ui/src/types.ts` — matches the updated backend contract.
|
||||
- `job-tracker-ui/src/correspondence-gmail-import.test.tsx` — proves the UI flow end to end at component level.
|
||||
@@ -6,34 +6,59 @@ files:
|
||||
- job-tracker-ui/src/components/Correspondence.tsx
|
||||
- job-tracker-ui/src/types.ts
|
||||
- job-tracker-ui/src/correspondence-gmail-import.test.tsx
|
||||
observability_surfaces:
|
||||
- job-tracker-ui/src/components/Correspondence.tsx Gmail tab linked-thread state
|
||||
- /gmail/job-candidates requests with queryOverride
|
||||
- /gmail/refresh-linked-threads workspace refresh requests
|
||||
- job-tracker-ui/src/correspondence-gmail-import.test.tsx
|
||||
verification:
|
||||
- npm ci (job-tracker-ui)
|
||||
- CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx
|
||||
---
|
||||
|
||||
Wired the job-aware Gmail matching contract into the actual job workspace UI.
|
||||
Wired the job-aware Gmail matching contract into the actual job workspace UI and completed the live linked-thread refresh loop.
|
||||
|
||||
## What changed
|
||||
|
||||
What changed:
|
||||
- `job-tracker-ui/src/types.ts`
|
||||
- added frontend contracts for job-aware Gmail matches, match reasons, import results, and enriched correspondence metadata
|
||||
- added frontend contracts for job-aware Gmail matches, linked-thread refresh results, import results, and enriched correspondence metadata
|
||||
- `job-tracker-ui/src/components/JobDetailsDialog.tsx`
|
||||
- now passes the loaded `job` into `Correspondence` so the Gmail tab can stay job-aware without another job fetch
|
||||
- passes the loaded `job` into `Correspondence` so the Gmail tab can stay job-aware without another job fetch
|
||||
- `job-tracker-ui/src/components/Correspondence.tsx`
|
||||
- replaced client-side Gmail ranking as the primary workflow
|
||||
- Gmail tab now calls `/gmail/job-candidates`
|
||||
- shows confidence, score, match reasons, and already-linked state
|
||||
- preserves manual query override via the same job-aware endpoint
|
||||
- refreshes correspondence plus Gmail candidate state after single-message and thread imports
|
||||
- automatically calls `/gmail/refresh-linked-threads` when the job already has linked Gmail thread ids
|
||||
- refreshes correspondence plus Gmail candidate state after single-message, thread, and linked-thread refresh actions
|
||||
- renders persisted Gmail metadata (`ExternalThreadId`, `ExternalFrom`, `ExternalTo`) in the correspondence view
|
||||
- shows linked-thread freshness/import summary inside the Gmail area
|
||||
- `job-tracker-ui/src/correspondence-gmail-import.test.tsx`
|
||||
- verifies ranked Gmail suggestions render with visible reasons/confidence
|
||||
- verifies single-message import refreshes the same job’s correspondence view
|
||||
- verifies automatic linked-thread refresh shows a later Gmail reply without manual re-import
|
||||
- verifies manual search override is sent as `queryOverride`
|
||||
|
||||
Verification:
|
||||
## Verification
|
||||
|
||||
- frontend dependencies were installed with `npm ci` in `job-tracker-ui`
|
||||
- focused React test passed:
|
||||
- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx`
|
||||
|
||||
Notes:
|
||||
- The Gmail tab now treats the backend as the source of truth for ranking while keeping the manual search field as a fallback override.
|
||||
## Verification Evidence
|
||||
|
||||
| # | Command | Exit Code | Verdict | Duration |
|
||||
|---|---------|-----------|---------|----------|
|
||||
| 1 | `npm ci` (job-tracker-ui) | 0 | ✅ pass | not recorded |
|
||||
| 2 | `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx` | 0 | ✅ pass | ~2.8s |
|
||||
|
||||
## Diagnostics
|
||||
|
||||
- Open a job with imported Gmail correspondence and inspect the Gmail tab chips: linked-thread count, last refresh summary, and Gmail connection `lastSyncedAt` show whether the workspace considers the thread live.
|
||||
- Watch network traffic for `GET /gmail/job-candidates` and `POST /gmail/refresh-linked-threads` to confirm the workspace distinguishes ranked suggestions from already-linked thread refresh.
|
||||
- Inspect rendered correspondence chips (`Thread ...`, `From ...`, `To ...`) to verify imported Gmail metadata survived the round trip from persistence to UI.
|
||||
- Read `job-tracker-ui/src/correspondence-gmail-import.test.tsx` for the durable automated proof of manual query override plus no-manual-reimport continuity.
|
||||
|
||||
## Notes
|
||||
|
||||
The Gmail tab now treats the backend as the source of truth for ranking while keeping the manual search field as a fallback override, and it refreshes known linked threads once per loaded job/thread-set automatically to avoid re-import loops.
|
||||
|
||||
Reference in New Issue
Block a user