# TODO Deferred work. Context: Officer is collapsing from multi-tenant / open-source-ready to a **single-user platform**. Treat multi-tenant indirection as accidental complexity, not a requirement. ## Single-user cleanup - [ ] **Remove dead `username` plumbing.** `pi-bridge.spawnPi` no longer uses `username` — it always runs as the service user. But the handlers still compute `toShellUsername(...)` and thread it through `sendAndAwait` → `spawnOptions`, where nothing reads it. `send-claude-code` declares `username` (lines 8, 36) without using it. Typechecks clean today (excess-property checks don't fire on variables), so it silently *looks* load-bearing. Touches: `channels/{whatsapp,telegram,discord}/handler.ts`, `channels/send-and-await.ts`, `channels/send-claude-code.ts`. After this, `toShellUsername` has one caller left (`provision.ts` → `generateClaudeSettings`) and may be inlinable. - [ ] **Delete or gut `scripts/provision-existing-users.sh`.** Line ~100 runs `sudo useradd -d "$HOME_DIR" -s /bin/zsh -M "$shell_user"`. This is what created the four vestigial Unix accounts (`andrepadez`, `john-wick`, `fedra`, `miguelbenoliel`) that cluttered the lightdm greeter. Nothing in the server creates Unix users; if this script runs again they come back. - [ ] **Collapse the rest of the multi-tenant machinery.** Candidates, in rough order of payoff: roles (`Super Admin`/`Member`), the sandboxed-vs-unsandboxed path split, per-email home dirs under `dev-data/{email}/home`, per-user server state (dock, user apps, AppRegistry keyed by email), and auth (passkeys-per-origin, JWT signin, unmounted `PasskeyGate`). ## Email - [x] **Gmail-style search operators** (done 2026-07-24, `0e5b91e`). `parseEmailQuery` + `searchEmails` in `api/email/email-db.ts` parse: `from:`/`to:`/`subject:`/`body:` → FTS5 column filters; `has:attachment`, `is:unread`/`is:read`, `label:X`, `before:`/`after:YYYY-MM-DD` → SQL `WHERE` on `emails`; quoted values → exact phrases; free text → prefix-AND full-text; unknown `op:val` falls back to free text. Structured-filters-only queries skip the FTS join. Validated on the real 18k-mail DB. Search box placeholder hints at operators. - [x] **`OR`** (done 2026-07-24, `85d38fb`). Query splits on top-level uppercase `OR` into branches; each branch is a self-contained condition (`id IN (FTS subquery)` + its SQL filters) and branches are OR'd — so OR works across full-text *and* structured filters. Verified on the 18k DB (`from:github OR from:deepgram` = 90+7 = 97 exactly). - [ ] **Remaining:** parenthesised grouping (nesting `(a OR b) c`), `in:sent/inbox/spam/trash` (folder scope), and relative dates (`newer_than:7d`). Grouping needs a recursive parser. - [ ] **Group search results into threads too.** Folder views group by conversation (`ee11c94`), but `/email/search` still returns one row per message. Apply the same `thread_id` collapse to the FTS result set so search matches Gmail's grouped results. - [x] **Conversation threading** (done 2026-07-24, `ee11c94`). `thread_id` column on `emails`: header-based for new mail (`id` = `sha1(Message-Id)`, so `References[0]` hashes to the root's id — `computeThreadId` in `email-db.ts`), subject+counterpart backfill for already-synced mail. `/messages` collapses to one row per thread (window fn) with count/unread; `/thread/:id` + `/thread/:id/read`; reader renders a collapsible stack. Verified on synthetic + 18k real DB. - [ ] **Upgrade old mail to exact threading.** One-time full re-fetch to capture `References` for already-synced mail — replaces the subject+counterpart fallback (which can over-merge recurring same-subject mail from one sender). Heavy/network-bound; opt-in. - [ ] **Store the RFC `Message-Id` header** on ingest so replies can set a real `In-Reply-To` (currently reply threading leans on subject/participants; `id` is a local hash, not the header). - [x] **Compose/reply/send** (done 2026-07-24). Gmail SMTP send with contacts autocomplete, rich contenteditable body (inline images at the caret via paste/drag-drop, Bcc), single close button. - [x] **`busy_timeout` on the email db** (done 2026-07-24, `9527e0b`). API + email sidecar share `emails.db`; wait out a concurrent writer instead of 500-ing with "database is locked". ## Known bugs - [ ] **`bootstrap.ts` runs `npm install -g` for Pi on every boot.** `findPiPackageDir` checks stale paths and an outdated package name, so `installPi` always fires and floods the logs with `EEXIST` noise. Should detect `@earendil-works/pi-coding-agent` at the real npm prefix. - [ ] **VNC mirror can orphan/duplicate x11vnc across sidecar restarts.** `vnc-manager.startSession` tracks the running mirror in module-level state and skips spawning only if that pid is alive. On a vnc-sidecar restart the state resets to null while the old x11vnc keeps running orphaned; the next `startSession` spawns a second one. Worse, `waitForPort` treats *any* listener on 5900 as success, so the platform can believe it started a mirror that is actually a stale process — desyncing into multiple contending x11vnc instances on `:0` (suspected cause of click-lag on 2026-07-16, cleared by killing all but one). Fix: before spawning, kill any existing `x11vnc -display :0` / free port 5900 (an `ExecStartPre`-style cleanup, like the old officer-vnc.service unit did with `vncserver -kill`). ## Infra (alpha) - [ ] **Delete `/etc/systemd/system/officer-vnc.service`.** Hand-installed unit (not in this repo) that ran `vncserver :1 -geometry 1920x1080 -localhost yes -fg` with `Restart=on-failure`, enabled at boot — it kept a whole parallel XFCE session alive on `:1` (283 MB, 166 tasks) independently of the platform, and silently respawned it whenever the display was killed. Obsolete now that the Desktop panel mirrors `:0` via x11vnc. Disabled 2026-07-15 (`systemctl disable --now`), but the unit file is still on disk. Nothing in the codebase recreates it and nothing documents it, so delete the file rather than leave a mystery service one `systemctl enable` away from returning. - [ ] **`ufw` blocks port 9010 on the LAN.** Default deny incoming; only `22/tcp`, `80/tcp`, `443/tcp`, and everything on `tailscale0` are allowed — so `http://192.168.47.196:9010` is unreachable from the LAN while localhost and Tailscale work. Fix: `sudo ufw allow from 192.168.47.0/24 to any port 9010 proto tcp` ## Backburner - [ ] **Music tagging feature** (`/music`, Mp3tag-inspired). v1 was scoped as a two-panel workspace — file browser left, table right, single "Load" context-menu item flattening audio files into the table. Discarded before completion; would need `GET /file-browser/flatten-audio`, a `useFilesAPI.flattenAudio` method, and the Music app panel rebuilt from scratch.