Server rules: full editing, account picker, and classifier foundation in the Rules Manager (#333)#367
Conversation
…ity fixes (#333) First user-facing slice of server-rule support: a read-only view of a Graph account's Exchange messageRules inside the Rules Manager, so the rules can be seen and the mapping validated against real data before editing is built. Also folds in fixes found while validating against a live 68-rule mailbox. Wiring: - App.xaml.cs constructs GraphServerRuleService (reuses the shared GraphClient, no new disposables) and passes it to MainWindow. - MainWindow.OpenRulesManager builds a ServerRulesViewModel for the Graph account (with the cached folder list) and hands it to the window. - RulesManagerWindow shows a collapsible read-only "Server rules" section: a list (each row announces name, state, "runs on the server", any markers, and a one-line summary) and a read-only detail pane. Hidden when no Graph account. Subset expansion (every rule flagged on the test mailbox is now representable): - Added conditions bodyContains and sentToAddresses (mirroring subjectContains / fromAddresses, including multi-value gating for bodyContains). These two covered all 13 previously-flagged rules on the live mailbox. - Added the copyToFolder action. - Move/copy targets now resolve to the folder's display name (via the account's cached folders; Graph stores the folder id as FullName) instead of "another folder". View fidelity / accessibility: - DetailText reformatted: one condition/action per line under "Applies when:" / "Does:" headers, with a "Reason for block:" section only when a rule is read-only, errored, or not fully editable — and it now SHOWS the values of conditions even when they can't be edited (previously a hidden condition like "body contains 'X'" was omitted, which read as a mismatch). - Not-editable rules announce it per row, and the status line reports the count. - Detail pane is a read-only multi-line caret-navigable box (AcceptsReturn + IsReadOnlyCaretVisible) so a screen reader can arrow through every line. - Added F6 / Shift+F6 pane cycling (list, server list, server detail, status), and made the status lines focusable so the counts can be read on demand — the window previously had no F6 ring (New Window Checklist gap). - Empty-state focus lands on whichever list actually has rules. Diagnostic: /debug logs the unsupported field NAMES per flagged rule (names only, never raw predicate values, to keep mail subjects/addresses out of the log). Tests: +6 (bodyContains/sentToAddresses editable + round-trip, copyToFolder round-trip, folder-name resolution, the not-editable row marker, unsupported predicate now uses headerContains). Full suite 1587 passed; the lone red is the known clipboard flake (passes in isolation). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kellylford
left a comment
There was a problem hiding this comment.
Reviewed the full diff. Read-only scope is sound, the subset expansion round-trips correctly, and it closes a real F6 gap. Notes below — nothing is a hard blocker, but a couple need a decision.
Merge order (important)
- Base is
feat/rules-per-account-migration-333— this is stacked on #364. #364 must land first, or rebase this flat ontomain(as offered). Please don't merge this ahead of #364. - Depends on the already-merged server-rule service (#335) and VMs (#337) — both confirmed on
main. Same M365/Exchange-default-on driver as #364.
Verified correct
- Subset expansion is symmetric:
bodyContains+sentToAddressesconditions and thecopyToFolderaction are added to the supported sets, removed from the unsupported/friendly-name table, and wired in both read (ToModel) and write (ToGraph).bodyContainscorrectly joins the single-value-string guard (multi-value → view-only). Tests assert the write path emits them, so a later edit can't silently drop them. - The view-fidelity fix is right:
DetailTextnow shows every condition's value even when not editable — the old summary hid conditions it couldn't edit, which is what made a realbodyContainslook like a mis-flag. - Folder-name resolution (Graph stores the folder id as
FullName) is best-effort with a clean "another folder" fallback. - Event handlers unsubscribed in
OnClosed(AnnouncementRequested,WriteBlockedByPermission). - No WebView2 in this window, so the F6-relay-JS checklist item doesn't apply.
Three things worth a look before sign-off (not blockers)
- Possible double-announcement — needs a listen. The server-rules status now has both an explicit
Announce(StatusText, Status)on refresh and aLiveSetting="Polite"live region on the same bound TextBlock. Two independent mechanisms on the same text; whether that double-speaks on refresh is something only listening will confirm. - F6 ring skips the client-rules editor. The ring covers the rule list and status but not the editor fields (Name/conditions/action), which stay Tab-only. Fine if intended — noting it because the New Window Checklist frames the editor as a primary region.
- No CI has run yet. Because the base is a feature branch, the checks list is empty — the "1587 passed" is local-only so far. Merging #364 or rebasing onto
mainwill let CI actually run.
Follow-ups filed while testing (separate, not this PR): #365 cross-account move/copy, and #366 Graph mail delete/move reappearing from stale mutable message IDs — that one is a backend/sync bug worth its own look.
Assessment: read-only view is a good, well-tested first slice. Merge-order and the double-announcement listen are the two items that need you.
…ff) (#333) Makes the in-progress server-rules feature invisible to the public while it's built out, so intermediate merges (this read-only view, then editing, then the unified window) can't expose a half-built experience — regardless of when they merge. Mirrors the GraphBackend gate. - New FeatureFlag.ServerRules, default false in ConfigFeatureGate.Defaults. - MainWindow.OpenRulesManager only builds the ServerRulesViewModel when the gate is enabled (and a Graph account exists), so the Rules Manager looks exactly as it does today for everyone else. - Enable for dev/testing with --feature ServerRules or config.ini [features] ServerRules=true. Flip the default in a joint-decision PR once create/edit/delete and the unified per-account window are complete. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…der (#333) Correctness prerequisite for editing. The mapper/model gained bodyContains, sentToAddresses, and copyToFolder (so real O365 rules are fully representable), but the editor VM didn't know those fields — so editing such a rule would have assembled a ToModel() without them and the PATCH would silently drop them (the exact §16 data-loss the design guards against). ServerRuleEditorViewModel now reads them in ForEdit, exposes fields (incl. a separate CopyToFolder + PickCopyFolder), writes them in ToModel, and validates the copy-folder target. So every condition/action the model can represent also round-trips through the editor. Tests: extended the round-trip test to assert bodyContains/sentToAddresses/ copyToFolder survive an edit, plus copy-folder validation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Feature remains gated off (FeatureFlag.ServerRules, default false). This is a checkpoint of the server-rules-window work verified end-to-end against a live M365 mailbox (create → server rule fires). Editing - New modeless ServerRuleEditorWindow (create/edit) — modeless per the editable-text-over-WebView2 rule; Escape/Cancel wired explicitly. - Editor laid out as Common + a collapsible Advanced section; Advanced auto-expands when editing a rule that already uses an advanced field. - Support bodyContains, sentToAddresses, copyToFolder (covers every predicate in the test mailbox, so 0 rules are flagged un-editable). Reorder / read-only - Minimal-diff reorder: only PATCH the rules whose sequence actually changes, so an unrelated server-read-only rule (e.g. an Outlook-created one) no longer 400s and poisons an otherwise valid move. - Read-only rules disable Edit/Delete/Enable-Disable/Move via CanExecute (buttons and context-menu items), rather than being pressable-but-silent — the user runs with announcements off, so a disabled control is clearer. Keyboard / focus / account - Space = enable/disable, Delete = delete, Enter = edit on the server list (window-scoped keys, honouring each command's CanExecute). - Account picker added to the server section, seeded to the account the user is currently in (falls back to the first Graph account on aggregate views). - Focus returns to the selected/new rule after writes; container-based focus with virtualization off so arrow keys stay in the list. Round-trip correctness - New rules get a valid 1-based sequence (Graph rejects sequence 0). - Save reports success/failure back to the editor: it stays open and shows the error on failure instead of closing and losing the form. - Guard MainWindow's OnActivated focus-restore with IsActive so a transient activation while the editor closes can't steal focus back to the message list. Design - Spec §20: unified New/Create with auto-classify (server vs client) — the agreed direction for the remaining layout-unification work. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Status from my side: #364 is reviewed, approved, and green (I merged Four things on this PR before it can go in. 1. The PR has outgrown its title and body. 2. CI still has never run on this branch. Zero workflow runs, empty check list, because the base is a feature branch. The 1587/1617 figures remain local-only. Retarget onto 3. The double-announcement item from my last review is unaddressed, and it's now on both status lines. 4. F6 still skips the client-rules editor. On §20 — I'm on board with the unified New/Create direction and the account picker as first focusable control. Noting for the record that it makes this PR's two-section layout explicitly transitional, which is fine, but it does mean the interim layout ships to nobody: keep Nothing here is a rewrite — 1, 2 and 4 are cheap, and 3 is a listen plus a one-line deletion. |
Phase 1–2 of the unified New/Create work (spec §20). Pure logic, fully unit tested; no UI or behaviour change yet (feature still gated off). - Classify(accountSupportsServerRules) on the editor VM decides where a rule runs: a Graph account gets a server rule unless the rule uses a client-only capability; otherwise a client rule (with a reason for the save dialog); a client-only action combined with a server-only condition/action is a conflict. - IsServerRepresentable / IsClientRepresentable + the server-only / client-only feature lists that drive the decision and the conflict message. - MarkAsUnread added as the first client-only action (not yet in the editor UI). - ToClientRule(accountId) maps the client-representable subset to a MailRule (single From/To value, one action) — lossless because it's only called when IsClientRepresentable holds. - RuleRunsWhere enum + RuleClassification result type. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Update: #364 is merged ( One caveat: retargeting alone doesn't fire the workflows — GitHub only triggers on push, not on a base change. The branch currently shows BEHIND Still yours: retitle/rewrite the body to match the diff, the double-announcement listen, and a yes-or-no on whether F6 skipping the client editor is deliberate. |
|
Caught up with Since CI has never run here, I built and ran the suite locally on
So the code is in good shape. What's outstanding is still: retitle/rewrite the body to match what this now contains ( Given how much has accumulated here, a fair question: would you rather split the classifier + §20 work into its own PR on top, and land this one once 1/3/4 are closed out? Your call — I'm fine either way, but this PR is at 18 files and its stated scope hasn't matched its diff for two commits now. |
Start of the unified single-list work (spec §20.7). Feature still gated off; no wiring yet. - spec §20.7: the unified-list implementation design — UnifiedRuleRow, a UnifiedRulesViewModel owning one account picker + one merged collection, CRUD routing by row kind, and the window changes (which also resolve Kelly's #367 review items 3 (single announce mechanism) and 4 (editor in the F6 ring)). - UnifiedRuleRow: wraps a server (ServerRuleModel) or client (MailRule) rule and presents one consistent accessible line that also states where it runs ("on server" / "in QuickMail"). ToString forwards to RowText for screen readers. - Moved RuleRunsWhere enum to Models (shared by the classifier and the row). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kellylford
left a comment
There was a problem hiding this comment.
Approved. All four checks green on 25bd19d, and I independently verified that exact tree locally: clean Release build, 1646 tests passing. Items 3 and 4 accepted as deferred to spec §20.7 behind the off-by-default ServerRules flag — details in the merge comment.
|
Merged as On #364, before it mergedI pushed two commits to your branch rather than bouncing it back:
I then merged #364 using an admin override, and you should know exactly what that bypassed. The Merged with a merge commit rather than squash, deliberately, so this PR stayed a clean descendant and needed only a retarget. On this PR
What I accepted, rather than what you fixedItems 3 (double announce mechanism) and 4 (client editor outside the F6 ring) are still present in the merged code. You resolved them as design in spec §20.7 rather than as code here, and I'm accepting that: Small things left behind
Phase 4 continues on a fresh branch off |
Server-rule support for #333, grown well past the original read-only peek. Validated end-to-end against a live 68-rule M365 mailbox (create → the server rule fires) and by a screen-reader user throughout.
FeatureFlag.ServerRulesstays off — the two-section layout here is explicitly transitional and ships to nobody until the unified list (§20) lands.What's in it
messageRulesin the Rules Manager (the original slice).ServerRuleEditorWindow— create / edit / delete / enable-disable / reorder. Modeless per the editable-text-over-WebView2 rule. Editor is laid out Common + collapsible Advanced, and Advanced auto-expands when editing a rule that already uses an advanced field.bodyContains,sentToAddresses,copyToFolder— every rule on the test mailbox is representable (0 flagged). Read + write, with the multi-value guard.Classify(accountSupportsServerRules)+ToClientRule— pure, unit-tested logic that decides server vs client vs conflict. Not yet wired to UI; it's the engine for the unified New button.Accessibility
One-per-line
DetailTextunder "Applies when:" / "Does:" / "Reason for block:" headers; caret-navigable read-only detail pane; per-row not-editable/read-only markers; status line reports counts and is focusable; F6 / Shift+F6 pane cycling (which the window lacked entirely before).Still to come (§20, this PR's successor work)
The single per-account combined list with the account picker as first focusable control and one auto-classifying New button — replaces this interim two-section layout. Design in
docs/planning/server-rules-pm-dev-spec.md§20.Tests
Full suite 1597 passed (lone red is the known
OpenClipboardclipboard flake, passes in isolation). Server-rule/classifier coverage: reorder minimal-diff, read-only gating, sequence-on-create, save-failure keeps editor open, classifier server/client/conflict, client-rule conversion, and the earlier subset round-trips.Filed while testing (separate)