Skip to content

feat(session): header/footer + page-number authoring (DocxSession → editor) (#236)#274

Merged
JSv4 merged 3 commits into
mainfrom
claude/issue-236-evaluation-znvbn3
Jul 12, 2026
Merged

feat(session): header/footer + page-number authoring (DocxSession → editor) (#236)#274
JSv4 merged 3 commits into
mainfrom
claude/issue-236-evaluation-znvbn3

Conversation

@JSv4

@JSv4 JSv4 commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Closes #236.

Evaluation: the issue is present

I verified the gap in the current codebase (post-v7.0.1). DocxSession could inspect a section's header/footer parts (SectionInfo.HeaderPartUris/FooterPartUris — read-only) but had no method to create or edit them, and no page-number field op. Confirmed across every layer named in the issue: DocxSession.cs, DocxSessionBridge.cs, editor.ts, Dispatcher.cs, docx_scalpel. This is not a redlining concern and does not touch WmlComparer/DocxDiff — it's the DocxSession mutation/editor surface — so per the task's scoping it's implemented directly (no redline-engine changes).

What this adds

Three new DocxSession methods, rippled through all layers per CLAUDE.md:

Method Behavior
SetHeaderText(bodyAnchor, HeaderFooterKind, markdown) Set the running header for the section that owns any body block.
SetFooterText(bodyAnchor, HeaderFooterKind, markdown) Same, for the footer.
InsertPageNumberField(anchor, PageNumberField = CurrentPage) Append a native PAGE/NUMPAGES field to a paragraph.
  • Addressing. SetHeaderText/SetFooterText take any body block in the target section; the governing w:sectPr is resolved exactly as GetSectionInfo resolves it (forward mid-doc section break → body trailing sectPr), synthesizing a trailing sectPr if the body has none. A non-body anchor is an AnchorWrongKind error (a story attaches to a body section).
  • Kinds & visibility. HeaderFooterKind = Default/First/Even → the reference w:type. First sets the section's w:titlePg; Even sets w:evenAndOddHeaders in the settings part — without those Word ignores the story. Same-kind twice reuses the part and replaces content (idempotent "set"); a mismatched/stale reference self-heals.
  • Content. Same markdown subset as InsertParagraph; each un-styled paragraph gets the built-in Header/Footer style so it inherits Word's centre/right tab stops. Created returns the new hdr{N}/ftr{N} paragraph anchors → feed one to InsertPageNumberField. The field is a native complex field (fldChar/instrText, cached "1").
  • Undo/redo. Extended the snapshot to reconcile header/footer part create/delete (previously only the annotations custom-XML part was reconciled — the RestoreSnapshot TODO). The snapshot records each part's relationship id; restore deletes parts the snapshot lacks and re-creates the ones it has with their original relId (AddNewPart<T>(relId)) so the restored sectPr reference resolves. One documented edge: the w:evenAndOddHeaders flag (only set by Even) isn't reverted by undo — idempotent, no visual effect without an even story.

Recipe — the S-1 running footer this issue was found reproducing

var body = session.Project().AnchorIndex.Values
    .First(t => t.Anchor.Kind == "p" && t.Anchor.Scope == "body").Anchor.Id;
var footer = session.SetFooterText(body, HeaderFooterKind.Default, "Last Updated October 2025");
session.SetParagraphFormat(footer.Created[0].Id, new ParagraphFormatOp { Alignment = ParagraphAlignment.Center });
session.InsertPageNumberField(footer.Created[0].Id, PageNumberField.CurrentPage);

Ripple

DocxSessionOps + DocxSessionJson (single-owner facade first) → WASM DocxSessionBridge + npm DocxSession (setHeaderText/setFooterText/insertPageNumberField, new HeaderFooterKind/PageNumberField string-union types) → stdio Dispatcher + docx-scalpel (set_header_text/set_footer_text/insert_page_number_field, new enums) → docs (docx_mutation_api.md section + anchor-lifecycle rows + decision tree, CHANGELOG, CLAUDE.md).

Testing

  • .NET: new DocxSessionTests DS250–DS262 (create / reuse-replaces-content / footer+page-number compose / First title-page / Even settings flag / undo↔redo of part creation / no-sectPr synthesis / wrong-kind + non-paragraph errors / NUMPAGES / saved-bytes reopen / empty payload). Full suite 2682 passed, 0 failed, 3 skipped.
  • Release build of the library (warnings-as-errors) clean; WASM bridge project builds; pyhost builds.
  • npm: tsc --noEmit clean; full npm run build (WASM + bundles) succeeds; new Playwright spec docx-session-headerfooter.spec.ts exercises SetFooterText + InsertPageNumberField through the WASM bridge (compose → project → save → reopen) plus the non-body error envelope — both green.
  • Python: modules compile and HeaderFooterKind/PageNumberField import; the docx-scalpel methods mirror the existing insert_paragraph shape (stdio integration not run in this environment).

Out of scope (deliberate follow-ups, noted in the issue)

  • The editor's visual header/footer editing region (the parts live outside the body) — this ships the engine + wire the editor will drive.
  • Footnote/endnote authoring (tracked separately).
  • Page-number field switches (w:pgNumType, \* roman).

🤖 Generated with Claude Code

https://claude.ai/code/session_01RjdqsSuxy8GJjK9TLVsfKd


Generated by Claude Code

DocxSession could only *inspect* a section's header/footer parts
(SectionInfo.HeaderPartUris/FooterPartUris) — there was no surface to
create or edit them, blocking faithful reproduction of real filings (the
S-1 running footer + centered page number could not be authored). Add
three methods, rippled through every layer per CLAUDE.md.

- SetHeaderText/SetFooterText(bodyAnchor, HeaderFooterKind, markdown):
  set the running header/footer for the section that owns any body block.
  The governing w:sectPr is resolved exactly as GetSectionInfo resolves
  it (synthesizing a trailing sectPr if the body has none). Creates the
  HeaderPart/FooterPart + relationship + w:headerReference/w:footerReference
  of the requested kind if absent, else replaces its content. Paragraphs
  get the built-in Header/Footer style (centre/right tab stops); First
  sets w:titlePg, Even sets w:evenAndOddHeaders. Created returns the new
  hdr{N}/ftr{N} paragraph anchors.
- InsertPageNumberField(anchor, PageNumberField=CurrentPage): append a
  native complex PAGE/NUMPAGES field to a paragraph.
- Undo/redo now reconciles header/footer part create/delete in
  RestoreSnapshot (snapshot records each part's relationship id; re-create
  uses AddNewPart<T>(relId) so the restored sectPr reference resolves),
  resolving the long-standing TODO there.

Ripple: DocxSessionOps + DocxSessionJson, WASM DocxSessionBridge + npm
DocxSession (setHeaderText/setFooterText/insertPageNumberField, new
HeaderFooterKind/PageNumberField types), stdio Dispatcher + docx-scalpel
(set_header_text/set_footer_text/insert_page_number_field, new enums),
plus docs (docx_mutation_api.md, CHANGELOG, CLAUDE.md). The editor's
visual header/footer region is a deliberate follow-up.

Tests: DocxSessionTests DS250-DS262 and a WASM Playwright spec
(docx-session-headerfooter). Full .NET suite (2682) green; library
Release build clean.
JSv4 added 2 commits July 12, 2026 17:40
…t reordering settings

Both the DocxDiff header/footer renderer and the new DocxSession
SetHeaderText(Even) routed the whole settings root through
WmlOrderElementsPerStandard, whose Order_settings table lacked
hdrShapeDefaults/shapeDefaults — children real Word documents carry —
so those children were sorted out of their CT_Settings schema slots and
the part failed schema validation ("unexpected child element
'hdrShapeDefaults'", caught smoke-testing this PR against
TestFiles/DB006-Source2.docx).

- Add the two missing entries to Order_settings (updateFields <
  hdrShapeDefaults < footnotePr; smartTagType < shapeDefaults <
  doNotEmbedSmartTags, per the transitional CT_Settings sequence).
- Replace both private EnsureEvenAndOddHeaders copies with one shared
  WordprocessingMLUtil.EnsureEvenAndOddHeaders that inserts the flag at
  its own slot (before the first child the table knows to come later)
  and never moves any other settings child — unknown children
  (extension content) stay exactly where Word wrote them.
- Document the evenAndOddHeaders sharp edge: the flag is
  document-global and governs footers too, so an Even header alongside
  only a Default footer blanks even-page footers (spec-correct Word
  behavior, verified in LibreOffice).

Tests: DS263 (synthetic settings carrying hdrShapeDefaults /
shapeDefaults) + DS264 (the real Word-authored fixture that caught the
bug). Full .NET suite 2684 passed / 0 failed / 3 skipped.
@JSv4 JSv4 merged commit ffe5e0c into main Jul 12, 2026
12 checks passed
@JSv4 JSv4 deleted the claude/issue-236-evaluation-znvbn3 branch July 12, 2026 22:53
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.

feat(editor): header/footer + page-number authoring (DocxSession → editor)

2 participants