Skip to content

Read document outlines in the chapter menu - #13

Merged
hawkff merged 4 commits into
mainfrom
feat/document-outlines
Oct 1, 2026
Merged

hawkff merged 4 commits into
mainfrom
feat/document-outlines

Conversation

@hawkff

@hawkff hawkff commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Read PDF and DJVU outlines and EPUB navigation tables in the Tab chapter menu. Preserve nested entries, source order, and page destinations.

  • Resolve EPUB fragments against the rendered pages, including image and SVG spine items.
  • Preserve DOC headings and DOCX outline levels and inherited heading styles.
  • Load chapter metadata in the background and discard results for replaced books.
  • Keep FB2 section titles and comic bookmark and folder fallbacks.

Summary by CodeRabbit

  • New Features
    • Chapter outlines are available for more book formats, including PDF, DjVu, EPUB, Word documents, FB2, and comic archives.
    • Nested chapter headings are reflected in the outline, and chapter menus select the chapter matching the current page.
    • Chapter information loads in the background, keeping reading and navigation responsive.
  • Bug Fixes
    • Improved chapter destinations and metadata handling, including skipped blank comic chapter titles and more reliable outline fallbacks.

Note

Read document outlines in the chapter menu across PDF, EPUB, DOCX, DJVU, DOC, and FB2

Extracts real table-of-contents outlines from supported formats and shows them as an indented chapter menu instead of relying on ComicInfo or folder markers.

  • Adds outline extraction for PDF (pdftohtml), DJVU (djvused bookmarks), EPUB 2 NCX/EPUB 3 nav (with fallback to rendered headings), DOCX outline levels via inherited styles, DOC via antiword DocBook output, and nested FB2/DocBook sections. See outline.go, epub_chapters.go, docx.go, doc.go.
  • Chapter values now carry a zero-based Chapter.Depth, and the menu indents entries by depth in source outline order.
  • Chapter extraction moves to a Bubble Tea command so opening the Chapters menu no longer blocks; panes cache results and ignore stale results for replaced or remapped panes. See ui/chapters.go, ui/ui.go.
  • Headers use Book.CurrentChapter, which picks the nearest preceding chapter with later equal-page entries winning ties.
  • Behavioral Change: Book.Chapters now returns os.ErrClosed for closed books, DOC loading requires antiword DocBook output (plain line-per-paragraph output is gone), and PDF/DJVU tests now need pdftohtml/djvused installed.

Macroscope summarized 4711d79.

RetriggerConfidence Score: 5/5

The PR appears safe to merge, with a non-blocking DOC formatting regression and an existing test-coverage gap.

Findings

  1. P2 DOC text loses decoration ▶
  2. P2 DOC headings lack integration coverage ▶
Fix with agent prompt
### Issue 1
internal/book/doc.go:34-39
Underlined and struck-through DOC text now becomes a plain span. The renderer applies no decoration to spans, so these runs lose their visual distinction. In particular, struck-through text can appear to be current text.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

### Issue 2
internal/book/tool.go:undefined-172
The new conversion relies on antiword’s DocBook output to detect headings, but the real-tool test checks only plain paragraphs; the nested-heading test uses hand-written XML. Add a DOC fixture with headings so a change in antiword’s output cannot silently break the chapter menu.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR adds nested chapter extraction across document formats and loads chapter metadata asynchronously for the menu. Since the previous review, it also changes how DOC emphasis roles are converted.

Reviews (4) · Last reviewed commit: "Avoid treating DOC text decorations as i..."

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 35 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 8471386b-b871-47a4-ab47-a439f1e19ca2

📥 Commits

Reviewing files that changed from the base of the PR and between 9ed63ca and 4711d79.

📒 Files selected for processing (3)
  • internal/book/chapters_test.go
  • internal/book/doc.go
  • internal/book/docx.go

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 443764f8-4109-4cad-bb4f-ac47c692d7f3

📥 Commits

Reviewing files that changed from the base of the PR and between 65cf5f9 and 9ed63ca.

