feat(workspace): unified application checklist (Phase 5 milestone 2)
Evolve the existing readiness workflow into one persisted, user-controlled checklist rather than adding a second tracker. ApplicationChecklistItem records only completion state and user intent. Each default system item carries a stable SystemKey and an AutoSignal — the same signal /readiness already computed — and re-syncs on every read: a satisfied signal auto-completes the item, a reverted signal reopens it, and a manual tick always wins. Users can add, reorder, dismiss and delete. Readiness is refactored into a projection of the checklist (score = completion percentage, completed/missing = live items by status). Its DTO shape and the workflowSignal/reminders health view are unchanged, so no API contract breaks. The workspace's next recommended action now comes from the first pending checklist item in category priority order (preparation, submission, follow-up, interview, custom), replacing the parallel ruleset — so the overview can never recommend something already ticked off, and a user's own task can be next. The table follows the established MariaDB-safe path: the scaffolded migration is a no-op and the idempotent reconciler owns the DDL for both providers. Verified on MariaDB 11 — auto_increment PK, varchar/datetime(6)/tinyint(1) columns, both indexes inside the key limit, cascade delete, unique system key per application, and NULL system keys not colliding for custom items. 329 backend tests, 94 frontend tests, type check, production build and both Docker builds pass locally. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Application Workspace (Phase 5)
|
||||
|
||||
> Phase 5, Milestone 1 (2026-07-18). The per-application workspace: what it is, what it deliberately
|
||||
> Phase 5, Milestones 1–2 (2026-07-19). The per-application workspace: what it is, what it deliberately
|
||||
> is not, and how it composes existing systems. Companion to `cv-builder.md`,
|
||||
> `ai-career-assistant.md`, `career-profile-model.md`.
|
||||
|
||||
@@ -18,6 +18,7 @@ The workspace **owns no data and duplicates none**. It is an aggregate read plus
|
||||
|
||||
| Section | Backed by (existing system) |
|
||||
|---|---|
|
||||
| Checklist | `ApplicationChecklistItem` — completion state only, seeded from the readiness signals |
|
||||
| CV | Phase 4 `CvVariant` — a lens over the master `CareerProfile` |
|
||||
| Analysis / Match / Interview | Phase 5 `AiWorkspacePanel` + `AiInteraction` history |
|
||||
| Documents | `Attachment` |
|
||||
@@ -42,19 +43,86 @@ Read-only and tenant-scoped (`OwnerUserId`), returning 404 for another user's ap
|
||||
|
||||
### Next recommended action
|
||||
|
||||
First unmet rule wins, ordered to match where the application actually is — understand the role,
|
||||
prepare the material, send it, then chase it:
|
||||
Milestone 2 moved this onto the checklist: **the first pending checklist item**, ordered by category
|
||||
priority (`preparation` → `submission` → `follow-up` → `interview` → `custom`) then the user's own
|
||||
ordering. `null` means nothing is outstanding.
|
||||
|
||||
1. `add-job-details` — no advert text (analysis and matching need it)
|
||||
2. `prepare-cv` — no CV variant attached and no tailored CV text
|
||||
3. `write-cover-letter` — no cover letter
|
||||
4. `attach-documents` — nothing attached
|
||||
5. `prepare-interview` — the application reached an interview stage
|
||||
6. `schedule-follow-up` — applied with no follow-up date
|
||||
7. `set-next-action` — no next action written
|
||||
That means the overview cannot recommend something the user has already ticked off, a dismissed item
|
||||
never comes back as a recommendation, and a task the user added themselves can legitimately be the
|
||||
next action. There is no second ruleset to keep in sync.
|
||||
|
||||
Prospect-stage applications short-circuit to CV → cover letter → submit. `null` means nothing is
|
||||
outstanding.
|
||||
## The checklist
|
||||
|
||||
`ApplicationChecklistItem` — a **workflow guidance layer**, not a store of truth. It records only "is
|
||||
this step done, and does the user still want it". The CV still lives in `CvVariant`, documents in
|
||||
`Attachment`, history in `JobEvent`, follow-up in `JobApplication.FollowUpAt`.
|
||||
|
||||
### System items and auto-completion
|
||||
|
||||
Each default item carries a stable `SystemKey` and usually an `AutoSignal` — the *same* signal
|
||||
`/readiness` already computed. On every read the service re-syncs:
|
||||
|
||||
- signal satisfied + item pending → **done**, `IsAutoCompleted = true`
|
||||
- signal no longer satisfied + item was auto-completed → back to **pending**
|
||||
- a **manual** tick clears `IsAutoCompleted` and therefore sticks, even against the signal
|
||||
|
||||
So "readiness says the CV is missing" and "the checklist says Prepare a CV is pending" cannot drift
|
||||
apart — they read the same state. The user always wins over the signal.
|
||||
|
||||
| System key | Category | Signal |
|
||||
|---|---|---|
|
||||
| `review-job-details` | preparation | advert text present |
|
||||
| `complete-career-profile` | preparation | career profile with at least one experience |
|
||||
| `prepare-cv` | preparation | CV variant attached, or tailored CV text |
|
||||
| `review-cv-match` | preparation | *manual* |
|
||||
| `create-cover-letter` | preparation | cover letter present |
|
||||
| `attach-portfolio` | preparation | `HasPortfolio` |
|
||||
| `attach-supporting-documents` | preparation | at least one `Attachment` |
|
||||
| `save-application-answers` | preparation | saved answer draft in `Notes` |
|
||||
| `capture-recruiter-contact` | preparation | `Company.RecruiterEmail` |
|
||||
| `confirm-submitted` | submission | applied date set and out of the prospect stage |
|
||||
| `add-follow-up-reminder` | follow-up | `FollowUpAt` set |
|
||||
| `set-next-action` | follow-up | `NextAction` written |
|
||||
| `prepare-interview-notes` | interview | prep notes present, **or** not at an interview stage |
|
||||
| `research-company` | interview | *manual* |
|
||||
|
||||
Seeding is idempotent per `(JobApplicationId, SystemKey)` — enforced by a unique index, so a re-read
|
||||
never duplicates. Custom items have a `NULL` `SystemKey`; both SQLite and MariaDB treat NULLs as
|
||||
distinct in a unique index, so a user can add as many as they like.
|
||||
|
||||
Deleting a **system** item dismisses it (a hard delete would be undone by the next seed); deleting a
|
||||
**custom** item removes the row. Dismissed items leave the progress denominator entirely.
|
||||
|
||||
### API
|
||||
|
||||
`/api/jobapplications/{id}/checklist` — `GET` (seeds + syncs + returns items and progress),
|
||||
`POST` (custom item), `PATCH /{itemId}`, `DELETE /{itemId}`, `PUT /order` (array of ids).
|
||||
Tenant-scoped on `OwnerUserId`; another user's application is a 404.
|
||||
|
||||
### Relationship to the other systems
|
||||
|
||||
- **Not `JobEvent`** — the checklist is forward-looking intent; `JobEvent` is the append-only history.
|
||||
- **Not follow-ups** — `FollowUpAt`, `RulesEngine` and the reminder hosted service still own
|
||||
scheduling. The checklist only asks whether a follow-up exists.
|
||||
- **Not `Attachment` / `CvVariant`** — it reads their presence as a signal and stores nothing of them.
|
||||
|
||||
### Future AI suggestions
|
||||
|
||||
An AI-suggested task is just a checklist row with `IsSystemGenerated = false` and no `AutoSignal`,
|
||||
created after the user approves it. Nothing in the AI path may create, complete or delete an item
|
||||
without approval — same rule as everywhere else (`ai-career-assistant.md`).
|
||||
|
||||
### Schema provisioning
|
||||
|
||||
`ApplicationChecklistItems` follows the established MariaDB-safe path: the scaffolded migration
|
||||
(`20260719085904_AddApplicationChecklistItems`) is a **no-op**, and the table is created by the
|
||||
idempotent reconciler in `StartupInitializationExtensions`, which has correct DDL per provider. A
|
||||
SQLite-scaffolded migration would emit `TEXT` datetimes and a PK without `AUTO_INCREMENT` on MariaDB —
|
||||
the failure that crashed prod startup for the Phase 4 tables.
|
||||
|
||||
Verified on MariaDB 11: `int AUTO_INCREMENT` PK, `varchar`/`datetime(6)`/`tinyint(1)` columns, both
|
||||
indexes inside the 3072-byte key limit, cascade delete from `JobApplications`, the unique index
|
||||
rejecting a duplicate system key, and NULL system keys not colliding.
|
||||
|
||||
## Frontend
|
||||
|
||||
@@ -66,18 +134,29 @@ The dialog passes an optional `onOpenWorkspace` callback rather than calling `us
|
||||
`JobDetailsDialog` must stay renderable without a `<Router>` (several suites mount it standalone), so
|
||||
router context belongs to the caller.
|
||||
|
||||
Implemented now: Overview, Job Details, and the sections that reuse an existing component
|
||||
The Checklist section (`ApplicationChecklist`) groups items by category, shows a completion bar, and
|
||||
supports tick/untick, add, remove and reorder. System items are labelled "Detected" when a signal
|
||||
completed them, custom items "Yours". Every mutation re-reads, because only the backend's sync knows
|
||||
the real post-mutation state.
|
||||
|
||||
Implemented now: Overview, Checklist, Job Details, and the sections that reuse an existing component
|
||||
(Analysis/Match/Interview → `AiWorkspacePanel`, Documents → `Attachments`,
|
||||
Communication → `Correspondence`). Sections owned by later milestones state their milestone instead of
|
||||
faking functionality.
|
||||
|
||||
## Relationship to `/readiness`
|
||||
|
||||
`GET /{id}/readiness` already computes a polish-oriented checklist (score, completed, missing,
|
||||
reminders) and still backs the dialog's Readiness tab. The workspace's next-action rules are
|
||||
deliberately narrower and action-shaped. Milestone 2 introduces the persisted, user-editable checklist
|
||||
and folds the readiness signals into it as defaults — at which point the overlap is resolved in one
|
||||
place rather than two.
|
||||
`GET /{id}/readiness` keeps its DTO shape (`score`, `level`, `completed`, `missing`, `reminders`,
|
||||
`workflowSignal`) and still backs the dialog's Readiness tab — but as of Milestone 2 it no longer runs
|
||||
its own parallel checklist. It **projects** the persisted checklist:
|
||||
|
||||
- `completed` / `missing` — the live (non-dismissed) items by status
|
||||
- `score` — the checklist completion percentage
|
||||
- `level` — Ready ≥ 80, Needs polish ≥ 60, otherwise Needs work
|
||||
- `reminders` / `workflowSignal` — unchanged; `BuildWorkflowSignal` remains the health/attention view
|
||||
|
||||
So the division is: **the checklist is the workflow the user drives, readiness is the calculation and
|
||||
health indicator derived from it.** One system, two projections.
|
||||
|
||||
## Extension points
|
||||
|
||||
@@ -89,6 +168,7 @@ place rather than two.
|
||||
## Milestones
|
||||
|
||||
1. ✅ Workspace foundation — route, nav shell, aggregate overview, next recommended action.
|
||||
2. Checklist and progress tracking (persisted + custom items).
|
||||
2. ✅ Checklist and progress tracking — persisted items, auto-completion from the readiness signals,
|
||||
custom items, reordering, dismissal; readiness refactored into a projection of it.
|
||||
3. Timeline and activity history. 4. Job analysis. 5. Career matching. 6. CV integration.
|
||||
7. Cover letter workflow. 8. Documents. 9. Interview preparation. 10. Dashboard improvements.
|
||||
|
||||
Reference in New Issue
Block a user