diff --git a/crates/tinyclient/src/gui/sidebar/home.rs b/crates/tinyclient/src/gui/sidebar/home.rs index e97a560..e47b1fc 100644 --- a/crates/tinyclient/src/gui/sidebar/home.rs +++ b/crates/tinyclient/src/gui/sidebar/home.rs @@ -106,6 +106,8 @@ pub struct Home { /// The Telegram folder chosen, by id. pub folder: Option, pub loaded: bool, + /// The home has completed a read; sublist loading keeps its cached rows ready. + pub loaded_once: bool, pub epoch: u64, // The user's folds, for this session. pub dms_all: bool, @@ -133,6 +135,10 @@ fn header(out: &mut Vec, label: &'static str) { /// user's order with folders. `open_space`: the Space of the open conversation. `unread`: the /// Unread segment, which keeps only what has news. pub fn discord(home: &Home, spaces: &[Space], open_space: Option<&str>, unread: bool) -> Vec { + // Spaces arrive separately. Showing them before chats inserts DMs above visible rows. + if !home.loaded_once { + return Vec::new(); + } let mut out = Vec::new(); if !unread || home.friend_requests > 0 { let after = if home.online > 0 { format!("{} online", home.online) } else { String::new() }; @@ -402,6 +408,8 @@ mod tests { in_voice: (0..5).map(|n| chat(&format!("v{n}"), 0)).collect(), requests: 2, online: 6, + loaded: true, + loaded_once: true, ..Home::for_network(Network::Discord) }; let got = shape(&discord(&home, &spaces, None, false), &spaces); @@ -409,6 +417,40 @@ mod tests { assert_eq!(shape(&discord(&home, &spaces, None, true), &spaces), ["[Direct messages]", "ines"]); } + #[test] + fn discord_waits_for_chats_before_showing_cached_servers() { + let spaces = [space("moss", None, 0, false)]; + let mut home = Home::for_network(Network::Discord); + assert!(discord(&home, &spaces, None, false).is_empty()); + assert!(discord(&home, &spaces, None, true).is_empty()); + + home.chats = vec![chat("ines", 1)]; + home.loaded = true; + home.loaded_once = true; + let ready = shape(&discord(&home, &spaces, None, false), &spaces); + assert_eq!(ready, ["", "[Direct messages]", "ines", "[Servers]", "moss"]); + // Refreshes and sublist loading keep the cached home ready. + home.epoch += 1; + home.loaded = false; + assert_eq!(shape(&discord(&home, &spaces, None, false), &spaces), ready); + } + + #[test] + fn discord_shows_servers_after_a_completed_empty_chat_read() { + let spaces = [space("moss", None, 0, false)]; + let home = Home { loaded: true, loaded_once: true, ..Home::for_network(Network::Discord) }; + assert_eq!(shape(&discord(&home, &spaces, None, false), &spaces), ["", "[Servers]", "moss"]); + } + + #[test] + fn discord_servers_arriving_after_chats_append_below_them() { + let home = Home { loaded: true, loaded_once: true, chats: vec![chat("ines", 0)], ..Home::for_network(Network::Discord) }; + let chats = shape(&discord(&home, &[], None, false), &[]); + let spaces = [space("moss", None, 0, false)]; + let complete = shape(&discord(&home, &spaces, None, false), &spaces); + assert!(complete.starts_with(&chats)); + } + #[test] fn teams_keeps_quiet_meetings_in_their_fold_and_shows_unread_channels_of_closed_teams() { const NOW: i64 = 100 * 86_400; diff --git a/crates/tinyclient/src/gui/sidebar/home_view.rs b/crates/tinyclient/src/gui/sidebar/home_view.rs index 79cd62c..02f21b0 100644 --- a/crates/tinyclient/src/gui/sidebar/home_view.rs +++ b/crates/tinyclient/src/gui/sidebar/home_view.rs @@ -116,6 +116,7 @@ impl Shell { home.archive_total = read.archive_total; home.drilled = rows(read.drilled); home.loaded = true; + home.loaded_once = true; this.rebuild_home(cx); }); } diff --git a/docs/design/polish/02-discord-slack.md b/docs/design/polish/02-discord-slack.md index f60104d..f121b29 100644 --- a/docs/design/polish/02-discord-slack.md +++ b/docs/design/polish/02-discord-slack.md @@ -70,6 +70,12 @@ Opens in the sidebar from the Threads row (`‹ Threads`, workspace name `text_f | `Unread` segment | check glyph, "Nothing unread", "3 muted channels have new messages" · Show them | n/a | n/a | | Threads | "No new replies" · "Threads you follow show here when someone replies." | five 48 skeletons | banner "Couldn't load threads" + Try again over saved rows | +The first Discord home remains in its loading state until its chat read finishes, +even when cached servers arrive sooner. DMs then appear above servers in the +normal order. A completed empty DM read still shows servers. Once the home has +loaded, background refreshes keep its existing rows visible, including when +returning from a sublist. + ## New tokens For reconciliation with `polish/01-system.md`: `LEAD 20` (leading box of every 28 row), `NAME_X 36`, `INDENT_CHILD 12`, `INDENT_SUB 28`; `tag` component (16, r4, 11/500, `text_soft` on `quiet_fill`, which survives here only as the tag fill); `folder_spine` (the folder's own colour at 70%, 3 wide); `sticky_header` (`sidebar` fill, no rule, per C8). **Divergence to reconcile**: C9 puts DMs in homes on 48 rows; Slack DMs stay 28 because they sit in the user's sections beside channels in one `uniform_list` (Slack's own structure, ruling 1). Discord DMs are 48. Glyphs added: mail, phone, radio, image, building-2, calendar, headphones, headphone-off, monitor, eye, list, moon, smile, log-out, external-link, check-check, wifi-off.