- app.py: wizard gate now reads get_config_dir()/user.yaml instead of
hardcoded repo-level config/ — fixes perpetual onboarding loop in
cloud mode where per-user wizard_complete was never seen
- app.py: page title corrected to "Peregrine"
- cloud_session.py: add get_config_dir() returning per-user config path
in cloud mode, repo config/ locally
- cloud_session.py: replace st.error() with JS redirect on missing/invalid
session token so users land on login page instead of error screen
- Home.py, 4_Apply.py, migrate.py: remove remaining AIHawk UI references
T8: compose.cloud.yml — multi-tenant cloud stack on port 8505, CLOUD_MODE=true,
per-user encrypted data at /devl/menagerie-data, joins caddy-proxy_caddy-internal
network; .env.example extended with five cloud-only env vars.
T10: app/telemetry.py — log_usage_event() is the ONLY entry point to usage_events
table; hard kill switch (all_disabled) checked before any DB write; complete no-op
in local mode; swallows all exceptions so telemetry never crashes the app;
psycopg2-binary added to requirements.txt. Event calls wired into 4_Apply.py at
cover_letter_generated and job_applied. 5 tests, 413/413 total passing.
resolve_session() is a no-op in local mode — no behavior change for existing users.
In cloud mode, injects user-scoped db_path into st.session_state at page load.
- generate() accepts previous_result + feedback; appends both to LLM prompt
- task_runner cover_letter handler parses params JSON, passes fields through
- Apply Workspace: "Refine with Feedback" expander with text area + Regenerate
button; only shown when a draft exists; clears feedback after submitting
- 8 new tests (TestGenerateRefinement + TestTaskRunnerCoverLetterParams)