Repository navigation
fix(docx-core): resolve run formatting from document defaults - #759
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
extractEffectiveRunFormatting returned '', 0 and false both for properties declared nowhere and for properties declared only in layers it does not read (w:docDefaults, table styles), so callers could not tell a known default from missing resolver coverage. RunFormatting fields are now nullable, and null means only "unresolved": a non-default declaration exists in an unread layer (docDefaults, or a table style for a run inside a table), or the property has no OOXML default and nothing declares it (fontName, fontSizePt). A property declared nowhere resolves to its OOXML default (false, highlightVal false, colorHex 'auto'). A toggle set by a direct layer is absolute and stays resolved; one reached only through style-level parity over an unread base is unresolved. StylesModel gains optional docDefaultsRPr and tableStyleRPrs probes (same docDefaultsRPr field #759 will resolve from). Consumers: - formatting tags: unresolved values form their own modal bucket (so tag output for resolved values is unchanged) and are never emitted; an unresolved size no longer produces <font size="0">. - heading detectors: only a resolved true counts as bold/underline. - formatting-convention check: tuples carry null; a divergence is only reported on a member resolved on both sides. - formatting-loss script: unresolved projects as 'unresolved'. Fixes #752
extractEffectiveRunFormatting returned '', 0 and false both for properties declared nowhere and for properties declared only in layers it does not read (w:docDefaults, table styles), so callers could not tell a known default from missing resolver coverage. RunFormatting fields are now nullable, and null means only "unresolved": a non-default declaration exists in an unread layer (docDefaults, or a table style for a run inside a table), or the property has no OOXML default and nothing declares it (fontName, fontSizePt). A property declared nowhere resolves to its OOXML default (false, highlightVal false, colorHex 'auto'). A toggle set by a direct layer is absolute and stays resolved; one reached only through style-level parity over an unread base is unresolved. StylesModel gains optional docDefaultsRPr and tableStyleRPrs probes (same docDefaultsRPr field #759 will resolve from). Consumers: - formatting tags: unresolved values form their own modal bucket (so tag output for resolved values is unchanged) and are never emitted; an unresolved size no longer produces <font size="0">. - heading detectors: only a resolved true counts as bold/underline. - formatting-convention check: tuples carry null; a divergence is only reported on a member resolved on both sides. - formatting-loss script: unresolved projects as 'unresolved'. Fixes #752
extractEffectiveRunFormatting returned '', 0 and false both for properties declared nowhere and for properties declared only in layers it does not read (w:docDefaults, table styles), so callers could not tell a known default from missing resolver coverage. RunFormatting fields are now nullable, and null means only "unresolved": a non-default declaration exists in an unread layer (docDefaults, or a table style for a run inside a table), or the property has no OOXML default and nothing declares it (fontName, fontSizePt). A property declared nowhere resolves to its OOXML default (false, highlightVal false, colorHex 'auto'). A toggle set by a direct layer is absolute and stays resolved; one reached only through style-level parity over an unread base is unresolved. StylesModel gains optional docDefaultsRPr and tableStyleRPrs probes (same docDefaultsRPr field #759 will resolve from). Consumers: - formatting tags: unresolved values form their own modal bucket (so tag output for resolved values is unchanged) and are never emitted; an unresolved size no longer produces <font size="0">. - heading detectors: only a resolved true counts as bold/underline. - formatting-convention check: tuples carry null; a divergence is only reported on a member resolved on both sides. - formatting-loss script: unresolved projects as 'unresolved'. Fixes #752
* fix(docx-core): expose unresolved run formatting extractEffectiveRunFormatting returned '', 0 and false both for properties declared nowhere and for properties declared only in layers it does not read (w:docDefaults, table styles), so callers could not tell a known default from missing resolver coverage. RunFormatting fields are now nullable, and null means only "unresolved": a non-default declaration exists in an unread layer (docDefaults, or a table style for a run inside a table), or the property has no OOXML default and nothing declares it (fontName, fontSizePt). A property declared nowhere resolves to its OOXML default (false, highlightVal false, colorHex 'auto'). A toggle set by a direct layer is absolute and stays resolved; one reached only through style-level parity over an unread base is unresolved. StylesModel gains optional docDefaultsRPr and tableStyleRPrs probes (same docDefaultsRPr field #759 will resolve from). Consumers: - formatting tags: unresolved values form their own modal bucket (so tag output for resolved values is unchanged) and are never emitted; an unresolved size no longer produces <font size="0">. - heading detectors: only a resolved true counts as bold/underline. - formatting-convention check: tuples carry null; a divergence is only reported on a member resolved on both sides. - formatting-loss script: unresolved projects as 'unresolved'. Fixes #752 * fix(docx-core): address #758 review on unresolved formatting - An unresolvable theme colour/font reference is unresolved (null) and stops inheritance, instead of reading as 'auto' or showing a lower layer through. - Modal b/i/u baseline and the convention vote drop a member that any run/instance leaves unresolved, so uncertainty in one member cannot split the tuple and change tags or erase a convention on another. - CHANGELOG enumerates the intentional output changes (explicit auto colour over Hyperlink style; unknown-norm tagging). * fix(scripts): project unresolved formatting as null in the loss check A string sentinel could collide with a real font literally named 'unresolved', hiding a dropped font declaration from D1. null never occurs as a resolved value. (#758 review)
extractEffectiveRunFormatting now reads w:docDefaults/w:rPrDefault/w:rPr as the lowest-precedence run-property layer (#753). Rebuilt on top of the nullable RunFormatting contract from #758: - StylesModel.docDefaultsRPr becomes a resolution layer: the last source for ordinary properties, and a 'default' toggle step that seeds the starting value before style parity and absolute direct formatting (MS-OI29500 note on 17.7.3; MS-OE376 2.7.7 documents no deviation that makes docDefaults another style level). - docDefaults leaves the unread-layer list. Table styles remain unread: for a run in a table, a property nothing above docDefaults declares is null when a table style declares a value different from the base. - fontName/fontSizePt resolve from docDefaults when declared; a theme font there resolves through the theme part and is null without one. - Comment and footnote tagged_text use the document defaults as their font baseline, so the inherited default font is not emitted as face= on every run (which also broke docx-markdoc annotation import). Fixes #753
c10e0c7 to
d78ab6b
Compare
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
LLM gate (advisory)All evaluated rules passed - 10 pass, 0 warn, 0 error, 6 skipped, 16 total FindingsNone. All 16 rules (10 evaluated, 6 skipped)
|
…e-style probe - Comment/footnote tagged_text report colour, size and font only where a layer above w:docDefaults declares them (extractAnnotationRunFormatting), instead of suppressing values equal to the document defaults. The equality baseline dropped a direct colour that restated the default over a named character style, which docx-markdoc then re-imported as the style's colour. - The table-style probe compares hex colours and font names case-insensitively, so a restatement in other casing stays resolved.
Codex peer review, round 1: CHANGES_REQUIRED, now addressed in 9196b88Reviewer: Codex (
Other review notes, no change needed: the resolution probes match the intended model (seed-on / style-off → on; table-on → null; table-off → on under style-off-preserves parity). The new exports are reachable from the built package. Codex correctly notes that it did not re-run the Word oracle. The toggle claims rest on the #753 oracle rows and the MS-OI29500 / MS-OE376 citations, as the PR body says. Gates rerun on 9196b88: build, lint:workspaces, check:cycles, spec-coverage, conformance-citations/doc/explorer, allure-labels/quality/filenames, tool-docs, test:docx-formatting-loss, and the docx-core, docx-mcp, docx-markdoc and docx-compare suites all pass. A confirmation review round follows. |
Codex peer review, confirmation round: APPROVED (9196b88)Codex re-verified both round-1 findings by running them:
No open findings. Probe files under |
Post-merge smoke: PASS (fb171f0 vs pre-merge 6e60dcf)I drove the MCP server (
Harness: |
Summary
extractEffectiveRunFormattingnow readsw:docDefaults/w:rPrDefault/w:rPras the lowest-precedence run-property layer (#753).This PR was rebuilt from scratch on top of #758 (6e60dcf). The July draft was based on old main and reverted
RunFormattingto non-null types. The rebuild keeps #758's nullable contract and turns its detection-onlyStylesModel.docDefaultsRPrprobe into a real resolution layer.What changes
false/'auto'), ornullforfontName/fontSizePt, which have no OOXML default.'default'step seeds the walk with the docDefaults value. Style-level on inverts the value, style-level off preserves it, and direct formatting is absolute. The seed itself is not absolute (see below).w:rFonts w:asciiTheme="minorHAnsi", which Word writes in nearly every document) resolve through the theme part. Without a theme part they staynull(fix(docx-core): expose unresolved run formatting #758 contract (b)).nullwhen a table style declares a value different from the base (the docDefaults value or the OOXML default). A toggle isnullwhen a table style turns it on and no direct layer set it.tagged_text(full mode) reports colour, size and font only where a layer above docDefaults declares them (extractAnnotationRunFormatting). Toggles, underline and highlight resolve through docDefaults as everywhere else. Without this, every annotation run in a document whose docDefaults declares a font carriedface="...", andimportDocxToMarkdocrejected all ILPA footnotes withANNOTATION_IMPORT_UNSUPPORTED. A first version suppressed values equal to the docDefaults; peer review showed that this drops a direct colour restating the default over a named character style, which markdoc then re-imports as the style's colour. That version was replaced.abcdefrestatesABCDEF).formatting_tags.tsand the convention vote informatting_convention.ts) now work for docDefaults-only documents with no further change. Tests cover the convention vote counting docDefaults italic and a run-in header that is bold only through docDefaults.Toggle semantics
Document defaults are the absolute base state. ECMA-376 5th ed. Part 1 §§17.7.3 and 17.7.5.1 define the inheritance and default layers. Microsoft [MS-OI29500] §17.7.3 describes document defaults as the fallback and base for toggle evaluation. [MS-OE376] §2.7.7 documents a paragraph-style reset deviation, but no deviation that makes
docDefaultsanother toggling style level. This matches the Word oracle rows in #753 (<toggle>.single.docDefaults.on,<toggle>.docDefaultsOnly,<toggle>.crossLevel.threeOn= on), which are pinned instyles-doc-defaults.test.ts.Corpus measurement (54 repo
.docx, one is an intentionally corrupt fixture; 41,919 text runs inword/document.xml, theme supplied)fontName: nullfontSizePt: nullfontSizePt: 0/fontName: ""The remaining 237
nullsizes come from 7 documents whose docDefaults omitw:szand where no consulted layer declares one. Most of them are NVCA COI source (162) and ILPA (33 + 33).Tests
styles-doc-defaults.test.ts: every property from docDefaults only; precedence under direct formatting and paragraph styles; toggle seed and parity (one, two and three levels on; style off); theme font in docDefaults with and without a theme; table-style overrides vs restating the docDefaults value; the read_file baseline (nosize="0", only the outlier tagged).styles-unresolved.test.ts: docDefaults expectations flip fromnullto resolved values (allowed by docx-core: run-formatting resolver ignores w:docDefaults, so most runs resolve fontName to an empty string #753's acceptance criteria).footnotes_structured.test.ts: annotation tags omit the inherited default font and keep the style size, an explicit Arial run, and a direct restatement of the default font over an Arial style.annotation-roundtrip.test.ts: for both comments and footnotes, a direct colour/size restating the docDefaults over a named style survives import, edit and compile.Validation
npm run build,lint:workspaces,check:cycles,check:spec-coverage,check:conformance-citations,check:conformance-doc,check:conformance-explorer,check:allure-labels,check:allure-quality,check:allure-filenames,check:tool-docs,test:docx-formatting-loss, and the docx-core, docx-mcp and docx-markdoc suites all pass.Fixes #753