# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Build & Test
```bash
just build # build binary to ./mdcx
just install # go install to $GOPATH/bin
just test # go test ./...
just test-v # verbose tests
go test ./converter/... -run TestRoundTrip # run specific tests
```
**Every bug fix gets a regression test.** Whenever you fix a bug, add a test that reproduces the exact case and fails on the original code (verify it fails before the fix, passes after). This codebase is a web of subtle round-trip traps — see "Round-trip gotchas" below; each was re-broken at least once before a test pinned it.
## Architecture
**mdcx** converts Markdown ↔ Confluence storage-format XML with round-trip fidelity. The binary lives at `cmd/mdcx/`.
### Package graph
```
cmd/mdcx/ CLI (cobra commands, package main)
├─ converter Core bidirectional conversion
├─ api Confluence REST client (pull/push)
├─ template Marker-based embed/extract
└─ format XML pretty-printer + ANSI colorizer
converter/
├─ md2xml.go goldmark parser → confluence.Renderer → XML
└─ xml2md.go golang.org/x/net/html walker → Markdown
confluence/
├─ renderer.go goldmark NodeRenderer (registered at priority 100)
└─ elements.go Macro builders: CodeMacro, InfoPanel, etc.
```
### Key design decisions
**Bidirectional round-trip preservation** — Confluence elements with no Markdown equivalent (inline comments, user mentions, attachment images, layout sections) survive round-trips via HTML `` attributes and `` markers. The md→xml renderer reconstructs proper `ac:*/ri:*` tags from these.
**CDATA preprocessing** — `xml2md.go` replaces `` with `` fake elements before parsing, because `golang.org/x/net/html` doesn't handle CDATA. The reverse direction uses `confluence.escapeCDATA()` to split `]]>` sequences.
**Renderer state** — `confluence.Renderer` tracks `taskIDCounter`, `inTaskBody`, `inlineCommentDepth`, `pageLinkDepth`, and pending attributes from HTML comments. This state is per-conversion and must remain consistent across the AST walk.
**Template markers** — `` / `` delimit the editable region in Confluence pages. The `push --template` flow: fetch page → replace between markers → update with version increment.
**Comment sidecar** — Confluence comment threads live outside the page body. `pull --with-comments` calls `/rest/api/content/{id}/child/comment` (paginated, with `children.comment` expanded two levels deep) and writes them to `
` lands between a code macro's open tag and its body — destroying round-trip for a no-language fenced code block after a heading. *Tests:* `TestNormalizeForVerify_BareTextAfterHeading`, `TestVerifyRoundTrip_CodeBlockAfterHeading`.
- **`fmt` line-wrapping must keep `…` whole** — breaking a line inside an inline code span injects the continuation indent into the span's text, which `xml2md` collapses into a spurious space *inside* the span (`` ` app.manifest.lock` ``). `format.splitAtoms` emits an entire `…` as one unbreakable atom. Significant whitespace between adjacent inline elements (` `) is carried on each atom's `spaceBefore` flag — never via `strings.Fields`, which drops trailing space. *Tests:* `TestPrettyXML_WrapKeepsCodeSpanWhole`, `TestPrettyXML_WrapPreservesSpaceBeforeCodeAfterWord`, `TestPrettyXML_SpaceBetweenInlineElements`.
### Verifying real documents
`mdcx verify ` formats the input, round-trips it through `xml2md → md2xml`, formats the output, and diffs them. The justfile in `~/data/1` runs this against five real Confluence pages — exercise it after any change to converter or renderer code, since the unit tests can miss multi-feature interactions (e.g. a task list inside a `` inside a table cell).