Skip to content

Comments

fix: treat code mark as exclusive in ADF inline formatting#27

Merged
Hinne1 merged 1 commit intomainfrom
claude/fix-code-mark-exclusive
Feb 17, 2026
Merged

fix: treat code mark as exclusive in ADF inline formatting#27
Hinne1 merged 1 commit intomainfrom
claude/fix-code-mark-exclusive

Conversation

@Hinne1
Copy link
Contributor

@Hinne1 Hinne1 commented Feb 17, 2026

Summary

  • Jira rejects ADF documents where the code mark is combined with other marks (strong, em, strike) on the same text node, returning 400 INVALID_INPUT
  • This happened when using inline code inside bold, italic, or strikethrough (e.g. **bold code text**)
  • The code mark is exclusive in the ADF spec and must not overlap with other marks
  • Added addMarkToContent() helper that skips adding outer marks to code nodes

Test plan

  • Added markdown_code_mark_test.go with tests for code inside bold, italic, strikethrough, and the combo that originally triggered the bug
  • All existing tests pass unchanged
  • Manually verified: the exact atl issue comment add that was returning 400 now works

Jira rejects ADF documents where the code mark is combined with other
marks (strong, em, strike) on the same text node, returning 400
INVALID_INPUT. This happened when using inline code inside bold, italic,
or strikethrough (e.g. **bold `code` text**).

The code mark is exclusive in the ADF spec and must not overlap with
other marks. When nesting inline code inside formatted text, the outer
mark is now skipped for code nodes.
@gemini-code-assist
Copy link

Summary of Changes

Hello @Hinne1, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical compatibility issue with Jira's ADF document processing. Previously, combining inline code with other formatting like bold or italic would lead to document rejection. The changes ensure that the code mark adheres to its exclusive nature within the ADF specification, preventing invalid mark combinations and allowing such markdown to be correctly processed by Jira.

Highlights

  • Jira ADF Compatibility Fix: Resolved an issue where Jira rejected ADF documents containing code marks combined with other inline formatting (like bold, italic, or strikethrough), which resulted in a 400 INVALID_INPUT error.
  • ADF Code Mark Exclusivity: Implemented logic to enforce the ADF specification's rule that the code mark is exclusive and cannot overlap with other marks.
  • New Helper Functions: Introduced hasCodeMark() to check for the presence of a code mark and addMarkToContent() to conditionally apply marks, ensuring outer marks are skipped for nodes already containing a code mark.
  • Comprehensive Testing: Added a new test file, markdown_code_mark_test.go, with specific tests for code inside bold, italic, and strikethrough, including the exact combination that previously caused the bug.
Changelog
  • internal/api/markdown.go
    • Added hasCodeMark function to detect inline code marks.
    • Added addMarkToContent helper function to apply marks while respecting code mark exclusivity.
    • Updated parseInline function to utilize addMarkToContent for strong, strike, and em marks.
  • internal/api/markdown_code_mark_test.go
    • Added new test file to verify correct handling of inline code marks within other formatting.
    • Included tests for code inside bold, italic, and strikethrough.
    • Added a specific test case for the combination that previously caused Jira to return INVALID_INPUT.
Activity
  • All existing tests passed without changes.
  • Manually verified that the atl issue comment add command, which previously failed with a 400 error, now functions correctly.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions
Copy link

Merging this branch will not change overall coverage

Impacted Packages Coverage Δ 🤖
github.com/enthus-appdev/atl-cli/internal/api 36.59% (ø)

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/enthus-appdev/atl-cli/internal/api/markdown.go 89.01% (ø) 364 324 40

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

Changed unit test files

  • github.com/enthus-appdev/atl-cli/internal/api/markdown_code_mark_test.go

@Hinne1 Hinne1 merged commit 1a16f2a into main Feb 17, 2026
11 checks passed
@Hinne1 Hinne1 deleted the claude/fix-code-mark-exclusive branch February 17, 2026 11:41
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively resolves the issue with Jira's exclusive code mark handling in ADF. The introduction of the hasCodeMark and addMarkToContent helper functions is a clean solution that also refactors parseInline to be more modular and less repetitive. The new tests in markdown_code_mark_test.go are a great addition to validate the fix. I have a few suggestions to make these new tests even more robust by adding more specific assertions.

