Skip to content

Commit 47969b2

Browse files
committed
fix(threading): address self-review findings
- 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
1 parent e7c1acb commit 47969b2

3 files changed

Lines changed: 25 additions & 2 deletions

File tree

backend/jmap/jmap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func (p *Provider) FetchEmails(_ context.Context, folder string, limit, offset u
168168
Properties: []string{
169169
"id", "subject", "from", "to", "replyTo", "receivedAt",
170170
"preview", "keywords", "mailboxIds", "hasAttachment",
171-
"messageId",
171+
"messageId", "inReplyTo", "references",
172172
},
173173
})
174174

internal/threading/jwz_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,26 @@ func TestBuildSubjectFallbackGroupingForOrphans(t *testing.T) {
8686
}
8787
}
8888

89+
func TestBuildSubjectFallbackGroupsLocalePrefixes(t *testing.T) {
90+
base := time.Date(2026, 4, 28, 10, 0, 0, 0, time.UTC)
91+
threads := Build([]EmailHeader{
92+
{ID: "<a@example>", Subject: "Foo", Date: base, EmailID: "1"},
93+
{ID: "<b@example>", Subject: "SV: Foo", Date: base.Add(time.Minute), EmailID: "2"},
94+
{ID: "<c@example>", Subject: "RV: Foo", Date: base.Add(2 * time.Minute), EmailID: "3"},
95+
{ID: "<d@example>", Subject: "Antw: Foo", Date: base.Add(3 * time.Minute), EmailID: "4"},
96+
})
97+
98+
if len(threads) != 1 {
99+
t.Fatalf("got %d threads, want 1", len(threads))
100+
}
101+
if threads[0].Subject != "foo" {
102+
t.Fatalf("got subject %q, want foo", threads[0].Subject)
103+
}
104+
if threads[0].Count != 4 {
105+
t.Fatalf("got grouped count %d, want 4", threads[0].Count)
106+
}
107+
}
108+
89109
func TestBuildEmptyReferencesList(t *testing.T) {
90110
threads := Build([]EmailHeader{
91111
{ID: "<a@example>", References: nil, Subject: "Foo", Date: time.Now(), EmailID: "1"},
@@ -120,6 +140,9 @@ func TestCanonicalSubjectNormalizesReplyAndForwardPrefixes(t *testing.T) {
120140
"Fwd: FW: Foo": "foo",
121141
"AW: WG: Tr: Foo": "foo",
122142
"Reé: Resp: Foo": "foo",
143+
"SV: VS: RV: Foo": "foo",
144+
"ENC: Antw: Foo": "foo",
145+
"Odp: R: I: Foo": "foo",
123146
" Foo ": "foo",
124147
}
125148

internal/threading/subject.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"strings"
66
)
77

8-
var subjectPrefixRE = regexp.MustCompile(`(?i)^(Re|Fwd|Fw|AW|WG|Tr|Reé|Resp)\s*:\s*`)
8+
var subjectPrefixRE = regexp.MustCompile(`(?i)^(Re|Fwd|Fw|AW|WG|Tr|Reé|Resp|SV|VS|RV|ENC|Antw|Odp|R|I)\s*:\s*`)
99

1010
func canonicalSubject(s string) string {
1111
s = strings.TrimSpace(s)

0 commit comments

Comments
 (0)