Skip to content

Commit 3f567c6

Browse files
committed
fix: apply the report arrangement to the live view and both exports
Reorder the live accordion to the outline (via _ordered_sections), add an explicit Apply button, and make the HTML export (_export_view) follow the outline like the notebook does. Factor the outline walk into a single _iter_arrangement helper that resolves and de-duplicates sections.
1 parent dd28b92 commit 3f567c6

2 files changed

Lines changed: 78 additions & 12 deletions

File tree

lumen/ai/report.py

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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))

lumen/tests/ai/test_story.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,25 @@ async def test_report_outline_inserts_headings():
221221
assert nb.index("Introduction") < nb.index("A done")
222222

223223

224+
async def test_report_outline_reorders_html_export():
225+
report = Report(
226+
Section(A(order=[]), title='Section A'),
227+
Section(B(order=[]), title='Section B'),
228+
title='R',
229+
)
230+
await report.execute()
231+
232+
report._story_outline = [
233+
{"heading": "Key Findings", "level": 1},
234+
{"section": "Section B"},
235+
{"section": "Section A"},
236+
]
237+
238+
html = report.to_html()
239+
assert "Key Findings" in html
240+
assert html.index("B done") < html.index("A done")
241+
242+
224243
async def test_report_arrange_editor_seeds_and_binds_outline():
225244
report = Report(
226245
Section(A(order=[]), title='Section A'),

0 commit comments

Comments
 (0)