Comment on lines +50 to +61
func TestMarkdownToADF_CodeInsideItalic(t *testing.T) {
adf := MarkdownToADF("*italic `code` here*")

para := adf.Content[0]
for _, c := range para.Content {
if hasCodeMark(c) {
if len(c.Marks) != 1 {
t.Errorf("code node should have only code mark, got %v", c.Marks)
}
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test correctly verifies that code marks are exclusive. To make it more robust, I suggest expanding it to also assert that the non-code parts of the string correctly receive the em mark. This would provide a more complete validation, similar to TestMarkdownToADF_CodeInsideBold.

func TestMarkdownToADF_CodeInsideItalic(t *testing.T) {
	adf := MarkdownToADF("*italic `code` here*")

	if len(adf.Content) != 1 {
		t.Fatalf("expected 1 block, got %d", len(adf.Content))
	}

	para := adf.Content[0]
	if para.Type != "paragraph" {
		t.Fatalf("expected paragraph, got %s", para.Type)
	}

	if len(para.Content) != 3 {
		b, _ := json.MarshalIndent(adf, "", "  ")
		t.Fatalf("expected 3 inline nodes, got %d:\n%s", len(para.Content), b)
	}

	// "italic " should have em mark
	if para.Content[0].Text != "italic " {
		t.Errorf("expected 'italic ', got %q", para.Content[0].Text)
	}
	if len(para.Content[0].Marks) != 1 || para.Content[0].Marks[0].Type != "em" {
		t.Errorf("expected [em] marks, got %v", para.Content[0].Marks)
	}

	// "code" should have ONLY code mark
	if para.Content[1].Text != "code" {
		t.Errorf("expected 'code', got %q", para.Content[1].Text)
	}
	if len(para.Content[1].Marks) != 1 || para.Content[1].Marks[0].Type != "code" {
		t.Errorf("expected [code] marks only, got %v", para.Content[1].Marks)
	}

	// " here" should have em mark
	if para.Content[2].Text != " here" {
		t.Errorf("expected ' here', got %q", para.Content[2].Text)
	}
	if len(para.Content[2].Marks) != 1 || para.Content[2].Marks[0].Type != "em" {
		t.Errorf("expected [em] marks, got %v", para.Content[2].Marks)
	}
}

Comment on lines +63 to +74
func TestMarkdownToADF_CodeInsideStrikethrough(t *testing.T) {
adf := MarkdownToADF("~~deleted `code` here~~")

para := adf.Content[0]
for _, c := range para.Content {
if hasCodeMark(c) {
if len(c.Marks) != 1 {
t.Errorf("code node should have only code mark, got %v", c.Marks)
}
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the italic test, this test for strikethrough can be strengthened. Explicitly checking that the text segments around the code block have the strike mark would make the test more comprehensive and help prevent future regressions.

func TestMarkdownToADF_CodeInsideStrikethrough(t *testing.T) {
	adf := MarkdownToADF("~~deleted `code` here~~")

	if len(adf.Content) != 1 {
		t.Fatalf("expected 1 block, got %d", len(adf.Content))
	}

	para := adf.Content[0]
	if para.Type != "paragraph" {
		t.Fatalf("expected paragraph, got %s", para.Type)
	}

	if len(para.Content) != 3 {
		b, _ := json.MarshalIndent(adf, "", "  ")
		t.Fatalf("expected 3 inline nodes, got %d:\n%s", len(para.Content), b)
	}

	// "deleted " should have strike mark
	if para.Content[0].Text != "deleted " {
		t.Errorf("expected 'deleted ', got %q", para.Content[0].Text)
	}
	if len(para.Content[0].Marks) != 1 || para.Content[0].Marks[0].Type != "strike" {
		t.Errorf("expected [strike] marks, got %v", para.Content[0].Marks)
	}

	// "code" should have ONLY code mark
	if para.Content[1].Text != "code" {
		t.Errorf("expected 'code', got %q", para.Content[1].Text)
	}
	if len(para.Content[1].Marks) != 1 || para.Content[1].Marks[0].Type != "code" {
		t.Errorf("expected [code] marks only, got %v", para.Content[1].Marks)
	}

	// " here" should have strike mark
	if para.Content[2].Text != " here" {
		t.Errorf("expected ' here', got %q", para.Content[2].Text)
	}
	if len(para.Content[2].Marks) != 1 || para.Content[2].Marks[0].Type != "strike" {
		t.Errorf("expected [strike] marks, got %v", para.Content[2].Marks)
	}
}

Comment on lines +76 to +98
func TestMarkdownToADF_BoldWithCodeAndCodeBlock(t *testing.T) {
// This was the exact combination that caused INVALID_INPUT from Jira
md := "**Migration: Falsche `s_action` korrigieren**\n\n```sql\nSELECT 1\n```"
adf := MarkdownToADF(md)

if len(adf.Content) != 2 {
b, _ := json.MarshalIndent(adf, "", " ")
t.Fatalf("expected 2 blocks (paragraph + codeBlock), got %d:\n%s", len(adf.Content), b)
}

// Verify no code node has additional marks
para := adf.Content[0]
for _, c := range para.Content {
if hasCodeMark(c) && len(c.Marks) > 1 {
t.Errorf("code node should have only code mark, got %v", c.Marks)
}
}

// Verify code block is present
if adf.Content[1].Type != "codeBlock" {
t.Errorf("expected codeBlock, got %s", adf.Content[1].Type)
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test covers the specific case that triggered the bug, which is excellent. To make it even more solid, I suggest adding assertions to verify that the non-code parts of the bolded string also have the strong mark. This provides a more complete verification of the paragraph's content and protects against regressions.

func TestMarkdownToADF_BoldWithCodeAndCodeBlock(t *testing.T) {
	// This was the exact combination that caused INVALID_INPUT from Jira
	md := "**Migration: Falsche `s_action` korrigieren**\n\n```sql\nSELECT 1\n```"
	adf := MarkdownToADF(md)

	if len(adf.Content) != 2 {
		b, _ := json.MarshalIndent(adf, "", "  ")
		t.Fatalf("expected 2 blocks (paragraph + codeBlock), got %d:\n%s", len(adf.Content), b)
	}

	// Verify paragraph content
	para := adf.Content[0]
	if para.Type != "paragraph" {
		t.Fatalf("expected paragraph, got %s", para.Type)
	}
	if len(para.Content) != 3 {
		b, _ := json.MarshalIndent(para, "", "  ")
		t.Fatalf("expected 3 inline nodes in paragraph, got %d:\n%s", len(para.Content), b)
	}

	// "Migration: Falsche " should have strong mark
	if para.Content[0].Text != "Migration: Falsche " {
		t.Errorf("expected 'Migration: Falsche ', got %q", para.Content[0].Text)
	}
	if len(para.Content[0].Marks) != 1 || para.Content[0].Marks[0].Type != "strong" {
		t.Errorf("expected [strong] marks, got %v", para.Content[0].Marks)
	}

	// "s_action" should have ONLY code mark
	if para.Content[1].Text != "s_action" {
		t.Errorf("expected 's_action', got %q", para.Content[1].Text)
	}
	if len(para.Content[1].Marks) != 1 || para.Content[1].Marks[0].Type != "code" {
		t.Errorf("expected [code] marks only, got %v", para.Content[1].Marks)
	}

	// " korrigieren" should have strong mark
	if para.Content[2].Text != " korrigieren" {
		t.Errorf("expected ' korrigieren', got %q", para.Content[2].Text)
	}
	if len(para.Content[2].Marks) != 1 || para.Content[2].Marks[0].Type != "strong" {
		t.Errorf("expected [strong] marks, got %v", para.Content[2].Marks)
	}

	// Verify code block is present
	if adf.Content[1].Type != "codeBlock" {
		t.Errorf("expected codeBlock, got %s", adf.Content[1].Type)
	}
}

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.

1 participant