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.
590 lines
31 KiB
Markdown
590 lines
31 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to Peregrine are documented here.
|
||
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||
|
||
---
|
||
|
||
## [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 new `GET /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), `build` command, and cloud/demo stack shortcuts.
|
||
- **theme.css** — `--color-overlay` token for modal/dialog backdrops.
|
||
|
||
### Fixed
|
||
|
||
- **Messages view layout** — changed `height: 100%` to `height: 100dvh` with a mobile
|
||
override for the 56px tab bar. `height: 100%` was resolving to "shrink-wrap" because
|
||
`.app-main` has no explicit height; compose bar is now correctly pinned to the bottom.
|
||
- **Accessibility: danger button contrast** — `btn--danger` used `color: white` on
|
||
`--app-accent` (Talon Orange), yielding 2.8:1 contrast (fails WCAG AA 4.5:1 for
|
||
normal text). Fixed to `color: var(--app-accent-text)` (dark navy, 5.5:1).
|
||
- **Accessibility: warning badge contrast** — `tab-badge` in Job Review used `color: white`
|
||
on `--color-warning` (amber). Same fix applied.
|
||
- **Theme: Interviews signal banners** — hardcoded `rgba(245,158,11,…)` / `rgba(39,174,…)`
|
||
/ `rgba(192,57,…)` replaced with `color-mix()` against `--color-warning/success/error`.
|
||
- **Theme: Interviews signal count** — `color: #e67e22` hardcode replaced with
|
||
`var(--app-accent)`.
|
||
- **Theme: References academic tag chip** — `color: #7c3aed` hardcode replaced with
|
||
`var(--status-synced)`; background uses `color-mix()` with the same token.
|
||
- **Theme: Interviews signal-move button** — `color: #fff` on `--color-primary` fails
|
||
in dark mode (light green bg); fixed to `var(--color-text-inverse)`.
|
||
- **Modal backdrops** — `rgba(0,0,0,0.5)` replaced with `var(--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 via `preview_text_override`.
|
||
|
||
### Fixed
|
||
|
||
- **Double bullets in resume optimizer** — `_section_text_for_prompt` now strips existing
|
||
bullet characters before prefixing with `•`, and `_reparse_experience_bullets` uses 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.css` media 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-light` bleeding 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_in`
|
||
in `user.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-live` region 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=true` write-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-profile` pushes
|
||
a library resume into the active profile; `PUT /api/settings/resume` syncs edits back
|
||
to the default library entry. `ResumeSyncConfirmModal` shows a before/after diff.
|
||
`ResumeProfileView` extended with career summary, education, and achievements sections.
|
||
`migrations/007_resume_sync.sql` adds `synced_at` to `resumes`.
|
||
- **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.yaml` rather than hardcoded values.
|
||
- **GitHub Actions CI** — workflow for public credibility badge (`ci.yml`).
|
||
- **`CF_APP_NAME` cloud annotation** — coordinator pipeline attribution for multi-product
|
||
cloud deployments.
|
||
|
||
### Changed
|
||
|
||
- `/contacts` route now redirects to `/messages`; nav item renamed "Messages" → "Contacts"
|
||
label removed. `ContactsView.vue` preserved for reference, router points to `MessagingView`.
|
||
- Survey `/analyze` endpoint 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.yml` updated for Vue/FastAPI architecture with tmpfs demo volume.
|
||
|
||
### Fixed
|
||
|
||
- Tier bypass and draft body persistence after page navigation.
|
||
- `canDraftLlm` cleanup and message list `limit` cap.
|
||
- DemoBanner button contrast — semantic surface token instead of hardcoded white.
|
||
- Period split in `profile_to_library` now 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_parser` extractors.
|
||
- Survey and interview tests updated for `hired_feedback` column 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 `/resumes` page 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_resume` extended with
|
||
`save_to_library` + `resume_name` params to save optimized resumes directly.
|
||
- **`resumes` DB migration** — `migrations/005_resumes_table.sql` adds `resumes` table
|
||
(10 columns) and `resume_id` FK on `jobs`.
|
||
- **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.yaml` on 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.
|
||
- **`wizardGuard` router gate** — all Vue routes require wizard completion; completed
|
||
users are bounced away from `/setup` to `/`.
|
||
- **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) when `user.yaml`
|
||
didn'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_resume` was passing a
|
||
file path string to `structure_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.vue` read
|
||
`data.experience` but the upload response wraps parsed data under `data.data`.
|
||
|
||
---
|
||
|
||
## [0.8.4] — 2026-04-02
|
||
|
||
### Fixed
|
||
|
||
- **Cloud: cover letter used wrong user's profile** — `generate_cover_letter.generate()`
|
||
loaded `_profile` from the global `config/user.yaml` at module import time, so all
|
||
cloud users got the default user's name, voice, and mission preferences in their
|
||
generated letters. `generate()` now accepts a `user_yaml_path` parameter; `task_runner`
|
||
derives 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_end` all accept a `profile` override so the
|
||
per-call profile is used end-to-end without breaking CLI mode.
|
||
- **Apply Workspace: hardcoded config paths in cloud mode** — `4_Apply.py` was loading
|
||
`_USER_YAML` and `RESUME_YAML` from the repo-root `config/` before `resolve_session()`
|
||
ran, so cloud users saw the global (Meg's) resume in the Apply tab. Both paths now
|
||
derive from `get_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 `vllm` from 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 install` was failing to fetch
|
||
`circuitforge-core` from the private Forgejo VCS URL. Added `FORGEJO_TOKEN`
|
||
repository secret and a `git config insteadOf` step to inject credentials
|
||
before `pip install`.
|
||
- **CI: settings API tests** — 6 `test_dev_api_settings` PUT/POST tests were
|
||
returning HTTP 500 in CI because `_user_yaml_path()` read the module-level
|
||
`DB_PATH` constant (frozen at import time), so `monkeypatch.setenv("STAGING_DB")`
|
||
had no effect. Fixed by reading `os.environ` at call time.
|
||
|
||
---
|
||
|
||
## [0.8.2] — 2026-04-01
|
||
|
||
### Fixed
|
||
- **CI pipeline** — `pip install -r requirements.txt` was failing in GitHub Actions
|
||
because `-e ../circuitforge-core` requires a sibling directory that doesn't exist
|
||
in a single-repo checkout. Replaced with a `git+https://` VCS URL fallback;
|
||
`Dockerfile.cfcore` still installs from the local `COPY` to avoid redundant
|
||
network fetches during Docker builds.
|
||
- **Vue-nav reload loop** — `sync_ui_cookie()` was calling
|
||
`window.parent.location.reload()` on every render when `user.yaml` has
|
||
`ui_preference: vue` but no Caddy proxy is in the traffic path (test instances,
|
||
bare Docker). Gated the reload on `PEREGRINE_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_URL` env var** — scheduler reads coordinator address from
|
||
`CF_ORCH_URL` (default `http://localhost:7700`); set to
|
||
`http://host.docker.internal:7700` in Docker compose files so containers can
|
||
reach the host coordinator.
|
||
- **All compose files on `Dockerfile.cfcore`** — `compose.yml`, `compose.cloud.yml`,
|
||
and `compose.test-cfcore.yml` all use the parent-context build. `build: .` is
|
||
removed from `compose.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 `Exception` instead of only
|
||
`RuntimeError` so connection errors and `FileNotFoundError` (missing llm.yaml)
|
||
surface as error messages rather than crashing the page silently.
|
||
|
||
### Added
|
||
- **`Dockerfile.cfcore`** — parent-context Dockerfile that copies
|
||
`circuitforge-core/` alongside `peregrine/` before `pip install`, resolving
|
||
the `-e ../circuitforge-core` editable 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_report` columns;
|
||
`save_optimized_resume` / `get_optimized_resume` helpers
|
||
- `GET /api/jobs/{id}/resume_optimizer` — fetch gap report + rewrite
|
||
- `POST /api/jobs/{id}/resume_optimizer/generate` — queue rewrite task
|
||
- `GET /api/jobs/{id}/resume_optimizer/task` — poll task status
|
||
- `web/src/components/ResumeOptimizerPanel.vue` — gap report (all tiers),
|
||
LLM rewrite section (paid+), hallucination warning badge, `.txt` download
|
||
- `ResumeOptimizerPanel` integrated into `ApplyWorkspace`
|
||
|
||
- **Vue SPA full merge** (closes #8) — `feature/vue-spa` merged to `main`
|
||
- `dev-api.py` — full FastAPI backend (settings, jobs, interviews, prep,
|
||
survey, digest, resume optimizer); cloud session middleware (JWT → per-user
|
||
SQLite); BYOK credential store
|
||
- `dev_api.py` — symlink → `dev-api.py` for importable module alias
|
||
- `scripts/job_ranker.py` — two-stage ranking for `/api/jobs/stack`
|
||
- `scripts/credential_store.py` — per-user BYOK API key management
|
||
- `scripts/user_profile.py` — `load_user_profile` / `save_user_profile`
|
||
- `web/src/components/TaskIndicator.vue` + `web/src/stores/tasks.ts` —
|
||
live background task queue display
|
||
- `web/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.py` were hardcoding `DEFAULT_DB`; now
|
||
use `get_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 breaking `monkeypatch.setattr`
|
||
in subsequent test files; replaced with targeted `monkeypatch.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 `main` and 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) from `feature/vue-spa`
|
||
- `web/src/components/ClassicUIButton.vue` — one-click switch back to the
|
||
Classic (Streamlit) UI; sets `prgn_ui=streamlit` cookie and appends
|
||
`?prgn_switch=streamlit` so `user.yaml` stays in sync
|
||
- `web/src/composables/useFeatureFlag.ts` — reads `prgn_demo_tier` cookie 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_preference` field (`streamlit` | `vue`,
|
||
default: `streamlit`) with round-trip `save()`
|
||
- `app/wizard/tiers.py` — `vue_ui_beta: "paid"` feature key; `demo_tier`
|
||
keyword arg on `can_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_MODE`
|
||
instances. Free / Paid / Premium pills let demo visitors explore all feature
|
||
tiers without an account. Persists via `prgn_demo_tier` cookie. Default: Paid
|
||
(most compelling first impression). `app/components/demo_toolbar.py`
|
||
|
||
- **Docker `web` service** — multi-stage nginx container serving the Vue SPA
|
||
`dist/` build. Added to `compose.yml` (port 8506), `compose.demo.yml`
|
||
(port 8507), `compose.cloud.yml` (port 8508). `manage.sh build` now includes
|
||
the `web` service alongside `app`.
|
||
|
||
### Changed
|
||
- **Caddy routing** — `menagerie.circuitforge.tech` and
|
||
`demo.circuitforge.tech` peregrine blocks now inspect the `prgn_ui` cookie
|
||
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 detection
|
||
- `tests/e2e/test_interactions.py` — full click-through with XFAIL/XPASS bucketing
|
||
- `tests/e2e/conftest.py` — Streamlit-aware wait helpers, error scanner, fixtures
|
||
- `tests/e2e/models.py` — `ErrorRecord`, `ModeConfig`, `diff_errors`
|
||
- `tests/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.py` now
|
||
checks `DEMO_MODE` before importing `discover.py`; returns a friendly error
|
||
immediately instead of crashing on missing `search_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 an
|
||
`st.info` placeholder instead, eliminating the hidden error element (#22)
|
||
- **Cloud: auth wall shown to E2E test browser** — `cloud_session.py` now falls
|
||
back to the `Cookie` header when `X-CF-Session` is absent (direct access
|
||
without Caddy). Playwright's `set_extra_http_headers()` does not propagate to
|
||
WebSocket handshakes; cookies do. Test harness uses `ctx.add_cookies()`.
|
||
- **E2E error scanner returned empty text for collapsed expanders** — switched
|
||
from `inner_text()` (respects CSS `display:none`) to `text_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** — `✨ Suggest` in
|
||
Settings → Search now calls `st.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_router` flaky 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 orchestration
|
||
- `scripts/integrations/apple_calendar.py` — `create_event()` / `update_event()`
|
||
via `caldav` + `icalendar`
|
||
- `scripts/integrations/google_calendar.py` — `create_event()` / `update_event()`
|
||
via `google-api-python-client` (service account); `test()` now makes a real API call
|
||
- `scripts/db.py` — `calendar_event_id TEXT` column (auto-migration) +
|
||
`set_calendar_event_id()` helper
|
||
- `environment.yml` — pin `caldav>=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.json` so 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, summary
|
||
- `scripts/linkedin_scraper.py` — Playwright URL scraper + export zip CSV parser;
|
||
atomic staging file write; URL validation; robust error handling
|
||
- `scripts/linkedin_parser.py` — staging file reader; re-runs HTML parser on stored
|
||
raw HTML so selector improvements apply without re-scraping
|
||
- `app/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.py` now reads
|
||
`get_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_DIR`
|
||
instead of the shared repo `config/` (would have merged all cloud users' data)
|
||
- **Cloud session redirect** — missing/invalid session token now JS-redirects to
|
||
`circuitforge.tech/login` instead 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_url` heuristic, and opt-out `local: true` flag
|
||
- **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.yaml`
|
||
under `byok_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_DB` now respects `STAGING_DB` environment variable (was silently ignoring it)
|
||
- `generate()` in cover letter refinement now correctly passes `max_tokens` kwarg
|
||
|
||
### 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()` accepts `previous_result`/`feedback`; task params passed through `submit_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_generate` background 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
|
||
- `params` column in `background_tasks` for wizard task payloads
|
||
- `wizard_complete`, `wizard_step`, `tier`, `dev_tier_override`, `dismissed_banners`,
|
||
`effective_tier` added to UserProfile
|
||
- MkDocs documentation site (Material theme, 20 pages)
|
||
- `LICENSE-MIT` and `LICENSE-BSL`, `CONTRIBUTING.md`, `CHANGELOG.md`
|
||
|
||
### Changed
|
||
- `app.py` wizard gate now checks `wizard_complete` flag 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.sh` cross-platform dependency installer
|
||
- `scripts/preflight.py` and `scripts/migrate.py`
|