First pass of the documentation audit. Every doc was read against what the code actually does now; this commit fixes the ones worth keeping and deletes the ones that were only describing a past. Corrected: - CLAUDE.md — said seven WebSocket providers (there are eight, and terminal/vault are byte relays now, not translating bridges), listed channels/ as "Telegram / WhatsApp / Discord bridges" (they are gone; what remains is how /chat drives an agent turn), missed officer-wallet in the PM2 list and notify/ in the layout, and described the per-account email SQLite stores without saying they are the sidecar's and that nothing in the platform opens them. Further Reading pointed at four files that no longer exist and missed the four newest. - docs/working-on-officer.md — PM2 list was four sidecars short, and it still explained the officer-claude rename as news. Replaced with the thing a reader actually needs: which process to restart for which change, and why restarting officer no longer costs you a terminal or an agent session. - TODO.md — the "dead username plumbing" item was mostly resolved by deleting the channels, and two email items pointed at api/email/email-db.ts, which is sidecar/email/store.ts now. - AGENTS.md — trailing paragraph listed the design notes being deleted here. - MUSIC_API.md — playlists were entirely undocumented: seven endpoints the phone app has no reference for. Added from the sidecar's own contract. - docs/jobs-unification.md — phases 1-3 shipped, so it now says so at the top. Phase 4 (push notifications) is the only reason the file still exists, and email sync is explicitly no longer part of it. Deleted, all superseded rather than merely old: - PHONE_APP.md — a February plan for apps that now exist, with their own repo and README. - MARKETING_WEBSITE.md — a plan for a site this repo does not contain. - SECURITY_AUDIT.md + SECURITY_FIXES.md — a February audit of a codebase since restructured; it still cites queue/handlers, which is now empty. - docs/DOCKERIZATION_PLAN.md — cites pty-sidecar, whatsapp and projects, all deleted. - SETUP_GUIDE.md — documents systemd units and setup scripts replaced by PM2 and `bun setup`. Not harmless: /etc/systemd/system/officer-pty-sidecar.service is still enabled on this host, pointing at a `monorepo/` directory that no longer exists, and has been failing to start ever since. That guide is how it got there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
140 lines
9.7 KiB
Markdown
140 lines
9.7 KiB
Markdown
# 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.** Mostly resolved by deleting the chat channels — the
|
|
handlers and `send-and-await.ts` that threaded `toShellUsername(...)` through to nothing are gone.
|
|
What remains: `send-claude-code.ts` still declares `username` without using it, and
|
|
`toShellUsername` has one real caller left (`provision.ts` → `generateClaudeSettings`), so it may
|
|
be inlinable.
|
|
|
|
- [x] **Delete or gut `scripts/provision-existing-users.sh`.** Done — deleted the script (it ran
|
|
`sudo useradd …`, the source of the vestigial `andrepadez`/`john-wick`/`fedra`/`miguelbenoliel`
|
|
Unix accounts). Also dropped the dead per-user VNC desktop provisioning (`provisionVncEnv`, the
|
|
`startxfce4` xstartup) from `provision.ts` — the mirror self-provisions its passwd in
|
|
`vnc-manager.ts` — and the Pi `.pi/agent/sessions` seed.
|
|
|
|
- [ ] **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 `sidecar/email/store.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 `sidecar/email/store.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".
|
|
|
|
- [ ] **Multiple views in the Email screen (tabs).** The screen grows beyond the current mailbox into
|
|
several switchable views:
|
|
- [ ] **Sender/domain management view.** Left panel: controls to group mail **by sender** or **by
|
|
domain** (room for more grouping axes later). From a group, run bulk actions on that
|
|
sender/domain: **unsubscribe + mark irrelevant**, **delete all mail** from it, **block/ignore
|
|
future incoming**, etc. Think "inbox cleanup / triage" — operate on a whole sender at once.
|
|
- Needs: aggregate query (count/size per sender + per domain), a block/ignore list the
|
|
sync/IDLE path honors on incoming, an unsubscribe action (List-Unsubscribe header / link),
|
|
and bulk delete over a sender/domain.
|
|
- [ ] **Chat view.** The existing AI email-assistant flow — likely stays roughly as-is, just lives
|
|
as one of the tabs (main view).
|
|
- Open question: tabs vs. some other view switcher; which view is default.
|
|
|
|
## Transmission
|
|
|
|
Phase 1 (parity with `_references/transmission-web`) shipped 2026-07-30: the `officer-transmission`
|
|
sidecar plus `/transmission` (torrents / stats / settings). Everything below is **phase 2** — none of
|
|
it exists in the reference app, so none of it was in scope for parity.
|
|
|
|
### Probably needed regardless
|
|
|
|
- [ ] **Resizable detail pane.** `TorrentsView` pins it at `DETAIL_HEIGHT = '45%'`, which is a guess.
|
|
Either a drag handle or a persisted height in `useLocalStorageState`, alongside the column set.
|
|
- [ ] **Revisit `DEFAULT_VISIBLE_COLUMNS`.** Eleven of thirty, chosen before ever seeing the table
|
|
rendered against the real 43-torrent library. Likely wrong in both directions.
|
|
- [ ] **Files tab on huge torrents.** `detail/FilesTab.tsx` builds the whole tree and renders every
|
|
node — no virtualisation. Fine at 14 files; unknown at a few thousand. If it stalls, the fix is
|
|
`useVirtualizer` over a flattened visible-node list, the same shape as `TorrentTable`.
|
|
|
|
### Ideas, unprioritised
|
|
|
|
- [ ] **Container→host path mapping.** The daemon reports its own namespace (`/downloads/complete`),
|
|
which is not a path on this host. A mapping table (`/downloads` → `~/hdds/08_TB_01/Torrents`,
|
|
`/downloadsTV` → `14_TB_02/Torrents`) would make locations clickable through to `/files` and
|
|
make "Set location" offer real destinations. Deliberately dropped from phase 1: the reference
|
|
app has no such feature, so it would have been config nothing read.
|
|
- [ ] **Push instead of poll.** Transmission RPC has no push channel, so `useTransmissionData` polls
|
|
every 5s. The sidecar could poll once and fan out over a WebSocket, which is both cheaper and
|
|
what every other live surface in the platform already does.
|
|
- [ ] **Cross-surface links.** Soulseek and `download-media` both land files in the same library;
|
|
Transmission is the third door to it. Worth a think about whether they should know about each
|
|
other at all.
|
|
|
|
## 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.
|