# Pay Salary Quick Action — Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task (in-session, fresh subagent + 2-stage review per task). Each subagent uses superpowers:test-driven-development for the Django-render portion. **Goal:** Add a "Pay Salary" tile to the home dashboard's admin Quick Actions row that deep-links to `/payroll/?action=pay-salary`; the payroll page auto-clicks the existing Pay Salary button (clean slate → type=Salary → managers-only scope → modal open), then strips the param. **Architecture:** A plain `` tile (admin-only branch) + a ~10-line client-side deep-link hook that triggers the existing `paySalaryBtn`. No duplicated behaviour, no view/model/URL change; `?action=` is inert server-side. **Tech Stack:** Django 5.2.7 templates + vanilla JS; SQLite local (`USE_SQLITE=true`); Bootstrap 5. **Design doc:** `docs/plans/2026-05-16-pay-salary-quick-action-design.md` (commit `fb19655`). **Branch / baseline:** `ai-dev`, HEAD `fb19655`, **207/207 tests passing**. On top of the paused, un-pushed Manager/Salaried + pay-type-filter + Salary-auto-scope commits. **Test command (Git Bash, per CLAUDE.md):** ```bash USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2 ``` > **TDD note.** Task 1's Django-render behaviour (tile present, param > inert) **is** unit-testable — do real TDD there (failing test first). > The auto-click itself is pure browser JS with no server surface, so — > per the approved precedent (Task-3 toggle, Salary auto-scope) — it is > verified by Konrad's manual checklist, NOT a Django test. Do not add a > JS-source-sniffing test. > ⛔ **HARD STOP after Task 2.** Do NOT `git push`, do NOT deploy. Hand > back to Konrad for the manual checklist. Ships bundled with the rest > of the paused work in ONE push, on his explicit say-so only. --- ### Task 1: Tile + deep-link hook + render test **Files:** - Modify: `core/templates/core/index.html` — admin Quick Actions card (the "Run Payroll" tile is currently lines 208–211) - Modify: `core/templates/core/payroll_dashboard.html` — after the `if (paySalaryBtn) { … }` block (currently lines 2101–2117; insert before line 2119) - Test: `core/tests.py` — new class `PaySalaryQuickActionTests`, inserted immediately before `class WorkHistoryTeamFilterTests` **Step 1: Write the failing test** Insert this class immediately before `class WorkHistoryTeamFilterTests(TestCase):` in `core/tests.py` (reuse module-level `User`/`TestCase`/`reverse` already imported by neighbouring classes — do not add imports if present): ```python class PaySalaryQuickActionTests(TestCase): """Home dashboard 'Pay Salary' Quick Action: an admin sees a tile that deep-links /payroll/?action=pay-salary; the param is inert server-side (no view change). The auto-open click is client-side JS, verified by manual checklist (not asserted here).""" @classmethod def setUpTestData(cls): cls.admin = User.objects.create_user( username='psqa_admin', password='pw', is_staff=True, is_superuser=True, ) def setUp(self): self.client.force_login(self.admin) def test_home_has_pay_salary_quick_action(self): resp = self.client.get('/') self.assertEqual(resp.status_code, 200) # The tile links to the payroll dashboard with the deep-link param. self.assertContains(resp, '?action=pay-salary') # And is labelled "Pay Salary". self.assertContains(resp, 'Pay Salary') def test_payroll_dashboard_ignores_action_param(self): # The param is purely a client-side signal; the view must not # care about it — same 200 as a plain /payroll/ load. resp = self.client.get('/payroll/?action=pay-salary') self.assertEqual(resp.status_code, 200) ``` **Step 2: Run the tests, verify they FAIL** ```bash USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests.PaySalaryQuickActionTests -v 2 ``` Expected: `test_home_has_pay_salary_quick_action` FAILS (no `?action=pay-salary` in `/`). `test_payroll_dashboard_ignores_action_param` will likely already PASS (param already inert) — that's fine; it's a guard that must stay green. **Step 3: Add the home dashboard tile** In `core/templates/core/index.html`, the "Run Payroll" tile currently is (lines 208–211): ```html Run Payroll ``` Insert the new tile IMMEDIATELY AFTER that closing `` (line 211) and before the "View History" tile (``): ```html {# === PAY SALARY — quick path: opens the Pay-Salary modal on /payroll/ === #} {# Same fa-user-tie icon as the payroll dashboard's Pay Salary button so #} {# users have one mental model for "salary = fa-user-tie". #} Pay Salary ``` Match the file's existing indentation (16 spaces for the ``). Each `{# #}` comment is a single self-contained line — do NOT let any `{#` span lines. **Step 4: Add the deep-link hook** In `core/templates/core/payroll_dashboard.html`, the `paySalaryBtn` wiring ends at line 2117 (` }` closing `if (paySalaryBtn) {`), followed by a blank line 2118 and the header-button comment at line 2119. Insert this block on the blank line AFTER line 2117 and BEFORE the `// When the modal is opened from the HEADER button …` comment: ```javascript // === Quick-action deep-link: /payroll/?action=pay-salary === // The home dashboard "Pay Salary" Quick Action links here with this // param. Auto-click the existing Pay Salary button (which does the // clean-slate + type=Salary + managers-only scoping + modal open), // then strip the param so a manual refresh or Back doesn't re-pop // the modal. Best-effort — never let a deep-link quirk block the page. try { var _qsAction = new URLSearchParams(window.location.search).get('action'); if (_qsAction === 'pay-salary' && paySalaryBtn) { paySalaryBtn.click(); var _u = new URL(window.location.href); _u.searchParams.delete('action'); window.history.replaceState({}, '', _u.pathname + _u.search + _u.hash); } } catch (e) { /* deep-link is best-effort; never block the page */ } ``` Notes: `paySalaryBtn` is the `var` declared at line 2100 — in scope here. It must run AFTER its `addEventListener` (line 2102) so the click has a handler — this placement (after line 2117) guarantees that. Do NOT modify the `paySalaryBtn` click handler or any modal-reset logic. **Step 5: Guard greps** ```bash grep -rn "^\s*{#" core/templates/core/index.html | awk -F: '$0 !~ /#}/ {print}' grep -rn "^\s*{#" core/templates/core/payroll_dashboard.html | awk -F: '$0 !~ /#}/ {print}' grep -c 'id="paySalaryBtn"' core/templates/core/payroll_dashboard.html grep -c 'action=pay-salary' core/templates/core/index.html ``` Expected: first two → no output; third → `1` (button id still unique, untouched); fourth → `1` (one new tile). **Step 6: Run tests, verify PASS** ```bash USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests.PaySalaryQuickActionTests -v 2 ``` Expected: 2 tests OK. Then full suite: ```bash USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2 ``` Expected: **208 tests OK** (207 baseline + 1 new render test; the param-inert test was already-green and stays green). If anything unrelated fails, STOP and report. **Step 7: Commit (local only, NO push, NEW commit)** ```bash git add core/templates/core/index.html core/templates/core/payroll_dashboard.html core/tests.py git commit -m "feat: Pay Salary quick action on home dashboard (deep-link to modal) Admin Quick Actions tile → /payroll/?action=pay-salary; the payroll page auto-clicks the existing paySalaryBtn then strips the param. Reuses all existing Pay-Salary machinery; param inert server-side. Co-Authored-By: Claude Opus 4.7 (1M context) " ``` **Self-review:** `git show HEAD` — exactly 3 files; the tile is inside the admin Quick Actions card (between Run Payroll and View History); the hook is after the `if (paySalaryBtn){}` block (not inside it); no change to the paySalaryBtn handler or modal resets; greps clean; suite 208. --- ### Task 2: Docs + final regression + HARD STOP **Files:** - Modify: `docs/plans/parked-work.md` - Modify: `CLAUDE.md` **Step 1: Full regression (anchor)** ```bash USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2 ``` Expected: **208 OK**. If not, STOP. **Step 2: Update `docs/plans/parked-work.md`** Read the file; find the paused Manager/Salaried entry (under "⏸ Paused … awaiting Konrad's verification"; already carries the pay-type-filter and Salary-auto-scope notes). Append, matching the file's prose style: > Also now: a home-dashboard admin Quick Actions tile **"Pay Salary"** > deep-links `/payroll/?action=pay-salary` and auto-opens the existing > Pay Salary modal (param stripped after). Design > `docs/plans/2026-05-16-pay-salary-quick-action-design.md`, plan > `docs/plans/2026-05-16-pay-salary-quick-action-plan.md`. Same HARD > STOP — bundled into the one push on Konrad's say-so. (If no such entry exists, add a short one under "⏸ Paused" matching the others' format. Report which case.) **Step 3: Update `CLAUDE.md`** In the "## Manager / Salaried pay (May 2026)" section, after the existing **"Salary picker safety:"** line, append one line in the same terse style: > **Pay Salary quick action:** the home dashboard's admin Quick Actions > row has a "Pay Salary" tile linking `/payroll/?action=pay-salary`; > `payroll_dashboard.html` JS auto-clicks the existing `paySalaryBtn` > on load when that param is present, then strips it via > `history.replaceState` (no re-pop on refresh). `?action=` is inert > server-side — no view/URL change. **Step 4: Verify docs** ```bash grep -n "action=pay-salary\|Pay Salary quick action\|Pay Salary" docs/plans/parked-work.md CLAUDE.md ``` Expected: new lines present in both files; Markdown intact. **Step 5: Commit (local only, NO push, NEW commit)** ```bash git add docs/plans/parked-work.md CLAUDE.md git commit -m "docs: note Pay Salary quick action (rides paused bundle) Co-Authored-By: Claude Opus 4.7 (1M context) " ``` **Self-review:** `git show --stat HEAD` — only the 2 doc files, additions only. --- ## ⛔ HARD STOP — hand back to Konrad After Task 2's commit: 1. `git status` clean; `git log --oneline -3` shows the 2 new commits on `ai-dev` on top of `fb19655`. 2. Full suite once more → **208/208 OK**. 3. **Do NOT `git push`. Do NOT deploy.** Report the commit list + test count and point Konrad at the **Manual verification** section of `docs/plans/2026-05-16-pay-salary-quick-action-design.md` (7 steps). 4. Push/deploy only on Konrad's explicit approval, bundled with the rest of the paused Manager/Salaried + pay-type-filter + Salary-auto-scope work in ONE push (github + gitea; deploy order: pull → migrate → collectstatic → restart last). ## Notes - **DRY:** the tile just triggers the existing `paySalaryBtn`; all real behaviour stays in one place. - **YAGNI:** no view/URL/server handling of `?action=`; no generic deep-link framework; no supervisor entry. - **Why the hook sits after `if (paySalaryBtn){}`:** the click handler must be attached before we synthesise a click; this placement guarantees ordering and keeps `paySalaryBtn` in scope. - **No migration / view / URL change**; only one new Django render test by design (the auto-click is JS, manual-checklist verified — documented precedent).