diff --git a/.bg-shell/manifest.json b/.bg-shell/manifest.json index 0637a08..d270783 100644 --- a/.bg-shell/manifest.json +++ b/.bg-shell/manifest.json @@ -1 +1,16 @@ -[] \ No newline at end of file +[ + { + "id": "e426dd27", + "label": "jobtracker-api-dll", + "command": "dotnet /home/pi/development/JobTracker/.gsd/worktrees/M001/JobTrackerApi/bin/Debug/net9.0/JobTrackerApi.dll", + "cwd": "/home/pi/.gsd/projects/a40e97ae9e8f/worktrees/M001", + "ownerSessionFile": "/home/pi/.gsd/sessions/--home-pi-development-JobTracker--/2026-03-27T07-47-13-751Z_c7885c29-a870-487a-969c-29460b7a5881.jsonl", + "persistAcrossSessions": false, + "startedAt": 1774597942244, + "processType": "server", + "group": null, + "readyPattern": null, + "readyPort": 5202, + "pid": 1175981 + } +] \ No newline at end of file diff --git a/.gsd/event-log.jsonl b/.gsd/event-log.jsonl new file mode 100644 index 0000000..4a8406f --- /dev/null +++ b/.gsd/event-log.jsonl @@ -0,0 +1 @@ +{"cmd":"plan-slice","params":{"milestoneId":"M001","sliceId":"S06"},"ts":"2026-03-27T07:47:00.102Z","actor":"agent","hash":"ad7ae36d97e9c851","session_id":"96f47087-e006-4aa2-8147-1cc42da4374d"} diff --git a/.gsd/milestones/M001/M001-ROADMAP.md b/.gsd/milestones/M001/M001-ROADMAP.md index baa5613..7542fba 100644 --- a/.gsd/milestones/M001/M001-ROADMAP.md +++ b/.gsd/milestones/M001/M001-ROADMAP.md @@ -1,132 +1,15 @@ -# M001: Gmail and draft quality loop +# M001: M001: Gmail and draft quality loop -**Vision:** Turn the existing job tracker into a daily-use personal job-search workspace where Gmail import and AI drafting are strong enough to trust, while preserving manual control over all real-world sending and applying. +## Vision +Turn the existing job tracker into a daily-use personal job-search workspace where Gmail import and AI drafting are strong enough to trust, while preserving manual control over all real-world sending and applying. -## Success Criteria - -- User can import a job found elsewhere, generate a tailored CV and cover-letter package that feels specific enough to start from, and save/edit that package inside the job workspace. -- User can connect Gmail, import the right message or full thread into a job with less cleanup than before, and see the imported correspondence reflected in that job’s timeline/workspace. -- After a thread is linked to a job, later inbound messages and user-sent Gmail replies appear on that job automatically without requiring the user to re-import the thread manually. -- User can generate a follow-up or reply draft grounded in the imported correspondence and saved job/application context. -- The daily loop of job table → follow-up/dashboard → individual job workspace feels coherent and actionable for an individual user. -- No part of the milestone auto-sends email or auto-applies to jobs. - -## Key Risks / Unknowns - -- Gmail import may still feel unreliable if matching, whole-thread import, and linked-thread refresh clarity do not improve enough to reduce manual cleanup. -- AI draft quality may still feel generic even though the draft surfaces already exist. -- The workflow may remain fragmented if table/dashboard/job-detail changes do not land as one coherent loop. -- Real value may depend on live Gmail + AI + persisted job context wiring, not isolated endpoint improvements. -- Automatic Gmail thread refresh may be harder than one-time import if Gmail history/state handling is brittle or expensive. - -## Proof Strategy - -- Gmail import reliability and trust → retire in S01 by proving a user can connect Gmail, review likely messages or threads for a job, import the right correspondence into that job, and keep a linked thread current as new replies arrive. -- AI draft quality and usefulness → retire in S02 by proving imported job context plus profile/CV context produce tailored drafts the user can edit and save as actual working material. -- Reply/follow-up assistance grounded in real context → retire in S03 by proving imported correspondence and saved draft state feed a useful reply/follow-up drafting flow. -- Workflow coherence across daily surfaces → retire in S04 and S05 by proving the table/dashboard/job workspace work as one control loop and by re-checking the whole loop end-to-end. - -## Verification Classes - -- Contract verification: backend/frontend tests, artifact checks for new endpoints and UI flows, persisted state checks, import/draft wiring verification -- Integration verification: real Gmail OAuth/import plus live AI-service-backed draft generation exercised through the app -- Operational verification: repeated use of the workflow across auth/config/service boundaries without dangerous outbound automation -- UAT / human verification: whether Gmail import feels trustworthy, whether linked threads stay current automatically enough for real use, and whether drafts feel strong enough to start from - -## Milestone Definition of Done - -This milestone is complete only when all are true: - -- all slice deliverables are complete -- Gmail import, linked-thread continuity, correspondence state, and draft-generation surfaces are actually wired together -- the real browser entrypoint exists and is exercised through the table/dashboard/job loop -- success criteria are re-checked against live behavior, not just artifact presence -- final integrated acceptance scenarios pass - -## Requirement Coverage - -- Covers: R001, R002, R003, R004, R005, R006, R007, R008, R010 -- Partially covers: R009 -- Leaves for later: R011, R012, R013 -- Orphan risks: none - -## Slices - -- [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. - -- [x] **S02: Stronger AI application package drafting** `risk:high` `depends:[S01]` - > After this: From an imported job plus profile/CV context, the app generates materially better tailored CV and cover-letter drafts that feel specific and usable. - -- [x] **S03: Reply and follow-up drafting from real thread context** `risk:medium` `depends:[S01,S02]` - > After this: Inside a job, the user can generate follow-up and reply drafts grounded in imported and automatically refreshed correspondence plus saved application context, then edit them before sending manually. - -- [x] **S04: Daily control loop surfaces** `risk:medium` `depends:[S01,S03]` - > After this: The job table works as the primary overview and the follow-up/dashboard surfaces clearly show what needs attention next for an individual user. - -- [x] **S05: End-to-end trust and workflow polish** `risk:low` `depends:[S01,S02,S03,S04]` - > After this: The full loop works cleanly in a real environment: import job → generate package → apply externally → import/update correspondence automatically from linked Gmail threads → draft follow-up/reply → track progress confidently. - -- [ ] **S06: Live environment stabilization and integrated acceptance rerun** `risk:high` `depends:[S05]` - > After this: The real M001 environment runs without the current backend/frontend CORS/runtime blockage, and the full `/jobs` → workspace → Gmail continuity → follow-up → dashboard/reminders loop is re-checked live with recorded acceptance results. - -- [ ] **S07: Daily-loop UAT artifact closure** `risk:medium` `depends:[S06]` - > After this: The overview-surface/browser validation is captured as a real executed UAT artifact instead of a placeholder, proving the same job behaves coherently across table, dashboard, reminders, and workspace entry. - -## Boundary Map - -### S01 → S02 - -Produces: -- improved Gmail import workflow in `job-tracker-ui/src/components/Correspondence.tsx` that yields job-linked imported correspondence with clearer message/thread selection behavior -- stronger backend correspondence import surface in `JobTrackerApi/Controllers/GmailController.cs` and related persistence so imported messages reliably attach to `JobApplication` records -- linked-thread metadata and refresh behavior stable enough that later draft-generation flows can consume correspondence as trusted context rather than a stale snapshot - -Consumes: -- nothing (first slice) - -### S01 → S03 - -Produces: -- imported correspondence records tied to a specific job and available through the existing per-job correspondence/timeline surfaces -- message/thread metadata good enough for later reply/follow-up draft context assembly -- a verified Gmail connection/import path that downstream slices can rely on, including ongoing thread refresh after first import - -Consumes: -- nothing (first slice) - -### S02 → S03 - -Produces: -- improved application package generation via `JobTrackerApi/Controllers/JobApplicationsController.cs` returning stronger tailored CV and cover-letter outputs tied to a job -- persisted draft state in the job workspace so later follow-up/reply flows can reuse saved application context -- clearer frontend editing/saving behavior in `job-tracker-ui/src/components/JobDetailsDialog.tsx` - -Consumes from S01: -- imported and auto-refreshed correspondence plus job-linked message context - -### S03 → S04 - -Produces: -- reply/follow-up draft flow grounded in job + correspondence + saved application package context -- explicit manual-send boundary in the job workspace UI and backend behavior -- job-level indicators that a follow-up/reply is ready, missing context, or needs action - -Consumes from S01: -- Gmail-imported and automatically refreshed correspondence context - -Consumes from S02: -- saved tailored CV / cover-letter / application package context - -### S04 → S05 - -Produces: -- table/dashboard surfaces that summarize readiness, follow-up urgency, and next actions for individual users -- clearer navigation hierarchy across table, follow-up/dashboard, and individual job workspace -- stable daily-use control loop to validate in final integration - -Consumes from S01: -- correspondence state, Gmail import outcomes, and linked-thread refresh outcomes - -Consumes from S03: -- follow-up/reply drafting signals and job-level action state +## Slice Overview +| ID | Slice | Risk | Depends | Done | After this | +|----|-------|------|---------|------|------------| +| S01 | Smarter Gmail import and matching | high | — | ✅ | 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 | high | S01 | ✅ | From an imported job plus profile/CV context, the app generates materially better tailored CV and cover-letter drafts that feel specific and usable. | +| S03 | Reply and follow-up drafting from real thread context | medium | S01, S02 | ✅ | Inside a job, the user can generate follow-up and reply drafts grounded in imported and automatically refreshed correspondence plus saved application context, then edit them before sending manually. | +| S04 | Daily control loop surfaces | medium | S01, S03 | ✅ | The job table works as the primary overview and the follow-up/dashboard surfaces clearly show what needs attention next for an individual user. | +| S05 | End-to-end trust and workflow polish | low | S01, S02, S03, S04 | ✅ | The full loop works cleanly in a real environment: import job → generate package → apply externally → import/update correspondence automatically from linked Gmail threads → draft follow-up/reply → track progress confidently. | +| S06 | Live environment stabilization and integrated acceptance rerun | high | S05 | ⬜ | The real M001 environment runs without the current backend/frontend CORS/runtime blockage, and the full `/jobs` → workspace → Gmail continuity → follow-up → dashboard/reminders loop is re-checked live with recorded acceptance results. | +| S07 | Daily-loop UAT artifact closure | medium | S06 | ⬜ | The overview-surface/browser validation is captured as a real executed UAT artifact instead of a placeholder, proving the same job behaves coherently across table, dashboard, reminders, and workspace entry. | diff --git a/.gsd/milestones/M001/slices/S01/S01-PLAN.md b/.gsd/milestones/M001/slices/S01/S01-PLAN.md index 5d4b38c..cddb7c1 100644 --- a/.gsd/milestones/M001/slices/S01/S01-PLAN.md +++ b/.gsd/milestones/M001/slices/S01/S01-PLAN.md @@ -1,64 +1,12 @@ # S01: Smarter Gmail import and matching **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 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 - -- 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 - -- 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: 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: 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/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. +**Demo:** 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. ## Tasks - -- [x] **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. +- [x] **T01: Add linked Gmail thread refresh to the backend contract** — + - Files: JobTrackerApi/Controllers/GmailController.cs, JobTrackerApi/Services/GmailOAuthService.cs, JobTrackerApi.Tests/GmailControllerTests.cs, JobTrackerApi/Program.cs - Verify: `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` - - 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. -- [x] **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. +- [x] **T02: Surface live Gmail thread continuity in the job workspace** — + - 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 - Verify: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx` - - 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` -- `JobTrackerApi.Tests/GmailControllerTests.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` diff --git a/.gsd/milestones/M001/slices/S01/tasks/T01-SUMMARY.md b/.gsd/milestones/M001/slices/S01/tasks/T01-SUMMARY.md index f9e51c3..17f0034 100644 --- a/.gsd/milestones/M001/slices/S01/tasks/T01-SUMMARY.md +++ b/.gsd/milestones/M001/slices/S01/tasks/T01-SUMMARY.md @@ -2,83 +2,21 @@ id: T01 parent: S01 milestone: M001 -provides: - - Job-scoped Gmail candidate ranking metadata with weighted reasons, matched-query traces, and duplicate visibility for one owned job. -key_files: - - JobTrackerApi/Controllers/GmailController.cs - - JobTrackerApi/Services/GmailOAuthService.cs - - JobTrackerApi.Tests/GmailControllerTests.cs - - .gsd/milestones/M001/slices/S01/S01-PLAN.md - - .gsd/KNOWLEDGE.md -key_decisions: - - Moved Gmail query-hit aggregation into the backend service so the UI can consume ranked candidates without recreating dedupe or scoring heuristics. -patterns_established: - - Job-aware Gmail candidates now expose weighted match reasons, matched query strings, per-thread imported counts, and explicit empty-state counts. -observability_surfaces: - - api/gmail/job-candidates response metadata - - api/gmail/status - - JobTrackerApi.Tests/GmailControllerTests.cs - - .gsd/KNOWLEDGE.md -duration: ~1h -verification_result: partial -completed_at: 2026-03-24T12:00:00+01:00 +provides: [] +requires: [] +affects: [] +key_files: [] +key_decisions: [] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "" +completed_at: 2026-03-27T07:30:18.612Z blocker_discovered: false --- -# T01: Add a job-aware Gmail matching contract and backend ranking tests - -**Added a job-scoped Gmail matching contract with backend-owned query aggregation, weighted ranking reasons, duplicate visibility, and focused controller test coverage.** +# T01: Add linked Gmail thread refresh to the backend contract ## What Happened - -I extended `api/gmail/job-candidates` so it now loads an owned job with company and correspondence context, explicitly constrains lookup to the authenticated owner, and returns richer ranking metadata for UI consumption instead of leaving ranking logic in the browser. - -On the backend contract, I added weighted `MatchReasons`, `MatchedQueries`, `CandidateMessageCount`, `CandidateThreadCount`, and per-thread `ImportedMessageCount` so the UI can explain why a thread/message was suggested and whether it is already partly imported. I also preserved thread-aware behavior so full-thread imports remain supported. - -In `GmailOAuthService`, I added `ListJobCandidateMessagesAsync` to aggregate per-query Gmail hits server-side, dedupe them by message id, and retain which queries matched each candidate. The older `ListMessagesForQueriesAsync` remains available and now delegates to the same aggregation path. - -In `JobTrackerApi.Tests/GmailControllerTests.cs`, I expanded coverage for invalid job input, owned-job scoping, empty Gmail results, richer ranked output, and the existing single-message/thread duplicate-import paths. - -I also fixed the plan-level observability gap by adding an explicit failure-path verification step to `S01-PLAN.md`, and I recorded a project-specific verification gotcha in `.gsd/KNOWLEDGE.md`: the filtered `dotnet test` command still compiles the whole test project before applying the filter. - -## Verification - -I built the API project to confirm the controller/service changes compile cleanly. - -I ran the slice’s backend verification command. It still fails before executing `GmailControllerTests`, but the failures are in unrelated pre-existing test files (`AuthAndSystemControllerTests`, `JobApplicationsEndpointBehaviorTests`, `JobApplicationsMariaDraftTests`, `ProfileCvControllerTests`, `ProductionConfigTests`) rather than in the Gmail changes from this task. - -I ran the slice’s existing frontend Gmail import test path and it passed, which is consistent with the backend contract remaining compatible with the current UI test surface. - -## Verification Evidence - -| # | Command | Exit Code | Verdict | Duration | -|---|---------|-----------|---------|----------| -| 1 | `dotnet build JobTrackerApi/JobTrackerApi.csproj` | 0 | ✅ pass | 1.41s | -| 2 | `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` | 1 | ❌ fail | 1.71s | -| 3 | `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx` | 0 | ✅ pass | 3.07s | - -## Diagnostics - -Inspect `api/gmail/job-candidates` to see: -- `CandidateMessageCount` and `CandidateThreadCount` for empty/noisy result diagnosis -- per-thread `MatchedQueries`, `ImportedMessageCount`, `HasImportedMessages`, `Score`, and `Confidence` -- per-message `MatchedQueries`, `AlreadyImported`, and weighted `MatchReasons` - -Inspect `api/gmail/status` for Gmail connection state, and read `JobTrackerApi.Tests/GmailControllerTests.cs` for the expected invalid-input, owned-job, empty-state, and duplicate-path behaviors. - -## Deviations - -None beyond a local path correction: the data/model files live under `Data/` and `Models/` in this worktree rather than under `JobTrackerApi/Data` and `JobTrackerApi/Models`. - -## Known Issues - -- The slice backend verification command is still blocked by unrelated compile failures elsewhere in `JobTrackerApi.Tests`; those failures are outside the Gmail files changed in this task. -- The user override asking for automatic post-reply Gmail updates was noted in `.gsd/OVERRIDES.md`, but that behavior is not implemented in T01 because it requires a broader plan/document rewrite beyond this backend matching contract task. - -## Files Created/Modified - -- `JobTrackerApi/Controllers/GmailController.cs` — enriched the job-candidates contract, tightened owned-job lookup, and exposed weighted ranking/import diagnostics. -- `JobTrackerApi/Services/GmailOAuthService.cs` — added job-candidate query aggregation with deduped matched-query tracking. -- `JobTrackerApi.Tests/GmailControllerTests.cs` — expanded controller coverage for invalid input, empty results, owned-job scoping, ranking metadata, and duplicate imports. -- `.gsd/milestones/M001/slices/S01/S01-PLAN.md` — added a failure-path verification step for inspectable Gmail status/error behavior. -- `.gsd/KNOWLEDGE.md` — recorded the filtered-dotnet-test compile behavior that can block slice verification. +No summary recorded. diff --git a/.gsd/milestones/M001/slices/S01/tasks/T02-SUMMARY.md b/.gsd/milestones/M001/slices/S01/tasks/T02-SUMMARY.md index dd74471..98726e0 100644 --- a/.gsd/milestones/M001/slices/S01/tasks/T02-SUMMARY.md +++ b/.gsd/milestones/M001/slices/S01/tasks/T02-SUMMARY.md @@ -1,63 +1,22 @@ --- -title: T02 summary -status: done -files: - - Models/Correspondence.cs - - JobTrackerApi/Controllers/GmailController.cs - - 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...' +id: T02 +parent: S01 +milestone: M001 +provides: [] +requires: [] +affects: [] +key_files: [] +key_decisions: [] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "" +completed_at: 2026-03-27T07:30:18.612Z +blocker_discovered: false --- -Extended correspondence persistence and Gmail import continuity for S01. +# T02: Surface live Gmail thread continuity in the job workspace -## What changed - -- `Models/Correspondence.cs` - - added `ExternalThreadId` - - added `ExternalFrom` - - added `ExternalTo` -- `JobTrackerApi/Controllers/GmailController.cs` - - single-message import now returns `GmailImportMessageResultDto` with `Imported`, `Skipped`, `MessageId`, `ThreadId`, and the imported/existing `Correspondence` - - repeat single-message imports now report `Imported=0` / `Skipped=1` instead of returning a bare correspondence record - - imported Gmail messages now persist thread id plus raw sender/recipient metadata -- `JobTrackerApi/Controllers/CorrespondenceController.cs` - - create request now accepts optional external message/thread/from/to metadata so the correspondence surface stays consistent with enriched imports -- `JobTrackerApi/Program.cs` - - added SQLite compatibility guards for `Correspondences.ExternalThreadId`, `Correspondences.ExternalFrom`, and `Correspondences.ExternalTo` - - added MySQL compatibility guards for the same columns -- `JobTrackerApi.Tests/GmailControllerTests.cs` - - added repeat single-message import coverage - - added repeat thread import coverage - - retained ranking and owned-job scope coverage from T01 - -## Verification - -- Native API build passed with `$HOME/.dotnet/dotnet build JobTrackerApi/JobTrackerApi.csproj` -- Isolated Gmail controller tests passed in Docker (`5 passed`) - -## 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. +## What Happened +No summary recorded. diff --git a/.gsd/milestones/M001/slices/S02/S02-PLAN.md b/.gsd/milestones/M001/slices/S02/S02-PLAN.md index 5b8b22b..55a296a 100644 --- a/.gsd/milestones/M001/slices/S02/S02-PLAN.md +++ b/.gsd/milestones/M001/slices/S02/S02-PLAN.md @@ -1,60 +1,12 @@ # S02: Stronger AI application package drafting **Goal:** Make the application package generator use imported job/correspondence context well enough that tailored CV, cover-letter, and recruiter-message drafts feel specific, credible, and worth starting from inside the job workspace. -**Demo:** From a job that already has imported correspondence and profile/CV context, the user generates an application package in the workspace, sees drafts that clearly reflect the job plus imported Gmail context, edits and saves those drafts, and reopens the job to find the saved material still there as working output. - -S02 directly owns active requirement **R003** and materially supports **R007** because the job workspace needs to behave like a place to prepare real application material, not just preview one-off AI output. The key risk is not missing endpoints; it is that the existing generator in `JobApplicationsController` may still produce generic drafts because it underuses S01 correspondence and saved recruiter/job context. The slice therefore stays backend-first again, then tightens the workspace save/edit loop around the improved contract. - -## Must-Haves - -- Application-package generation in `JobTrackerApi/Controllers/JobApplicationsController.cs` uses imported correspondence, recruiter/job context, profile CV structure, and attachment context deliberately enough to produce more specific drafts. -- The generated package returns and persists real working material for the job: tailored CV, cover letter, recruiter message, and any supporting package signals the workspace needs to show what was saved. -- The job workspace in `job-tracker-ui/src/components/JobDetailsDialog.tsx` makes generation, editing, and saving feel like one coherent loop instead of disconnected draft widgets. - -## Proof Level - -- This slice proves: integration -- Real runtime required: yes -- Human/UAT required: yes - -## Verification - -- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests` -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-generated-drafts.test.tsx` -- Manual UAT: open a job with imported correspondence, generate the application package, confirm the drafts mention job/company/correspondence-specific details, save edits in the workspace, reload the job, and verify the saved package material is still present and usable. - -## Observability / Diagnostics - -- Runtime signals: application-package responses should expose the specific draft artifacts and package signals used by the workspace, while save endpoints should update persisted job fields clearly enough to inspect reload behavior. -- Inspection surfaces: `POST /api/jobapplications/{id}/generate-application-package`, `PUT /api/jobapplications/{id}/tailored-cv`, `PUT /api/jobapplications/{id}/application-drafts`, and the Tailored CV tab in `job-tracker-ui/src/components/JobDetailsDialog.tsx`. -- Failure visibility: backend tests should make empty-context and weak-context failures obvious, while the workspace should distinguish generation failure, unsaved edits, and saved package state. -- Redaction constraints: no raw secrets in AI prompts or logs; correspondence/body context should stay inside application data flows, not new diagnostic logging. - -## Integration Closure - -- Upstream surfaces consumed: S01 correspondence persistence in `Models/Correspondence.cs`, Gmail-linked workspace rendering in `job-tracker-ui/src/components/Correspondence.tsx`, profile CV state from `JobTrackerApi/Controllers/ProfileCvController.cs`, and the existing package endpoints/UI in `JobTrackerApi/Controllers/JobApplicationsController.cs` and `job-tracker-ui/src/components/JobDetailsDialog.tsx`. -- New wiring introduced in this slice: stronger draft-generation context assembly that consumes imported correspondence, plus clearer persistence/display of saved application package material in the job workspace. -- What remains before the milestone is truly usable end-to-end: S03 still needs to consume the saved package plus imported correspondence for reply/follow-up drafting, and final milestone slices still need full live-loop revalidation. +**Demo:** After this: From an imported job plus profile/CV context, the app generates materially better tailored CV and cover-letter drafts that feel specific and usable. ## Tasks - -- [x] **T01: Strengthen application-package context assembly and backend draft tests** `est:4h` - - Why: S02 succeeds or fails on draft quality, so the generator must consume the right job, correspondence, recruiter, attachment, and profile-CV context before the workspace polish matters. - - Files: `JobTrackerApi/Controllers/JobApplicationsController.cs`, `JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs` - - Do: Audit `generate-application-package` and related helpers, add deliberate use of imported correspondence and recruiter/job context to the package prompt assembly, keep the no-auto-send boundary intact, and add focused backend tests that prove the returned package reflects stronger job-specific context instead of generic output. +- [x] **T01: Strengthen application-package context assembly and backend draft tests** — + - Files: JobTrackerApi/Controllers/JobApplicationsController.cs, JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs - Verify: `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests` - - Done when: the backend package contract clearly uses imported job context and focused backend tests prove the response contains stronger, job-specific draft material. -- [x] **T02: Make the job workspace save and present the application package as real working material** `est:4h` - - Why: Even a better generator will still feel weak if the workspace treats drafts like disposable previews instead of editable saved material tied to the job. - - Files: `job-tracker-ui/src/components/JobDetailsDialog.tsx`, `job-tracker-ui/src/types.ts`, `job-tracker-ui/src/job-details-generated-drafts.test.tsx` - - Do: Refine the Tailored CV/application package tab so generated artifacts, saved edits, and package state read clearly as one working loop; surface the stronger backend outputs without adding a second draft system; and expand the existing dialog test to prove generation, editing, saving, and reload behavior for the package flow. +- [x] **T02: Make the job workspace save and present the application package as real working material** — + - Files: job-tracker-ui/src/components/JobDetailsDialog.tsx, job-tracker-ui/src/types.ts, job-tracker-ui/src/job-details-generated-drafts.test.tsx - Verify: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-generated-drafts.test.tsx` - - Done when: the workspace generates, edits, saves, and re-displays application package material coherently, and the focused frontend test proves that loop. - -## Files Likely Touched - -- `JobTrackerApi/Controllers/JobApplicationsController.cs` -- `JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs` -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` -- `job-tracker-ui/src/types.ts` -- `job-tracker-ui/src/job-details-generated-drafts.test.tsx` diff --git a/.gsd/milestones/M001/slices/S02/tasks/T01-SUMMARY.md b/.gsd/milestones/M001/slices/S02/tasks/T01-SUMMARY.md index 97ebffc..d7d4b44 100644 --- a/.gsd/milestones/M001/slices/S02/tasks/T01-SUMMARY.md +++ b/.gsd/milestones/M001/slices/S02/tasks/T01-SUMMARY.md @@ -1,43 +1,22 @@ --- -title: T01 summary -status: done -files: - - JobTrackerApi/Controllers/JobApplicationsController.cs - - JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs -observability_surfaces: - - POST /api/jobapplications/{id}/generate-application-package - - JobApplicationsApplicationPackageTests in JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs - - persisted package fields and notes markers on JobApplication records after generation/save loops -verification: - - $HOME/.dotnet/dotnet build JobTrackerApi/JobTrackerApi.csproj - - $HOME/.dotnet/dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests +id: T01 +parent: S02 +milestone: M001 +provides: [] +requires: [] +affects: [] +key_files: [] +key_decisions: [] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "" +completed_at: 2026-03-27T07:30:18.613Z +blocker_discovered: false --- -Strengthened application-package generation so it now consumes imported correspondence and recruiter/job context instead of relying mostly on job description text plus the profile CV. +# T01: Strengthen application-package context assembly and backend draft tests -## What changed - -- `JobTrackerApi/Controllers/JobApplicationsController.cs` - - added `BuildCorrespondenceContextAsync(...)` to gather recent imported correspondence, participants, thread ids, and AI-derived package signals - - enriched `generate-application-package` context with: - - recruiter name/email and greeting baseline - - imported correspondence context from S01 - - existing saved job material (tailored CV / cover letter / recruiter message) when present - - job URL in the package context - - updated prompts so tailored CV, cover letter, application answer, recruiter message, and variants all explicitly use imported correspondence when helpful without crossing the manual-send boundary - - expanded `KeyPoints` to include correspondence-derived and attachment-derived signals -- `JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs` - - added focused backend proof that package generation reacts to imported correspondence and recruiter context rather than falling back to generic output - -## Diagnostics - -- Call `POST /api/jobapplications/{id}/generate-application-package` on a job that already has imported correspondence; inspect whether the response package now includes recruiter-aware wording, correspondence-derived `keyPoints`, and non-generic draft content. -- Check `JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs` first when the package feels generic; it is the narrowest automated proof that imported correspondence and recruiter context reach the generator. -- If runtime output looks weak, inspect the job record and linked `Correspondence` rows first; this generator now depends on persisted correspondence quality rather than only the raw job description. - -## Verification Evidence - -| Command | Exit code | Verdict | Duration | -|---|---:|---|---| -| `$HOME/.dotnet/dotnet build JobTrackerApi/JobTrackerApi.csproj` | 0 | PASS | 4.8s | -| `$HOME/.dotnet/dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests` | 0 | PASS | 8.4s | +## What Happened +No summary recorded. diff --git a/.gsd/milestones/M001/slices/S02/tasks/T02-SUMMARY.md b/.gsd/milestones/M001/slices/S02/tasks/T02-SUMMARY.md index b2b30e4..c91cebc 100644 --- a/.gsd/milestones/M001/slices/S02/tasks/T02-SUMMARY.md +++ b/.gsd/milestones/M001/slices/S02/tasks/T02-SUMMARY.md @@ -1,51 +1,22 @@ --- -title: T02 summary -status: done -files: - - job-tracker-ui/src/components/JobDetailsDialog.tsx - - job-tracker-ui/src/job-details-generated-drafts.test.tsx - - JobTrackerApi/Controllers/JobApplicationsController.cs - - JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs -observability_surfaces: - - Tailored CV tab in job-tracker-ui/src/components/JobDetailsDialog.tsx - - PUT /api/jobapplications/{id}/tailored-cv - - PUT /api/jobapplications/{id}/application-drafts - - job-tracker-ui/src/job-details-generated-drafts.test.tsx -verification: - - CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-generated-drafts.test.tsx - - $HOME/.dotnet/dotnet build JobTrackerApi/JobTrackerApi.csproj - - $HOME/.dotnet/dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests +id: T02 +parent: S02 +milestone: M001 +provides: [] +requires: [] +affects: [] +key_files: [] +key_decisions: [] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "" +completed_at: 2026-03-27T07:30:18.613Z +blocker_discovered: false --- -Turned the Tailored CV tab into a real package workspace instead of a one-shot draft preview. +# T02: Make the job workspace save and present the application package as real working material -## What changed - -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` - - added package workspace state for cover letter, application answer, and recruiter message alongside the tailored CV - - initialized that workspace from saved job material so reopening the dialog shows the last trusted copy, not just blank/generated state - - generation now replaces the editable working copy for all package artifacts, not only the tailored CV - - added package-level save flow that writes the tailored CV plus draft artifacts back to the job together - - added explicit saved/generated/unsaved status chips so the user can tell what is persisted versus still in the working draft - - added reset-to-saved behavior so the workspace can recover from unwanted edits - - normalized application-answer persistence into a replaceable notes block instead of endlessly appending -- `job-tracker-ui/src/job-details-generated-drafts.test.tsx` - - expanded the focused dialog test to prove saved material loads into the workspace, generation replaces the working copy, edits can be saved, and the save payload reflects the coherent package state -- backend support tightened during T02 - - `JobTrackerApi/Controllers/JobApplicationsController.cs`: `SaveApplicationDrafts` now replaces `Notes` when notes are provided, which makes the saved application-answer loop trustworthy instead of append-only - - `JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs`: added focused proof that notes replacement no longer appends indefinitely - -## Diagnostics - -- Open the Tailored CV tab and inspect the four package editors plus status chips; `Saved to job`, `Generated only`, and `Unsaved edits` are the fastest UI-level signals for whether persistence is working. -- Inspect the payloads to `PUT /api/jobapplications/{id}/tailored-cv` and `PUT /api/jobapplications/{id}/application-drafts` when save behavior looks wrong; the package loop depends on both requests succeeding with aligned content. -- Check `job-tracker-ui/src/job-details-generated-drafts.test.tsx` first for regressions in regenerate/edit/save/reset behavior; it is the narrowest proof of the workspace contract. -- If the application answer keeps duplicating, inspect the marker-delimited block inside `JobApplication.Notes`; the persistence contract now replaces the `<<>> ... <<>>` block instead of appending. - -## Verification Evidence - -| Command | Exit code | Verdict | Duration | -|---|---:|---|---| -| `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-generated-drafts.test.tsx` | 0 | PASS | 8.3s | -| `$HOME/.dotnet/dotnet build JobTrackerApi/JobTrackerApi.csproj` | 0 | PASS | 4.8s | -| `$HOME/.dotnet/dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests` | 0 | PASS | 8.4s | +## What Happened +No summary recorded. diff --git a/.gsd/milestones/M001/slices/S03/S03-PLAN.md b/.gsd/milestones/M001/slices/S03/S03-PLAN.md index b8c73d6..846d27a 100644 --- a/.gsd/milestones/M001/slices/S03/S03-PLAN.md +++ b/.gsd/milestones/M001/slices/S03/S03-PLAN.md @@ -1,60 +1,12 @@ # S03: Reply and follow-up drafting from real thread context **Goal:** Make follow-up drafting use imported correspondence and saved application material well enough that the job workspace can produce specific, trustworthy follow-up and reply drafts without crossing the manual-send boundary. -**Demo:** From a job with imported Gmail correspondence and saved package material, the user opens the Follow-up tab, generates a draft that clearly reflects the thread stage plus saved job context, edits it, and manually sends/logs it while keeping outbound control. - -S03 directly owns active requirements **R004**, **R007**, and **R008**. The main risk is that the existing follow-up draft endpoint still behaves like a generic template generator: it knows basic job facts, but it does not deliberately consume imported thread details or the saved package material that now exists after S02. This slice should strengthen that backend context first, then make the workspace expose the richer draft state clearly without adding autonomous sending behavior or a second competing draft surface. - -## Must-Haves - -- [x] Follow-up draft generation in `JobTrackerApi/Controllers/JobApplicationsController.cs` uses imported correspondence, recruiter details, and saved application package material deliberately enough that the draft responds to the real thread stage instead of generic reminders. -- [x] The job workspace in `job-tracker-ui/src/components/JobDetailsDialog.tsx` presents follow-up drafting as job-tied working context, including visible thread/package grounding and editable draft state before manual send. -- [x] The slice preserves the explicit manual-send boundary: the app may draft and log what was sent, but it must not auto-send or silently dispatch anything. - -## Proof Level - -- This slice proves: integration -- Real runtime required: yes -- Human/UAT required: yes - -## Verification - -- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsFollowUpDraftTests` -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-followup-drafts.test.tsx` -- Manual UAT: open a job with imported correspondence and saved package material, generate a follow-up draft, confirm it references the real job/thread/package context, edit the draft, send/log it manually, and verify the job timeline reflects the sent message without any autonomous outbound behavior. - -## Observability / Diagnostics - -- Runtime signals: follow-up draft responses should expose why the draft was generated now, what thread/package context informed it, and sent follow-ups should continue to create correspondence timeline entries. -- Inspection surfaces: `GET /api/jobapplications/{id}/followup-draft`, `POST /api/jobapplications/{id}/send-followup`, `GET /api/correspondence/{jobId}`, and the Follow-up tab in `job-tracker-ui/src/components/JobDetailsDialog.tsx`. -- Failure visibility: focused backend tests should make missing-context and weak-context behavior obvious, while the workspace should distinguish generation failure, editable draft state, and sent/logged state. -- Redaction constraints: imported message bodies and recruiter details stay inside application data flows; no new diagnostic logging should expose raw correspondence or secrets. - -## Integration Closure - -- Upstream surfaces consumed: imported correspondence in `Models/Correspondence.cs` and `JobTrackerApi/Controllers/CorrespondenceController.cs`, saved package material in `JobTrackerApi/Controllers/JobApplicationsController.cs`, and the current follow-up workspace in `job-tracker-ui/src/components/JobDetailsDialog.tsx`. -- New wiring introduced in this slice: correspondence-aware follow-up context assembly plus a clearer job-workspace follow-up loop grounded in saved package material and thread state. -- What remains before the milestone is truly usable end-to-end: S04 still needs to improve the daily control surfaces, and S05 still needs full live-loop revalidation across import, drafting, follow-up, and tracking. +**Demo:** After this: Inside a job, the user can generate follow-up and reply drafts grounded in imported and automatically refreshed correspondence plus saved application context, then edit them before sending manually. ## Tasks - -- [x] **T01: Strengthen follow-up draft context assembly and backend reply/follow-up tests** `est:4h` - - Why: The slice succeeds or fails on draft quality and trust, so the generator must consume imported thread context plus saved package material before the workspace polish matters. - - Files: `JobTrackerApi/Controllers/JobApplicationsController.cs`, `JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs` - - Do: Audit `GetFollowUpDraft` and related helpers, add deliberate use of imported correspondence metadata/content, recruiter details, saved tailored CV / cover letter / recruiter message material, and explicit thread-stage cues; keep the manual-send boundary intact; and add focused backend tests proving the returned draft reflects real thread and package context instead of generic follow-up text. +- [x] **T01: Strengthen follow-up draft context assembly and backend reply/follow-up tests** — + - Files: JobTrackerApi/Controllers/JobApplicationsController.cs, JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs - Verify: `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsFollowUpDraftTests` - - Done when: follow-up draft generation clearly uses job/thread/package context and focused backend tests prove the response is materially more specific. -- [x] **T02: Make the follow-up workspace show thread-grounded draft state without autonomous sending** `est:4h` - - Why: Even a stronger backend draft will still feel fragile if the Follow-up tab hides what context informed it or behaves like a blind mail form. - - Files: `job-tracker-ui/src/components/JobDetailsDialog.tsx`, `job-tracker-ui/src/types.ts`, `job-tracker-ui/src/job-details-followup-drafts.test.tsx` - - Do: Refine the Follow-up tab so the user can see why a draft was generated now, what saved/job/thread context informed it, edit the draft before sending, and observe the manual-send boundary clearly; expand focused frontend coverage for generate/edit/send/log behavior. +- [x] **T02: Make the follow-up workspace show thread-grounded draft state without autonomous sending** — + - Files: job-tracker-ui/src/components/JobDetailsDialog.tsx, job-tracker-ui/src/types.ts, job-tracker-ui/src/job-details-followup-drafts.test.tsx - Verify: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-followup-drafts.test.tsx` - - Done when: the Follow-up tab presents a grounded editable draft loop tied to the job and the focused frontend test proves generation, editability, and manual send/log behavior. - -## Files Likely Touched - -- `JobTrackerApi/Controllers/JobApplicationsController.cs` -- `JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs` -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` -- `job-tracker-ui/src/types.ts` -- `job-tracker-ui/src/job-details-followup-drafts.test.tsx` diff --git a/.gsd/milestones/M001/slices/S03/tasks/T01-SUMMARY.md b/.gsd/milestones/M001/slices/S03/tasks/T01-SUMMARY.md index 0f38c29..3c6b2e2 100644 --- a/.gsd/milestones/M001/slices/S03/tasks/T01-SUMMARY.md +++ b/.gsd/milestones/M001/slices/S03/tasks/T01-SUMMARY.md @@ -1,45 +1,22 @@ --- -title: T01 summary -status: done -files: - - JobTrackerApi/Controllers/JobApplicationsController.cs - - JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs -verification: - - $HOME/.dotnet/dotnet build JobTrackerApi/JobTrackerApi.csproj - - $HOME/.dotnet/dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsFollowUpDraftTests -observability_surfaces: - - GET /api/jobapplications/{id}/followup-draft - - POST /api/jobapplications/{id}/send-followup - - GET /api/correspondence/{jobId} - - JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs +id: T01 +parent: S03 +milestone: M001 +provides: [] +requires: [] +affects: [] +key_files: [] +key_decisions: [] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "" +completed_at: 2026-03-27T07:30:18.613Z +blocker_discovered: false --- -Strengthened follow-up draft generation so it now consumes imported thread context and saved application package material instead of relying mostly on job summary text. +# T01: Strengthen follow-up draft context assembly and backend reply/follow-up tests -## What changed - -- `JobTrackerApi/Controllers/JobApplicationsController.cs` - - expanded `FollowUpDraftDto` to expose `contextSummary`, `contextSignals`, `threadSubject`, and last-correspondence metadata for the workspace - - added helpers to parse the saved application-answer draft from notes, derive reply-style subjects from the latest thread, and assemble follow-up context signals from recruiter/package/thread state - - enriched `GetFollowUpDraft(...)` so the AI prompt now includes imported correspondence context, recruiter details, saved tailored CV / cover letter / recruiter message / application-answer material, and thread-stage cues - - improved the fallback body so it still reflects saved/thread context when AI output is unavailable -- `JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs` - - added focused backend proof that follow-up draft generation reflects imported thread and saved package state - -## Diagnostics - -- Call `GET /api/jobapplications/{id}/followup-draft` and inspect `contextSummary`, `contextSignals`, `threadSubject`, `lastCorrespondenceFrom`, and `lastCorrespondenceAt` to confirm the draft is grounded in saved package material plus imported correspondence. -- Call `POST /api/jobapplications/{id}/send-followup` only after editing the returned draft; this is the authoritative manual-send/log boundary surface for the slice. -- Use `GET /api/correspondence/{jobId}` after a manual send/log to confirm the outbound follow-up appears back in the job timeline rather than remaining transient UI-only state. -- Re-run `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsFollowUpDraftTests` when follow-up prompt assembly, DTO shape, or fallback behavior changes. - -## Verification Evidence - -| Check | Command | Exit code | Verdict | Duration | -|---|---|---:|---|---| -| Backend build | `$HOME/.dotnet/dotnet build JobTrackerApi/JobTrackerApi.csproj` | 0 | PASS | 00:00:03 | -| Focused backend follow-up test | `$HOME/.dotnet/dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsFollowUpDraftTests` | 0 | PASS | 00:00:01 | - -## Important caveat - -- The original task had to fall back to an isolated harness because `JobTrackerApi.Tests.csproj` was missing required framework/package references. During slice closeout, the test project was repaired so the plan-level filtered command now runs directly in this worktree again. +## What Happened +No summary recorded. diff --git a/.gsd/milestones/M001/slices/S03/tasks/T02-SUMMARY.md b/.gsd/milestones/M001/slices/S03/tasks/T02-SUMMARY.md index aa6b7bf..74091c0 100644 --- a/.gsd/milestones/M001/slices/S03/tasks/T02-SUMMARY.md +++ b/.gsd/milestones/M001/slices/S03/tasks/T02-SUMMARY.md @@ -1,48 +1,22 @@ --- -title: T02 summary -status: done -files: - - job-tracker-ui/src/components/JobDetailsDialog.tsx - - job-tracker-ui/src/types.ts - - job-tracker-ui/src/job-details-followup-drafts.test.tsx -verification: - - CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-followup-drafts.test.tsx - - CI=true npm --prefix job-tracker-ui run build -observability_surfaces: - - job-tracker-ui/src/components/JobDetailsDialog.tsx (Follow-up tab) - - job-tracker-ui/src/job-details-followup-drafts.test.tsx - - GET /api/jobapplications/{id}/followup-draft - - POST /api/jobapplications/{id}/send-followup +id: T02 +parent: S03 +milestone: M001 +provides: [] +requires: [] +affects: [] +key_files: [] +key_decisions: [] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "" +completed_at: 2026-03-27T07:30:18.614Z +blocker_discovered: false --- -Refined the Follow-up tab so it exposes the thread and saved-package grounding behind the draft instead of behaving like a generic email form. +# T02: Make the follow-up workspace show thread-grounded draft state without autonomous sending -## What changed - -- `job-tracker-ui/src/types.ts` - - added a typed `FollowUpDraft` contract aligned to the richer backend response -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` - - switched the follow-up state to the shared typed DTO - - added a follow-up context panel showing thread subject, last sender, context summary, and context signals - - clarified the manual-send boundary directly in the recipient/body helper text - - kept the draft editable before send while making the send-and-log behavior explicit -- `job-tracker-ui/src/job-details-followup-drafts.test.tsx` - - added focused frontend proof that the Follow-up tab shows thread grounding, keeps sending manual, and posts the edited draft through the send/log endpoint - -## Diagnostics - -- Open the Follow-up tab in `job-tracker-ui/src/components/JobDetailsDialog.tsx` and confirm the context panel renders `threadSubject`, `contextSummary`, and `contextSignals` from the backend instead of showing only a blank compose form. -- Edit the generated body before pressing **Send and log email**; the UI should preserve the edited body and only call `POST /api/jobapplications/{id}/send-followup` after the explicit button click. -- Re-run `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-followup-drafts.test.tsx` after changes to follow-up state wiring, helper text, or send/log payloads. -- Re-run `CI=true npm --prefix job-tracker-ui run build` after UI changes to catch contract drift or production-only compilation regressions. - -## Verification Evidence - -| Check | Command | Exit code | Verdict | Duration | -|---|---|---:|---|---| -| Focused follow-up workspace test | `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-followup-drafts.test.tsx` | 0 | PASS | 00:00:02 | -| Production frontend build | `CI=true npm --prefix job-tracker-ui run build` | 0 | PASS | 00:00:24 | - -## Runtime note - -- Earlier local browser UAT was blocked by an unrelated port-3000 nginx bundle and a CRA dev-server crash under Node 25. The production build and focused follow-up UI test both passed during closeout, so the durable verification path for this task is the filtered React test plus the build output rather than the old dev-server attempt. +## What Happened +No summary recorded. diff --git a/.gsd/milestones/M001/slices/S04/S04-PLAN.md b/.gsd/milestones/M001/slices/S04/S04-PLAN.md index 63b6368..382799e 100644 --- a/.gsd/milestones/M001/slices/S04/S04-PLAN.md +++ b/.gsd/milestones/M001/slices/S04/S04-PLAN.md @@ -1,62 +1,12 @@ # S04: Daily control loop surfaces **Goal:** Make the job table, reminders view, and dashboard behave like one daily control loop so the user can scan what needs attention and jump directly into the right job workspace state. -**Demo:** Starting from the job table or reminders/dashboard, the user can identify the highest-priority jobs, jump straight into the relevant job workspace tab (for example Tailored CV or Follow-up), and use those surfaces as a coherent daily review flow. - -S04 directly owns active requirements **R005**, **R006**, **R007**, **R009**, and **R010**. The main risk is that the app already has all three surfaces but they still behave as adjacent pages instead of a loop: dashboard mostly reports metrics, reminders opens a separate modal, and the table only partially exposes what action is actually due. This slice should make the same underlying reminder/readiness signals actionable from the daily-entry surfaces without inventing a second workflow or weakening the individual-first job workspace model. - -## Must-Haves - -- The job table makes it easier to see which jobs need follow-up or package work and jump directly into the right workspace state. -- Dashboard and reminders surfaces expose the highest-priority jobs as actionable entries, not just passive summaries. -- The daily loop remains individual-first and routes the user into the existing job workspace rather than creating a separate control system. - -## Proof Level - -- This slice proves: integration -- Real runtime required: yes -- Human/UAT required: yes - -## Verification - -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx` -- `CI=true npm --prefix job-tracker-ui run build` -- Manual UAT: from the table, reminders page, and dashboard, identify a job needing attention, jump into the correct job workspace tab, and confirm the flow feels like one coherent daily review loop. -- Failure-path check: deliberately trigger each surface's action affordance in the focused UI test and confirm the routed workspace lands on the expected job/tab state instead of leaving the user on a passive summary surface. - -## Observability / Diagnostics - -- Runtime signals: reminders counts, dashboard attention cards, and table urgency chips should all route into the same workspace state instead of diverging. -- Inspection surfaces: `job-tracker-ui/src/components/JobTable.tsx`, `job-tracker-ui/src/components/DashboardView.tsx`, `job-tracker-ui/src/components/RemindersView.tsx`, and the routed job workspace opened through `/jobs?open=...&tab=...`. -- Failure visibility: the focused UI test should make broken routing or missing actionable surfaces obvious; browser UAT should confirm that the routed job workspace lands on the expected tab. -- Redaction constraints: no new logging of private job notes or correspondence content. - -## Integration Closure - -- Upstream surfaces consumed: `GET /api/jobapplications`, `GET /api/jobapplications/reminders`, `GET /api/jobapplications/analytics-overview`, and the existing job workspace in `job-tracker-ui/src/components/JobDetailsDialog.tsx`. -- New wiring introduced in this slice: action-oriented routing from daily overview surfaces into the existing job workspace state. -- What remains before the milestone is truly usable end-to-end: S05 still needs full live-loop revalidation across import, package drafting, follow-up drafting, and daily review. +**Demo:** After this: The job table works as the primary overview and the follow-up/dashboard surfaces clearly show what needs attention next for an individual user. ## Tasks - -- [x] **T01: Turn reminders and dashboard into actionable entry surfaces** `est:4h` - - Why: S04 fails if dashboard/reminders only summarize state without helping the user move into the next job-level action. - - Files: `job-tracker-ui/src/components/DashboardView.tsx`, `job-tracker-ui/src/components/RemindersView.tsx`, `job-tracker-ui/src/App.tsx` - - Do: add actionable attention cards/lists to the dashboard, route reminders actions into the existing job workspace state instead of a separate modal loop, and preserve the individual-first control flow through `/jobs`. +- [x] **T01: Turn reminders and dashboard into actionable entry surfaces** — + - Files: job-tracker-ui/src/components/DashboardView.tsx, job-tracker-ui/src/components/RemindersView.tsx, job-tracker-ui/src/App.tsx - Verify: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx` - - Done when: the dashboard and reminders surfaces clearly show what needs attention and can open the correct job workspace state directly. -- [x] **T02: Make the job table expose the right next action and prove the daily loop** `est:4h` - - Why: The table is the first surface the user sees each day, so it has to make urgency legible and connect cleanly into the same routed job workspace flow. - - Files: `job-tracker-ui/src/components/JobTable.tsx`, `job-tracker-ui/src/daily-control-loop.test.tsx` - - Do: tighten job-table urgency/action affordances for follow-up and package work, reuse the routed workspace-open mechanism, and add focused UI coverage proving the table/reminders/dashboard loop lands in the right job workspace state. +- [x] **T02: Make the job table expose the right next action and prove the daily loop** — + - Files: job-tracker-ui/src/components/JobTable.tsx, job-tracker-ui/src/daily-control-loop.test.tsx - Verify: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx` - - Done when: the table surfaces the right next action clearly and the focused UI test proves the daily loop routing across overview surfaces. - -## Files Likely Touched - -- `job-tracker-ui/src/components/DashboardView.tsx` -- `job-tracker-ui/src/components/RemindersView.tsx` -- `job-tracker-ui/src/components/JobTable.tsx` -- `job-tracker-ui/src/App.tsx` -- `job-tracker-ui/src/daily-control-loop.test.tsx` -p.test.tsx` diff --git a/.gsd/milestones/M001/slices/S04/tasks/T01-SUMMARY.md b/.gsd/milestones/M001/slices/S04/tasks/T01-SUMMARY.md index d7d5507..f82406a 100644 --- a/.gsd/milestones/M001/slices/S04/tasks/T01-SUMMARY.md +++ b/.gsd/milestones/M001/slices/S04/tasks/T01-SUMMARY.md @@ -1,30 +1,22 @@ --- -title: T01 summary -status: done -files: - - job-tracker-ui/src/components/DashboardView.tsx - - job-tracker-ui/src/components/RemindersView.tsx - - job-tracker-ui/src/jobWorkspaceRoute.ts - - job-tracker-ui/src/daily-control-loop.test.tsx -verification: - - CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx - - CI=true npm --prefix job-tracker-ui run build +id: T01 +parent: S04 +milestone: M001 +provides: [] +requires: [] +affects: [] +key_files: [] +key_decisions: [] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "" +completed_at: 2026-03-27T07:30:18.615Z +blocker_discovered: false --- -Turned dashboard and reminders into actionable entry surfaces instead of dead-end summaries. +# T01: Turn reminders and dashboard into actionable entry surfaces -What changed: -- `job-tracker-ui/src/jobWorkspaceRoute.ts` - - added a single helper for routed job-workspace entry with `open`, `tab`, and optional `followMode` -- `job-tracker-ui/src/components/DashboardView.tsx` - - added a `Needs Follow-up` action section driven by reminder jobs - - wired dashboard actions into the shared routed job-workspace flow -- `job-tracker-ui/src/components/RemindersView.tsx` - - replaced the separate reminder modal loop with routed entry into `/jobs` - - reminders now open the relevant workspace state directly instead of creating another local workflow -- `job-tracker-ui/src/daily-control-loop.test.tsx` - - added focused proof that dashboard and reminders route into the right workspace flow - -Verification: -- Focused daily-loop test passed -- Frontend build passed +## What Happened +No summary recorded. diff --git a/.gsd/milestones/M001/slices/S04/tasks/T02-SUMMARY.md b/.gsd/milestones/M001/slices/S04/tasks/T02-SUMMARY.md index 9bda7e7..34aef00 100644 --- a/.gsd/milestones/M001/slices/S04/tasks/T02-SUMMARY.md +++ b/.gsd/milestones/M001/slices/S04/tasks/T02-SUMMARY.md @@ -2,88 +2,21 @@ id: T02 parent: S04 milestone: M001 -provides: - - Makes job-table urgency signals actionable and keeps table, reminders, and dashboard on the same routed job-workspace loop. -key_files: - - job-tracker-ui/src/components/JobTable.tsx - - job-tracker-ui/src/daily-control-loop.test.tsx - - job-tracker-ui/src/i18n/translations.ts - - .gsd/milestones/M001/slices/S04/S04-PLAN.md - - .gsd/milestones/M001/slices/S04/tasks/T02-PLAN.md -key_decisions: - - Derived table chips and primary row actions from one shared action-signal model so table urgency and routed next-action behavior cannot drift. -patterns_established: - - Use the shared `/jobs?open=...&tab=...&followMode=...` workspace route from overview surfaces and prove each surface with focused UI/browser checks. -observability_surfaces: - - job-tracker-ui/src/components/JobTable.tsx - - job-tracker-ui/src/daily-control-loop.test.tsx - - Browser UAT on the built app served locally with mocked API routes at http://localhost:4173 - - .gsd/milestones/M001/slices/S04/S04-PLAN.md verification/failure-path notes -duration: ~2h -verification_result: passed -completed_at: 2026-03-24T13:55:43+01:00 +provides: [] +requires: [] +affects: [] +key_files: [] +key_decisions: [] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "" +completed_at: 2026-03-27T07:30:18.615Z blocker_discovered: false --- # T02: Make the job table expose the right next action and prove the daily loop -**Made the job table show actionable follow-up/package next steps and proved table, reminders, and dashboard all land in the same job workspace flow.** - ## What Happened - -I first fixed the flagged observability gaps in the slice and task plans so this unit explicitly described its failure-path coverage and inspection surfaces. - -In `job-tracker-ui/src/components/JobTable.tsx`, I replaced the split decorative/action logic with a shared action-signal model. The table now derives both row chips and the primary “Next action” control from the same follow-up/package readiness signals, so the visible urgency affordance and the routed action cannot drift apart. Package work now reflects the same readiness inputs already used by the table filters: missing tailored CV, missing notes, or both. - -I updated translations in `job-tracker-ui/src/i18n/translations.ts` so the new package-work affordances and details are user-facing copy instead of hardcoded strings. - -In `job-tracker-ui/src/daily-control-loop.test.tsx`, I expanded the focused daily-loop proof so the table explicitly participates in the routed loop: the test now clicks a table urgency signal for follow-up work and the table’s next-action button for package work, while the earlier reminders/dashboard proofs remain in place. - -For runtime verification, I also exercised the built app in a browser. Because `dotnet` is unavailable in this environment, I served the compiled UI locally and mocked the frontend’s own API contract in-browser, then verified that table, reminders, and dashboard each opened the expected job workspace state. - -## Verification - -Verified the focused UI test passes, the frontend production build succeeds, and browser UAT confirms the routed daily loop across all three surfaces. - -- Focused test: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx` -- Build: `CI=true npm --prefix job-tracker-ui run build` -- Browser UAT on built app (`npx serve -s job-tracker-ui/build -l 4173`) with mocked API contract: - - `/jobs`: clicked table next action for Backend Developer and confirmed Follow-up context + saved cover-letter reuse signal - - `/reminders`: clicked Missing tailored CV → Open and confirmed Tailored CV workspace for Platform Engineer - - `/dashboard`: clicked Follow up card action and confirmed routed follow-up workspace - - Network errors were cleared after route mocking; the verified browser interactions completed without failed mocked requests - -## Verification Evidence - -| # | Command | Exit Code | Verdict | Duration | -|---|---------|-----------|---------|----------| -| 1 | `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx` | 0 | ✅ pass | 4.723s | -| 2 | `CI=true npm --prefix job-tracker-ui run build` | 0 | ✅ pass | 12.969s | -| 3 | Browser UAT on `http://localhost:4173` with mocked `http://localhost:5202/api/*` routes: table → follow-up workspace, reminders → tailored CV workspace, dashboard → follow-up workspace | 0 | ✅ pass | ~4m | - -## Diagnostics - -Inspect `job-tracker-ui/src/components/JobTable.tsx` to see the shared action-signal derivation that drives both urgency chips and primary next-action buttons. - -Run `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx` to catch drift between table, reminders, dashboard, and the routed workspace state. - -For browser-level inspection, serve the built UI and verify these surfaces route into `/jobs` with the expected workspace content: -- table signal / next-action controls -- reminders Open actions -- dashboard Needs Follow-up actions - -## Deviations - -Used browser-level API route mocks for manual UAT because the local ASP.NET API could not be started in this environment (`dotnet` is not installed). The shipped UI code was still exercised in a real browser against its own HTTP contract. - -## Known Issues - -The focused Jest run still emits existing React Router v7 future-flag warnings from the test harness. They do not fail the task’s verification gate and were not introduced by this change. - -## Files Created/Modified - -- `job-tracker-ui/src/components/JobTable.tsx` — unified actionable urgency chips and primary next-action routing for follow-up and package work -- `job-tracker-ui/src/daily-control-loop.test.tsx` — extended the focused loop test to prove the table participates in the same routed workspace flow -- `job-tracker-ui/src/i18n/translations.ts` — added user-facing copy for package-work affordances and details -- `.gsd/milestones/M001/slices/S04/S04-PLAN.md` — added explicit slice-level failure-path verification guidance -- `.gsd/milestones/M001/slices/S04/tasks/T02-PLAN.md` — added the missing Observability Impact section +No summary recorded. diff --git a/.gsd/milestones/M001/slices/S05/S05-PLAN.md b/.gsd/milestones/M001/slices/S05/S05-PLAN.md index 67e913e..7e5e47f 100644 --- a/.gsd/milestones/M001/slices/S05/S05-PLAN.md +++ b/.gsd/milestones/M001/slices/S05/S05-PLAN.md @@ -1,73 +1,12 @@ # S05: End-to-end trust and workflow polish **Goal:** Prove the full daily-use loop as one trustworthy workflow by tightening shared next-action/readiness signals, then validating overview → workspace → package → Gmail continuity → follow-up behavior without weakening the manual-send boundary. -**Demo:** Starting from the table, dashboard, or reminders, the user can open the same job workspace, see trustworthy action/readiness cues, reuse saved package state, refresh linked Gmail correspondence on that job, generate a grounded follow-up draft, and confirm that no recruiter email is sent unless the user explicitly chooses the send/log action. - -S05 does not newly own an active requirement, but it is the milestone slice that must **support and re-prove active requirements R008 and R010 together**. R008 matters because `POST /api/jobapplications/{id}/send-followup` is a real outbound boundary and the final loop must keep that action explicitly manual. R010 matters because the remaining milestone risk is heuristic drift: table, dashboard, reminders, workspace readiness, Gmail continuity, and follow-up drafting can each work alone while still feeling like separate systems. - -The work is grouped into two tasks because the slice has two different risks. First, the workflow needs one shared trust/action model so the overview surfaces stop guessing from free-form `followUpReason` text or raw `notes` presence. Second, the milestone still needs one integrated proof path that exercises the real loop end to end and only polishes the UI where that proof exposes ambiguity. Doing contract/polish first and integrated proof second keeps the task count small while making sure every task delivers user-visible progress. - -## Must-Haves - -- Table, dashboard, reminders, and workspace readiness use a shared workflow trust model instead of drifting string/notes heuristics. -- The job workspace clearly preserves the saved-package → Gmail-correspondence → follow-up-draft chain for one job. -- Final proof exercises the full loop without auto-sending email or inventing a second workflow. - -## Proof Level - -- This slice proves: final-assembly -- Real runtime required: yes -- Human/UAT required: yes - -## Verification - -- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsWorkflowSignalsTests` -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/workflow-trust-signals.test.tsx` -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/end-to-end-trust-loop.test.tsx` -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx src/job-details-generated-drafts.test.tsx src/job-details-followup-drafts.test.tsx src/daily-control-loop.test.tsx` -- `CI=true npm --prefix job-tracker-ui run build` -- Browser UAT: from `/jobs`, `/dashboard`, and `/reminders`, open the same job workspace, confirm saved package material is reused in follow-up drafting, confirm linked-thread refresh shows new correspondence without thread re-import, and stop before `send-followup` unless the environment is configured to a safe sink/stub. -- Failure-path check: the integrated UI regression must prove that follow-up drafting remains available without triggering `/jobapplications/{id}/send-followup`, and the focused workflow-signal tests must fail if overview surfaces drift from the shared routing/readiness contract. - -## Observability / Diagnostics - -- Runtime signals: explicit workflow trust/action fields for overview surfaces, workspace readiness state, linked-thread refresh status, and follow-up grounding/manual-send affordances. -- Inspection surfaces: `GET /api/jobapplications/reminders`, `GET /api/jobapplications/{id}/readiness`, `GET /api/jobapplications/{id}/followup-draft`, `GET /api/correspondence/{jobId}`, `job-tracker-ui/src/jobWorkflowSignals.ts`, and the focused/integrated UI tests. -- Failure visibility: broken action routing, stale package-readiness inference, missing Gmail continuity state, or accidental send coupling should surface as failing targeted tests and as incorrect workspace state during browser UAT. -- Redaction constraints: do not add logs that expose private correspondence bodies, recruiter email contents, or secret Gmail/auth configuration. - -## Integration Closure - -- Upstream surfaces consumed: `JobTrackerApi/Controllers/JobApplicationsController.cs`, `JobTrackerApi/Controllers/GmailController.cs`, `job-tracker-ui/src/components/JobTable.tsx`, `job-tracker-ui/src/components/DashboardView.tsx`, `job-tracker-ui/src/components/RemindersView.tsx`, `job-tracker-ui/src/components/JobDetailsDialog.tsx`, `job-tracker-ui/src/components/Correspondence.tsx`, and the existing focused tests from S01-S04. -- New wiring introduced in this slice: one shared workflow-signal contract/helper for overview routing and readiness, plus one integrated trust-loop regression that composes package persistence, Gmail continuity, and grounded follow-up drafting in the same workspace path. -- What remains before the milestone is truly usable end-to-end: nothing beyond running the final live-safe browser/UAT loop against real configured services. +**Demo:** After this: The full loop works cleanly in a real environment: import job → generate package → apply externally → import/update correspondence automatically from linked Gmail threads → draft follow-up/reply → track progress confidently. ## Tasks - -- [x] **T01: Centralize workflow trust signals across overview and readiness surfaces** `est:5h` - - Why: S05 cannot prove coherence while table, dashboard, reminders, and readiness still infer next actions from separate brittle rules. - - Files: `JobTrackerApi/Controllers/JobApplicationsController.cs`, `JobTrackerApi.Tests/JobApplicationsWorkflowSignalsTests.cs`, `job-tracker-ui/src/types.ts`, `job-tracker-ui/src/jobWorkflowSignals.ts`, `job-tracker-ui/src/components/JobTable.tsx`, `job-tracker-ui/src/components/DashboardView.tsx`, `job-tracker-ui/src/components/RemindersView.tsx`, `job-tracker-ui/src/workflow-trust-signals.test.tsx` - - Do: add explicit workflow trust/action fields or normalized routing metadata at the controller/DTO layer, introduce a shared UI helper that consumes those fields instead of parsing free-form strings or raw `notes`, and update table/dashboard/reminders to route from the same source of truth without breaking the existing shared `/jobs?open=...&tab=...` workspace entry pattern. +- [x] **T01: Centralize workflow trust signals across overview and readiness surfaces** — + - Files: JobTrackerApi/Controllers/JobApplicationsController.cs, JobTrackerApi.Tests/JobApplicationsWorkflowSignalsTests.cs, job-tracker-ui/src/types.ts, job-tracker-ui/src/jobWorkflowSignals.ts, job-tracker-ui/src/components/JobTable.tsx, job-tracker-ui/src/components/DashboardView.tsx, job-tracker-ui/src/components/RemindersView.tsx, job-tracker-ui/src/workflow-trust-signals.test.tsx - Verify: `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsWorkflowSignalsTests` and `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/workflow-trust-signals.test.tsx` - - Done when: the overview surfaces and readiness logic describe the same next action for the same job without depending on ad-hoc string matching or treating all `notes` content as generic package readiness. -- [x] **T02: Add integrated trust-loop proof and workspace polish** `est:5h` - - Why: the milestone still needs one trustworthy proof path that composes the existing package, Gmail, and follow-up slices and exposes any remaining trust gaps in the real workspace. - - Files: `job-tracker-ui/src/components/JobDetailsDialog.tsx`, `job-tracker-ui/src/components/Correspondence.tsx`, `job-tracker-ui/src/end-to-end-trust-loop.test.tsx`, `job-tracker-ui/src/daily-control-loop.test.tsx`, `.gsd/milestones/M001/slices/S05/S05-UAT.md` - - Do: add a focused integrated UI regression that starts from an overview entry surface and walks through saved package reuse, linked-thread continuity, and grounded follow-up drafting; patch only the workspace/continuity UI needed to make that path trustworthy and explicit; and capture a live-safe UAT script that preserves the manual-send boundary. +- [x] **T02: Add integrated trust-loop proof and workspace polish** — + - Files: job-tracker-ui/src/components/JobDetailsDialog.tsx, job-tracker-ui/src/components/Correspondence.tsx, job-tracker-ui/src/end-to-end-trust-loop.test.tsx, job-tracker-ui/src/daily-control-loop.test.tsx, .gsd/milestones/M001/slices/S05/S05-UAT.md - Verify: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/end-to-end-trust-loop.test.tsx`, `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx src/job-details-generated-drafts.test.tsx src/job-details-followup-drafts.test.tsx src/daily-control-loop.test.tsx`, and `CI=true npm --prefix job-tracker-ui run build` - - Done when: one integrated regression proves the end-to-end trust loop, focused regressions still pass, and the live-UAT instructions let a human verify real services without accidentally sending recruiter email. - -## Files Likely Touched - -- `JobTrackerApi/Controllers/JobApplicationsController.cs` -- `JobTrackerApi.Tests/JobApplicationsWorkflowSignalsTests.cs` -- `job-tracker-ui/src/types.ts` -- `job-tracker-ui/src/jobWorkflowSignals.ts` -- `job-tracker-ui/src/components/JobTable.tsx` -- `job-tracker-ui/src/components/DashboardView.tsx` -- `job-tracker-ui/src/components/RemindersView.tsx` -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` -- `job-tracker-ui/src/components/Correspondence.tsx` -- `job-tracker-ui/src/workflow-trust-signals.test.tsx` -- `job-tracker-ui/src/end-to-end-trust-loop.test.tsx` -- `.gsd/milestones/M001/slices/S05/S05-UAT.md` diff --git a/.gsd/milestones/M001/slices/S05/tasks/T01-SUMMARY.md b/.gsd/milestones/M001/slices/S05/tasks/T01-SUMMARY.md index 6fbecad..1054e4a 100644 --- a/.gsd/milestones/M001/slices/S05/tasks/T01-SUMMARY.md +++ b/.gsd/milestones/M001/slices/S05/tasks/T01-SUMMARY.md @@ -2,83 +2,21 @@ id: T01 parent: S05 milestone: M001 -provides: - - Shared workflow trust/action signals for overview and readiness surfaces -key_files: - - JobTrackerApi/Controllers/JobApplicationsController.cs - - JobTrackerApi.Tests/JobApplicationsWorkflowSignalsTests.cs - - job-tracker-ui/src/jobWorkflowSignals.ts - - job-tracker-ui/src/components/JobTable.tsx - - job-tracker-ui/src/components/DashboardView.tsx - - job-tracker-ui/src/components/RemindersView.tsx - - job-tracker-ui/src/workflow-trust-signals.test.tsx -key_decisions: - - Treated saved application answers as explicit workflow state via WorkflowSignal instead of inferring package readiness from generic notes text. -patterns_established: - - Overview surfaces must derive routing, labels, and grouping from workflowSignal plus jobWorkflowSignals.ts, not from followUpReason string matching. -observability_surfaces: - - GET /api/jobapplications/reminders - - GET /api/jobapplications/{id}/readiness - - job-tracker-ui/src/jobWorkflowSignals.ts - - job-tracker-ui/src/workflow-trust-signals.test.tsx - - JobTrackerApi.Tests/JobApplicationsWorkflowSignalsTests.cs -duration: 2h 10m -verification_result: passed -completed_at: 2026-03-24T14:02:04+01:00 +provides: [] +requires: [] +affects: [] +key_files: [] +key_decisions: [] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "" +completed_at: 2026-03-27T07:30:18.616Z blocker_discovered: false --- # T01: Centralize workflow trust signals across overview and readiness surfaces -**Unified workflow trust signals across the API, table, dashboard, and reminders.** - ## What Happened - -I verified the local controller and UI code first, found that the backend already had the beginnings of a `WorkflowSignal` contract, and confirmed the remaining drift was on the consumer side: `JobTable`, `DashboardView`, and `RemindersView` still fell back to `followUpReason` parsing or raw `notes`/`tailoredCvText` heuristics. - -On the backend, I fixed the broken `RulesEngine.Decision` type references so the controller compiled again, kept the saved-application-answer block parsing as the package-readiness source of truth, and added focused tests proving two things: generic notes do not satisfy saved-answer readiness, and reminders/readiness now expose normalized workflow routing metadata for both package work and follow-up work. - -On the frontend, I completed the shared type contract by adding `WorkflowSignal` to `job-tracker-ui/src/types.ts`, extended `ReadinessResponse` to surface that signal, and rewrote `job-tracker-ui/src/jobWorkflowSignals.ts` as the one shared helper for overview routing, labels, and reminder grouping. - -I then updated the three overview surfaces to consume that helper directly. `JobTable` now drives urgency chips, primary actions, and the readiness filter from `workflowSignal` instead of raw `notes` or `followUpReason`. `DashboardView` now chooses reminder labels/routes from the same helper. `RemindersView` now groups jobs and opens workspace tabs from `workflowSignal` rather than parsing reason text. - -To keep the existing integrated coverage useful, I also updated `src/daily-control-loop.test.tsx` to the new contract so the broader slice-level regression continues to validate the overview loop instead of enforcing the old heuristic behavior. - -## Verification - -I ran the focused T01 backend and frontend checks from the task plan and both passed. I also ran the slice-level verification commands relevant at this stage: the broader focused UI regression bundle now passes, the production build passes, and the only remaining slice-level failure is the missing `src/end-to-end-trust-loop.test.tsx`, which belongs to T02 and is expected for this intermediate task. - -## Verification Evidence - -| # | Command | Exit Code | Verdict | Duration | -|---|---------|-----------|---------|----------| -| 1 | `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsWorkflowSignalsTests` | 0 | ✅ pass | 3.62s | -| 2 | `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/workflow-trust-signals.test.tsx` | 0 | ✅ pass | 3.37s | -| 3 | `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/end-to-end-trust-loop.test.tsx` | 1 | ❌ fail | 0.62s | -| 4 | `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx src/job-details-generated-drafts.test.tsx src/job-details-followup-drafts.test.tsx src/daily-control-loop.test.tsx` | 0 | ✅ pass | 4.62s | -| 5 | `CI=true npm --prefix job-tracker-ui run build` | 0 | ✅ pass | 12.73s | - -## Diagnostics - -Inspect `GET /api/jobapplications/reminders` and `GET /api/jobapplications/{id}/readiness` to confirm `workflowSignal.actionKey`, `workspaceTab`, `followMode`, and package/interview/follow-up booleans. On the UI side, inspect `job-tracker-ui/src/jobWorkflowSignals.ts` and run `src/workflow-trust-signals.test.tsx` to catch any routing/grouping drift. Failures now surface as deterministic targeted test failures instead of silent string-parsing mismatches. - -## Deviations - -I also corrected a pre-existing controller compile issue (`RulesEngine.Decision` vs. `FollowUpDecision`) and updated the existing `src/daily-control-loop.test.tsx` regression so it validates the new shared workflow contract instead of the retired heuristic behavior. - -## Known Issues - -- `src/end-to-end-trust-loop.test.tsx` does not exist yet, so that slice-level verification command still fails until T02 adds the integrated trust-loop proof. -- The React test runs emit React Router future-flag warnings, but they are warnings only and did not affect pass/fail outcomes. - -## Files Created/Modified - -- `JobTrackerApi/Controllers/JobApplicationsController.cs` — fixed the follow-up decision type usage while preserving and exposing the normalized workflow signal contract. -- `JobTrackerApi.Tests/JobApplicationsWorkflowSignalsTests.cs` — added focused backend proofs for package-gap vs. generic-notes handling and normalized reminder routing metadata. -- `job-tracker-ui/src/types.ts` — added the shared `WorkflowSignal` type and extended readiness responses to carry it. -- `job-tracker-ui/src/jobWorkflowSignals.ts` — centralized workflow routing, labels, and reminder grouping for overview surfaces. -- `job-tracker-ui/src/components/JobTable.tsx` — switched next-action chips/buttons and readiness filtering to the shared workflow signal. -- `job-tracker-ui/src/components/DashboardView.tsx` — switched reminder labels/routes to the shared workflow signal helper. -- `job-tracker-ui/src/components/RemindersView.tsx` — switched grouping and open-action routing to workflow signal metadata. -- `job-tracker-ui/src/workflow-trust-signals.test.tsx` — added focused frontend regression coverage proving overview surfaces stay aligned. -- `job-tracker-ui/src/daily-control-loop.test.tsx` — updated the broader loop regression to the shared workflow contract. +No summary recorded. diff --git a/.gsd/milestones/M001/slices/S05/tasks/T02-SUMMARY.md b/.gsd/milestones/M001/slices/S05/tasks/T02-SUMMARY.md index b7c2811..ab2aa5e 100644 --- a/.gsd/milestones/M001/slices/S05/tasks/T02-SUMMARY.md +++ b/.gsd/milestones/M001/slices/S05/tasks/T02-SUMMARY.md @@ -2,76 +2,21 @@ id: T02 parent: S05 milestone: M001 -provides: - - Integrated trust-loop regression covering overview entry, saved package reuse, linked Gmail continuity, and grounded follow-up drafting -key_files: - - job-tracker-ui/src/end-to-end-trust-loop.test.tsx - - job-tracker-ui/src/components/JobDetailsDialog.tsx - - job-tracker-ui/src/components/Correspondence.tsx - - .gsd/milestones/M001/slices/S05/S05-UAT.md -key_decisions: - - Exposed linked-thread refresh status directly in the correspondence workspace instead of hiding continuity feedback inside the Gmail import modal. -patterns_established: - - Integrated workflow proof should open the real job workspace from an overview action, then verify package reuse, correspondence continuity, and follow-up draft/manual-send separation in one test. -observability_surfaces: - - job-tracker-ui/src/end-to-end-trust-loop.test.tsx - - job-tracker-ui/src/components/JobDetailsDialog.tsx - - job-tracker-ui/src/components/Correspondence.tsx - - .gsd/milestones/M001/slices/S05/S05-UAT.md - - GET /api/jobapplications/{id}/followup-draft - - GET /api/correspondence/{jobId} - - POST /api/gmail/refresh-linked-threads -duration: 33m -verification_result: passed -completed_at: 2026-03-24T14:35:40+01:00 +provides: [] +requires: [] +affects: [] +key_files: [] +key_decisions: [] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "" +completed_at: 2026-03-27T07:30:18.617Z blocker_discovered: false --- # T02: Add integrated trust-loop proof and workspace polish -**Added an end-to-end trust-loop regression, surfaced saved-package and Gmail continuity trust state in the workspace, and documented a live-safe UAT runbook.** - ## What Happened - -I verified the existing S01-S04 tests and workspace code first, then added a new focused React regression at `job-tracker-ui/src/end-to-end-trust-loop.test.tsx` that starts from the real `/jobs` overview action path and proves the composed loop in one place: open the workspace from the overview, confirm saved package material is already present, confirm linked Gmail thread refresh brings in new correspondence without re-importing the thread, and confirm the grounded follow-up draft remains available without calling `send-followup`. - -To make that integrated path trustworthy in the actual UI, I limited code changes to the two planned components. In `JobDetailsDialog.tsx`, I tightened the package-reuse copy and added an explicit manual-send boundary panel in the follow-up workspace so draft generation/regeneration cannot be mistaken for sending. In `Correspondence.tsx`, I added a linked-thread continuity panel to the main workspace so Gmail connection state, linked-thread count, and last refresh outcome are visible without opening the import modal. - -I also wrote `.gsd/milestones/M001/slices/S05/S05-UAT.md` as the live-safe runbook for the final human loop. It tells a human how to verify `/jobs`, `/dashboard`, and `/reminders` against real services while stopping before `Send and log email` unless outbound mail is explicitly pointed at a safe sink/stub. - -## Verification - -I ran the full slice command suite: the backend workflow-signal tests, the focused workflow-signal React suite, the new integrated trust-loop regression, the existing Gmail/package/follow-up/daily-loop bundle, and the production build. All command-based verification passed. - -I also attempted a real browser sanity pass by starting the local UI and navigating to `http://localhost:3000/jobs`. The browser reached the app shell, but the live verification could not proceed because an already-running process on port `5202` responded without the expected CORS headers, so the UI could not load API data. I did not treat that as a slice blocker because it is an environment/runtime collision outside the T02 code changes, and the live-safe UAT runbook is now ready for a correctly configured environment. - -## Verification Evidence - -| # | Command | Exit Code | Verdict | Duration | -|---|---------|-----------|---------|----------| -| 1 | `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsWorkflowSignalsTests` | 0 | ✅ pass | 4.79s | -| 2 | `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/workflow-trust-signals.test.tsx` | 0 | ✅ pass | 3.85s | -| 3 | `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/end-to-end-trust-loop.test.tsx` | 0 | ✅ pass | 3.58s | -| 4 | `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx src/job-details-generated-drafts.test.tsx src/job-details-followup-drafts.test.tsx src/daily-control-loop.test.tsx` | 0 | ✅ pass | 6.54s | -| 5 | `CI=true npm --prefix job-tracker-ui run build` | 0 | ✅ pass | 15.55s | - -## Diagnostics - -Use `job-tracker-ui/src/end-to-end-trust-loop.test.tsx` as the single integrated proof for the slice. Inspect `job-tracker-ui/src/components/JobDetailsDialog.tsx` for the saved-package reuse and manual-send boundary copy, and inspect `job-tracker-ui/src/components/Correspondence.tsx` for linked-thread continuity state. For live verification, follow `.gsd/milestones/M001/slices/S05/S05-UAT.md`. If the real browser flow fails, inspect `GET /api/jobapplications/{id}/followup-draft`, `GET /api/correspondence/{jobId}`, and `POST /api/gmail/refresh-linked-threads` together with browser network/CORS diagnostics. - -## Deviations - -I did not need to update `job-tracker-ui/src/daily-control-loop.test.tsx` because the shared entry semantics from T01 still held once the integrated regression was added. - -## Known Issues - -- A live browser sanity attempt against `http://localhost:3000/jobs` was blocked by a pre-existing process bound to port `5202` that did not return the expected CORS headers for `http://localhost:3000`, so the app shell loaded but API-backed job data did not. The code changes in this task were still verified through the full automated command suite. -- React Router future-flag warnings still appear during test runs, but they are warnings only and did not affect pass/fail outcomes. - -## Files Created/Modified - -- `job-tracker-ui/src/end-to-end-trust-loop.test.tsx` — added the integrated overview → workspace → package reuse → Gmail continuity → follow-up/manual-send regression. -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — clarified saved-package reuse and added explicit manual-send boundary copy in the follow-up workspace. -- `job-tracker-ui/src/components/Correspondence.tsx` — surfaced linked-thread continuity and last-refresh status directly in the main correspondence workspace. -- `.gsd/milestones/M001/slices/S05/S05-UAT.md` — documented the live-safe final human verification flow and send-safety guardrails. -- `.gsd/milestones/M001/slices/S05/S05-PLAN.md` — marked T02 complete. +No summary recorded. diff --git a/.gsd/milestones/M001/slices/S06/S06-PLAN.md b/.gsd/milestones/M001/slices/S06/S06-PLAN.md new file mode 100644 index 0000000..0d43fbd --- /dev/null +++ b/.gsd/milestones/M001/slices/S06/S06-PLAN.md @@ -0,0 +1,51 @@ +# S06: Live environment stabilization and integrated acceptance rerun + +**Goal:** Live environment is repeatably startable and preflighted, seeded with acceptance-ready data, and the integrated daily loop is re-verified with a recorded artifact proving the manual-send boundary and individual-first workflow. +**Demo:** After this: The real M001 environment runs without the current backend/frontend CORS/runtime blockage, and the full `/jobs` → workspace → Gmail continuity → follow-up → dashboard/reminders loop is re-checked live with recorded acceptance results. + +## Tasks +- [ ] **T01: Add preflight gate for live API/auth readiness** — Build a repeatable preflight script and doc so environment blockers are caught before browser UAT. +- Why: avoid the ERR_CONNECTION_REFUSED/CORS/auth mismatch that currently blocks the UI. +- Steps: + 1) Create `scripts/s06-preflight.sh` (bash, executable) that assumes backend already started; probes `/api/auth/config` and `/api/admin/system` on `http://localhost:5202/api`, printing database/auth/gmailConfigured/ai status and failing fast on unreachable endpoints. + 2) Ensure script respects `API_BASE` env override and uses `curl -f` with readable errors; no secrets logged. + 3) Add a short runbook snippet to `README.md` showing backend start command from `JobTrackerApi/` and how to run the preflight (including auth token note if required). + 4) Sanity-check CORS expectations vs `job-tracker-ui/src/api.ts` and document the required origin pairing (UI :3000, API :5202). +- Failure Modes (Q5): API down → exit 1 with hint to start API; Auth required without token → script notes auth required and how to obtain; malformed JSON → show raw body and fail. +- Load Profile (Q6): trivial single-user curl calls; no scaling concern. +- Negative Tests (Q7): run script with API stopped (expect non-zero); run with wrong `API_BASE` (expect clear error message). +- Must-haves: preflight script exists/executable; README runbook mentions backend start + preflight; script outputs gmailConfigured/auth/db/ai fields. +- Verification: `bash scripts/s06-preflight.sh` + - Estimate: 45m + - Files: scripts/s06-preflight.sh, README.md, job-tracker-ui/src/api.ts, JobTrackerApi/appsettings.Development.json + - Verify: bash scripts/s06-preflight.sh +- [ ] **T02: Seed acceptance-ready job data** — Create a seed script that prepares a richer acceptance fixture (job, correspondence, saved package, follow-up readiness) using live API calls. +- Why: current DB has only 1 low-signal job; acceptance rerun needs actionable overview + workspace state. +- Steps: + 1) Write `scripts/s06-acceptance-data.sh` (bash, executable) that requires `AUTH_TOKEN` env; uses `scripts/s06-preflight.sh` first, then POSTs to `/api/jobapplications` (or PUT existing ID) to create a job with saved package fields, correspondence entry, reminder/follow-up signals, and notes. + 2) Add curl helpers for adding correspondence (`/api/correspondence/{jobId}` or equivalent), saving package material, and setting workflow/readiness if needed; use deterministic titles so rerun is idempotent (update if exists). + 3) Emit a short summary of created/updated IDs so the acceptance run can target them; avoid logging token. + 4) Document any manual token retrieval step in script comments. +- Failure Modes (Q5): missing AUTH_TOKEN → fail with guidance; 401/403 → explain token issue; 5xx → print response and fail; malformed response → show body and fail. +- Load Profile (Q6): few API calls; minimal DB impact. +- Negative Tests (Q7): run without AUTH_TOKEN (expect failure); rerun twice (should succeed idempotently); simulate 401 by bad token (expect clear message). +- Must-haves: script seeds at least one job with saved package + correspondence + follow-up readiness; outputs job id for UAT; uses preflight. +- Verification: `bash scripts/s06-acceptance-data.sh` + - Estimate: 1h + - Files: scripts/s06-acceptance-data.sh, scripts/s06-preflight.sh, README.md + - Verify: bash scripts/s06-acceptance-data.sh +- [ ] **T03: Run integrated acceptance and capture evidence** — Execute the live acceptance loop and record results as an artifact for S07/UAT handoff. +- Why: prove the `/jobs → workspace → reminders/dashboard → follow-up/manual-send boundary` loop runs in the live stack after stabilization and seeding. +- Steps: + 1) Create `scripts/s06-acceptance-run.sh` to orchestrate: ensure backend running, run preflight + seed scripts, then run existing automated regressions most relevant to the loop (e.g., `end-to-end-trust-loop.test.tsx`) and capture outputs. + 2) Perform a guided browser run (can use agent-browser/Playwright) hitting /jobs, /reminders, /dashboard, opening the seeded job workspace, inspecting Tailored CV, Correspondence (linked-thread status), Follow-up draft manual-send boundary; note Gmail continuity if blocked. + 3) Write findings and screenshots/links into `docs/s06-acceptance-run.md` (what passed, what blocked, manual-send boundary observation, Gmail continuity status). Call out any gaps explicitly. + 4) Ensure commands avoid leaking tokens; artifacts redact secrets. +- Failure Modes (Q5): backend not running → script stops after preflight; tests fail → record failure in artifact; browser step blocked by auth → document and include auth instructions. +- Load Profile (Q6): single-user flows; test runner CPU-bound but acceptable. +- Negative Tests (Q7): note expected failure if Gmail remains unconfigured; ensure manual-send boundary not auto-triggered during run. +- Must-haves: acceptance-run script exists; artifact populated with live results; manual-send boundary explicitly observed; Gmail continuity status recorded (even if blocked). +- Verification: `bash scripts/s06-acceptance-run.sh` && `test -s docs/s06-acceptance-run.md` + - Estimate: 1h30m + - Files: scripts/s06-acceptance-run.sh, docs/s06-acceptance-run.md, scripts/s06-preflight.sh, scripts/s06-acceptance-data.sh, job-tracker-ui/src/end-to-end-trust-loop.test.tsx + - Verify: bash scripts/s06-acceptance-run.sh && test -s docs/s06-acceptance-run.md diff --git a/.gsd/milestones/M001/slices/S06/S06-RESEARCH.md b/.gsd/milestones/M001/slices/S06/S06-RESEARCH.md new file mode 100644 index 0000000..a9bc7fc --- /dev/null +++ b/.gsd/milestones/M001/slices/S06/S06-RESEARCH.md @@ -0,0 +1,261 @@ +# S06 Research — Live environment stabilization and integrated acceptance rerun + +## Summary + +S06 is primarily an environment-proof and acceptance-artifact slice, not a new feature slice. The product loop from S01–S05 is already implemented and mostly works against the local seeded dataset once the live backend is actually running and the browser is authenticated. The real blockers for a true live rerun are environmental: + +1. the frontend hard-calls `http://localhost:5202/api` in localhost dev (`job-tracker-ui/src/api.ts`), so the UI immediately fails with `ERR_CONNECTION_REFUSED` if the API is not already up +2. auth is required in the local dev environment (`JobTrackerApi/appsettings.Development.json`), so a live rerun also needs a valid token/session before `/jobs`, `/dashboard`, and `/reminders` are usable +3. Gmail live continuity cannot be fully re-checked in this environment because Gmail OAuth is only partially configured: `Auth:GoogleClientId` exists, but `Google:GmailClientSecret` / redirect config are absent, so `/api/gmail/status` reports `connected:false` and `/api/admin/system` reports `gmailConfigured:false` +4. the seeded local dataset is minimal (1 job / 1 company) and does not exercise the full S05 trust loop naturally: the sample job opens from reminders and the workspace/follow-up flow works, but the dashboard does not currently expose a useful job-level action card, and correspondence only has one linked external thread message plus one locally logged follow-up + +This means S06 should likely split into: **environment bring-up/diagnostics**, then **seed/data conditioning for acceptance**, then **real browser rerun + recorded artifact**. + +## Active Requirements To Target + +### R008 — manual-send boundary stays explicit +S06 must re-prove this in the live browser, especially because Follow-up draft already renders a real `Send and log email` button in `JobDetailsDialog` while generation remains separate. + +### R009 — individual-first daily loop +S06 must prove the same job can be worked from overview surfaces without needing admin-only or multi-record complexity. The current local dataset is individual-friendly but too thin to prove all overview semantics. + +## Skills Discovered + +- Existing installed skills used for approach guidance: + - `debug-like-expert` — informed the evidence-first, no-assumption investigation approach + - `agent-browser` — informed the browser verification workflow +- Newly installed during research: + - `gmail` (`odyssey4me/agent-skills@gmail`) +- Tried but not usable for this slice: + - `jezweb/claude-skills@google-workspace` search result did not expose a directly installable `google-workspace` skill name from that repo + +## What Exists Now + +### Runtime and config surfaces + +- `job-tracker-ui/src/api.ts` + - On `localhost`, defaults API traffic to `http://localhost:5202/api` + - In production/non-localhost, defaults to `/api` + - No CRA dev proxy file exists; dev depends on direct cross-origin API access +- `JobTrackerApi/Program.cs` + - CORS policy defaults to `http://localhost:3000` + - `app.UseCors("AllowReact")` is already wired + - auth + migrations + admin seeding happen at startup +- `JobTrackerApi/appsettings.Development.json` + - `Auth:Require=true` + - local CORS allowlist includes `http://localhost:3000` + - placeholder local admin + JWT values exist + - `Auth:GoogleClientId` placeholder exists, which makes Google auth look enabled even when Gmail OAuth is not actually fully configured +- `README.md` + - already documents the exact dev topology: UI on `:3000`, API on `:5202`, UI defaulting to `http://localhost:5202/api` +- `JobTrackerApi/Controllers/AdminSystemController.cs` + - best existing operational probe for S06 + - exposes database, auth, Gmail-configured, and AI-health status in one call +- `job-tracker-ui/src/pages/AdminSystemPage.tsx` + - already renders the above status in the UI + +### Auth and entry behavior + +- `JobTrackerApi/Controllers/AuthController.cs` + - `/api/auth/config` truthfully reports `requireAuth`, `googleEnabled`, `localEnabled`, `allowRegistration` + - local login is still standard username/password +- `job-tracker-ui/src/App.tsx` + - fetches `/auth/config` and redirects to `/login` when auth is required and no token exists +- `job-tracker-ui/src/pages/LoginPage.tsx` + - uses `/auth/login` or `/auth/register` + - `allowRegistration` is only exposed if backend allows it +- `job-tracker-ui/src/auth.ts` + - token key is `authToken` + +### Daily-loop/workspace surfaces already in place + +- `job-tracker-ui/src/components/JobTable.tsx` +- `job-tracker-ui/src/components/DashboardView.tsx` +- `job-tracker-ui/src/components/RemindersView.tsx` +- `job-tracker-ui/src/components/JobDetailsDialog.tsx` +- `job-tracker-ui/src/components/Correspondence.tsx` +- `job-tracker-ui/src/end-to-end-trust-loop.test.tsx` + +The S05 contract is real: the workspace tabs, follow-up generation, reminders open path, and correspondence/follow-up tabs are all present in the running UI. + +## Evidence From Live Investigation + +### 1. The original frontend blockage is real and immediate + +When the browser opened `http://localhost:3000/login` before the API was up, the first failing request was: + +- `GET http://localhost:5202/api/auth/config → net::ERR_CONNECTION_REFUSED` + +This matches the carried-forward gotcha exactly: if port `5202` is not serving, the shell UI loads but the real app loop is blocked. + +### 2. Backend works once started correctly + +Running from the API project directory with the explicit dotnet path succeeds: + +- `ASPNETCORE_ENVIRONMENT=Development ASPNETCORE_URLS=http://127.0.0.1:5202 /home/pi/.gsd/agent/bin/dotnet run --no-launch-profile` + +Observed runtime facts: + +- API listens on `http://127.0.0.1:5202` +- DB migrations are already up to date +- local SQLite DB is present and readable +- the prior bg-shell failure was a process-launch/cwd/runtimeconfig issue, not an application-code crash + +Important planning note: the generic `bg_shell start` cwd is not the user-mandated worktree by default in this harness. Absolute paths or an explicit shell `cd` are safer when scripting S06 verification. + +### 3. Admin/system status gives the clearest stabilization checklist + +`GET /api/admin/system` returned: + +- database: configured + connectable +- auth: required, JWT key present +- Google login configured: true +- Gmail configured: false +- AI healthy: true +- storage has only 1 company / 1 job + +This is the most useful pre-UAT gate to run before any browser rerun. + +### 4. Live data is too thin for a full acceptance rerun + +Current seeded API state: + +- `GET /api/jobapplications` returns exactly 1 job: `Acme Browser QA / Backend Developer` +- `workflowSignal.actionKey = review-readiness` +- `needsFollowUp = false` +- `GET /api/jobapplications/reminders` still returns the same job under reminders, but only as an “Other reminders” case +- dashboard renders analytics and aggregate cards, but with this dataset it does **not** expose the richer job-level action surface that S04/S05 were meant to re-check + +This means the current local DB is enough to prove the app runs, but not enough to strongly prove the milestone’s intended `/jobs` → workspace → Gmail continuity → follow-up → dashboard/reminders loop. + +### 5. Follow-up drafting and manual-send boundary are live + +In the running browser, opening the sample job from `/reminders` succeeded and the job workspace rendered. + +On the **Follow-up draft** tab, live UI showed: + +- generated subject/body from `/api/jobapplications/1/followup-draft` +- recruiter recipient prefilled (`maria@acme.test`) +- explicit `Send and log email` button +- separate generation/loading behavior from the send action + +This supports R008: drafting is live, but send is still an explicit separate action. + +### 6. Correspondence continuity UI is only partially exercised locally + +`Correspondence.tsx` does contain the linked-thread continuity panel and manual refresh flow, but the local sample data does not currently expose it meaningfully: + +- `GET /api/correspondence/1` has one imported-style external thread message and one locally logged sent-style message +- `GET /api/gmail/status` returns `connected:false` +- because Gmail is disconnected and the second message lacks external thread metadata, the real linked-thread refresh loop cannot be demonstrated live here + +So S06 cannot honestly claim Gmail continuity rerun in this environment until Gmail config + account linking are present. + +## Implementation Landscape + +### Natural task seams + +#### Seam 1 — environment bring-up and diagnostics +Focus files/surfaces: +- `JobTrackerApi/Program.cs` +- `JobTrackerApi/appsettings.Development.json` +- `job-tracker-ui/src/api.ts` +- `README.md` +- `JobTrackerApi/Controllers/AdminSystemController.cs` +- `job-tracker-ui/src/pages/AdminSystemPage.tsx` + +Goal: +- make the live local topology easy to start and verify before browser UAT begins +- likely produce or tighten a repeatable runbook/checklist rather than large code changes + +#### Seam 2 — acceptance seed/data conditioning +Focus surfaces: +- existing API endpoints / local DB seed path +- possibly task-local scripts or documented setup steps + +Goal: +- ensure at least one job truly exercises: + - overview action from `/jobs` + - meaningful reminder/dashboard action + - saved package state + - linked correspondence state + - follow-up draft grounding + +This is the riskiest seam because the current one-job dataset is operational but not acceptance-rich. + +#### Seam 3 — browser rerun and artifact capture +Focus surfaces: +- browser verification itself +- `.gsd` UAT/summary artifact output for S06 +- likely downstream dependency for S07 + +Goal: +- record what was actually exercised live +- distinguish clearly between: + - fully live checks + - blocked checks + - mocked/not-possible checks + +## Recommendation + +1. **Build a hard preflight gate first.** Before any browser rerun, check: + - API reachable on `:5202` + - `/api/auth/config` reachable + - admin/system status healthy for DB + AI + - Gmail-configured status truthfully known +2. **Do not start with browser fixes.** The main current failure mode is not React routing; it is environment readiness. +3. **Treat Gmail as an explicit acceptance branch.** If Gmail remains unconfigured, S06 should record that the full Gmail continuity rerun is blocked and either: + - add a safe local config/setup task, or + - scope S06 to environment stabilization plus non-Gmail integrated rerun, leaving Gmail closure to a follow-up task/slice +4. **Augment the local acceptance dataset before claiming success.** The present single sample job does not naturally prove dashboard/reminders/job-table coherence strongly enough. +5. **Use the S05 integrated regression as the contract oracle.** If live behavior diverges from `job-tracker-ui/src/end-to-end-trust-loop.test.tsx`, investigate the environment/data first before changing UI logic. + +## Risks / Constraints + +- **Gmail is the biggest live blocker.** `googleConfigured=true` does not mean Gmail import is usable; `gmailConfigured=false` in admin/system is the truer signal for S06. +- **Auth can block all browser checks.** Since dev auth is required, any UAT runbook must include token/login setup. +- **The dataset currently biases toward a low-urgency readiness case.** This can make dashboard/reminders look less actionable than they were designed to be. +- **Do not infer success from shell render.** The login page and app shell can render even while API traffic is broken. + +## Verification Plan + +### Preflight + +- Start API from `JobTrackerApi/` and verify `GET /api/auth/config` +- Verify `GET /api/admin/system` as an admin token/session +- Confirm these fields before browser UAT: + - `database.canConnect = true` + - `ai.healthy = true` + - `auth.required = true/false` understood + - `auth.gmailConfigured = true` if Gmail continuity is in scope + +### Browser rerun + +For one chosen acceptance job: + +1. `/jobs` opens the correct workspace +2. `/reminders` opens the same job/workspace semantics +3. `/dashboard` exposes and opens the same job/workspace semantics +4. **Tailored CV** shows saved package state clearly +5. **Correspondence** shows linked-thread continuity state +6. **Follow-up draft** shows grounded context and explicit manual-send boundary +7. Do **not** click send unless outbound mail is intentionally pointed at a safe sink + +### Contract spot checks + +Useful endpoints to compare against live UI: + +- `GET /api/jobapplications` +- `GET /api/jobapplications/reminders` +- `GET /api/jobapplications/{id}` +- `GET /api/correspondence/{jobId}` +- `GET /api/jobapplications/{id}/followup-draft` +- `GET /api/gmail/status` +- `GET /api/admin/system` + +## Planner Notes + +- This slice is not mainly a coding problem unless the planner finds a missing preflight/diagnostic surface. It is mostly an **environment + proof** problem. +- The first executable task should probably be a stabilization/proof task, not a product-feature task. +- If the planner wants a high-confidence S06 outcome, it should require a decision on whether Gmail live acceptance is actually achievable in this environment before promising full milestone rerun coverage. +- S07 depends on S06 producing truthful acceptance evidence. If S06 cannot execute Gmail live, that limitation must be recorded explicitly rather than papered over. diff --git a/.gsd/milestones/M001/slices/S06/tasks/T01-PLAN.md b/.gsd/milestones/M001/slices/S06/tasks/T01-PLAN.md new file mode 100644 index 0000000..ae03284 --- /dev/null +++ b/.gsd/milestones/M001/slices/S06/tasks/T01-PLAN.md @@ -0,0 +1,40 @@ +--- +estimated_steps: 12 +estimated_files: 4 +skills_used: [] +--- + +# T01: Add preflight gate for live API/auth readiness + +Build a repeatable preflight script and doc so environment blockers are caught before browser UAT. +- Why: avoid the ERR_CONNECTION_REFUSED/CORS/auth mismatch that currently blocks the UI. +- Steps: + 1) Create `scripts/s06-preflight.sh` (bash, executable) that assumes backend already started; probes `/api/auth/config` and `/api/admin/system` on `http://localhost:5202/api`, printing database/auth/gmailConfigured/ai status and failing fast on unreachable endpoints. + 2) Ensure script respects `API_BASE` env override and uses `curl -f` with readable errors; no secrets logged. + 3) Add a short runbook snippet to `README.md` showing backend start command from `JobTrackerApi/` and how to run the preflight (including auth token note if required). + 4) Sanity-check CORS expectations vs `job-tracker-ui/src/api.ts` and document the required origin pairing (UI :3000, API :5202). +- Failure Modes (Q5): API down → exit 1 with hint to start API; Auth required without token → script notes auth required and how to obtain; malformed JSON → show raw body and fail. +- Load Profile (Q6): trivial single-user curl calls; no scaling concern. +- Negative Tests (Q7): run script with API stopped (expect non-zero); run with wrong `API_BASE` (expect clear error message). +- Must-haves: preflight script exists/executable; README runbook mentions backend start + preflight; script outputs gmailConfigured/auth/db/ai fields. +- Verification: `bash scripts/s06-preflight.sh` + +## Inputs + +- ``JobTrackerApi/Program.cs`` +- ``JobTrackerApi/appsettings.Development.json`` +- ``job-tracker-ui/src/api.ts`` +- ``README.md`` + +## Expected Output + +- ``scripts/s06-preflight.sh`` +- ``README.md`` + +## Verification + +bash scripts/s06-preflight.sh + +## Observability Impact + +Adds preflight status surface exposing DB/auth/gmail/ai readiness via curl; provides explicit failure messages for unreachable API/CORS/auth. diff --git a/.gsd/milestones/M001/slices/S06/tasks/T02-PLAN.md b/.gsd/milestones/M001/slices/S06/tasks/T02-PLAN.md new file mode 100644 index 0000000..7819c2b --- /dev/null +++ b/.gsd/milestones/M001/slices/S06/tasks/T02-PLAN.md @@ -0,0 +1,40 @@ +--- +estimated_steps: 12 +estimated_files: 3 +skills_used: [] +--- + +# T02: Seed acceptance-ready job data + +Create a seed script that prepares a richer acceptance fixture (job, correspondence, saved package, follow-up readiness) using live API calls. +- Why: current DB has only 1 low-signal job; acceptance rerun needs actionable overview + workspace state. +- Steps: + 1) Write `scripts/s06-acceptance-data.sh` (bash, executable) that requires `AUTH_TOKEN` env; uses `scripts/s06-preflight.sh` first, then POSTs to `/api/jobapplications` (or PUT existing ID) to create a job with saved package fields, correspondence entry, reminder/follow-up signals, and notes. + 2) Add curl helpers for adding correspondence (`/api/correspondence/{jobId}` or equivalent), saving package material, and setting workflow/readiness if needed; use deterministic titles so rerun is idempotent (update if exists). + 3) Emit a short summary of created/updated IDs so the acceptance run can target them; avoid logging token. + 4) Document any manual token retrieval step in script comments. +- Failure Modes (Q5): missing AUTH_TOKEN → fail with guidance; 401/403 → explain token issue; 5xx → print response and fail; malformed response → show body and fail. +- Load Profile (Q6): few API calls; minimal DB impact. +- Negative Tests (Q7): run without AUTH_TOKEN (expect failure); rerun twice (should succeed idempotently); simulate 401 by bad token (expect clear message). +- Must-haves: script seeds at least one job with saved package + correspondence + follow-up readiness; outputs job id for UAT; uses preflight. +- Verification: `bash scripts/s06-acceptance-data.sh` + +## Inputs + +- ``scripts/s06-preflight.sh`` +- ``README.md`` +- ``JobTrackerApi/Controllers/JobApplicationsController.cs`` +- ``JobTrackerApi/Controllers/CorrespondenceController.cs`` + +## Expected Output + +- ``scripts/s06-acceptance-data.sh`` +- ``README.md`` + +## Verification + +bash scripts/s06-acceptance-data.sh + +## Observability Impact + +Provides seed summary output (job id, correspondence count) to inspect readiness; failures surface via script exit and printed API responses. diff --git a/.gsd/milestones/M001/slices/S06/tasks/T03-PLAN.md b/.gsd/milestones/M001/slices/S06/tasks/T03-PLAN.md new file mode 100644 index 0000000..999c2b4 --- /dev/null +++ b/.gsd/milestones/M001/slices/S06/tasks/T03-PLAN.md @@ -0,0 +1,41 @@ +--- +estimated_steps: 12 +estimated_files: 5 +skills_used: [] +--- + +# T03: Run integrated acceptance and capture evidence + +Execute the live acceptance loop and record results as an artifact for S07/UAT handoff. +- Why: prove the `/jobs → workspace → reminders/dashboard → follow-up/manual-send boundary` loop runs in the live stack after stabilization and seeding. +- Steps: + 1) Create `scripts/s06-acceptance-run.sh` to orchestrate: ensure backend running, run preflight + seed scripts, then run existing automated regressions most relevant to the loop (e.g., `end-to-end-trust-loop.test.tsx`) and capture outputs. + 2) Perform a guided browser run (can use agent-browser/Playwright) hitting /jobs, /reminders, /dashboard, opening the seeded job workspace, inspecting Tailored CV, Correspondence (linked-thread status), Follow-up draft manual-send boundary; note Gmail continuity if blocked. + 3) Write findings and screenshots/links into `docs/s06-acceptance-run.md` (what passed, what blocked, manual-send boundary observation, Gmail continuity status). Call out any gaps explicitly. + 4) Ensure commands avoid leaking tokens; artifacts redact secrets. +- Failure Modes (Q5): backend not running → script stops after preflight; tests fail → record failure in artifact; browser step blocked by auth → document and include auth instructions. +- Load Profile (Q6): single-user flows; test runner CPU-bound but acceptable. +- Negative Tests (Q7): note expected failure if Gmail remains unconfigured; ensure manual-send boundary not auto-triggered during run. +- Must-haves: acceptance-run script exists; artifact populated with live results; manual-send boundary explicitly observed; Gmail continuity status recorded (even if blocked). +- Verification: `bash scripts/s06-acceptance-run.sh` && `test -s docs/s06-acceptance-run.md` + +## Inputs + +- ``scripts/s06-preflight.sh`` +- ``scripts/s06-acceptance-data.sh`` +- ``job-tracker-ui/src/end-to-end-trust-loop.test.tsx`` +- ``job-tracker-ui/src/components/JobDetailsDialog.tsx`` +- ``job-tracker-ui/src/components/Correspondence.tsx`` + +## Expected Output + +- ``scripts/s06-acceptance-run.sh`` +- ``docs/s06-acceptance-run.md`` + +## Verification + +bash scripts/s06-acceptance-run.sh && test -s docs/s06-acceptance-run.md + +## Observability Impact + +Orchestrated run logs preflight/seed/test results; artifact captures UI observations incl. manual-send boundary and Gmail continuity. Scripts surface failures with exit codes and summarized outputs. diff --git a/.gsd/milestones/M001/slices/S07/S07-PLAN.md b/.gsd/milestones/M001/slices/S07/S07-PLAN.md new file mode 100644 index 0000000..7d87de7 --- /dev/null +++ b/.gsd/milestones/M001/slices/S07/S07-PLAN.md @@ -0,0 +1,6 @@ +# S07: Daily-loop UAT artifact closure + +**Goal:** TBD +**Demo:** After this: The overview-surface/browser validation is captured as a real executed UAT artifact instead of a placeholder, proving the same job behaves coherently across table, dashboard, reminders, and workspace entry. + +## Tasks diff --git a/.gsd/state-manifest.json b/.gsd/state-manifest.json new file mode 100644 index 0000000..9df0c98 --- /dev/null +++ b/.gsd/state-manifest.json @@ -0,0 +1,834 @@ +{ + "version": 1, + "exported_at": "2026-03-27T07:47:00.101Z", + "milestones": [ + { + "id": "M001", + "title": "M001: Gmail and draft quality loop", + "status": "active", + "depends_on": [], + "created_at": "2026-03-27T07:30:18.608Z", + "completed_at": null, + "vision": "Turn the existing job tracker into a daily-use personal job-search workspace where Gmail import and AI drafting are strong enough to trust, while preserving manual control over all real-world sending and applying.", + "success_criteria": [ + "User can import a job found elsewhere, generate a tailored CV and cover-letter package that feels specific enough to start from, and save/edit that package inside the job workspace.", + "User can connect Gmail, import the right message or full thread into a job with less cleanup than before, and see the imported correspondence reflected in that job’s timeline/workspace.", + "After a thread is linked to a job, later inbound messages and user-sent Gmail replies appear on that job automatically without requiring the user to re-import the thread manually.", + "User can generate a follow-up or reply draft grounded in the imported correspondence and saved job/application context.", + "The daily loop of job table → follow-up/dashboard → individual job workspace feels coherent and actionable for an individual user.", + "No part of the milestone auto-sends email or auto-applies to jobs." + ], + "key_risks": [], + "proof_strategy": [], + "verification_contract": "", + "verification_integration": "", + "verification_operational": "", + "verification_uat": "", + "definition_of_done": [], + "requirement_coverage": "", + "boundary_map_markdown": "## Boundary Map\n\n### S01 → S02\n\nProduces:\n- improved Gmail import workflow in `job-tracker-ui/src/components/Correspondence.tsx` that yields job-linked imported correspondence with clearer message/thread selection behavior\n- stronger backend correspondence import surface in `JobTrackerApi/Controllers/GmailController.cs` and related persistence so imported messages reliably attach to `JobApplication` records\n- linked-thread metadata and refresh behavior stable enough that later draft-generation flows can consume correspondence as trusted context rather than a stale snapshot\n\nConsumes:\n- nothing (first slice)\n\n### S01 → S03\n\nProduces:\n- imported correspondence records tied to a specific job and available through the existing per-job correspondence/timeline surfaces\n- message/thread metadata good enough for later reply/follow-up draft context assembly\n- a verified Gmail connection/import path that downstream slices can rely on, including ongoing thread refresh after first import\n\nConsumes:\n- nothing (first slice)\n\n### S02 → S03\n\nProduces:\n- improved application package generation via `JobTrackerApi/Controllers/JobApplicationsController.cs` returning stronger tailored CV and cover-letter outputs tied to a job\n- persisted draft state in the job workspace so later follow-up/reply flows can reuse saved application context\n- clearer frontend editing/saving behavior in `job-tracker-ui/src/components/JobDetailsDialog.tsx`\n\nConsumes from S01:\n- imported and auto-refreshed correspondence plus job-linked message context\n\n### S03 → S04\n\nProduces:\n- reply/follow-up draft flow grounded in job + correspondence + saved application package context\n- explicit manual-send boundary in the job workspace UI and backend behavior\n- job-level indicators that a follow-up/reply is ready, missing context, or needs action\n\nConsumes from S01:\n- Gmail-imported and automatically refreshed correspondence context\n\nConsumes from S02:\n- saved tailored CV / cover-letter / application package context\n\n### S04 → S05\n\nProduces:\n- table/dashboard surfaces that summarize readiness, follow-up urgency, and next actions for individual users\n- clearer navigation hierarchy across table, follow-up/dashboard, and individual job workspace\n- stable daily-use control loop to validate in final integration\n\nConsumes from S01:\n- correspondence state, Gmail import outcomes, and linked-thread refresh outcomes\n\nConsumes from S03:\n- follow-up/reply drafting signals and job-level action state" + }, + { + "id": "M002", + "title": "", + "status": "active", + "depends_on": [], + "created_at": "2026-03-27T07:30:18.632Z", + "completed_at": null, + "vision": "", + "success_criteria": [], + "key_risks": [], + "proof_strategy": [], + "verification_contract": "", + "verification_integration": "", + "verification_operational": "", + "verification_uat": "", + "definition_of_done": [], + "requirement_coverage": "", + "boundary_map_markdown": "" + }, + { + "id": "M003", + "title": "", + "status": "active", + "depends_on": [], + "created_at": "2026-03-27T07:30:18.632Z", + "completed_at": null, + "vision": "", + "success_criteria": [], + "key_risks": [], + "proof_strategy": [], + "verification_contract": "", + "verification_integration": "", + "verification_operational": "", + "verification_uat": "", + "definition_of_done": [], + "requirement_coverage": "", + "boundary_map_markdown": "" + }, + { + "id": "M004", + "title": "", + "status": "active", + "depends_on": [], + "created_at": "2026-03-27T07:30:18.632Z", + "completed_at": null, + "vision": "", + "success_criteria": [], + "key_risks": [], + "proof_strategy": [], + "verification_contract": "", + "verification_integration": "", + "verification_operational": "", + "verification_uat": "", + "definition_of_done": [], + "requirement_coverage": "", + "boundary_map_markdown": "" + } + ], + "slices": [ + { + "milestone_id": "M001", + "id": "S01", + "title": "Smarter Gmail import and matching", + "status": "complete", + "risk": "high", + "depends": [], + "demo": "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.", + "created_at": "2026-03-27T07:30:18.611Z", + "completed_at": null, + "full_summary_md": "", + "full_uat_md": "", + "goal": "Finish S01 by turning the existing job-aware Gmail import flow into a live linked-thread continuity loop for one job workspace.", + "success_criteria": "", + "proof_level": "", + "integration_closure": "", + "observability_impact": "", + "sequence": 0, + "replan_triggered_at": null + }, + { + "milestone_id": "M001", + "id": "S02", + "title": "Stronger AI application package drafting", + "status": "complete", + "risk": "high", + "depends": [ + "S01" + ], + "demo": "From an imported job plus profile/CV context, the app generates materially better tailored CV and cover-letter drafts that feel specific and usable.", + "created_at": "2026-03-27T07:30:18.613Z", + "completed_at": null, + "full_summary_md": "", + "full_uat_md": "", + "goal": "Make the application package generator use imported job/correspondence context well enough that tailored CV, cover-letter, and recruiter-message drafts feel specific, credible, and worth starting from inside the job workspace.", + "success_criteria": "", + "proof_level": "", + "integration_closure": "", + "observability_impact": "", + "sequence": 0, + "replan_triggered_at": null + }, + { + "milestone_id": "M001", + "id": "S03", + "title": "Reply and follow-up drafting from real thread context", + "status": "complete", + "risk": "medium", + "depends": [ + "S01", + "S02" + ], + "demo": "Inside a job, the user can generate follow-up and reply drafts grounded in imported and automatically refreshed correspondence plus saved application context, then edit them before sending manually.", + "created_at": "2026-03-27T07:30:18.613Z", + "completed_at": null, + "full_summary_md": "", + "full_uat_md": "", + "goal": "Make follow-up drafting use imported correspondence and saved application material well enough that the job workspace can produce specific, trustworthy follow-up and reply drafts without crossing the manual-send boundary.", + "success_criteria": "", + "proof_level": "", + "integration_closure": "", + "observability_impact": "", + "sequence": 0, + "replan_triggered_at": null + }, + { + "milestone_id": "M001", + "id": "S04", + "title": "Daily control loop surfaces", + "status": "complete", + "risk": "medium", + "depends": [ + "S01", + "S03" + ], + "demo": "The job table works as the primary overview and the follow-up/dashboard surfaces clearly show what needs attention next for an individual user.", + "created_at": "2026-03-27T07:30:18.615Z", + "completed_at": null, + "full_summary_md": "", + "full_uat_md": "", + "goal": "Make the job table, reminders view, and dashboard behave like one daily control loop so the user can scan what needs attention and jump directly into the right job workspace state.", + "success_criteria": "", + "proof_level": "", + "integration_closure": "", + "observability_impact": "", + "sequence": 0, + "replan_triggered_at": null + }, + { + "milestone_id": "M001", + "id": "S05", + "title": "End-to-end trust and workflow polish", + "status": "complete", + "risk": "low", + "depends": [ + "S01", + "S02", + "S03", + "S04" + ], + "demo": "The full loop works cleanly in a real environment: import job → generate package → apply externally → import/update correspondence automatically from linked Gmail threads → draft follow-up/reply → track progress confidently.", + "created_at": "2026-03-27T07:30:18.616Z", + "completed_at": null, + "full_summary_md": "", + "full_uat_md": "", + "goal": "Prove the full daily-use loop as one trustworthy workflow by tightening shared next-action/readiness signals, then validating overview → workspace → package → Gmail continuity → follow-up behavior without weakening the manual-send boundary.", + "success_criteria": "", + "proof_level": "", + "integration_closure": "", + "observability_impact": "", + "sequence": 0, + "replan_triggered_at": null + }, + { + "milestone_id": "M001", + "id": "S06", + "title": "Live environment stabilization and integrated acceptance rerun", + "status": "pending", + "risk": "high", + "depends": [ + "S05" + ], + "demo": "The real M001 environment runs without the current backend/frontend CORS/runtime blockage, and the full `/jobs` → workspace → Gmail continuity → follow-up → dashboard/reminders loop is re-checked live with recorded acceptance results.", + "created_at": "2026-03-27T07:30:18.617Z", + "completed_at": null, + "full_summary_md": "", + "full_uat_md": "", + "goal": "Live environment is repeatably startable and preflighted, seeded with acceptance-ready data, and the integrated daily loop is re-verified with a recorded artifact proving the manual-send boundary and individual-first workflow.", + "success_criteria": "- Preflight gate confirms API/auth readiness, admin/system status, and Gmail configuration truthfully before any UI run.\n- Acceptance seed produces at least one job with saved package material, correspondence, and follow-up readiness so overview surfaces and workspace tabs show actionable state.\n- Integrated acceptance rerun is executed against the live stack, and results (passes/blocks) are captured in a committed artifact covering /jobs → workspace → reminders/dashboard → follow-up/manual-send boundary.", + "proof_level": "integration / operational — real runtime and browser walk required, with human/UAT notes when automation cannot cover.", + "integration_closure": "Upstream surfaces consumed: `JobTrackerApi/Program.cs`, `JobTrackerApi/appsettings.Development.json`, `/api/admin/system`, `/api/auth/config`, `/api/jobapplications*`, `/api/correspondence/{id}`, `/api/gmail/status`, `job-tracker-ui/src/api.ts`, overview/workspace components.\nNew wiring introduced: preflight + acceptance-data scripts to make runtime bring-up and seeding repeatable; acceptance run script + artifact tying overview entry and workspace tabs to live endpoints.\nRemaining for milestone end-to-end: S07 will turn the rerun into the final UAT artifact, but S06 must already deliver truthful live evidence and note any Gmail continuity gaps.", + "observability_impact": "Runtime signals: `/api/admin/system` fields (database/auth/gmailConfigured/ai health), `/api/auth/config` truthfulness, seeded job presence via `/api/jobapplications` and reminders/readiness endpoints.\nInspection surfaces: `scripts/s06-preflight.sh`, `scripts/s06-acceptance-data.sh`, `scripts/s06-acceptance-run.sh` outputs; browser UI states on /jobs, /dashboard, /reminders, workspace tabs.\nFailure visibility: each script should exit non-zero with clear message (missing token, API unreachable, seed failure); acceptance artifact must call out blocked checks explicitly.\nRedaction constraints: keep tokens/PII out of artifacts; use env vars for secrets (e.g., AUTH_TOKEN, GMAIL creds) and avoid logging raw tokens.", + "sequence": 0, + "replan_triggered_at": null + }, + { + "milestone_id": "M001", + "id": "S07", + "title": "Daily-loop UAT artifact closure", + "status": "pending", + "risk": "medium", + "depends": [ + "S06" + ], + "demo": "The overview-surface/browser validation is captured as a real executed UAT artifact instead of a placeholder, proving the same job behaves coherently across table, dashboard, reminders, and workspace entry.", + "created_at": "2026-03-27T07:30:18.617Z", + "completed_at": null, + "full_summary_md": "", + "full_uat_md": "", + "goal": "", + "success_criteria": "", + "proof_level": "", + "integration_closure": "", + "observability_impact": "", + "sequence": 0, + "replan_triggered_at": null + } + ], + "tasks": [ + { + "milestone_id": "M001", + "slice_id": "S01", + "id": "T01", + "title": "Add linked Gmail thread refresh to the backend contract", + "status": "complete", + "one_liner": "", + "narrative": "", + "verification_result": "", + "duration": "", + "completed_at": "2026-03-27T07:30:18.612Z", + "blocker_discovered": false, + "deviations": "", + "known_issues": "", + "key_files": [], + "key_decisions": [], + "full_summary_md": "", + "description": "", + "estimate": "", + "files": [ + "JobTrackerApi/Controllers/GmailController.cs", + "JobTrackerApi/Services/GmailOAuthService.cs", + "JobTrackerApi.Tests/GmailControllerTests.cs", + "JobTrackerApi/Program.cs" + ], + "verify": "`dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests`", + "inputs": [], + "expected_output": [], + "observability_impact": "", + "full_plan_md": "", + "sequence": 0 + }, + { + "milestone_id": "M001", + "slice_id": "S01", + "id": "T02", + "title": "Surface live Gmail thread continuity in the job workspace", + "status": "complete", + "one_liner": "", + "narrative": "", + "verification_result": "", + "duration": "", + "completed_at": "2026-03-27T07:30:18.612Z", + "blocker_discovered": false, + "deviations": "", + "known_issues": "", + "key_files": [], + "key_decisions": [], + "full_summary_md": "", + "description": "", + "estimate": "", + "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" + ], + "verify": "`CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx`", + "inputs": [], + "expected_output": [], + "observability_impact": "", + "full_plan_md": "", + "sequence": 0 + }, + { + "milestone_id": "M001", + "slice_id": "S02", + "id": "T01", + "title": "Strengthen application-package context assembly and backend draft tests", + "status": "complete", + "one_liner": "", + "narrative": "", + "verification_result": "", + "duration": "", + "completed_at": "2026-03-27T07:30:18.613Z", + "blocker_discovered": false, + "deviations": "", + "known_issues": "", + "key_files": [], + "key_decisions": [], + "full_summary_md": "", + "description": "", + "estimate": "", + "files": [ + "JobTrackerApi/Controllers/JobApplicationsController.cs", + "JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs" + ], + "verify": "`dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests`", + "inputs": [], + "expected_output": [], + "observability_impact": "", + "full_plan_md": "", + "sequence": 0 + }, + { + "milestone_id": "M001", + "slice_id": "S02", + "id": "T02", + "title": "Make the job workspace save and present the application package as real working material", + "status": "complete", + "one_liner": "", + "narrative": "", + "verification_result": "", + "duration": "", + "completed_at": "2026-03-27T07:30:18.613Z", + "blocker_discovered": false, + "deviations": "", + "known_issues": "", + "key_files": [], + "key_decisions": [], + "full_summary_md": "", + "description": "", + "estimate": "", + "files": [ + "job-tracker-ui/src/components/JobDetailsDialog.tsx", + "job-tracker-ui/src/types.ts", + "job-tracker-ui/src/job-details-generated-drafts.test.tsx" + ], + "verify": "`CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-generated-drafts.test.tsx`", + "inputs": [], + "expected_output": [], + "observability_impact": "", + "full_plan_md": "", + "sequence": 0 + }, + { + "milestone_id": "M001", + "slice_id": "S03", + "id": "T01", + "title": "Strengthen follow-up draft context assembly and backend reply/follow-up tests", + "status": "complete", + "one_liner": "", + "narrative": "", + "verification_result": "", + "duration": "", + "completed_at": "2026-03-27T07:30:18.613Z", + "blocker_discovered": false, + "deviations": "", + "known_issues": "", + "key_files": [], + "key_decisions": [], + "full_summary_md": "", + "description": "", + "estimate": "", + "files": [ + "JobTrackerApi/Controllers/JobApplicationsController.cs", + "JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs" + ], + "verify": "`dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsFollowUpDraftTests`", + "inputs": [], + "expected_output": [], + "observability_impact": "", + "full_plan_md": "", + "sequence": 0 + }, + { + "milestone_id": "M001", + "slice_id": "S03", + "id": "T02", + "title": "Make the follow-up workspace show thread-grounded draft state without autonomous sending", + "status": "complete", + "one_liner": "", + "narrative": "", + "verification_result": "", + "duration": "", + "completed_at": "2026-03-27T07:30:18.614Z", + "blocker_discovered": false, + "deviations": "", + "known_issues": "", + "key_files": [], + "key_decisions": [], + "full_summary_md": "", + "description": "", + "estimate": "", + "files": [ + "job-tracker-ui/src/components/JobDetailsDialog.tsx", + "job-tracker-ui/src/types.ts", + "job-tracker-ui/src/job-details-followup-drafts.test.tsx" + ], + "verify": "`CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-followup-drafts.test.tsx`", + "inputs": [], + "expected_output": [], + "observability_impact": "", + "full_plan_md": "", + "sequence": 0 + }, + { + "milestone_id": "M001", + "slice_id": "S04", + "id": "T01", + "title": "Turn reminders and dashboard into actionable entry surfaces", + "status": "complete", + "one_liner": "", + "narrative": "", + "verification_result": "", + "duration": "", + "completed_at": "2026-03-27T07:30:18.615Z", + "blocker_discovered": false, + "deviations": "", + "known_issues": "", + "key_files": [], + "key_decisions": [], + "full_summary_md": "", + "description": "", + "estimate": "", + "files": [ + "job-tracker-ui/src/components/DashboardView.tsx", + "job-tracker-ui/src/components/RemindersView.tsx", + "job-tracker-ui/src/App.tsx" + ], + "verify": "`CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx`", + "inputs": [], + "expected_output": [], + "observability_impact": "", + "full_plan_md": "", + "sequence": 0 + }, + { + "milestone_id": "M001", + "slice_id": "S04", + "id": "T02", + "title": "Make the job table expose the right next action and prove the daily loop", + "status": "complete", + "one_liner": "", + "narrative": "", + "verification_result": "", + "duration": "", + "completed_at": "2026-03-27T07:30:18.615Z", + "blocker_discovered": false, + "deviations": "", + "known_issues": "", + "key_files": [], + "key_decisions": [], + "full_summary_md": "", + "description": "", + "estimate": "", + "files": [ + "job-tracker-ui/src/components/JobTable.tsx", + "job-tracker-ui/src/daily-control-loop.test.tsx" + ], + "verify": "`CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx`", + "inputs": [], + "expected_output": [], + "observability_impact": "", + "full_plan_md": "", + "sequence": 0 + }, + { + "milestone_id": "M001", + "slice_id": "S05", + "id": "T01", + "title": "Centralize workflow trust signals across overview and readiness surfaces", + "status": "complete", + "one_liner": "", + "narrative": "", + "verification_result": "", + "duration": "", + "completed_at": "2026-03-27T07:30:18.616Z", + "blocker_discovered": false, + "deviations": "", + "known_issues": "", + "key_files": [], + "key_decisions": [], + "full_summary_md": "", + "description": "", + "estimate": "", + "files": [ + "JobTrackerApi/Controllers/JobApplicationsController.cs", + "JobTrackerApi.Tests/JobApplicationsWorkflowSignalsTests.cs", + "job-tracker-ui/src/types.ts", + "job-tracker-ui/src/jobWorkflowSignals.ts", + "job-tracker-ui/src/components/JobTable.tsx", + "job-tracker-ui/src/components/DashboardView.tsx", + "job-tracker-ui/src/components/RemindersView.tsx", + "job-tracker-ui/src/workflow-trust-signals.test.tsx" + ], + "verify": "`dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsWorkflowSignalsTests` and `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/workflow-trust-signals.test.tsx`", + "inputs": [], + "expected_output": [], + "observability_impact": "", + "full_plan_md": "", + "sequence": 0 + }, + { + "milestone_id": "M001", + "slice_id": "S05", + "id": "T02", + "title": "Add integrated trust-loop proof and workspace polish", + "status": "complete", + "one_liner": "", + "narrative": "", + "verification_result": "", + "duration": "", + "completed_at": "2026-03-27T07:30:18.617Z", + "blocker_discovered": false, + "deviations": "", + "known_issues": "", + "key_files": [], + "key_decisions": [], + "full_summary_md": "", + "description": "", + "estimate": "", + "files": [ + "job-tracker-ui/src/components/JobDetailsDialog.tsx", + "job-tracker-ui/src/components/Correspondence.tsx", + "job-tracker-ui/src/end-to-end-trust-loop.test.tsx", + "job-tracker-ui/src/daily-control-loop.test.tsx", + ".gsd/milestones/M001/slices/S05/S05-UAT.md" + ], + "verify": "`CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/end-to-end-trust-loop.test.tsx`, `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx src/job-details-generated-drafts.test.tsx src/job-details-followup-drafts.test.tsx src/daily-control-loop.test.tsx`, and `CI=true npm --prefix job-tracker-ui run build`", + "inputs": [], + "expected_output": [], + "observability_impact": "", + "full_plan_md": "", + "sequence": 0 + }, + { + "milestone_id": "M001", + "slice_id": "S06", + "id": "T01", + "title": "Add preflight gate for live API/auth readiness", + "status": "pending", + "one_liner": "", + "narrative": "", + "verification_result": "", + "duration": "", + "completed_at": null, + "blocker_discovered": false, + "deviations": "", + "known_issues": "", + "key_files": [], + "key_decisions": [], + "full_summary_md": "", + "description": "Build a repeatable preflight script and doc so environment blockers are caught before browser UAT.\n- Why: avoid the ERR_CONNECTION_REFUSED/CORS/auth mismatch that currently blocks the UI.\n- Steps:\n 1) Create `scripts/s06-preflight.sh` (bash, executable) that assumes backend already started; probes `/api/auth/config` and `/api/admin/system` on `http://localhost:5202/api`, printing database/auth/gmailConfigured/ai status and failing fast on unreachable endpoints.\n 2) Ensure script respects `API_BASE` env override and uses `curl -f` with readable errors; no secrets logged.\n 3) Add a short runbook snippet to `README.md` showing backend start command from `JobTrackerApi/` and how to run the preflight (including auth token note if required).\n 4) Sanity-check CORS expectations vs `job-tracker-ui/src/api.ts` and document the required origin pairing (UI :3000, API :5202).\n- Failure Modes (Q5): API down → exit 1 with hint to start API; Auth required without token → script notes auth required and how to obtain; malformed JSON → show raw body and fail.\n- Load Profile (Q6): trivial single-user curl calls; no scaling concern.\n- Negative Tests (Q7): run script with API stopped (expect non-zero); run with wrong `API_BASE` (expect clear error message).\n- Must-haves: preflight script exists/executable; README runbook mentions backend start + preflight; script outputs gmailConfigured/auth/db/ai fields.\n- Verification: `bash scripts/s06-preflight.sh`", + "estimate": "45m", + "files": [ + "scripts/s06-preflight.sh", + "README.md", + "job-tracker-ui/src/api.ts", + "JobTrackerApi/appsettings.Development.json" + ], + "verify": "bash scripts/s06-preflight.sh", + "inputs": [ + "`JobTrackerApi/Program.cs`", + "`JobTrackerApi/appsettings.Development.json`", + "`job-tracker-ui/src/api.ts`", + "`README.md`" + ], + "expected_output": [ + "`scripts/s06-preflight.sh`", + "`README.md`" + ], + "observability_impact": "Adds preflight status surface exposing DB/auth/gmail/ai readiness via curl; provides explicit failure messages for unreachable API/CORS/auth.", + "full_plan_md": "", + "sequence": 0 + }, + { + "milestone_id": "M001", + "slice_id": "S06", + "id": "T02", + "title": "Seed acceptance-ready job data", + "status": "pending", + "one_liner": "", + "narrative": "", + "verification_result": "", + "duration": "", + "completed_at": null, + "blocker_discovered": false, + "deviations": "", + "known_issues": "", + "key_files": [], + "key_decisions": [], + "full_summary_md": "", + "description": "Create a seed script that prepares a richer acceptance fixture (job, correspondence, saved package, follow-up readiness) using live API calls.\n- Why: current DB has only 1 low-signal job; acceptance rerun needs actionable overview + workspace state.\n- Steps:\n 1) Write `scripts/s06-acceptance-data.sh` (bash, executable) that requires `AUTH_TOKEN` env; uses `scripts/s06-preflight.sh` first, then POSTs to `/api/jobapplications` (or PUT existing ID) to create a job with saved package fields, correspondence entry, reminder/follow-up signals, and notes.\n 2) Add curl helpers for adding correspondence (`/api/correspondence/{jobId}` or equivalent), saving package material, and setting workflow/readiness if needed; use deterministic titles so rerun is idempotent (update if exists).\n 3) Emit a short summary of created/updated IDs so the acceptance run can target them; avoid logging token.\n 4) Document any manual token retrieval step in script comments.\n- Failure Modes (Q5): missing AUTH_TOKEN → fail with guidance; 401/403 → explain token issue; 5xx → print response and fail; malformed response → show body and fail.\n- Load Profile (Q6): few API calls; minimal DB impact.\n- Negative Tests (Q7): run without AUTH_TOKEN (expect failure); rerun twice (should succeed idempotently); simulate 401 by bad token (expect clear message).\n- Must-haves: script seeds at least one job with saved package + correspondence + follow-up readiness; outputs job id for UAT; uses preflight.\n- Verification: `bash scripts/s06-acceptance-data.sh`", + "estimate": "1h", + "files": [ + "scripts/s06-acceptance-data.sh", + "scripts/s06-preflight.sh", + "README.md" + ], + "verify": "bash scripts/s06-acceptance-data.sh", + "inputs": [ + "`scripts/s06-preflight.sh`", + "`README.md`", + "`JobTrackerApi/Controllers/JobApplicationsController.cs`", + "`JobTrackerApi/Controllers/CorrespondenceController.cs`" + ], + "expected_output": [ + "`scripts/s06-acceptance-data.sh`", + "`README.md`" + ], + "observability_impact": "Provides seed summary output (job id, correspondence count) to inspect readiness; failures surface via script exit and printed API responses.", + "full_plan_md": "", + "sequence": 0 + }, + { + "milestone_id": "M001", + "slice_id": "S06", + "id": "T03", + "title": "Run integrated acceptance and capture evidence", + "status": "pending", + "one_liner": "", + "narrative": "", + "verification_result": "", + "duration": "", + "completed_at": null, + "blocker_discovered": false, + "deviations": "", + "known_issues": "", + "key_files": [], + "key_decisions": [], + "full_summary_md": "", + "description": "Execute the live acceptance loop and record results as an artifact for S07/UAT handoff.\n- Why: prove the `/jobs → workspace → reminders/dashboard → follow-up/manual-send boundary` loop runs in the live stack after stabilization and seeding.\n- Steps:\n 1) Create `scripts/s06-acceptance-run.sh` to orchestrate: ensure backend running, run preflight + seed scripts, then run existing automated regressions most relevant to the loop (e.g., `end-to-end-trust-loop.test.tsx`) and capture outputs.\n 2) Perform a guided browser run (can use agent-browser/Playwright) hitting /jobs, /reminders, /dashboard, opening the seeded job workspace, inspecting Tailored CV, Correspondence (linked-thread status), Follow-up draft manual-send boundary; note Gmail continuity if blocked.\n 3) Write findings and screenshots/links into `docs/s06-acceptance-run.md` (what passed, what blocked, manual-send boundary observation, Gmail continuity status). Call out any gaps explicitly.\n 4) Ensure commands avoid leaking tokens; artifacts redact secrets.\n- Failure Modes (Q5): backend not running → script stops after preflight; tests fail → record failure in artifact; browser step blocked by auth → document and include auth instructions.\n- Load Profile (Q6): single-user flows; test runner CPU-bound but acceptable.\n- Negative Tests (Q7): note expected failure if Gmail remains unconfigured; ensure manual-send boundary not auto-triggered during run.\n- Must-haves: acceptance-run script exists; artifact populated with live results; manual-send boundary explicitly observed; Gmail continuity status recorded (even if blocked).\n- Verification: `bash scripts/s06-acceptance-run.sh` && `test -s docs/s06-acceptance-run.md`", + "estimate": "1h30m", + "files": [ + "scripts/s06-acceptance-run.sh", + "docs/s06-acceptance-run.md", + "scripts/s06-preflight.sh", + "scripts/s06-acceptance-data.sh", + "job-tracker-ui/src/end-to-end-trust-loop.test.tsx" + ], + "verify": "bash scripts/s06-acceptance-run.sh && test -s docs/s06-acceptance-run.md", + "inputs": [ + "`scripts/s06-preflight.sh`", + "`scripts/s06-acceptance-data.sh`", + "`job-tracker-ui/src/end-to-end-trust-loop.test.tsx`", + "`job-tracker-ui/src/components/JobDetailsDialog.tsx`", + "`job-tracker-ui/src/components/Correspondence.tsx`" + ], + "expected_output": [ + "`scripts/s06-acceptance-run.sh`", + "`docs/s06-acceptance-run.md`" + ], + "observability_impact": "Orchestrated run logs preflight/seed/test results; artifact captures UI observations incl. manual-send boundary and Gmail continuity. Scripts surface failures with exit codes and summarized outputs.", + "full_plan_md": "", + "sequence": 0 + } + ], + "decisions": [ + { + "seq": 1, + "id": "D001", + "when_context": "M001", + "scope": "scope", + "decision": "Product entry point", + "choice": "External job discovery, then import into the app", + "rationale": "The user finds jobs on job sites first; the app begins when a role is imported.", + "revisable": "No", + "made_by": "collaborative", + "superseded_by": null + }, + { + "seq": 2, + "id": "D002", + "when_context": "M001", + "scope": "pattern", + "decision": "AI action model", + "choice": "Assistive drafting and analysis only; no autonomous sending", + "rationale": "The user wants AI help but explicitly does not want auto-send or auto-apply behavior.", + "revisable": "No", + "made_by": "collaborative", + "superseded_by": null + }, + { + "seq": 3, + "id": "D003", + "when_context": "M001", + "scope": "scope", + "decision": "Primary user", + "choice": "Individual job seeker", + "rationale": "The product is designed for individuals managing their own search, not recruiter or team workflows.", + "revisable": "Yes — if product direction changes later", + "made_by": "collaborative", + "superseded_by": null + }, + { + "seq": 4, + "id": "D004", + "when_context": "M001", + "scope": "pattern", + "decision": "Daily navigation hierarchy", + "choice": "Job table first, then follow-up/dashboard, then individual job workspace", + "rationale": "The user explicitly described this as the intended control flow for daily use.", + "revisable": "Yes — if real usage disproves the hierarchy", + "made_by": "collaborative", + "superseded_by": null + }, + { + "seq": 5, + "id": "D005", + "when_context": "M001", + "scope": "roadmap", + "decision": "First milestone focus", + "choice": "Prioritize Gmail import quality and AI draft quality before broader expansion", + "rationale": "The user identified Gmail import and AI drafts as the weakest current areas and the first bar for daily use.", + "revisable": "Yes — if execution proves another blocker is more fundamental", + "made_by": "collaborative", + "superseded_by": null + }, + { + "seq": 6, + "id": "D006", + "when_context": "M001/S02", + "scope": "workspace-persistence", + "decision": "How the saved application answer draft should persist inside the job workspace before a dedicated field exists", + "choice": "Store the application answer draft in a replaceable notes block and make SaveApplicationDrafts overwrite notes when notes are explicitly provided", + "rationale": "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.", + "revisable": "Yes", + "made_by": "agent", + "superseded_by": null + }, + { + "seq": 7, + "id": "D007", + "when_context": "M001/S01", + "scope": "gmail-continuity", + "decision": "What “good Gmail import” now means for M001", + "choice": "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.", + "rationale": "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.", + "revisable": "Yes — if Gmail API or product constraints later require a different sync model", + "made_by": "human", + "superseded_by": null + }, + { + "seq": 8, + "id": "D008", + "when_context": "M001/S01 planning", + "scope": "gmail-sync", + "decision": "How linked Gmail threads stay current in S01", + "choice": "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.", + "rationale": "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.", + "revisable": "Yes — if real usage shows job-scoped pull refresh is too slow or misses important continuity cases.", + "made_by": "agent", + "superseded_by": null + }, + { + "seq": 9, + "id": "D009", + "when_context": "M001/S01 closure", + "scope": "gmail-matching", + "decision": "Where Gmail candidate aggregation and ranking logic should live for job-aware import", + "choice": "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.", + "rationale": "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.", + "revisable": "Yes", + "made_by": "agent", + "superseded_by": null + }, + { + "seq": 10, + "id": "D010", + "when_context": "M001/S03 closeout", + "scope": "followup-drafting", + "decision": "How follow-up grounding should be exposed to the workspace", + "choice": "Return explicit follow-up grounding fields (`contextSummary`, `contextSignals`, `threadSubject`, and last-correspondence metadata) from the backend DTO instead of making the React workspace infer them client-side.", + "rationale": "The slice needed draft trust, not just draft text. Putting grounding signals in the API contract gives the UI a durable explanation surface, keeps thread/package inference in one place with generation logic, and makes backend/frontend tests assert the same source of truth.", + "revisable": "Yes", + "made_by": "agent", + "superseded_by": null + }, + { + "seq": 11, + "id": "D011", + "when_context": "M001/S05 planning", + "scope": "workflow-trust", + "decision": "How S05 should represent daily-loop readiness and next-action state across overview surfaces", + "choice": "Introduce explicit workflow trust/action signals from the backend/UI contract and reuse them across the table, dashboard, reminders, and shared workspace instead of continuing to infer behavior from free-form `followUpReason` strings or raw `notes` presence in each component.", + "rationale": "S05 is an end-to-end polish slice where the remaining risk is fragmented trust, not missing subsystems. Centralizing workflow signals avoids heuristic drift between overview surfaces, respects the saved application-answer notes-block constraint, and gives the final integrated regression one source of truth for R010 while preserving the explicit manual-send boundary required by R008.", + "revisable": "Yes", + "made_by": "agent", + "superseded_by": null + }, + { + "seq": 12, + "id": "D012", + "when_context": "M001/S05", + "scope": "workspace-observability", + "decision": "Where linked Gmail thread continuity status should be exposed in the final trust loop", + "choice": "Show linked-thread refresh state directly in the correspondence workspace, including linked thread count and last refresh outcome, instead of hiding continuity feedback inside the Gmail import modal only.", + "rationale": "The end-to-end trust loop depends on users being able to verify that already-linked Gmail threads stay current without re-importing. Surfacing continuity state in the main workspace keeps the job timeline trustworthy, supports final UAT, and avoids making thread refresh feel like a hidden one-off import behavior.", + "revisable": "Yes", + "made_by": "agent", + "superseded_by": null + } + ], + "verification_evidence": [] +} \ No newline at end of file diff --git a/JobTrackerApi/exports/daily_export_20260325.json b/JobTrackerApi/exports/daily_export_20260325.json new file mode 100644 index 0000000..807f757 --- /dev/null +++ b/JobTrackerApi/exports/daily_export_20260325.json @@ -0,0 +1,99 @@ +{ + "Version": "dailyexport.v1", + "CreatedAt": "2026-03-25T02:00:00.0368687+01:00", + "Companies": [ + { + "Id": 1, + "OwnerUserId": "23dc196b-f227-4499-93fe-403d8801e21c", + "Name": "Acme Browser QA", + "Location": null, + "Source": null, + "RecruiterName": "Maria Recruiter", + "RecruiterEmail": "maria@acme.test", + "RecruiterLinkedIn": null, + "LastContactedAt": "2026-03-24T11:15:21.4772436", + "NextContactAt": "2026-03-24T00:00:00", + "PipelineStage": null + } + ], + "JobApplications": [ + { + "Id": 1, + "OwnerUserId": "23dc196b-f227-4499-93fe-403d8801e21c", + "JobTitle": "Backend Developer", + "CompanyId": 1, + "Company": null, + "Status": "Waiting", + "DateApplied": "2026-03-01T13:00:00+01:00", + "Location": null, + "Salary": null, + "NextAction": null, + "FollowUpAt": "2026-03-24T00:00:00", + "FeedbackRequestedAt": null, + "RecruiterMessageDraft": "Saved browser recruiter message", + "HasResume": true, + "HasCoverLetter": true, + "HasPortfolio": false, + "HasOtherAttachment": false, + "IsDeleted": false, + "DeletedAt": null, + "ResponseReceived": true, + "ResponseDate": null, + "Notes": "Browser-seeded notes\n\n\u003C\u003C\u003CAPPLICATION_ANSWER_DRAFT\u003E\u003E\u003E\nSaved browser application answer\n\u003C\u003C\u003CEND_APPLICATION_ANSWER_DRAFT\u003E\u003E\u003E", + "CoverLetterText": "Saved browser cover letter", + "JobUrl": "https://example.test/backend-developer", + "Description": "Need .NET APIs and strong stakeholder communication.", + "TranslatedDescription": null, + "DescriptionLanguage": null, + "Tags": "[\u0022.NET\u0022, \u0022APIs\u0022, \u0022Communication\u0022]", + "Deadline": null, + "ShortSummary": "Strong overlap in backend API delivery.", + "TailoredCvText": "Saved browser tailored CV", + "TailoredCvUpdatedAt": "2026-03-24T10:58:13.226164+01:00", + "LastReminderEmailSentAt": null, + "Messages": [], + "Attachments": [], + "Events": [], + "DaysSince": 23 + } + ], + "Correspondence": [ + { + "Id": 1, + "JobApplicationId": 1, + "From": "Company", + "Subject": "Backend Developer application update", + "Channel": "Email", + "ExternalMessageId": "browser-msg-1", + "ExternalThreadId": "browser-thread-1", + "ExternalFrom": "Maria Recruiter \u003Cmaria@acme.test\u003E", + "ExternalTo": "admin@example.com", + "Content": "We are aligning interview slots and need someone who can own the API layer.", + "Date": "2026-03-10T10:00:00+01:00" + }, + { + "Id": 2, + "JobApplicationId": 1, + "From": "Me", + "Subject": "Re: Backend Developer application update", + "Channel": "Email", + "ExternalMessageId": null, + "ExternalThreadId": null, + "ExternalFrom": null, + "ExternalTo": null, + "Content": "Hi Maria,\n\nEdited browser follow-up.\n\nThanks,\nadmin@example.com", + "Date": "2026-03-24T11:15:21.4521755" + } + ], + "Attachments": [], + "Events": [], + "Rules": { + "Id": 1, + "AppliedFollowUpDays": 14, + "AppliedGhostDays": 30, + "OfferFollowUpDays": 7, + "OfferGhostDays": 14, + "FeedbackFollowUpDays": 7, + "FeedbackGhostDays": 14 + } +} \ No newline at end of file diff --git a/JobTrackerApi/exports/daily_export_20260326.json b/JobTrackerApi/exports/daily_export_20260326.json new file mode 100644 index 0000000..7e9d2ab --- /dev/null +++ b/JobTrackerApi/exports/daily_export_20260326.json @@ -0,0 +1,99 @@ +{ + "Version": "dailyexport.v1", + "CreatedAt": "2026-03-26T02:00:00.005823+01:00", + "Companies": [ + { + "Id": 1, + "OwnerUserId": "23dc196b-f227-4499-93fe-403d8801e21c", + "Name": "Acme Browser QA", + "Location": null, + "Source": null, + "RecruiterName": "Maria Recruiter", + "RecruiterEmail": "maria@acme.test", + "RecruiterLinkedIn": null, + "LastContactedAt": "2026-03-24T11:15:21.4772436", + "NextContactAt": "2026-03-24T00:00:00", + "PipelineStage": null + } + ], + "JobApplications": [ + { + "Id": 1, + "OwnerUserId": "23dc196b-f227-4499-93fe-403d8801e21c", + "JobTitle": "Backend Developer", + "CompanyId": 1, + "Company": null, + "Status": "Waiting", + "DateApplied": "2026-03-01T13:00:00+01:00", + "Location": null, + "Salary": null, + "NextAction": null, + "FollowUpAt": "2026-03-24T00:00:00", + "FeedbackRequestedAt": null, + "RecruiterMessageDraft": "Saved browser recruiter message", + "HasResume": true, + "HasCoverLetter": true, + "HasPortfolio": false, + "HasOtherAttachment": false, + "IsDeleted": false, + "DeletedAt": null, + "ResponseReceived": true, + "ResponseDate": null, + "Notes": "Browser-seeded notes\n\n\u003C\u003C\u003CAPPLICATION_ANSWER_DRAFT\u003E\u003E\u003E\nSaved browser application answer\n\u003C\u003C\u003CEND_APPLICATION_ANSWER_DRAFT\u003E\u003E\u003E", + "CoverLetterText": "Saved browser cover letter", + "JobUrl": "https://example.test/backend-developer", + "Description": "Need .NET APIs and strong stakeholder communication.", + "TranslatedDescription": null, + "DescriptionLanguage": null, + "Tags": "[\u0022.NET\u0022, \u0022APIs\u0022, \u0022Communication\u0022]", + "Deadline": null, + "ShortSummary": "Strong overlap in backend API delivery.", + "TailoredCvText": "Saved browser tailored CV", + "TailoredCvUpdatedAt": "2026-03-24T10:58:13.226164+01:00", + "LastReminderEmailSentAt": null, + "Messages": [], + "Attachments": [], + "Events": [], + "DaysSince": 24 + } + ], + "Correspondence": [ + { + "Id": 1, + "JobApplicationId": 1, + "From": "Company", + "Subject": "Backend Developer application update", + "Channel": "Email", + "ExternalMessageId": "browser-msg-1", + "ExternalThreadId": "browser-thread-1", + "ExternalFrom": "Maria Recruiter \u003Cmaria@acme.test\u003E", + "ExternalTo": "admin@example.com", + "Content": "We are aligning interview slots and need someone who can own the API layer.", + "Date": "2026-03-10T10:00:00+01:00" + }, + { + "Id": 2, + "JobApplicationId": 1, + "From": "Me", + "Subject": "Re: Backend Developer application update", + "Channel": "Email", + "ExternalMessageId": null, + "ExternalThreadId": null, + "ExternalFrom": null, + "ExternalTo": null, + "Content": "Hi Maria,\n\nEdited browser follow-up.\n\nThanks,\nadmin@example.com", + "Date": "2026-03-24T11:15:21.4521755" + } + ], + "Attachments": [], + "Events": [], + "Rules": { + "Id": 1, + "AppliedFollowUpDays": 14, + "AppliedGhostDays": 30, + "OfferFollowUpDays": 7, + "OfferGhostDays": 14, + "FeedbackFollowUpDays": 7, + "FeedbackGhostDays": 14 + } +} \ No newline at end of file diff --git a/README.md b/README.md index 1c1e9d6..141f60d 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,29 @@ dotnet run By default the API listens on `http://localhost:5202` (see `JobTrackerApi/Properties/launchSettings.json`). +### Local preflight before browser/UAT + +Run the backend first from `JobTrackerApi/`, then use the preflight gate from the repo root: + +```bash +bash scripts/s06-preflight.sh +``` + +The preflight assumes the dev pairing used by `job-tracker-ui/src/api.ts` and `JobTrackerApi/appsettings.Development.json`: + +- UI origin: `http://localhost:3000` +- API base: `http://localhost:5202/api` + +It first checks the anonymous `GET /api/auth/config` endpoint, then probes `GET /api/admin/system` for DB/Gmail/AI readiness. +If auth is enabled and `/api/admin/system` returns `401/403`, export an admin bearer token first and rerun: + +```bash +export AUTH_TOKEN="" +bash scripts/s06-preflight.sh +``` + +To obtain a local admin token in dev, log in against the API with the seeded admin email/password from `JobTrackerApi/appsettings.Development.json` (or your environment override) via `POST /api/auth/login`, then export only the returned access token. The script never prints token values. Use `API_BASE` if your API is not on the default dev port. + ### 2) Run the UI ```bash diff --git a/keys/key-b3ca4672-1056-4ac2-ba47-0432608a4115.xml b/keys/key-b3ca4672-1056-4ac2-ba47-0432608a4115.xml new file mode 100644 index 0000000..4c53c94 --- /dev/null +++ b/keys/key-b3ca4672-1056-4ac2-ba47-0432608a4115.xml @@ -0,0 +1,16 @@ + + + 2026-03-27T07:52:25.0540436Z + 2026-03-27T07:52:25.0540436Z + 2026-06-25T07:52:25.0540436Z + + + + + + + mfglwuKFrMSiWcbTVDEbPYM0eGAqlsOMHe89hNOsZUguUMMiusdx3m3ZQJvxnBCxeXte6OS+zvpZl3tIizvgHg== + + + + \ No newline at end of file diff --git a/scripts/s06-preflight.sh b/scripts/s06-preflight.sh new file mode 100755 index 0000000..1f559f7 --- /dev/null +++ b/scripts/s06-preflight.sh @@ -0,0 +1,195 @@ +#!/usr/bin/env bash +set -euo pipefail + +API_BASE="${API_BASE:-http://localhost:5202/api}" +AUTH_TOKEN="${AUTH_TOKEN:-}" +CURL_TIMEOUT="${CURL_TIMEOUT:-15}" +CONNECT_TIMEOUT="${CONNECT_TIMEOUT:-3}" + +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +note() { + printf '%s\n' "$*" +} + +fail() { + printf 'ERROR: %s\n' "$*" >&2 + exit 1 +} + +json_get() { + local file="$1" + local path="$2" + python3 - "$file" "$path" <<'PY' +import json +import sys + +file_path, path = sys.argv[1], sys.argv[2] +with open(file_path, 'r', encoding='utf-8') as fh: + data = json.load(fh) + +value = data +for part in path.split('.'): + if isinstance(value, dict) and part in value: + value = value[part] + else: + raise KeyError(path) + +if value is None: + print('null') +elif isinstance(value, bool): + print('true' if value else 'false') +elif isinstance(value, (dict, list)): + print(json.dumps(value, separators=(',', ':'))) +else: + print(value) +PY +} + +validate_json() { + local file="$1" + if ! python3 - "$file" <<'PY' +import json +import sys +with open(sys.argv[1], 'r', encoding='utf-8') as fh: + json.load(fh) +PY + then + printf 'Raw response body:\n' >&2 + cat "$file" >&2 + fail "Malformed JSON returned by API." + fi +} + +request_json() { + local name="$1" + local url="$2" + local body_file="$3" + shift 3 + local -a headers=("$@") + local err_file="$TMP_DIR/${name}.curl.err" + local status_file="$TMP_DIR/${name}.status" + : >"$err_file" + + set +e + curl -fsS \ + --connect-timeout "$CONNECT_TIMEOUT" \ + --max-time "$CURL_TIMEOUT" \ + -H 'Accept: application/json' \ + "${headers[@]}" \ + -o "$body_file" \ + -w '%{http_code}' \ + "$url" >"$status_file" 2>"$err_file" + local curl_exit=$? + set -e + + if [[ $curl_exit -eq 0 ]]; then + validate_json "$body_file" + return 0 + fi + local status='000' + if [[ -s "$status_file" ]]; then + status="$(tr -d '\r\n' <"$status_file")" + fi + + case "$curl_exit:$status" in + 7:*|28:*|52:*|56:*|6:*) + printf 'curl: %s\n' "$(tr -d '\r' <"$err_file")" >&2 + fail "Cannot reach ${url}. Start the API from JobTrackerApi/ with 'dotnet run' and verify it is listening on ${API_BASE%/api}." + ;; + 22:401|22:403) + note "${name}: auth required." + note "Hint: export AUTH_TOKEN with an admin bearer token from POST ${API_BASE}/auth/login before rerunning this preflight." + return 22 + ;; + 22:*) + local diag_body="$TMP_DIR/${name}.diag.body" + local diag_status="$TMP_DIR/${name}.diag.status" + local diag_err="$TMP_DIR/${name}.diag.err" + curl -sS \ + --connect-timeout "$CONNECT_TIMEOUT" \ + --max-time "$CURL_TIMEOUT" \ + -H 'Accept: application/json' \ + "${headers[@]}" \ + -o "$diag_body" \ + -w '%{http_code}' \ + "$url" >"$diag_status" 2>"$diag_err" || true + local http_status="$(tr -d '\r\n' <"$diag_status")" + printf 'curl: %s\n' "$(tr -d '\r' <"$err_file")" >&2 + if [[ -s "$diag_body" ]]; then + printf 'Response body:\n' >&2 + cat "$diag_body" >&2 + printf '\n' >&2 + fi + fail "${name} probe failed with HTTP ${http_status:-unknown}." + ;; + *) + printf 'curl: %s\n' "$(tr -d '\r' <"$err_file")" >&2 + fail "${name} probe failed with curl exit ${curl_exit}." + ;; + esac +} + +auth_config_body="$TMP_DIR/auth-config.json" +request_json "auth config" "$API_BASE/auth/config" "$auth_config_body" + +require_auth="$(json_get "$auth_config_body" 'requireAuth')" +google_enabled="$(json_get "$auth_config_body" 'googleEnabled')" +local_enabled="$(json_get "$auth_config_body" 'localEnabled')" +allow_registration="$(json_get "$auth_config_body" 'allowRegistration')" + +note "Preflight target: ${API_BASE}" +note "cors.requiredOriginPair=UI http://localhost:3000 -> API http://localhost:5202/api" +note "auth.requireAuth=${require_auth}" +note "auth.localEnabled=${local_enabled}" +note "auth.googleEnabled=${google_enabled}" +note "auth.allowRegistration=${allow_registration}" + +system_body="$TMP_DIR/admin-system.json" +declare -a auth_headers=() +if [[ -n "$AUTH_TOKEN" ]]; then + auth_headers+=(-H "Authorization: Bearer ${AUTH_TOKEN}") +fi + +if request_json "admin system" "$API_BASE/admin/system" "$system_body" "${auth_headers[@]}"; then + db_provider="$(json_get "$system_body" 'database.provider')" + db_configured="$(json_get "$system_body" 'database.looksConfigured')" + db_connect="$(json_get "$system_body" 'database.canConnect')" + db_target="$(json_get "$system_body" 'database.target')" + db_warning="$(json_get "$system_body" 'database.warning')" + auth_required_runtime="$(json_get "$system_body" 'auth.required')" + gmail_configured="$(json_get "$system_body" 'auth.gmailConfigured')" + ai_healthy="$(json_get "$system_body" 'ai.healthy')" + ai_model="$(json_get "$system_body" 'ai.model')" + ai_last_error="$(json_get "$system_body" 'ai.lastError')" + + note "db.provider=${db_provider}" + note "db.looksConfigured=${db_configured}" + note "db.canConnect=${db_connect}" + note "db.target=${db_target}" + note "db.warning=${db_warning}" + note "auth.runtimeRequired=${auth_required_runtime}" + note "gmailConfigured=${gmail_configured}" + note "ai.healthy=${ai_healthy}" + note "ai.model=${ai_model}" + note "ai.lastError=${ai_last_error}" + + if [[ "$db_connect" != "true" ]]; then + fail "Database is not connectable according to /admin/system." + fi + + note "Preflight passed." + exit 0 +fi + +note "db.provider=unknown" +note "db.looksConfigured=unknown" +note "db.canConnect=unknown" +note "db.target=unknown" +note "db.warning=admin token required to inspect database status" +note "gmailConfigured=unknown" +note "ai.healthy=unknown" +note "ai.model=unknown" +note "ai.lastError=unknown" +note "Preflight partially passed: anonymous auth config is reachable, but admin/system requires an admin bearer token for DB/Gmail/AI details." diff --git a/scripts/s06-preflight.test.sh b/scripts/s06-preflight.test.sh new file mode 100755 index 0000000..92d18b6 --- /dev/null +++ b/scripts/s06-preflight.test.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SCRIPT="$ROOT_DIR/scripts/s06-preflight.sh" +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +pass() { + printf 'PASS: %s\n' "$1" +} + +fail() { + printf 'FAIL: %s\n' "$1" >&2 + exit 1 +} + +run_expect_failure() { + local name="$1" + local expected="$2" + shift 2 + local stdout_file="$TMP_DIR/${name}.out" + local stderr_file="$TMP_DIR/${name}.err" + + set +e + "$@" >"$stdout_file" 2>"$stderr_file" + local exit_code=$? + set -e + + if [[ $exit_code -eq 0 ]]; then + printf 'stdout:\n' >&2 + cat "$stdout_file" >&2 + printf '\nstderr:\n' >&2 + cat "$stderr_file" >&2 + fail "${name} unexpectedly succeeded" + fi + + if ! grep -Fq "$expected" "$stderr_file" && ! grep -Fq "$expected" "$stdout_file"; then + printf 'stdout:\n' >&2 + cat "$stdout_file" >&2 + printf '\nstderr:\n' >&2 + cat "$stderr_file" >&2 + fail "${name} did not contain expected text: ${expected}" + fi + + pass "$name" +} + +run_expect_failure api_down "Start the API from JobTrackerApi/ with 'dotnet run'" env API_BASE="http://127.0.0.1:59999/api" bash "$SCRIPT" +run_expect_failure wrong_api_base "Start the API from JobTrackerApi/ with 'dotnet run'" env API_BASE="http://localhost:1/api" bash "$SCRIPT" + +SERVER_LOG="$TMP_DIR/mock.log" +python3 - <<'PY' >"$SERVER_LOG" 2>&1 & +import http.server +import json +import socketserver + +class Handler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + if self.path == '/api/auth/config': + payload = { + 'requireAuth': True, + 'googleEnabled': True, + 'localEnabled': True, + 'allowRegistration': False, + } + body = json.dumps(payload).encode('utf-8') + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.send_header('Content-Length', str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + if self.path == '/api/admin/system': + body = b'{not-json\n' + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.send_header('Content-Length', str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + self.send_response(404) + self.end_headers() + + def log_message(self, fmt, *args): + return + +with socketserver.TCPServer(('127.0.0.1', 59998), Handler) as httpd: + httpd.serve_forever() +PY +SERVER_PID=$! +trap 'kill "$SERVER_PID" >/dev/null 2>&1 || true; rm -rf "$TMP_DIR"' EXIT +sleep 1 +run_expect_failure malformed_json "Malformed JSON returned by API." env API_BASE="http://127.0.0.1:59998/api" bash "$SCRIPT" +kill "$SERVER_PID" >/dev/null 2>&1 || true +wait "$SERVER_PID" 2>/dev/null || true + +pass "all preflight negative tests"