Skip to content

feat: pagination — numbering formats, forced breaks, resets, PDF page labels (#46)#55

Merged
drnachio merged 6 commits into
developfrom
feature/issue-46-pagination
Apr 23, 2026
Merged

feat: pagination — numbering formats, forced breaks, resets, PDF page labels (#46)#55
drnachio merged 6 commits into
developfrom
feature/issue-46-pagination

Conversation

@drnachio
Copy link
Copy Markdown
Owner

@drnachio drnachio commented Apr 23, 2026

Summary

  • Adds page.pageNumbering (format + startAt) plus :::pagebreak / :::numbering directives and per-heading breakBefore so documents can mix roman front matter, arabic chapters from 1, and chapters opening on the right-hand page.
  • VDTPage now carries pageNumberValue / pageLabel / pageNumberFormat / blankForParity; the PDF backend emits a /PageLabels number tree (with /P prefix fallback past Z for alpha).
  • {pageNumber} placeholder now resolves to page.pageLabel.
  • Sandbox UI, warnings (unknownDirective, numberingInvalid*, pagebreakInvalidParity, parityCascade, alphaPdfOverflow), docs (EN + ES), and tests land in the same PR.

Closes #46
Unblocks #45

Test plan

  • pnpm --filter postext test — 188 passing (adds numbering-page.test.ts and directives.test.ts, updates exports.test.ts)
  • pnpm check-types — postext / postext-pdf / postext-sandbox clean; web typecheck error is pre-existing on develop
  • pnpm lint — no new errors (pre-existing warnings only in postext-sandbox)
  • Sandbox preview — Numbering subsection renders under Page (Format + Start at); Break before toggle present per heading level (H1–H6)
  • Manual check: load a document with :::numbering + heading breakBefore: { enabled: true, parity: 'odd' } and confirm the PDF page indicator matches the printed labels in Preview / Acrobat

🤖 Generated with Claude Code

… labels (#46)

Overhauls the pagination system so postext can produce books and long-form
documents with the numbering conventions readers expect (roman front
matter, arabic chapters from 1, chapters opening on the right-hand page,
etc.).

- `page.pageNumbering` (format + startAt) drives the document-wide default.
- `:::pagebreak` and `:::numbering` directives parsed as new block type,
  consumed by the placement pipeline as control markers.
- Heading `breakBefore` (enabled + parity) forces page opens per level,
  padding with a blank parity page when needed.
- `VDTPage` now carries `pageNumberValue`, `pageLabel`,
  `pageNumberFormat`, and `blankForParity`.
- `buildPageLabels` / `collectPageLabelRuns` added to `numbering.ts`.
- PDF backend emits a `/PageLabels` number tree matching VDT runs, with
  per-page `P` prefix fallback past Z for alpha formats.
- `{pageNumber}` placeholder now resolves to `page.pageLabel`.
- Sandbox: Numbering subsection under Page; `Break before` toggle +
  parity selector per heading level.
- New warnings: unknown directive, invalid numbering format / startAt,
  invalid parity, parity cascade, alpha PDF overflow.
- Docs updated in EN + ES (configuration, document-format, sandbox).
- Tests: buildPageLabels, collectPageLabelRuns, directive parser.

Closes #46
Unblocks #45

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
postext Ready Ready Preview, Comment Apr 23, 2026 8:39am

Request Review

When a heading `breakBefore: { parity: 'odd' }` or `:::pagebreak{parity=...}`
forces a new page on a specific parity, a blank padding page can be
inserted to push the next content onto the right side. That blank page
was previously inheriting the PREVIOUS chapter's title in the
`{chapterTitle}` header placeholder — making the reader think chapter N
continues across a blank left-hand page before chapter N+1 begins.

Conceptually the blank page only exists because of the upcoming chapter,
so the parity padding should carry the new chapter's title. After
computing titles the walker now scans backwards from each H1's page
index and overwrites any consecutive `blankForParity` pages with the
upcoming chapter's title.

`computeChapterTitles` gains an optional `pages?: ChapterTitlePageInfo[]`
parameter for backwards compatibility — existing callers that only pass
`(blocks, totalPages)` keep the legacy behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…arse

next-intl parses messages through ICU MessageFormat, so a literal `{...}`
was being read as a malformed placeholder and the sandbox page crashed
with `INVALID_MESSAGE: MALFORMED_ARGUMENT`. Removing the example
attributes from the tooltip sidesteps the parser entirely — the button's
default action already inserts a directive with sensible defaults, so the
tooltip doesn't need to spell the attribute syntax out.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e parity

Adds two new values to `HeadingBreakParity` (and the `:::pagebreak{parity=...}`
attribute): `'always-odd'` and `'always-even'`. These guarantee at least
one mandatory blank separator page between the previous content and the
new heading, on top of the existing parity guarantee.

The leading separator is flagged `blankForForce: true` — it belongs to
the *previous* chapter (acts as a deliberate end-of-chapter gap). Any
parity padding that follows is still `blankForParity: true` and
belongs to the *new* chapter, preserving the prior fix.

`computeChapterTitles` stops its backward walk at `blankForForce`
pages, so the separator keeps the previous title in its header.

Sandbox:
- SelectInput for `breakBefore.parity` now exposes 5 options and the
  whole conditional block is wrapped in `NestedGroup` for consistent
  visual grouping with other toggle + children patterns.
- EN/ES labels added for the two new modes.
- Warnings validator accepts the new attribute values.

Docs + tests updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a document opens with a heading that has `breakBefore.enabled`
(or the source begins with a `:::pagebreak`), the placement cursor is
already sitting on page 0 with nothing placed yet. Running parity or
`always-*` padding at that point would insert a spurious leading blank
page — there is no previous content to separate from.

`enforcePageParity` now short-circuits when the cursor is on page 0
and every column of that page is still empty. Once any content has
been placed, parity enforcement behaves exactly as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@drnachio drnachio merged commit 94c3d7f into develop Apr 23, 2026
3 checks passed
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