Files
platform/TODO.md
T
pastilhasandClaude Opus 5 387664964c add a transmission sidecar and ui
officer-transmission is a new pm2 peer that owns the transmission rpc
connection and exposes a curated /_officer/* contract instead of proxying
raw rpc. it absorbs the three quirks callers otherwise have to know about:
the 409 x-transmission-session-id handshake, failures returned as
{"result": "..."} inside http 200, and basic auth where an empty username
must send no header at all.

/transmission is the ui, on the workspace/panel framework: a filter nav and
three sections (torrents, stats, settings). the torrent list is virtualised
with 30 available columns, multi-select, and a right-click menu; the detail
pane covers general, files as a real tree, peers and trackers. filters and
the open torrent live in the url, so a filtered view is a link.

phase 1 goal was parity with _references/transmission-web. follow-up work is
recorded in TODO.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 17:09:19 +00:00

10 KiB

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 sendAndAwaitspawnOptions, 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.tsgenerateClaudeSettings) and may be inlinable.

  • 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

  • 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.

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

  • 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, /downloadsTV14_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.