@@ -45,11 +45,11 @@ impl SectionMap {
4545 & self ,
4646 txn : & T ,
4747 section : Section ,
48- uid : i64 ,
48+ uid : Option < i64 > ,
4949 ) -> Option < SectionOperation > {
5050 let container = self . get_section ( txn, section. as_ref ( ) ) ?;
5151 Some ( SectionOperation {
52- uid : UserId :: from ( uid ) ,
52+ uid : uid . map ( UserId :: from) ,
5353 container,
5454 section,
5555 change_tx : self . change_tx . clone ( ) ,
@@ -65,6 +65,71 @@ impl SectionMap {
6565 }
6666}
6767
68+ /// Represents different types of user-specific view collections in a folder.
69+ ///
70+ /// Sections are **per-user** organizational categories that allow each user in a
71+ /// collaborative folder to maintain their own personal view collections. Each section
72+ /// type has a specific semantic purpose.
73+ ///
74+ /// # Section Types
75+ ///
76+ /// ## Predefined Sections
77+ ///
78+ /// - **Favorite**: Views the user has marked as favorites for quick access
79+ /// - **Recent**: Recently accessed views, typically ordered by access time
80+ /// - **Trash**: Views the user has deleted (pending permanent removal)
81+ /// - **Private**: Views that are private to the user and hidden from others
82+ ///
83+ /// ## Custom Sections
84+ ///
85+ /// - **Custom(String)**: User-defined section types for extensibility
86+ ///
87+ /// # Storage Architecture
88+ ///
89+ /// Each section in the CRDT is stored as a nested map structure:
90+ ///
91+ /// ```text
92+ /// SectionMap
93+ /// ├─ "favorite" (MapRef)
94+ /// │ ├─ "1" (uid) → Array[SectionItem, SectionItem, ...]
95+ /// │ ├─ "2" (uid) → Array[SectionItem, SectionItem, ...]
96+ /// │ └─ ...
97+ /// ├─ "recent" (MapRef)
98+ /// │ └─ ...
99+ /// ├─ "trash" (MapRef)
100+ /// │ └─ ...
101+ /// └─ "private" (MapRef)
102+ /// └─ ...
103+ /// ```
104+ ///
105+ /// This allows multiple users to collaborate on the same folder while maintaining
106+ /// independent personal collections. For example:
107+ /// - User 1's favorites don't affect User 2's favorites
108+ /// - Each user has their own trash bin
109+ /// - Each user has their own private views
110+ ///
111+ /// # String Representation
112+ ///
113+ /// Each section variant has a unique string identifier used as the CRDT map key:
114+ /// - `Favorite` → `"favorite"`
115+ /// - `Recent` → `"recent"`
116+ /// - `Trash` → `"trash"`
117+ /// - `Private` → `"private"`
118+ /// - `Custom("my_section")` → `"my_section"`
119+ ///
120+ /// # Examples
121+ ///
122+ /// ```rust,ignore
123+ /// use collab::folder::Section;
124+ ///
125+ /// // Predefined sections
126+ /// let fav = Section::Favorite;
127+ /// assert_eq!(fav.as_ref(), "favorite");
128+ ///
129+ /// // Custom section
130+ /// let custom = Section::from("my_custom_section".to_string());
131+ /// assert_eq!(custom.as_ref(), "my_custom_section");
132+ /// ```
68133#[ derive( Clone , Debug , PartialEq , Eq ) ]
69134pub enum Section {
70135 Favorite ,
@@ -119,7 +184,7 @@ pub enum TrashSectionChange {
119184pub type SectionsByUid = HashMap < UserId , Vec < SectionItem > > ;
120185
121186pub struct SectionOperation {
122- uid : UserId ,
187+ uid : Option < UserId > ,
123188 container : MapRef ,
124189 section : Section ,
125190 change_tx : Option < SectionChangeSender > ,
@@ -130,8 +195,8 @@ impl SectionOperation {
130195 & self . container
131196 }
132197
133- fn uid ( & self ) -> & UserId {
134- & self . uid
198+ fn uid ( & self ) -> Option < & UserId > {
199+ self . uid . as_ref ( )
135200 }
136201
137202 pub fn get_sections < T : ReadTxn > ( & self , txn : & T ) -> SectionsByUid {
@@ -154,9 +219,12 @@ impl SectionOperation {
154219 }
155220
156221 pub fn contains_with_txn < T : ReadTxn > ( & self , txn : & T , view_id : & ViewId ) -> bool {
222+ let Some ( uid) = self . uid ( ) else {
223+ return false ;
224+ } ;
157225 match self
158226 . container ( )
159- . get_with_txn :: < _ , ArrayRef > ( txn, self . uid ( ) . as_ref ( ) )
227+ . get_with_txn :: < _ , ArrayRef > ( txn, uid. as_ref ( ) )
160228 {
161229 None => false ,
162230 Some ( array) => {
@@ -173,9 +241,12 @@ impl SectionOperation {
173241 }
174242
175243 pub fn get_all_section_item < T : ReadTxn > ( & self , txn : & T ) -> Vec < SectionItem > {
244+ let Some ( uid) = self . uid ( ) else {
245+ return vec ! [ ] ;
246+ } ;
176247 match self
177248 . container ( )
178- . get_with_txn :: < _ , ArrayRef > ( txn, self . uid ( ) . as_ref ( ) )
249+ . get_with_txn :: < _ , ArrayRef > ( txn, uid. as_ref ( ) )
179250 {
180251 None => vec ! [ ] ,
181252 Some ( array) => {
@@ -201,6 +272,9 @@ impl SectionOperation {
201272 id : T ,
202273 prev_id : Option < T > ,
203274 ) {
275+ let Some ( uid) = self . uid ( ) else {
276+ return ;
277+ } ;
204278 let section_items = self . get_all_section_item ( txn) ;
205279 let id = id. as_ref ( ) ;
206280 let old_pos = section_items
@@ -217,7 +291,7 @@ impl SectionOperation {
217291 . unwrap_or ( 0 ) ;
218292 let section_array = self
219293 . container ( )
220- . get_with_txn :: < _ , ArrayRef > ( txn, self . uid ( ) . as_ref ( ) ) ;
294+ . get_with_txn :: < _ , ArrayRef > ( txn, uid. as_ref ( ) ) ;
221295 // If the new position index is greater than the length of the section, yrs will panic
222296 if new_pos > section_items. len ( ) as u32 {
223297 return ;
@@ -233,9 +307,12 @@ impl SectionOperation {
233307 txn : & mut TransactionMut ,
234308 ids : Vec < T > ,
235309 ) {
310+ let Some ( uid) = self . uid ( ) else {
311+ return ;
312+ } ;
236313 if let Some ( fav_array) = self
237314 . container ( )
238- . get_with_txn :: < _ , ArrayRef > ( txn, self . uid ( ) . as_ref ( ) )
315+ . get_with_txn :: < _ , ArrayRef > ( txn, uid. as_ref ( ) )
239316 {
240317 for id in & ids {
241318 if let Some ( pos) = self
@@ -267,8 +344,11 @@ impl SectionOperation {
267344 }
268345
269346 pub fn add_sections_item ( & self , txn : & mut TransactionMut , items : Vec < SectionItem > ) {
347+ let Some ( uid) = self . uid ( ) else {
348+ return ;
349+ } ;
270350 let item_ids = items. iter ( ) . map ( |item| item. id ) . collect :: < Vec < _ > > ( ) ;
271- self . add_sections_for_user_with_txn ( txn, self . uid ( ) , items) ;
351+ self . add_sections_for_user_with_txn ( txn, uid, items) ;
272352 if let Some ( change_tx) = self . change_tx . as_ref ( ) {
273353 match self . section {
274354 Section :: Favorite => { } ,
@@ -298,16 +378,77 @@ impl SectionOperation {
298378 }
299379
300380 pub fn clear ( & self , txn : & mut TransactionMut ) {
381+ let Some ( uid) = self . uid ( ) else {
382+ return ;
383+ } ;
301384 if let Some ( array) = self
302385 . container ( )
303- . get_with_txn :: < _ , ArrayRef > ( txn, self . uid ( ) . as_ref ( ) )
386+ . get_with_txn :: < _ , ArrayRef > ( txn, uid. as_ref ( ) )
304387 {
305388 let len = array. iter ( txn) . count ( ) ;
306389 array. remove_range ( txn, 0 , len as u32 ) ;
307390 }
308391 }
309392}
310393
394+ /// An item in a user's section, representing a view and when it was added.
395+ ///
396+ /// `SectionItem` is the fundamental unit stored in sections (Favorite, Recent, Trash, Private).
397+ /// Each item records both which view is in the section and when it was added, enabling
398+ /// time-based operations like sorting by recency or tracking deletion times.
399+ ///
400+ /// # Fields
401+ ///
402+ /// * `id` - The UUID of the view in this section
403+ /// * `timestamp` - Unix timestamp (milliseconds) when the view was added to this section
404+ ///
405+ /// # Storage Format
406+ ///
407+ /// SectionItems are serialized as Yrs `Any` values (essentially JSON-like maps) in the CRDT:
408+ ///
409+ /// ```json
410+ /// {
411+ /// "id": "550e8400-e29b-41d4-a716-446655440000",
412+ /// "timestamp": 1704067200000
413+ /// }
414+ /// ```
415+ ///
416+ /// Multiple items are stored in a Yrs array per user per section:
417+ ///
418+ /// ```text
419+ /// section["favorite"]["123"] = [
420+ /// SectionItem { id: view_1, timestamp: 1704067200000 },
421+ /// SectionItem { id: view_2, timestamp: 1704068000000 },
422+ /// ...
423+ /// ]
424+ /// ```
425+ ///
426+ /// # Usage Patterns
427+ ///
428+ /// ## Favorite Sections
429+ /// ```rust,ignore
430+ /// let favorites = folder.get_my_favorite_sections(Some(uid));
431+ /// for item in favorites {
432+ /// println!("View {} favorited at {}", item.id, item.timestamp);
433+ /// }
434+ /// ```
435+ ///
436+ /// ## Recent Sections (sorted by timestamp)
437+ /// ```rust,ignore
438+ /// let mut recent = folder.get_my_recent_sections(Some(uid));
439+ /// recent.sort_by_key(|item| std::cmp::Reverse(item.timestamp)); // newest first
440+ /// let most_recent = recent.first();
441+ /// ```
442+ ///
443+ /// ## Trash Sections (check deletion time)
444+ /// ```rust,ignore
445+ /// let trash = folder.get_my_trash_sections(Some(uid));
446+ /// let thirty_days_ago = current_timestamp() - (30 * 24 * 60 * 60 * 1000);
447+ /// let permanently_delete = trash
448+ /// .iter()
449+ /// .filter(|item| item.timestamp < thirty_days_ago)
450+ /// .collect::<Vec<_>>();
451+ /// ```
311452#[ derive( Debug , Clone , Eq , PartialEq , Serialize , Deserialize ) ]
312453pub struct SectionItem {
313454 pub id : ViewId ,
0 commit comments