Skip to content

Feature/repeat header on page breaks#567

Open
azamat7g wants to merge 8 commits into
johnfercher:masterfrom
azamat7g:feature/repeat-header-on-page-breaks
Open

Feature/repeat header on page breaks#567
azamat7g wants to merge 8 commits into
johnfercher:masterfrom
azamat7g:feature/repeat-header-on-page-breaks

Conversation

@azamat7g

@azamat7g azamat7g commented May 2, 2026

Copy link
Copy Markdown

Description
Implements row-level repeat-on-page-break functionality. Allows marking individual rows to be automatically re-injected at the top of new pages when content overflows.

Use Case: Multi-page tables (invoices, reports, data exports) where column headers must repeat on every new page.

How It Works

// Create a header row that repeats on page breaks
headerRow := row.New(8).
    Add(col.New(4).Add(text.NewCol("ID"))).
    Add(col.New(8).Add(text.NewCol("Description"))).
    WithRepeatOnPageBreak()

m.AddRows(headerRow)

// Add many data rows (will trigger page breaks automatically)
for i := 0; i < 100; i++ {
    m.AddRow(8, col.New(4), col.New(8))
}

// Output:
// Page 1: [Header] [Row 1-20]
// Page 2: [Header] [Row 21-40]  <- Header repeats automatically
// Page 3: [Header] [Row 41-60]  <- Header repeats automatically

Comparison: Global Header vs Repeat Header

Feature RegisterHeader() WithRepeatOnPageBreak()
Appears on Every page (global) Only on page breaks (local)
Use case Document header, logo, metadata Table header, section title
Setup m.RegisterHeader(rows...) row.New().WithRepeatOnPageBreak()
image_2026-05-03_04-05-14

Checklist

