From 14e8a62f7614ee7e98c8c1576c7c7b003ef0f0f2 Mon Sep 17 00:00:00 2001 From: theorose49 Date: Tue, 30 Jun 2026 14:01:33 +0900 Subject: [PATCH] =?UTF-8?q?fix(mail):=20=EB=8B=B5=EC=9E=A5=EB=8F=84=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=97=90=20=EA=B0=81=EA=B0=81=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C(=EC=8A=A4=EB=A0=88=EB=93=9C=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=EA=B0=80=EC=8B=9C=EC=84=B1)=20+=20=EB=8F=99?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EC=A7=84=ED=96=89=20=EC=8B=A0=ED=98=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 가시성을 메일 단위 → '내가 참여한 스레드 전체'로: 한 스레드에서 한 통이라도 from/to/cc면 그 스레드의 원문·답장 모두 노출(각각의 행). 무관 스레드는 숨김. - isSyncing(): 백필/수동 동기화 진행 중이면 mails 응답에 syncing=true → 프론트 폴링 Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/httpapi/handlers_projects.go | 21 +++++++++++++++++++-- internal/httpapi/mail_sync.go | 6 ++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/internal/httpapi/handlers_projects.go b/internal/httpapi/handlers_projects.go index 5e860fe..ea428af 100644 --- a/internal/httpapi/handlers_projects.go +++ b/internal/httpapi/handlers_projects.go @@ -603,6 +603,9 @@ func (s *Server) handleListProjectMails(w http.ResponseWriter, r *http.Request) if st.LastError != "" { resp["error"] = st.LastError } + if isSyncing(id) { + resp["syncing"] = true + } // First ever view → kick off full backfill in the background (only if mail // integration is configured). Already-stored mail is shown regardless. if !synced && s.mail.Enabled() { @@ -619,10 +622,24 @@ func (s *Server) handleListProjectMails(w http.ResponseWriter, r *http.Request) notes[n.MessageID] = n } me := s.email(r) + // 가시성: 내가 참여한 "스레드 전체"를 노출(원문 + 답장 각각). 한 스레드에서 + // 한 통이라도 내가 from/to/cc면 그 스레드의 모든 메일이 보인다. + myThreads := map[string]bool{} + involvedSolo := map[string]bool{} + for _, row := range rows { + m := mailsync.Message{From: row.FromAddr, To: row.ToAddr, Cc: row.CcAddr} + if m.Involves(me) { + if row.ThreadID != "" { + myThreads[row.ThreadID] = true + } else { + involvedSolo[row.MessageID] = true + } + } + } items := make([]mailItem, 0, len(rows)) for _, row := range rows { - m := mailsync.Message{ID: row.MessageID, From: row.FromAddr, To: row.ToAddr, Cc: row.CcAddr} - if !m.Involves(me) { + visible := involvedSolo[row.MessageID] || (row.ThreadID != "" && myThreads[row.ThreadID]) + if !visible { continue } it := mailItem{ProjectMailMsg: row} diff --git a/internal/httpapi/mail_sync.go b/internal/httpapi/mail_sync.go index 44f45d4..8d2e3f9 100644 --- a/internal/httpapi/mail_sync.go +++ b/internal/httpapi/mail_sync.go @@ -16,6 +16,12 @@ import ( // syncInFlight prevents concurrent syncs of the same project. var syncInFlight sync.Map // projectID -> struct{} +// isSyncing reports whether a sync is currently running for the project. +func isSyncing(projectID string) bool { + _, ok := syncInFlight.Load(projectID) + return ok +} + // syncProjectMail pulls the project's client-domain mail across ALL active member // mailboxes (domain-wide delegation), upserts the headers into ProjectMailMsg, and // records sync state. full=true pages the entire history; otherwise just the newest