# Next Session: WhatsApp Web + Playwright Migration (Implementation)

## What

Replace Annie's WhatsApp agent I/O layer from mobile u2 screen scraping (Tasker + uiautomator2 on Pixel) to **WhatsApp Web + Playwright on Panda**. The trigger pipeline, response generation, context management, and compaction are unchanged — only the read/write layer swaps. This migration was decided after 3 sessions proved u2 delivery is too brittle (resource IDs change, navigation clicks wrong elements, Tasker metadata unreliable).

## Plan

**Read the plan first:** `~/.claude/plans/adaptive-popping-treehouse.md`

This plan has been through **2 rounds of adversarial review** (architecture + code quality) with **27 findings all addressed**. Key review-driven design decisions are embedded in the plan. Do NOT skip reading it — implementing without the review context will reintroduce the bugs the reviewers found.

## Key Design Decisions (from adversarial review)

1. **Lazy asyncio.Lock** — `WABrowser._init_lock` is created inside `get_instance()`, NOT at class definition scope. Class-scope `asyncio.Lock()` crashes with "no running event loop" on import.

2. **Single `_page_lock` for ALL page operations** — Poll loop, health monitor, and sender ALL acquire `browser._page_lock` before any `page.*` call. Playwright's CDP connection is not safe for concurrent coroutine access. Without this lock, interleaved CDP commands produce "Target closed" crashes.

3. **Clipboard paste, not `page.type()`** — `page.type()` interprets `\n` as Enter, which prematurely sends partial messages. Use `navigator.clipboard.writeText(text)` + Ctrl+V instead. This also bypasses contenteditable event listener issues.

4. **Navigate to chat before every send** — `wa_sender.send_message(page, text, chat_name)` always calls `navigate_to_chat()` first. Never assume the current chat is correct (the health monitor or a prior send may have changed it).

5. **Last-seen persistence + first-run catch-up** — `last_seen_id` checkpointed to `~/.her-os/whatsapp-web/last_seen.json` on every poll. On startup: load from file. If file missing (first run): read DOM, set last_seen to latest message, **process NONE**. This prevents replaying old messages after a restart.

6. **Sender inheritance for consecutive messages** — In WhatsApp Web groups, sender name only appears on the first message in a sequence. Subsequent messages from the same person have no sender element. Track `current_sender` top-to-bottom during extraction, inherit for missing elements.

7. **Anti-detection** — `--disable-blink-features=AutomationControlled`, real user-agent, `navigator.webdriver` override via init script. NO `--disable-gpu` (detection fingerprint). If WhatsApp bans: fall back to archived u2 code in `_legacy/`.

8. **Archive u2 code, don't delete** — `receiver.py`, `scraper.py`, `tasker_watchdog.py` moved to `_legacy/` folder. If WhatsApp Web automation is detected and session banned, we can revert.

9. **Selector validation on startup** — After connecting, verify every CRITICAL selector matches an element. Log error + try fallback selector if primary misses. Selectors have primary + fallback chains.

10. **QR alerts via Telegram** — When session expires (QR_NEEDED state), save screenshot and send via Telegram bot to Rajesh. No VNC/SSH display needed.

11. **Corrected Chromium memory flag** — `--js-flags=--max-old-space-size=512` (not bare `--max-old-space-size` which is Node.js-only and silently ignored by Chromium).

12. **Health endpoint in agent.py** — Moved from deleted receiver.py. Reports: session_state, poll_count, last_message_at, circuit_breaker, cadence_remaining.

## Files to Modify (in order)

### Phase 0: Selector Discovery (MUST do first)
- Launch headed Chromium on Panda, open WhatsApp Web, scan QR
- Use DevTools to identify and verify selectors for: chat_list, message_in, msg_text, msg_sender, msg_time, compose_box, send_button, qr_code, phone_disconnected
- Record primary + fallback selectors for critical elements

### Phase 1: Browser Infrastructure
- **CREATE** `services/whatsapp-agent/wa_browser.py` (~150 lines) — Playwright singleton, lazy lock, session lifecycle, QR Telegram alerts, anti-detection, profile corruption recovery, PID file

### Phase 2: Message Reading
- **CREATE** `services/whatsapp-agent/wa_web.py` (~180 lines) — DOM polling, message extraction with sender inheritance, selector validation, last_seen persistence, catch-up pass

### Phase 3: Message Sending
- **CREATE** `services/whatsapp-agent/wa_sender.py` (~100 lines) — Navigate-then-send, clipboard paste, tick mark verification

### Phase 4: Integration
- **CREATE** `services/whatsapp-agent/filters.py` (~50 lines) — Noise filter + greeting collapse extracted from receiver.py
- **MODIFY** `services/whatsapp-agent/agent.py` — Poll loop, health endpoint, remove Tasker/ADB imports
- **MODIFY** `services/whatsapp-agent/config.py` — Browser config, remove u2 constants
- **MODIFY** `services/whatsapp-agent/responder.py` — Remove u2 delivery, delegate to wa_sender
- **MODIFY** `services/whatsapp-agent/wa_observability.py` — Wire emit calls
- **ARCHIVE** `receiver.py`, `scraper.py`, `tasker_watchdog.py` → `_legacy/`

### Phase 5: Testing
- **CREATE** `test_filters.py`, `test_wa_browser.py`, `test_wa_web.py`, `test_wa_sender.py`, `test_agent_poll.py`
- **ARCHIVE** `test_receiver.py`, `test_scraper.py`, `test_tasker_watchdog.py` → `_legacy/`
- Keep: test_trigger, test_context_mgr, test_compaction, test_mutex, test_dedup, test_circuit_breaker, test_context_client, test_responder (updated)

## Env Vars Needed on Panda (~/.her-os/.env)

```bash
WHATSAPP_BROWSER_DIR=/home/rajesh/.her-os/whatsapp-web
PLAYWRIGHT_HEADLESS=true
PLAYWRIGHT_BROWSERS_PATH=/home/rajesh/.her-os/playwright-browsers
POLL_INTERVAL_S=5
GEMMA4_BASE_URL=http://192.168.68.52:8003/v1
TELEGRAM_BOT_TOKEN=<existing bot token>
TELEGRAM_ALERT_CHAT_ID=<Rajesh's chat ID>
```

## Start Command

```bash
cat ~/.claude/plans/adaptive-popping-treehouse.md
```

Then implement the plan. All adversarial findings are already addressed in it. Start with Phase 0 (selector discovery) — no code until selectors are verified.

## Verification

1. **Unit tests:** `cd services/whatsapp-agent && python -m pytest tests/ -v` — all pass
2. **Deploy:** `git push` → `ssh panda "cd ~/workplace/her/her-os && git pull"`
3. **First install:** `ssh panda ".venv/bin/pip install 'playwright==1.52.0' && PLAYWRIGHT_BROWSERS_PATH=~/.her-os/playwright-browsers .venv/bin/playwright install chromium"`
4. **QR setup:** `PLAYWRIGHT_HEADLESS=false .venv/bin/python services/whatsapp-agent/agent.py` — scan QR from Pixel
5. **Production:** `./start.sh whatsapp` from laptop
6. **Health:** `curl http://panda:8780/v1/health` → `session_state: "connected"`
7. **E2E:** Send "Annie, what time is it?" from Rajesh's phone → Annie replies in WhatsApp
