From 5ee30e3ab5c0be9369ee88030169c51264cce900 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Tue, 24 Mar 2026 12:14:31 +0100 Subject: [PATCH] chore(M001/S01): auto-commit after plan-slice --- .gsd/DECISIONS.md | 1 + .gsd/journal/2026-03-24.jsonl | 5 ++ .gsd/milestones/M001/slices/S01/S01-PLAN.md | 77 +++++++------------ .../M001/slices/S01/tasks/T01-PLAN.md | 44 ++++++----- .../M001/slices/S01/tasks/T02-PLAN.md | 51 ++++++------ .../M001/slices/S01/tasks/T03-PLAN.md | 54 ------------- 6 files changed, 81 insertions(+), 151 deletions(-) delete mode 100644 .gsd/milestones/M001/slices/S01/tasks/T03-PLAN.md diff --git a/.gsd/DECISIONS.md b/.gsd/DECISIONS.md index 447e1d4..64e9f27 100644 --- a/.gsd/DECISIONS.md +++ b/.gsd/DECISIONS.md @@ -13,3 +13,4 @@ | D005 | M001 | roadmap | First milestone focus | Prioritize Gmail import quality and AI draft quality before broader expansion | The user identified Gmail import and AI drafts as the weakest current areas and the first bar for daily use. | Yes — if execution proves another blocker is more fundamental | collaborative | | 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 | diff --git a/.gsd/journal/2026-03-24.jsonl b/.gsd/journal/2026-03-24.jsonl index ec99bf7..4ad96fa 100644 --- a/.gsd/journal/2026-03-24.jsonl +++ b/.gsd/journal/2026-03-24.jsonl @@ -23,3 +23,8 @@ {"ts":"2026-03-24T11:07:25.846Z","flowId":"e9654b40-266b-4b80-8bc0-81ad865b7163","seq":2,"eventType":"dispatch-match","rule":"rewrite-docs (override gate)","data":{"unitType":"rewrite-docs","unitId":"M001/S01"}} {"ts":"2026-03-24T11:07:25.852Z","flowId":"e9654b40-266b-4b80-8bc0-81ad865b7163","seq":3,"eventType":"unit-start","data":{"unitType":"rewrite-docs","unitId":"M001/S01"}} {"ts":"2026-03-24T11:11:32.454Z","flowId":"e9654b40-266b-4b80-8bc0-81ad865b7163","seq":4,"eventType":"unit-end","data":{"unitType":"rewrite-docs","unitId":"M001/S01","status":"completed","artifactVerified":true},"causedBy":{"flowId":"e9654b40-266b-4b80-8bc0-81ad865b7163","seq":3}} +{"ts":"2026-03-24T11:11:32.813Z","flowId":"e9654b40-266b-4b80-8bc0-81ad865b7163","seq":5,"eventType":"iteration-end","data":{"iteration":2}} +{"ts":"2026-03-24T11:11:32.813Z","flowId":"a8498eb1-9aa2-41cf-98d5-c908c31835fb","seq":1,"eventType":"iteration-start","data":{"iteration":3}} +{"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}} diff --git a/.gsd/milestones/M001/slices/S01/S01-PLAN.md b/.gsd/milestones/M001/slices/S01/S01-PLAN.md index cfece7b..ac2d02d 100644 --- a/.gsd/milestones/M001/slices/S01/S01-PLAN.md +++ b/.gsd/milestones/M001/slices/S01/S01-PLAN.md @@ -1,17 +1,16 @@ # S01: Smarter Gmail import and matching -**Goal:** Make Gmail import job-aware enough that a user reviewing one imported job sees the right candidate messages/threads first, can import a complete thread when that is the safer choice, and then trust the linked correspondence to stay current automatically when new Gmail replies arrive or when the user sends a reply from Gmail. -**Demo:** From a job opened in the workspace, the user connects Gmail, sees ranked thread/message suggestions built from that job’s company/recruiter context, imports either one message or a full thread, and then sees later inbound or user-sent replies from that linked Gmail thread appear on the same job without needing to manually re-pull or re-import the thread. +**Goal:** Finish S01 by turning the existing job-aware Gmail import flow into a live linked-thread continuity loop for one job workspace. +**Demo:** From a job opened in the workspace, the user connects Gmail, sees ranked thread/message suggestions for that job, imports a full thread, and later sees new inbound or user-sent Gmail replies land on the same job automatically through the linked-thread refresh flow without re-importing the thread manually. -S01 directly advances active requirements **R001**, **R002**, and **R010**. The work remains backend-first because the highest risk is now continuity, not just initial matching: if ranking, thread identity, and linked-thread refresh stay shallow, the UI can look nicer while the real correspondence loop still feels untrustworthy. The completed tasks below established the initial matching/import contract; the remaining work extends that contract into full-thread continuity so downstream drafting and tracking slices can rely on a living thread instead of a one-time import snapshot. +S01 still owns active requirement **R002** and materially supports **R010**. The earlier groundwork already exists in code: `GET /api/gmail/job-candidates`, enriched correspondence metadata, and the ranked `Correspondence` Gmail UI are present. The remaining slice risk is now narrower but higher value: thread continuity. I am therefore grouping the remaining work into two tasks only. The first task closes the backend/runtime contract for linked-thread refresh and failure diagnosis. The second task wires that refresh into the actual job workspace so the user experiences continuity instead of a one-time import snapshot. This keeps each executor task within one fresh context window while still making every completed task produce visible user progress. ## Must-Haves -- A job-scoped Gmail matching API ranks candidate messages/threads for a specific `JobApplication` using company, recruiter, title, and existing correspondence signals rather than a generic inbox search alone. -- Gmail import persists enough metadata to preserve message/thread identity, expose already-imported state, and reduce duplicate cleanup when the user imports single messages or whole threads into a job. -- The system can import an entire Gmail thread as a job-linked correspondence history, not just isolated messages, while keeping message ordering and duplicate protection intact. -- Already-linked Gmail threads can refresh automatically so new inbound messages and user-sent replies from Gmail show up on the job without requiring a manual re-import flow. -- The job workspace `Correspondence` tab shows ranked Gmail suggestions with match reasons/confidence, keeps a manual search override, and refreshes the visible job-linked correspondence list after import or linked-thread sync. +- Linked Gmail thread refresh works from already-imported thread ids stored on job correspondence and imports only newly discovered messages into that same job. +- The refresh flow preserves duplicate safety for both inbound recruiter messages and user-sent Gmail replies. +- The job workspace surfaces linked-thread freshness clearly enough that the user can tell the thread is live, not a stale snapshot. +- The slice verification proves both the backend contract and the UI continuity loop in named test files plus one real Gmail UAT pass. ## Proof Level @@ -23,65 +22,43 @@ S01 directly advances active requirements **R001**, **R002**, and **R010**. The - `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` - `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx` -- Failure-path inspection: hit `api/gmail/status` while disconnected; exercise the job-scoped Gmail matching endpoint with an invalid or inaccessible `jobApplicationId`; and exercise the linked-thread refresh path with an unknown thread id or stale sync cursor. Confirm the API exposes connection state plus clear validation/not-found errors without leaking Gmail content. -- Manual UAT: run the app, connect a real Gmail account, open one job in the workspace, confirm ranked Gmail suggestions appear before manual search, import one message and one full thread, send or receive another reply in Gmail on that linked thread, and verify the new reply appears on that same job without a manual re-import step and without any auto-send behavior. +- Manual UAT: connect a real Gmail account, open one job workspace, import a full Gmail thread, send or receive a later reply in Gmail on that same thread, reopen or refresh the workspace, and confirm the new message appears on that job without using the import action again. ## Observability / Diagnostics -- Runtime signals: ranked match reasons/confidence returned per candidate, import result counts for imported/skipped messages, linked-thread sync result counts, newest synced Gmail history marker/cursor per linked thread, and existing request logs with trace ids around Gmail endpoints. -- Inspection surfaces: `api/gmail/status`, the job-scoped Gmail matching and linked-thread refresh responses in `JobTrackerApi/Controllers/GmailController.cs`, any Gmail sync/background service introduced for linked threads, the correspondence list in `job-tracker-ui/src/components/Correspondence.tsx`, and backend/frontend tests covering duplicate and sync failure paths. -- Failure visibility: Gmail connection state, empty/noisy candidate lists, duplicate-skip counts, stale thread-sync state, and per-candidate imported/already-linked flags should remain visible enough to tell whether failure came from query construction, Gmail retrieval, dedupe, sync cursor handling, or UI wiring. -- Redaction constraints: never expose OAuth tokens or full message bodies in logs; diagnostics should stay at message metadata, ranking reasons, sync counts, and import results. +- Runtime signals: linked-thread refresh result counts per job (`threadsChecked`, `imported`, `skipped`), per-thread duplicate-safe status, and `lastSyncedAt`/refresh timestamps exposed through the Gmail status or refresh contract. +- Inspection surfaces: `JobTrackerApi/Controllers/GmailController.cs`, `JobTrackerApi/Services/GmailOAuthService.cs`, `JobTrackerApi.Tests/GmailControllerTests.cs`, and the linked-thread state rendered in `job-tracker-ui/src/components/Correspondence.tsx`. +- Failure visibility: invalid job/thread lookup, disconnected Gmail state, empty linked-thread sets, Gmail fetch failures, and duplicate-only refresh runs should each produce distinct API/UI outcomes rather than a generic silent no-op. +- Redaction constraints: diagnostics must stay at thread/message ids, counts, timestamps, and sender labels; never log OAuth secrets or full Gmail bodies. ## Integration Closure -- Upstream surfaces consumed: `JobTrackerApi/Services/GmailOAuthService.cs`, `Data/JobTrackerContext.cs`, `Models/JobApplication.cs`, `Models/Company.cs`, `Models/Correspondence.cs`, `job-tracker-ui/src/components/JobDetailsDialog.tsx`, and `job-tracker-ui/src/components/Correspondence.tsx`. -- New wiring introduced in this slice: a job-aware Gmail candidate contract in the API, correspondence persistence that retains Gmail thread/message identity, and job-context-driven rendering in the workspace Gmail import UI. -- Additional wiring now required by the override: a linked-thread refresh mechanism that can detect new Gmail replies for already-imported threads and merge them into the job’s correspondence timeline without forcing the user to re-run import manually. -- What remains before the milestone is truly usable end-to-end: later slices still need to consume the imported correspondence for stronger drafting and daily workflow surfaces, but Gmail import itself must now be trustworthy both at first import and during ongoing thread updates. +- Upstream surfaces consumed: `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`. +- New wiring introduced in this slice: a job-scoped linked-thread refresh contract plus automatic workspace refresh behavior for already-linked Gmail threads. +- What remains before the milestone is truly usable end-to-end: Gmail import continuity should be complete after this slice; later slices still consume that trusted correspondence context for drafting and daily workflow surfaces. ## Tasks -- [x] **T01: Add a job-aware Gmail matching contract and backend ranking tests** `est:3h` - - Why: The slice risk sits in matching quality, so the backend needs to own candidate discovery, ranking, and duplicate awareness before the UI can present trustworthy suggestions. - - Files: `JobTrackerApi/Controllers/GmailController.cs`, `JobTrackerApi/Services/GmailOAuthService.cs`, `JobTrackerApi.Tests/GmailControllerTests.cs` - - Do: Add a job-scoped Gmail candidate endpoint that loads the owned job plus company context, builds multiple Gmail queries from recruiter/company/title/correspondence signals, dedupes messages and groups them by thread, and returns ranked suggestions with match reasons, confidence inputs, and already-imported flags; cover the contract with focused controller/service tests instead of leaving ranking in `Correspondence.tsx`. +- [ ] **T01: Add linked Gmail thread refresh to the backend contract** `est:4h` + - Why: R002 is still unmet until the app can turn a one-time import into continuing thread history for the same job. + - Files: `JobTrackerApi/Controllers/GmailController.cs`, `JobTrackerApi/Services/GmailOAuthService.cs`, `JobTrackerApi.Tests/GmailControllerTests.cs`, `JobTrackerApi/Program.cs` + - Do: Add a job-scoped refresh path that reads already-linked `ExternalThreadId` values for one owned job, fetches messages for those known Gmail threads, imports only unseen message ids into the same job, updates refresh timestamps/status, and exposes a clear duplicate-safe result contract. Follow D008: use bounded refresh over known imported thread ids rather than inbox-wide Gmail watch/history infrastructure. - Verify: `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` - - Done when: the API can return ranked Gmail candidates for one job with explicit reasons and duplicate state, and backend tests prove owned-job lookup plus ranking/dedupe behavior. -- [x] **T02: Persist Gmail thread metadata and harden import continuity** `est:3h` - - Why: Smarter matching is not enough if imported correspondence still loses thread identity or forces downstream slices to re-derive sender/thread information later. - - Files: `Models/Correspondence.cs`, `JobTrackerApi/Controllers/GmailController.cs`, `JobTrackerApi/Controllers/CorrespondenceController.cs`, `JobTrackerApi/Program.cs`, `JobTrackerApi.Tests/GmailControllerTests.cs` - - Do: Extend correspondence persistence and import logic so Gmail imports store thread/message identity plus sender/recipient metadata, update import responses and duplicate handling to reflect imported vs skipped outcomes clearly, and update startup schema guards so older SQLite/MySQL dev databases remain bootable when the new fields land. - - Verify: `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` - - Done when: imported correspondence keeps enough Gmail metadata for thread continuity and dedupe, compatibility guards are planned with the schema change, and tests prove single-message/thread imports behave correctly on repeat imports. -- [x] **T03: Wire ranked Gmail suggestions into the job workspace UI** `est:3h` - - Why: The slice is only complete when the user can act on the smarter backend contract inside the actual job workspace rather than through a generic inbox search UI. - - Files: `job-tracker-ui/src/components/JobDetailsDialog.tsx`, `job-tracker-ui/src/components/Correspondence.tsx`, `job-tracker-ui/src/types.ts`, `job-tracker-ui/src/correspondence-gmail-import.test.tsx` - - Do: Pass job context from `JobDetailsDialog` into `Correspondence`, replace client-side primary ranking with the server-provided candidate contract, show match reasons/confidence/import state and thread actions, keep manual query/search as a fallback override, and add a React test that proves ranked suggestions and import refresh behavior in the dialog. + - Done when: the API can refresh linked Gmail threads for one job, import new inbound or sent replies without duplicate re-imports, and tests prove success, duplicate-only, disconnected, and invalid-job cases. +- [ ] **T02: Surface live Gmail thread continuity in the job workspace** `est:3h` + - Why: The slice is only complete when the user can see the linked thread stay current inside the real `Correspondence` workspace flow. + - Files: `job-tracker-ui/src/components/Correspondence.tsx`, `job-tracker-ui/src/types.ts`, `job-tracker-ui/src/correspondence-gmail-import.test.tsx`, `job-tracker-ui/src/components/JobDetailsDialog.tsx` + - Do: Wire the workspace to call the new linked-thread refresh contract automatically when appropriate for already-linked threads, show refresh/loading/freshness state in the Gmail area and correspondence list, and prove in the React test that a newly synced Gmail reply appears on the job without using the import action again. - Verify: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx` - - Done when: the Correspondence Gmail tab opens on ranked job-aware suggestions, still supports manual search overrides, refreshes the correspondence list after import, and the new UI test passes. -- [ ] **T04: Add linked-thread refresh so imported Gmail threads stay current** `est:4h` - - Why: The user’s trust bar is no longer just “can I import the right thread once?” but “does that thread continue to reflect reality after I reply or receive another response in Gmail?” - - Files: `JobTrackerApi/Controllers/GmailController.cs`, `JobTrackerApi/Services/GmailOAuthService.cs`, `JobTrackerApi/Program.cs`, `Models/Correspondence.cs`, `JobTrackerApi.Tests/GmailControllerTests.cs` - - Do: Add a linked-thread refresh path that can pull new Gmail messages for already-imported job threads using stored Gmail thread/message identity, dedupe against existing correspondence, import newly discovered inbound and sent replies into the same job, and expose enough sync status for the UI to know whether the thread is current. - - Verify: `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` - - Done when: an already-linked Gmail thread can be refreshed without manual re-import, new sent/inbound replies land as correspondence on the same job, and focused tests prove dedupe plus stale-thread handling. -- [ ] **T05: Surface automatic Gmail thread continuity in the workspace UI** `est:3h` - - Why: Even with backend sync, the slice will still feel broken if the user cannot tell that a linked thread is auto-updating or whether a newly sent Gmail reply has already been incorporated. - - Files: `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` - - Do: Update the Correspondence tab to distinguish one-time suggestions from already-linked live threads, show last-sync or updating state, trigger automatic refresh on load and after manual job actions where appropriate, and extend the React test so a newly synced Gmail reply appears in the job correspondence list without a fresh import flow. - - Verify: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx` - - Done when: the user can see that a thread is linked/live, newly synced replies appear in the workspace automatically or on lightweight refresh, and the UI test covers the no-manual-reimport continuity path. + - Done when: the job workspace clearly distinguishes ranked import suggestions from already-linked live threads, performs linked-thread refresh through the new backend contract, and the UI test covers the no-manual-reimport continuity path. ## Files Likely Touched - `JobTrackerApi/Controllers/GmailController.cs` - `JobTrackerApi/Services/GmailOAuthService.cs` -- `Models/Correspondence.cs` -- `JobTrackerApi/Controllers/CorrespondenceController.cs` -- `JobTrackerApi/Program.cs` - `JobTrackerApi.Tests/GmailControllerTests.cs` -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` +- `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` diff --git a/.gsd/milestones/M001/slices/S01/tasks/T01-PLAN.md b/.gsd/milestones/M001/slices/S01/tasks/T01-PLAN.md index de6418f..243bd27 100644 --- a/.gsd/milestones/M001/slices/S01/tasks/T01-PLAN.md +++ b/.gsd/milestones/M001/slices/S01/tasks/T01-PLAN.md @@ -1,52 +1,54 @@ --- estimated_steps: 4 -estimated_files: 3 +estimated_files: 4 skills_used: - - best-practices + - aspnet-core - test --- -# T01: Add a job-aware Gmail matching contract and backend ranking tests +# T01: Add linked Gmail thread refresh to the backend contract **Slice:** S01 — Smarter Gmail import and matching **Milestone:** M001 ## Description -Build the backend contract that makes Gmail import job-aware. The executor should keep the existing OAuth flow, but add a new job-scoped candidate discovery path that uses the actual `JobApplication`, `Company`, recruiter, and existing correspondence data to rank likely Gmail messages/threads for one job. This task closes the main risk in R002 and anchors the import loop on the imported job from R001 instead of a generic inbox search. +Finish the backend half of S01 by making already-imported Gmail threads refreshable for a single job. The executor should build on the existing job-aware matching/import flow already present in `GmailController`, keep the scope bounded to known linked thread ids, and return a clear refresh result that distinguishes new imports from duplicate-only refreshes and disconnected/failure states. ## Steps -1. Inspect the current Gmail controller/service contract and define a job-scoped candidate response shape in `JobTrackerApi/Controllers/GmailController.cs` that includes ranked thread/message suggestions, match reasons, confidence or score inputs, and already-imported state. -2. Implement the backend matching flow in `JobTrackerApi/Controllers/GmailController.cs` and `JobTrackerApi/Services/GmailOAuthService.cs`: load the owned job with company context, derive multiple Gmail queries from recruiter/company/title/correspondence signals, fetch/dedupe results, group by thread, and rank them server-side. -3. Preserve the existing generic Gmail endpoints, but make the new job-aware endpoint the primary contract for this slice so the UI can consume it without recreating ranking heuristics in the browser. -4. Expand `JobTrackerApi.Tests/GmailControllerTests.cs` to cover owned-job lookup, empty/duplicate behavior, and ranked candidate output with explicit reasons instead of only the current missing-message-ids case. +1. Inspect the current Gmail import code in `JobTrackerApi/Controllers/GmailController.cs`, the Gmail API wrapper in `JobTrackerApi/Services/GmailOAuthService.cs`, and the current `GmailControllerTests` coverage to identify the smallest thread-refresh contract that can satisfy R002. +2. Add a job-scoped linked-thread refresh path in `JobTrackerApi/Controllers/GmailController.cs` that loads one owned job, gathers distinct linked `ExternalThreadId` values from its correspondence, fetches Gmail messages for those known threads, and imports only unseen `ExternalMessageId` values into the same job. +3. Extend `JobTrackerApi/Services/GmailOAuthService.cs` with the thread-level retrieval helper(s) needed by that controller path, and keep diagnostics bounded to counts, ids, timestamps, and connection state rather than full message bodies. +4. Expand `JobTrackerApi.Tests/GmailControllerTests.cs` to cover successful refresh with a new inbound reply, successful refresh with a new user-sent reply, duplicate-only refresh, disconnected Gmail state, and invalid/inaccessible job handling. ## Must-Haves -- [ ] The new backend contract is scoped to one owned `JobApplication` and never bypasses the EF ownership filters already enforced in `Data/JobTrackerContext.cs`. -- [ ] Ranked candidates include enough detail for the UI to explain why a message or thread was suggested and whether it was already imported. -- [ ] Tests assert the new contract and failure/duplicate cases, not just the happy path. +- [ ] The refresh path operates on already-linked Gmail thread ids for one owned job; it does not introduce inbox-wide watch/history infrastructure for this slice. +- [ ] New Gmail replies import into the same `JobApplication` with existing duplicate protection based on `ExternalMessageId`. +- [ ] The refresh contract exposes enough status to tell whether the run imported new messages, found only duplicates, or could not run because Gmail/job state was invalid. ## Verification - `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` -- Confirm the updated tests cover job-scoped matching, duplicate flags, and invalid or missing job inputs. +- Confirm the updated tests assert at least one new-message refresh, one duplicate-only refresh, and one failure-path outcome. ## Observability Impact -- Signals added/changed: per-candidate match reasons/score inputs and explicit already-imported flags in the job-scoped Gmail response. -- How a future agent inspects this: hit the job-aware Gmail endpoint in `JobTrackerApi/Controllers/GmailController.cs` or read the focused assertions in `JobTrackerApi.Tests/GmailControllerTests.cs`. -- Failure state exposed: the response or tests should make it obvious whether failure came from job lookup, Gmail candidate retrieval, or ranking/dedupe. +- Signals added/changed: linked-thread refresh result counts, last refresh/sync timestamp updates, and explicit disconnected/invalid-job outcomes. +- How a future agent inspects this: read the refresh DTO and controller action in `JobTrackerApi/Controllers/GmailController.cs` and the focused assertions in `JobTrackerApi.Tests/GmailControllerTests.cs`. +- Failure state exposed: the API should make it obvious whether nothing happened because there were no linked threads, Gmail was disconnected, every message was already imported, or the job was not accessible. ## Inputs -- `JobTrackerApi/Controllers/GmailController.cs` — existing Gmail API surface and import behavior. -- `JobTrackerApi/Services/GmailOAuthService.cs` — existing Gmail list/detail retrieval and current N+1 summary hydration. -- `JobTrackerApi.Tests/GmailControllerTests.cs` — current minimal Gmail controller test coverage. +- `JobTrackerApi/Controllers/GmailController.cs` — current job-aware Gmail matching and import endpoints. +- `JobTrackerApi/Services/GmailOAuthService.cs` — current Gmail list/detail helpers and connection status updates. +- `JobTrackerApi.Tests/GmailControllerTests.cs` — existing Gmail controller test coverage. +- `JobTrackerApi/Program.cs` — existing Gmail/correspondence runtime wiring and compatibility guards. ## Expected Output -- `JobTrackerApi/Controllers/GmailController.cs` — adds the job-scoped Gmail matching endpoint and ranking contract. -- `JobTrackerApi/Services/GmailOAuthService.cs` — supports the new candidate discovery flow. -- `JobTrackerApi.Tests/GmailControllerTests.cs` — proves the new backend matching behavior and duplicate/failure paths. +- `JobTrackerApi/Controllers/GmailController.cs` — adds the linked-thread refresh contract for one job. +- `JobTrackerApi/Services/GmailOAuthService.cs` — adds thread-fetch support used by refresh. +- `JobTrackerApi.Tests/GmailControllerTests.cs` — proves refresh success, duplicate-only, and failure-path behavior. +- `JobTrackerApi/Program.cs` — updates any wiring needed for the new refresh flow or diagnostics. diff --git a/.gsd/milestones/M001/slices/S01/tasks/T02-PLAN.md b/.gsd/milestones/M001/slices/S01/tasks/T02-PLAN.md index 974856b..18282c2 100644 --- a/.gsd/milestones/M001/slices/S01/tasks/T02-PLAN.md +++ b/.gsd/milestones/M001/slices/S01/tasks/T02-PLAN.md @@ -1,56 +1,55 @@ --- estimated_steps: 4 -estimated_files: 5 +estimated_files: 4 skills_used: - - best-practices + - react-best-practices - test --- -# T02: Persist Gmail thread metadata and harden import continuity +# T02: Surface live Gmail thread continuity in the job workspace **Slice:** S01 — Smarter Gmail import and matching **Milestone:** M001 ## Description -Make imported Gmail correspondence durable enough to support real thread continuity. This task extends persistence and import handling so that once the user chooses a suggested Gmail message or thread, the app keeps the metadata needed for dedupe, timeline continuity, and later reply/follow-up context instead of collapsing everything down to a bare message body. +Complete S01 in the UI by turning the existing ranked Gmail import tab into a live linked-thread workspace. The executor should preserve the current ranked suggestion/import flow, then layer in automatic refresh for already-linked Gmail threads so the user sees new replies appear on the same job without using the import action again. ## Steps -1. Extend `Models/Correspondence.cs` so Gmail imports can retain thread/message identity and sender/recipient metadata that downstream slices can trust. -2. Update `JobTrackerApi/Controllers/GmailController.cs` import paths and `JobTrackerApi/Controllers/CorrespondenceController.cs` response behavior so imported records populate and expose the new Gmail metadata while keeping the existing no-auto-send boundary intact. -3. Update `JobTrackerApi/Program.cs` startup compatibility guards for SQLite/MySQL so older dev databases can gain the new correspondence columns without breaking local startup. -4. Add or extend `JobTrackerApi.Tests/GmailControllerTests.cs` assertions for repeat single-message imports, repeat thread imports, and import result payloads that distinguish imported vs skipped work. +1. Inspect the current Gmail UI in `job-tracker-ui/src/components/Correspondence.tsx`, the host wiring in `job-tracker-ui/src/components/JobDetailsDialog.tsx`, and the existing component test in `job-tracker-ui/src/correspondence-gmail-import.test.tsx`. +2. Update `job-tracker-ui/src/types.ts` and `job-tracker-ui/src/components/Correspondence.tsx` to consume the new backend linked-thread refresh contract and track refresh/loading/freshness state separately from the ranked import-suggestion state. +3. Wire `job-tracker-ui/src/components/Correspondence.tsx` so already-linked threads refresh automatically at the right moment in the job workspace flow, refresh the rendered correspondence list after sync, and make the linked/live state legible in the UI. +4. Extend `job-tracker-ui/src/correspondence-gmail-import.test.tsx` to prove the continuity path: after a thread is already linked, a refresh brings in a later Gmail reply and renders it on the same job without another import action. ## Must-Haves -- [ ] Imported Gmail correspondence keeps message/thread identity plus sender/recipient metadata in `Models/Correspondence.cs` rather than forcing later slices to re-derive it. -- [ ] Import endpoints clearly report imported vs skipped outcomes and continue deduping on repeat imports. -- [ ] Runtime schema guards in `JobTrackerApi/Program.cs` are updated alongside the model change so legacy/dev databases remain bootable. +- [ ] The UI keeps ranked job-aware Gmail suggestions for first import while clearly distinguishing already-linked live threads from new import candidates. +- [ ] Linked-thread refresh happens through the new backend contract and updates the visible correspondence list without requiring the user to click an import button again. +- [ ] The React test proves the continuity path, not just the original ranked-import path. ## Verification -- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` -- Confirm the tests prove duplicate single-message import handling, duplicate thread import handling, and the enriched import payload contract. +- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx` +- Manually inspect the Correspondence dialog to confirm linked-thread state, refresh/loading state, and the newly synced message all appear in the workspace. ## Observability Impact -- Signals added/changed: persisted Gmail metadata on correspondence records and explicit imported/skipped counts from Gmail import actions. -- How a future agent inspects this: inspect `Models/Correspondence.cs`, the import payloads in `JobTrackerApi/Controllers/GmailController.cs`, and startup column guards in `JobTrackerApi/Program.cs`. -- Failure state exposed: repeat imports should now reveal whether the issue is dedupe, missing metadata persistence, or schema mismatch on startup. +- Signals added/changed: visible linked/live thread state, refresh progress/freshness state, and clearer no-new-messages vs failure feedback. +- How a future agent inspects this: run `job-tracker-ui/src/correspondence-gmail-import.test.tsx` and inspect the Gmail area in `job-tracker-ui/src/components/Correspondence.tsx`. +- Failure state exposed: the UI should distinguish refresh failure, disconnected Gmail, no linked threads, and successful refresh with zero new messages. ## Inputs -- `JobTrackerApi/Controllers/GmailController.cs` — T01 job-aware matching/import contract and existing import behavior. -- `Models/Correspondence.cs` — current correspondence persistence model. -- `JobTrackerApi/Controllers/CorrespondenceController.cs` — current correspondence list/create/delete surface. -- `JobTrackerApi/Program.cs` — runtime schema repair path that must stay aligned with new columns. -- `JobTrackerApi.Tests/GmailControllerTests.cs` — backend Gmail coverage expanded in T01. +- `job-tracker-ui/src/components/Correspondence.tsx` — current ranked Gmail import UI. +- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — host dialog that owns the job workspace. +- `job-tracker-ui/src/types.ts` — frontend Gmail and correspondence contracts. +- `job-tracker-ui/src/correspondence-gmail-import.test.tsx` — existing Gmail import component test. +- `JobTrackerApi/Controllers/GmailController.cs` — T01 backend refresh contract consumed by the UI. ## Expected Output -- `Models/Correspondence.cs` — persists Gmail message/thread and sender/recipient metadata. -- `JobTrackerApi/Controllers/GmailController.cs` — populates and reports the new import metadata cleanly. -- `JobTrackerApi/Controllers/CorrespondenceController.cs` — exposes the enriched correspondence records back to the UI. -- `JobTrackerApi/Program.cs` — keeps legacy/dev DB startup safe after the schema change. -- `JobTrackerApi.Tests/GmailControllerTests.cs` — proves continuity and duplicate-import behavior. +- `job-tracker-ui/src/components/Correspondence.tsx` — renders linked-thread refresh state and continuity behavior. +- `job-tracker-ui/src/types.ts` — matches the backend refresh contract. +- `job-tracker-ui/src/correspondence-gmail-import.test.tsx` — proves the no-manual-reimport continuity path. +- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — includes any required wiring for the refreshed workspace flow. diff --git a/.gsd/milestones/M001/slices/S01/tasks/T03-PLAN.md b/.gsd/milestones/M001/slices/S01/tasks/T03-PLAN.md deleted file mode 100644 index 5ab0539..0000000 --- a/.gsd/milestones/M001/slices/S01/tasks/T03-PLAN.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -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.