diff --git a/docs/plans/2026-04-23-inline-filters-plan.md b/docs/plans/2026-04-23-inline-filters-plan.md
new file mode 100644
index 0000000..e5b4eff
--- /dev/null
+++ b/docs/plans/2026-04-23-inline-filters-plan.md
@@ -0,0 +1,1439 @@
+# Inline Filters (Pill-as-Dropdown) Implementation Plan
+
+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
+
+**Goal:** Replace the modal-based filter form on `/report/` with interactive pill-dropdowns (Apply button appears only when pending changes exist, bidirectional project↔team cross-filter auto-removes invalid selections), and retire `_report_config_modal.html` entirely.
+
+**Architecture:** Template-only change except for one backend tweak. The existing filter-pill strip (shipped with Executive Report v2) becomes clickable — each pill opens a Bootstrap-like popover containing the relevant editable widget (date picker or Choices.js multi-select). A scoped IIFE JS module inside `report.html` manages popover state, tracks dirty filters, and rebuilds the submit URL on Apply. Cross-filter data comes from a single JSON `
+```
+
+**Step 4.3: Check syntax**
+
+```bash
+USE_SQLITE=true DJANGO_DEBUG=true python manage.py check
+```
+
+Expected: only the `staticfiles.W004` warning.
+
+**Step 4.4: Commit**
+
+```bash
+git add core/templates/core/report.html
+git commit -m "$(cat <<'EOF'
+JS: pill-popover interactive module for inline filters
+
+One scoped IIFE that wires up the three filter pills into an
+interactive, state-managed UI:
+
+- Click pill -> open popover (lazy-inits Choices.js on first open)
+- Click outside / Esc / other pill -> close
+- OK commits popover's local edits into pending state; dirty pills
+ get orange outline + pulsing dot; Apply button slides in at the
+ right end of the strip
+- Cross-filter: picking projects auto-removes now-invalid teams
+ with toast notice ("Removed Team X — no logs on selected projects"),
+ and vice versa. Scope = entire history.
+- Apply -> rebuilds querystring from pending state + navigates
+ (full page reload, same URL scheme as the retired modal)
+- Reset -> reverts all pills to URL-current values
+
+XSS-safe throughout: textContent and createElement; no innerHTML with
+user data. Matches the pattern in base.html's work-log-payroll modal
+from the previous feature.
+
+Graceful fallback: if Choices.js CDN fails to load, the module bails
+early with a console warning; native still works
+inside the popovers.
+
+Co-Authored-By: Claude Opus 4.7 (1M context)
+EOF
+)"
+```
+
+---
+
+### 🛑 CHECKPOINT 1 — all pill interactions demoable
+
+Konrad should open `/report/` in the browser (hard refresh) and walk through:
+
+1. Click the date pill → popover opens with month/custom toggle + pickers
+2. Change from-month → click OK → pill shows new range, Apply button appears, pill has orange dirty outline
+3. Click Apply → URL updates → report re-renders with new filters
+4. Click projects pill → popover with Choices.js multi-select → pick 2 projects → OK → pill updates to "Project A + 1 more"
+5. Click teams pill → popover → if a project is already selected, teams that haven't worked on it are disabled; pick a valid team → OK
+6. Intentionally: pick a project, then add a team that doesn't match → on OK, that team gets auto-removed with a toast
+7. Click the × on a pill (existing behaviour) → filter clears via full page reload
+8. Click Reset when dirty → all pills revert, Apply disappears
+9. Esc key → any open popover closes
+10. Modal still works via the "Generate Report" button on the dashboard + "New Report" button on the report page (Task 5 retires them)
+
+Await approval before Task 5 (modal retirement — destructive).
+
+---
+
+## Task 5: Retire the modal
+
+**Files:**
+- Modify: `core/templates/core/index.html` (line 187 — change button to plain link; line 605 — remove include)
+- Modify: `core/templates/core/report.html` (lines 20-23 delete header "New Report" button; lines 387-389 delete bottom "New Report" button; line 400 remove include)
+- Delete: `core/templates/core/_report_config_modal.html`
+- Modify: `core/views.py` (lines 434-435 remove from `index()` context; lines 2395-2404 remove from `generate_report` context)
+
+**Step 5.1: Update `core/templates/core/index.html`**
+
+Find around line 187 (Quick Actions "Generate Report"):
+
+```django
+
+
+ Generate Report
+
+```
+
+Replace with a plain link to `/report/` pre-loaded with the current month:
+
+```django
+{# Plain link: pills on the report page are the new-report interface. #}
+
+
+ Generate Report
+
+```
+
+Then find line 605 and delete the `{% include 'core/_report_config_modal.html' %}` line entirely.
+
+**Step 5.2: Update `core/templates/core/report.html`**
+
+Delete the header "New Report" button block (around lines 20-23):
+
+```django
+
+
+ New Report
+
+```
+
+Delete the bottom "New Report" button block (around lines 387-389):
+
+```django
+
+ New Report
+
+```
+
+Delete the include line (around line 400):
+
+```django
+{% include 'core/_report_config_modal.html' %}
+```
+
+(Also delete any comment block immediately above that include, if it only refers to the modal.)
+
+**Step 5.3: Delete the modal template file**
+
+```bash
+git rm core/templates/core/_report_config_modal.html
+```
+
+**Step 5.4: Update `core/views.py` — remove `selected_*_ids` threading**
+
+In `index()` (around line 434):
+
+Find:
+```python
+ 'selected_project_ids': [],
+ 'selected_team_ids': [],
+```
+
+Delete these two lines.
+
+In `generate_report` (around line 2395-2404):
+
+Find the whole block:
+```python
+ # Pass projects and teams so the "New Report" modal's dropdowns can
+ # populate (same lists the Dashboard modal uses)
+ context['projects'] = Project.objects.all().order_by('name')
+ context['teams'] = Team.objects.all().order_by('name')
+ # For the modal's pre-selection: stringify the IDs so
+ # the template's `{% if p.id|stringformat:"s" in selected_project_ids %}`
+ # comparison works (Django templates compare strings to strings).
+ context['selected_project_ids'] = [str(p) for p in (project_ids or [])]
+ context['selected_team_ids'] = [str(t) for t in (team_ids or [])]
+```
+
+**Keep** the `projects` / `teams` context keys (the pills' popovers still use them to render `` lists). But **remove** the `selected_project_ids` / `selected_team_ids` stringify block — the JS reads those from `json_script` tags which the template still needs. Wait — the pills DO use `selected_project_ids` / `selected_team_ids` for pre-selection. So these stay!
+
+Actually, re-read Task 2's markup: the popover ` ` tags use `{% if p.id|stringformat:"s" in selected_project_ids %} selected{% endif %}`, and the json_script tag emits `{{ selected_project_ids|json_script:"urlSelectedProjectIds" }}`. Both references are in Task 2's markup — so **these context keys stay**.
+
+**Correction**: do NOT remove the `selected_project_ids` / `selected_team_ids` context keys. The inline-filters feature still uses them.
+
+Only the **modal itself** is deleted; the data it consumed is still needed by the pill markup.
+
+So Step 5.4 reduces to: **no change to `core/views.py`**. All backend context stays.
+
+**Step 5.5: Run tests + manage.py check**
+
+```bash
+USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 0 2>&1 | tail -5
+USE_SQLITE=true DJANGO_DEBUG=true python manage.py check
+```
+
+Expected: `Ran 44 tests ... OK`; only the `node_modules` warning.
+
+**Step 5.6: Verify no dangling references to the deleted modal**
+
+```bash
+grep -rn "reportConfigModal\|_report_config_modal" core/templates/ core/views.py
+```
+
+Expected: **zero hits**. If any remain, delete them.
+
+**Step 5.7: Commit**
+
+```bash
+git add core/templates/core/index.html core/templates/core/report.html
+git commit -m "$(cat <<'EOF'
+Retire _report_config_modal.html — pills are the new-report interface
+
+- Dashboard 'Generate Report' button: modal trigger -> plain link
+ to /report/?from_month=&to_month=
+- Report page 'New Report' buttons (both header + bottom action bar):
+ deleted (the pills replace them)
+- _report_config_modal.html: deleted
+- No backend changes — selected_project_ids / selected_team_ids
+ context keys are still used by the pill popovers for pre-selection
+ (just not by the retired modal)
+
+grep -rn 'reportConfigModal' core/templates/ core/views.py returns 0.
+
+Co-Authored-By: Claude Opus 4.7 (1M context)
+EOF
+)"
+```
+
+---
+
+## Task 6: Final QA + mark shipped
+
+**Files:**
+- Modify: `docs/plans/2026-04-23-inline-filters-design.md` (append "Shipped" block)
+
+**Step 6.1: Manual QA matrix**
+
+As admin, walk through:
+
+| Flow | Expected |
+|---|---|
+| `/report/` no filters | Pills show current-month range + "All Projects" + "All Teams"; no Apply button; no × buttons |
+| Click date pill → change month → OK → Apply | URL has `?from_month=...&to_month=...`; report re-renders |
+| Click projects pill → pick 2 → OK → teams popover | Teams not linked to either project are disabled in the dropdown |
+| Pick a team not linked to selected projects → OK | Team gets auto-removed with toast "Removed Team X — no logs on selected projects" |
+| Reset button (when dirty) | All pills revert, Apply disappears |
+| Esc key when popover open | Popover closes |
+| Existing × buttons on pills | Still clear individual filters via full page reload |
+| Click dashboard "Generate Report" card | Navigates directly to `/report/?from_month=&to_month=` — no modal |
+| `grep -rn 'reportConfigModal' core/templates/` | Zero hits |
+| Supervisor user hits `/report/` | 403 (unchanged) |
+| PDF download with any filter | Mirrors current filter state (unchanged) |
+| Phone viewport | Popovers dock to bottom of viewport full-width |
+
+**Step 6.2: Full test run**
+
+```bash
+USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 0 2>&1 | tail -5
+```
+
+Expected: `Ran 44 tests ... OK`.
+
+**Step 6.3: Django system check + migrations dry-run**
+
+```bash
+USE_SQLITE=true DJANGO_DEBUG=true python manage.py check
+USE_SQLITE=true DJANGO_DEBUG=true python manage.py makemigrations --dry-run
+```
+
+Expected: only `staticfiles.W004`; "No changes detected".
+
+**Step 6.4: Append "Shipped" block to the design doc**
+
+Append to `docs/plans/2026-04-23-inline-filters-design.md`:
+
+```markdown
+---
+
+## Shipped — 23 Apr 2026
+
+**Commits:** 30d0991 (design) through the Task 6 shipped commit.
+**Plan:** `docs/plans/2026-04-23-inline-filters-plan.md`
+**Tests:** 42 → 44 (2 new — InlineFiltersPairsContextTests covering
+the `project_team_pairs_json` context key + NULL-team exclusion).
+
+**QA outcome:** 44/44 tests pass. `manage.py check` clean.
+`makemigrations --dry-run` reports no changes. All 10 manual QA
+flows green. Cross-filter auto-remove + toast behaviour verified.
+_report_config_modal.html fully retired; grep confirms no dangling
+references.
+
+**Deferred / out of scope (revisit if requested):**
+- AJAX partial re-render (still full page reload on Apply)
+- "Save this filter set" / named filter presets
+- Permissive cross-filter variant (strict was chosen)
+- Date-range-scoped cross-filter (entire history was chosen)
+
+**Notable decisions made during implementation:**
+- Modal `selected_project_ids` / `selected_team_ids` context keys
+ were KEPT (not removed as the design initially suggested) because
+ the pill popovers still use them for pre-selecting the Choices.js
+ `` tags and for URL-diff initialisation via json_script.
+- Choices.js re-init on cross-filter change (destroy + reinstantiate)
+ is wasteful but the simplest way to reflect ` `
+ changes; acceptable at FoxFitt's scale (≤10 projects, ≤10 teams).
+```
+
+**Step 6.5: Commit**
+
+```bash
+git add docs/plans/2026-04-23-inline-filters-design.md
+git commit -m "Docs: mark Inline Filters feature as shipped (23 Apr 2026)
+
+Co-Authored-By: Claude Opus 4.7 (1M context) "
+```
+
+---
+
+### 🛑 CHECKPOINT 2 — ship
+
+Final demo: all pills work, modal gone, tests green, PDF still respects filters.
+
+**On approval → single batched push** of all 6 commits (plus the two design-doc commits from earlier — 30d0991 inline-filters-design + 12edafa adjustments-tab-design). The adjustments-tab design doc rides along since it was committed locally in the same brainstorm session.
+
+Gemini deploy prompt (CSS change → collectstatic mandatory):
+
+```bash
+git fetch github && git pull github ai-dev && \
+ python3 manage.py collectstatic --noinput && \
+ sudo systemctl restart django-dev.service
+```
+
+---
+
+## Out of scope (not in this plan)
+
+- Inline filter persistence across browser tabs (a URL-sharing improvement; URLs already bookmarkable)
+- Filter-chip sort order preference
+- Keyboard navigation between pills (Tab works via native button focus)
+- Adjustments tab (separate plan after this ships)
+
+---
+
+Plan complete and saved to `docs/plans/2026-04-23-inline-filters-plan.md`. Two execution options:
+
+**1. Subagent-Driven (this session)** — I dispatch a fresh subagent per task, review between tasks, fast iteration.
+
+**2. Parallel Session (separate)** — Open a new Claude Code session with `superpowers:executing-plans`, batch execution.
+
+**Which approach?**