Skip to content

Sidebar instantiates every folder row once per account (accounts × rows) #5

Description

@tilladam

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions