@@ -498,7 +498,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
498498 }
499499 PortfolioMessage :: NewDocumentWithName { name } => {
500500 let mut new_document = DocumentMessageHandler :: default ( ) ;
501- new_document. name = name;
501+ new_document. name = self . resolve_document_name ( name, None ) ;
502502
503503 responses. add ( DocumentMessage :: PTZUpdate ) ;
504504
@@ -799,28 +799,24 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
799799 }
800800 } ) ;
801801
802- match ( document_name, document_path, document_name_from_path) {
803- ( Some ( name) , _, None ) => {
804- document. name = name;
805- }
802+ let candidate_name = match ( document_name, document_path, document_name_from_path) {
803+ ( Some ( name) , _, None ) => name,
806804 ( _, Some ( path) , Some ( name) ) => {
807- document. name = name;
808805 document. path = Some ( path) ;
806+ name
809807 }
810- ( _, _, Some ( name) ) => {
811- document. name = name;
812- }
813- _ => {
814- document. name = DEFAULT_DOCUMENT_NAME . to_string ( ) ;
815- }
816- }
808+ ( _, _, Some ( name) ) => name,
809+ _ => String :: new ( ) ,
810+ } ;
811+ document. name = self . resolve_document_name ( candidate_name, None ) ;
817812
818813 // Load the document into the portfolio so it opens in the editor
819814 self . load_document ( document, document_id, responses) ;
820815 }
821816 PortfolioMessage :: OpenImage { name, image } => {
817+ // `NewDocumentWithName`'s handler routes empty/None-equivalent names through `resolve_document_name` which assigns the next available "Untitled Document {N}".
822818 responses. add ( PortfolioMessage :: NewDocumentWithName {
823- name : name. clone ( ) . unwrap_or ( DEFAULT_DOCUMENT_NAME . into ( ) ) ,
819+ name : name. clone ( ) . unwrap_or_default ( ) ,
824820 } ) ;
825821
826822 responses. add ( DocumentMessage :: PasteImage {
@@ -847,7 +843,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
847843 }
848844 PortfolioMessage :: OpenSvg { name, svg } => {
849845 responses. add ( PortfolioMessage :: NewDocumentWithName {
850- name : name. clone ( ) . unwrap_or ( DEFAULT_DOCUMENT_NAME . into ( ) ) ,
846+ name : name. clone ( ) . unwrap_or_default ( ) ,
851847 } ) ;
852848
853849 // Parse the SVG to extract its declared canvas origin and dimensions from the viewBox attribute.
@@ -1359,6 +1355,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
13591355 self . refresh_panel_content ( target_active, responses) ;
13601356 }
13611357 }
1358+ PortfolioMessage :: RenameDocument { new_name } => {
1359+ let resolved_name = self . resolve_document_name ( new_name, self . active_document_id ) ;
1360+ responses. add ( DocumentMessage :: RenameDocument { new_name : resolved_name } ) ;
1361+ }
13621362 PortfolioMessage :: SelectDocument { document_id } => {
13631363 // Auto-save the document we are leaving
13641364 let mut node_graph_open = false ;
@@ -1744,10 +1744,22 @@ impl PortfolioMessageHandler {
17441744 }
17451745 }
17461746
1747- pub fn generate_new_document_name ( & self ) -> String {
1747+ /// Resolves a proposed document name: if it's empty or only whitespace, falls back to the next
1748+ /// available "Untitled Document {N}" via [`Self::generate_new_document_name`]. Otherwise trims surrounding
1749+ /// whitespace and returns it. `exclude` is forwarded so a renaming document can skip its own current
1750+ /// name when computing the fallback (preventing self-collision).
1751+ pub fn resolve_document_name ( & self , name : String , exclude : Option < DocumentId > ) -> String {
1752+ let trimmed = name. trim ( ) ;
1753+ if trimmed. is_empty ( ) { self . generate_new_document_name ( exclude) } else { trimmed. to_string ( ) }
1754+ }
1755+
1756+ /// `exclude` lets a renaming caller skip its own current name so a document can rename back to its
1757+ /// existing slot rather than colliding with itself and getting bumped to the next number.
1758+ pub fn generate_new_document_name ( & self , exclude : Option < DocumentId > ) -> String {
17481759 let mut doc_title_numbers = self
17491760 . document_ids
17501761 . iter ( )
1762+ . filter ( |id| exclude != Some ( * * id) )
17511763 . filter_map ( |id| self . document_details ( * id) )
17521764 . filter_map ( |doc| {
17531765 doc. name
0 commit comments