Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions crates/tinyclient/src/gui/sidebar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ pub struct State {
muted_open: bool,
/// The network home on show: what chatd said, and the user's folds.
pub home: home::Home,
saved_homes: HashMap<Network, home::Home>,
home_list: ListState,
home_items: Rc<Vec<home::Item>>,
/// The home's Unread segment, and how many rows it holds.
Expand Down Expand Up @@ -165,6 +166,7 @@ impl State {
muted_news: Vec::new(),
muted_open: false,
home: home::Home::default(),
saved_homes: HashMap::new(),
home_list: list_state(cx),
home_items: Rc::new(Vec::new()),
home_unread: false,
Expand Down Expand Up @@ -319,9 +321,7 @@ impl Shell {
}
}
let home_network = mode.network().filter(|_| matches!(mode, Mode::Network(_)));
if home_network != state.home.network {
state.home = home_network.map(home::Home::for_network).unwrap_or_default();
}
state.home.switch_network(home_network, &mut state.saved_homes);
state.mode = mode;
state.cursor = None;
state.drill = None;
Expand All @@ -338,7 +338,8 @@ impl Shell {
hub.watch_rows(WATCH, [], cx);
self.show_mode(cx);
self.rebuild_inbox(cx);
self.load_directs(cx);
// Navigation reads immediately; only background event bursts are debounced.
self.load_home(cx);
}

fn here(&self) -> Location {
Expand Down
59 changes: 58 additions & 1 deletion crates/tinyclient/src/gui/sidebar/home.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! mixed, so the list is a GPUI `list` whose heights are known from each item's kind.

use protocol::{Badge, Conversation, Folder, Key, Network, Space, Special};
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;

/// Discord DMs before "Show n more" (product rulings: 10).
Expand Down Expand Up @@ -118,6 +118,24 @@ pub struct Home {
}

impl Home {
/// Save the outgoing home and restore the destination without waiting for chatd.
pub fn switch_network(&mut self, network: Option<Network>, saved: &mut HashMap<Network, Home>) {
if self.network == network {
return;
}
let mut outgoing = std::mem::take(self);
// An answer started before leaving must never overwrite a restored home.
outgoing.epoch += 1;
outgoing.loaded = outgoing.loaded_once;
outgoing.drilled.clear();
if let Some(previous) = outgoing.network {
saved.insert(previous, outgoing);
}
*self = network
.map(|n| saved.remove(&n).unwrap_or_else(|| Home::for_network(n)))
.unwrap_or_default();
}

pub fn for_network(network: Network) -> Home {
Home { network: Some(network), ..Home::default() }
}
Expand Down Expand Up @@ -367,6 +385,45 @@ mod tests {
.collect()
}

#[test]
fn switching_networks_restores_chats_and_folds_without_a_read() {
let mut saved = HashMap::new();
let mut home = Home {
loaded: true,
loaded_once: true,
chats: vec![chat("club", 0)],
chats_all: true,
epoch: 7,
..Home::for_network(Network::Signal)
};
home.switch_network(Some(Network::Discord), &mut saved);
assert!(!home.loaded);
home.switch_network(None, &mut saved);
home.switch_network(Some(Network::Signal), &mut saved);
assert!(home.loaded && home.loaded_once && home.chats_all);
assert_eq!(home.chats[0].key.id, "club");
assert_ne!(home.epoch, 7, "a pre-navigation read must be stale");
assert_eq!(shape(&signal(&home), &[]), ["club"]);
}

#[test]
fn leaving_a_sublist_restores_the_completed_home_loading_state() {
let mut saved = HashMap::new();
let mut home = Home {
loaded_once: true,
loaded: false,
drilled: vec![chat("request", 0)],
..Home::for_network(Network::Discord)
};
home.switch_network(Some(Network::Signal), &mut saved);
home.switch_network(Some(Network::Discord), &mut saved);
assert!(home.loaded);
assert!(home.drilled.is_empty());
let epoch = home.epoch;
home.switch_network(Some(Network::Discord), &mut saved);
assert_eq!(home.epoch, epoch);
}

#[test]
fn dms_show_every_one_with_a_number_then_the_newest_up_to_ten() {
let mut chats: Vec<_> = (0..14).map(|n| chat(&format!("d{n}"), 0)).collect();
Expand Down
6 changes: 6 additions & 0 deletions docs/design/polish/02-discord-slack.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ 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.

Network homes are cached for the GUI session: switching back displays saved
chats and folds immediately, then refreshes from chatd in the background.
Explicit navigation skips the event-burst debounce. A network opened for the
first time still waits for its local chat read. Returning from a sublist restores
the completed home's loading state, including a previously empty home.

## 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.

Expand Down
Loading