Pushover renders the notification body in a proportional font, so the
space-padded label/value columns from #64 never lined up on a phone. The body
collapsed into a ragged flat list with no visual grouping. The html variant
was worse: the padding sat inside the HTML, so it collapsed to a single space.
What changed
Each counter now renders as emoji value label, grouped two per line and
joined by a middle dot:
value leads — the numbers are the first thing the eye lands on
html style now bolds the values instead of the labels
Non-protected reads as public, matching the non_protected_tag
use_emoji now defaults to true; false gives the plain variant
dry runs read 7 would be removed
8 lines -> 5
_notify_row / LABEL_WIDTH / VALUE_WIDTH are replaced by _stat_row.
New docs/notify-preview.py renders the realbuild_summary() output as a
phone-width PNG in a proportional font, so layout changes can be reviewed
visually instead of guessed at. docs/notify-preview.png is that render.
test_build_summary_body_does_not_repeat_title_phrase — body must not
restate the title verbatim
## Why
Pushover renders the notification body in a **proportional font**, so the
space-padded label/value columns from #64 never lined up on a phone. The body
collapsed into a ragged flat list with no visual grouping. The `html` variant
was worse: the padding sat *inside* the HTML, so it collapsed to a single space.
## What changed
Each counter now renders as `emoji value label`, grouped two per line and
joined by a middle dot:
```
qbit-maid: 7 torrents removed
Scanned 42 torrents in 12s
🗑️ 7 removed · 👻 4 orphaned
⏭️ 5 ignored · 🐣 3 premature
🛡️ 10 protected · 🌐 20 public
```
- **value leads** — the numbers are the first thing the eye lands on
- `html` style now bolds the **values** instead of the labels
- `Non-protected` reads as `public`, matching the `non_protected_tag`
- `use_emoji` now defaults to `true`; `false` gives the plain variant
- dry runs read `7 would be removed`
- 8 lines -> 5
`_notify_row` / `LABEL_WIDTH` / `VALUE_WIDTH` are replaced by `_stat_row`.
New `docs/notify-preview.py` renders the **real** `build_summary()` output as a
phone-width PNG in a proportional font, so layout changes can be reviewed
visually instead of guessed at. `docs/notify-preview.png` is that render.
## Tests
`python3 -m unittest test_qbitmaid test_write_csv` -> **58 pass** (was 51 on `main`).
New guards:
- `test_build_summary_has_no_space_padding` — fails if any run of 3+ spaces
reappears in either style, with or without emoji
- `test_build_summary_groups_two_counters_per_line` — exactly 3 rows, one
separator each
- `test_build_summary_value_leads_label` — `7 removed`, never `Removed 7`
- `test_build_summary_emoji_off_has_no_emoji` — `use_emoji = false` emits none
- `test_build_summary_body_does_not_repeat_title_phrase` — body must not
restate the title verbatim
Pushover renders the notification body in a proportional font, so the
space-padded label/value columns never lined up on a phone — the body
collapsed into a ragged flat list with no visual grouping. The html
variant was worse: the padding sat inside the HTML, so it collapsed to a
single space.
Counters are now grouped two per line and joined with a middle dot, which
survives any font. The body drops from 8 lines to 5 and no longer restates
the outcome the title already carries.
- replace _notify_row/LABEL_WIDTH/VALUE_WIDTH with _stat_row
- add docs/notify-preview.py, which renders the real build_summary()
output as a phone-width PNG so layout changes can be reviewed visually
- tests: assert no space padding, two counters per row, and that the body
does not repeat the title
Switch the summary to the layout jblu picked: each counter renders as
"emoji value label", grouped two per line and joined by a middle dot.
Scanned 42 torrents in 12s
🗑️ 7 removed · 👻 4 orphaned
⏭️ 5 ignored · 🐣 3 premature
🛡️ 10 protected · 🌐 20 public
- the value leads, so the numbers are the first thing the eye lands on
- html style now bolds the values instead of the labels
- "Non-protected" reads as "public", matching the non_protected_tag
- use_emoji now defaults to true; false gives the plain variant
- dry runs read "7 would be removed"
trexBot
changed title from fix(notify): group counters instead of padding columns to feat(notify): value-first emoji rows2026-11-03 15:10:08 -06:00
trexBot
merged commit 5b54d9e558 into main2026-11-03 15:19:40 -06:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Why
Pushover renders the notification body in a proportional font, so the
space-padded label/value columns from #64 never lined up on a phone. The body
collapsed into a ragged flat list with no visual grouping. The
htmlvariantwas worse: the padding sat inside the HTML, so it collapsed to a single space.
What changed
Each counter now renders as
emoji value label, grouped two per line andjoined by a middle dot:
htmlstyle now bolds the values instead of the labelsNon-protectedreads aspublic, matching thenon_protected_taguse_emojinow defaults totrue;falsegives the plain variant7 would be removed_notify_row/LABEL_WIDTH/VALUE_WIDTHare replaced by_stat_row.New
docs/notify-preview.pyrenders the realbuild_summary()output as aphone-width PNG in a proportional font, so layout changes can be reviewed
visually instead of guessed at.
docs/notify-preview.pngis that render.Tests
python3 -m unittest test_qbitmaid test_write_csv-> 58 pass (was 51 onmain).New guards:
test_build_summary_has_no_space_padding— fails if any run of 3+ spacesreappears in either style, with or without emoji
test_build_summary_groups_two_counters_per_line— exactly 3 rows, oneseparator each
test_build_summary_value_leads_label—7 removed, neverRemoved 7test_build_summary_emoji_off_has_no_emoji—use_emoji = falseemits nonetest_build_summary_body_does_not_repeat_title_phrase— body must notrestate the title verbatim
Switch the summary to the layout jblu picked: each counter renders as "emoji value label", grouped two per line and joined by a middle dot. Scanned 42 torrents in 12s 🗑️ 7 removed · 👻 4 orphaned ⏭️ 5 ignored · 🐣 3 premature 🛡️ 10 protected · 🌐 20 public - the value leads, so the numbers are the first thing the eye lands on - html style now bolds the values instead of the labels - "Non-protected" reads as "public", matching the non_protected_tag - use_emoji now defaults to true; false gives the plain variant - dry runs read "7 would be removed"fix(notify): group counters instead of padding columnsto feat(notify): value-first emoji rows