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.

  1. Full gpg (PGP) crypto: decrypt, verify, sign, and encrypt via the system gpg CLI with --status-fd parsing 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.
  2. 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; opened set / bus openLast grow 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/defaultPalette in 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 cli as 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 shared runLuaChunk runner in src/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/crm behind a Client interface, with HubSpot the concrete src/lib/crm/hubspot implementation; 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.

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.go stay 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.