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
- Settings: add st.rerun() after storing _kw_suggestions so chips appear
immediately without requiring a tab switch (#18)
- Setup wizard step 4: prefill name/email/phone from parsed resume when
identity fields are blank; saved values take precedence on re-visit (#17)
- Home dashboard: sync section shows provider name when Notion is connected,
or 'Set up a sync integration' with a settings link when not configured (#16)
- cloud_session.py: add _ensure_provisioned() called in resolve_session() so
new Google OAuth signups get a free Heimdall key created on first page load;
previously resolve returned "free" tier but no key was ever written to
Heimdall, leaving users in an untracked state
- Home.py: replace conda run invocation in "Score All Unscored Jobs" with
sys.executable so the button works inside Docker where conda is not present
- 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
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.
- Settings → Search: add-title (+) and Import buttons crashed with
StreamlitAPIException when writing to _sp_titles_multi after it was
already instantiated. Fix: pending-key pattern (_sp_titles_pending /
_sp_locs_pending) applied before widget renders on next pass.
- Home setup banners: fired for email/notion/keywords even when those
features were already configured. Add 'done' condition callables
(_email_configured, _notion_configured, _keywords_configured) to
suppress banners automatically when config files are present.
- Services tab start/stop buttons: docker CLI was unavailable inside
the container so _docker_available was False and buttons never showed.
Bind-mount host /usr/bin/docker (ro) + /var/run/docker.sock into the
app container so it can control sibling containers via DooD pattern.