Skip to content

Enhance native Markdown support - #28

Merged
Chefski merged 7 commits into
devfrom
codex/enhance-native-markdown
Jul 25, 2026
Merged

Enhance native Markdown support#28
Chefski merged 7 commits into
devfrom
codex/enhance-native-markdown

Conversation

@Chefski

@Chefski Chefski commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • render headings with the correct bold typography across native previews and iOS/macOS editors
  • add Setext headings and indented code block parsing
  • resolve full, collapsed, and shortcut reference-style links and images
  • preserve literal Markdown through escaping and round-trip export/import handling
  • keep YAML frontmatter removal and fenced-code behavior intact
  • add focused and parameterized parity coverage

Why

The native editor supported the most common Markdown constructs, but heading typography and several CommonMark-compatible forms did not match the Docmost web editor. Literal Markdown characters could also be interpreted as formatting during a round trip.

Impact

Imported Markdown now retains more of its intended document structure, headings display consistently as bold headings, and literal syntax is exported safely. This closes several practical parity gaps without introducing a third-party dependency.

Full arbitrary nested CommonMark structures and unrestricted HTML remain outside the scope of this change.

Review fixes

  • escape ordered-list markers symmetrically
  • preserve nested lists ahead of indented-code parsing
  • handle escaped reference brackets, labels, and literals
  • preserve reference-shaped text inside code blocks
  • require standalone YAML frontmatter delimiters
  • parse multiline paragraph Markdown only once
  • move heading preview formatting into a testable helper

Validation

All current PR checks pass:

  • iOS unit tests
  • macOS unit tests
  • iPad build
  • SwiftLint
  • CRDT runtime drift
  • Git hygiene
  • Cubic AI review

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 12 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="docmostly/Features/Editor/NativeEditorMarkdownParser.swift">

<violation number="1" location="docmostly/Features/Editor/NativeEditorMarkdownParser.swift:16">
P2: Escaped reference syntax is expanded into visible link destination text. Skip a `[` (and `![`) when preceded by an unescaped backslash so `\[label]` remains literal Markdown during import.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


