The package tree's nodes are self-describing: every run and paragraph carries its resolved direct properties, because codecs resolve source style cascades at read time. That is the right semantics but the wrong payload for documents consumed in token-metered contexts (the MCP/LLM path) -- a document with one body style applied to hundreds of paragraphs repeats the property tuple hundreds of times. This issue adds the two standard compression mechanisms, under one hard constraint.
Proposed shape:
{
"styles": {
"s1": { "paragraph": { "alignment": "justified", "spacingAfterPt": 6 },
"run": { "fontPt": 11 } }
},
"children": [
{ "node": { "kind": "paragraph", "style": "s1", "styleId": "BodyTextFirstIndent20", "runs": [ ... ] },
"style": "s1",
"children": [ ... ] }
]
}
- Referenced styles: a package-local
styles table; nodes carry style refs into it. Inherited styles: a group's style cascades to descendants. Resolution is one overlay chain -- package base, nearest ancestor group's style, the node's own style ref, the node's direct properties; innermost wins. The same overlay applies one level further down for run defaults vs a run's own properties.
Rules that keep this from re-importing what resolve-at-read was built to avoid:
- Compression only, never semantics: the flat codec-exchange form is always fully materialised (no table, no refs); flatten() expands references and inheritance into direct properties; effective properties are identical in both forms by law. The table is a dictionary, not a program: no
basedOn graph inside it, resolved canonical properties only, and never frames -- a position is a fact about a node, not a style.
style is not styleId: the new ref points at the package's own minted table (producer-side, deterministic -- a frequency pass factoring repeated property tuples into s1, s2, ... in stable order); styleId keeps its existing job as the source format's opaque style name for round-trip fidelity.
- Comparison and hashing operate on effective properties: two serialisations of the same document may differ in how aggressively they factored the table, so content hashing and structural diffing resolve first.
Rides the DocumentPackage promotion (ExaDev/document-schema.js umbrella) without a version bump of its own, but belongs in its design from day one -- minting discipline and effective-property hashing are cheapest to get right before any consumer has hashed a serialised form.
The package tree's nodes are self-describing: every run and paragraph carries its resolved direct properties, because codecs resolve source style cascades at read time. That is the right semantics but the wrong payload for documents consumed in token-metered contexts (the MCP/LLM path) -- a document with one body style applied to hundreds of paragraphs repeats the property tuple hundreds of times. This issue adds the two standard compression mechanisms, under one hard constraint.
Proposed shape:
{ "styles": { "s1": { "paragraph": { "alignment": "justified", "spacingAfterPt": 6 }, "run": { "fontPt": 11 } } }, "children": [ { "node": { "kind": "paragraph", "style": "s1", "styleId": "BodyTextFirstIndent20", "runs": [ ... ] }, "style": "s1", "children": [ ... ] } ] }stylestable; nodes carrystylerefs into it. Inherited styles: a group'sstylecascades to descendants. Resolution is one overlay chain -- package base, nearest ancestor group's style, the node's ownstyleref, the node's direct properties; innermost wins. The same overlay applies one level further down for run defaults vs a run's own properties.Rules that keep this from re-importing what resolve-at-read was built to avoid:
basedOngraph inside it, resolved canonical properties only, and neverframes-- a position is a fact about a node, not a style.styleis notstyleId: the new ref points at the package's own minted table (producer-side, deterministic -- a frequency pass factoring repeated property tuples into s1, s2, ... in stable order);styleIdkeeps its existing job as the source format's opaque style name for round-trip fidelity.Rides the DocumentPackage promotion (ExaDev/document-schema.js umbrella) without a version bump of its own, but belongs in its design from day one -- minting discipline and effective-property hashing are cheapest to get right before any consumer has hashed a serialised form.