aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md26
-rw-r--r--CMakeLists.txt1
-rw-r--r--README.md35
-rw-r--r--assets/qtmaildir.desktop13
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md40
-rw-r--r--src/CMakeLists.txt16
-rw-r--r--src/main.cpp8
-rw-r--r--src/resources.qrc6
8 files changed, 141 insertions, 4 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e0ff08b..c78cb94 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,22 @@ point at which they are stable.
### Added
+- Tags render as coloured chips instead of text in a column. The account tag
+ sits in front of the subject in the thread list, and the functional tags fill
+ a single row under the message pane, with anything that does not fit
+ collapsing into a `+N` chip whose tooltip names the rest.
+- `[tagcolors]` config group. Colours resolve by exact tag first, then by
+ top-level prefix, so one `shopping` entry covers `shopping/amazon` and
+ `shopping/nike` while `shopping/amazon` can still override its own. Built-in
+ defaults cover the usual state tags; anything unconfigured gets a stable
+ colour derived from its name.
+- `color` and `label` keys in an account stanza, setting the account chip's
+ fill and its text. `label` shortens a long key for display only and renames
+ nothing in notmuch; unset falls back to the key.
+- The application icon is now used: window icon, a `.desktop` entry, and
+ install rules placing both into `hicolor` and `share/applications`.
+- Toolbar and menu actions carry icons from the system theme, falling back to
+ text where a theme lacks one.
- Menu bar covering every action: File, Edit, Message, View and Help.
- Toolbar with the frequent subset, Sync, Archive, Delete and Undo.
- **Help > Keyboard shortcuts**, listing the current bindings. Generated from
@@ -29,8 +45,14 @@ point at which they are stable.
every column. The tag change was already applied, but `Tags` sat after the
stretching `Subject` column and was pushed off-screen, so Delete looked like
it had done nothing.
-- Thread list columns reordered to Tags, Date, From, Subject. Subject stretches
- and is now last, so no column can be pushed out of view.
+- Thread list columns are Date, From and Subject, all resizable. The tags
+ column is gone: spelling out a dozen tags per row consumed most of the list's
+ width. Widening past the viewport scrolls horizontally rather than squeezing
+ the other columns.
+- Hierarchical tags in `[tagcolors]` were silently ignored. QSettings treats
+ `/` in a key as a group separator, so `shopping/amazon` becomes a nested key
+ that `childKeys()` never returns, and every tag containing a `/` fell through
+ to its prefix.
- Three default bindings never fired. Typing a capital sends `Shift`+the key,
but `N`, `F` and `G` were stored as the unshifted key, which no keystroke
produces, leaving `toggle_unread`, `flag` and `sync` dead. A bare capital in
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ed9db9c..7b73a7f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,6 +4,7 @@ project(qtmaildir VERSION 0.1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets WebEngineWidgets Test)
diff --git a/README.md b/README.md
index f7e2444..37ea3c9 100644
--- a/README.md
+++ b/README.md
@@ -91,6 +91,8 @@ name = Your Name
address = you@example.org
maildir = work-mail ; relative to notmuch's database.path
drafts = Drafts ; recorded for v2; unused today
+label = W ; optional chip text; defaults to the key
+color = #2f6fa8 ; optional chip colour; generated when unset
[account.personal]
name = Your Name
@@ -98,6 +100,13 @@ address = you@example.net
maildir = personal
drafts = Drafts
+[tagcolors]
+; Optional. Colours resolve by exact tag first, then by top-level prefix, so
+; one entry covers a whole hierarchy.
+shopping = #3366cc ; also colours shopping/amazon, shopping/nike, ...
+shopping/amazon = #ff9900 ; ... unless the exact tag overrides it
+work = #cc4444
+
[queries]
Inbox = tag:inbox
Unread = tag:unread
@@ -114,6 +123,32 @@ Saved-query buttons appear in alphabetical order rather than file order:
QSettings returns keys sorted, and preserving file order would mean
hand-rolling an INI parser.
+## Tags
+
+Tags render as coloured chips, and fall into two kinds.
+
+**Account tags** (`account-<key>`, matching an `[account.<key>]` stanza) say
+which mailbox a thread arrived in. They appear as a chip in front of the
+subject in the thread list, coloured by that account's `color` key and labelled
+by its `label` key. `label` changes the chip text only; the notmuch tag is
+never renamed, so queries and external tagging are unaffected.
+
+**Functional tags** say what state a thread is in. They fill one row under the
+message pane, sorted, with whatever does not fit collapsing into a `+N` chip
+whose tooltip lists the rest. Colours come from `[tagcolors]`, falling back to
+built-in defaults for the usual state tags (`flagged`, `unread`, `deleted`,
+`spam`, `attachment`, `replied`, and others), and finally to a colour derived
+from the tag name so no chip is ever unstyled.
+
+Lookup is exact tag first, then top-level prefix. One `shopping` entry
+therefore covers `shopping/amazon` and `shopping/nike`, while a
+`shopping/amazon` entry still overrides its own.
+
+Note that a `/` in an INI key is a group separator to QSettings, so
+`shopping/amazon = #ff9900` is stored as a nested key and written to the file
+as `shopping\amazon`. It is read back correctly; the escaping is QSettings'
+own.
+
## Keybindings
Defaults, all rebindable through `[keys]`:
diff --git a/assets/qtmaildir.desktop b/assets/qtmaildir.desktop
new file mode 100644
index 0000000..98e435b
--- /dev/null
+++ b/assets/qtmaildir.desktop
@@ -0,0 +1,13 @@
+[Desktop Entry]
+Type=Application
+Version=1.0
+Name=qtmaildir
+GenericName=Mail Reader
+Comment=Read and organize a local notmuch-indexed Maildir
+Exec=qtmaildir
+Icon=qtmaildir
+Terminal=false
+Categories=Network;Email;Qt;
+Keywords=mail;email;notmuch;maildir;
+StartupNotify=true
+StartupWMClass=qtmaildir
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 1e08bb1..915c8cb 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
@@ -44,8 +44,9 @@ taking that too literally.
| 8 | No buttons or menu entries for archive, undo, etc | discoverability | M | **done** |
| 9 | No in-app view of configured shortcuts | discoverability | S | **done** |
| 10 | Reaching an account's inbox takes two steps | workflow | S | open |
-| 11 | Icon, `.desktop` file, SlackBuild | packaging | M | open |
+| 11 | Icon, `.desktop` file, SlackBuild | packaging | M | **partly done**: icon and `.desktop` landed, SlackBuild open |
| 13 | No visual feedback that an action stuck | feedback | S | **done** |
+| 14 | Tag column unreadable, tags need another home | presentation | M | **done** |
Sizes are rough: XS under an hour, S a sitting, M a session.
@@ -398,6 +399,43 @@ and inspected: normal, unread, deleted, spam, and deleted-plus-unread rows.
---
+## 14. Tag column unreadable
+
+**Observed:** with tags spelled out per row the column ran to 500 pixels of
+mostly repeated text ("account-privateemail-danilo.macri attachment flagged
+inbox passed replied"), dominated by the account prefix, and consumed most of
+the list's width.
+
+**Cause:** presentation, not data. 96 tags in this database, many hierarchical
+(`shopping/amazon`, `mailing-list/SBo`), rendered as a joined string.
+
+**Approach:** the column is gone. Tags now render as coloured chips in two
+places, split by taxonomy:
+
+- The **account tag** says which mailbox a thread came from. It draws as a chip
+ in front of the subject, coloured and labelled from its own `[account.<key>]`
+ stanza via new `color` and `label` keys. `label` is display-only; the notmuch
+ tag is never renamed.
+- **Functional tags** say what state a thread is in. They fill a single row
+ under the message pane, with overflow collapsing into a `+N` chip whose
+ tooltip lists the hidden ones. A single row keeps the message area from
+ shifting between threads with different tag counts.
+
+Colours resolve exact tag first, then top-level prefix, so one `shopping` entry
+covers the hierarchy without listing all 96. Unconfigured tags fall back to a
+hash of the name, stable so a chip never changes colour as the list scrolls.
+
+**Defect found while building:** QSettings treats `/` in a key as a group
+separator, so `shopping/amazon` becomes a nested key that `childKeys()` never
+returns. Reading `[tagcolors]` with `childKeys()` silently dropped every
+hierarchical tag, and each fell through to its prefix colour. Fixed by reading
+`allKeys()`, with a regression test. The same gotcha is already documented in
+`CLAUDE.md` for `[account.work]` section names.
+
+**Deferred:** clicking a chip to search that tag. Display only for now.
+
+---
+
## Deferred, unsized, or split out
Items noted while triaging but not part of the original list. Same numbering
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 7e4cea8..26cb37c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -6,6 +6,9 @@ add_library(qtmaildir_lib STATIC
htmlbuilder.cpp
cidschemehandler.cpp
notmuchworker.cpp
+ tagchip.cpp
+ tagcolors.cpp
+ tagstrip.cpp
threadlistmodel.cpp
mailsync.cpp
threadcidmap.cpp
@@ -20,7 +23,18 @@ target_include_directories(qtmaildir_lib
target_link_libraries(qtmaildir_lib
PUBLIC Qt6::Widgets Qt6::WebEngineWidgets PkgConfig::GMIME ${NOTMUCH_LIBRARY})
-add_executable(qtmaildir main.cpp)
+# resources.qrc belongs to the executable, not to the static library. A qrc
+# compiled into a .a registers itself from a global initialiser, and the linker
+# drops that object because nothing references it, so the resource silently
+# fails to exist at runtime.
+add_executable(qtmaildir main.cpp resources.qrc)
target_link_libraries(qtmaildir PRIVATE qtmaildir_lib)
install(TARGETS qtmaildir RUNTIME DESTINATION bin)
+
+# The icon goes into the hicolor theme under its scalable directory, which is
+# where a desktop environment looks for the Icon= name in the .desktop entry.
+install(FILES ${CMAKE_SOURCE_DIR}/assets/icons/qtmaildir.svg
+ DESTINATION share/icons/hicolor/scalable/apps)
+install(FILES ${CMAKE_SOURCE_DIR}/assets/qtmaildir.desktop
+ DESTINATION share/applications)
diff --git a/src/main.cpp b/src/main.cpp
index 4d3ed0a..231594f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -17,6 +17,7 @@
*/
#include <QApplication>
+#include <QIcon>
#include <QMessageBox>
#include <QWebEngineUrlScheme>
@@ -76,6 +77,13 @@ int main(int argc, char *argv[])
app.setOrganizationName(QStringLiteral("qtmaildir"));
app.setApplicationVersion(QStringLiteral(QTMAILDIR_VERSION));
+ // Compiled in rather than read from disk, so the icon is there whether or
+ // not the app was installed. setDesktopFileName() is what lets a Wayland
+ // compositor match the window to its .desktop entry, which is where the
+ // taskbar icon really comes from there.
+ app.setWindowIcon(QIcon(QStringLiteral(":/icons/qtmaildir.svg")));
+ app.setDesktopFileName(QStringLiteral("qtmaildir"));
+
// Fail loudly on an ABI mismatch rather than crashing later.
if (LIBNOTMUCH_MAJOR_VERSION < 5) {
QMessageBox::critical(nullptr, QObject::tr("qtmaildir"),
diff --git a/src/resources.qrc b/src/resources.qrc
new file mode 100644
index 0000000..7bdb592
--- /dev/null
+++ b/src/resources.qrc
@@ -0,0 +1,6 @@
+<!DOCTYPE RCC>
+<RCC version="1.0">
+ <qresource prefix="/">
+ <file alias="icons/qtmaildir.svg">../assets/icons/qtmaildir.svg</file>
+ </qresource>
+</RCC>