@@ -1020,8 +1020,12 @@ def _init_view(self):
10201020 mode = "tree" , sizing_mode = "stretch_width" , height = 400 ,
10211021 )
10221022 self ._outline_editor .param .watch (self ._on_outline_change , "value" )
1023+ self ._outline_apply = Button (
1024+ label = "Apply" , variant = "contained" , on_click = self ._apply_outline ,
1025+ )
10231026 self ._outline_dialog = Dialog (
10241027 self ._outline_editor ,
1028+ Row (self ._outline_apply , align = "end" , margin = (10 , 0 , 0 , 0 ), sizing_mode = "stretch_width" ),
10251029 show_close_button = True ,
10261030 title = "Arrange Report" ,
10271031 width_option = "md" ,
@@ -1159,14 +1163,11 @@ def _export_views(self):
11591163 views .append (Typography (self ._overall_story_title , variant = "h2" ))
11601164 views .append (self ._overall_story )
11611165 if self ._story_outline :
1162- # Follow the user-arranged outline: headings and sections in order.
1163- for block in self ._story_outline :
1164- if "heading" in block :
1165- views .append (Typography (block ["heading" ], variant = f"h{ block .get ('level' , 2 )} " ))
1166- elif "section" in block :
1167- section = self ._section_by_title (block ["section" ])
1168- if section is not None :
1169- self ._append_section_views (views , section )
1166+ for kind , value , level in self ._iter_arrangement ():
1167+ if kind == "heading" :
1168+ views .append (Typography (value , variant = f"h{ level } " ))
1169+ else :
1170+ self ._append_section_views (views , value )
11701171 else :
11711172 for section in self :
11721173 self ._append_section_views (views , section )
@@ -1199,9 +1200,17 @@ def _export_view(self):
11991200 cards = []
12001201 if self ._overall_story is not None :
12011202 cards .append ((self ._overall_story_title , self ._overall_story ))
1202- cards += [
1203- (section .title , section ) for section in self if section .include_in_export
1204- ]
1203+ if self ._story_outline :
1204+ # Follow the arranged outline so HTML matches the notebook order.
1205+ for kind , value , _ in self ._iter_arrangement ():
1206+ if kind == "heading" :
1207+ cards .append ((value , Markdown (f"### { value } " )))
1208+ else :
1209+ cards .append ((value .title , value ))
1210+ else :
1211+ cards += [
1212+ (section .title , section ) for section in self if section .include_in_export
1213+ ]
12051214 return Accordion (
12061215 * cards ,
12071216 active = list (range (len (cards ))),
@@ -1240,6 +1249,14 @@ def _open_arrange_dialog(self, event=None):
12401249
12411250 def _on_outline_change (self , event ):
12421251 self ._story_outline = event .new or []
1252+ self ._populate_view ()
1253+
1254+ def _apply_outline (self , event = None ):
1255+ # Read the editor's current value explicitly and apply the arrangement
1256+ # to both the live report and the exports.
1257+ self ._story_outline = list (self ._outline_editor .value or [])
1258+ self ._populate_view ()
1259+ self ._outline_dialog .open = False
12431260
12441261 async def _annotate_report (self , event = None ):
12451262 """Write an AI story over the selected sections and insert it into the report."""
@@ -1316,10 +1333,40 @@ def _section_header(self, section):
13161333 title = Typography (section .param .title , variant = "h3" , margin = 0 )
13171334 return Row (checkbox , title , align = "center" , sizing_mode = "stretch_width" )
13181335
1336+ def _iter_arrangement (self , selected_only = True ):
1337+ """
1338+ Yield the arranged outline as ('heading', text, level) or
1339+ ('section', section, None) items, resolving section titles and
1340+ dropping duplicates (and, by default, discarded sections).
1341+ """
1342+ seen = set ()
1343+ for block in self ._story_outline :
1344+ if "heading" in block :
1345+ yield "heading" , block ["heading" ], block .get ("level" , 2 )
1346+ elif "section" in block :
1347+ section = self ._section_by_title (block ["section" ])
1348+ if section is None or id (section ) in seen :
1349+ continue
1350+ if selected_only and not section .include_in_export :
1351+ continue
1352+ seen .add (id (section ))
1353+ yield "section" , section , None
1354+
1355+ def _ordered_sections (self ):
1356+ """Sections in the arranged outline order (unreferenced ones kept at the end)."""
1357+ if not self ._story_outline :
1358+ return list (self )
1359+ ordered = [s for kind , s , _ in self ._iter_arrangement (selected_only = False ) if kind == "section" ]
1360+ seen = {id (section ) for section in ordered }
1361+ for section in self :
1362+ if id (section ) not in seen :
1363+ ordered .append (section )
1364+ return ordered
1365+
13191366 def _populate_view (self ):
13201367 headers = {}
13211368 objects = []
1322- for section in self :
1369+ for section in self . _ordered_sections () :
13231370 header = self ._section_headers .get (section ) or self ._section_header (section )
13241371 headers [section ] = header
13251372 objects .append ((header , section ))
0 commit comments