Move the full compose pipeline (anchor-pair splicing, append-before-sectPr,
hyperlink-rels patching, zip split/repack, final placeholder pass) into
pkg/docforge/docx/compose.go, decoupled from paliad's DB row types. The
engine now owns the entire .docx assembly.
New neutral types in docx:
- Carrier{Bytes, Stylemap} — the opaque base .docx, preserved
byte-for-byte outside the spliced regions (the lossless docforge
carrier for .docx).
- Section{Key, OrderIndex, Included, ContentMDDE, ContentMDEN} — the
format-neutral content input.
- Composer / NewComposer / ComposeOptions on those neutral types.
internal/services keeps SubmissionComposer + ComposeOptions as a thin
mapping wrapper (SubmissionSection -> docx.Section, Base.SectionSpec.Stylemap
+ BaseBytes -> docx.Carrier). handlers + the comprehensive compose_test are
unchanged; the test drives the wrapper end-to-end and its byte-exact OOXML
assertions pass = behaviour preserved.
Retired the slice-1 docx.XMLAttrEscape wrapper + its services forwarder:
compose now calls the local xmlAttrEscape inside the docx package.
Sequencing note: the paragraph-level neutral model (Document/Block/Slot the
PRD §3.2 sketches) is deferred to slice 6, where the authoring importer +
format exporters consume it. Building it now, ahead of any consumer, would
be speculative and risk the byte-identical guarantee for no gain (PRD §4 B3
principle). Carrier is the part of the model that earns its keep this cycle.
Verification: go build ./... clean, go vet clean, full module test green.
m/paliad#157
Relocate the in-house OOXML machinery out of internal/services into the
first docforge adapter, with zero behaviour change:
submission_merge.go -> pkg/docforge/docx/merge.go (placeholder
substitution renderer + preview-HTML emitter)
submission_md.go -> pkg/docforge/docx/markdown.go (Markdown->OOXML
walker incl. the b78a984 underscore-fix)
submission_render.go -> pkg/docforge/docx/dotm.go (.dotm->.docx)
+ their _test.go files (git-tracked renames, 84-99% identical)
internal/services keeps thin type-alias + forwarder shims
(docforge_shims.go) so every caller in services/handlers/main compiles
and behaves identically: PlaceholderMap, MissingPlaceholderFn,
SubmissionRenderer, HyperlinkAllocator (aliases); NewSubmissionRenderer,
DefaultMissingMarker, RenderMarkdownToOOXML[WithStyles], ConvertDotmToDocx,
SanitiseSubmissionFileName (forwarders). docx.XMLAttrEscape is exported so
submission_compose.go's hyperlink-rels inserts reuse the walker's escaping.
Three mis-filed pretty-printer tests (legalSourcePretty, ourSideDE/EN,
patentNumberUPC) that exercise the vars layer move back to
internal/services/submission_vars_pretty_test.go.
Placeholder grammar + PlaceholderMap stay co-located with the renderer in
docx for now; slice 3 hoists the format-neutral grammar to the docforge
root with the VariableResolver interface.
Verification: go build ./... clean, go vet clean, full module test green
(the byte-exact OOXML golden tests in merge/compose/render pass unchanged
= behaviour preserved). gofmt drift on the moved files is pre-existing
(72/169 services files already drift; no gofmt gate).
m/paliad#157