Docs & Rules

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

2+ names in these docs no longer match the code.

mainc0748e7listed 4h ago

Is this yours? Install to manage it

Once installed, Striff checks every pull request.

72
72
31
15
10
6
1 stale33
8
1 stale
95 docs, listed Oct 3
kellylford/QuickMailOpen repository

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.

  • GoneImapService
    ProfileContext → AccountService → CredentialService → OAuthService → ImapService → SmtpService → ConfigService → LocalStoreService → ContactService → TemplateService → RuleService → SyncService → ConnectivityService → OutboxService → ViewService → CommandRegistry → MainViewModel → MainWindow.

    The repository once held QuickMail/Services/ImapService.cs. It doesn't now. Closest names there: IMailService, SmtpService, ImapMailService.

    View on GitHub

  • GoneImapService
    ProfileContext → AccountService → CredentialService → ConfigService → ProviderCatalog → AutoDiscoverService → OAuthService → ImapService → SmtpService → LocalStoreService → ContactService → TemplateService → RuleService → SyncService → ConnectivityService → OutboxService → ViewService → CommandRegistry → MainViewModel → MainWindow.

    The repository once held QuickMail/Services/ImapService.cs. It doesn't now. Closest names there: IMailService, SmtpService, ImapMailService.

    View on GitHub

kellylford/QuickMail — documented rules

72 of 72 rules, printed October 3, 2026.

The sentence in your docs
Read Oct 3
**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.
GroupModel has Display
Holds
Read Oct 3
LoadAllGroupsAsync, CreateGroupAsync, RenameGroupAsync, DeleteGroupAsync, AddMemberAsync, RemoveMemberAsync, ListGroupsForContactAsync, TouchGroupAsync.
IContactService has LoadAllGroupsAsync
Holds
Read Oct 3
LoadAllGroupsAsync, CreateGroupAsync, RenameGroupAsync, DeleteGroupAsync, AddMemberAsync, RemoveMemberAsync, ListGroupsForContactAsync, TouchGroupAsync.
IContactService has CreateGroupAsync
Holds
Read Oct 3
LoadAllGroupsAsync, CreateGroupAsync, RenameGroupAsync, DeleteGroupAsync, AddMemberAsync, RemoveMemberAsync, ListGroupsForContactAsync, TouchGroupAsync.
IContactService has RenameGroupAsync
Holds
Read Oct 3
LoadAllGroupsAsync, CreateGroupAsync, RenameGroupAsync, DeleteGroupAsync, AddMemberAsync, RemoveMemberAsync, ListGroupsForContactAsync, TouchGroupAsync.
IContactService has DeleteGroupAsync
Holds
Read Oct 3
LoadAllGroupsAsync, CreateGroupAsync, RenameGroupAsync, DeleteGroupAsync, AddMemberAsync, RemoveMemberAsync, ListGroupsForContactAsync, TouchGroupAsync.
IContactService has AddMemberAsync
Holds
Read Oct 3
LoadAllGroupsAsync, CreateGroupAsync, RenameGroupAsync, DeleteGroupAsync, AddMemberAsync, RemoveMemberAsync, ListGroupsForContactAsync, TouchGroupAsync.
IContactService has RemoveMemberAsync
Holds
Read Oct 3
LoadAllGroupsAsync, CreateGroupAsync, RenameGroupAsync, DeleteGroupAsync, AddMemberAsync, RemoveMemberAsync, ListGroupsForContactAsync, TouchGroupAsync.
IContactService has ListGroupsForContactAsync
Holds
Read Oct 3
LoadAllGroupsAsync, CreateGroupAsync, RenameGroupAsync, DeleteGroupAsync, AddMemberAsync, RemoveMemberAsync, ListGroupsForContactAsync, TouchGroupAsync.
IContactService has TouchGroupAsync
Holds
Read Oct 3
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
FlagManagerViewModel has CanMoveDown
Holds
Read Oct 3
Calls FlagService.SetKDefaultFlagAsync(SelectedFlag.Id).
FlagService has a SetKDefaultFlagAsync method
Holds
Read Oct 3
event Func<string, string, Task<bool>>? ConfirmDeleteRequested — raised before delete;
FlagManagerViewModel has ConfirmDeleteRequested
Holds
Read Oct 3
event Action<string, AnnouncementCategory>? AnnouncementRequested — for accessibility announcements.
FlagManagerViewModel has AnnouncementRequested
Holds
Read Oct 3
Flags: List<FlagDefinition> (loaded from FlagService)
FlagPickerViewModel has Flags
Holds
Read Oct 3
Raises FlagSelected: event Action<FlagDefinition?>? — View subscribes and calls MainViewModel.SetFlagAsync(summary, flag).
FlagPickerViewModel has FlagSelected
Holds
Read Oct 3
Modify QuickMail/ViewModels/SettingsViewModel.cs — add [ObservableProperty] private bool _announceFlagStatus;
SettingsViewModel has _announceFlagStatus
Holds
Read Oct 3
**TestConnectionAsync for Graph accounts:** AccountEditorViewModel.TestConnectionAsync is currently IMAP-specific — it guards on ImapHost, builds an AccountModel with IMAP fields, and calls ConnectAsync.
AccountEditorViewModel depends on AccountModel
Holds
Read Oct 3
GraphSendMailService.cs: implements ISendMailService.
GraphSendMailService implements ISendMailService
Holds
Read Oct 3
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).
MainViewModel has ShowTabStrip
Holds
Read Oct 3
File QuickMail/Models/ConfigModel.cs, Change Add WindowingPreferences Windowing property
ConfigModel has Windowing
Holds
Read Oct 3
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.