Skip to content

fix(convert): coalesce a bold/italic span Confluence's editor split around a link - #112

Merged
willkg merged 2 commits into
mainfrom
fix/coalesce-split-marks-around-links
Aug 30, 2026
Merged

fix(convert): coalesce a bold/italic span Confluence's editor split around a link#112
willkg merged 2 commits into
mainfrom
fix/coalesce-split-marks-around-links

Conversation

@willkg

@willkg willkg commented Aug 30, 2026

Copy link
Copy Markdown
Member

Summary

  • Confluence's ADF stores formatting as marks per text run rather than nested elements. MdToConfluence always publishes a bold/italic span containing a link nested (<strong>text <a>link</a></strong>), which round-trips fine on its own — but once that page is opened and saved in Confluence's editor (even with no actual edit), it re-serializes through ADF and comes back split: <strong>text </strong><a><strong>link</strong></a>.
  • storage_to_md.go rendered those two nodes independently, producing **text **[**link**](url). The closing ** is preceded by a space, so CommonMark's flanking rule refuses to treat it as emphasis at all — the exported markdown came back with literal ** characters, not just missing bold styling.
  • Verified live 2026-08-30 by PUTting a test page's own unmodified atlas_doc_format body back at it via the API — exactly what the editor does on every save — and confirming the storage came back split.

Fixes #111.

Fix

  • Added coalesceSplitMarks in storage_to_md.go: detects adjacent inline nodes sharing a formatting mark (including a link whose entire content carries that mark) and hoists the mark to wrap the whole run before rendering — the only shape markdown can actually express.
  • A link only partly marked is left untouched, since there's nothing correct to hoist.
  • Updated CLAUDE.md's architecture notes to describe the repair.

Test plan

  • Added TestStorageToMarkdownCoalescesSplitMarks (bold/italic, both orderings, plain adjacent-run merging, partially-marked link left alone)
  • make check passes (vet, fmt-check, test, build, lint)
  • Reproduced the bug live against a throwaway test page (deleted afterward) via a direct atlas_doc_format PUT mimicking an editor save

willkg added 2 commits August 30, 2026 07:47
…round a link

Confluence's ADF stores marks per text run rather than as nested
elements, so a bold span containing a link -- which MdToConfluence
always publishes nested -- comes back from a page that's been opened
and saved in Confluence's editor as two adjacent runs sharing the
mark instead: <strong>text </strong><a><strong>link</strong></a>.
Rendered independently that produces "**text **[**link**](url)",
whose closing ** is preceded by a space and so doesn't open emphasis
at all under CommonMark's flanking rule -- the exported markdown came
back with literal asterisks, not just unstyled text.

Verified live 2026-08-30 by PUTting a page's own unmodified
atlas_doc_format body back at it (what the editor does on every
save, without changing content) and confirming the storage came back
split.

Fixes #111.
Code review on the original fix found the same editor-induced split
also happens for an internal Confluence page/space link (<ac:link>),
not just a markdown link (<a>): verified live the same way, by
PUTting a bold internal link's own unmodified atlas_doc_format body
back at the page. A naive name check wouldn't have caught it, since
ac:link's visible text sits one level deeper, inside ac:link-body,
rather than directly on the link element.

Also fixes a latent data-loss path the review flagged: merging two
adjacent same-tag mark elements kept only one side's attrs map,
silently dropping the other's.
@willkg

willkg commented Aug 30, 2026

Copy link
Copy Markdown
Member Author

Code review found two real gaps in the original fix, both addressed in 1831df2:

  1. The repair only matched plain <a> links. The identical editor-induced split also happens for an internal Confluence page/space link (<ac:link>) — verified live the same way, by PUTting a bold internal link's own unmodified atlas_doc_format back at the page. A naive name-check addition wouldn't have worked, since <ac:link>'s visible text sits one level deeper (ac:link-body) than <a>'s. Added isLinkNode/linkTextBody/withLinkTextBody to cover both shapes, plus a dedicated test (TestACLinkCoalescesSplitBoldMark) and a golden fixture (testdata/storage2md/split-marks) for the <a> cases.
  2. Merging two adjacent same-tag mark elements kept only one side's attrs map, silently dropping the other's — currently unobservable since no mark-tag renderer reads attrs, but a latent trap. Fixed with mergeAttrs.

The other two findings (case duplication between the mark-before-link/mark-after-link branches, and the unconditional same-tag merge being broader than strictly needed) were judged acceptable as-is: the duplication is a few lines and adding an abstraction over it would cost more clarity than it buys, and the unconditional merge is necessary for a third adjacent run to fold into an already-repaired node and is documented as such.

@willkg
willkg merged commit 4449c1a into main Aug 30, 2026
1 check passed
@willkg
willkg deleted the fix/coalesce-split-marks-around-links branch August 30, 2026 12:03
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.

read/export: a bold/italic span that Confluence's editor splits around a link comes back broken

1 participant