From 029a50e2466f4f8858fc94ece17ffaf2841a8c1d Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sat, 8 Aug 2026 11:36:06 +0200 Subject: docs: record message rows, and the user's verdict on them CLAUDE.md described a QTableView over a table model, which has not been true since the view port. Updated with the traps the port produced, each of which shipped a plausible-looking broken build before being caught: - A tree numbers rows per parent, so nothing may be keyed on a row NUMBER. - drawBranches runs before the row's cells, so an expander on a content column is painted over by the delegate's background. - setRootIsDecorated(false) removes the style's HIT AREA along with its indicator, leaving a glyph that renders and does nothing. - isExpanded and setExpanded are keyed on column 0. - A reply's indent must beat the account chip's width, and visualRect reports the indent correctly even when nothing is visibly indented. - paintEvent runs after the cells, so a full-row fill erases their text. Also the notmuch ownership rule, which is a double-free if undone: messages reached through a thread are freed with it, so walkReplies holds them raw against this file's own RAII convention. Item 20 is marked built, not done, and item 53 records why. The user's verdict on the finished result was that the table view does not fit the use, said with every cue in and working. That is a design finding rather than a defect: the item shipped exactly what its four decisions specified, and all four were the user's own choices. Recording it as a defect would misattribute the cause; recording nothing would leave the next session building on a rejected design. Item 53 carries the cause verified in code rather than guessed. A message row fills the same five columns as a thread row (threadlistmodel.cpp:275-283 mirroring :428-431), so replies land on the same rigid column boundaries as the threads around them, and the eye reads columns before indentation or tint. The reference the user gave has no column rules through its reply rows at all, and that absence is the one thing three added cues cannot supply. --- .../plans/2026-08-03-post-0.1.0-usability.md | 76 +++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) (limited to 'docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md') 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 a925cd8..9afbd7a 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 @@ -68,7 +68,7 @@ taking that too literally. | 17 | No completion for tags in the query bar | workflow | M | **done** | | 18 | No visual cue that there are unsynced edits | feedback | S | **done** | | 19 | No prompt to sync on exit when edits are pending | behavior | S | **done** | -| 20 | Thread view does not match the user's mental model | presentation | L | open, specified 2026-08-08, branch `item-20-message-rows` | +| 20 | Thread view does not match the user's mental model | presentation | L | **built 2026-08-08**, branch `item-20-message-rows`; see 53 | | 21 | Default shortcuts are not sensible enough | discoverability | S | open | | 22 | Translatability audit and i18n wiring | correctness | M | open | | 23 | No way to save a search query from the UI | workflow | M | open | @@ -101,6 +101,7 @@ taking that too literally. | 50 | Esc blanks the pane but leaves the row selected | workflow | XS | **done** | | 51 | Clicking a subject scrolls the list sideways | presentation | XS | open | | 52 | `test_querycompleter` fails under Wayland, passes offscreen | testing | XS | **done** | +| 53 | Message rows still read as a table, not as a conversation | presentation | ? | open, unspecified | Sizes are rough: XS under an hour, S a sitting, M a session. @@ -1231,6 +1232,18 @@ is centralised rather than smeared across call sites. warrant a feature branch (`item-20-message-rows`, 2026-08-08). It is a left-pane model and view replacement plus a selection-scope change, not a refactor. +**Built 2026-08-08 across ten tasks**, all four decisions implemented: message +rows in the left pane, the root row as the thread's first message, replies +indented by depth, action scope following the selected row kind and named in the +status bar. 15 test binaries green, 65 tests in `test_mainwindow` alone, ten +mutation checks. + +**The user's verdict on the result: not convinced it fits.** Recorded here +rather than in a commit message, because the item shipped working and the +reservation is about the DESIGN, not about a defect. See item 53, which carries +the diagnosis. Do not treat item 20 as a success story to build on without +reading it. + **A cheaper alternative was offered and declined.** Collapsible `
` blocks per message inside the existing single-document message pane would have delivered per-message inspection at size S, touching only `htmlbuilder.cpp` and @@ -3147,6 +3160,67 @@ popup how it likes. makes the width guard fail with its explanation, where before the change that case would have passed. +## 53. Message rows still read as a table, not as a conversation + +**Observed (user, 2026-08-08)**, on the finished item 20: *"I'm not very +convinced about this session's work. I don't think the table view fits our +use."* Said after the expander, the indent, the thread spine, the tint and the +dimmed text were all in and working, so it is not a report that a cue is +missing. It is a judgement on the result. + +**This is a design finding, not a defect.** Item 20 shipped exactly what its +four decisions specified and every one of them was the user's own choice. The +work is sound; what it produced is not what was wanted. Recording it as a defect +would misattribute the cause, and recording nothing would leave the next session +building on a design the user has already rejected. + +**Cause (verified in code, 2026-08-08).** A message row fills the SAME five +columns as a thread row: `ThreadListModel::data` answers `DateColumn`, +`AuthorsColumn` and `SubjectColumn` for message rows at +`src/threadlistmodel.cpp:275-283`, mirroring the thread-row branch at +`:428-431`. One model, one column grid, both row kinds. So every reply lands on +the same rigid column boundaries as the threads around it, and the eye reads +columns before it reads indentation or tint. + +Compare the reference the user gave. In the Thunderbird screenshots the reply +rows carry sender and date only, laid out freely on a plain band, with no column +rules running through them. The structure comes from the ABSENCE of the grid, +which is the one thing three added cues cannot supply. + +Two aggravating details, both visible in the 2026-08-08 screenshots: + +- Every reply repeats `Re: `, near-identical down the + whole block, which is exactly the visual signature of a table of records. + Dropping the redundant prefix was offered and not chosen; it is worth + revisiting first because it is the cheapest of these by a wide margin. +- Reply rows keep the same row height as thread rows, since + `setUniformRowHeights(true)` is required for the tag strip's band arithmetic + (`src/mainwindow.cpp`). A conversation view would want tighter replies. + +**Approach: unspecified, and deliberately so.** Ask the user what to change +before proposing anything, exactly as item 20 required. The plausible directions +differ enormously in cost and are not interchangeable: + +1. **Span the columns for message rows.** Draw a reply as one free-form band + (sender, date, no grid) rather than as cells. `QTreeView::setFirstColumnSpanned` + does this per row without a second model. Cheapest real change, keeps + everything else built. +2. **Drop the `Re:` prefix and shrink what a reply shows.** XS on its own, and + worth trying before anything structural. +3. **Two different row shapes.** A delegate that paints a reply row entirely + itself, ignoring the columns. More control, and it fights `uniformRowHeights`. +4. **Abandon message rows in the list** and revisit the `
`-per-message + design in the message pane, which was offered at size S on 2026-08-08 and + declined in favour of this. Item 20's work would largely be reverted. + +**Constraint that shapes all of them.** The tag strip is why `ThreadListView` +exists, and its band arithmetic assumes a uniform row height and a known column +layout. Anything that varies row height or removes columns for one row kind has +to answer for the strip on thread rows, which must not change. + +**Do not start any of these from this description.** It says what is and why, +not what was wanted. + ## Deferred, unsized, or split out Items noted while triaging but not part of the original list. Same numbering -- cgit v1.2.3