Context
PR #104 landed sub-group 1 of the folder-listing hardening sweep (#91, #92, #95, #98). The #92 work is partial: the LIST-STATUS capability flag and the public shape of Connection::list_folders_with_status are plumbed, but the actual single-round-trip wiring is deferred because `async-imap` does not yet expose the extended LIST command.
What exists today (on `main`)
- `rimap_imap::connection::Connection::has_list_status_capability() -> bool` — probed post-login alongside `MOVE` / `UIDPLUS` via `caps.has_str("LIST-STATUS")`.
- `rimap_imap::connection::Connection::list_folders_with_status(pattern: &str) -> Result<Vec<(Folder, Option)>, ImapError>` — public async method. Always takes the per-folder-STATUS fallback path regardless of the capability.
- `rimap_imap::ops::folders::list_with_status(session, pattern, _has_list_status: bool)` — the inner helper. The `_has_list_status` argument is reserved for the dispatch flip.
- `rimap_server::tools::admin::list_folders::handle` already calls `list_folders_with_status` and will pick up the optimization automatically.
What this issue tracks
Once `async-imap` exposes LIST-STATUS — either through a dedicated `Session::list_status` method or through generic raw-command machinery that cleanly parses interleaved `STATUS` response-data lines alongside `LIST` — flip the dispatch in `list_with_status` to issue `LIST "" RETURN (STATUS (MESSAGES UIDVALIDITY UNSEEN))` as a single round-trip when `has_list_status` is true.
Acceptance criteria
-
When the server advertises `LIST-STATUS` and async-imap's API supports the extended LIST, `list_folders_with_status` issues ONE IMAP command and returns `Vec<(Folder, Option)>` with STATUS pre-populated — verified by counting round-trips in a Dovecot integration test (Dovecot 2.3+ supports LIST-STATUS).
-
When async-imap's API is available but the server does NOT advertise `LIST-STATUS`, the fallback path is taken unchanged. No regression on legacy servers.
-
Non-selectable folders continue to return `status = None` regardless of path.
-
Signature of `Connection::list_folders_with_status` and `rimap_server::tools::admin::list_folders::handle` is unchanged — the flip is a behavior change, not an API change.
Blockers / watch
- Upstream: track `async-imap` releases for LIST-STATUS support. If it doesn't land in a reasonable timeframe, the alternative is to use async-imap's `run_command_and_check_ok` (or equivalent) with an untagged-response channel, and parse the STATUS data lines manually. That's more invasive but removes the external dependency.
References
🤖 Generated with Claude Code
Context
PR #104 landed sub-group 1 of the folder-listing hardening sweep (#91, #92, #95, #98). The #92 work is partial: the LIST-STATUS capability flag and the public shape of
Connection::list_folders_with_statusare plumbed, but the actual single-round-trip wiring is deferred because `async-imap` does not yet expose the extended LIST command.What exists today (on `main`)
What this issue tracks
Once `async-imap` exposes LIST-STATUS — either through a dedicated `Session::list_status` method or through generic raw-command machinery that cleanly parses interleaved `STATUS` response-data lines alongside `LIST` — flip the dispatch in `list_with_status` to issue `LIST "" RETURN (STATUS (MESSAGES UIDVALIDITY UNSEEN))` as a single round-trip when `has_list_status` is true.
Acceptance criteria
When the server advertises `LIST-STATUS` and async-imap's API supports the extended LIST, `list_folders_with_status` issues ONE IMAP command and returns `Vec<(Folder, Option)>` with STATUS pre-populated — verified by counting round-trips in a Dovecot integration test (Dovecot 2.3+ supports LIST-STATUS).
When async-imap's API is available but the server does NOT advertise `LIST-STATUS`, the fallback path is taken unchanged. No regression on legacy servers.
Non-selectable folders continue to return `status = None` regardless of path.
Signature of `Connection::list_folders_with_status` and `rimap_server::tools::admin::list_folders::handle` is unchanged — the flip is a behavior change, not an API change.
Blockers / watch
References
🤖 Generated with Claude Code