chore(M001/S01): auto-commit after plan-slice

This commit is contained in:
Connor
2026-03-24 09:25:41 +01:00
parent 0fec2aad4d
commit 6b7323955e
6 changed files with 239 additions and 0 deletions
+1
View File
@@ -11,3 +11,4 @@
| D003 | M001 | scope | Primary user | Individual job seeker | The product is designed for individuals managing their own search, not recruiter or team workflows. | Yes — if product direction changes later | collaborative | | D003 | M001 | scope | Primary user | Individual job seeker | The product is designed for individuals managing their own search, not recruiter or team workflows. | Yes — if product direction changes later | collaborative |
| D004 | M001 | pattern | Daily navigation hierarchy | Job table first, then follow-up/dashboard, then individual job workspace | The user explicitly described this as the intended control flow for daily use. | Yes — if real usage disproves the hierarchy | collaborative | | D004 | M001 | pattern | Daily navigation hierarchy | Job table first, then follow-up/dashboard, then individual job workspace | The user explicitly described this as the intended control flow for daily use. | Yes — if real usage disproves the hierarchy | collaborative |
| 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 | | 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/S01 planning | architecture | How S01 will improve Gmail import matching | Add a job-scoped backend Gmail matching contract and persist Gmail thread/message metadata on correspondence instead of relying on client-only scoring | Backend ranking can use owned job, company, recruiter, and prior correspondence context consistently, gives the UI explicit match reasons and duplicate state, and preserves thread continuity for later reply/follow-up slices without changing the no-auto-send boundary. | Yes — if real Gmail usage shows the ranking contract or stored metadata is insufficient | agent |
+5
View File
@@ -2,3 +2,8 @@
{"ts":"2026-03-24T08:18:12.498Z","flowId":"80cd8f39-4c60-4e08-98bb-5b177f964592","seq":2,"eventType":"dispatch-match","rule":"planning (no research, not S01) → research-slice","data":{"unitType":"research-slice","unitId":"M001/S01"}} {"ts":"2026-03-24T08:18:12.498Z","flowId":"80cd8f39-4c60-4e08-98bb-5b177f964592","seq":2,"eventType":"dispatch-match","rule":"planning (no research, not S01) → research-slice","data":{"unitType":"research-slice","unitId":"M001/S01"}}
{"ts":"2026-03-24T08:18:12.504Z","flowId":"80cd8f39-4c60-4e08-98bb-5b177f964592","seq":3,"eventType":"unit-start","data":{"unitType":"research-slice","unitId":"M001/S01"}} {"ts":"2026-03-24T08:18:12.504Z","flowId":"80cd8f39-4c60-4e08-98bb-5b177f964592","seq":3,"eventType":"unit-start","data":{"unitType":"research-slice","unitId":"M001/S01"}}
{"ts":"2026-03-24T08:21:44.843Z","flowId":"80cd8f39-4c60-4e08-98bb-5b177f964592","seq":4,"eventType":"unit-end","data":{"unitType":"research-slice","unitId":"M001/S01","status":"completed","artifactVerified":true},"causedBy":{"flowId":"80cd8f39-4c60-4e08-98bb-5b177f964592","seq":3}} {"ts":"2026-03-24T08:21:44.843Z","flowId":"80cd8f39-4c60-4e08-98bb-5b177f964592","seq":4,"eventType":"unit-end","data":{"unitType":"research-slice","unitId":"M001/S01","status":"completed","artifactVerified":true},"causedBy":{"flowId":"80cd8f39-4c60-4e08-98bb-5b177f964592","seq":3}}
{"ts":"2026-03-24T08:21:45.581Z","flowId":"80cd8f39-4c60-4e08-98bb-5b177f964592","seq":5,"eventType":"iteration-end","data":{"iteration":1}}
{"ts":"2026-03-24T08:21:45.581Z","flowId":"5d1e657f-dcfe-4e7d-9de8-02e081926e97","seq":1,"eventType":"iteration-start","data":{"iteration":2}}
{"ts":"2026-03-24T08:21:45.650Z","flowId":"5d1e657f-dcfe-4e7d-9de8-02e081926e97","seq":2,"eventType":"dispatch-match","rule":"planning → plan-slice","data":{"unitType":"plan-slice","unitId":"M001/S01"}}
{"ts":"2026-03-24T08:21:45.655Z","flowId":"5d1e657f-dcfe-4e7d-9de8-02e081926e97","seq":3,"eventType":"unit-start","data":{"unitType":"plan-slice","unitId":"M001/S01"}}
{"ts":"2026-03-24T08:25:40.911Z","flowId":"5d1e657f-dcfe-4e7d-9de8-02e081926e97","seq":4,"eventType":"unit-end","data":{"unitType":"plan-slice","unitId":"M001/S01","status":"completed","artifactVerified":true},"causedBy":{"flowId":"5d1e657f-dcfe-4e7d-9de8-02e081926e97","seq":3}}
@@ -0,0 +1,71 @@
# 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 them with clearer confidence signals, and ends up with job-linked correspondence that needs materially less cleanup.
**Demo:** From a job opened in the workspace, the user connects Gmail, sees ranked thread/message suggestions built from that jobs company/recruiter context, imports either one message or a full thread, and immediately sees the imported correspondence reflected on that job without any auto-send behavior.
S01 directly advances active requirements **R001** and **R002**, and it also lays the continuity foundation S04 will later rely on for **R010**. The work is split backend-first because the highest risk is the matching contract itself: if ranking, dedupe, and metadata stay client-only, the UI can look nicer while the import loop still feels untrustworthy. Once that contract exists, the frontend task can focus on presenting the ranked results cleanly instead of inventing its own heuristics.
## 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 job workspace `Correspondence` tab shows ranked Gmail suggestions with match reasons/confidence, keeps a manual search override, and refreshes the job-linked correspondence list after import.
## Proof Level
- This slice proves: integration
- Real runtime required: yes
- Human/UAT required: yes
## Verification
- `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`
- 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, and verify the imported correspondence appears on that same job with duplicates skipped and no send/apply automation.
## Observability / Diagnostics
- Runtime signals: ranked match reasons/confidence returned per candidate, import result counts for imported/skipped messages, and existing request logs with trace ids around Gmail endpoints.
- Inspection surfaces: `api/gmail/status`, the new job-scoped Gmail matching response in `JobTrackerApi/Controllers/GmailController.cs`, the correspondence list in `job-tracker-ui/src/components/Correspondence.tsx`, and backend/frontend tests covering duplicate and failure paths.
- Failure visibility: Gmail connection state, empty/noisy candidate lists, duplicate-skip counts, and per-candidate imported/already-linked flags should remain visible enough to tell whether failure came from query construction, Gmail retrieval, dedupe, or UI wiring.
- Redaction constraints: never expose OAuth tokens or full message bodies in logs; diagnostics should stay at message metadata, ranking reasons, and import counts.
## 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.
- 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 should be trustworthy after this slice.
## Tasks
- [ ] **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`.
- 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.
- [ ] **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.
- [ ] **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.
- 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.
## 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`
- `job-tracker-ui/src/components/Correspondence.tsx`
- `job-tracker-ui/src/types.ts`
- `job-tracker-ui/src/correspondence-gmail-import.test.tsx`
@@ -0,0 +1,52 @@
---
estimated_steps: 4
estimated_files: 3
skills_used:
- best-practices
- test
---
# T01: Add a job-aware Gmail matching contract and backend ranking tests
**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.
## 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.
## 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.
## 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.
## 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.
## 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.
## 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.
@@ -0,0 +1,56 @@
---
estimated_steps: 4
estimated_files: 5
skills_used:
- best-practices
- test
---
# T02: Persist Gmail thread metadata and harden import continuity
**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.
## 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.
## 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.
## 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.
## 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.
## 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.
## 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.
@@ -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 jobs 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.