# 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).