From 4574a6e8d1253c0a70972f0c6e4d13d528ea6420 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Tue, 4 Aug 2026 11:27:21 +0200 Subject: docs: add item 26, no way to tag from the UI The user asked how to add a tag to a message and the answer is that you cannot: every tagging action writes a hardcoded name, so archive, delete, spam, flag and toggle_unread are the only tags reachable from the UI. For an application whose purpose is organising mail by tag, that is a hole worth its own item rather than being folded into item 25. The plumbing is already there. tagSelected() takes arbitrary add and remove lists, applyTagsToThreads() handles multi-row selections in one query, undo works, and the completer already holds every tag in the database, which is what makes completing the dialog's input the obvious guard against creating 'shoppping' beside 'shopping'. Co-Authored-By: Claude Opus 5 --- .../plans/2026-08-03-post-0.1.0-usability.md | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md index 205710a..6fd40c1 100644 --- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md +++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md @@ -64,6 +64,7 @@ taking that too literally. | 23 | No way to save a search query from the UI | workflow | M | open | | 24 | No right-click actions on the thread list | discoverability | S | open | | 25 | No select-all, and bulk actions are undiscoverable | workflow | S | open | +| 26 | No way to add or remove an arbitrary tag from the UI | workflow | S | open | Sizes are rough: XS under an hour, S a sitting, M a session. @@ -1117,6 +1118,48 @@ combined-query design is what makes this plausible at all, but "plausible" is not "measured", and this item is the one that turns a rare accident into a routine keystroke. Measure it against a real query before shipping the binding. +## 26. No way to add or remove an arbitrary tag from the UI + +**Observed (user, 2026-08-04), as a question:** "as it is today, how do I add a +new tag to a message?" The answer is that you cannot. The only route is +`notmuch tag` in a terminal, or the companion `mailctl`. + +**Cause: every tagging action writes a hardcoded tag name.** `registerActions()` +offers exactly five: `archive` (removes `inbox`), `delete` (adds `deleted`), +`spam` (adds `spam`, removes `inbox`), `flag` (adds `flagged`) and +`toggle_unread`. Nothing accepts a tag the user types. For an application whose +entire purpose is organising mail by tag, that is a conspicuous hole, and it is +the reason this item exists at all rather than being folded into item 25. + +**The machinery is already complete.** `tagSelected(add, remove, description)` +takes arbitrary lists and is the single funnel every mutation already uses; +`applyTagsToThreads()` resolves a multi-row selection in one combined query; +undo works through `TagChange::inverted()`; and `QueryCompleter` already holds +every tag in the database, refreshed on the worker thread. This item is a +dialog and two actions, not new plumbing. + +**Approach.** + +- An `add_tag` action opening a small dialog with a line edit, and a `remove_tag` + counterpart. Both act on the whole selection, like every other tag action. +- **Complete against the known tag list.** The completer's tag vocabulary is the + same data, so offering it here costs nothing and prevents the obvious failure + mode: typing `shoppping` and silently creating a new tag next to `shopping`. + This is the strongest argument for the dialog over a free-text prompt. +- Removing should offer the tags actually present on the selection rather than + every tag in the database, since removing a tag that is not there is a no-op + the user cannot see. + +**Constraints.** + +- **A tag name is not free text.** notmuch accepts a lot, but a leading `-`, an + embedded space or an empty string will produce confusing results or a failed + write. Validate before sending, and say what was rejected. +- No confirmation dialog, per `CLAUDE.md`; undo covers a mistyped tag. +- A tag the user invents is new to the completer, and `onTagsApplied()` already + refreshes the list when a mutation introduces an unknown tag, so that path is + in place and should be relied on rather than duplicated. + --- ## Deferred, unsized, or split out -- cgit v1.2.3