diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-04 11:36:16 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-04 12:54:54 +0200 |
| commit | 0a160651cfb9a0f580bcc446941058a339e55643 (patch) | |
| tree | 911d60baf92426f5f32a662d5e9ebf6e72ae0cf2 /README.md | |
| parent | 4574a6e8d1253c0a70972f0c6e4d13d528ea6420 (diff) | |
| download | qtmaildir-0a160651cfb9a0f580bcc446941058a339e55643.tar.gz qtmaildir-0a160651cfb9a0f580bcc446941058a339e55643.zip | |
feat(tags): add an Edit tags dialog on Ctrl+T
Five hardcoded tags were the only ones reachable from the UI: archive,
delete, spam, flag and toggle_unread. For an application whose purpose is
organising mail by tag, applying any other one meant leaving for a
terminal. Item 26 of the usability backlog, raised by the user asking how
to add a tag and finding they could not.
One dialog rather than separate add and remove actions, at the user's
choice: filing something under a new tag while dropping inbox is one
thought, not two. Type tags to add or remove, comma separated, or clear a
checkbox to drop a tag already on the selection without retyping its
name.
Both fields complete against the tag list MainWindow already holds for
the query completer. Completion is a guard against typing shoppping
beside shopping, never a whitelist: inventing a tag is the entire point,
so any valid name goes through whether or not it exists yet.
Tri-state checkboxes carry the multi-thread case, and are where the risk
is. A tag on some selected threads shows partially checked, and leaving
it alone changes nothing; the opposite reading would silently tag threads
the user never looked at. A tag already on every thread and left checked
is likewise not a change and is not sent as one.
Tag names are validated before anything is applied, through a free
function so the rules are testable on their own. Empty, a leading dash
(notmuch's CLI reads it as removal, making such a tag a trap), whitespace
and control characters are refused by name and reason. Nothing is applied
until the whole set passes, since the user cannot tell which half of a
partial change landed.
TagDialog is pure UI: handed the vocabulary and the current state,
returning two lists, contacting no worker. That is what lets its fifteen
tests run without a notmuch database. Integration is a single call to the
existing tagSelected(), so undo, the optimistic model update, the
combined multi-row query and the completer refresh for a brand-new tag
all come for free.
One test assumption was wrong and the code was right: a case asserted
that QStringLiteral("null\0byte") truncates at the null and reads as
empty. It does not, so the null is caught as a control character. The
test was corrected rather than the validator.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 22 |
1 files changed, 22 insertions, 0 deletions
@@ -248,6 +248,27 @@ an attachment, so it is visible without opening the thread. It comes from the `attachment` tag notmuch applies while indexing, not from parsing the message, and costs no extra query. +## Tagging + +Archive, delete, spam, flag and toggle-unread write fixed tags. For anything +else, **Ctrl+T** opens a dialog over the selected threads: type tags to add or +remove, separated by commas, or clear a checkbox to drop a tag already present. + +Both fields complete against every tag in your database, which is a guard +against typing `shoppping` beside `shopping`, not a restriction: a tag that does +not exist yet is exactly what the dialog is for, so any valid name is accepted. + +With several threads selected, a tag on only some of them shows a partially +checked box saying how many. **Leaving it alone changes nothing.** Check it to +apply it to all, clear it to remove it from all. + +Tag names are rejected if empty, if they start with `-` (notmuch reads that as +"remove this tag"), or if they contain spaces or unprintable characters. The +dialog says which name was refused and why, and applies nothing until the whole +set is valid. + +Every change goes on the undo stack, so `Ctrl+Z` reverses a mistyped tag. + ## Unsynced changes Tagging changes the notmuch index at once, but the mail store only learns about @@ -311,6 +332,7 @@ Defaults, all rebindable through `[keys]`: | `Ctrl+Space` | `complete_query` | Focus the query bar and offer completions | | `Ctrl+H` | `toggle_html` | Switch the thread between HTML and plain text | | `Ctrl+M` | `load_remote` | Load remote images for the current thread | +| `Ctrl+T` | `edit_tags` | Add or remove any tag on the selected threads | | `Ctrl+Shift+D` | `message_details` | Show the full headers of every message in the thread | | `Ctrl+Z` | `undo` | Undo the last tag change | | `Ctrl+G` | `sync` | Run the configured sync command | |