📒 Files selected for processing (20)
  • internal/book/chapters.go
  • internal/book/chapters_test.go
  • internal/book/doc.go
  • internal/book/docx.go
  • internal/book/docx_test.go
  • internal/book/epub.go
  • internal/book/epub_chapters.go
  • internal/book/epub_layout.go
  • internal/book/epub_render.go
  • internal/book/epub_test.go
  • internal/book/fb2.go
  • internal/book/fb2_test.go
  • internal/book/outline.go
  • internal/book/tool.go
  • internal/book/tool_test.go
  • internal/ui/chapters.go
  • internal/ui/chapters_test.go
  • internal/ui/menu.go
  • internal/ui/ui.go
  • internal/ui/ui_test.go

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change adds chapter extraction for several book formats, records outline depth and chapter page positions, and loads chapter metadata asynchronously for pane headers and menus.

Changes

Chapter extraction and navigation

Layer / File(s) Summary
Chapter contract and archive outlines
internal/book/chapters.go, internal/book/outline.go, internal/book/chapters_test.go
Chapter records now include zero-based depth. Chapter extraction handles closed books, comic metadata, and PDF and DjVu outlines. CurrentChapter selects the nearest preceding chapter.
EPUB navigation and destinations
internal/book/epub.go, internal/book/epub_chapters.go, internal/book/epub_layout.go, internal/book/epub_render.go, internal/book/epub_test.go, internal/book/chapters_test.go
EPUB chapter extraction reads navigation documents or NCX data, resolves chapter destinations, and records heading depth and first-line page positions.
Document-format headings and nesting
internal/book/doc.go, internal/book/docx.go, internal/book/fb2.go, internal/book/tool.go, internal/book/*_test.go
DOC extraction converts Antiword DocBook output. DOCX heading detection reads outline levels and inherited styles. DOC, DOCX, and FB2 headings carry nesting depth into chapter data.
Asynchronous chapter loading and display
internal/ui/chapters.go, internal/ui/chapters_test.go, internal/ui/menu.go, internal/ui/ui.go, internal/ui/ui_test.go
The UI caches chapter data per pane, ignores stale loading results, and displays loading state, nested chapter labels, and cached current chapters in menus and pane headers.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Model
  participant BookChapters as Book.Chapters
  User->>Model: Open chapter menu
  Model->>BookChapters: Load chapters asynchronously
  BookChapters-->>Model: Return chapters and error
  Model->>Model: Update matching pane cache
  Model-->>User: Refresh chapter menu
Loading

Merge Risk: ⚪ Minimal · up to 9ed63

The chapter-navigation changes have no established merge-blocking issue and are ready to merge subject to normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 9ed63

Chapter destinations remain validated, displayed titles are sanitized, and results from replaced books are rejected. Automatic extraction adds background parsing work, but no introduced security defect was established. Converter isolation and cancellation of archive metadata reads remain incompletely characterized.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The directly affected boundary is untrusted document content processed by the local reader and its converter subprocesses. If a third-party converter were compromised, exposure could extend to files and environment accessible to the reader's OS identity. The inspected invocation does not establish stronger process isolation; no converter exploit or privilege elevation was demonstrated.

Security Findings and Attack Paths

  • observed — The new extraction path supplies the opened document to fixed converter commands with separate arguments, rather than constructing shell commands from chapter text. EPUB external links are excluded from chapter destinations. These controls counter command-injection and external-destination concerns on the inspected path, without establishing the safety of third-party parsers.

Trust Boundaries and Controls

  • observed — Converter output crosses back into the reader through bounded reads. Each invocation has a two-minute timeout, bounded diagnostics and cancellation when stdout exceeds its limit. PDF outline output is limited to 4 MiB; DjVu outline output is limited to 1 MiB, and its parser limits nesting and entry count.

Resilience and Maintainability Implications

  • inferred — Book identity checks contain stale publication across replacement even when extraction completes late. ZIP entry lists remain unchanged during Close, while RAR Close performs no cleanup, narrowing the initial concern about concurrent archive mutation. These facts do not prove prompt termination of abandoned archive metadata reads, whose cancellation behavior remains a coverage gap rather than an established security finding.

Hardening Proposals

  • proposed — If hostile documents are part of the supported threat model, consider restricting converter filesystem and network authority. Existing timeout and output controls limit execution and returned data, but are not a substitute for OS-level isolation. This is a hardening proposal, not an observed PR vulnerability.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 42 functions across 20 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding document outline entries to the chapter menu.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread internal/book/tool.go Resolved
Comment thread internal/book/docx.go Resolved
Comment thread internal/book/tool.go
func openDOC(path string) (*Book, error) {
out, err := runTool("antiword", "-w", "0", "-m", "UTF-8.txt", path)
out, err := runTool("antiword", "-x", "db", path)
if err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 DOC headings lack integration coverage The new conversion relies on antiword’s DocBook output to detect headings, but the real-tool test checks only plain paragraphs; the nested-heading test uses hand-written XML. Add a DOC fixture with headings so a change in antiword’s output cannot silently break the chapter menu.

Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/book/tool.go
Line: 172

Comment:
**DOC headings lack integration coverage** The new conversion relies on antiword’s DocBook output to detect headings, but the real-tool test checks only plain paragraphs; the nested-heading test uses hand-written XML. Add a DOC fixture with headings so a change in antiword’s output cannot silently break the chapter menu.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The real antiword test covers the converter invocation and paragraph output; DocBook fixtures cover nested headings and page destinations. A binary DOC fixture with heading styles remains a coverage gap.

Comment thread internal/book/doc.go Resolved
Comment thread internal/book/doc.go Outdated
Comment on lines +34 to +37
out.name.Local = "em"
if n.attr("role") == "bold" {
out.name.Local = "strong"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium book/doc.go:34

emphasis elements with role="underline" or role="strikethrough" are rendered as <em>, so Antiword documents using those Word styles lose their underline/strikethrough formatting and appear italic instead. Handle these roles separately by emitting <u> and <s> (while retaining <strong> for bold).

-		out.name.Local = "em"
-		if n.attr("role") == "bold" {
-			out.name.Local = "strong"
-		}
+		switch n.attr("role") {
+		case "bold":
+			out.name.Local = "strong"
+		case "underline":
+			out.name.Local = "u"
+		case "strikethrough":
+			out.name.Local = "s"
+		}
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @internal/book/doc.go around lines 34-37:

`emphasis` elements with `role="underline"` or `role="strikethrough"` are rendered as `<em>`, so Antiword documents using those Word styles lose their underline/strikethrough formatting and appear italic instead. Handle these roles separately by emitting `<u>` and `<s>` (while retaining `<strong>` for `bold`).

Evidence trail:
Reviewed commit 05d8a7a: `internal/book/doc.go:33-37`; `internal/book/tool.go:169-180`; `internal/book/epub_render.go:388-390`; `internal/book/epub_layout.go:31-39,304-313`. Antiword implementation: https://github.com/grobian/antiword/blob/main/xml.c#L824-L888

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only italic emphasis now selects em; bold selects strong. Underline and strikethrough stay plain text because the shared layout does not draw those decorations. Added a role-mapping regression test.

Comment thread internal/book/doc.go
Comment on lines +34 to +39
switch n.attr("role") {
case "", "italic":
out.name.Local = "em"
case "bold":
out.name.Local = "strong"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 DOC text loses decoration Underlined and struck-through DOC text now becomes a plain span. The renderer applies no decoration to spans, so these runs lose their visual distinction. In particular, struck-through text can appear to be current text.

Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/book/doc.go
Line: 34-39

Comment:
**DOC text loses decoration** Underlined and struck-through DOC text now becomes a plain span. The renderer applies no decoration to spans, so these runs lose their visual distinction. In particular, struck-through text can appear to be current text.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOC previously used plain-text output, which did not display underline or strikethrough. Chapter extraction now preserves headings and the supported bold/italic styles. Drawing other decorations requires a separate change to the shared text layout; this keeps the prior plain-text behavior rather than substituting italics.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s fair. Since the previous DOC path rendered plain text and did not preserve underline or strikethrough either, this change does not introduce a regression; limiting decoration support to the existing bold/italic mappings is intentional. I’m withdrawing this finding.

@hawkff
hawkff merged commit dd8bd0f into main Oct 1, 2026
9 checks passed
@hawkff
hawkff deleted the feat/document-outlines branch October 1, 2026 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant