Commit graph

455 commits

Author SHA1 Message Date
5bca5aaa20 fix: DemoBanner button contrast — use semantic surface token instead of hardcoded white
--color-primary in dark mode is a medium-light green (#6ab870); white on green
yields ~2.2:1 contrast (fails WCAG AA 4.5:1 minimum). Using --color-surface
(dark navy in dark mode, near-white in light mode) ensures the text always
contrasts strongly with the primary background regardless of theme.

Also tints banner background with 8% primary via color-mix() so it reads as
visually distinct from the page surface without being loud.
2026-04-21 10:14:37 -07:00
230cfb074c fix(demo): smoke-test fixes — card reset, toast error type, apply hint, text contrast
- JobCardStack: expose resetCard() to restore card after a blocked action
- JobReviewView: call resetCard() when approve/reject returns false; prevents
  card going blank after demo guard blocks the action
- useApi: add 'demo-blocked' to ApiError union; return truthy error from the
  403 interceptor so store callers bail early (no optimistic UI update)
- ApplyView: add HintChip to desktop split-pane layout (was mobile-only)
- HintChip: fix text color — --app-primary-light is near-white in light theme,
  causing invisible text; switch to --color-text for cross-theme contrast
- vite.config.ts: support VITE_API_TARGET env var for dev proxy override
- migrations/006: add date_posted, hired_feedback columns and references_ table
  (columns existed in live DB but were missing from migration history)
- DemoBanner: commit component and test (were untracked)
2026-04-21 10:14:37 -07:00
302033598c feat(demo): switch demo data volume to tmpfs, wire DEMO_SEED_FILE 2026-04-21 10:14:37 -07:00
ad26f02d5f feat(demo): add committed seed SQL and startup loader 2026-04-21 10:14:36 -07:00
03206aa34c feat(demo): add IS_DEMO write-block guard on mutating endpoints 2026-04-21 10:14:15 -07:00
55f464080f feat(demo): wire DemoBanner, WelcomeModal, HintChip into app + views 2026-04-21 10:14:15 -07:00
d96cdfa89b feat(demo): add HintChip component with per-view localStorage dismiss 2026-04-21 10:14:15 -07:00
a16d562e06 feat(demo): add WelcomeModal with localStorage gate 2026-04-21 10:14:15 -07:00
63334f5278 feat: messaging tab — messages, templates, draft reply (#74)
Some checks failed
CI / Backend (Python) (push) Failing after 1m48s
CI / Frontend (Vue) (push) Failing after 21s
Mirror / mirror (push) Failing after 7s
Merges feature/messaging-tab into main.

Features:
- Migration 008: messages + message_templates tables with 4 built-in templates
- API endpoints: CRUD for messages and templates, draft-reply (BYOK tier gate), approve
- PUT /api/messages/{id} for draft body persistence
- Pinia store (messaging.ts) with full action set
- MessageLogModal: log calls and in-person meetings with backdated timestamps
- MessageTemplateModal: apply (with token substitution + highlight), create, edit
- MessagingView: two-panel job list + UNION timeline (contacts + messages), Osprey easter egg
- Router: /messages route, /contacts redirect, nav renamed Messages
- Integration test suite (8 tests, 766 total passing)
- CRITICAL fix: _get_effective_tier() no longer trusts X-CF-Tier client header
2026-04-20 20:26:41 -07:00
b1e92b0e52 feat(docker): add /peregrine/ base-path routing in nginx
Adds location blocks for /peregrine/assets/ and /peregrine/ so the SPA
works correctly when accessed via a Caddy prefix that does not strip the
path (e.g. direct host access without reverse proxy stripping).
2026-04-20 20:26:31 -07:00
91e2faf5d0 fix: tier bypass, draft body persistence, canDraftLlm cleanup, limit cap
- CRITICAL: Remove X-CF-Tier header trust from _get_effective_tier; use
  Heimdall in cloud mode and APP_TIER env var in single-tenant only
- HIGH: Add update_message_body helper + PUT /api/messages/{id} endpoint;
  updateMessageBody store action; approveDraft now persists edits to DB
  before calling approve so history always shows the final approved text
- Cleanup: Remove dead canDraftLlm ref, checkLlmAvailable function, and
  v-else-if Enable LLM drafts link; show Draft reply button unconditionally
- MEDIUM: Cap GET /api/messages limit param with Query(ge=1, le=1000)
- Test: Update test_draft_without_llm_returns_402 to patch effective_tier
  instead of sending X-CF-Tier header
2026-04-20 17:19:17 -07:00
6812e3f9ef feat: /messages route + /contacts redirect + nav rename (#74) 2026-04-20 13:04:27 -07:00
899cd3604b feat: MessagingView two-panel layout + draft approval + Osprey easter egg (#74) 2026-04-20 13:02:24 -07:00
aa09b20e7e feat: MessageTemplateModal component (apply/create/edit modes) (#74) 2026-04-20 12:58:00 -07:00
b77ec81cc6 fix: thread logged_at through message stack; Esc handler and localNow fixes
- scripts/messaging.py: add logged_at param to create_message; use provided value or fall back to _now_utc()
- dev-api.py: add logged_at: Optional[str] = None to MessageCreateBody
- web/src/stores/messaging.ts: remove logged_at from Omit, add as optional intersection so callers can pass it through
- web/src/components/MessageLogModal.vue: pass logged_at in handleSubmit payload; move @keydown.esc from backdrop to modal-dialog (which holds focus); compute localNow fresh inside watch so it reflects actual open time
2026-04-20 12:55:41 -07:00
8df3297ab6 feat: MessageLogModal component (#74) 2026-04-20 12:52:19 -07:00
222eb4a088 fix: messaging store error handling and Content-Type headers 2026-04-20 12:50:51 -07:00
47a40c9e36 feat: messaging Pinia store (#74) 2026-04-20 12:48:15 -07:00
dfcc264aba test: use db.add_contact helper in integration test fixture
Replace raw sqlite3 INSERT in test_draft_without_llm_returns_402 with
add_contact() so the fixture stays in sync with schema changes
automatically.
2026-04-20 12:45:47 -07:00
d3dfd015bf feat(cloud): add CF_APP_NAME=peregrine for coordinator pipeline attribution
Allocations from peregrine cloud containers were showing pipeline=null
in cf-orch analytics. Adding CF_APP_NAME to both app and api service
blocks so LLMRouter passes it as the pipeline tag on each allocation.
2026-04-20 12:43:05 -07:00
e11750e0e6 test: messaging HTTP integration tests (#74) 2026-04-20 12:41:45 -07:00
715a8aa33e feat: LLM reply draft, tiers BYOK gate, and messaging API endpoints (#74) 2026-04-20 12:36:16 -07:00
091834f1ae test: add missing update_template KeyError test (#74) 2026-04-20 12:32:35 -07:00
ea961d6da9 feat: messaging DB helpers + unit tests (#74) 2026-04-20 11:55:43 -07:00
9eca0c21ab feat: migration 008 — messages + message_templates tables (#74) 2026-04-20 11:51:59 -07:00
5020144f8d fix: update interview + survey tests for hired_feedback column and async analyze endpoint 2026-04-20 11:48:22 -07:00
9101e716ba fix: async survey/analyze via task queue (#107)
Move POST /api/jobs/:id/survey/analyze off the FastAPI worker thread
by routing it through the LLM task queue (same pattern as cover_letter,
company_research, resume_optimize).

- Extract prompt builders + run_survey_analyze() to scripts/survey_assistant.py
- Add survey_analyze to LLM_TASK_TYPES (task_scheduler.py) with 2.5 GB VRAM budget
  (text mode: phi3:mini; visual mode uses vision service's own VRAM pool)
- Add elif branch in task_runner._run_task; result stored as JSON in error col
- Replace sync endpoint body with submit_task(); add GET /survey/analyze/task poll
- Update survey.ts store: analyze() now fires task + polls at 3s interval;
  silently attaches to existing in-flight task when is_new=false
- SurveyView button label shows task stage while polling

Fixes load-test spike: ~22 greenlets blocking on LLM inference at 100 concurrent
users, causing 90s poll timeouts on cover_letter and research tasks.
2026-04-20 11:06:14 -07:00
acc04b04eb docs(config): add cf_text and cf_voice trunk service backends to llm.yaml.example
Documents the cf-orch allocation pattern for cf-text and cf-voice as
openai_compat backends with a cf_orch block. Products enable these when
CF_ORCH_URL is set; the router allocates via the broker and calls the
managed service directly. No catalog or leaf details here — those live
in cf-orch node profiles (The Orchard trunk/leaf split).
2026-04-20 10:56:22 -07:00
280f4271a5 feat: add Plausible analytics to Vue SPA and docs
Some checks failed
CI / Backend (Python) (push) Failing after 1m13s
CI / Frontend (Vue) (push) Failing after 20s
Mirror / mirror (push) Failing after 7s
2026-04-16 21:15:55 -07:00
1c9bfc9fb6 test: integration tests for resume library<->profile sync endpoints 2026-04-16 14:29:00 -07:00
22bc57242e feat: ResumeProfileView — career_summary, education, achievements sections and sync status label 2026-04-16 14:22:36 -07:00
9f984c22cb feat: resume store — add career_summary, education, achievements, lastSynced state
Extends the resume Pinia store with EducationEntry interface, four new
refs (career_summary, education, achievements, lastSynced), education
CRUD helpers, and load/save wiring for all new fields. lastSynced is
set to current ISO timestamp on successful save.
2026-04-16 14:15:07 -07:00
fe3e4ff539 feat: ResumesView — Apply to profile button, Active profile badge, sync notice, unsaved-changes guard 2026-04-16 14:13:44 -07:00
43599834d5 feat: ResumeSyncConfirmModal — before/after confirmation for profile sync 2026-04-16 14:11:37 -07:00
fe5371613e feat: extend PUT /api/settings/resume to sync content back to default library entry
When a default_resume_id is set in user.yaml, saving the resume profile
now calls profile_to_library and update_resume_content to keep the
library entry in sync. Returns {"ok": true, "synced_library_entry_id": <int|null>}.
2026-04-16 14:09:56 -07:00
369bf68399 feat: POST /api/resumes/{id}/apply-to-profile — library→profile sync with auto-backup 2026-04-16 14:06:52 -07:00
eef6c33d94 feat: add EducationEntry model, extend ResumePayload with education/achievements/career_summary
- Add EducationEntry Pydantic model (institution, degree, field, start_date, end_date)
- Extend ResumePayload with career_summary str, education List[EducationEntry], achievements List[str]
- Rewrite _normalize_experience to pass through Vue-native format (period/responsibilities keys) unchanged; AIHawk format (key_responsibilities/employment_period) still converted
- Extend GET /api/settings/resume to fall back to user.yaml for legacy career_summary when resume YAML is missing or the field is empty
2026-04-16 14:02:59 -07:00
53bfe6b326 feat: add update_resume_synced_at and update_resume_content db helpers
Expose synced_at in _resume_as_dict (with safe fallback for pre-migration
DBs), and add two new helpers: update_resume_synced_at (library→profile
direction) and update_resume_content (profile→library direction, updates
text/struct_json/word_count/synced_at/updated_at).
2026-04-16 13:14:10 -07:00
cd787a2509 fix: period split in profile_to_library handles ISO dates with hyphens
Fixes a bug where ISO-formatted dates (e.g. '2023-01 – 2025-03') in the
period field were split incorrectly. The old code replaced the en-dash with
a hyphen first, then split on the first hyphen, causing dates like '2023-01'
to be split into '2023' and '01' instead of the expected start/end pair.

The fix splits on the dash/dash separator *before* normalizing to plain
hyphens, ensuring round-trip conversion of dates with embedded hyphens.

Adds two regression tests:
- test_profile_to_library_period_split_iso_dates: verifies en-dash separation
- test_profile_to_library_period_split_em_dash: verifies em-dash separation
2026-04-16 13:11:22 -07:00
048a5f4cc3 feat: resume_sync.py — library↔profile transform functions with tests
Pure transform functions (no LLM, no DB) bridging the two resume
representations: library struct_json ↔ ResumePayload content fields.
Exports library_to_profile_content, profile_to_library,
make_auto_backup_name, blank_fields_on_import. 22 tests, all passing.
2026-04-16 13:04:56 -07:00
fe4947a72f feat: add synced_at column to resumes table (migration 007) 2026-04-16 12:58:00 -07:00
4e11cf3cfa fix: sanitize invalid JSON escape sequences from LLM output in resume optimizer
LLMs occasionally emit backslash sequences that are valid regex but not valid
JSON (e.g. \s, \d, \p). This caused extract_jd_signals() to fall through to
the exception handler, leaving llm_signals empty. With no LLM signals, the
optimizer fell back to TF-IDF only — which is more conservative and can
legitimately return zero gaps, making the UI appear to say the resume is fine.

Fix: strip bare backslashes not followed by a recognised JSON escape character
("  \  /  b  f  n  r  t  u) before parsing. Preserves \n, \", etc.

Reproduces: cover letter generation concurrent with gap analysis raises the
probability of a slightly malformed LLM response due to model load.
2026-04-16 11:11:50 -07:00
a4a2216c2f ci: add GitHub Actions CI for public credibility badge
Some checks failed
CI / Backend (Python) (push) Failing after 1m16s
CI / Frontend (Vue) (push) Failing after 19s
Mirror / mirror (push) Failing after 7s
Lean self-contained workflow — no Forgejo-specific secrets.
circuitforge-core installs from Forgejo git (public repo).
Forgejo (.forgejo/workflows/ci.yml) remains the canonical CI.

Backend: ruff + pytest | Frontend: vue-tsc + vitest
2026-04-15 20:20:13 -07:00
797032bd97 ci: remove stale .github/workflows/ci.yml
Some checks failed
CI / Backend (Python) (push) Failing after 1m21s
CI / Frontend (Vue) (push) Failing after 19s
Mirror / mirror (push) Failing after 10s
The .forgejo/workflows/ci.yml is the canonical CI definition.
The old .github/workflows/ci.yml was being mirrored to GitHub via
--mirror push, triggering GitHub Actions runs that fail because
FORGEJO_TOKEN and other Forgejo-specific secrets are not set there.

GitHub Actions does not process .forgejo/workflows/ so removing
this file stops the spurious GitHub runs. ISSUE_TEMPLATE and
pull_request_template.md are preserved in .github/.
2026-04-15 20:11:07 -07:00
fb8b464dd0 fix: use resume_parser extractors in import endpoint to clean CID glyphs
The import endpoint was doing its own inline PDF/DOCX/ODT extraction
without calling _clean_cid(). Bullet CIDs (127, 149, 183) and other
ATS-reembedded font artifacts were stored raw, surfacing as (cid:127)
in the resume library. Switch to extract_text_from_pdf/docx/odt from
resume_parser.py which already handle two-column layouts and CID cleaning.
2026-04-15 12:23:12 -07:00
ec521e14c5 fix: sweep user DBs on cloud startup for pending migrations 2026-04-15 12:18:23 -07:00
a302049f72 fix: add date_posted migration + cloud startup sweep
date_posted column was added to db.py CREATE TABLE but had no migration
file, so existing user DBs were missing it. The list_jobs endpoint queries
this column, causing 500 errors and empty Apply/Review queues for all
existing cloud users while job_counts (which doesn't touch date_posted)
continued to work — making the home page show correct counts but tabs show
empty data.

Fixes:
- migrations/006_date_posted.sql: ALTER TABLE to add date_posted to existing DBs
- dev_api.py lifespan: on startup in cloud mode, sweep all user DBs in
  CLOUD_DATA_ROOT and apply pending migrations — ensures schema changes land
  for every user on each deploy, not only on their first post-deploy request
2026-04-15 12:17:55 -07:00
03b9e52301 feat: references tracker and recommendation letter system (#96)
- references_ + job_references tables with CREATE + migration
- Full CRUD: GET/POST /api/references, PATCH/DELETE /api/references/:id
- Link/unlink to jobs: POST/DELETE /api/references/:id/link-job/:job_id
- GET /api/references/for-job/:job_id — linked refs with prep/letter drafts
- POST /api/references/:id/prep-email — LLM drafts heads-up email to send
  reference before interview; persisted to job_references.prep_email
- POST /api/references/:id/rec-letter — LLM drafts recommendation letter
  reference can edit and send on their letterhead (Paid/BYOK tier)
- ReferencesView.vue: add/edit/delete form, tag system (technical/managerial/
  character/academic), inline confirm-before-delete
- Route /references + IdentificationIcon nav link
2026-04-15 08:42:06 -07:00
0e4fce44c4 feat: shadow listing detector, hired feedback widget, contacts manager
Shadow listing detector (#95):
- Capture date_posted from JobSpy in discover.py + insert_job()
- Add date_posted migration to _MIGRATIONS
- _shadow_score() heuristic: 'shadow' (≥30 days stale), 'stale' (≥14 days)
- list_jobs() computes shadow_score per listing
- JobCard.vue: 'Ghost post' and 'Stale' badges with tooltip

Post-hire feedback widget (#91):
- Add hired_feedback migration to _MIGRATIONS
- POST /api/jobs/:id/hired-feedback endpoint
- InterviewCard.vue: optional widget on hired cards with factor
  checkboxes + freetext; dismissible; shows saved state
- PipelineJob interface extended with hired_feedback field

Contacts manager (#73):
- GET /api/contacts endpoint with job join, direction/search filters
- New ContactsView.vue: searchable table, inbound/outbound filter,
  signal chip column, job link
- Route /contacts added; Contacts nav link (UsersIcon) in AppNav

Also: add git to Dockerfile apt-get for circuitforge-core editable install
2026-04-15 08:34:12 -07:00
6599bc6952 chore: ignore runtime data artifacts
Add gitignore entries for:
- data/.feedback_ratelimit.json (rate limit state)
- data/email_score.jsonl.bad-labels (debug artifact from label review)
- data/config/ (runtime config directory)
2026-04-15 08:16:14 -07:00