Commit graph

421 commits

Author SHA1 Message Date
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
8e36863a49 feat: Interview prep Q&A, cf-orch hardware profile, a11y fixes, dark theme
Some checks failed
CI / Backend (Python) (push) Failing after 2m15s
CI / Frontend (Vue) (push) Failing after 21s
Mirror / mirror (push) Failing after 9s
Backend
- dev-api.py: Q&A suggest endpoint, Log Contact, cf-orch node detection in wizard
  hardware step, canonical search_profiles format (profiles:[...]), connections
  settings endpoints, Resume Library endpoints
- db_migrate.py: migrations 002/003/004 — ATS columns, resume review, final
  resume struct
- discover.py: _normalize_profiles() for legacy wizard YAML format compat
- resume_optimizer.py: section-by-section resume parsing + scoring
- task_runner.py: Q&A and contact-log task types
- company_research.py: accessibility brief column wiring
- generate_cover_letter.py: restore _candidate module-level binding

Frontend
- InterviewPrepView.vue: Q&A chat tab, Log Contact form, MarkdownView rendering
- InterviewCard.vue: new reusable card component for interviews kanban
- InterviewsView.vue: rejected analytics section with stage breakdown chips
- ResumeProfileView.vue: sync with new resume store shape
- SearchPrefsView.vue: cf-orch toggle, profile format migration
- SystemSettingsView.vue: connections settings wiring
- ConnectionsSettingsView.vue: new view for integration connections
- MarkdownView.vue: new component for safe markdown rendering
- ApplyWorkspace.vue: a11y — h1→h2 demotion, aria-expanded on Q&A toggle,
  confirmation dialog on Reject action (#98 #99 #100)
- peregrine.css: explicit [data-theme="dark"] token block for light-OS users (#101),
  :focus-visible outline (#97)
- wizard.css: cf-orch hardware step styles
- WizardHardwareStep.vue: cf-orch node display, profile selection with orch option
- WizardLayout.vue: hardware step wiring

Infra
- compose.yml / compose.cloud.yml: cf-orch agent sidecar, llm.cloud.yaml mount
- Dockerfile.cfcore: cf-core editable install in image build
- HANDOFF-xanderland.md: Podman/systemd setup guide for beta tester
- podman-standalone.sh: standalone Podman run script

Tests
- test_dev_api_settings.py: remove stale worktree path bootstrap (credential_store
  now in main repo); fix job_boards fixture to use non-empty list
- test_wizard_api.py: update profiles assertion to superset check (cf-orch added);
  update step6 assertion to canonical profiles[].titles format
2026-04-14 17:01:18 -07:00
91943022a8 docs: add docs badge linking to docs.circuitforge.tech/peregrine in README
Some checks failed
CI / Backend (Python) (push) Failing after 1m17s
CI / Frontend (Vue) (push) Successful in 20s
Mirror / mirror (push) Failing after 10s
2026-04-14 08:19:57 -07:00
7467fb5416 feat: wire cf_text as openai_compat backend in llm.yaml
Some checks failed
CI / Backend (Python) (push) Failing after 12s
CI / Frontend (Vue) (push) Successful in 20s
Mirror / mirror (push) Failing after 7s
Adds the cf-text inference service (circuitforge-core) to the LLM
fallback chain as the first option for cover letter generation.
cf-text now exposes /v1/chat/completions (added in cf-core 69a338b),
making it a drop-in openai_compat backend at port 8006.

CF_TEXT_MODEL and CF_TEXT_PORT added to .env.example. Closes #75.
2026-04-12 17:10:41 -07:00
278413b073 feat: load mission alignment domains from config/mission_domains.yaml
Removes hardcoded _MISSION_SIGNALS and _MISSION_DEFAULTS dicts from
generate_cover_letter.py. Domains and signals are now defined in
config/mission_domains.yaml, which ships with the original 5 domains
(music, animal_welfare, education, social_impact, health) plus 3 new
ones (privacy, accessibility, open_source).

Any key in user.yaml mission_preferences not present in the YAML is
treated as a user-defined domain with no signal detection — custom
note only. Closes #78.
2026-04-12 16:46:13 -07:00
d60f05ec17 chore: release v0.8.6 — resume review modal + resume manager
Some checks failed
CI / Backend (Python) (push) Failing after 42s
CI / Frontend (Vue) (push) Successful in 26s
Mirror / mirror (push) Failing after 9s
Release / release (push) Failing after 5s
2026-04-12 12:26:46 -07:00
f22e713968 fix: review issues — import size limit, aria labels, CSS vars, Set reactivity 2026-04-12 11:52:23 -07:00
35cb99f99c feat: resume review modal + resume manager complete 2026-04-12 11:46:18 -07:00
1ada92f7d7 feat: add Resumes route and nav link
Add /resumes route (lazy-loaded ResumesView) to router and add
DocumentTextIcon Resumes entry to AppNav sidebar navLinks after Apply.
2026-04-12 11:36:13 -07:00
8245333c9c feat: add ResumeLibraryCard to Apply workspace 2026-04-12 11:35:06 -07:00
d4a2107411 feat: add ResumesView standalone resume library manager 2026-04-12 11:32:29 -07:00
f7b719f854 feat: replace inline review with ResumeReviewModal; add save-to-library on approve 2026-04-12 11:29:03 -07:00
a3aaed0e0c feat: add ResumeReviewModal with paged tab navigation and color-coded status 2026-04-12 11:18:01 -07:00
1253ef15a0 chore: deploy resume library endpoints to cloud 2026-04-12 11:12:02 -07:00
ae7549c2c9 feat: add resume library and per-job resume API endpoints
- POST/GET /api/resumes — create and list resumes
- POST /api/resumes/import — import from .txt/.pdf/.docx/.odt/.yaml
- GET/PATCH/DELETE /api/resumes/{id} — CRUD for individual resumes
- POST /api/resumes/{id}/set-default — set default resume
- GET/PATCH /api/jobs/{job_id}/resume — per-job resume association
- Extend approve_resume to optionally save to resume library (save_to_library + resume_name body fields)
- 9 passing tests in tests/test_resumes_api.py
2026-04-12 10:42:38 -07:00
365eff1506 feat: add resume library CRUD helpers to db.py 2026-04-12 10:39:32 -07:00
6e73bfc48a feat: add resumes table and jobs.resume_id column (migration 005) 2026-04-12 10:36:57 -07:00
70d1543a65 fix: make sync section dynamic based on configured integrations
Replaced stale module-level _NOTION_CONNECTED flag (evaluated once at
import time) with live _notion_configured() calls so the dashboard
reflects the actual integration state on each render.

- Sync section subheader/button: "Send to Notion" only when Notion is
  configured; otherwise shows "Set up a sync integration" prompt
- Caption and metric label drop "to Notion" when no integration is set
- Closes #16
2026-04-06 10:08:21 -07:00
6115a68550 feat: Vue SPA demo mode support
Some checks failed
CI / Backend (Python) (push) Failing after 10s
CI / Frontend (Vue) (push) Successful in 18s
Mirror / mirror (push) Failing after 8s
- useToast.ts: global reactive toast singleton for cross-component toasts
- App.vue: sticky demo mode banner + global toast slot
- router: bypass wizard gate entirely in demo mode (pre-seeded data)
- ApplyWorkspace, CompanyResearchModal: guard generate() in demo mode
- fineTune store: guard submitJob() in demo mode
- ui_switcher.py: remove Vue→Streamlit fallback in demo mode (now handled natively)

All LLM-triggering actions show a toast and no-op in demo mode.
Backend already blocks inference via DEMO_MODE env; Vue layer adds UX signal.

Closes #46
2026-04-06 00:07:26 -07:00
9f9453a3b0 ci: wire Forgejo Actions workflows and add .cliff.toml
Some checks failed
CI / Backend (Python) (push) Failing after 29s
CI / Frontend (Vue) (push) Successful in 1m2s
Mirror / mirror (push) Failing after 9s
Adds the three standard CircuitForge workflows from cf-agents:
- ci.yml: split backend (Python/ruff/pytest) + frontend (Node/vue-tsc/vitest) jobs
- mirror.yml: push-to-GitHub + Codeberg on main/tags
- release.yml: changelog generation + Forgejo release on v* tags
  (Docker push disabled pending BSL registry policy — cf-agents#3)

Also adds .cliff.toml with conventional-commits changelog config.

Full-stack ci.yml variant tracked in cf-agents#4; update when available.
Required secrets: GITHUB_MIRROR_TOKEN, CODEBERG_MIRROR_TOKEN, FORGEJO_RELEASE_TOKEN

Closes #69
2026-04-05 23:57:43 -07:00
c0649c7328 chore: migrate @app.on_event to FastAPI lifespan handler
Some checks failed
CI / test (push) Failing after 21s
Replaces the deprecated @app.on_event('startup') decorator with
the asynccontextmanager lifespan pattern. Startup logic (.env load
+ db migration) moves into lifespan(); no shutdown logic needed.

Closes #70
2026-04-05 23:50:50 -07:00
3458122537 fix: rename setup.sh → install.sh; four installer gaps from #71
Some checks failed
CI / test (push) Failing after 21s
- Rename setup.sh to install.sh (standardise with cf-orch installer pattern)
- Remove hardcoded dev-machine path from activate_git_hooks(); go straight
  to .githooks/ core.hooksPath fallback — the absolute path was only valid
  on the author's machine and surprised self-hosters
- Add capture_license_key(): optional CFG-XXXX-… prompt after setup_env(),
  writes CF_LICENSE_KEY + HEIMDALL_URL into .env when a valid key is entered
- Fix next-steps port: read STREAMLIT_PORT from .env instead of hardcoding
  8501 (Docker default is 8502; users who customise the port saw a wrong URL)
- Update .env.example: STREAMLIT_PORT=8502 (Docker default, not bare-metal 8501)
- Fix stale git clone URL in docs/getting-started/installation.md:
  git.circuitforge.io → git.opensourcesolarpunk.com/Circuit-Forge/peregrine
- Update all setup.sh references across manage.sh, Makefile, CONTRIBUTING.md,
  README.md, and docs/ to install.sh

Closes #71
2026-04-05 23:33:51 -07:00
d2aa169dfb refactor: use shorter circuitforge_core.api import for feedback router 2026-04-05 21:21:57 -07:00
c1d6e53ff3 chore: sync dev-api.py with dev_api.py feedback router wiring 2026-04-05 20:41:02 -07:00
2f790b1a69 feat: wire feedback router from circuitforge-core 2026-04-05 18:45:18 -07:00
dc508d7197 fix: update tests to match refactored scheduler and free-tier Vue SPA
Some checks failed
CI / test (push) Failing after 28s
- task_scheduler: extend LocalScheduler (concrete class), not TaskScheduler
  (Protocol); remove unsupported VRAM kwargs from super().__init__()
- dev-api: lazy import db_migrate inside _startup() to avoid worktree
  scripts cache issue in test_dev_api_settings.py
- test_task_scheduler: update VRAM-attribute tests to match LocalScheduler
  (no _available_vram/_reserved_vram); drop deepest-queue VRAM-gating
  ordering assertion (LocalScheduler is FIFO, not priority-gated);
  suppress PytestUnhandledThreadExceptionWarning on crash test; fix
  budget assertion to not depend on shared pytest tmp dir state
- test_dev_api_settings: patch path functions (_resume_path, _search_prefs_path,
  _license_path, _tokens_path, _config_dir) instead of removed module-level
  constants; mock _TRAINING_JSONL for finetune status idle test
- test_wizard_tiers: Vue SPA is free tier (issue #20), assert True
- test_wizard_api: patch _search_prefs_path() function, not SEARCH_PREFS_PATH
- test_ui_switcher: free-tier vue preference no longer downgrades to streamlit
2026-04-05 07:35:45 -07:00
fb9f751321 chore: bump circuitforge-core dep comment to >=0.8.0 (orch split)
Some checks failed
CI / test (push) Failing after 22s
2026-04-04 22:49:03 -07:00
ac3e97d6c8 feat(#62): Fine-Tune tab — training pair management + real submit
Some checks failed
CI / test (push) Failing after 21s
API (dev-api.py):
- GET /api/settings/fine-tune/pairs — list pairs from JSONL with index/instruction/source_file
- DELETE /api/settings/fine-tune/pairs/{index} — remove a pair and rewrite JSONL
- POST /api/settings/fine-tune/submit — now queues prepare_training task (replaces UUID stub)
- GET /api/settings/fine-tune/status — returns pairs_count from JSONL (not just DB task)

Store (fineTune.ts):
- TrainingPair interface
- pairs, pairsLoading refs
- loadPairs(), deletePair() actions

Vue (FineTuneView.vue):
- Step 2 shows scrollable pairs list with instruction + source file
- ✕ button on each pair calls deletePair(); list/count update immediately
- loadPairs() called on mount
2026-04-04 22:30:16 -07:00
42c9c882ee feat(#59): LLM-assisted generation for all settings form fields
Some checks failed
CI / test (push) Failing after 21s
API endpoints (dev-api.py):
- POST /api/settings/profile/generate-summary → {summary}
- POST /api/settings/profile/generate-missions → {mission_preferences}
- POST /api/settings/profile/generate-voice → {voice}
- POST /api/settings/search/suggest → replaces stub; handles titles/locations/exclude_keywords

Vue (MyProfileView.vue):
- Generate ✦ button on candidate_voice textarea (was missing)

Vue (SearchPrefsView.vue + search store):
- Suggest button for Exclude Keywords section (matches titles/locations pattern)
- suggestExcludeKeywords() in search store
- acceptSuggestion() extended to 'exclude' type
2026-04-04 22:27:20 -07:00
4f825d0f00 feat(#45): manual theme switcher (light/dark/solarized/colorblind-safe)
Some checks failed
CI / test (push) Failing after 18s
- theme.css: explicit [data-theme] blocks for light, dark, solarized-dark,
  solarized-light, colorblind (Wong 2011 palette); auto-dark media query
  updated to :root:not([data-theme]) so explicit themes always win
- useTheme.ts: singleton composable — setTheme(), restoreTheme(), initTheme();
  persists to localStorage + API; coordinates with hacker mode exit
- AppNav.vue: theme <select> in sidebar footer; exitHackerMode now calls
  restoreTheme() instead of deleting data-theme directly
- useEasterEgg.ts: hacker mode toggle-off calls restoreTheme()
- App.vue: calls initTheme() on mount before restore()
- dev-api.py: POST /api/settings/theme endpoint persists to user.yaml
2026-04-04 22:22:04 -07:00
64554dbef1 feat(#43): numbered SQL migration runner (Rails-style)
Some checks failed
CI / test (push) Failing after 19s
- migrations/001_baseline.sql: full schema baseline (all tables/cols)
- scripts/db_migrate.py: apply sorted *.sql files, track in schema_migrations
- Wired into FastAPI startup and Streamlit app.py startup
- Replaces ad-hoc digest_queue CREATE in _startup()
- 6 tests covering apply, idempotency, partial apply, failure rollback
- docs/developer-guide/contributing.md: migration authoring guide
2026-04-04 22:17:42 -07:00
065c02feb7 feat(vue): Home dashboard parity — Enrich button, Danger Zone, setup banners (closes #57)
Some checks failed
CI / test (push) Failing after 20s
API additions (dev-api.py):
- GET /api/tasks — list active background tasks
- DELETE /api/tasks/{task_id} — per-task cancel
- POST /api/tasks/kill — kill all stuck tasks
- POST /api/tasks/discovery|email-sync|enrich|score|sync — queue/trigger each workflow
- POST /api/jobs/archive — archive by statuses array
- POST /api/jobs/purge — hard delete by statuses or target (email/non_remote/rescrape)
- POST /api/jobs/add — queue URL imports
- POST /api/jobs/upload-csv — upload CSV with URL column
- GET  /api/config/setup-banners — list undismissed onboarding hints
- POST /api/config/setup-banners/{key}/dismiss — dismiss a banner

HomeView.vue:
- 4th WorkflowButton: "Fill Missing Descriptions" (always visible, not gated on enrichment_enabled)
- Danger Zone redesign: scope radio (pending-only vs pending+approved), Archive & reset (primary)
  vs Hard purge (secondary), inline confirm dialogs, active task list with per-task cancel,
  Kill all stuck button, More Options (email purge / non-remote / wipe+rescrape)
- Setup banners: dismissible onboarding hints pulled from /api/config/setup-banners,
  5-second polling for active task list to stay live

app/Home.py:
- Danger Zone redesign: same scope radio + archive/purge with confirm steps
- Background task list with per-task cancel and Kill all stuck button
- More options expander (email purge, non-remote, wipe+rescrape)
- Setup banners section at page bottom
2026-04-04 22:05:06 -07:00
53b07568d9 feat(vue): accumulated parity work — Q&A, Apply highlights, AppNav switcher, cloud API
API additions (dev-api.py split across this and next commit):
- /api/jobs/{job_id}/qa GET/PATCH/suggest — Interview Prep answer storage + LLM suggestions
- /api/settings/ui-preference POST — persist streamlit/vue preference to user.yaml
- cancel_task() added to scripts/db.py (per-task cancel for Danger Zone)

Vue / UI:
- AppNav: " Classic" button to switch back to Streamlit UI (writes cookie + persists to user.yaml)
- ApplyWorkspace: Resume Highlights panel (collapsible skills/domains/keywords with job-match highlighting)
- SettingsView: hide Data tab in cloud mode (showData guard)
- ResumeProfileView: minor improvements
- useApi.ts: error handling improvements

Infra:
- compose.cloud.yml: add api service (uvicorn dev_api running in cloud container)
- docker/web/nginx.conf: proxy /api/* to api service in cloud mode
- README.md: Vue SPA now listed as Free tier feature
2026-04-04 22:04:51 -07:00
173da49087 feat: wire circuitforge-core config.load_env at entry points (closes #68 partial)
Some checks failed
CI / test (push) Failing after 19s
- app/app.py: load_env at module level (safe in Docker, fills gaps on bare-metal)
- dev_api.py: load_env in startup handler (avoids test-env pollution)
- requirements.txt: note >= 0.7.0 requirement; TODO tag once cf-core cuts release

db.migration runner deferral: tracked in #43 (Rails-style numbered migrations)
CFOrchClient VRAM wiring: already present in task_scheduler via CF_ORCH_URL env var
2026-04-04 19:37:58 -07:00
1ab1dffc47 feat: cf-core env-var LLM config + coordinator auth (closes #67)
Some checks failed
CI / test (push) Failing after 38s
- LLMRouter shim: tri-level config priority (local yaml > user yaml > env-var)
- .env.example: document OLLAMA_HOST, OLLAMA_MODEL, OPENAI_MODEL, ANTHROPIC_MODEL,
  CF_LICENSE_KEY, CF_ORCH_URL
- Wizard Step 5: env-var setup hint + optional Ollama fields for remote profile
- Preflight: write OLLAMA_HOST to .env when Ollama is adopted from host process
2026-04-04 19:27:24 -07:00