Delete useApiFetch.ts wrapper (returned T|null) and update prep.ts and
prep.test.ts to import useApiFetch from useApi.ts directly, destructuring
{ data, error } to match the established pattern used by all other stores.
Adds usePrepStore (Pinia) for interview prep data: parallel fetch of
research brief, contacts, task status, and full job detail; setInterval-
based polling that stops on completion and re-fetches; clear() cancels
the interval and resets all state. Also adds useApiFetch composable
wrapper (returns T|null directly) used by the store.
- Replace lazy import + scripts.db.get_research with inline SQL via _get_db(),
matching the pattern used by research_task_status and get_job_contacts
- Exclude raw_output from SELECT instead of post-fetch pop
- Change HTTPException in generate_research to positional-arg style
- Update test_get_research_found/not_found to patch dev_api._get_db
- _parse_message now prefers text/html over text/plain so digest emails
retain href attribute values needed for link extraction
- Strip <head>, <style>, <script> blocks before storing to remove CSS/JS
garbage while keeping anchor tags intact
- Remove [:4000] truncation — digest emails need full body for URL regex
- Update test: large body should NOT be truncated (assert len == 10_000)
Setup-form Pinia store with per-stage computed lanes, optimistic status
mutation on move, and API-error rollback. Shallow-copies API response
objects on fetch to prevent shared-reference mutation across tests.
Adds GET /api/interviews to fetch all pipeline-stage jobs in one call,
and POST /api/jobs/:id/move to transition a job between kanban statuses
with automatic timestamp stamping (or rejection_stage capture).
- router: add /apply/:id → ApplyWorkspaceView (lazy-loaded)
- ApplyView.vue: approved job list sorted by match score; badges for
match %, remote, and cover-letter draft status; links to workspace
- ApplyWorkspaceView.vue: two-panel desktop layout (sticky job details
left, editor right); cover letter state machine (none/queued/running/
ready/failed); auto-resize textarea; word count toolbar; Save button
with dirty tracking; Download PDF (programmatic <a> click, named file);
Generate with AI + Retry; Mark as Applied + Reject Listing actions;
polling loop for in-flight generation tasks; toast on action
- HomeView.vue: split combined "Archive Pending + Rejected" into three
separate per-status archive buttons (only shown when count > 0)
- dev-api.py: add GET /api/jobs/:id, POST /api/jobs/:id/applied,
PATCH /api/jobs/:id/cover_letter, POST .../cover_letter/generate
(wires submit_task), GET .../cover_letter/task (poll), GET .../pdf
(reportlab); has_cover_letter field on list + detail responses
When a new job prop arrives after approve/reject, the watch cleared the
exit-transform inline style while the spring transition was still active,
causing the card to animate from offscreen back to center before the new
card rendered. Fix: set transition:none before clearing the style, then
restore it on the next rAF — browser paints the new position first.