kellylford/QuickMailGitHubLast refreshed Oct 3, 2026
This is a public, read-only report. Striff reads this repository's docs, turns each sentence that makes a claim about the code into a rule, and checks the rule against the code on the default branch. How this works
Names these docs write that the code no longer has2
Each sentence below names a type the default branch doesn't declare, or declares somewhere else. Edit the doc so it matches the code, or bring the type back.
**SyncService** runs background IMAP sync. It raises FolderSynced, MessagesRemoved, and RulesApplied events; MainViewModel subscribes and merges new data into observable collections.
MainViewModel depends on SyncService
Holds
Read Oct 3
A static ProviderCatalog.IsICloud(account) replaces what used to be an ImapHost == "imap.mail.me.com" comparison duplicated across six files.
ProviderCatalog has an IsICloud method
Holds
Read Oct 3
A second Gmail entry, gmail-oauth ("Gmail (sign in with Google)", IProviderCatalog.GmailGoogleSignIn), defaults to AuthType.OAuth2Google and carries no app-password hint.
IProviderCatalog has GmailGoogleSignIn
Holds
Read Oct 3
no runtime authentication path consults it, so an account already saved as OAuth2Google connects normally regardless, and AccountEditorViewModel.ShowGoogleAuthOption ORs in AuthType == OAuth2Google so such an account is never shown a blank Authentication combo.
AccountEditorViewModel has ShowGoogleAuthOption
Holds
Read Oct 3
The model exposes computed ResolvedMemberCount and MissingContactCount (both [JsonIgnore]) and a human-readable Display ("Name, N members" / "(M missing)" / "Empty group") so the UI does not have to recompute them.
GroupModel has ResolvedMemberCount
Holds
Read Oct 3
The model exposes computed ResolvedMemberCount and MissingContactCount (both [JsonIgnore]) and a human-readable Display ("Name, N members" / "(M missing)" / "Empty group") so the UI does not have to recompute them.
GroupModel has MissingContactCount
Holds
Read Oct 3
The model exposes computed ResolvedMemberCount and MissingContactCount (both [JsonIgnore]) and a human-readable Display ("Name, N members" / "(M missing)" / "Empty group") so the UI does not have to recompute them.
Actions lists them all when there is more than one (#682), and AllActions() returns them in the order they run — marking, then copying, then moving or deleting.
MailRule has an AllActions method
Holds
Read Oct 3
Every rule belongs to one account (AccountId);
MailRule has AccountId
Holds
Read Oct 3
LoadRules throws RulesFileUnreadableException (#700), because every writer loads the whole list, changes it and saves it back, so an empty read made the next save wipe the file.
RuleService has LoadRules
Holds
Read Oct 3
Arriving mail reaches the rules through SyncService.ApplyRulesToArrivalsAsync: the store records every message it caches as waiting for rules (MessageSummary.rules_applied = 0; rows cached before the column existed default to done), and SettlePendingRulesAsync runs the rules on an Inbox's waiting mail that is *arriving* and records all of it done, one pass at a time per Inbox, recording other folders' waiting mail done without running anything.
SyncService has ApplyRulesToArrivalsAsync
Holds
Read Oct 3
they now ask for a pass straight after caching, through ISyncService.ApplyPendingRulesAsync.
ISyncService has ApplyPendingRulesAsync
Holds
Read Oct 3
A SavedView bundles one or more folders with a view mode, filter, sort order, optional Hotkey gesture string, IsDefault flag, and optional DaysOfMail limit.
SavedView has Hotkey
Holds
Read Oct 3
A SavedView bundles one or more folders with a view mode, filter, sort order, optional Hotkey gesture string, IsDefault flag, and optional DaysOfMail limit.
SavedView has DaysOfMail
Holds
Read Oct 3
FindByGesture() checks user overrides from hotkeys.json first, then falls back to defaults.
CommandRegistry has a FindByGesture method
Holds
Read Oct 3
**BatchObservableCollection<T>** (Helpers/BatchObservableCollection.cs) — subclass of ObservableCollection<T> that suppresses individual CollectionChanged notifications during bulk mutations and fires a single Reset when the batch closes. This prevents screen readers from re-announcing the focused item after every insert during background sync. Always use BeginBatchScope() in a using block (auto-closes on exception);
BatchObservableCollection has a BeginBatchScope method
Holds
Read Oct 3
the manual BeginBatch() / EndBatch() pair is available for explicit try/finally sites.
BatchObservableCollection has a BeginBatch method
Holds
Read Oct 3
the manual BeginBatch() / EndBatch() pair is available for explicit try/finally sites.
BatchObservableCollection has an EndBatch method
Holds
Read Oct 3
It exposes Groups: ObservableCollection<GroupModel> sorted by LastUsedTicks descending and a derived SelectedGroupMembers: ObservableCollection<ContactModel> rebuilt whenever SelectedGroup changes.
AddressBookViewModel has Groups
Holds
Read Oct 3
It exposes Groups: ObservableCollection<GroupModel> sorted by LastUsedTicks descending and a derived SelectedGroupMembers: ObservableCollection<ContactModel> rebuilt whenever SelectedGroup changes.
AddressBookViewModel has SelectedGroupMembers
Holds
Read Oct 3
Confirmation dialogs and screen reader announcements are surfaced via event Func<string, string, Task<bool>>? ConfirmRequested and event Action<string, AnnouncementCategory>? AnnouncementRequested;
AddressBookViewModel has ConfirmRequested
Holds
Read Oct 3
Confirmation dialogs and screen reader announcements are surfaced via event Func<string, string, Task<bool>>? ConfirmRequested and event Action<string, AnnouncementCategory>? AnnouncementRequested;
AddressBookViewModel has AnnouncementRequested
Holds
Read Oct 3
The InsertGroup(Action<ContactModel>) private helper iterates SelectedGroupMembers in recency order and calls the inserter for each — the Compose window sets those inserters via SetInsertActions(to, cc, bcc) so a group can be dropped into any address field with one command.
AddressBookViewModel has an InsertGroup method
Holds
Read Oct 3
it shares the same IContactService instance and raises GroupsChanged for the parent to refresh.
GroupManagerViewModel has GroupsChanged
Holds
Read Oct 3
GraphMailService has **no ILocalStoreService dependency** — every read goes straight to the Graph REST API, so the uncreated SQLite schema never matters.
GraphMailService may not depend on ILocalStoreService
Holds
Read Oct 3
All of them are handled in CommandPaletteWindow.OnPreviewKeyDown.
CommandPaletteWindow has OnPreviewKeyDown
Holds
Read Oct 3
Markdown mode inserts the equivalent syntax through MarkdownEditing (Helpers/MarkdownEditing.cs — pure, unit-tested text operations applied via TextBox.SelectedText so each toggle is one undo unit).
MarkdownEditing is in QuickMail.Helpers
Holds
Read Oct 3
Rich-mode messages are sent as multipart/alternative by MimeMessageBuilder whenever ComposeModel.HtmlBody is non-empty.
MimeMessageBuilder depends on ComposeModel
Holds
Read Oct 3
EventEditorWindow and GoToDateWindow enter dates, times and the repeat interval through Controls/DateTimeField.cs — a TextBox subclass that steps with the arrow keys.
DateTimeField is in QuickMail.Controls
Holds
Read Oct 3
Stepping and free-text parsing live in Helpers/DateTimeFieldParser.cs (pure, unit-tested in DateTimeFieldParserTests).
DateTimeFieldParser is in QuickMail.Helpers
Holds
Read Oct 3
Helpers/TabStripNavigation.cs installs this once, from App.OnStartup, as a class handler on TabControl — a per-control attached property can be forgotten when a window with tabs is added, a class handler cannot.
TabStripNavigation is in QuickMail.Helpers
Holds
Read Oct 3
Helpers/ContextMenuKeys.cs carries the full account (issue #631) — it is why the attachment lists open their own menu for Shift+F10 and deliberately leave the Applications key to Windows.
ContextMenuKeys is in QuickMail.Helpers
Holds
Read Oct 3
Each consults Helpers/ContextMenuFocusPolicy.cs, which exists because Keyboard.FocusedElement being null means two unrelated things:
ContextMenuFocusPolicy is in QuickMail.Helpers
Holds
Read Oct 3
MainViewModel.SetGroupFlagAsync was added to support batch flagging from the context menu (with the same optimistic update and announcement pattern used by SetMessageFlagAsync).
MainViewModel has SetGroupFlagAsync
Holds
Read Oct 3
MessageSort.FlaggedFirst added.
MessageSort has FlaggedFirst
Holds
Read Oct 3
FlagName/FlagColorHex are resolved from FlagService.LoadFlagDefinitionsAsync() in MainViewModel after summaries are loaded.
FlagService has a LoadFlagDefinitionsAsync method
Holds
Read Oct 3
IsServerFlagged is a transient import field on MailMessageSummary (not persisted directly; it drives setting FlagId to the default flag's Id if no local flag_id exists, and vice versa — see §9).
MailMessageSummary has an IsServerFlagged field
Holds
Read Oct 3
**Write:** New SetMessageFlaggedAsync in GraphMailService:
GraphMailService has SetMessageFlaggedAsync
Holds
Read Oct 3
MailServiceRouter must expose SetMessageFlaggedAsync and route to the correct backend (ImapMailService or GraphMailService) based on account type — following the same dispatch pattern used for DeleteMessageAsync, MarkReadAsync, etc.
MailServiceRouter has SetMessageFlaggedAsync
Holds
Read Oct 3
Flags: ObservableCollection<FlagDefinition> (sorted by SortOrder)
FlagManagerViewModel has Flags
Holds
Read Oct 3
CanMoveUp / CanMoveDown: based on SelectedFlag's position
FlagManagerViewModel has CanMoveUp
Holds
Read Oct 3
CanMoveUp / CanMoveDown: based on SelectedFlag's position
**TestConnectionAsync for Graph accounts:** AccountEditorViewModel.TestConnectionAsync is currently IMAP-specific — it guards on ImapHost, builds an AccountModel with IMAP fields, and calls ConnectAsync.
Router itself implements IMailService so MainViewModel, SyncService, RuleService are unchanged — they always saw IMailService.
MailServiceRouter implements IMailService
Holds
Read Oct 3
MailMessageDetail : MailMessageSummary inherits.
MailMessageDetail extends MailMessageSummary
Holds
Read Oct 3
**--online mode (#62):** GraphMailService has **no ILocalStoreService dependency** — every read (connect, folders, summaries, detail, since-date) goes straight to Graph, so --online (which leaves the SQLite schema uncreated) is fully supported for Graph accounts with no behavioral difference.
GraphMailService may not depend on ILocalStoreService
Holds
Read Oct 3
GraphSendMailService builds the MIME with MimeMessageBuilder, base64-encodes it, and POSTs the ASCII base64 via GraphClient.PostRawAsync.
GraphSendMailService depends on GraphClient
Holds
Read Oct 3
**GraphClient.PostRawAsync** was added;
GraphClient has PostRawAsync
Holds
Read Oct 3
The ICS-reply MIME building moved to MimeMessageBuilder.BuildIcsReply, shared by both backends.
MimeMessageBuilder has BuildIcsReply
Holds
Read Oct 3
When a new account is added at runtime, the caller (MainViewModel.AddAccountAsync) must call mailRouter.RegisterAccount(account.Id, backendForKind(account.BackendKind)) before the first IMailService method runs against that account.
MailServiceRouter has a RegisterAccount method
Holds
Read Oct 3
Add a case for [features] that copies all key/value pairs into ConfigModel.Features.
ConfigModel has Features
Holds
Read Oct 3
A MessageWindow in v1 has its own MessageWindowViewModel, which is a thin wrapper around a single MessageTabViewModel and a *minimal* message list (for the user to navigate to the next / previous message without going back to the main window).
MessageWindow depends on MessageWindowViewModel
Holds
Read Oct 3
Add MainWindow.OpenTabs / ActiveTab / ShowTabStrip (all hidden by default).
File QuickMail/ViewModels/MainViewModel.cs, Change Add OpenTabs , ActiveTab , ShowTabStrip , MessageOpenMode , the tab commands, the HandleOpenMessageAsync decision tree, and the new events
MainViewModel has MessageOpenMode
Holds
Read Oct 3
File QuickMail/ViewModels/ComposeViewModel.cs, Change Add WindowTitle , Subject (if not already), the debounced title refresh, and the ComposeKind enum
ComposeViewModel has WindowTitle
Holds
Read Oct 3
File QuickMail/ViewModels/ComposeViewModel.cs, Change Add WindowTitle , Subject (if not already), the debounced title refresh, and the ComposeKind enum
ComposeViewModel has ComposeKind
Holds
72 rules from 5 docs
See these where the change is. The browser extension puts a pull request's doc findings, and a diagram of what it changed, on the GitHub page itself, so the code and what your docs say about it are side by side.