diff --git a/README.md b/README.md
index e646c98..6da438e 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,106 @@
-# JobOps — job search orchestration (personal fork)
+# JobOps
-Self-hosted stack: scrapes job boards, AI-scores fit, tailors resumes (RxResume), tracks application email.
+Self-hosted job search orchestration: discover roles from multiple sources, score fit with your profile, draft tailored resumes and cover letters, export PDFs, and track application email—**you still submit applications yourself**; JobOps prepares the work and keeps state organized.
-You still apply yourself; the app finds roles, helps match CVs, and keeps status straight.
+Licensed under **AGPLv3 + Commons Clause** — see [LICENSE](LICENSE).
-Docker-based. See [LICENSE](LICENSE) for terms.
+
-
+## What’s in this repo
-## Documentation
+| Area | Role |
+|------|------|
+| **`orchestrator/`** | Express API, SQLite + Drizzle, pipeline, React (Vite) UI, LLM integrations, Reactive Resume PDF flow |
+| **`shared/`** | Shared TypeScript types and settings registry |
+| **`docs-site/`** | Docusaurus user and developer documentation |
+| **`extractors/`** | Per-source crawlers (Adzuna, Gradcracker, UK Visa Jobs, Hiring Café, Startup Jobs, JobSpy helpers, etc.) |
-Full docs live in this repo under `docs-site/`.
+Root `package.json` is an npm **workspace** root; day-to-day app commands usually run under `orchestrator/`.
-```bash
-npm install
-npm run docs:dev
-```
+## Features (high level)
-Then open the local URL Docusaurus prints (usually `http://localhost:3000`).
+- **Sources**: Multiple boards and aggregators (exact list evolves; see docs and extractor packages).
+- **Scoring & tailoring**: LLM compares jobs to your resume profile; optional drafts for summary, headline, skills, and project selection.
+- **PDFs**: Tailored exports via **Reactive Resume** (v4 or v5 API). Optional **local JSON resume** (`JOBOPS_LOCAL_RESUME_PATH` or Settings) as the base document for profile/tailoring; PDF export still uses RxResume when configured.
+- **Pipeline**: Scheduled or manual runs (`POST /api/pipeline/run`, webhook trigger).
+- **Post-application**: Optional Gmail-based inbox for interview/offer/rejection signals.
+- **Job list filters** (orchestrator UI): Narrow the pipeline job list by **multiple sources** and **countries** (country is inferred from each listing’s location text). Filters sync to the URL (`source`, `sourceExclude`, `countries`, `countriesExclude`). Each source/country chip cycles **off → include → exclude** (exclude shows in red); listings marked **remote** still pass country include/exclude rules.
+- **Data**: SQLite and generated artifacts under `./data` (default in Docker).
-## Quick start
+## Requirements
+
+- **Node.js 22** (see `package.json` / Volta pin) for local development.
+- **Docker + Compose v2** for the recommended production-style run.
+
+## Quick start (Docker)
```bash
git clone
-cd Jobber # or whatever you named the directory
+cd Jobber
cp .env.example .env
-# Edit .env: model / LLM keys, RXRESUME_*, search settings, etc.
+# Edit .env: MODEL / LLM keys, Reactive Resume or local resume path, optional BASIC_AUTH, etc.
-docker compose up -d
+docker compose up -d --build
```
-Dashboard: `http://localhost:3005` (host port from `docker-compose.yml`; app listens on 3001 inside the container).
+- **UI**: `http://localhost:3005` (host port mapped in `docker-compose.yml`; app listens on **3001** inside the container).
+- **Health**: `GET /health` on the same origin.
-## Features (summary)
+Persist data by backing up the mounted **`./data`** directory.
-- **Sources**: LinkedIn, Indeed, Glassdoor, Adzuna, Hiring Café, Gradcracker, UK Visa Jobs (and extensible extractors).
-- **Scoring**: LLM ranking vs your profile (OpenAI, OpenRouter, OpenAI-compatible, Gemini, etc.).
-- **Resumes**: Tailored PDFs via [RxResume v4](https://v4.rxresu.me).
-- **Email**: Gmail integration for interview / offer / rejection signals.
-- **Data**: SQLite under `./data` when using the default compose setup.
+## Local development
+
+From the repository root:
+
+```bash
+npm install
+```
+
+Then use the orchestrator workspace (see **`orchestrator/README.md`** for API tables and scripts):
+
+```bash
+cd orchestrator
+cp .env.example .env
+npm run db:migrate
+npm run dev
+```
+
+Typical dev URLs: API **http://localhost:3001**, Vite client **http://localhost:5173**.
+
+## Configuration notes
+
+- **LLM**: Configurable provider (OpenRouter default; also OpenAI, OpenAI-compatible endpoints, Gemini, Ollama, LM Studio, etc.). Keys can be set in `.env` or onboarding.
+- **Reactive Resume**: v5 API key or v4 email/password; optional self-hosted `RXRESUME_URL`. Full behavior is documented in the docs site under **Reactive Resume**.
+- **Local resume JSON**: Set `JOBOPS_LOCAL_RESUME_PATH` (or the Settings field) to a Reactive Resume–shaped export so you do not need a selected cloud “base resume” for profile and tailoring. PDF generation still performs a temporary import/print through RxResume when credentials are present.
+
+## Documentation
+
+| Resource | Contents |
+|----------|----------|
+| **`docs-site/`** | User-facing guides (build with `npm run docs:dev` from repo root) |
+| **`orchestrator/README.md`** | Orchestrator layout, endpoints, dev commands |
+| **`DEPLOY_GITEA_VM_CRON_TELEGRAM.md`** | VM/container deploy, cron, optional Telegram |
+| **`AGENTS.md`** | API/logging/SSE conventions for contributors and automation |
## Deploy, cron, Telegram
-See [DEPLOY_GITEA_VM_CRON_TELEGRAM.md](./DEPLOY_GITEA_VM_CRON_TELEGRAM.md) for VM or container deploy, scheduled `POST /api/pipeline/run`, and optional Telegram notifications.
+See **[DEPLOY_GITEA_VM_CRON_TELEGRAM.md](./DEPLOY_GITEA_VM_CRON_TELEGRAM.md)** for production deploy, scheduling `POST /api/pipeline/run`, and optional notifications.
+
+## Verification (CI parity)
+
+From the repo root, before merging substantial changes:
+
+```bash
+./orchestrator/node_modules/.bin/biome ci .
+npm run check:types:shared
+npm --workspace orchestrator run check:types
+npm --workspace orchestrator run build:client
+npm --workspace orchestrator run test:run
+```
+
+(Additional workspace typechecks apply in CI; see **`AGENTS.md`**.)
## License
-**AGPLv3 + Commons Clause** — self-host, use, and modify; you may not sell the software or offer paid hosting/support whose value substantially comes from this codebase. Details in [LICENSE](LICENSE).
+**AGPLv3 + Commons Clause** — you may self-host, use, and modify; you may not sell the software or offer paid hosting or support whose value substantially comes from this codebase. See [LICENSE](LICENSE).
diff --git a/orchestrator/src/client/pages/OrchestratorPage.test.tsx b/orchestrator/src/client/pages/OrchestratorPage.test.tsx
index ca7aeeb..e68ae00 100644
--- a/orchestrator/src/client/pages/OrchestratorPage.test.tsx
+++ b/orchestrator/src/client/pages/OrchestratorPage.test.tsx
@@ -204,7 +204,7 @@ vi.mock("./orchestrator/OrchestratorFilters", () => ({
OrchestratorFilters: ({
onTabChange,
onOpenCommandBar,
- onSourceFilterChange,
+ onSourceSelectionChange,
onSponsorFilterChange,
onSalaryFilterChange,
onResetFilters,
@@ -214,7 +214,7 @@ vi.mock("./orchestrator/OrchestratorFilters", () => ({
}: {
onTabChange: (t: FilterTab) => void;
onOpenCommandBar: () => void;
- onSourceFilterChange: (source: string) => void;
+ onSourceSelectionChange: (include: string[], exclude: string[]) => void;
onSponsorFilterChange: (value: string) => void;
onSalaryFilterChange: (value: {
mode: "at_least" | "at_most" | "between";
@@ -241,7 +241,10 @@ vi.mock("./orchestrator/OrchestratorFilters", () => ({
>
Set Sort
-