# rmfmail Debugging Guide

## Debug HTTP server (`--debug-server`)

`rmfmail --debug-server` starts a loopback Express API **alongside** the normal
GUI — the WebView still opens, but a second HTTP surface is mounted on
`127.0.0.1:9334` (`ports.rmfmaildbg` from miscinfo) against the **same live store and
imapManager**. No parallel SQLite handle, no parallel sync — it is a window
onto the running daemon, not a second daemon.

This differs from `--server` mode, which *replaces* the GUI with Express.

Purpose: drive and inspect daemon state from an external tool (curl, a test
script, an agent) so GUI-path bugs can be reproduced without a human clicking.

### Lifecycle / restart for test cycles

There is **no kill endpoint** — shutting the daemon down from inside itself is
fragile. Use the CLI:

```
rmfmail -kill                      # clean shutdown (kills only rmfmail's own tracked PIDs)
rmfmail --debug-server --verbose   # relaunch with the debug API + console logging
```

`rmfmail -kill` reads `~/.rmfmail/instance.json` and SIGTERMs only the PIDs
that instance owns — it never name-sweeps `node.exe` or `msgernative.exe`
(other apps share those). Safe to cycle repeatedly.

### Base URL

```
http://127.0.0.1:9334/api
```

### Read endpoints

| Method | Path | Notes |
|--------|------|-------|
| GET  | `/accounts` | account list |
| GET  | `/folders/:accountId` | folder tree (`id`, `path`, counts) |
| GET  | `/messages/:accountId/:folderId?page=&pageSize=&sort=&sortDir=&search=` | envelope list |
| GET  | `/messages/unified/inbox?page=&pageSize=` | unified inbox |
| GET  | `/message/:accountId/:uid?allowRemote=&folderId=` | **full parsed message** — the click→render path |
| GET  | `/message/:accountId/:uid/attachment/:attachmentId` | attachment bytes |
| GET  | `/search?q=&page=&pageSize=` | FTS search |
| GET  | `/sync/pending` | queued local actions awaiting server mirror |
| GET  | `/contacts?q=` | contact autocomplete |
| GET  | `/settings` | settings JSON |

### Write / action endpoints

| Method | Path | Body |
|--------|------|------|
| POST  | `/sync` / `/sync/:accountId` | force a sync |
| POST  | `/messages/delete` | `{accountId, uids, folderId}` |
| POST  | `/messages/move` | `{accountId, uids, fromFolderId, toFolderId}` |
| POST  | `/messages/spam` | mark spam |
| POST  | `/message/:accountId/:uid/move` | single-message move |
| POST  | `/message/:accountId/:uid/undelete` | restore |
| PATCH | `/message/:accountId/:uid/flags` | `{flags:[...]}` |
| POST  | `/draft` | save draft |
| POST  | `/send` | send |
| POST  | `/reauth/:accountId` | OAuth re-auth |

### Reproducing the loading-body bug

```
# pick a uid from the INBOX list, then time the full-message fetch
curl -s -m 60 -w '\n%{time_total}s\n' \
  "http://127.0.0.1:9334/api/message/bobma/<uid>?folderId=131"
```

A healthy fetch is well under a second. A multi-second figure means the parse
queued behind a pile-up or the event loop is wedged by sync — check the daemon
log (`~/.rmfmail/logs/rmfmail-<date>.log`) for `[parse]`, `[parse-worker]`,
`cmd-chain wait`, and `IDLE-suspend wait` lines.

Note: while a large first-sync is running the event loop is saturated and any
endpoint can be slow — that saturation is itself a bug under investigation, not
a debug-server limitation.

## Popout window (🗗) headless testing

`--debug-server` runs also log the popout server's per-launch token:

```
[popout] window server on http://127.0.0.1:<port> (loopback, token-gated)
[popout] DEBUG token=<token> (logged because --debug-server)
```

With those you can exercise the whole native-popout path without clicking:

```
# The page the msger window loads (subject + toolbar + body iframe)
curl "http://127.0.0.1:<port>/v?a=<acct>&u=<uid>&f=<folderId>&t=<token>"

# Toolbar action → daemon → popoutAction event → main window opens editable
# compose (reply/replyAll/forward/editAsNew) or flags/deletes
curl -X POST -H "content-type: application/json" -d '{"action":"reply"}' \
  "http://127.0.0.1:<port>/action?a=<acct>&u=<uid>&f=<folderId>&t=<token>"
```

Visual check without disturbing the desktop: msger render mode loads the same
URL in a hidden window and writes a PNG —

```
node --input-type=module -e "import {showMessageBox} from '@bobfrankston/msger';
await showMessageBox({url:'http://127.0.0.1:<port>/v?...&t=<token>',
  size:{width:820,height:900}, render:'c:/temp/popout.png', renderDelay:800});"
```

Success signals in the log: `[popout] action <name> a=... u=...` on the daemon
side, then `[client] compose-tick ... mode=reply ... compose visible`.
