Presentation slides are concatenated with no boundary, so untitled slides merge into the previous one
- Dominant language
- Rust
- Stars
- 21.5k
- Forks
- 1.3k
- Avg merge
- 42m
- Merged PRs (30d)
- 17
Description
## Summary
The three presentation parsers concatenate every slide's blocks into one list with nothing between them, so a slide with no title placeholder is indistinguishable from a continuation of the previous slide.
A slide's title becomes a `Heading`, so decks that title every slide read correctly by accident. A slide with no title contributes no structural block at all — two such slides in a row produce two adjacent paragraphs, exactly as if they were two paragraphs of one slide, and bullet lists from different slides merge into one list.
## Reproduction
Tested with `firecrawl-anydoc==0.1.6`.
```python
from pptx import Presentation
from pptx.util import Inches
import anydoc
prs = Presentation()
s1 = prs.slides.add_slide(prs.slide_layouts[5])
s1.shapes.title.text = "Titled slide"
for text in ("Second slide, no title placeholder.", "Third slide, also untitled."):
slide = prs.slides.add_slide(prs.slide_layouts[6]) # blank layout
slide.shapes.add_textbox(Inches(1), Inches(1), Inches(8), Inches(2)).text_frame.text = text
prs.save("untitled.pptx")
print(anydoc.to_markdown("untitled.pptx"))
```
## Observed
```
## Titled slide
Second slide, no title placeholder.
Third slide, also untitled.
```
Nothing marks where slide 2 ends and slide 3 begins.
## Expected
A separator between slides:
```
## Titled slide
---
Second slide, no title placeholder.
---
Third slide, also untitled.
```
## Why this is not the pagination you already declined
#26 settled that "Markdown has no pages, so dropping the break itself is the right call", and that framing is right for a `w:type="page"` break — that is layout. A slide is different: it is a container in the source model, and every one of its shapes belongs to it. Losing the boundary loses document structure, not pagination.
This asks for no page number and no new concept. `Block::Rule` already exists, is already emitted (`src/shared/html.rs:432`, for `
`), and already renders as `---` (`src/render/markdown/mod.rs:201`).
## Root cause
All three presentation parsers have the same shape — accumulate per slide, append, no separator:
- `src/formats/pptx/mod.rs` — `parse_shapes(sp_tree, &ctx, &mut blocks)?` writes straight into the shared `blocks`
- `src/formats/odf/mod.rs` — `parse_presentation` does `blocks.append(&mut title); blocks.append(&mut body);` per `draw:page`
- `src/formats/ppt/mod.rs` — `for (sid, blocks) in slides { out.extend(blocks); ... }`
## Scope
I have not scanned a corpus, so I will not claim a frequency. The structural argument is what I am confident in: any deck with a section divider, a full-bleed image slide, a quote slide, or a continuation slide has slides with no title placeholder, and those are the slides that merge.
Where every slide is titled, the current output is already correct and a separator only makes the boundary explicit.
## Proposed fix
Emit `Block::Rule` between slides — never leading, and never doubled by a slide that produces no blocks.
This is core-only. Every binding already maps `model::Block::Rule` (`node/src/document.rs:88`, `python/src/document.rs:83`, `wasm/src/document.rs:96`) and already declares the `rule` kind (`node/index.d.ts:60`, `python/anydoc/_anydoc.pyi:84`, `wasm/src/typescript.rs:47`), so no binding source or type surface changes and no new block variant.
PR follows.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the per-slide loops in src/formats/pptx/mod.rs, src/formats/odf/mod.rs, and src/formats/ppt/mod.rs, tracing how each appends parsed blocks. Check the existing Block::Rule handling in src/shared/html.rs and src/render/markdown/mod.rs. Done means slide boundaries produce single, non-leading, non-doubled --- separators while existing titled-slide output remains correct.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100