Skip to content

feat(threading): JWZ conversation view#1188

Open
mvanhorn wants to merge 4 commits intofloatpane:masterfrom
mvanhorn:feature/509-threaded-conversation-view
Open

feat(threading): JWZ conversation view#1188
mvanhorn wants to merge 4 commits intofloatpane:masterfrom
mvanhorn:feature/509-threaded-conversation-view

Conversation

@mvanhorn
Copy link
Copy Markdown
Contributor

@mvanhorn mvanhorn commented Apr 28, 2026

What?

  • Add internal/threading package implementing the Jamie Zawinski threading algorithm against Message-ID / In-Reply-To / References headers, with subject-fallback grouping for orphans
  • Carry MessageID, InReplyTo, and References through fetcher, the IMAP/JMAP/POP3 backends, the on-disk email cache, the daemon RPC types, and the inbox model so threading works against cached headers without server round-trips
  • Inbox renders threaded mode with one row per thread root, showing the count and last-sender; Enter toggles expand/collapse; expanded children render indented with markers
  • T keybind toggles flat vs threaded for the current folder; the per-folder mode persists via folder_cache.go
  • Subject canonicalization handles Re:, Fwd:, Fw:, AW:, WG:, Tr: (lowercased, stripped repeatedly so Re: Re: Foo -> foo)
  • Tests cover: 3-message chains, forks, missing-parent placeholders, subject-fallback grouping, empty References, deterministic ordering across repeated Build() calls
  • VHS demo (screenshots/cmd/threading_demo + screenshots/threading_demo.tape): flat (5 emails) → threaded (3 rows with (3) count on the root) → expanded (5 rows with on children) → collapsed → flat

demo

Simulated demo (Remotion) — matcha theme, scripted UI, not a live capture.

Why?

This is the maintainer's spec from issue #509 and the more detailed #1130:

"Group emails into conversation threads using In-Reply-To and References headers (RFC 5322). Display threads as collapsible groups in the inbox, showing the latest message and a count of messages in the thread."

"Build threads with the Jamie Zawinski algorithm (the one Thunderbird uses) so we don't have to rely on X-GM-THRID. Threading should be done client-side from the cached header set so it works across providers."

The framing in #1130 is the user-visible argument: "Showing each reply as a separate inbox row is how Mutt looked in 1999. Modern terminal clients (aerc, himalaya) all thread."

The launch threads on r/coolgithubprojects + r/CLI + r/selfhosted (cumulative 161 upvotes, 32 comments) consistently flagged conversation grouping as the gap users notice first when comparing matcha to gmail/superhuman/aerc.

Notes

  • Touches main.go (alongside in-flight chore: refactor main.go #845 and feat: offline mode initial commit #686). Conflicts should be mechanical - the threading wiring in main.go is small (cache-conversion paths to carry References/InReplyTo). Happy to rebase or stack PRs.
  • Ordering ties in JWZ are broken on EmailID so Build() is deterministic across runs.
  • The implementation deliberately avoids X-GM-THRID and IMAP THREAD (RFC 5256) per the spec - threading is purely client-side over cached envelope data.
  • Out of scope: per-thread mark-as-read propagation rules (kept current behavior); thread-aware archive/delete (uses single-message semantics for now).

Closes #509. Addresses #1130.

This contribution was developed with AI assistance.

Adds an internal/threading package implementing the Jamie Zawinski
threading algorithm (Message-ID + In-Reply-To + References) with
subject-fallback grouping for orphans. The inbox renders one row per
thread root with a count and last sender; pressing Enter toggles
expand/collapse; the per-folder flat-vs-threaded mode persists via
folder_cache.

The MessageID/InReplyTo/References metadata is now carried through
fetcher and the IMAP/JMAP/POP3 backends, the on-disk email cache, the
daemon RPC types, and the inbox model so threading works against
cached headers without server round-trips. Per the maintainer's spec
in floatpane#509 and floatpane#1130: client-side, provider-agnostic, JWZ rather than
X-GM-THRID, deterministic ordering.

- internal/threading/jwz.go: ThreadNode, Thread, Build()
- internal/threading/subject.go: canonicalSubject()
- internal/threading/jwz_test.go: chains, forks, missing parents,
  subject-fallback grouping, deterministic ordering
- tui/inbox.go: threaded mode rendering + 'T' toggle + expand/collapse
- config/folder_cache.go: persist threaded toggle per folder
- backend/{imap,jmap,pop3}: emit MessageID/InReplyTo/References
- screenshots/cmd/threading_demo: VHS helper

Closes floatpane#509. Addresses floatpane#1130.
threading_demo.tape captures: flat (5 emails) -> threaded (3 rows with
'(3)' count on the root) -> expanded thread (5 rows with indented '↪'
markers on the children) -> collapsed -> back to flat. Rendered to
public/assets/threading_demo.gif.
- backend/jmap: Email/get now requests inReplyTo and references
  alongside messageId so JMAP-backed accounts thread by real
  References/In-Reply-To rather than falling through to subject grouping
- internal/threading/subject: add Swedish/Norwegian/Danish (SV),
  Finnish (VS), Spanish (RV), Portuguese (ENC), Dutch (Antw), Polish
  (Odp), and Italian (R/I) reply/forward prefixes
- internal/threading/jwz_test: regression coverage for SV/RV/Antw
  subject-fallback grouping
@mvanhorn mvanhorn requested a review from a team as a code owner April 28, 2026 16:04
@github-actions github-actions Bot added ci CI / build pipeline enhancement New feature or request labels Apr 28, 2026
public/assets/threading-remotion-demo.gif shows the flat inbox (5 emails),
T toggle to threaded mode (3 rows with the (3) count on the JWZ-grouped
root), Enter to expand the thread tree (showing the original + 2 replies
indented with ↪ markers). Rendered by Remotion at 1280x720, encoded via
--scale=0.6 --every-nth-frame=3.
@andrinoff
Copy link
Copy Markdown
Member

the PR has conflicts with your previous changes in #1186

@floatpanebot floatpanebot added area/build Build system / Makefile / packaging area/cli CLI flags / commands area/daemon Daemon / RPC area/fetcher IMAP fetch / IDLE / search area/sender SMTP send path area/theme Theming / colors area/tui Terminal UI / view layer labels May 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/build Build system / Makefile / packaging area/cli CLI flags / commands area/daemon Daemon / RPC area/fetcher IMAP fetch / IDLE / search area/sender SMTP send path area/theme Theming / colors area/tui Terminal UI / view layer ci CI / build pipeline enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FEAT: Conversation/thread view

3 participants