static func blocks(from markdown: String) -> [NativeEditorBlock] {
let markdown = removingLeadingYAMLFrontMatter(from: markdown)
let markdown = resolvingReferenceStyleLinks(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Escaped reference syntax is expanded into visible link destination text. Skip a [ (and ![) when preceded by an unescaped backslash so \[label] remains literal Markdown during import.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docmostly/Features/Editor/NativeEditorMarkdownParser.swift, line 16:

<comment>Escaped reference syntax is expanded into visible link destination text. Skip a `[` (and `![`) when preceded by an unescaped backslash so `\[label]` remains literal Markdown during import.</comment>

<file context>
@@ -13,7 +13,9 @@ enum NativeEditorMarkdownParser {
 
     static func blocks(from markdown: String) -> [NativeEditorBlock] {
-        let markdown = removingLeadingYAMLFrontMatter(from: markdown)
+        let markdown = resolvingReferenceStyleLinks(
+            in: removingLeadingYAMLFrontMatter(from: markdown)
+        )
</file context>

Comment thread docmostly/Features/Editor/NativeEditorMarkdownParser+FrontMatter.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0444922b9d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

continue
}

if let commonMarkBlock = commonMarkBlock(in: lines, startingAt: index) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Parse exported deeply nested lists as lists

When this new CommonMark block check runs before listItemBlock, any line with four leading spaces is consumed as an indented code block. The exporter still emits two spaces per list indent, so an indentLevel == 2 list item round-trips as - Grandchild and is imported as code instead of a nested list item, corrupting documents with lists deeper than one level. Defer indented-code parsing until after list detection, or exclude indented list markers from the code-block path.

Useful? React with 👍 / 👎.

resolvedFence = openingFence
return line
}
return replacingReferenceStyleLinks(in: line, definitions: definitions)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve reference-looking text inside indented code

Because every non-fenced line is passed through replacingReferenceStyleLinks whenever a definition exists, lines that will later be parsed as indented code are still rewritten. For example, a four-space code line containing [Guide][docs] is converted to [Guide](...) before indentedCodeBlock builds the code block, so imported code content is mutated; skip indented code ranges here or resolve references after block classification.

Useful? React with 👍 / 👎.

let startsOrderedList = startsWithOrderedListMarker(text)
let isExactMarker = exactMarkers.contains(text)
guard startsFixedSyntax || startsFence || startsOrderedList || isExactMarker else { return text }
return "\\\(text)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Escape ordered-list paragraphs at the marker

When paragraph text starts with an ordered-list marker, this branch emits a leading escape before the digit, e.g. \1. item. Markdown backslash escapes only apply to punctuation, and the importer’s unescaper also leaves a slash before 1, so exporting and re-importing a literal 1. item paragraph changes the saved text to include the backslash. Escape the period instead, or otherwise make this ordered-list escape symmetric.

Useful? React with 👍 / 👎.

private static func escapingMarkdownBlockPrefix(_ line: Substring) -> String {
let text = String(line)
let fixedPrefixes = ["# ", "## ", "### ", "#### ", "##### ", "###### ", "> ", "- ", "+ "]
let exactMarkers = ["---", "***", "___"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Escape all setext underline marker lines

This only treats exact ---, ***, and ___ as block-leading syntax, but the new setext parser accepts any non-empty line made entirely of - or = as a heading underline. A paragraph with a hard break before ---- or ==== therefore exports unchanged and re-imports as a heading, losing the second paragraph line. Escape all setext underline-only lines, not just the exact divider markers.

Useful? React with 👍 / 👎.

let isImage = line[index] == "!" && line.index(after: index) < line.endIndex &&
line[line.index(after: index)] == "["
let openLabel = isImage ? line.index(after: index) : index
if line[openLabel] == "[",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip escaped reference-style link openings

This matcher does not check whether the opening [ is escaped, so a literal reference such as \[Guide][docs] is still rewritten when a [docs]: ... definition exists. The later inline parser keeps the escaped bracket literal, but the content has already been mutated to \[Guide](...), changing user text that explicitly opted out of link parsing. Ignore escaped opening brackets before resolving reference-style links.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0444922b9d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

continue
}

if let commonMarkBlock = commonMarkBlock(in: lines, startingAt: index) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Parse exported deeply nested lists as lists

When this new CommonMark block check runs before listItemBlock, any line with four leading spaces is consumed as an indented code block. The exporter still emits two spaces per list indent, so an indentLevel == 2 list item round-trips as - Grandchild and is imported as code instead of a nested list item, corrupting documents with lists deeper than one level. Defer indented-code parsing until after list detection, or exclude indented list markers from the code-block path.

Useful? React with 👍 / 👎.

resolvedFence = openingFence
return line
}
return replacingReferenceStyleLinks(in: line, definitions: definitions)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve reference-looking text inside indented code

Because every non-fenced line is passed through replacingReferenceStyleLinks whenever a definition exists, lines that will later be parsed as indented code are still rewritten. For example, a four-space code line containing [Guide][docs] is converted to [Guide](...) before indentedCodeBlock builds the code block, so imported code content is mutated; skip indented code ranges here or resolve references after block classification.

Useful? React with 👍 / 👎.

let startsOrderedList = startsWithOrderedListMarker(text)
let isExactMarker = exactMarkers.contains(text)
guard startsFixedSyntax || startsFence || startsOrderedList || isExactMarker else { return text }
return "\\\(text)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Escape ordered-list paragraphs at the marker

When paragraph text starts with an ordered-list marker, this branch emits a leading escape before the digit, e.g. \1. item. Markdown backslash escapes only apply to punctuation, and the importer’s unescaper also leaves a slash before 1, so exporting and re-importing a literal 1. item paragraph changes the saved text to include the backslash. Escape the period instead, or otherwise make this ordered-list escape symmetric.

Useful? React with 👍 / 👎.

private static func escapingMarkdownBlockPrefix(_ line: Substring) -> String {
let text = String(line)
let fixedPrefixes = ["# ", "## ", "### ", "#### ", "##### ", "###### ", "> ", "- ", "+ "]
let exactMarkers = ["---", "***", "___"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Escape all setext underline marker lines

This only treats exact ---, ***, and ___ as block-leading syntax, but the new setext parser accepts any non-empty line made entirely of - or = as a heading underline. A paragraph with a hard break before ---- or ==== therefore exports unchanged and re-imports as a heading, losing the second paragraph line. Escape all setext underline-only lines, not just the exact divider markers.

Useful? React with 👍 / 👎.

let isImage = line[index] == "!" && line.index(after: index) < line.endIndex &&
line[line.index(after: index)] == "["
let openLabel = isImage ? line.index(after: index) : index
if line[openLabel] == "[",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip escaped reference-style link openings

This matcher does not check whether the opening [ is escaped, so a literal reference such as \[Guide][docs] is still rewritten when a [docs]: ... definition exists. The later inline parser keeps the escaped bracket literal, but the content has already been mutated to \[Guide](...), changing user text that explicitly opted out of link parsing. Ignore escaped opening brackets before resolving reference-style links.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0444922b9d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

continue
}

if let commonMarkBlock = commonMarkBlock(in: lines, startingAt: index) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Parse exported deeply nested lists as lists

When this new CommonMark block check runs before listItemBlock, any line with four leading spaces is consumed as an indented code block. The exporter still emits two spaces per list indent, so an indentLevel == 2 list item round-trips as - Grandchild and is imported as code instead of a nested list item, corrupting documents with lists deeper than one level. Defer indented-code parsing until after list detection, or exclude indented list markers from the code-block path.

Useful? React with 👍 / 👎.

resolvedFence = openingFence
return line
}
return replacingReferenceStyleLinks(in: line, definitions: definitions)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve reference-looking text inside indented code

Because every non-fenced line is passed through replacingReferenceStyleLinks whenever a definition exists, lines that will later be parsed as indented code are still rewritten. For example, a four-space code line containing [Guide][docs] is converted to [Guide](...) before indentedCodeBlock builds the code block, so imported code content is mutated; skip indented code ranges here or resolve references after block classification.

Useful? React with 👍 / 👎.

let startsOrderedList = startsWithOrderedListMarker(text)
let isExactMarker = exactMarkers.contains(text)
guard startsFixedSyntax || startsFence || startsOrderedList || isExactMarker else { return text }
return "\\\(text)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Escape ordered-list paragraphs at the marker

When paragraph text starts with an ordered-list marker, this branch emits a leading escape before the digit, e.g. \1. item. Markdown backslash escapes only apply to punctuation, and the importer’s unescaper also leaves a slash before 1, so exporting and re-importing a literal 1. item paragraph changes the saved text to include the backslash. Escape the period instead, or otherwise make this ordered-list escape symmetric.

Useful? React with 👍 / 👎.

private static func escapingMarkdownBlockPrefix(_ line: Substring) -> String {
let text = String(line)
let fixedPrefixes = ["# ", "## ", "### ", "#### ", "##### ", "###### ", "> ", "- ", "+ "]
let exactMarkers = ["---", "***", "___"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Escape all setext underline marker lines

This only treats exact ---, ***, and ___ as block-leading syntax, but the new setext parser accepts any non-empty line made entirely of - or = as a heading underline. A paragraph with a hard break before ---- or ==== therefore exports unchanged and re-imports as a heading, losing the second paragraph line. Escape all setext underline-only lines, not just the exact divider markers.

Useful? React with 👍 / 👎.

let isImage = line[index] == "!" && line.index(after: index) < line.endIndex &&
line[line.index(after: index)] == "["
let openLabel = isImage ? line.index(after: index) : index
if line[openLabel] == "[",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip escaped reference-style link openings

This matcher does not check whether the opening [ is escaped, so a literal reference such as \[Guide][docs] is still rewritten when a [docs]: ... definition exists. The later inline parser keeps the escaped bracket literal, but the content has already been mutated to \[Guide](...), changing user text that explicitly opted out of link parsing. Ignore escaped opening brackets before resolving reference-style links.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown

Confidence Score: 3/5

The heading typography and reference-link changes are safe, but the new escaping layer has a concrete data-corruption path for paragraphs that start with ordered-list notation.

The ordered-list escaping strategy produces \1. text, but \ followed by a digit is not a CommonMark escape sequence, so the backslash survives re-import and corrupts the paragraph text. The parameterised parity test "1. literal ordered list" directly covers this case and would fail at runtime — the PR notes that tests were not executed. Everything else (heading fonts, setext/indented-code parsing, reference resolution, inline escape guards) looks correct.

NativeEditorMarkdownParser+Escaping.swift — specifically escapingMarkdownBlockPrefix and its interaction with unescapedMarkdownPlainText for the ordered-list case. NativeEditorMarkdownParityTests.swift confirms the broken case in its argument list but the tests were not run.

Important Files Changed

Filename Overview
docmostly/Features/Editor/NativeEditorMarkdownParser+Escaping.swift New file adding block-level and inline escape/unescape helpers; ordered-list escaping uses a \N. pattern that isn't a valid CommonMark escape sequence, breaking the round-trip for paragraphs starting with ordered-list notation.
docmostly/Features/Editor/NativeEditorMarkdownParser+References.swift New file resolving full, collapsed, and shortcut reference-style links/images; the definition parser uses an unescaped bracket search which can mishandle labels containing \].
docmostly/Features/Editor/NativeEditorMarkdownParser+CommonMarkBlocks.swift New file adding setext heading and indented code block parsing; logic looks correct, including tab expansion and trailing-blank-line trimming for code blocks.
docmostly/Features/Editor/NativeEditorMarkdownParser+FrontMatter.swift Refactored removingLeadingYAMLFrontMatter into its own extension file with no functional changes.
docmostly/Features/Editor/NativeEditorMarkdownParser+InlineMarks.swift Updated inline mark parsing to respect escape sequences when locating delimiters and to unescape plain-text spans; the link-label escaping helper is removed and consolidated into the new Escaping extension.
docmostly/Features/Editor/NativeEditorBlockKind.swift Adds per-level heading fonts for levels 3–6 and a stronglyEmphasizedEditorFont variant using .heavy weight for bold-within-heading rendering.
docmostly/Features/Editor/NativeEditorRichBlockPreviewView.swift Adds previewText computed property on the View to apply heavy-weight font to strongly-emphasized runs inside headings; view logic would be more testable in a view model per project guidelines.
docmostly/Features/Editor/NativeEditorTextInputView+iOS.swift Threads NativeEditorBlockKind through renderedText/applyInlinePresentationFonts to apply .heavy UIFont weight for bold runs inside headings; adds a headingTextStyle private extension covering all 6 heading levels.
docmostly/Features/Editor/NativeEditorTextInputView+macOS.swift Mirrors the iOS heading-font changes for macOS using NSFont and NSFontManager; parallel with iOS implementation and looks correct.
docmostlyTests/Editor/NativeEditorMarkdownParityTests.swift New parity test file; the parameterized literalMarkdownSyntaxRoundTripsAsParagraph test includes "1. literal ordered list" which will fail at runtime due to the ordered-list escaping bug.
docmostlyTests/Editor/NativeEditorHeadingMarkdownTests.swift Adds a bold-heading round-trip test through ProseMirror; test structure and assertions look correct.
docmostly/Features/Editor/NativeEditorMarkdownParser.swift Wires commonMarkBlock and resolvingReferenceStyleLinks into the parsing pipeline; the default export case now uses escapedBlockLeadingMarkdown, which introduces the ordered-list escaping bug.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["blocks(from: markdown)"] --> B["removingLeadingYAMLFrontMatter"]
    B --> C["resolvingReferenceStyleLinks"]
    C --> D{"Per-line loop"}
    D -->|fenced code| E["fencedCodeBlock"]
    D -->|setext / indented| F["commonMarkBlock (NEW)"]
    F --> F1["setextHeadingBlock"]
    F --> F2["indentedCodeBlock"]
    D -->|images| G["appendImageMarkdownBlocks..."]
    D -->|ATX heading / list / quote| H["richBlock"]
    D -->|table| I["tableBlock"]
    D -->|blockquote| J["blockquoteBlock"]
    D -->|list item| K["listItemBlock"]
    D -->|paragraph| L["paragraphBlock"]
    M["markdown(from: blocks)"] --> N{"Per-block switch"}
    N -->|heading| O["## text"]
    N -->|list / quote / code / divider / table| P["structured prefix"]
    N -->|paragraph / other| Q["escapedBlockLeadingMarkdown (NEW)"]
    Q --> Q1["escapedMarkdownPlainText inline"]
    Q --> Q2["escapingMarkdownBlockPrefix - ordered-list branch bug"]
    style Q2 fill:#ffcccc,stroke:#cc0000
Loading

Comments Outside Diff (2)

  1. docmostly/Features/Editor/NativeEditorMarkdownParser+Escaping.swift, line 227-235 (link)

    P1 Ordered-list escape corrupts round-trip text

    escapingMarkdownBlockPrefix prepends \ to ordered-list lines, producing e.g. \1. literal ordered list. However \1 is not a valid CommonMark escape sequence — digits are not ASCII punctuation — so unescapedMarkdownPlainText does not consume the backslash and the re-imported paragraph text becomes \1. literal ordered list instead of 1. literal ordered list. The parameterized parity test for "1. literal ordered list" would fail at runtime. The fix is to escape the . after the digits (e.g. produce 1\. text) since . IS in markdownEscapableCharacters and unescapedMarkdownPlainText will correctly strip the backslash on re-import.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: docmostly/Features/Editor/NativeEditorMarkdownParser+Escaping.swift
    Line: 227-235
    
    Comment:
    **Ordered-list escape corrupts round-trip text**
    
    `escapingMarkdownBlockPrefix` prepends `\` to ordered-list lines, producing e.g. `\1. literal ordered list`. However `\1` is not a valid CommonMark escape sequence — digits are not ASCII punctuation — so `unescapedMarkdownPlainText` does not consume the backslash and the re-imported paragraph text becomes `\1. literal ordered list` instead of `1. literal ordered list`. The parameterized parity test for `"1. literal ordered list"` would fail at runtime. The fix is to escape the `.` after the digits (e.g. produce `1\. text`) since `.` IS in `markdownEscapableCharacters` and `unescapedMarkdownPlainText` will correctly strip the backslash on re-import.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Codex

  2. docmostly/Features/Editor/NativeEditorRichBlockPreviewView.swift, line 684-695 (link)

    P2 View logic in a computed property rather than a view model

    The AGENTS.md guide says "Place view logic into view models or similar, so it can be tested." previewText performs non-trivial AttributedString mutation — iterating runs and applying per-range fonts based on block kind — that would be straightforward to unit-test in isolation. As a computed property directly on the View struct it bypasses that guidance. Moving this transform into a dedicated helper (e.g. a static function on NativeEditorBlock or a view model) would make it independently testable.

    Context Used: AGENTS.md (source)

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: docmostly/Features/Editor/NativeEditorRichBlockPreviewView.swift
    Line: 684-695
    
    Comment:
    **View logic in a computed property rather than a view model**
    
    The AGENTS.md guide says "Place view logic into view models or similar, so it can be tested." `previewText` performs non-trivial `AttributedString` mutation — iterating runs and applying per-range fonts based on block kind — that would be straightforward to unit-test in isolation. As a computed property directly on the `View` struct it bypasses that guidance. Moving this transform into a dedicated helper (e.g. a static function on `NativeEditorBlock` or a view model) would make it independently testable.
    
    **Context Used:** AGENTS.md ([source](https://app.greptile.com/chefski/github/Chefski/docmostly/-/custom-context?memory=b96280a5-77a8-422c-b334-bf960d63b0fc))
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

    Fix in Codex

Fix All in Codex

Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
docmostly/Features/Editor/NativeEditorMarkdownParser+Escaping.swift:227-235
**Ordered-list escape corrupts round-trip text**

`escapingMarkdownBlockPrefix` prepends `\` to ordered-list lines, producing e.g. `\1. literal ordered list`. However `\1` is not a valid CommonMark escape sequence — digits are not ASCII punctuation — so `unescapedMarkdownPlainText` does not consume the backslash and the re-imported paragraph text becomes `\1. literal ordered list` instead of `1. literal ordered list`. The parameterized parity test for `"1. literal ordered list"` would fail at runtime. The fix is to escape the `.` after the digits (e.g. produce `1\. text`) since `.` IS in `markdownEscapableCharacters` and `unescapedMarkdownPlainText` will correctly strip the backslash on re-import.

### Issue 2 of 3
docmostly/Features/Editor/NativeEditorMarkdownParser+References.swift:57-61
**Unescaped bracket search in reference-definition parser**

`referenceDefinition` uses `trimmed.firstIndex(of: "]")` to locate the end of the link label, which does not respect `\]` escapes inside the label. A label like `[foo\]bar]` would split at the `\]` position, extracting `foo\` as the label and leaving `bar]` to fail the colon check, silently rejecting a valid definition. The `closingReferenceBracket` helper used elsewhere already handles escapes correctly; it could be reused here.

### Issue 3 of 3
docmostly/Features/Editor/NativeEditorRichBlockPreviewView.swift:684-695
**View logic in a computed property rather than a view model**

The AGENTS.md guide says "Place view logic into view models or similar, so it can be tested." `previewText` performs non-trivial `AttributedString` mutation — iterating runs and applying per-range fonts based on block kind — that would be straightforward to unit-test in isolation. As a computed property directly on the `View` struct it bypasses that guidance. Moving this transform into a dedicated helper (e.g. a static function on `NativeEditorBlock` or a view model) would make it independently testable.

Reviews (1): Last reviewed commit: "feat: enhance native markdown support" | Re-trigger Greptile

Comment on lines +57 to +61
let trimmed = line.dropFirst(leadingSpaces)
guard trimmed.first == "[",
let closeLabel = trimmed.firstIndex(of: "]") else {
return nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Unescaped bracket search in reference-definition parser

referenceDefinition uses trimmed.firstIndex(of: "]") to locate the end of the link label, which does not respect \] escapes inside the label. A label like [foo\]bar] would split at the \] position, extracting foo\ as the label and leaving bar] to fail the colon check, silently rejecting a valid definition. The closingReferenceBracket helper used elsewhere already handles escapes correctly; it could be reused here.

Prompt To Fix With AI
This is a comment left during a code review.
Path: docmostly/Features/Editor/NativeEditorMarkdownParser+References.swift
Line: 57-61

Comment:
**Unescaped bracket search in reference-definition parser**

`referenceDefinition` uses `trimmed.firstIndex(of: "]")` to locate the end of the link label, which does not respect `\]` escapes inside the label. A label like `[foo\]bar]` would split at the `\]` position, extracting `foo\` as the label and leaving `bar]` to fail the colon check, silently rejecting a valid definition. The `closingReferenceBracket` helper used elsewhere already handles escapes correctly; it could be reused here.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Codex

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 10 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread docmostly/Features/Editor/NativeEditorMarkdownParser+Escaping.swift Outdated
@Chefski
Chefski merged commit 616462e into dev Jul 25, 2026
7 checks passed
@Chefski
Chefski deleted the codex/enhance-native-markdown branch July 25, 2026 07:22
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