Future work: backlog
Ranked by impact over effort, collected from AGENTS.md requirements not yet built, spec decision records, and review residuals. Effort is S/M/L relative to the send-dialogue milestone. AGENTS.md is normative; a spec and plan are prerequisites before implementation per the project’s workflow.
v1 release target
The release cut: notmutt covers the author’s day-to-day mail without the current escape hatches. Two gates define v1; everything else in the backlog ranks after them, and both gates still need a spec and a plan before code.
- Full gpg (PGP) crypto: decrypt, verify, sign, and encrypt via the system gpg CLI with
--status-fdparsing and gpg-agent + external pinentry (Tier 1 item 1). Decrypt and signing are the named start; encryption and verification ride the same provider. S/MIME sign/encrypt/decrypt is explicitly NOT a v1 gate - it may land now or later. - Better thread markers in the index: the tree that renders today (tree run, collapse/expand, overflow windowing, the opened-message mark) stops being enough on a long thread. v1 wants state to read at the thread and subtree level - a message’s read/answered/flagged/replied state visible on the tree run, not only the top row’s flag cell - and a collapsed thread to carry that state honestly on the one line that stands for the conversation.
Tier 1: foundational, build next
1. Crypto providers: PGP via gpg, S/MIME in-process (R10) - effort M-L
The PGP half (sign/encrypt/decrypt/verify via gpg) is the v1 crypto gate - see the v1 release target above; the S/MIME half is not on the v1 bar and may land now or later with the same provider interface.
No sign/encrypt/decrypt on the send or read path yet. PGP via the system gpg CLI with --status-fd parsing (aerc’s gpgbin pattern - the agent/passphrase machinery is why it is a subprocess), S/MIME in-process (go.mozilla.org/pkcs7
-
stdlib x509, emailProtection EKU enforced), gpg-agent + external pinentry with TUI suspend/resume (the only passphrase path; no loopback mode - Go cannot zero secrets).
- Sign/encrypt is a transform stage between go-message assembly and the send job (assemble -> sign/encrypt per dialogue flags -> fcc -> send).
- Decrypt/verify is an async job on the read path; the pager renders body and signature status.
- Key selection is a selector dialogue (R4 machinery) fed by
gpg --list-secret-keys --with-colons. - go-pgpmail builds on go-message for the PGP transform; pkcs7 + x509 in process for S/MIME (both verified in the mail-library decision record).
Impact: the client becomes usable for signed/encrypted mail - table stakes for this user’s workflow. Pointers: AGENTS.md R10, references/neomutt/ncrypt/*, references/neomutt/smime/smime.c, references/aerc/lib/crypto/gpg/gpgbin.
Tier 2: schedule when a slot opens
2. Fix the redraw_test harness race, enable -race in CI - effort S
TestCursorMovePartialRepaint (src/tui/redraw_test.go) races the vendored renderer goroutine against the test goroutine under -race; verified pre-existing at the milestone base. The fix is a harness-side synchronization (join or poll the renderer), then -race becomes the CI standard without a skip list.
Impact: removes the one known red flag; CI runs race-clean like the rest of the suite. Pointers: src/tui/redraw_test.go (fake_screen_test.go - the renderer goroutine’s SetContent vs the test goroutine’s Get, re-verified 2026-08-19 under -race: TestLoopCursorMoveRepaints and TestFrameChromeSurvivesRefresh both race).
3. Staged-tag persistence (R14) - effort M
Staged tag ops and the undo buffer are session-local today; persistence is explicit future work in R14. The design is spec’d: a durable, multi-writer buffer (one file per op, ULID ids, crash-isolated) that MCP/CLI/Lua/TUI all append to and the session picks up - see docs/staged-ops.md.
Impact: undo becomes durable; mis-taps stop being permanent after a restart; the MCP server gains a stage-only write surface without touching notmuch. Pointers: AGENTS.md R14, docs/staged-ops.md, src/tui/model.go stage/apply/undo.
4. Send retry reopens the compose dialogue with the failed message (R4) - effort S-M
neomutt’s bg_send_retry seed: a failed send retains the Email and re-opens the compose dialog with the failed message. The PhaseFailed path exists (e retries in place); the missing piece is reopening with the failed state and its captured output shown.
Impact: send failures stop being lossy - the user edits and re-sends the actual failed message. Pointers: references/neomutt/send (async_send branch, bg_send_retry), src/app/send.go, src/compose/state.go PhaseFailed.
5. Address cache as a filter side effect (R2) - effort S-M
The reference pipeline’s remaining side effect: the address cache for query completion (notifications landed with the filter job’s completion event, src/app/notify_beeep.go). It subscribes to the same event.
Impact: query completion arrives with the filter pipeline. Pointers: AGENTS.md R2, references/muttrc/afew/config (the reference side effects).
6. Markdown compose, HTML send (R4) - effort M
Compose the body in markdown and emit multipart/alternative with an HTML part; code blocks get syntax highlighting. Sits at the same assemble stage the signer will use (assemble -> transform -> fcc -> send), so the two share the wiring. goldmark + chroma for parse/highlight - large, established, audited (the R7 supply-chain bar).
Impact: the compose path gains real formatting without an editor change. Pointers: AGENTS.md R4, src/app/ai_draft.go (the assemble-stage shape), the go-message decision record.
Tier 3: later, smaller, or gated
- DBus dark/light sync (R12) - effort S-M, build-tag-gated (
dbus), godbus dependency only in that build. - Send-epoch stamping on SendResult - effort S. Closes the one accepted residual from the M2 snapshot fix: a channel-delivered failure racing a retry dispatch can re-apply the stale failure for one Update.
- UX nits accepted in the send milestone - effort S each. formIdx not clamped per tab; compose frame overflows below height 11;
openedset / busopenLastgrow per session. - Extractable TUI library (R5) - effort L, architectural. When the TUI stabilizes; the core has no UI code by design, so extraction is packaging.
- MIME cache compression knob - effort S, only after measuring (R13: “compress first” is explicitly a future knob, not a requirement).
- PDF export niceties (the pager E key) - effort S each. A second export of the same message overwrites the identical YYYYMMDD-
.pdf name (a numeric collision suffix is the fix); the weasyprint binary path is hardcoded with the required --no-http-redirects flag (a config override is the fix); the save prompt picks only the destination folder, the generated filename resolves app-side and is not visible before commit. Pointers: src/app/export.go.
Landed (removed from the ranks)
Implemented since this backlog was drafted; kept here as the audit trail.
-
Dark-mode HTML rendering -
[html] dark-mode = auto|on|off(auto follows the theme variant): mail backgrounds reflect onto the theme’s normal bg by an exact per-channel isometry (white lands exactly on the theme bg, pairwise distances preserved), text colors invert lightness keeping their hue with a WCAG guard, and a dark-declared mail passes through the luma gate unchanged. Spec: docs/superpowers/specs/2026-09-01-dark-mode-html-design.md. Pointers:src/lib/html(AdaptBG/AdaptFG/IsLight),src/mail/html.go, the R11 theme store. - Filter engine with exclusive tag groups (R2) -
src/filter/(filter.go, mover.go), the [filter] job with DRY-RUN as a first-class mode, the [tag-groups] config, derived folder rules from account + preset data. The muttrc post-new hook and afew are reference shapes only. Algorithmic filters (bayes, DKIM) remain future plug-ins behind the same per-message contract. - MIME cache (R13) -
src/cache/: bbolt, keyed by (path, size, mtime) to attachment metadata, hit-only in steady state. - Fuzz targets on the mail-parsing boundary -
src/mail/fuzz_test.go. - Theme onedark port (R11) -
defaultTheme/defaultPalettein src/config/config.go (the muttrc onedark reference port); the base16 palette converter stays a future task (R11). - Lua bindings (R8) - the plugin layer (
src/app/lua_plugin.go, lua_action.go) and the build-tag-gated AI provider layer (src/app/ai/) shipped on the neovim model; TOML stays the config language. - Preview popup vs open-reads distinction - the p key opens a popup preview over the index (src/tui/preview.go); enter promotes it to a full open.
- Whole-fill dirty batch on refresh - the phase-2 emit loop runs inside one BeginMerge/EndMerge pair (src/app/refresh.go); the measured 17.7x trade is design-decisions record 15.
- cgo binding re-evaluation - resolved: the batched ThreadsWalk closed the gap (~1.6s vs the CLI’s 1.5s full walk) and cgo IS the runtime backend; the CLI survives behind
-tags clias the escape hatch (AGENTS.md R1, decision record 3). - Notifications as a filter side effect -
src/app/notify_beeep.go, wired to the filter job’s completion event (filterjob.go); the address cache half remains Tier 2 item 5. - Lua IPC channel (R8) -
notmutt lua '<chunk>'relays a chunk to a live session over a same-user unix socket (0700 runtime dir, SO_PEERCRED on linux), runs it on the R8 VM with the metadata ctx surface, and returns the print capture; the reply stays off the TUI bus. Spec: docs/superpowers/specs/2026-09-03-lua-ipc-design.md. Pointers:src/app/lua_ipc.go(client),src/app/lua_ipc_server.go, the sharedrunLuaChunkrunner insrc/app/lua_action.go. - HubSpot follow-up workflow - a
lua && crm-gated queue over the index (Q): pulls contacts scanned into HubSpot, briefs one from its CRM record plus website and web/news research over the referenced[ai]provider, then drafts a personalized follow-up into a prefilled compose- nothing sends automatically. Send (or dismiss) writes the processed marker back to HubSpot, the source of truth, so the next pull skips it. The core is vendor-neutral in
src/lib/crmbehind aClientinterface, with HubSpot the concretesrc/lib/crm/hubspotimplementation; the app side is a thin adapter (src/app/crm_engine.go, stub-split on!lua || !crm) feeding the tui surface. Spec: docs/superpowers/specs/2026-09-03-hubspot-workflow-design.md. Pointers:src/lib/crm(workflow.go, briefing.go, research.go),src/lib/crm/hubspot/client.go,src/app/crm_engine.go,src/tui/crm.go.
- nothing sends automatically. Send (or dismiss) writes the processed marker back to HubSpot, the source of truth, so the next pull skips it. The core is vendor-neutral in
Process notes
- Every Tier 1/2 item needs a spec (docs/superpowers/specs/) then a plan (docs/superpowers/plans/) before implementation, per the project workflow.
src/cache/*,src/app/cachejob.go, and (unless an exception is granted)src/core/view.gostay READ-ONLY for AI agents; items touching the cache need an explicit approval.- The benchmark harness from the lag investigation (real tea.Program, end-to- end per-press cost) should be kept as a reusable perf gate when the TUI gains features that touch the render path.