Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fe792df
feat(sections): add chapter numbering to section page numbering
luccas-harbour Jun 2, 2026
d60b95b
feat(layout): render chapter-prefixed page numbers
luccas-harbour Jun 2, 2026
6f084f6
fix(page-number): fall back chapter headings
luccas-harbour Jun 2, 2026
eff487d
fix(page-number): use no-break chapter hyphen
luccas-harbour Jun 2, 2026
55a6a55
fix(page-number): clear stale chapter children
luccas-harbour Jun 2, 2026
ed342bd
fix(layout): stamp section page format
luccas-harbour Jun 2, 2026
e41555e
fix(page-number): accept nested chapter markers
luccas-harbour Jun 2, 2026
a7ced9a
fix(layout): converge page tokens by output
luccas-harbour Jun 2, 2026
be9b573
fix(header-footer): disable buckets for restarts
luccas-harbour Jun 2, 2026
4d06b50
fix(page-number): preserve body token metadata
luccas-harbour Jun 2, 2026
0e9ad09
fix(header-footer): pass per-rid chapter context
luccas-harbour Jun 2, 2026
26b47d4
fix(layout): key chapter cache by fragments
luccas-harbour Jun 2, 2026
b287a41
fix(page-number): allow safe chapter separators
luccas-harbour Jun 2, 2026
a9cf6b2
fix(header-footer): measure chapter tokens in prelayout
luccas-harbour Jun 2, 2026
5baad65
fix(header-footer): preserve chapter page prefix in editor
luccas-harbour Jun 2, 2026
dcb98b2
fix(header-footer): clear stale chapter editor context
luccas-harbour Jun 2, 2026
bd6d2bb
fix(header-footer): prelayout resolved headings
luccas-harbour Jun 2, 2026
8948059
fix(header-footer): prelayout heading ordinal fallback
luccas-harbour Jun 3, 2026
9070452
fix(header-footer): prelayout two digit page tokens
luccas-harbour Jun 3, 2026
c95dfa0
fix(layout-bridge): keep numpages headers bucketed
luccas-harbour Jun 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/docs/document-api/reference/_generated-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1078,5 +1078,5 @@
}
],
"marker": "{/* GENERATED FILE: DO NOT EDIT. Regenerate via `pnpm run docapi:sync`. */}",
"sourceHash": "5f439c4117bbb2e55f227e7711df415c1173f8c476954c6e412a4b8b45edd1a3"
"sourceHash": "fc2e513626b5bfc6383d968b9bffdcea0f086469bf867a588dd7319c8b75c7de"
}
2 changes: 1 addition & 1 deletion apps/docs/document-api/reference/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ The tables below are grouped by namespace.
| <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><a href="/document-api/reference/sections/set-page-setup"><code>sections.setPageSetup</code></a></span> | <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><code>editor.doc.sections.setPageSetup(...)</code></span> | Set page size/orientation properties for a section. |
| <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><a href="/document-api/reference/sections/set-columns"><code>sections.setColumns</code></a></span> | <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><code>editor.doc.sections.setColumns(...)</code></span> | Set column configuration for a section. |
| <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><a href="/document-api/reference/sections/set-line-numbering"><code>sections.setLineNumbering</code></a></span> | <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><code>editor.doc.sections.setLineNumbering(...)</code></span> | Enable or configure line numbering for a section. |
| <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><a href="/document-api/reference/sections/set-page-numbering"><code>sections.setPageNumbering</code></a></span> | <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><code>editor.doc.sections.setPageNumbering(...)</code></span> | Set page numbering format/start for a section. |
| <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><a href="/document-api/reference/sections/set-page-numbering"><code>sections.setPageNumbering</code></a></span> | <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><code>editor.doc.sections.setPageNumbering(...)</code></span> | Set page numbering format/start and chapter numbering settings for a section. |
| <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><a href="/document-api/reference/sections/set-title-page"><code>sections.setTitlePage</code></a></span> | <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><code>editor.doc.sections.setTitlePage(...)</code></span> | Enable or disable title-page behavior for a section. |
| <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><a href="/document-api/reference/sections/set-odd-even-headers-footers"><code>sections.setOddEvenHeadersFooters</code></a></span> | <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><code>editor.doc.sections.setOddEvenHeadersFooters(...)</code></span> | Enable or disable odd/even header-footer mode in document settings. |
| <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><a href="/document-api/reference/sections/set-vertical-align"><code>sections.setVerticalAlign</code></a></span> | <span style={{ whiteSpace: 'nowrap', wordBreak: 'normal', overflowWrap: 'normal' }}><code>editor.doc.sections.setVerticalAlign(...)</code></span> | Set vertical page alignment for a section. |
Expand Down
16 changes: 16 additions & 0 deletions apps/docs/document-api/reference/sections/get.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ Returns a SectionInfo object with full section properties including margins, col
| `pageBorders.top.style` | string | no | |
| `pageBorders.zOrder` | enum | no | `"front"`, `"back"` |
| `pageNumbering` | object | no | |
| `pageNumbering.chapterSeparator` | enum | no | `"hyphen"`, `"period"`, `"colon"`, `"emDash"`, `"enDash"` |
| `pageNumbering.chapterStyle` | integer | no | |
| `pageNumbering.format` | enum | no | `"decimal"`, `"lowerLetter"`, `"upperLetter"`, `"lowerRoman"`, `"upperRoman"`, `"numberInDash"` |
| `pageNumbering.start` | integer | no | |
| `pageSetup` | object | no | |
Expand Down Expand Up @@ -614,6 +616,20 @@ Returns a SectionInfo object with full section properties including margins, col
"pageNumbering": {
"additionalProperties": false,
"properties": {
"chapterSeparator": {
"enum": [
"hyphen",
"period",
"colon",
"emDash",
"enDash"
],
"type": "string"
},
"chapterStyle": {
"minimum": 1,
"type": "integer"
},
"format": {
"enum": [
"decimal",
Expand Down
14 changes: 14 additions & 0 deletions apps/docs/document-api/reference/sections/list.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,20 @@ Returns a SectionsListResult with an ordered array of section summaries and thei
"pageNumbering": {
"additionalProperties": false,
"properties": {
"chapterSeparator": {
"enum": [
"hyphen",
"period",
"colon",
"emDash",
"enDash"
],
"type": "string"
},
"chapterStyle": {
"minimum": 1,
"type": "integer"
},
"format": {
"enum": [
"decimal",
Expand Down
52 changes: 48 additions & 4 deletions apps/docs/document-api/reference/sections/set-page-numbering.mdx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
---
title: sections.setPageNumbering
sidebarTitle: sections.setPageNumbering
description: Set page numbering format/start for a section.
description: Set page numbering format/start and chapter numbering settings for a section.
---

{/* GENERATED FILE: DO NOT EDIT. Regenerate via `pnpm run docapi:sync`. */}

## Summary

Set page numbering format/start for a section.
Set page numbering format/start and chapter numbering settings for a section.

- Operation ID: `sections.setPageNumbering`
- API member path: `editor.doc.sections.setPageNumbering(...)`
Expand All @@ -20,7 +20,7 @@ Set page numbering format/start for a section.

## Expected result

Returns a SectionMutationResult receipt; reports NO_OP if page numbering format already matches.
Returns a SectionMutationResult receipt; reports NO_OP if page numbering settings already match.

## Input fields

Expand All @@ -42,6 +42,24 @@ Returns a SectionMutationResult receipt; reports NO_OP if page numbering format
| `target.kind` | `"section"` | yes | Constant: `"section"` |
| `target.sectionId` | string | yes | |

### Variant 3 (target.kind="section")

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `chapterStyle` | integer | yes | |
| `target` | SectionAddress | yes | SectionAddress |
| `target.kind` | `"section"` | yes | Constant: `"section"` |
| `target.sectionId` | string | yes | |

### Variant 4 (target.kind="section")

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `chapterSeparator` | enum | yes | `"hyphen"`, `"period"`, `"colon"`, `"emDash"`, `"enDash"` |
| `target` | SectionAddress | yes | SectionAddress |
| `target.kind` | `"section"` | yes | Constant: `"section"` |
| `target.sectionId` | string | yes | |

### Example request

```json
Expand Down Expand Up @@ -107,7 +125,7 @@ Returns a SectionMutationResult receipt; reports NO_OP if page numbering format
```json
{
"additionalProperties": false,
"oneOf": [
"anyOf": [
{
"required": [
"target",
Expand All @@ -119,9 +137,35 @@ Returns a SectionMutationResult receipt; reports NO_OP if page numbering format
"target",
"format"
]
},
{
"required": [
"target",
"chapterStyle"
]
},
{
"required": [
"target",
"chapterSeparator"
]
}
],
"properties": {
"chapterSeparator": {
"enum": [
"hyphen",
"period",
"colon",
"emDash",
"enDash"
],
"type": "string"
},
"chapterStyle": {
"minimum": 1,
"type": "integer"
},
"format": {
"enum": [
"decimal",
Expand Down
4 changes: 2 additions & 2 deletions packages/document-api/src/contract/operation-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1259,8 +1259,8 @@ export const OPERATION_DEFINITIONS = {
},
'sections.setPageNumbering': {
memberPath: 'sections.setPageNumbering',
description: 'Set page numbering format/start for a section.',
expectedResult: 'Returns a SectionMutationResult receipt; reports NO_OP if page numbering format already matches.',
description: 'Set page numbering format/start and chapter numbering settings for a section.',
expectedResult: 'Returns a SectionMutationResult receipt; reports NO_OP if page numbering settings already match.',
requiresDocumentContext: true,
metadata: mutationOperation({
idempotency: 'conditional',
Expand Down
11 changes: 10 additions & 1 deletion packages/document-api/src/contract/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,8 @@ const sectionLineNumberingSchema = objectSchema(
const sectionPageNumberingSchema = objectSchema({
start: { type: 'integer', minimum: 1 },
format: sectionPageNumberFormatSchema,
chapterStyle: { type: 'integer', minimum: 1 },
chapterSeparator: { type: 'string', enum: ['hyphen', 'period', 'colon', 'emDash', 'enDash'] },
});

const sectionHeaderFooterRefsSchema = objectSchema({
Expand Down Expand Up @@ -4018,10 +4020,17 @@ const operationSchemas: Record<OperationId, OperationSchemaSet> = {
target: sectionAddressSchema,
start: { type: 'integer', minimum: 1 },
format: sectionPageNumberFormatSchema,
chapterStyle: { type: 'integer', minimum: 1 },
chapterSeparator: { type: 'string', enum: ['hyphen', 'period', 'colon', 'emDash', 'enDash'] },
},
['target'],
),
oneOf: [{ required: ['target', 'start'] }, { required: ['target', 'format'] }],
anyOf: [
{ required: ['target', 'start'] },
{ required: ['target', 'format'] },
{ required: ['target', 'chapterStyle'] },
{ required: ['target', 'chapterSeparator'] },
],
},
output: sectionMutationResultSchemaFor('sections.setPageNumbering'),
success: sectionMutationSuccessSchema,
Expand Down
1 change: 1 addition & 0 deletions packages/document-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1429,6 +1429,7 @@ export type {
SectionPageBorders,
SectionPageMargins,
SectionPageNumbering,
SectionPageNumberingChapterSeparator,
SectionPageNumberingFormat,
SectionPageSetup,
SectionRangeDomain,
Expand Down
52 changes: 51 additions & 1 deletion packages/document-api/src/sections/sections.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,57 @@ describe('sections API validation', () => {
executeSectionsSetPageNumbering(adapter, {
target: { kind: 'section', sectionId: 'section-0' },
}),
).toThrow(/requires at least one of start or format/i);
).toThrow(/requires at least one of start, format, chapterStyle, or chapterSeparator/i);
});

it('accepts chapterStyle for setPageNumbering', () => {
const setPageNumbering = mock(makeAdapter().setPageNumbering);
const adapter = makeAdapter({ setPageNumbering });

executeSectionsSetPageNumbering(adapter, {
target: { kind: 'section', sectionId: 'section-0' },
chapterStyle: 1,
});

expect(setPageNumbering).toHaveBeenCalledWith(
{ target: { kind: 'section', sectionId: 'section-0' }, chapterStyle: 1 },
{ changeMode: 'direct', dryRun: false, expectedRevision: undefined },
);
});

it('accepts valid chapterSeparator for setPageNumbering', () => {
const setPageNumbering = mock(makeAdapter().setPageNumbering);
const adapter = makeAdapter({ setPageNumbering });

executeSectionsSetPageNumbering(adapter, {
target: { kind: 'section', sectionId: 'section-0' },
chapterSeparator: 'enDash',
});

expect(setPageNumbering).toHaveBeenCalledWith(
{ target: { kind: 'section', sectionId: 'section-0' }, chapterSeparator: 'enDash' },
{ changeMode: 'direct', dryRun: false, expectedRevision: undefined },
);
});

it('rejects invalid chapterSeparator for setPageNumbering', () => {
const adapter = makeAdapter();
expect(() =>
executeSectionsSetPageNumbering(adapter, {
target: { kind: 'section', sectionId: 'section-0' },
chapterSeparator: 'slash' as any,
}),
).toThrow(/chapterSeparator/i);
});

it('rejects chapterStyle less than 1 for setPageNumbering', () => {
const adapter = makeAdapter();
expect(() =>
executeSectionsSetPageNumbering(adapter, {
target: { kind: 'section', sectionId: 'section-0' },
chapterStyle: 0,
}),
).toThrow(/chapterStyle/i);
});

it('requires at least one field for setPageBorders', () => {
Expand Down
21 changes: 19 additions & 2 deletions packages/document-api/src/sections/sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
SectionHeaderFooterVariant,
SectionDirection,
SectionOrientation,
SectionPageNumberingChapterSeparator,
SectionVerticalAlign,
SectionsClearHeaderFooterRefInput,
SectionsClearPageBordersInput,
Expand Down Expand Up @@ -41,6 +42,7 @@ export type {
SectionBreakType,
SectionHeaderFooterKind,
SectionHeaderFooterVariant,
SectionPageNumberingChapterSeparator,
SectionsClearHeaderFooterRefInput,
SectionsClearPageBordersInput,
SectionsGetInput,
Expand Down Expand Up @@ -81,6 +83,13 @@ const PAGE_NUMBER_FORMATS = [
'upperRoman',
'numberInDash',
] as const;
const PAGE_NUMBER_CHAPTER_SEPARATORS: readonly SectionPageNumberingChapterSeparator[] = [
'hyphen',
'period',
'colon',
'emDash',
'enDash',
] as const;
const PAGE_BORDER_DISPLAYS = ['allPages', 'firstPage', 'notFirstPage'] as const;
const PAGE_BORDER_OFFSET_FROM_VALUES = ['page', 'text'] as const;
const PAGE_BORDER_Z_ORDER_VALUES = ['front', 'back'] as const;
Expand Down Expand Up @@ -390,17 +399,25 @@ export function executeSectionsSetPageNumbering(
options?: MutationOptions,
): SectionMutationResult {
assertSectionTarget(input, 'sections.setPageNumbering');
if (!hasAnyDefined(input as unknown as Record<string, unknown>, ['start', 'format'])) {
if (
!hasAnyDefined(input as unknown as Record<string, unknown>, ['start', 'format', 'chapterStyle', 'chapterSeparator'])
) {
throw new DocumentApiValidationError(
'INVALID_INPUT',
'sections.setPageNumbering requires at least one of start or format.',
'sections.setPageNumbering requires at least one of start, format, chapterStyle, or chapterSeparator.',
);
}

if (input.start !== undefined) assertPositiveInteger(input.start, 'sections.setPageNumbering.start');
if (input.format !== undefined) {
assertOneOf(input.format, 'sections.setPageNumbering.format', PAGE_NUMBER_FORMATS);
}
if (input.chapterStyle !== undefined) {
assertPositiveInteger(input.chapterStyle, 'sections.setPageNumbering.chapterStyle');
}
if (input.chapterSeparator !== undefined) {
assertOneOf(input.chapterSeparator, 'sections.setPageNumbering.chapterSeparator', PAGE_NUMBER_CHAPTER_SEPARATORS);
}

return adapter.setPageNumbering(input, normalizeMutationOptions(options));
}
Expand Down
6 changes: 6 additions & 0 deletions packages/document-api/src/sections/sections.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export type SectionPageNumberingFormat =
| 'upperRoman'
| 'numberInDash';

export type SectionPageNumberingChapterSeparator = 'hyphen' | 'period' | 'colon' | 'emDash' | 'enDash';

export interface SectionPageMargins {
top?: number;
right?: number;
Expand Down Expand Up @@ -73,6 +75,8 @@ export interface SectionLineNumbering {
export interface SectionPageNumbering {
start?: number;
format?: SectionPageNumberingFormat;
chapterStyle?: number;
chapterSeparator?: SectionPageNumberingChapterSeparator;
}

export interface SectionHeaderFooterRefs {
Expand Down Expand Up @@ -227,6 +231,8 @@ export interface SectionsSetLineNumberingInput extends SectionTargetInput {
export interface SectionsSetPageNumberingInput extends SectionTargetInput {
start?: number;
format?: SectionPageNumberingFormat;
chapterStyle?: number;
chapterSeparator?: SectionPageNumberingChapterSeparator;
}

export interface SectionsSetTitlePageInput extends SectionTargetInput {
Expand Down
2 changes: 2 additions & 0 deletions packages/document-api/src/types/sd-sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export interface SDSection {
pageNumbering?: {
start?: number;
format?: 'decimal' | 'lowerLetter' | 'upperLetter' | 'lowerRoman' | 'upperRoman' | 'numberInDash';
chapterStyle?: number;
chapterSeparator?: 'hyphen' | 'period' | 'colon' | 'emDash' | 'enDash';
};
titlePage?: boolean;
oddEvenHeadersFooters?: boolean;
Expand Down
Loading
Loading