From 0ca4624195cdd8c78ff614e3912af5b914458497 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Wed, 26 Aug 2026 13:28:51 +0200 Subject: feat: flag what you answered, mark what was forwarded to you Item 68, which turned out to be three things once its premise was measured. The note asked to extend a "passed" subject rule to "Fw:"; there was no subject rule, and the correlation it rested on did not exist. What did exist was a gap nobody had reported. Reply and forward now flag their source. The Maildir R and P flags, which every other client sets and notmuch reads back as "replied" and "passed", had never been written here: measured on the developer's index, all 317 "replied" and all 6 "passed" came from other clients. ComposeWindow emits sourceMessageAnswered after a successful send and MainWindow routes it through sendMessageTagChange, message-scoped and off the undo stack, for the reason auto mark-read is: the flag records that the mail went, and the send cannot be undone. ComposeContext carries sourceMessageId rather than reusing inReplyTo, which is deliberately empty on a forward so the recipient's client does not file it under the thread it left. Keying on it made the "passed" half dead code that compiled and never fired. A resumed draft is excluded: its kind records how the file was opened, not what the user is doing, so flagging on it would set R from a guess. A received forward gets its own mark. Derived from the subject at paint time, storing nothing and reaching no server, because "passed" means "I forwarded this" and setting it from a guess would assert something false on 222 existing messages. subjectIsForwarded() shares forwardSubject()'s prefix table so the two cannot disagree, strips a Re: chain first, and takes extra locale spellings from [general] forward_prefixes, which extends the built-in table rather than replacing it. A mutation survived the first round and corrected a claim in the code: QRegularExpression::escape already makes a punctuation prefix inert, so the word guard is not about pattern validity. It stops a configured "-" matching "-: x". The comment and test say that now. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LXCZFLXbAii5n5wtovpdhh --- src/threadlistmodel.h | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'src/threadlistmodel.h') diff --git a/src/threadlistmodel.h b/src/threadlistmodel.h index 717537c..2e56328 100644 --- a/src/threadlistmodel.h +++ b/src/threadlistmodel.h @@ -135,6 +135,12 @@ public: /// Item 69 draws this as a mark where it used to read as the word /// "passed" in the tag strip. IsPassedRole, + /// True when the SUBJECT reads as a forward someone sent the user. + /// + /// Item 68. Derived from the subject at query time, not from a tag or + /// a Maildir flag: `passed` means "I forwarded this", which is a + /// different fact. Nothing is stored and nothing reaches the server. + IsReceivedForwardRole, /// bool; the message was replied to, from the Maildir "R" flag. IsRepliedRole, @@ -187,6 +193,16 @@ public: /// The pattern DateFormatRole answers with. Empty means the system format. void setDateFormat(const QString &format) { m_dateFormat = format; } + /// Extra subject prefixes counting as a received forward (item 68). + /// + /// Pushed in from the config exactly as setDateFormat() is, rather than + /// giving the model a Config: both are display values the window already + /// holds, and the model draws rather than resolves. + void setForwardPrefixes(const QStringList &prefixes) + { + m_forwardPrefixes = prefixes; + } + /// One row per thread, with no expander and no reply count. /// /// For the Sent view, where a thread is the wrong unit: the user's model of @@ -203,6 +219,29 @@ public: /// The children are not discarded, only hidden. Leaving flat mode restores /// the tree without reloading anything. void setFlatMode(bool flat); + + /// Whether the list is showing the trash view. + /// + /// The doomed fill exists to tell the user a message is on its way out of + /// a view it is still sitting in. In the trash that is redundant: every + /// row is deleted, and a list painted entirely crimson says nothing while + /// costing legibility. Set on EVERY query run, like flat mode, so it + /// cannot leak into the next view. + void setTrashView(bool trash); + + /// Drops any top-level row whose message no longer carries \p tag. + /// + /// The optimistic counterpart to a row simply vanishing at the next query. + /// Delete strips `inbox`, and in the Inbox view the row it stripped it + /// from stops belonging there; leaving it until the next sync is what made + /// a deleted message sit in the inbox looking undeleted. + /// + /// Top-level rows ONLY, and deliberately: a reply that no longer matches + /// still belongs to the conversation the user has open, and removing it + /// would collapse a thread under the reader's hands. \p tag is the tag the + /// CURRENT VIEW requires, so a caller passes what the query filters on and + /// nothing else. + void removeThreadsWithoutTag(const QString &tag); bool flatMode() const { return m_flatMode; } QModelIndex index(int row, int column, @@ -414,5 +453,7 @@ private: QVector m_threads; const TagColors *m_tagColors = nullptr; QString m_dateFormat; + QStringList m_forwardPrefixes; bool m_flatMode = false; + bool m_trashView = false; }; -- cgit v1.2.3