diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-07 16:28:52 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-07 16:28:52 +0200 |
| commit | 20168b4a9a4763ea70ab53e276be381a63ba5436 (patch) | |
| tree | 1dbedbd2b1fe4849acd7a2bc119f080ae32eb294 | |
| parent | 39cbde74a560407e24b05e171df883421aa2153e (diff) | |
| download | qtmaildir-20168b4a9a4763ea70ab53e276be381a63ba5436.tar.gz qtmaildir-20168b4a9a4763ea70ab53e276be381a63ba5436.zip | |
docs: record how rendering probes mislead, and ThreadListView
A session was spent chasing a defect that did not exist, because three
separate rendering probes returned confidently wrong results and each
was believed until contradicted. The specific failure modes are worth
recording, since the next attempt will otherwise rediscover them:
counting lit pixels cannot tell bold from regular in either direction,
viewport()->render() returns blank images in several ordinary
situations, and a "saturated pixel" threshold catches the antialiased
edge of the selection highlight and passes whatever the code does.
The bug that started it was not in the code at all: the desktop's Qt
font was configured Bold in qt6ct, so every row rendered bold and
setBold() changed nothing.
Also records ThreadListView in the architecture diagram, with the three
consequences of painting a row-wide strip in a per-cell view that are
easiest to undo by accident.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| -rw-r--r-- | CLAUDE.md | 46 |
1 files changed, 42 insertions, 4 deletions
@@ -42,7 +42,8 @@ UI thread Worker thread MainWindow NotmuchWorker ├ query row: QComboBox, QLineEdit, └ owns the only notmuch_database_t* │ saved-query QPushButtons - ├ QTableView ── ThreadListModel (SubjectDelegate) + ├ ThreadListView ── ThreadListModel + │ (RowStyleDelegate every column, SubjectDelegate on Subject) └ MessageView (header QLabel, QWebEngineView, attachment bar, TagStrip) Config (INI) KeyMap MailSync (QProcess) MimeParser (GMime) @@ -53,9 +54,21 @@ The query row and the message-pane header are **built inline in `MainWindow` and `MessageView`**, not as named widget classes. Earlier revisions of this diagram listed `QueryBar`, `SavedQueryBar`, `HeaderWidget` and `AttachmentBar`; none of those types have ever existed, and looking for them wastes a search. The widget -classes that do exist are `MessageView`, `TagStrip`, `TagDialog` and -`SubjectDelegate`; `TagChip` is a namespace of painting helpers, not a widget, -and `ThreadCidMap` is a struct. +classes that do exist are `MessageView`, `ThreadListView`, `TagStrip`, +`TagDialog`, `RowStyleDelegate` and `SubjectDelegate`; `TagChip` is a namespace +of painting helpers, not a widget, and `ThreadCidMap` is a struct. + +**`ThreadListView` exists because a delegate cannot paint outside its column.** +The tag chips under each row are one strip spanning the whole width, so they +are drawn in the view's `paintEvent` after the cells. Consequences that are +easy to undo by accident: `SubjectDelegate` reads `AccountLabelRole`, which +belongs to the ROW, so installing it view-wide draws the account chip into +every column (a `Q_ASSERT` catches this); row height must be set on the +vertical header, since a table takes one height per row and a column's +`sizeHint` only applies if the view happens to ask that column; and because +alternating colours, the selection and the model's `BackgroundRole` are all +painted per cell, the view has to fill the strip's band itself, honouring all +three or a deleted row is cut in half and every other row shows a bare stripe. **No `notmuch_*` pointer ever crosses the thread boundary.** Data crosses as the plain value structs in `src/types.h` (`ThreadSummary`, `MessageRef`, `TagChange`), over queued @@ -157,6 +170,31 @@ it. Verify against a real keyboard before changing a default on reachability gro separate, real trap `normalizeSequence()` handles is a **bare capital** (`N` parses to unshifted Key_N, which no keystroke emits). +**Rendering probes lie in specific, repeatable ways.** A whole session was spent chasing +a defect that did not exist because of these; each was believed until it was contradicted. + +- **Counting lit pixels cannot tell bold from regular.** Antialiasing lights a similar + number either way, so an "ink count" reads identical whichever is true, in both + directions. It measures nothing. **Text width** distinguishes weights (277px against + 306px for one string at 12pt), and a **strict pixel diff** distinguishes renders. +- **`viewport()->render()` returns a blank image** in several ordinary situations: before + the widget is exposed, when the content sits outside a viewport narrower than the + columns, and sometimes with no discernible cause. A probe that reports "no ink anywhere" + is far more likely broken than the code it is testing. Check that it finds the thing it + expects to find *before* trusting it to report the thing it expects to miss. +- **A "saturated pixel" threshold catches antialiased edges of the selection highlight**, + hundreds of distinct near-background colours, and will pass whatever the code does. Match + the exact colours the model supplies instead. Two versions of one test passed under + mutation before this was noticed. +- Every rendering test needs a **mutation check** and a guard proving it *can* fail: assert + the geometry it depends on (a column is on screen, a row has non-zero height) rather than + assuming it. + +The bug that started all this was not in the code at all: the desktop's Qt font was +configured **Bold** in qt6ct, so every row rendered bold and `setBold(true)` changed +nothing. Before concluding a Qt facility is broken, check the desktop's own font and theme +configuration. + ## Web view security The most security-sensitive area: a browser engine pointed at input from strangers. Do not |
