MailSidebar renders one section per account, and inside that loop it iterates the
global mailbox row list, hiding rows belonging to other accounts. Instantiation
grows as accounts × total rows. Five accounts of 150 folders build 3,775
SidebarItems to show 750 rows.
The output is correct — this is wasted construction and layout, repeated on every
sidebar refresh. Line references are against 3e10ba7.
Where
ui/components/sidebar.slint:613 opens the per-account loop; :649 nests the global
list inside it:
for account in root.account-mailboxes : VerticalBox {
...
for mailbox in root.mailboxes : SidebarItem {
visible: !mailbox.is_account && mailbox.context == account.context;
root.mailboxes is the flat, all-accounts list from make_mailbox_rows
(src/mail_view_model.rs:1055), with collapsed subtrees already filtered out.
Measurement
A harness instantiating MailSidebar with literal MailboxRow arrays (synthetic
data), rendered headless via slint-viewer 1.17.1 --style fluent --screenshot; time
covers parse + item construction + layout + one frame, two runs each, ±10 ms.
| Case |
Visible rows |
SidebarItems built |
Time |
| 1 account × 750 folders |
750 |
751 |
0.13 s |
| 5 accounts × 150 folders |
750 |
3,775 |
0.29 s |
Same content in both; only the multiplier differs. The item counts are exact; the
timings are indicative only, since slint-viewer interprets rather than compiles.
Not a startup-only cost: set_mailboxes / set_account_mailboxes
(src/mail_view_model.rs:365, :862) install a new ModelRc on every refresh, so
the tree is rebuilt each time — 23 call sites, including sync-driven updates.
What this is not
The hidden rows do not inflate the scroll extent; the
height: root.visible ? root.row-height : 0px treatment works. Control test, same
120 visible rows, 369 vs 121 instantiated items: thumb 92 px vs 96 px. So a tiny
scrollbar thumb means genuinely many visible rows, and fixing this will not by itself
make a large folder tree scroll smoothly — that needs virtualization.
Two smaller findings
make_account_mailbox_rows (:1147) calls make_mailbox_rows and filters it, so
the full list is built twice per refresh.
ui/views/mail-view.slint instantiates three MailSidebars (:142, :315,
:516) gated by visible: rather than if, so all three build a full item tree.
Not a drop-in fix: minimal-sidebar is referenced by id at :356/:366/:423,
and each sidebar holds local section open state that an if would reset.
Suggested fix
Give the sidebar accounts that already carry their own rows, so the inner loop
iterates a per-account list:
export struct MailAccountSection {
account: MailboxRow,
folders: [MailboxRow],
}
Slint structs accept array fields (verified with slint-viewer --check). Grouping
once on the Rust side also removes the double construction above. Rendering is
unchanged, since row pitch lives in row-height rather than in layout spacing.
Implemented locally against 3e10ba7: +114/−51 across ui/models.slint,
ui/components/sidebar.slint, ui/views/mail-view.slint, ui/app.slint,
src/mail_view_model.rs, plus a unit test. fmt, clippy -D warnings,
test --workspace --locked and slint-viewer --check ui/app.slint pass; 5 × 150
drops to 755 items (0.12 s), and 3 × 40 renders pixel-identical to current main.
Happy to open it as a PR, or to drop it if you would rather keep the flat model.
Follow-ups, not part of that change
- Virtualization — a flattened row model in a
ListView. Needs the per-section
open state (currently local Slint state) hoisted into Rust as a collapsed-id set,
like collapsed_folder_ids.
- The three always-instantiated sidebars, and the per-refresh
ModelRc teardown.
- Cosmetic: account rows are contiguous 31 px bands while unified/categories/labels
rows are 28 px pills with 3 px gaps, because account rows fold their spacing into
row-height to keep hidden rows at zero height. Normalizable once they're gone.
macOS 15, slint-viewer 1.17.1, repo at 3e10ba7. No real mailbox data used.
Disclosure: investigated and patched with generative AI assistance (Claude Code); I
have reviewed every line and reproduced every number here.
MailSidebarrenders one section per account, and inside that loop it iterates theglobal mailbox row list, hiding rows belonging to other accounts. Instantiation
grows as accounts × total rows. Five accounts of 150 folders build 3,775
SidebarItems to show 750 rows.The output is correct — this is wasted construction and layout, repeated on every
sidebar refresh. Line references are against
3e10ba7.Where
ui/components/sidebar.slint:613opens the per-account loop;:649nests the globallist inside it:
root.mailboxesis the flat, all-accounts list frommake_mailbox_rows(
src/mail_view_model.rs:1055), with collapsed subtrees already filtered out.Measurement
A harness instantiating
MailSidebarwith literalMailboxRowarrays (syntheticdata), rendered headless via
slint-viewer 1.17.1 --style fluent --screenshot; timecovers parse + item construction + layout + one frame, two runs each, ±10 ms.
SidebarItems builtSame content in both; only the multiplier differs. The item counts are exact; the
timings are indicative only, since
slint-viewerinterprets rather than compiles.Not a startup-only cost:
set_mailboxes/set_account_mailboxes(
src/mail_view_model.rs:365,:862) install a newModelRcon every refresh, sothe tree is rebuilt each time — 23 call sites, including sync-driven updates.
What this is not
The hidden rows do not inflate the scroll extent; the
height: root.visible ? root.row-height : 0pxtreatment works. Control test, same120 visible rows, 369 vs 121 instantiated items: thumb 92 px vs 96 px. So a tiny
scrollbar thumb means genuinely many visible rows, and fixing this will not by itself
make a large folder tree scroll smoothly — that needs virtualization.
Two smaller findings
make_account_mailbox_rows(:1147) callsmake_mailbox_rowsand filters it, sothe full list is built twice per refresh.
ui/views/mail-view.slintinstantiates threeMailSidebars (:142,:315,:516) gated byvisible:rather thanif, so all three build a full item tree.Not a drop-in fix:
minimal-sidebaris referenced by id at:356/:366/:423,and each sidebar holds local section open state that an
ifwould reset.Suggested fix
Give the sidebar accounts that already carry their own rows, so the inner loop
iterates a per-account list:
Slint structs accept array fields (verified with
slint-viewer --check). Groupingonce on the Rust side also removes the double construction above. Rendering is
unchanged, since row pitch lives in
row-heightrather than in layout spacing.Implemented locally against
3e10ba7: +114/−51 acrossui/models.slint,ui/components/sidebar.slint,ui/views/mail-view.slint,ui/app.slint,src/mail_view_model.rs, plus a unit test.fmt,clippy -D warnings,test --workspace --lockedandslint-viewer --check ui/app.slintpass; 5 × 150drops to 755 items (0.12 s), and 3 × 40 renders pixel-identical to current
main.Happy to open it as a PR, or to drop it if you would rather keep the flat model.
Follow-ups, not part of that change
ListView. Needs the per-sectionopenstate (currently local Slint state) hoisted into Rust as a collapsed-id set,like
collapsed_folder_ids.ModelRcteardown.rows are 28 px pills with 3 px gaps, because account rows fold their spacing into
row-heightto keep hidden rows at zero height. Normalizable once they're gone.macOS 15,
slint-viewer1.17.1, repo at3e10ba7. No real mailbox data used.Disclosure: investigated and patched with generative AI assistance (Claude Code); I
have reviewed every line and reproduced every number here.