check with "x", ONLY IF APPLIED to your change

  • All methods associated with structs has func (<first letter of struct> *struct) method() {} name style.
  • Wrote unit tests for new/changed features.
  • Followed the unit test when,should naming pattern.
  • All mocks created with m := mocks.NewConstructor(t).
  • All mocks using m.EXPECT().MethodName() method to mock methods.
  • Updated docs/*
  • Updated example_test.go.
  • Updated README.md
  • New public methods/structs/interfaces has comments upside them explaining they responsibilities
  • Executed make dod with none issues pointed out by golangci-lint

@coderabbitai

coderabbitai Bot commented May 2, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0acea09b-96bd-464b-9955-6cfe128a378f

📥 Commits

Reviewing files that changed from the base of the PR and between 2fb9e0b and 39b111b.

📒 Files selected for processing (1)
  • maroto.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Rows can be marked to automatically reappear at the top of each new page when a page break occurs.
  • Bug Fixes

    • Improved page-break handling so repeating rows are inserted on the new page before overflowing content, preventing layout truncation.
  • Documentation

    • Added a “Repeat Header” docs page and sidebar entry with usage notes and two examples.
  • Tests

    • Added unit, example, integration tests and a layout fixture validating repeating-row behavior.

Walkthrough

Add repeat-on-page-break: extend the Row interface and implementation, update Maroto’s page-break logic to copy marked rows onto new pages, and add mocks, tests, an example, a JSON fixture, and documentation plus a sidebar entry.

Changes

Repeat Header on Page Breaks

Layer / File(s) Summary
Interface Contract
pkg/core/core.go
Row interface extended with WithRepeatOnPageBreak() Row and IsRepeatOnPageBreak() bool.
Row Implementation
pkg/components/row/row.go
Row adds repeatOnPageBreak bool; implements WithRepeatOnPageBreak() setter and IsRepeatOnPageBreak() getter.
Examples
pkg/components/row/example_test.go
ExampleRow_WithRepeatOnPageBreak demonstrating a header row that repeats across generated pages.
Maroto Page-Break Flow
maroto.go
addRow() now, on overflow, collects rows with IsRepeatOnPageBreak() (excluding current header), creates a new page and header, conditionally appends collected repeat rows, then re-checks capacity and may create another page+header if still insufficient. New helpers: collectRepeatRows() and addRepeatRows().
Mocks
mocks/Row.go
Added mock methods, expecter and call helpers for WithRepeatOnPageBreak() and IsRepeatOnPageBreak().
Tests & Fixture
pkg/components/row/row_test.go, maroto_test.go, test/maroto/maroto_add_row_repeat.json
Unit test for row flag, integration subtest asserting repeated-row page structure, and JSON fixture describing expected pages.
Docs & Sidebar
docs/v2/features/repeatheader.md, docs/v2/features/_sidebar.md
New feature documentation with examples and related features; sidebar entry added for “Repeat Header”.

Sequence Diagram

sequenceDiagram
    participant Client as Client
    participant Maroto as Maroto
    participant Page as Page
    participant Row as Row

    Client->>Maroto: AddRow(row)
    Maroto->>Row: IsRepeatOnPageBreak?
    Maroto->>Page: fitsOnCurrentPage? (height check)
    alt does not fit
        Maroto->>Maroto: collectRepeatRows()
        Maroto->>Page: fillPageToAddNew() / addHeader()
        Maroto->>Maroto: addRepeatRows(repeatRows)
        Maroto->>Maroto: recheck capacity (header+repeatRows)
        alt still does not fit
            Maroto->>Page: fillPageToAddNew() / addHeader()
        end
        Maroto->>Page: add overflowing row
    else fits
        Maroto->>Page: append row
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I hop through pages, light and spry,
A header set to never shy,
When space runs out and lines must part,
I pop atop the next page's start,
Mark me once — I'll play my part. 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feature/repeat header on page breaks' clearly and concisely summarizes the main feature added—row-level repeat-on-page-break functionality for headers.
Description check ✅ Passed The description covers all required sections: clear explanation of the feature, use case, code examples, comparison with existing functionality, and a complete checklist with most items checked.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: Turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get your free trial and get 200 agent minutes per Slack user (a $50 value).


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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/v2/features/repeatheader.md`:
- Around line 58-72: The example uses text.NewCol with the wrong signature and
nests it inside col.New(...).Add(...), causing compilation: change usages of
col.New(...).Add(text.NewCol(...)) and
row.New(...).Add(col.New(...).Add(text.NewCol(...))) to use
text.NewCol(gridSize, value) directly as the column component (e.g., replace the
col.New(...).Add(text.NewCol(...)) pattern with just text.NewCol(gridSize,
"value")) and update all similar data-row and header-row usages (referencing
text.NewCol, col.New, row.New, m.AddRow, m.AddRows); also ensure fmt is imported
in the example imports.
- Around line 130-207: Add missing language specifiers and blank lines around
fenced code blocks and ensure headings are surrounded by blank lines: add a
language tag (e.g., ```text) to the "Output Structure" example block and use
```go for Go examples (the blocks that contain "m.RegisterHeader(...)", "cfg :=
config.NewBuilder()", and the "header := row.New..." examples), and insert a
blank line before and after each fenced block and before/after the headings
"Single Repeat Header", "Multiple Repeat Headers (Section Headers)" and "Styled
Repeat Headers" so the markdownlint MD040, MD031 and MD022 violations are
resolved.

In `@maroto.go`:
- Around line 224-236: The code appends row r unconditionally after
m.addHeader() and m.addRepeatRows(), which can cause m.currentHeight + rowHeight
to exceed the page maxHeight; update the flow in the block that uses
collectRepeatRows(), fillPageToAddNew(), addHeader(), and addRepeatRows() to
re-check available space before appending r: compute or check if
m.currentHeight+rowHeight > m.maxHeight (or call the same page-break logic as
fillPageToAddNew()) and if so start a new page (adjust currentHeight, call
addHeader()/addRepeatRows() as needed) before doing m.currentHeight += rowHeight
and m.rows = append(m.rows, r) so r never overflows the page boundary.
- Around line 380-395: collectRepeatRows is including rows that were registered
via RegisterHeader/addHeader, causing header rows with WithRepeatOnPageBreak to
be duplicated; fix by filtering out header-managed rows when collecting repeats:
in collectRepeatRows build a lookup of rows that addHeader/RegisterHeader added
(e.g., use m.headerRows or create a temporary map keyed by row identity) and
skip any r present in that lookup or marked as header before appending; this
ensures collectRepeatRows only returns true repeat-on-page-break rows not
already handled by addHeader, leaving addRepeatRows unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8b5df5e7-030c-4b5e-b9c5-325cead857c1

📥 Commits

Reviewing files that changed from the base of the PR and between aa2645f and 542eebf.

📒 Files selected for processing (10)
  • docs/v2/features/_sidebar.md
  • docs/v2/features/repeatheader.md
  • maroto.go
  • maroto_test.go
  • mocks/Row.go
  • pkg/components/row/example_test.go
  • pkg/components/row/row.go
  • pkg/components/row/row_test.go
  • pkg/core/core.go
  • test/maroto/maroto_add_row_repeat.json

Comment thread docs/v2/features/repeatheader.md
Comment thread docs/v2/features/repeatheader.md Outdated
Comment thread maroto.go
Comment thread maroto.go
azamat7g and others added 3 commits May 3, 2026 11:39
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
docs/v2/features/repeatheader.md (1)

46-52: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix non-compiling Go examples (fmt import and text.NewCol usage).

Line 69-71 uses fmt.Sprintf but fmt is not imported; Line 48 imports col but it’s unused in the first example.
Also, Line 95-105 still uses col.New(...).Add(text.NewCol(...)) with the old text.NewCol call style; this example will not compile as written.

Suggested patch
 import (
+	"fmt"
 	"github.com/johnfercher/maroto/v2"
-	"github.com/johnfercher/maroto/v2/pkg/components/col"
 	"github.com/johnfercher/maroto/v2/pkg/components/row"
 	"github.com/johnfercher/maroto/v2/pkg/components/text"
 	"github.com/johnfercher/maroto/v2/pkg/config"
 )
 func (p *Product) GetHeader() core.Row {
 	return row.New(8).
-		Add(col.New(3).Add(text.NewCol("Item ID"))).
-		Add(col.New(6).Add(text.NewCol("Product Name"))).
-		Add(col.New(3).Add(text.NewCol("Price"))).
+		Add(text.NewCol(3, "Item ID")).
+		Add(text.NewCol(6, "Product Name")).
+		Add(text.NewCol(3, "Price")).
 		WithRepeatOnPageBreak()  // Header repeats on page breaks
 }

 func (p *Product) GetContent(i int) core.Row {
 	return row.New(6).
-		Add(col.New(3).Add(text.NewCol(p.ID))).
-		Add(col.New(6).Add(text.NewCol(p.Name))).
-		Add(col.New(3).Add(text.NewCol(fmt.Sprintf("$%.2f", p.Price))))
+		Add(text.NewCol(3, p.ID)).
+		Add(text.NewCol(6, p.Name)).
+		Add(text.NewCol(3, fmt.Sprintf("$%.2f", p.Price)))
 }
#!/bin/bash
# Verify this docs file still contains problematic patterns.
rg -n 'fmt\.Sprintf|pkg/components/col|col\.New\(|text\.NewCol\("[^"]+"\)' docs/v2/features/repeatheader.md

Also applies to: 69-71, 95-105

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/v2/features/repeatheader.md` around lines 46 - 52, Add the missing "fmt"
import, remove the unused "col" import in the first example, and update the old
text.NewCol usage to the current struct form so examples compile: import fmt
into the import block and delete the unused
github.com/johnfercher/maroto/v2/pkg/components/col import from the first
snippet; then replace any col.New(...).Add(text.NewCol("...")) calls with
col.New(...).Add(text.Col{Text: fmt.Sprintf("...")}) (or text.Col{Text: "..."}
when no formatting is needed) so the examples use the current text.Col symbol
instead of the deprecated text.NewCol.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/v2/features/repeatheader.md`:
- Around line 130-135: The "## Output Structure" section is empty and should
either be completed with the promised example or removed; update the
docs/v2/features/repeatheader.md by either inserting the intended example
showing the new page structure when a page break occurs (diagram, prose, or code
block) immediately after the "## Output Structure" heading, or delete the entire
"## Output Structure" heading and any placeholder line so the flow jumps
directly to "## Related Features"; ensure the change keeps formatting consistent
with surrounding markdown.

---

Duplicate comments:
In `@docs/v2/features/repeatheader.md`:
- Around line 46-52: Add the missing "fmt" import, remove the unused "col"
import in the first example, and update the old text.NewCol usage to the current
struct form so examples compile: import fmt into the import block and delete the
unused github.com/johnfercher/maroto/v2/pkg/components/col import from the first
snippet; then replace any col.New(...).Add(text.NewCol("...")) calls with
col.New(...).Add(text.Col{Text: fmt.Sprintf("...")}) (or text.Col{Text: "..."}
when no formatting is needed) so the examples use the current text.Col symbol
instead of the deprecated text.NewCol.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 12fae359-3a04-4a6d-8131-77ea2451144b

📥 Commits

Reviewing files that changed from the base of the PR and between 542eebf and 3c993fc.

📒 Files selected for processing (1)
  • docs/v2/features/repeatheader.md

Comment thread docs/v2/features/repeatheader.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
docs/v2/features/repeatheader.md (2)

93-99: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Incomplete fix: Same compilation error from previous review.

The fix from the earlier review (commit 1e0965b) corrected the first example but missed this second example. Lines 95-97 still use the broken pattern col.New(3).Add(text.NewCol("Item ID")), which has two problems:

  1. text.NewCol requires (gridSize int, value string) — the grid-size argument is missing.
  2. col.Add expects ...core.Component, but text.NewCol returns core.Col (incompatible interfaces).

This pattern is also repeated in lines 103-105.

🐛 Proposed fix — use `text.NewCol` directly
 func (p *Product) GetHeader() core.Row {
 	return row.New(8).
-		Add(col.New(3).Add(text.NewCol("Item ID"))).
-		Add(col.New(6).Add(text.NewCol("Product Name"))).
-		Add(col.New(3).Add(text.NewCol("Price"))).
+		Add(text.NewCol(3, "Item ID")).
+		Add(text.NewCol(6, "Product Name")).
+		Add(text.NewCol(3, "Price")).
 		WithRepeatOnPageBreak()  // Header repeats on page breaks
 }
 
 func (p *Product) GetContent(i int) core.Row {
 	return row.New(6).
-		Add(col.New(3).Add(text.NewCol(p.ID))).
-		Add(col.New(6).Add(text.NewCol(p.Name))).
-		Add(col.New(3).Add(text.NewCol(fmt.Sprintf("$%.2f", p.Price))))
+		Add(text.NewCol(3, p.ID)).
+		Add(text.NewCol(6, p.Name)).
+		Add(text.NewCol(3, fmt.Sprintf("$%.2f", p.Price)))
 }

Note: Ensure fmt is also imported in the complete example.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/v2/features/repeatheader.md` around lines 93 - 99, The GetHeader example
still uses the broken pattern col.New(...).Add(text.NewCol("...")) which omits
the gridSize arg and mismatches types: text.NewCol(gridSize, value) returns
core.Col and should be added directly to the row, not wrapped by
col.New(...).Change Product.GetHeader to call text.NewCol with an explicit grid
size and value (e.g. text.NewCol(3, "Item ID"), text.NewCol(6, "Product Name"),
text.NewCol(3, "Price")) and pass those directly into row.New(...).Add(...);
also ensure fmt is imported in the surrounding example as noted.

46-52: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing fmt import breaks the example.

Line 69 uses fmt.Sprintf, but the fmt package is not imported. This example will not compile.

🐛 Proposed fix
 import (
+	"fmt"
+
 	"github.com/johnfercher/maroto/v2"
 	"github.com/johnfercher/maroto/v2/pkg/components/col"
 	"github.com/johnfercher/maroto/v2/pkg/components/row"
 	"github.com/johnfercher/maroto/v2/pkg/components/text"
 	"github.com/johnfercher/maroto/v2/pkg/config"
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/v2/features/repeatheader.md` around lines 46 - 52, The example is
missing the fmt import used by fmt.Sprintf, causing a compile error; update the
import block in this file to include the "fmt" package so references to
fmt.Sprintf compile successfully, locating the import list near the existing
imports (github.com/johnfercher/maroto/v2, components/col, row, text, and
config) and adding "fmt" alongside them.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@docs/v2/features/repeatheader.md`:
- Around line 93-99: The GetHeader example still uses the broken pattern
col.New(...).Add(text.NewCol("...")) which omits the gridSize arg and mismatches
types: text.NewCol(gridSize, value) returns core.Col and should be added
directly to the row, not wrapped by col.New(...).Change Product.GetHeader to
call text.NewCol with an explicit grid size and value (e.g. text.NewCol(3, "Item
ID"), text.NewCol(6, "Product Name"), text.NewCol(3, "Price")) and pass those
directly into row.New(...).Add(...); also ensure fmt is imported in the
surrounding example as noted.
- Around line 46-52: The example is missing the fmt import used by fmt.Sprintf,
causing a compile error; update the import block in this file to include the
"fmt" package so references to fmt.Sprintf compile successfully, locating the
import list near the existing imports (github.com/johnfercher/maroto/v2,
components/col, row, text, and config) and adding "fmt" alongside them.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f2e84b65-0cc7-48ba-895a-4d89f67ed717

📥 Commits

Reviewing files that changed from the base of the PR and between 3c993fc and 6e5d3e9.

📒 Files selected for processing (1)
  • docs/v2/features/repeatheader.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@maroto.go`:
- Around line 232-239: The current flow calls m.addRepeatRows(repeatRows) then,
if the row still doesn't fit, calls m.fillPageToAddNew() and m.addHeader(),
which can produce a page with only header/repeat rows and place the overflow row
on a page without repeats; change the logic so that when
m.currentHeight+rowHeight > maxHeight-m.footerHeight after calling
m.addRepeatRows(repeatRows) you perform the page break (m.fillPageToAddNew()),
call m.addHeader(), and then re-add the repeat rows (e.g., call
m.addRepeatRows(repeatRows) again or move the original addRepeatRows call to
after addHeader()) before placing the overflow row so the overflow row always
has its repeat rows on the same new page.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e8e64f7a-be0b-49a7-a736-9c0bb526af6b

📥 Commits

Reviewing files that changed from the base of the PR and between 6e5d3e9 and 2fb9e0b.

📒 Files selected for processing (1)
  • maroto.go

Comment thread maroto.go Outdated
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