- Lower vue_ui_beta gate to "free" so all licensed users can access the
new UI without a paid subscription
- Remove "Paid tier" wording from the Try New UI banner
- Fix Vue SPA navigation in cloud/demo deployments: add VITE_BASE_PATH
build arg so Vite sets the correct subpath base, and pass
import.meta.env.BASE_URL to createWebHistory() so router links
emit /peregrine/... paths that Caddy can match
- Fix feedback button missing on cloud instance by passing
FORGEJO_API_TOKEN through compose.cloud.yml
- Remove vLLM container from compose.yml (vLLM dropped from stack;
cf-research service in cfcore covers the use case)
- Fix cloud config path in Apply page (use get_config_dir() so per-user
cloud data roots resolve correctly for user.yaml and resume YAML)
- Refactor generate_cover_letter._build_system_context and
_build_mission_notes to accept explicit profile arg (enables
per-user cover letter generation in cloud multi-tenant mode)
- Add API proxy block to nginx.conf (Vue web container can now call
/api/ directly without Vite dev proxy)
- Update .env.example: remove vLLM vars, add research model + tuning
vars for external vLLM deployments
- Update llm.yaml: switch vllm base_url to host.docker.internal
(vLLM now runs outside Docker stack)
Closes#63 (feedback button)
Related: #8 (Vue SPA), #50–#62 (parity milestone)
vllm is now managed by cf-orch as a host process — no Docker service
defined in compose.yml. Preflight was detecting port 8000 (llm_server)
and generating a vllm stub in compose.override.yml with no image,
causing `docker compose up` to error on startup.
Phi-4-mini's cached modeling_phi3.py imports SlidingWindowCache which
was removed in transformers 5.x. Qwen2.5-3B uses built-in qwen2 arch
and works cleanly. Reorder so Qwen is tried first.
Remove claude_code, github_copilot, and anthropic from all cloud fallback
orders — cloud accounts must not route through personal/dev LLM backends.
vllm_research and ollama_research are the only permitted research backends.
llm.cloud.yaml is now bind-mounted at /app/config/llm.yaml in compose.cloud.yml.
- discover.py: run_discovery() accepts config_dir param; auto-derives it
from db_path parent (per-user in cloud, falls back to /app/config)
- task_runner.py: passes db_path.parent/config as config_dir to run_discovery
- wizard (0_Setup.py): write 'titles' key not 'job_titles' — matches what
discover.py and all custom board scrapers read
- adzuna/theladders/craigslist: fall back to 'job_titles' for existing
profiles written by older wizard versions
- Fixed Sheridan's live config in place (job_titles → titles)
Module-level DB_PATH is frozen at import time, so monkeypatch.setenv()
in tests had no effect on _user_yaml_path(). Reading os.environ directly
fixes 6 test_dev_api_settings PUT/POST failures in CI where
/devl/job-seeker/ doesn't exist.
FORGEJO_TOKEN secret injected via env var (not inline expression) to
avoid CI injection risk; git insteadOf redirect authenticates the
git+https:// circuitforge-core VCS URL at install time.
CI checks out only the peregrine repo — ../circuitforge-core doesn't exist,
causing 'pip install -r requirements.txt' to fail.
requirements.txt now uses git+https://...@main as the fallback for CI and
bare pip installs. Dockerfile.cfcore installs cfcore from the local COPY
first (skipping the requirements.txt line) to avoid a redundant network
fetch during Docker builds.
compose.yml was still using build: . which fails because requirements.txt
has -e ../circuitforge-core outside the build context. Now matches
compose.cloud.yml and compose.test-cfcore.yml.
Switch compose.cloud.yml build context to Dockerfile.cfcore (parent
context includes circuitforge-core/ as sibling). Adds CF_ORCH_URL so
the cloud container can reach the cf-orch coordinator on the host.
Threads coordinator_url from CF_ORCH_URL env var (default localhost:7700)
into the cfcore TaskScheduler so Docker instances can point at
host.docker.internal:7700 instead of the container's own loopback.
Also adds CF_ORCH_URL to compose.test-cfcore.yml and mounts persistent
patched configs (llm.docker.yaml, user.docker.yaml) for the test instance.
sync_ui_cookie() was calling window.parent.location.reload() on every
render when user.yaml has ui_preference=vue, but no Caddy is in the
traffic path (test instances, bare Docker). This caused an infinite
reload loop because the reload just came back to Streamlit.
Gate the reload on PEREGRINE_CADDY_PROXY=1. Without it, the cookie is
still written silently but no reload is attempted. Add the env var to
compose.yml and compose.cloud.yml (both are behind Caddy); omit from
compose.test-cfcore.yml so test instances stay stable.
- Dockerfile: restored to original (build: . context, no cfcore) so
existing compose.yml / compose.cloud.yml builds are unaffected
- Dockerfile.cfcore: parent-context build that copies circuitforge-core/
alongside peregrine/ before pip install; resolves -e ../circuitforge-core
- compose.test-cfcore.yml: single-user test instance on port 8516;
run from parent dir with context: .. so both repos are in scope
Use this to smoke-test cfcore shims before promoting to prod cloud.
- Catch all exceptions (not just RuntimeError) so FileNotFoundError,
connection errors, etc. surface as error messages rather than crashing
the page silently
- Show "No new suggestions found" info message when the LLM returns
empty arrays — previously the spinner completed with no UI feedback
- Hint to upload resume when RESUME_PATH is missing (new users)
- Only rerun() when there are actual results to display
Pages were hardcoding DEFAULT_DB at import time, meaning cloud-mode
per-user DB routing was silently ignored. Pages affected:
1_Job_Review, 5_Interviews, 6_Interview_Prep, 7_Survey.
Adds resolve_session("peregrine") + get_db_path() pattern to each,
matching the pattern already used in 4_Apply.py.
Fixes#24.
importlib.reload(dev_api) reset all module-level globals (RESUME_PATH,
SEARCH_PREFS_PATH, etc.) on every digest/interviews test, causing
subsequent monkeypatch.setattr calls in test_dev_api_settings.py to
silently fail — the patched attribute was reset between fixture setup
and the actual HTTP request.
Fix: patch dev_api.DB_PATH directly via monkeypatch, which pytest reverts
cleanly after each test without touching any other module state.
Also sync resume optimizer endpoints to dev-api.py (hyphen variant).
ApplyWorkspace.vue: kept HEAD (vue-spa) version for resume optimizer panel,
cl-error__actions wrapper, and ResumeOptimizerPanel import. main's older
version lacked these additions.
VRAM detection now uses cf-orch free VRAM when coordinator is running,
making the scheduler cooperative with other cf-orch consumers.
Enqueue return value now checked — queue-full tasks are marked failed.
- ui_switcher.py: add explicit guard that forces pref=streamlit when
DEMO_MODE=true, before the tier-downgrade check. Demo Vue SPA (#46)
is not yet implemented, so navigating there produced a blank screen.
- app.py: call sync_ui_cookie inside wizard gate block before st.stop()
so that cloud users with ui_preference=vue are redirected correctly
even when the first-run wizard is still active. Previous behaviour
called sync_ui_cookie after pg.run() which was never reached.
- demo/config/user.yaml: reset ui_preference to streamlit (belt-and-
suspenders alongside the code guard).
Closes: demo blank-screen regression reported 2026-03-24.
render_banner() was incorrectly guarded by 'if not IS_DEMO' — the spec
says the banner is open to all demo visitors. render_banner() already
handles its own eligibility check internally (_DEMO_MODE or can_use).
- Initialize simulated_tier session state for demo mode after resolve_session/init_db
- Render demo toolbar before pg.run() when IS_DEMO is set
- Render ui_switcher banner before pg.run() for non-demo paid-tier users (guarded with try/except)
- Sync ui_preference cookie after pg.run() (guarded with try/except)
- All imports are local (inside if-blocks) to avoid Streamlit circular import issues
- Add explanatory comments to all 5 bare except Exception blocks clarifying that UI components must not crash the app
- Refactor sync_ui_cookie() to load UserProfile once instead of up to 3 times in normal path
- Store profile reference and reuse it in tier downgrade protection block
- Replace importlib.reload() pattern in tests with unittest.mock.patch for _DEMO_MODE
- Improves test isolation and eliminates module state contamination across test runs
- All 5 tests pass (100%)