napi/PLAN.md

149 lines
5.6 KiB
Markdown

# Plan: Emacs Integration for napi — Remote File-Notify via TRAMP
## Context
Fénix wants **real-time awareness in Emacs** of student deliveries on zzz (qu3v3d0.tech). The GNU Emacs manual confirms that `file-notify-add-watch` works on **remote machines** via TRAMP — it runs `inotifywait` on the remote host through SSH and streams events back in real-time. No polling.
This means: **no modifications to watcher scripts on zzz**, no reverse SSH tunnels, no Porthole/JSON-RPC. Pure Emacs, using built-in capabilities + a dedicated daemon.
## Architecture
```
emacs --daemon=napi (local)
├── TRAMP SSH → zzz:/home/*/python/ (ASIR1 — 21 dirs)
│ └── inotifywait running on zzz (auto-started by TRAMP)
├── TRAMP SSH → zzz:/home/*/html/ (DDAW2 — 19 dirs)
│ └── inotifywait running on zzz (auto-started by TRAMP)
└── On file event → forward notification to `water` daemon
├── Desktop notification (D-Bus)
├── Sound alert (paplay)
├── *napi-log* buffer (persistent log)
└── Minibuffer message
emacsclient --socket-name=napi --eval '(napi-dashboard)' ← interact
emacsclient --socket-name=water --eval '(napi-log)' ← view in main UI
```
## Prerequisites
- `inotify-tools` installed on zzz (provides `inotifywait`) — **already present** (watchers use it)
- SSH key access from local → zzz — **already working** (`ssh fenix@qu3v3d0.tech`)
- Emacs 30.1 on local — **confirmed**
## Implementation Steps
### Step 1: Create `~/napi/emacs/napi.el`
The core elisp package. ~200 lines. Key components:
| Component | Purpose |
|:----------|:--------|
| `napi-watch-start` | Sets up TRAMP file-notify watches on all student dirs |
| `napi-watch-stop` | Removes all watches cleanly |
| `napi-notify` | Handles a file event: log + desktop notification + sound |
| `napi-log` | Interactive: open `*napi-log*` buffer |
| `napi-open-student` | Interactive: completing-read → open student dir/notas.md |
| `napi-dashboard` | Interactive: overview of all students + last delivery |
| `C-c n` prefix | Keybindings for all interactive commands |
**Student name maps** (username → full name) embedded in elisp, mirroring watcher scripts.
**TRAMP watch setup** — the core innovation:
```elisp
;; For each ASIR1 student:
(file-notify-add-watch
"/ssh:fenix@qu3v3d0.tech:/home/jara/python/"
'(change)
#'napi--handle-event)
;; 40 watches total (21 ASIR1 + 19 DDAW2)
;; Each spawns a persistent inotifywait on zzz via SSH
```
**Event handler** filters noise (Thumbs.db, __pycache__, .tmp, etc.) and forwards clean notifications to `water` daemon via:
```elisp
(shell-command
"emacsclient --socket-name=water --eval '(napi--show-notification ...)' &")
```
**Reconnection**: TRAMP sends a `stopped` event when SSH drops. The handler auto-re-establishes the watch after a delay.
### Step 2: Create systemd user unit for `napi` daemon
File: `~/.config/systemd/user/emacs-napi.service`
```ini
[Unit]
Description=Emacs daemon for napi student monitoring
After=network-online.target
Wants=network-online.target
[Service]
Type=forking
ExecStart=/usr/bin/emacs --daemon=napi -l ~/napi/emacs/napi-init.el
ExecStop=/usr/bin/emacsclient --socket-name=napi --eval "(kill-emacs)"
Restart=on-failure
RestartSec=30
[Install]
WantedBy=default.target
```
### Step 3: Create `~/napi/emacs/napi-init.el`
Minimal init file for the `napi` daemon (not the full `~/.emacs.d/init.el`):
```elisp
;; Loaded ONLY by emacs --daemon=napi
(load "~/napi/emacs/napi.el")
(napi-watch-start)
(message "napi: watching %d student directories on zzz" napi--watch-count)
```
### Step 4: Also load `napi.el` in `water` daemon
Add to `~/.emacs.d/init.el` (near line 1478, following `funciones-art.el` pattern):
```elisp
(load "~/napi/emacs/napi.el")
```
This gives the `water` daemon the `napi-log`, `napi-dashboard`, `napi-open-student` commands and the `C-c n` keybindings — but **no watches** (those run in the `napi` daemon only).
### Step 5: Verify `inotifywait` on zzz
```bash
ssh fenix@qu3v3d0.tech "which inotifywait && inotifywait --help | head -1"
```
## Files to Create/Modify
| File | Action | Location |
|:-----|:-------|:---------|
| `emacs/napi.el` | **CREATE** | `~/napi/emacs/napi.el` |
| `emacs/napi-init.el` | **CREATE** | `~/napi/emacs/napi-init.el` |
| `emacs-napi.service` | **CREATE** | `~/.config/systemd/user/emacs-napi.service` |
| `init.el` | **MODIFY** (1 line) | `~/.emacs.d/init.el` |
**No modifications to zzz** — no watcher changes, no new SSH keys, no reverse tunnels.
## Testing
1. **Unit test** — load `napi.el` in `water`, call `(napi-notify ...)` manually
2. **TRAMP watch test** — single watch on one student dir, touch a file on zzz
3. **Full integration** — start `napi` daemon, simulate SFTP upload as student
4. **Stress test** — confirm 40 concurrent TRAMP watches are stable
5. **Reconnection** — kill SSH, verify watch auto-restores
6. **UI test**`C-c n l` (log), `C-c n s` (student), `C-c n d` (dashboard)
## Risk: 40 Concurrent SSH/inotifywait Processes
Each `file-notify-add-watch` via TRAMP spawns a separate `inotifywait` process on zzz. 40 watches = 40 persistent SSH channels + 40 inotifywait processes.
**Mitigation**: TRAMP reuses SSH connections (ControlMaster). So it's 1 SSH connection + 40 remote processes. The zzz server's `inotify` limit (`fs.inotify.max_user_watches`) is typically 65536 — 40 is nothing.
**Fallback**: If 40 watches proves unstable, consolidate to 2 watches on parent dirs (`/home/` with recursive) + filter events by path. Or switch to the emacsclient-via-SSH approach (see FINDINGS.md).