Skip to content

fix(markdown): details body with nested code fences#629

Merged
rainxchzed merged 2 commits into
mainfrom
fix/markdown-details-nested-fence
May 17, 2026
Merged

fix(markdown): details body with nested code fences#629
rainxchzed merged 2 commits into
mainfrom
fix/markdown-details-nested-fence

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 17, 2026

Summary

GHS README's "Show full setup guide" <details> contained a properties code fence. The inner terminated our 3-backtick ghs-details wrapper early, so body content leaked outside the collapsible and remainder of README rendered monospace.

Fix: size the wrapper fence to max(4, longestRun + 1) of backticks in body. Nested fences no longer close it. Bumps readme cache key v2→v3 to invalidate stale entries.

Test plan

  • Open GHS repo Details → README → collapsed details shows nothing else below; expand → renders OAuth setup steps + table + code block correctly.
  • Content after </details> (Wiki & Resources section) renders as normal markdown, not monospace.

Summary by CodeRabbit

  • New Features

    • Theme-aware Markdown rendering for expandable details (respects dark/light theme)
  • Bug Fixes

    • README content is now refreshed to display the latest information
    • Improved handling and rendering of collapsible Markdown sections that contain fenced code blocks to prevent premature termination and preserve formatting

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

Walkthrough

Details-block preprocessing now sizes ghs-details fences based on the longest backtick run to avoid premature fence termination; presentation rendering uses theme-aware Markdown components and a simpler fence-body extraction; README cache key is bumped to force refresh of previously cached READMEs.

Changes

README Processing and Cache Invalidation

Layer / File(s) Summary
Dynamic fence delimiter for details blocks
feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt
Details blocks wrapped in ghs-details fences now use dynamically-sized backtick delimiters instead of fixed triple-backticks, determined by scanning the longest consecutive backtick run in the body. A new longestBacktickRun(text: String): Int helper computes this length.
Presentation: fence extraction and theme-aware components
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/markdown/ExpandableDetails.kt
ExpandableDetails detects system dark/light theme and passes githubStoreMarkdownComponents(..., isDark) to the Markdown renderer. extractFenceBody was simplified to take a contiguous substring between the first and last CODE_FENCE_CONTENT tokens.
README cache version bump
feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/repository/DetailsRepositoryImpl.kt
getReadme cache key version is updated (bumped) so previously cached README entries under the old key are bypassed and entries are refetched and reprocessed.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Poem

🐰 I munch through backticks both short and long,
I stitch a fence just sturdy and strong.
Theme-aware colors now dance in the light,
Old READMEs refreshed — everything's bright! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately reflects the main fix: handling nested code fences within markdown details blocks by dynamically sizing the wrapper fence.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/markdown-details-nested-fence

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/repository/DetailsRepositoryImpl.kt (1)

394-397: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the comment to reflect the v3 bump.

The comment references "v2" but the code now uses "v3". Update the comment to explain why the version was bumped from v2 to v3 (dynamic fence handling for details blocks with nested code fences).

📝 Proposed fix to update the comment
-        // v2 — bumped after markdown preprocessor overhaul (alerts,
-        // emoji, details, image-row). Forces re-fetch so users get a
-        // properly-processed readme instead of waiting for the stale
-        // v1 entry to expire.
+        // v3 — bumped after fixing details blocks with nested code fences.
+        // Forces re-fetch so users get properly-processed readmes with
+        // dynamic fence delimiters instead of waiting for stale v2 entries
+        // to expire.
         val cacheKey = "details:readme:v3:$owner/$repo"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/repository/DetailsRepositoryImpl.kt`
around lines 394 - 397, The comment above the readme processing version bump
still says "v2" but the code uses v3; update that comment in
DetailsRepositoryImpl (the comment block near the readme/markdown preprocessor
in DetailsRepositoryImpl.kt) to say "v3" and briefly state the reason: bumped
from v2 to v3 to support dynamic fence handling for details blocks with nested
code fences (ensures proper processing of nested fenced code in details blocks),
keeping the surrounding explanatory lines about forcing a re-fetch so users get
the newly processed readme.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/repository/DetailsRepositoryImpl.kt`:
- Around line 394-397: The comment above the readme processing version bump
still says "v2" but the code uses v3; update that comment in
DetailsRepositoryImpl (the comment block near the readme/markdown preprocessor
in DetailsRepositoryImpl.kt) to say "v3" and briefly state the reason: bumped
from v2 to v3 to support dynamic fence handling for details blocks with nested
code fences (ensures proper processing of nested fenced code in details blocks),
keeping the surrounding explanatory lines about forcing a re-fetch so users get
the newly processed readme.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c7c6d3ed-9906-43a1-9148-def2d13b7c88

📥 Commits

Reviewing files that changed from the base of the PR and between 4112426 and cae46db.

📒 Files selected for processing (2)
  • feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/repository/DetailsRepositoryImpl.kt
  • feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 17, 2026

Greptile Summary

