update sidebar
This commit is contained in:
@@ -46,6 +46,8 @@ setting_accessors! {
|
|||||||
pub nip4e: bool,
|
pub nip4e: bool,
|
||||||
pub trusted_relays: Vec<String>,
|
pub trusted_relays: Vec<String>,
|
||||||
pub file_server: Url,
|
pub file_server: Url,
|
||||||
|
pub pinned_rooms: Vec<u64>,
|
||||||
|
pub expanded_sections: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signer kind
|
/// Signer kind
|
||||||
@@ -130,6 +132,14 @@ pub struct Settings {
|
|||||||
|
|
||||||
/// Server for blossom media attachments
|
/// Server for blossom media attachments
|
||||||
pub file_server: Url,
|
pub file_server: Url,
|
||||||
|
|
||||||
|
/// Pinned sidebar room ids, in pin order
|
||||||
|
#[serde(default)]
|
||||||
|
pub pinned_rooms: Vec<u64>,
|
||||||
|
|
||||||
|
/// Expanded sidebar tree sections; `None` means the default sections
|
||||||
|
#[serde(default)]
|
||||||
|
pub expanded_sections: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
@@ -142,6 +152,8 @@ impl Default for Settings {
|
|||||||
nip4e: false,
|
nip4e: false,
|
||||||
trusted_relays: vec![],
|
trusted_relays: vec![],
|
||||||
file_server: Url::parse(DEFAULT_FILE_SERVER).unwrap(),
|
file_server: Url::parse(DEFAULT_FILE_SERVER).unwrap(),
|
||||||
|
pinned_rooms: vec![],
|
||||||
|
expanded_sections: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,6 +183,13 @@ impl AppSettings {
|
|||||||
cx.global::<GlobalAppSettings>().0.clone()
|
cx.global::<GlobalAppSettings>().0.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The underlying settings entity, which notifies whenever any field changes.
|
||||||
|
/// Settings load asynchronously, so observers can watch it to pick up values
|
||||||
|
/// that arrive after construction.
|
||||||
|
pub fn entity(&self) -> &Entity<Settings> {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the global settings instance
|
/// Set the global settings instance
|
||||||
fn set_global(state: Entity<Self>, cx: &mut App) {
|
fn set_global(state: Entity<Self>, cx: &mut App) {
|
||||||
cx.set_global(GlobalAppSettings(state));
|
cx.set_global(GlobalAppSettings(state));
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use gpui::{
|
|||||||
UniformListScrollHandle, Window, div, px, retain_all, uniform_list,
|
UniformListScrollHandle, Window, div, px, retain_all, uniform_list,
|
||||||
};
|
};
|
||||||
use person::PersonRegistry;
|
use person::PersonRegistry;
|
||||||
|
use settings::AppSettings;
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
use state::NostrRegistry;
|
use state::NostrRegistry;
|
||||||
use theme::{ActiveTheme, TABBAR_HEIGHT};
|
use theme::{ActiveTheme, TABBAR_HEIGHT};
|
||||||
@@ -49,17 +50,17 @@ pub struct Sidebar {
|
|||||||
pinned_rooms: Vec<u64>,
|
pinned_rooms: Vec<u64>,
|
||||||
|
|
||||||
/// Event subscriptions
|
/// Event subscriptions
|
||||||
_subscriptions: SmallVec<[Subscription; 1]>,
|
_subscriptions: SmallVec<[Subscription; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sidebar {
|
impl Sidebar {
|
||||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
|
let settings = AppSettings::global(cx).read(cx).entity().clone();
|
||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
|
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
|
|
||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
// Subscribe for registry new events
|
|
||||||
cx.subscribe_in(&chat, window, move |this, _s, event, _window, cx| {
|
cx.subscribe_in(&chat, window, move |this, _s, event, _window, cx| {
|
||||||
if event == &ChatEvent::Ping {
|
if event == &ChatEvent::Ping {
|
||||||
this.new_requests = true;
|
this.new_requests = true;
|
||||||
@@ -68,12 +69,16 @@ impl Sidebar {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
subscriptions.push(cx.observe(&settings, move |this, _settings, cx| {
|
||||||
|
this.restore_state(cx);
|
||||||
|
}));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
scroll_handle: UniformListScrollHandle::new(),
|
scroll_handle: UniformListScrollHandle::new(),
|
||||||
new_requests: false,
|
new_requests: false,
|
||||||
expanded: BTreeSet::from([TreeSection::Community, TreeSection::Messages]),
|
expanded: load_expanded(cx),
|
||||||
pinned_rooms: Vec::new(),
|
pinned_rooms: AppSettings::get_pinned_rooms(cx),
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,6 +92,7 @@ impl Sidebar {
|
|||||||
self.new_requests = false;
|
self.new_requests = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.save_expanded(cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,16 +100,43 @@ impl Sidebar {
|
|||||||
self.expanded.contains(§ion)
|
self.expanded.contains(§ion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn restore_state(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let pinned_rooms = AppSettings::get_pinned_rooms(cx);
|
||||||
|
let expanded = load_expanded(cx);
|
||||||
|
|
||||||
|
if self.pinned_rooms == pinned_rooms && self.expanded == expanded {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pinned_rooms = pinned_rooms;
|
||||||
|
self.expanded = expanded;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_expanded(&self, cx: &mut App) {
|
||||||
|
let keys = self
|
||||||
|
.expanded
|
||||||
|
.iter()
|
||||||
|
.map(|section| section.key().to_string())
|
||||||
|
.collect();
|
||||||
|
AppSettings::update_expanded_sections(Some(keys), cx);
|
||||||
|
}
|
||||||
|
|
||||||
fn pin_room(&mut self, room_id: u64, cx: &mut Context<Self>) {
|
fn pin_room(&mut self, room_id: u64, cx: &mut Context<Self>) {
|
||||||
if !self.pinned_rooms.contains(&room_id) {
|
if !self.pinned_rooms.contains(&room_id) {
|
||||||
self.pinned_rooms.push(room_id);
|
self.pinned_rooms.push(room_id);
|
||||||
}
|
}
|
||||||
self.expanded.insert(TreeSection::Pins);
|
self.expanded.insert(TreeSection::Pins);
|
||||||
|
|
||||||
|
AppSettings::update_pinned_rooms(self.pinned_rooms.clone(), cx);
|
||||||
|
self.save_expanded(cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unpin_room(&mut self, room_id: u64, cx: &mut Context<Self>) {
|
fn unpin_room(&mut self, room_id: u64, cx: &mut Context<Self>) {
|
||||||
self.pinned_rooms.retain(|id| *id != room_id);
|
self.pinned_rooms.retain(|id| *id != room_id);
|
||||||
|
|
||||||
|
AppSettings::update_pinned_rooms(self.pinned_rooms.clone(), cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,6 +476,16 @@ fn nav_item(id: &'static str, icon: IconName, label: &'static str, command: Comm
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_expanded(cx: &App) -> BTreeSet<TreeSection> {
|
||||||
|
let Some(keys) = AppSettings::get_expanded_sections(cx) else {
|
||||||
|
return BTreeSet::from([TreeSection::Community, TreeSection::Messages]);
|
||||||
|
};
|
||||||
|
|
||||||
|
keys.iter()
|
||||||
|
.filter_map(|key| TreeSection::from_key(key.as_str()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
impl Panel for Sidebar {
|
impl Panel for Sidebar {
|
||||||
fn panel_id(&self) -> SharedString {
|
fn panel_id(&self) -> SharedString {
|
||||||
"Sidebar".into()
|
"Sidebar".into()
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use gpui::{
|
|||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::{Icon, IconName, Sizable, StyledExt, h_flex};
|
use ui::{Icon, IconName, Sizable, StyledExt, h_flex};
|
||||||
|
|
||||||
/// Collapsible tree sections; declaration order is render order.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub enum TreeSection {
|
pub enum TreeSection {
|
||||||
Pins,
|
Pins,
|
||||||
@@ -34,9 +33,27 @@ impl TreeSection {
|
|||||||
Self::Messages => IconName::Message,
|
Self::Messages => IconName::Message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn key(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Pins => "pins",
|
||||||
|
Self::Requests => "requests",
|
||||||
|
Self::Community => "community",
|
||||||
|
Self::Messages => "messages",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_key(key: &str) -> Option<Self> {
|
||||||
|
match key {
|
||||||
|
"pins" => Some(Self::Pins),
|
||||||
|
"requests" => Some(Self::Requests),
|
||||||
|
"community" => Some(Self::Community),
|
||||||
|
"messages" => Some(Self::Messages),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One rendered tree row, in flattened order.
|
|
||||||
pub enum SidebarRow {
|
pub enum SidebarRow {
|
||||||
Section {
|
Section {
|
||||||
section: TreeSection,
|
section: TreeSection,
|
||||||
@@ -57,12 +74,10 @@ pub enum SidebarRow {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A community shown under the Community section.
|
|
||||||
pub struct CommunityEntry {
|
pub struct CommunityEntry {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Communities to show until the Concord backend is wired up.
|
|
||||||
pub fn dummy_communities() -> &'static [CommunityEntry] {
|
pub fn dummy_communities() -> &'static [CommunityEntry] {
|
||||||
// TODO(concord): replace with ConcordRegistry communities, see docs/concord-usage.md.
|
// TODO(concord): replace with ConcordRegistry communities, see docs/concord-usage.md.
|
||||||
&[
|
&[
|
||||||
@@ -75,7 +90,6 @@ pub fn dummy_communities() -> &'static [CommunityEntry] {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Presentation differences between the rows [`TreeRow`] draws.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum TreeRowKind {
|
pub enum TreeRowKind {
|
||||||
Section,
|
Section,
|
||||||
@@ -83,7 +97,6 @@ pub enum TreeRowKind {
|
|||||||
Hint,
|
Hint,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Folder/file row. One element for section headers, community rows and hints.
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct TreeRow {
|
pub struct TreeRow {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
@@ -95,7 +108,6 @@ pub struct TreeRow {
|
|||||||
label: SharedString,
|
label: SharedString,
|
||||||
count: Option<usize>,
|
count: Option<usize>,
|
||||||
dot: bool,
|
dot: bool,
|
||||||
selected: bool,
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||||
}
|
}
|
||||||
@@ -116,7 +128,6 @@ impl TreeRow {
|
|||||||
label: label.into(),
|
label: label.into(),
|
||||||
count: None,
|
count: None,
|
||||||
dot: false,
|
dot: false,
|
||||||
selected: false,
|
|
||||||
on_click: None,
|
on_click: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,11 +162,6 @@ impl TreeRow {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selected(mut self, selected: bool) -> Self {
|
|
||||||
self.selected = selected;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_click(
|
pub fn on_click(
|
||||||
mut self,
|
mut self,
|
||||||
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||||
@@ -196,9 +202,6 @@ impl RenderOnce for TreeRow {
|
|||||||
.font_normal()
|
.font_normal()
|
||||||
.text_color(cx.theme().text_placeholder)
|
.text_color(cx.theme().text_placeholder)
|
||||||
})
|
})
|
||||||
.when(self.selected, |this| {
|
|
||||||
this.bg(cx.theme().ghost_element_selected)
|
|
||||||
})
|
|
||||||
.when_some(self.caret, |this, caret| {
|
.when_some(self.caret, |this, caret| {
|
||||||
this.child(Icon::new(caret).xsmall().text_color(cx.theme().icon_muted))
|
this.child(Icon::new(caret).xsmall().text_color(cx.theme().icon_muted))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# Sidebar tree redesign
|
# Sidebar tree redesign
|
||||||
|
|
||||||
Status: steps 1-7 implemented. Search lives in `panels/search.rs`; the sidebar
|
Status: steps 1-9 implemented. Search lives in `panels/search.rs`; the sidebar
|
||||||
renders the nav rail, the flattened tree, per-row pin/unpin menus, and the
|
renders the nav rail, the flattened tree, per-row pin/unpin menus, and the
|
||||||
Community section from placeholder data (`TODO(concord)`). Remaining: optional
|
Community section from placeholder data (`TODO(concord)`). Pins and expanded
|
||||||
step 8 (persistence), step 9 (remove the unused `TreeRow::selected` and run the
|
sections persist through `settings::Settings`. `cargo check`, `cargo clippy
|
||||||
final cleanup).
|
--workspace --all-targets` and `rustfmt --check` on the changed files are clean.
|
||||||
|
Remaining: the §15 manual QA checklist (needs the running app).
|
||||||
|
|
||||||
Scope: `crates/workspace/src/sidebar` (`mod.rs`, `entry.rs`, new `tree.rs`),
|
Scope: `crates/workspace/src/sidebar` (`mod.rs`, `entry.rs`, new `tree.rs`),
|
||||||
new panel shells in `crates/workspace/src/panels/`, and the `Command` wiring in
|
new panel shells in `crates/workspace/src/panels/`, and the `Command` wiring in
|
||||||
@@ -108,6 +109,20 @@ pinned_rooms: Vec<u64>, // room ids in pin order
|
|||||||
Defaults: `expanded = {Community, Messages}` (Requests intentionally absent;
|
Defaults: `expanded = {Community, Messages}` (Requests intentionally absent;
|
||||||
Pins only matters when non-empty and starts expanded).
|
Pins only matters when non-empty and starts expanded).
|
||||||
|
|
||||||
|
Both fields persist through `settings::Settings`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[serde(default)] pinned_rooms: Vec<u64>,
|
||||||
|
#[serde(default)] expanded_sections: Option<Vec<String>>,
|
||||||
|
```
|
||||||
|
|
||||||
|
`expanded_sections` is an `Option` so that an empty list (the user collapsed
|
||||||
|
everything) is distinguishable from the field never having been written, which
|
||||||
|
keeps the `{Community, Messages}` default. `TreeSection::key()`/`from_key()` map
|
||||||
|
the sections to their stable string keys. Because settings load asynchronously,
|
||||||
|
`Sidebar` observes the settings entity and re-reads both fields in
|
||||||
|
`restore_state` instead of trusting the constructor's read.
|
||||||
|
|
||||||
New methods:
|
New methods:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -116,6 +131,7 @@ fn is_expanded(&self, section: TreeSection) -> bool;
|
|||||||
fn pin_room(&mut self, room_id: u64, cx: &mut Context<Self>);
|
fn pin_room(&mut self, room_id: u64, cx: &mut Context<Self>);
|
||||||
fn unpin_room(&mut self, room_id: u64, cx: &mut Context<Self>);
|
fn unpin_room(&mut self, room_id: u64, cx: &mut Context<Self>);
|
||||||
fn is_pinned(&self, room_id: u64) -> bool;
|
fn is_pinned(&self, room_id: u64) -> bool;
|
||||||
|
fn restore_state(&mut self, cx: &mut Context<Self>); // step 8
|
||||||
fn tree_rows(&self, cx: &App) -> Vec<SidebarRow>; // see §5
|
fn tree_rows(&self, cx: &App) -> Vec<SidebarRow>; // see §5
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -349,14 +365,34 @@ unused until step 5 consumes them. Run the checks in §15 after each step.
|
|||||||
flattening and rendering landed with step 5 (`SidebarRow::Community` ->
|
flattening and rendering landed with step 5 (`SidebarRow::Community` ->
|
||||||
`TreeRow`), so this step added the missing hint branch and confirmed the §10
|
`TreeRow`), so this step added the missing hint branch and confirmed the §10
|
||||||
placeholder names.
|
placeholder names.
|
||||||
- [ ] **Step 8 (optional) — persistence.** Add
|
- [x] **Step 8 — persistence.** `settings::Settings` gained
|
||||||
`#[serde(default)] pinned_rooms: Vec<u64>` (and optionally
|
`#[serde(default)] pinned_rooms: Vec<u64>` and
|
||||||
`expanded_sections: Vec<String>`) to `settings::Settings`, register accessors
|
`#[serde(default)] expanded_sections: Option<Vec<String>>`, both registered in
|
||||||
in `setting_accessors!`, and load/save from `Sidebar`. The `#[serde(default)]`
|
`setting_accessors!` (so `AppSettings::get_*`/`update_*` exist). The
|
||||||
attribute is required: `Settings` has no defaults today, so a new field
|
`#[serde(default)]` attribute is required: `Settings` has no serde defaults, so
|
||||||
without it breaks parsing of existing `.settings` files.
|
a new field without it breaks parsing of existing `.settings` files. `Sidebar::new`
|
||||||
- [ ] **Step 9 — cleanup.** `cargo fmt`, remove dead imports/helpers, run
|
loads both (falling back to the default sections when the setting is `None`),
|
||||||
clippy.
|
and `toggle_section`/`pin_room`/`unpin_room` write back through
|
||||||
|
`AppSettings::update_*`; the settings observer already saves on every change, so
|
||||||
|
no explicit file I/O was added. `expanded_sections` is `Option` so that
|
||||||
|
collapsing every folder does not silently revert to the default on restart.
|
||||||
|
Stale pinned ids are still skipped at flatten time rather than pruned on load.
|
||||||
|
|
||||||
|
Settings load asynchronously (a deferred, background file read), so the
|
||||||
|
constructor's read always sees defaults on a cold start. To pick up the loaded
|
||||||
|
values, `AppSettings::entity()` now exposes the inner `Entity<Settings>` (it
|
||||||
|
notifies on every field change) and `Sidebar` observes it, re-reading through
|
||||||
|
`restore_state` and re-rendering only when the values actually differ. Without
|
||||||
|
this the sidebar would render with empty pins until the next unrelated change.
|
||||||
|
The observation is on the inner entity because `AppSettings` itself never
|
||||||
|
notifies its own observers.
|
||||||
|
- [x] **Step 9 — cleanup.** Removed `TreeRow::selected` (the field, the builder
|
||||||
|
method, and the `ghost_element_selected` render branch) — it was the only dead
|
||||||
|
code left after step 5. No other unused imports or helpers remained.
|
||||||
|
`cargo clippy --workspace --all-targets` reports zero warnings. Formatting is
|
||||||
|
checked per file with `rustfmt +nightly --check`; `cargo fmt --all` is **not**
|
||||||
|
run, because the repo's committed formatting does not match the installed
|
||||||
|
nightly rustfmt (many pre-existing diffs in unrelated files).
|
||||||
|
|
||||||
## 13. Files touched
|
## 13. Files touched
|
||||||
|
|
||||||
@@ -370,7 +406,7 @@ unused until step 5 consumes them. Run the checks in §15 after each step.
|
|||||||
| `crates/workspace/src/lib.rs` | `Command` variants + `on_command` arms |
|
| `crates/workspace/src/lib.rs` | `Command` variants + `on_command` arms |
|
||||||
| `crates/ui/src/icon.rs` | New icon variants |
|
| `crates/ui/src/icon.rs` | New icon variants |
|
||||||
| `assets/icons/{folder,compass,message}.svg` | New assets |
|
| `assets/icons/{folder,compass,message}.svg` | New assets |
|
||||||
| `crates/settings/src/lib.rs` | Optional step 8 only |
|
| `crates/settings/src/lib.rs` | Step 8: `pinned_rooms`, `expanded_sections`, accessors, `entity()` |
|
||||||
|
|
||||||
## 14. Edge cases
|
## 14. Edge cases
|
||||||
|
|
||||||
@@ -394,8 +430,10 @@ unused until step 5 consumes them. Run the checks in §15 after each step.
|
|||||||
|
|
||||||
## 15. Validation
|
## 15. Validation
|
||||||
|
|
||||||
- `cargo fmt --check` (workspace `rustfmt.toml`).
|
- `rustfmt +nightly --check` on the changed files (not `cargo fmt --all`: the
|
||||||
- `cargo check -p workspace` and `cargo clippy -p workspace --all-targets`.
|
repo's committed formatting does not match the installed nightly rustfmt, so a
|
||||||
|
workspace-wide check reports many pre-existing diffs).
|
||||||
|
- `cargo check --workspace` and `cargo clippy --workspace --all-targets`.
|
||||||
- Manual QA checklist:
|
- Manual QA checklist:
|
||||||
- Inbox/Browse/Search each open their panel; clicking the same nav item again
|
- Inbox/Browse/Search each open their panel; clicking the same nav item again
|
||||||
focuses the existing panel instead of duplicating it;
|
focuses the existing panel instead of duplicating it;
|
||||||
@@ -409,6 +447,8 @@ unused until step 5 consumes them. Run the checks in §15 after each step.
|
|||||||
room; clicking a pinned row opens it;
|
room; clicking a pinned row opens it;
|
||||||
- Messages lists ongoing rooms and still opens the screening modal for
|
- Messages lists ongoing rooms and still opens the screening modal for
|
||||||
non-ongoing rooms;
|
non-ongoing rooms;
|
||||||
|
- pins and expanded/collapsed folders survive an app restart (collapsing every
|
||||||
|
folder also survives, rather than reverting to the default sections);
|
||||||
- empty states at 0 ongoing and 0 requests.
|
- empty states at 0 ongoing and 0 requests.
|
||||||
- There is no GPUI test infrastructure in the repo (no `#[gpui::test]`
|
- There is no GPUI test infrastructure in the repo (no `#[gpui::test]`
|
||||||
anywhere), so tests are limited to pure helpers (`TreeSection` defaults, pin
|
anywhere), so tests are limited to pure helpers (`TreeSection` defaults, pin
|
||||||
@@ -417,7 +457,8 @@ unused until step 5 consumes them. Run the checks in §15 after each step.
|
|||||||
|
|
||||||
## 16. Open questions
|
## 16. Open questions
|
||||||
|
|
||||||
1. **Persistence.** Persist pins and folder state, or keep them session-local?
|
1. **Persistence.** Resolved in step 8: pins and expanded sections persist in
|
||||||
|
`settings::Settings`.
|
||||||
2. **Row density.** `h_8` vs the current `h_9`; `SIDEBAR_WIDTH` stays 240px for
|
2. **Row density.** `h_8` vs the current `h_9`; `SIDEBAR_WIDTH` stays 240px for
|
||||||
now, one indent level fits.
|
now, one indent level fits.
|
||||||
3. **Community entries.** Preferred dummy names/branding before the real
|
3. **Community entries.** Preferred dummy names/branding before the real
|
||||||
|
|||||||
Reference in New Issue
Block a user