diff options
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 43 |
1 files changed, 43 insertions, 0 deletions
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 |
