Messaging overhaul: expandable email timeline with lazy body loading, sticky compose bar replacing always-visible action buttons, layout height fixed to 100dvh. Accessibility fixes for contrast failures on orange/amber backgrounds. Theme-aware replacements for hardcoded colors in Interviews, References, and JobReview. Indeed alert parser, Oracle HCM scraper, manage.sh compose engine detection.
31 KiB
Changelog
All notable changes to Peregrine are documented here. Format follows Keep a Changelog.
[Unreleased]
[0.9.4] — 2026-05-08
Added
- Messages view — expandable email timeline — click any email item to lazy-load
and read the full body inline (HTML stripped to plain text via
DOMParser). Bodies are fetched on-demand via the newGET /api/contacts/{id}endpoint to avoid loading 50KB+ email bodies on every page view. - Messages view — compose bar — action buttons (Log call, Log note, Use template, Draft reply with LLM, Call via Osprey) moved from the always-visible header into a sticky bottom compose bar triggered by a + New toggle. Reduces visual clutter when just reading the thread.
- Home view — "Skip review" checkbox — when adding jobs by URL, a checkbox (default on) sends them directly to the Apply queue, bypassing Job Review.
- ContactsView — sync status — shows last completed sync time and a spinner when an email sync is running.
- imap_sync: Indeed alert parser —
parse_indeed_alert()extracts job title, company, location, salary, and canonical URL from Indeed Job Alert digest emails. - scrape_url: Oracle HCM support — Playwright-based scraper for Oracle HCM CandidateExperience portals (React SPAs requiring JS execution).
- manage.sh — compose engine auto-detection (docker compose / podman compose /
podman-compose),
buildcommand, and cloud/demo stack shortcuts. - theme.css —
--color-overlaytoken for modal/dialog backdrops.
Fixed
- Messages view layout — changed
height: 100%toheight: 100dvhwith a mobile override for the 56px tab bar.height: 100%was resolving to "shrink-wrap" because.app-mainhas no explicit height; compose bar is now correctly pinned to the bottom. - Accessibility: danger button contrast —
btn--dangerusedcolor: whiteon--app-accent(Talon Orange), yielding 2.8:1 contrast (fails WCAG AA 4.5:1 for normal text). Fixed tocolor: var(--app-accent-text)(dark navy, 5.5:1). - Accessibility: warning badge contrast —
tab-badgein Job Review usedcolor: whiteon--color-warning(amber). Same fix applied. - Theme: Interviews signal banners — hardcoded
rgba(245,158,11,…)/rgba(39,174,…)/rgba(192,57,…)replaced withcolor-mix()against--color-warning/success/error. - Theme: Interviews signal count —
color: #e67e22hardcode replaced withvar(--app-accent). - Theme: References academic tag chip —
color: #7c3aedhardcode replaced withvar(--status-synced); background usescolor-mix()with the same token. - Theme: Interviews signal-move button —
color: #fffon--color-primaryfails in dark mode (light green bg); fixed tovar(--color-text-inverse). - Modal backdrops —
rgba(0,0,0,0.5)replaced withvar(--color-overlay)for theme consistency.
[0.9.3] — 2026-05-05
Added
- Editable resume review — proposed summary and experience bullets in the review modal
are now editable text areas. Edits flow through
apply_review_decisions()and override the LLM output in the final resume struct. Preview textarea in Apply Workspace is also editable, with manual changes preserved through the approve step viapreview_text_override.
Fixed
- Double bullets in resume optimizer —
_section_text_for_promptnow strips existing bullet characters before prefixing with•, and_reparse_experience_bulletsuses a greedy strip regex so• •patterns can no longer survive parsing. - Asterisk markup in summary — added
_clean_summary_markup()to strip LLM-generated markdown bullet chars (*,-, etc.) from career summary output; injected no-markdown rule into the LLM prompt's CRITICAL RULES list. - Light theme dark CSS bleed —
peregrine.cssmedia dark override now scoped to:root:not([data-theme])(auto mode only) instead of:root:not([data-theme="hacker"]). Fixes dark navy--app-primary-light/--app-accent-lightbleeding into light themes (light, solarized-light, colorblind) on dark-OS machines.
[0.9.2] — 2026-05-02
Added
- Cover letter training export (#111) — opt-in consent gate (
training_export_opt_ininuser.yaml, default off) lets users export applied-job cover letters as Alpaca-format JSONL for local fine-tuning. Per-job exclude/restore curation in Settings → Fine-Tune. Streaming JSONL download merges DB pairs with any previously uploaded file pairs. Cloud fine-tune Phase 2 stub (501) reserved for cf-orch integration. - WizardTrainingStep — new onboarding consent step inserted between Resume and Identity; skippable, opt-in default off, cloud-aware privacy copy.
- a11y: confirmed-state toggle (no optimistic DOM divergence), visible Premium tier gate
with upgrade link,
aria-liveregion on pairs list, cloud-aware consent copy.
[0.9.0] — 2026-04-20
Added
- Messaging tab (#74) — per-job communication timeline replacing
/contacts. Unified view of IMAP emails (job_contacts) and manually logged entries (messages). Log calls and in-person notes with timestamp. Message template library with 4 built-in templates (follow-up, thank-you, accommodation request, withdrawal) and user-created templates with{{token}}substitution. LLM draft reply for inbound emails (BYOK-unlockable, BSL 1.1). Draft approval flow with inline editing and one-click clipboard copy. Osprey IVR stub button (Phase 2 placeholder with easter egg).migrations/008_messaging.sql. - Public demo experience (#103) — full read-only demo mode at
demo.circuitforge.tech/peregrine.IS_DEMO=truewrite-blocks all mutating API endpoints with a toast notification. Ephemeral seed data via tmpfs +demo/seed.sql(resets on container start). WelcomeModal on first visit (localStorage-gated). Per-view HintChips guiding new users through the job search flow (localStorage-dismissed). DemoBanner with accessible CTA buttons (WCAG-compliant contrast in light and dark themes).migrations/006_missing_columns.sql. - References tracker and recommendation letter system (#96) — track professional references and generate LLM-drafted recommendation request letters.
- Shadow listing detector — flags duplicate or aggregator-reposted job listings.
- Hired feedback widget — capture post-hire notes and retrospective feedback on jobs.
- Interview prep Q&A — LLM-generated practice questions for the selected job.
- Resume library ↔ profile sync —
POST /api/resumes/{id}/apply-to-profilepushes a library resume into the active profile;PUT /api/settings/resumesyncs edits back to the default library entry.ResumeSyncConfirmModalshows a before/after diff.ResumeProfileViewextended with career summary, education, and achievements sections.migrations/007_resume_sync.sqladdssynced_attoresumes. - Plausible analytics — lightweight privacy-preserving analytics in Vue SPA and docs.
- cf_text / cf_voice LLM backends — wire trunk service backends in
llm.yaml. - Mission alignment domains — load preferred company domains from
config/mission_domains.yamlrather than hardcoded values. - GitHub Actions CI — workflow for public credibility badge (
ci.yml). CF_APP_NAMEcloud annotation — coordinator pipeline attribution for multi-product cloud deployments.
Changed
/contactsroute now redirects to/messages; nav item renamed "Messages" → "Contacts" label removed.ContactsView.vuepreserved for reference, router points toMessagingView.- Survey
/analyzeendpoint is now fully async via the task queue (no blocking LLM call on the request thread). - nginx config adds
/peregrine/base-path routing for subdirectory deployments. compose.demo.ymlupdated for Vue/FastAPI architecture with tmpfs demo volume.
Fixed
- Tier bypass and draft body persistence after page navigation.
canDraftLlmcleanup and message listlimitcap.- DemoBanner button contrast — semantic surface token instead of hardcoded white.
- Period split in
profile_to_librarynow handles ISO date strings containing hyphens. - Cloud startup sweeps all user DBs for pending migrations on deploy.
- Resume import strips CID glyph references via
resume_parserextractors. - Survey and interview tests updated for
hired_feedbackcolumn and async analyze flow.
[0.8.6] — 2026-04-12
Added
- Resume Review Modal — paged tabbed dialog replaces the inline resume review
section in the Apply workspace. Pages through Skills diff, Summary diff, one page
per experience entry, and a Confirm summary. Color-coded tab status: unvisited
(gray), in-progress (indigo), accepted (green), partial (amber), skipped (slate).
Full ARIA tabs pattern with focus trap and
Teleport to body. - Resume Library — new
/resumespage for managing saved resumes. Two-column layout: list sidebar + full-text preview pane. Supports import (.txt, .pdf, .docx, .odt, .yaml), rename (Edit), set as default, download (txt/pdf/yaml), and delete (guarded: disabled when only resume or is default). 5 MB upload limit. - ResumeLibraryCard — compact widget shown above the ATS Resume Optimizer in the Apply workspace. Displays the currently active resume for the job (job-specific or global default), with Switch and Manage deep links.
- Resume library API —
GET/POST /api/resumes,GET/PATCH/DELETE /api/resumes/{id},POST /api/resumes/{id}/set-default,POST /api/resumes/import,GET/PATCH /api/jobs/{job_id}/resume.approve_resumeextended withsave_to_library+resume_nameparams to save optimized resumes directly. resumesDB migration —migrations/005_resumes_table.sqladdsresumestable (10 columns) andresume_idFK onjobs.- Resumes nav link — Document icon entry added after Apply in the main nav.
Changed
- Resume optimizer "Awaiting review" state now triggers the Review Modal instead of rendering an inline diff; save-to-library checkbox and name input surfaced on the preview confirmation step.
[0.8.5] — 2026-04-02
Added
- Vue onboarding wizard — 7-step first-run setup replaces the Streamlit wizard
in the Vue SPA: Hardware detection → Tier → Resume upload/build → Identity →
Inference & API keys → Search preferences → Integrations. Progress saves to
user.yamlon every step; crash-recovery resumes from the last completed step. - Wizard API endpoints —
GET /api/wizard/status,POST /api/wizard/step,GET /api/wizard/hardware,POST /api/wizard/inference/test,POST /api/wizard/complete. Inference test always soft-fails so Ollama being unreachable never blocks setup completion. - Cloud auto-skip — cloud instances automatically complete steps 1 (hardware), 2 (tier), and 5 (inference) and drop the user directly on the Resume step.
wizardGuardrouter gate — all Vue routes require wizard completion; completed users are bounced away from/setupto/.- Chip-input search step — job titles and locations entered as press-Enter/comma chips; validates at least one title before advancing.
- Integrations tile grid — optional step 7 shows Notion, Calendar, Slack, Discord, Drive with paid-tier badges; skippable on Finish.
Fixed
- User config isolation: dangerous fallback removed —
_user_yaml_path()fell back to/devl/job-seeker/config/user.yaml(legacy profile) whenuser.yamldidn't exist at the expected path; new users now get an empty dict instead of another user's data. Affects profile, resume, search, and all wizard endpoints. - Resume path not user-isolated —
RESUME_PATH = Path("config/plain_text_resume.yaml")was a relative CWD path shared across all users. Replaced with_resume_path()derived from_user_yaml_path()/STAGING_DB. - Resume upload silently returned empty data —
upload_resumewas passing a file path string tostructure_resume()which expects raw text; now reads bytes and dispatches to the correct extractor (extract_text_from_pdf/_docx/_odt). - Wizard resume step read wrong envelope field —
WizardResumeStep.vuereaddata.experiencebut the upload response wraps parsed data underdata.data.
[0.8.4] — 2026-04-02
Fixed
- Cloud: cover letter used wrong user's profile —
generate_cover_letter.generate()loaded_profilefrom the globalconfig/user.yamlat module import time, so all cloud users got the default user's name, voice, and mission preferences in their generated letters.generate()now accepts auser_yaml_pathparameter;task_runnerderives it from the per-user config directory (db_path/../config/user.yaml) and passes it through._build_system_context,_build_mission_notes,detect_mission_alignment,build_prompt, and_trim_to_letter_endall accept aprofileoverride so the per-call profile is used end-to-end without breaking CLI mode. - Apply Workspace: hardcoded config paths in cloud mode —
4_Apply.pywas loading_USER_YAMLandRESUME_YAMLfrom the repo-rootconfig/beforeresolve_session()ran, so cloud users saw the global (Meg's) resume in the Apply tab. Both paths now derive fromget_config_dir()after session resolution.
Changed
- Vue SPA open to all tiers — Vue 3 frontend is no longer gated behind the beta flag; all tier users can switch to the Vue UI from Settings.
- LLM model candidates — vllm backend now tries Qwen2.5-3B first, Phi-4-mini as fallback (was reversed). cf_orch allocation block added to vllm config.
- Preflight — removed
vllmfrom Docker adoption list; vllm is now managed entirely by cf-orch and should not be stubbed by preflight.
[0.8.3] — 2026-04-01
Fixed
- CI: Forgejo auth — GitHub Actions
pip installwas failing to fetchcircuitforge-corefrom the private Forgejo VCS URL. AddedFORGEJO_TOKENrepository secret and agit config insteadOfstep to inject credentials beforepip install. - CI: settings API tests — 6
test_dev_api_settingsPUT/POST tests were returning HTTP 500 in CI because_user_yaml_path()read the module-levelDB_PATHconstant (frozen at import time), somonkeypatch.setenv("STAGING_DB")had no effect. Fixed by readingos.environat call time.
[0.8.2] — 2026-04-01
Fixed
- CI pipeline —
pip install -r requirements.txtwas failing in GitHub Actions because-e ../circuitforge-corerequires a sibling directory that doesn't exist in a single-repo checkout. Replaced with agit+https://VCS URL fallback;Dockerfile.cfcorestill installs from the localCOPYto avoid redundant network fetches during Docker builds. - Vue-nav reload loop —
sync_ui_cookie()was callingwindow.parent.location.reload()on every render whenuser.yamlhasui_preference: vuebut no Caddy proxy is in the traffic path (test instances, bare Docker). Gated the reload onPEREGRINE_CADDY_PROXY=1; instances without the env var set the cookie silently and skip the reload.
Changed
- cfcore VRAM lease integration — the task scheduler now acquires a VRAM lease
from the cf-orch coordinator before running a batch of LLM tasks and releases it
when the batch completes. Visible in the coordinator dashboard at
:7700. CF_ORCH_URLenv var — scheduler reads coordinator address fromCF_ORCH_URL(defaulthttp://localhost:7700); set tohttp://host.docker.internal:7700in Docker compose files so containers can reach the host coordinator.- All compose files on
Dockerfile.cfcore—compose.yml,compose.cloud.yml, andcompose.test-cfcore.ymlall use the parent-context build.build: .is removed fromcompose.yml.
[0.8.1] — 2026-04-01
Fixed
- Job title suggester silent failure — when the LLM returned empty arrays or non-JSON text, the spinner would complete with zero UI feedback. Now shows an explicit "No new suggestions found" info message with a resume-upload hint for new users who haven't uploaded a resume yet.
- Suggester exception handling — catch
Exceptioninstead of onlyRuntimeErrorso connection errors andFileNotFoundError(missing llm.yaml) surface as error messages rather than crashing the page silently.
Added
Dockerfile.cfcore— parent-context Dockerfile that copiescircuitforge-core/alongsideperegrine/beforepip install, resolving the-e ../circuitforge-coreeditable requirement inside Docker.compose.test-cfcore.yml— single-user test instance on port 8516 for smoke-testing cfcore shim integration before promoting to the cloud instance.
[0.8.0] — 2026-04-01
Added
-
ATS Resume Optimizer (gap report free; LLM rewrite paid+)
scripts/resume_optimizer.py— full pipeline: TF-IDF gap extraction →prioritize_gaps→rewrite_for_ats→ hallucination guard (anchor-set diffing on employers, institutions, and dates)scripts/db.py—optimized_resume+ats_gap_reportcolumns;save_optimized_resume/get_optimized_resumehelpersGET /api/jobs/{id}/resume_optimizer— fetch gap report + rewritePOST /api/jobs/{id}/resume_optimizer/generate— queue rewrite taskGET /api/jobs/{id}/resume_optimizer/task— poll task statusweb/src/components/ResumeOptimizerPanel.vue— gap report (all tiers), LLM rewrite section (paid+), hallucination warning badge,.txtdownloadResumeOptimizerPanelintegrated intoApplyWorkspace
-
Vue SPA full merge (closes #8) —
feature/vue-spamerged tomaindev-api.py— full FastAPI backend (settings, jobs, interviews, prep, survey, digest, resume optimizer); cloud session middleware (JWT → per-user SQLite); BYOK credential storedev_api.py— symlink →dev-api.pyfor importable module aliasscripts/job_ranker.py— two-stage ranking for/api/jobs/stackscripts/credential_store.py— per-user BYOK API key managementscripts/user_profile.py—load_user_profile/save_user_profileweb/src/components/TaskIndicator.vue+web/src/stores/tasks.ts— live background task queue displayweb/public/— peregrine logo assets (SVG + PNG)
-
API test suite — 5 new test modules (622 tests total)
tests/test_dev_api_settings.py(38 tests)tests/test_dev_api_interviews.py,test_dev_api_prep.py,test_dev_api_survey.py,test_dev_api_digest.py
Fixed
- Cloud DB routing —
app/pages/1_Job_Review.py,5_Interviews.py,6_Interview_Prep.py,7_Survey.pywere hardcodingDEFAULT_DB; now useget_db_path()for correct per-user routing in cloud mode (#24) - Test isolation —
importlib.reload(dev_api)in digest/interviews fixtures reset all module globals, silently breakingmonkeypatch.setattrin subsequent test files; replaced with targetedmonkeypatch.setattr(dev_api, "DB_PATH", tmp_db)(#26)
[0.7.0] — 2026-03-22
Added
-
Vue 3 SPA — beta access for paid tier — The new Vue 3 frontend (built with Vite + UnoCSS) is now merged into
mainand available to paid-tier subscribers as an opt-in beta. The Streamlit UI remains the default and will continue to receive full support.web/— full Vue 3 SPA source (components, stores, router, composables, views) fromfeature/vue-spaweb/src/components/ClassicUIButton.vue— one-click switch back to the Classic (Streamlit) UI; setsprgn_ui=streamlitcookie and appends?prgn_switch=streamlitsouser.yamlstays in syncweb/src/composables/useFeatureFlag.ts— readsprgn_demo_tiercookie for demo toolbar visual consistency (display-only, not an authoritative gate)
-
UI switcher — Reddit-style opt-in to the Vue SPA with durable preference persistence and graceful fallback.
app/components/ui_switcher.py—sync_ui_cookie(),switch_ui(),render_banner(),render_settings_toggle()scripts/user_profile.py—ui_preferencefield (streamlit|vue, default:streamlit) with round-tripsave()app/wizard/tiers.py—vue_ui_beta: "paid"feature key;demo_tierkeyword arg oncan_use()for thread-safe demo mode simulation- Banner (dismissible, paid tier only) + Settings → System → Deployment toggle
- Caddy cookie routing:
prgn_ui=vue→ nginx Vue SPA; absent/streamlit→ Streamlit. 502 fallback clears cookie and redirects with?ui_fallback=1
-
Demo toolbar — slim full-width tier-simulation bar for
DEMO_MODEinstances. Free / Paid / Premium pills let demo visitors explore all feature tiers without an account. Persists viaprgn_demo_tiercookie. Default: Paid (most compelling first impression).app/components/demo_toolbar.py -
Docker
webservice — multi-stage nginx container serving the Vue SPAdist/build. Added tocompose.yml(port 8506),compose.demo.yml(port 8507),compose.cloud.yml(port 8508).manage.sh buildnow includes thewebservice alongsideapp.
Changed
- Caddy routing —
menagerie.circuitforge.techanddemo.circuitforge.techperegrine blocks now inspect theprgn_uicookie and fan-out to the Vue SPA service or Streamlit accordingly.
[0.6.2] — 2026-03-18
Added
- Playwright E2E test harness — smoke + interaction test suite covering all
three Peregrine instances (demo / cloud / local). Navigates every page, checks
for DOM errors on load, clicks every interactable element, diffs errors
before/after each click, and XFAIL-marks expected demo-mode failures so
neutering-guard regressions are surfaced as XPASSes. Screenshots on failure.
tests/e2e/test_smoke.py— page-load error detectiontests/e2e/test_interactions.py— full click-through with XFAIL/XPASS bucketingtests/e2e/conftest.py— Streamlit-aware wait helpers, error scanner, fixturestests/e2e/models.py—ErrorRecord,ModeConfig,diff_errorstests/e2e/modes/— per-mode configs (demo / cloud / local)tests/e2e/pages/— page objects for all 7 pages including Settings tabs
Fixed
- Demo: "Discovery failed" error on Home page load —
task_runner.pynow checksDEMO_MODEbefore importingdiscover.py; returns a friendly error immediately instead of crashing on missingsearch_profiles.yaml(#21) - Demo: silent
st.error()in collapsed Practice Q&A expander — Interview Prep no longer auto-triggers the LLM on page render in demo mode; shows anst.infoplaceholder instead, eliminating the hidden error element (#22) - Cloud: auth wall shown to E2E test browser —
cloud_session.pynow falls back to theCookieheader whenX-CF-Sessionis absent (direct access without Caddy). Playwright'sset_extra_http_headers()does not propagate to WebSocket handshakes; cookies do. Test harness usesctx.add_cookies(). - E2E error scanner returned empty text for collapsed expanders — switched
from
inner_text()(respects CSSdisplay:none) totext_content()so errors inside collapsed Streamlit expanders are captured with their full text.
[0.6.1] — 2026-03-16
Fixed
- Keyword suggestions not visible on first render —
✨ Suggestin Settings → Search now callsst.rerun()after storing results; chips appear immediately without requiring a tab switch (#18) - Wizard identity step required manual re-entry of resume data — step 4 (Identity) now prefills name, email, and phone from the parsed resume when those fields are blank; existing saved values are not overwritten (#17)
- "Send to Notion" hardcoded on Home dashboard — sync section now shows the connected provider name, or a "Set up a sync integration" prompt with a Settings link when no integration is configured (#16)
test_generate_calls_llm_routerflaky in full suite — resolved by queue optimizer merge; mock state pollution eliminated (#12)
[0.6.0] — 2026-03-16
Added
- Calendar integration — push interview events to Apple Calendar (CalDAV) or
Google Calendar directly from the Interviews kanban. Idempotent: a second push
updates the existing event rather than creating a duplicate. Button shows
"📅 Add to Calendar" on first push and "🔄 Update Calendar" thereafter.
Event title:
{Stage}: {Job Title} @ {Company}; 1hr duration at noon UTC; job URL and company research brief included in event description.scripts/calendar_push.py— push/update orchestrationscripts/integrations/apple_calendar.py—create_event()/update_event()viacaldav+icalendarscripts/integrations/google_calendar.py—create_event()/update_event()viagoogle-api-python-client(service account);test()now makes a real API callscripts/db.py—calendar_event_id TEXTcolumn (auto-migration) +set_calendar_event_id()helperenvironment.yml— pincaldav>=1.3,icalendar>=5.0,google-api-python-client>=2.0,google-auth>=2.0
[0.4.1] — 2026-03-13
Added
- LinkedIn profile import — one-click import from a public LinkedIn profile URL
(Playwright headless Chrome, no login required) or from a LinkedIn data export zip.
Staged to
linkedin_stage.jsonso the profile is parsed once and reused across sessions without repeated network requests. Available on all tiers including Free.scripts/linkedin_utils.py— HTML parser with ordered CSS selector fallbacks; extracts name, experience, education, skills, certifications, summaryscripts/linkedin_scraper.py— Playwright URL scraper + export zip CSV parser; atomic staging file write; URL validation; robust error handlingscripts/linkedin_parser.py— staging file reader; re-runs HTML parser on stored raw HTML so selector improvements apply without re-scrapingapp/components/linkedin_import.py— shared Streamlit widget (status bar, preview, URL import, advanced zip upload) used by both wizard and Settings- Wizard step 3: new "🔗 LinkedIn" tab alongside Upload and Build Manually
- Settings → Resume Profile: collapsible "Import from LinkedIn" expander
- Dockerfile: Playwright Chromium install added to Docker image
Fixed
- Cloud mode perpetual onboarding loop — wizard gate in
app.pynow readsget_config_dir()/user.yaml(per-user in cloud, repo-level locally) instead of a hardcoded repo path; completing the wizard now correctly exits it in cloud mode - Cloud resume YAML path — wizard step 3 writes resume to per-user
CONFIG_DIRinstead of the shared repoconfig/(would have merged all cloud users' data) - Cloud session redirect — missing/invalid session token now JS-redirects to
circuitforge.tech/logininstead of showing a raw error message - Removed remaining AIHawk UI references (
Home.py,4_Apply.py,migrate.py)
[0.3.0] — 2026-03-06
Added
- Feedback button — in-app issue reporting with screenshot paste support; posts
directly to Forgejo as structured issues; available from sidebar on all pages
(
app/feedback.py,scripts/feedback_api.py,app/components/paste_image.py) - BYOK cloud backend detection —
scripts/byok_guard.py: pure Python detection engine with full unit test coverage (18 tests); classifies backends as cloud or local based on type,base_urlheuristic, and opt-outlocal: trueflag - BYOK activation warning — one-time acknowledgment required in Settings when a
new cloud LLM backend is enabled; shows data inventory (what leaves your machine,
what stays local), provider policy links; ack state persisted to
config/user.yamlunderbyok_acknowledged_backends - Sidebar cloud LLM indicator — amber badge on every page when any cloud backend is active; links to Settings; disappears when reverted to local-only config
- LLM suggest: search terms — three-angle analysis from resume (job titles, skills keywords, and exclude terms to filter irrelevant listings)
- LLM suggest: resume keywords — skills gap analysis against job descriptions
- LLM Suggest button in Settings → Search → Skills & Keywords section
- Backup/restore script (
scripts/backup.py) — multi-instance and legacy support PRIVACY.md— short-form privacy notice linked from Settings
Changed
- Settings save button for LLM Backends now gates on cloud acknowledgment before
writing
config/llm.yaml
Fixed
- Settings widget crash on certain rerun paths
- Docker service controls in Settings → System tab
DEFAULT_DBnow respectsSTAGING_DBenvironment variable (was silently ignoring it)generate()in cover letter refinement now correctly passesmax_tokenskwarg
Security / Privacy
- Full test suite anonymized — fictional "Alex Rivera" replaces all real personal data
in test fixtures (
tests/test_cover_letter.py,test_imap_sync.py,test_classifier_adapters.py,test_db.py) - Complete PII scrub from git history: real name, email address, and phone number
removed from all 161 commits across both branches via
git filter-repo
[0.2.0] — 2026-02-26
Added
- Cover letter iterative refinement: "Refine with Feedback" expander in Apply Workspace;
generate()acceptsprevious_result/feedback; task params passed throughsubmit_task - Expanded first-run wizard: 7-step onboarding with GPU detection, tier selection, resume upload/parsing, LLM inference test, search profile builder, integration cards
- Tier system: free / paid / premium feature gates (
app/wizard/tiers.py) - 13 integration drivers: Notion, Google Sheets, Airtable, Google Drive, Dropbox, OneDrive, MEGA, Nextcloud, Google Calendar, Apple Calendar, Slack, Discord, Home Assistant — with auto-discovery registry
- Resume parser: PDF (pdfplumber) and DOCX (python-docx) + LLM structuring
wizard_generatebackground task type with iterative refinement (feedback loop)- Dismissible setup banners on Home page (13 contextual prompts)
- Developer tab in Settings: tier override selectbox and wizard reset button
- Integrations tab in Settings: connect / test / disconnect all 12 non-Notion drivers
- HuggingFace token moved to Developer tab
paramscolumn inbackground_tasksfor wizard task payloadswizard_complete,wizard_step,tier,dev_tier_override,dismissed_banners,effective_tieradded to UserProfile- MkDocs documentation site (Material theme, 20 pages)
LICENSE-MITandLICENSE-BSL,CONTRIBUTING.md,CHANGELOG.md
Changed
app.pywizard gate now checkswizard_completeflag in addition to file existence- Settings tabs reorganised: Integrations tab added, Developer tab conditionally shown
- HF token removed from Services tab (now Developer-only)
Removed
- Dead
app/pages/3_Resume_Editor.py(functionality lives in Settings → Resume Profile)
[0.1.0] — 2026-02-01
Added
- Initial release: JobSpy discovery pipeline, SQLite staging, Streamlit UI
- Job Review, Apply Workspace, Interviews kanban, Interview Prep, Survey Assistant
- LLM router with fallback chain (Ollama, vLLM, Claude Code wrapper, Anthropic)
- Notion sync, email sync with IMAP classifier, company research with SearXNG
- Background task runner with daemon threads
- Vision service (moondream2) for survey screenshot analysis
- Adzuna, The Ladders, and Craigslist custom board scrapers
- Docker Compose profiles: remote, cpu, single-gpu, dual-gpu
setup.shcross-platform dependency installerscripts/preflight.pyandscripts/migrate.py