diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-07 17:51:52 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-07 17:51:52 +0200 |
| commit | 334b510e2673a6ab3875ffa7a4c5b3b2dd09a369 (patch) | |
| tree | 59b38af32af41d629a0c02ecd8e0fcfcddbb60e6 /docs | |
| parent | 6003bab2d4c491a857b7a728f2f23c719723ef1a (diff) | |
| download | qtmaildir-334b510e2673a6ab3875ffa7a4c5b3b2dd09a369.tar.gz qtmaildir-334b510e2673a6ab3875ffa7a4c5b3b2dd09a369.zip | |
feat(ui): fill the blank message pane with a branded placeholder
An empty right pane said nothing, and multi-select made it a routine
sight. It now carries the wordmark, thread counts that run their query
when clicked, and a sync line that appears only when something needs
attention.
Rendered into the existing web view as a third document shape, so there
is one document path and one set of security rules. The brand palette is
a deliberate exception to deriving colours from the desktop theme, since
a logo is brand rather than chrome; the theme still picks which of the
two sets is used.
Counts refresh when the pane is about to show rather than in the
background: one goes stale the moment a tag is edited, and refreshing one
nobody is looking at is work for nothing. A generation counter discards a
superseded reply, and a late answer cannot repaint over an opened thread.
The helper lines are real links because JavaScript is off in this
profile. The handler is gated on the placeholder actually being
displayed, so the same URL inside a message body is dropped: a stranger's
mail must not drive the thread list, even to run a harmless query.
Three defects found while building, all silent:
- Every CSS percentage was invalid. QString::arg does not collapse "%%"
into "%", so the document carried "50%%" and the browser dropped each
declaration holding one, disabling the mask, the glow and both radial
gradients while still rendering something plausible. Substitution is by
named token now, which cannot collide with a percent sign.
- A geometry probe endorsed the layout while that was live, because it
measured only properties without percentages.
- The font test passed against a build with one face missing, since the
other satisfied both of its checks on its own.
The mockup's light values needed correcting against a real pane: the grid
vanished at a 2% luminance step on white, and the glow subtracts light
there rather than adding it, washing the pane. Strength only, not hue.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 84 |
1 files changed, 83 insertions, 1 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 cba4e81..e82c8fc 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 @@ -78,7 +78,7 @@ taking that too literally. | 27 | The UI cannot see a sync it did not start | feedback | S | **done** | | 28 | Re-adding `unread` counts 2 unsynced changes, not 0 | correctness | S | **done** | | 29 | Sync button stays enabled during a background sync | feedback | XS | **done** | -| 30 | The blank right pane is wasted space | presentation | M | open | +| 30 | The blank right pane is wasted space | presentation | M | **done** | | 31 | The quit prompt has no highlighted default button | discoverability | XS | **done** | | 32 | Esc does not blank the right pane | workflow | XS | **done** | | 33 | Status bar messages never expire | feedback | S | **done** | @@ -1712,6 +1712,88 @@ pane. The **helper counts are the largest piece**: three `notmuch count` calls crossing to the worker, plus a refresh policy, since a count goes stale the moment a tag is edited. +### Outcome (done 2026-08-07) + +Built as specified, and confirmed against the running application in both +themes. Decisions taken while building, and three defects worth recording. + +**The counts refresh when the pane is about to show**, chosen by the user over +refreshing on the tag-list triggers or only after a sync. `showPlaceholderPane()` +is the single route to a blank pane, so the numbers are fetched exactly when +they are about to be read and never in the background. A generation counter +discards a superseded reply, and `onCountsReady` repaints only while the +placeholder is still displayed, so a late answer cannot replace an opened +thread. Counts are of THREADS, matching what a click on the line produces. + +**The helper lines are real links**, since JavaScript is off in this profile and +a count cannot be clickable any other way. They carry a `qtmaildir-query:` URL +caught in `acceptNavigationRequest`, and **the handler is gated on the +placeholder actually being displayed**. A message body is attacker-controlled +HTML and can carry the same URL; without the gate a link in a stranger's mail +could drive the thread list. The consequence would be mild (a query runs, +nothing mutates or is sent) but it is a boundary worth keeping shut. +`showThread`, `clear` and `showError` all close the gate, and a test asserts it. + +**Sizes are clamped, not fixed and not fluid**, per the user's "a mix of both". +The pane is a splitter panel whose width runs from a couple of hundred pixels to +most of a screen. Measured: the wordmark renders 26px in a 300px pane and 57.6px +in a 900px one, with `bodyScrollW == clientW` at every width tested. + +#### Three defects, all invisible + +1. **Every CSS percentage was invalid, and the pane still looked plausible.** + The stylesheet was built with `QString::arg` and `%%` for each percentage, + but **`arg()` does not collapse `%%` into `%`**: the document reached the + browser carrying `50%%`, and every declaration containing one was dropped. + That silently disabled the grid mask, the glow and both radial gradients. + The pane rendered, and the flat result read as "close but not like the + mockup" rather than as a fault. Fixed by substituting **named tokens** + (`@ACCENT@`, `@GRID@`) with `replace()`, which cannot collide with a percent + sign. `placeholderStyleHasNoUnsubstitutedTokens` pins it. + +2. **A geometry probe confirmed the layout while that bug was live.** It + measured `.title`, `.content` and `.icon-tile`, none of which carry a + percentage, so it reported everything correct. This is the failure mode + `CLAUDE.md` already warns about, in a new costume: the probe found what it + looked for and was trusted to report what it never checked. **A probe over a + stylesheet must assert the properties that the suspected fault would break**, + not the ones that happen to be convenient to measure. + +3. **The font test passed against a broken build.** Pointing one `@font-face` + at a nonexistent resource survived it: the other face alone satisfied both + the `data:font/woff2` check and the document-size check. Rewritten to require + both faces with a payload each, and verified by mutation. + +#### The mockup's light values do not survive a real pane + +Only the dark set ported cleanly. Rendered side by side, three light values +failed, all of them contrast rather than hue, because the mockup is a full-bleed +1920x1080 render and this is a pane against white: + +- The grid at `#d9dfe8` on white is roughly a 2% luminance step and vanished + outright, where `#182840` on `#060b10` reads clearly at the same opacity. +- The glow **subtracts** light on a light background instead of adding it, so + 14% washed most of the pane purple. +- The tile at `#f0f3f7` inside a `#d9dfe8` border did not separate from the + background, leaving the icon floating. + +`glowAlpha` and `gridOpacity` are therefore per-set rather than shared, and +`tileBorder` is its own colour rather than reusing `grid`. Landed at +`#b9c4d4` @ 45% for the grid, 6% for the glow, `#c7d0dd` for the tile border. +The dark set is unchanged. + +**The mask and the glow are sized relative to the pane**, the one deliberate +departure from the mockup's numbers. Its `circle` (farthest-corner) mask +completes its fade inside a 1920x1080 frame; the same ratio in a ~990x650 pane +puts the fade past the corners, so the grid ran uniform to the edges. +`closest-side` pins it to the nearer edge, and the glow is `min(95%, 900px)` +rather than a flat 900px that was taller than a short pane. + +**`resources.qrc` is now compiled into every test binary.** Library code reads +`:/fonts/` and a qrc inside the static library registers from a global +initialiser the linker drops, so without this a test would exercise only the +missing-resource fallback and pass against a broken build. + ## 31. The quit prompt has no highlighted default button **Observed (user, 2026-08-04):** "quit popup has no Predefined answer (there's |
