From 1bbd05b91f095f5e47a6f422899c1a4d76171e2a Mon Sep 17 00:00:00 2001 From: Vincent Bernardoff Date: Sat, 17 Oct 2026 14:35:57 +0200 Subject: [PATCH 1/3] fix: wait for Discord chats before showing cached servers Keep the Discord home in its existing loading state until the first home read completes. Spaces arrive separately and otherwise render before DMs, which then push the visible server list down. Track completed home reads independently of sublist loading so cached rows stay visible during refreshes and when leaving a sublist. Cover both arrival orders, empty DMs and cached rows in the sidebar tests, and document the loading behavior in the design notes. Co-Authored-By: GPT-6 --- crates/tinyclient/src/gui/sidebar/home.rs | 42 +++++++++++++++++++ .../tinyclient/src/gui/sidebar/home_view.rs | 1 + docs/design/polish/02-discord-slack.md | 5 +++ 3 files changed, 48 insertions(+) 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..7c89671 100644 --- a/docs/design/polish/02-discord-slack.md +++ b/docs/design/polish/02-discord-slack.md @@ -70,6 +70,11 @@ 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 and sublist loading keep its existing rows visible. + ## 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. From bb52c69a099ff646d0c0105fdd2e14d9b2040721 Mon Sep 17 00:00:00 2001 From: Vincent Bernardoff Date: Sat, 17 Oct 2026 14:36:22 +0200 Subject: [PATCH 2/3] doc: clarify cached Discord home behavior around sublists Describe cached home rows when returning from a sublist. Opening a sublist still uses that list's own loading state. Co-Authored-By: GPT-6 --- docs/design/polish/02-discord-slack.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/design/polish/02-discord-slack.md b/docs/design/polish/02-discord-slack.md index 7c89671..f121b29 100644 --- a/docs/design/polish/02-discord-slack.md +++ b/docs/design/polish/02-discord-slack.md @@ -73,7 +73,8 @@ Opens in the sidebar from the Threads row (`‹ Threads`, workspace name `text_f 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 and sublist loading keep its existing rows visible. +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. From a1712f9e75b634ddd3642b7e98f70636ee1e1af8 Mon Sep 17 00:00:00 2001 From: Vincent Bernardoff Date: Sat, 17 Oct 2026 15:10:49 +0200 Subject: [PATCH 3/3] test: fix platform archive URLs in the release fixture Give the Linux and Windows fixture assets URLs containing their archive filenames, as the existing assertion requires. Both CI jobs failed this test; the macOS fixture already included the filename. This changes only fixture data, with no runtime or documentation behavior change. Co-Authored-By: GPT-6 --- crates/tinyclient/src/update.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/tinyclient/src/update.rs b/crates/tinyclient/src/update.rs index 75d42a9..54270e9 100644 --- a/crates/tinyclient/src/update.rs +++ b/crates/tinyclient/src/update.rs @@ -755,8 +755,8 @@ mod tests { "assets": [ {"name": "tinyclient-v0.9.0-macos.zip", "uploader": {"name": "not-the-file"}, "browser_download_url": "https://github.com/itz4blitz/tinyclient/releases/download/v0.9.0/tinyclient-v0.9.0-macos.zip"}, {"name": "tinyclient-macos.zip", "uploader": {"name": "not-the-file"}, "browser_download_url": "https://github.com/itz4blitz/tinyclient/releases/download/v0.9.0/tinyclient-macos.zip"}, - {"name": "tinyclient-linux-x86_64.tar.gz", "browser_download_url": "https://example.invalid/linux"}, - {"name": "tinyclient-windows-x86_64.zip", "browser_download_url": "https://example.invalid/windows"}, + {"name": "tinyclient-linux-x86_64.tar.gz", "browser_download_url": "https://example.invalid/tinyclient-linux-x86_64.tar.gz"}, + {"name": "tinyclient-windows-x86_64.zip", "browser_download_url": "https://example.invalid/tinyclient-windows-x86_64.zip"}, {"name": "SHA256SUMS", "browser_download_url": "https://example.invalid/SHA256SUMS"} ] }"#;