This PR fixes a fence-collision bug where a nested ``` inside a <details> body terminated the outer ghs-details wrapper early, causing content to leak outside the collapsible and render the rest of the README in monospace.

  • preprocessMarkdown.kt: introduces longestBacktickRun and sizes the outer fence to max(4, longestRun + 1) backticks, guaranteeing no inner fence can close the wrapper.
  • ExpandableDetails.kt: replaces the child-iteration loop in extractFenceBody with a direct content.substring(first, last) slice (eliminating the double-newline introduced by counting both the prepend-newline and the EOL token), and passes githubStoreMarkdownComponents to the inner Markdown so alerts, nested details, and custom code-fence renderers work inside expanded sections.
  • DetailsRepositoryImpl.kt: bumps the readme cache key to v4 to force re-processing of stale entries.

Confidence Score: 5/5

Safe to merge — the fence-sizing and body-extraction changes are logically correct, and the cache key bump will invalidate stale entries for all existing users.

The fence-delimiter fix correctly prevents any inner backtick run from closing the outer wrapper. The extractFenceBody refactor eliminates the double-newline bug without introducing new edge cases — the raw-string slice naturally preserves inter-line newlines through EOL tokens that fall between the first and last CODE_FENCE_CONTENT offsets. Wiring githubStoreMarkdownComponents into the inner Markdown is the right call for nested rendering. No functional regressions were identified in the changed paths.

No files require special attention.

Important Files Changed

Filename Overview
feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt Adds longestBacktickRun helper and uses it to size the outer ghs-details fence delimiter dynamically; logic is correct — the outer fence is always at least 4 backticks and always one longer than any run inside the body.
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/markdown/ExpandableDetails.kt Refactors extractFenceBody to slice the raw content string between first/last CODE_FENCE_CONTENT offsets (fixing the double-newline bug), and wires githubStoreMarkdownComponents into the inner Markdown so nested fences, alerts, and details render correctly inside expanded sections.
feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/repository/DetailsRepositoryImpl.kt Cache key bumped from v2 to v4 to invalidate stale rendered READMEs; the in-code comment still describes the v1→v2 bump rationale and does not mention the v4 change (noted in a previous review comment).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["preprocessMarkdown(rawReadme)"] --> B["Detect details block"]
    B --> C["Extract summary + body"]
    C --> D["longestBacktickRun(body)"]
    D --> E["fence = repeat(max(4, longestRun + 1))"]
    E --> F["Emit: fence + ghs-details|encodedSummary + body + fence"]
    F --> G["Markdown parser tokenises outer fence as CODE_FENCE"]
    G --> H["githubStoreMarkdownComponents.codeFence handler"]
    H --> I{"infoStringForDetails?"}
    I -- yes --> J["ExpandableDetails composable"]
    I -- no --> K["Default code block renderer"]
    J --> L["extractFenceBody(model)"]
    L --> M["contentNodes = filter CODE_FENCE_CONTENT children"]
    M --> N["content.substring(first.start, last.end).trim()"]
    N --> O["Inner Markdown with githubStoreMarkdownComponents"]
    O --> P["Nested alerts / details / code blocks render correctly"]
Loading

Reviews (2): Last reviewed commit: "fix(markdown): preserve body line layout..." | Re-trigger Greptile

Comment on lines +592 to +604
private fun longestBacktickRun(text: String): Int {
var max = 0
var current = 0
for (c in text) {
if (c == '`') {
current++
if (current > max) max = current
} else {
current = 0
}
}
return max
}
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 longestBacktickRun is not line-aware

Per CommonMark, only a backtick run that occupies a whole line (optionally followed by spaces, nothing else) can act as a closing fence delimiter. Inline runs like two backticks or ````four backticks```` embedded in surrounding text cannot close a fence. The current implementation counts these inline sequences the same as genuine fence lines, so the computed outer fence can end up longer than strictly necessary. This is harmless in practice — the fence is always safe — but a body containing 5 consecutive inline backticks would produce a 6-backtick wrapper when 3 would suffice. Consider restricting the scan to backtick runs that start at a line boundary to match CommonMark's actual fence-close rule.

Fix in Claude Code

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/repository/DetailsRepositoryImpl.kt (1)

394-398: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stale comment references wrong cache version.

The comment on lines 394-397 describes this as "v2" but the actual cache key is v4. Update the comment to reflect the current version and the reason for this bump (nested fence handling fix).

📝 Suggested fix
-        // v2 — bumped after markdown preprocessor overhaul (alerts,
-        // emoji, details, image-row). Forces re-fetch so users get a
-        // properly-processed readme instead of waiting for the stale
-        // v1 entry to expire.
+        // v4 — bumped after fixing nested code-fence handling in
+        // <details> blocks. Forces re-fetch so users get properly-
+        // processed readmes instead of waiting for stale entries to
+        // expire.
         val cacheKey = "details:readme:v4:$owner/$repo"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/repository/DetailsRepositoryImpl.kt`
around lines 394 - 398, The inline comment above the cacheKey declaration in
DetailsRepositoryImpl.kt is outdated (it says "v2" while the cacheKey is
"details:readme:v4:$owner/$repo"); update that comment to state "v4" and mention
the actual reason for this bump (fix for nested fence handling in the markdown
preprocessor), so the comment matches the cacheKey variable (cacheKey) and
explains the version bump and context.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/repository/DetailsRepositoryImpl.kt`:
- Around line 394-398: The inline comment above the cacheKey declaration in
DetailsRepositoryImpl.kt is outdated (it says "v2" while the cacheKey is
"details:readme:v4:$owner/$repo"); update that comment to state "v4" and mention
the actual reason for this bump (fix for nested fence handling in the markdown
preprocessor), so the comment matches the cacheKey variable (cacheKey) and
explains the version bump and context.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8e8dc67e-b08d-4820-986b-2f46aabb5767

📥 Commits

Reviewing files that changed from the base of the PR and between cae46db and ee7db6b.

📒 Files selected for processing (2)
  • feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/repository/DetailsRepositoryImpl.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/markdown/ExpandableDetails.kt

@rainxchzed rainxchzed merged commit 6632fd3 into main May 17, 2026
1 check passed
@rainxchzed rainxchzed deleted the fix/markdown-details-nested-fence branch May 17, 2026 05:25
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