@readme/markdown 14.13.0 → 14.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -92197,6 +92197,10 @@ const tailwindPrefix = 'readme-tailwind';
92197
92197
 
92198
92198
 
92199
92199
  const TailwindRoot = ({ children, flow }) => {
92200
+ // `styles/gfm.scss` spaces the wrapped block children through this element and
92201
+ // relies on their margins collapsing through it. Keep it a plain block — no
92202
+ // border, padding, or block formatting context (overflow, display: flow-root/
92203
+ // flex/grid) — or the spacing breaks.
92200
92204
  const Tag = flow ? 'div' : 'span';
92201
92205
  return external_react_default().createElement(Tag, { className: tailwindPrefix }, children);
92202
92206
  };
@@ -96476,6 +96480,15 @@ const HTML_TABLE_STRUCTURE_TAGS = new Set([
96476
96480
  'caption',
96477
96481
  'colgroup',
96478
96482
  ]);
96483
+ /**
96484
+ * Tags whose bodies a later mdxish transform keeps raw and never re-parses as
96485
+ * markdown. Both layers depend on this property, not on the tags being figures:
96486
+ * the transformer treats them as non-promotable, and the tokenizer must not claim
96487
+ * markdown islands inside them (a claimed island would never be re-parsed and would
96488
+ * leak as literal text). Currently just the figure tags, whose bodies are owned by
96489
+ * the figure reassembly transform (`mdxishJsxToMdast`).
96490
+ */
96491
+ const NON_REPARSED_BODY_TAGS = new Set(['figure', 'figcaption']);
96479
96492
  /**
96480
96493
  * The two HTML "foreign content" namespaces: SVG and MathML. Their descendants
96481
96494
  * (`<path>`, `<mrow>`, …) are namespaced XML, not HTML tags or components. A closed
@@ -117853,7 +117866,7 @@ function normalizeProperties(node) {
117853
117866
  * Identifies custom MDX components and recursively parses markdown children.
117854
117867
  * Replaces tagName with PascalCase component name for React component resolution.
117855
117868
  *
117856
- * @see {@link https://github.com/readmeio/rmdx/blob/main/docs/mdxish-flow.md}
117869
+ * @see .claude/context/MDXish/Processor Overview.md
117857
117870
  * @param {Options} options - Configuration options
117858
117871
  * @param {CustomComponents} options.components - Available custom components
117859
117872
  * @param {Function} options.processMarkdown - Function to process markdown content
@@ -120253,12 +120266,95 @@ function tokenizeMagicBlockText(effects, ok, nok) {
120253
120266
  */
120254
120267
 
120255
120268
 
120269
+ ;// ./lib/micromark/mdx-component/continuation-checks.ts
120270
+
120271
+
120272
+ /**
120273
+ * Partial constructs the `mdxComponent` tokenizer runs via `effects.check` to
120274
+ * decide whether the next line continues the block. They live here (not in
120275
+ * `syntax.ts`) because they hold none of `createTokenize`'s closure state — each
120276
+ * is a self-contained, single-line lookahead.
120277
+ */
120278
+ function continuation_checks_tokenizeNonLazyContinuationStart(effects, ok, nok) {
120279
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
120280
+ const self = this;
120281
+ return start;
120282
+ function start(code) {
120283
+ if (markdownLineEnding(code)) {
120284
+ effects.enter(types_types.lineEnding);
120285
+ effects.consume(code);
120286
+ effects.exit(types_types.lineEnding);
120287
+ return after;
120288
+ }
120289
+ return nok(code);
120290
+ }
120291
+ function after(code) {
120292
+ if (self.parser.lazy[self.now().line]) {
120293
+ return nok(code);
120294
+ }
120295
+ return ok(code);
120296
+ }
120297
+ }
120298
+ // A markup-only line opens with a tag (`<x`/`</x`) and ends (ignoring trailing
120299
+ // spaces) at a `>`. That distinguishes a structural continuation like
120300
+ // `<span>b</span></div>` from a paragraph like `<b>Note:</b> read *this*`.
120301
+ function tokenizeMarkupOnlyContinuation(effects, ok, nok) {
120302
+ let lastNonSpace = null;
120303
+ return start;
120304
+ function start(code) {
120305
+ // Caller guarantees we are at `<` at the (already de-indented) line start.
120306
+ effects.enter(types_types.data);
120307
+ effects.consume(code);
120308
+ return afterLessThan;
120309
+ }
120310
+ function afterLessThan(code) {
120311
+ if (code === codes.slash) {
120312
+ effects.consume(code);
120313
+ return afterSlash;
120314
+ }
120315
+ return afterSlash(code);
120316
+ }
120317
+ // The `<` (or `</`) must introduce a real tag, not a stray `<` in prose.
120318
+ function afterSlash(code) {
120319
+ if (asciiAlpha(code)) {
120320
+ lastNonSpace = code;
120321
+ effects.consume(code);
120322
+ return scanToLineEnd;
120323
+ }
120324
+ effects.exit(types_types.data);
120325
+ return nok(code);
120326
+ }
120327
+ function scanToLineEnd(code) {
120328
+ if (code === null || markdownLineEnding(code)) {
120329
+ effects.exit(types_types.data);
120330
+ return lastNonSpace === codes.greaterThan ? ok(code) : nok(code);
120331
+ }
120332
+ if (!markdownSpace(code))
120333
+ lastNonSpace = code;
120334
+ effects.consume(code);
120335
+ return scanToLineEnd;
120336
+ }
120337
+ }
120338
+ // A line ending whose next line isn't a lazy paragraph continuation. Checked so a
120339
+ // component body's blank/continuation lines aren't stolen by an interrupted paragraph.
120340
+ const continuation_checks_nonLazyContinuationStart = {
120341
+ tokenize: continuation_checks_tokenizeNonLazyContinuationStart,
120342
+ partial: true,
120343
+ };
120344
+ // Lookahead for `plainClaimLineStart`: is this line markup-only, or a paragraph
120345
+ // that merely starts with a tag? Run via `effects.check` so it never consumes.
120346
+ const markupOnlyContinuation = {
120347
+ tokenize: tokenizeMarkupOnlyContinuation,
120348
+ partial: true,
120349
+ };
120350
+
120256
120351
  ;// ./lib/micromark/mdx-component/syntax.ts
120257
120352
 
120258
120353
 
120259
120354
 
120260
120355
 
120261
120356
 
120357
+
120262
120358
  // Raw tags (type-1: pre/script/style/textarea) and block tags (type-6: div,
120263
120359
  // section, …) always start a block, so they stay flow even with trailing
120264
120360
  // content. Other lowercase tags (i, span, …) follow the type-7 rule and only
@@ -120268,16 +120364,19 @@ const htmlFlowTagNames = new Set([...htmlRawNames, ...htmlBlockNames]);
120268
120364
  // blank lines between nested JSX siblings don't fragment the block. Excludes table
120269
120365
  // tags (mdxishTables owns their blank lines) and voids (never close).
120270
120366
  const plainBlockClaimTagNames = new Set([...htmlBlockNames].filter(tag => !HTML_TABLE_STRUCTURE_TAGS.has(tag) && !HTML_VOID_ELEMENTS.has(tag)));
120271
- const mdx_component_syntax_nonLazyContinuationStart = {
120272
- tokenize: mdx_component_syntax_tokenizeNonLazyContinuationStart,
120273
- partial: true,
120274
- };
120275
- // Lookahead for `plainClaimLineStart`: is this line markup-only, or a paragraph
120276
- // that merely starts with a tag? Run via `effects.check` so it never consumes.
120277
- const markupOnlyContinuation = {
120278
- tokenize: tokenizeMarkupOnlyContinuation,
120279
- partial: true,
120280
- };
120367
+ const foreignContentTags = new Set(FOREIGN_CONTENT_TAGS);
120368
+ // Type-7 lowercase tags (a, span, button, and unknown names): CommonMark ends their
120369
+ // block at a blank line, fragmenting 4+ column children into indented code. Claimable
120370
+ // only in block-wrapper shape (see `blockWrapperOpenerRest`); lookalike openers
120371
+ // (`<https://…>`) never find a closer, so their claim retreats cleanly to CommonMark.
120372
+ // Voids never close and raw/foreign-content bodies have dedicated owners.
120373
+ const isBlockWrapperClaimTagName = (tag) => !htmlFlowTagNames.has(tag) && !HTML_VOID_ELEMENTS.has(tag) && !foreignContentTags.has(tag);
120374
+ // Both are 4 columns per CommonMark, but they mean different things: a tab advances
120375
+ // to the next multiple of TAB_STOP_WIDTH, and INDENTED_CODE_MIN_COLUMNS is the depth
120376
+ // at which a line would fragment into indented code. Named separately so the two
120377
+ // concepts don't read as one incidental literal.
120378
+ const TAB_STOP_WIDTH = 4;
120379
+ const INDENTED_CODE_MIN_COLUMNS = 4;
120281
120380
  function resolveToMdxComponent(events) {
120282
120381
  let index = events.length;
120283
120382
  while (index > 0) {
@@ -120336,6 +120435,23 @@ function createTokenize(mode) {
120336
120435
  // `plainClaimLineStart`: after a blank line it may only continue on a tag line.
120337
120436
  let isPlainBlockClaim = false;
120338
120437
  let pendingBlankLine = false;
120438
+ // Type-7 tag claimed pending the block-wrapper (opener alone on its line) check.
120439
+ let pendingBlockWrapperClaim = false;
120440
+ // True once a block-wrapper claim is confirmed; relaxes the top-of-body island gate
120441
+ // below (type-7 fallback is the fragmentation bug, not intentional indented code).
120442
+ let isBlockWrapperClaim = false;
120443
+ // Leading indent columns of the current plain-claim line, reset per line; ≥4 is
120444
+ // where CommonMark would fragment the island as indented code. Tabs advance to the
120445
+ // next 4-column stop — the same rule `expandIndentToColumns`
120446
+ // (processor/transform/mdxish/indentation.ts) applies, kept in sync by hand since
120447
+ // this side works on a `Code` stream, not a string. NB: do NOT swap this for
120448
+ // `self.now().column`; micromark bumps the point column by 1 per `horizontalTab`
120449
+ // code (the trailing `virtualSpace` codes don't move it), so it measures a tab as
120450
+ // 1 column, reviving the tab-under-measurement bug this math exists to avoid.
120451
+ let plainClaimIndentColumns = 0;
120452
+ // True once a non-blank line follows the opener: a deep island below it is nested
120453
+ // (cosmetic indent), not top-of-body indented code.
120454
+ let sawPlainBlockBodyContent = false;
120339
120455
  // Code span tracking
120340
120456
  let codeSpanOpenSize = 0;
120341
120457
  let codeSpanCloseSize = 0;
@@ -120569,16 +120685,11 @@ function createTokenize(mode) {
120569
120685
  }
120570
120686
  // End of opening tag
120571
120687
  if (code === codes.greaterThan) {
120572
- if (requiresBraceAttr && !sawBraceAttr) {
120573
- // Plain lowercase block tags stay claimable in flow, gated per line by
120574
- // `plainClaimLineStart`; everything else falls through to CommonMark.
120575
- if (!isFlow || !plainBlockClaimTagNames.has(tagName))
120576
- return nok(code);
120577
- isPlainBlockClaim = true;
120578
- }
120688
+ if (requiresBraceAttr && !sawBraceAttr && !claimBraceLessTag())
120689
+ return nok(code);
120579
120690
  effects.consume(code);
120580
120691
  onOpenerLine = isFlow;
120581
- return body;
120692
+ return pendingBlockWrapperClaim ? blockWrapperOpenerRest : body;
120582
120693
  }
120583
120694
  // Quoted attribute value
120584
120695
  if (code === codes.quotationMark || code === codes.apostrophe) {
@@ -120633,9 +120744,38 @@ function createTokenize(mode) {
120633
120744
  // `/ ` without `>` is just part of the attribute area
120634
120745
  return afterOpenTagName(code);
120635
120746
  }
120747
+ // Whether a brace-less lowercase flow tag is claimable: type-6 tags immediately,
120748
+ // type-7 tags pending the block-wrapper check; anything else is CommonMark's.
120749
+ function claimBraceLessTag() {
120750
+ if (!isFlow)
120751
+ return false;
120752
+ if (plainBlockClaimTagNames.has(tagName)) {
120753
+ isPlainBlockClaim = true;
120754
+ return true;
120755
+ }
120756
+ if (isBlockWrapperClaimTagName(tagName)) {
120757
+ pendingBlockWrapperClaim = true;
120758
+ return true;
120759
+ }
120760
+ return false;
120761
+ }
120762
+ // A type-7 tag is claimed only as a block wrapper: opener alone on its line
120763
+ // (trailing spaces ok). Inline content after the opener bails to CommonMark.
120764
+ function blockWrapperOpenerRest(code) {
120765
+ if (markdownSpace(code)) {
120766
+ effects.consume(code);
120767
+ return blockWrapperOpenerRest;
120768
+ }
120769
+ if (!markdownLineEnding(code))
120770
+ return nok(code);
120771
+ pendingBlockWrapperClaim = false;
120772
+ isPlainBlockClaim = true;
120773
+ isBlockWrapperClaim = true;
120774
+ return body(code);
120775
+ }
120636
120776
  // Continuation for multi-line opening tags
120637
120777
  function openTagContinuationStart(code) {
120638
- return effects.check(mdx_component_syntax_nonLazyContinuationStart, openTagContinuationNonLazy, continuationAfter)(code);
120778
+ return effects.check(continuation_checks_nonLazyContinuationStart, openTagContinuationNonLazy, continuationAfter)(code);
120639
120779
  }
120640
120780
  function openTagContinuationNonLazy(code) {
120641
120781
  sawLineEnding = true;
@@ -120680,6 +120820,9 @@ function createTokenize(mode) {
120680
120820
  }
120681
120821
  if (code !== codes.space && code !== codes.horizontalTab) {
120682
120822
  openerLineHasContent = true;
120823
+ // Continuation content marks a later deep island as nested, not indented code.
120824
+ if (!onOpenerLine)
120825
+ sawPlainBlockBodyContent = true;
120683
120826
  }
120684
120827
  if (code === codes.backslash) {
120685
120828
  effects.consume(code);
@@ -120760,7 +120903,7 @@ function createTokenize(mode) {
120760
120903
  return inFencedCode;
120761
120904
  }
120762
120905
  function fencedCodeContinuationStart(code) {
120763
- return effects.check(mdx_component_syntax_nonLazyContinuationStart, fencedCodeContinuationNonLazy, continuationAfter)(code);
120906
+ return effects.check(continuation_checks_nonLazyContinuationStart, fencedCodeContinuationNonLazy, continuationAfter)(code);
120764
120907
  }
120765
120908
  function fencedCodeContinuationNonLazy(code) {
120766
120909
  sawLineEnding = true;
@@ -120968,7 +121111,7 @@ function createTokenize(mode) {
120968
121111
  }
120969
121112
  // ── Body continuation (line endings) ───────────────────────────────────
120970
121113
  function bodyContinuationStart(code) {
120971
- return effects.check(mdx_component_syntax_nonLazyContinuationStart, bodyContinuationNonLazy, continuationAfter)(code);
121114
+ return effects.check(continuation_checks_nonLazyContinuationStart, bodyContinuationNonLazy, continuationAfter)(code);
120972
121115
  }
120973
121116
  function bodyContinuationNonLazy(code) {
120974
121117
  sawLineEnding = true;
@@ -120994,8 +121137,10 @@ function createTokenize(mode) {
120994
121137
  return inBodyBraceString(code);
120995
121138
  return inBodyBraceExpr(code);
120996
121139
  }
120997
- if (isPlainBlockClaim)
121140
+ if (isPlainBlockClaim) {
121141
+ plainClaimIndentColumns = 0;
120998
121142
  return plainClaimLineStart(code);
121143
+ }
120999
121144
  return bodyLineStart(code);
121000
121145
  }
121001
121146
  // Dispatch a non-blank continuation line: fenced code at line start, else body.
@@ -121023,8 +121168,16 @@ function createTokenize(mode) {
121023
121168
  // continue on a tag line (`<…`); any markdown island (`**bold**`, `[block:…]`, a
121024
121169
  // fence) refuses so CommonMark html-flow reparses it exactly as it does today.
121025
121170
  function plainClaimLineStart(code) {
121026
- // Leading whitespace only → treat as a blank line, matching CommonMark.
121027
- if (code === codes.space || code === codes.horizontalTab) {
121171
+ // Leading whitespace only → treat as a blank line, matching CommonMark. A tab
121172
+ // advances to the next stop; its trailing `virtualSpace` fillers add nothing
121173
+ // but must still be consumed or they'd read as line content below.
121174
+ if (markdownSpace(code)) {
121175
+ if (code === codes.horizontalTab) {
121176
+ plainClaimIndentColumns += TAB_STOP_WIDTH - (plainClaimIndentColumns % TAB_STOP_WIDTH);
121177
+ }
121178
+ else if (code === codes.space) {
121179
+ plainClaimIndentColumns += 1;
121180
+ }
121028
121181
  effects.consume(code);
121029
121182
  return plainClaimLineStart;
121030
121183
  }
@@ -121033,10 +121186,19 @@ function createTokenize(mode) {
121033
121186
  effects.exit('mdxComponentData');
121034
121187
  return bodyContinuationStart(code);
121035
121188
  }
121036
- // Across a blank line the block only continues onto a markup-only line; a
121037
- // paragraph that merely starts with a tag must fall back so its markdown
121038
- // parses and rehype-raw re-nests it into the wrapper.
121039
121189
  if (pendingBlankLine) {
121190
+ // A 4+ col island nested under other tags is cosmetic nesting indent, not code:
121191
+ // keep claiming so promotion dedents + re-parses it as markdown (RM-17560).
121192
+ // Block-wrapper claims extend this to top-of-body islands — their CommonMark
121193
+ // fallback is the fragmentation bug, not intentional indented code. Tags whose
121194
+ // bodies stay raw are excluded: a claimed island there would leak as literal text.
121195
+ if (plainClaimIndentColumns >= INDENTED_CODE_MIN_COLUMNS &&
121196
+ (sawPlainBlockBodyContent || isBlockWrapperClaim) &&
121197
+ !NON_REPARSED_BODY_TAGS.has(tagName)) {
121198
+ return plainClaimContinue(code);
121199
+ }
121200
+ // Otherwise only a markup-only tag line continues; markdown/prose falls back to
121201
+ // CommonMark so it parses and rehype-raw re-nests it into the wrapper.
121040
121202
  if (code !== codes.lessThan)
121041
121203
  return nok(code);
121042
121204
  return effects.check(markupOnlyContinuation, plainClaimContinue, nok)(code);
@@ -121057,66 +121219,6 @@ function createTokenize(mode) {
121057
121219
  }
121058
121220
  };
121059
121221
  }
121060
- function mdx_component_syntax_tokenizeNonLazyContinuationStart(effects, ok, nok) {
121061
- // eslint-disable-next-line @typescript-eslint/no-this-alias
121062
- const self = this;
121063
- return start;
121064
- function start(code) {
121065
- if (markdownLineEnding(code)) {
121066
- effects.enter(types_types.lineEnding);
121067
- effects.consume(code);
121068
- effects.exit(types_types.lineEnding);
121069
- return after;
121070
- }
121071
- return nok(code);
121072
- }
121073
- function after(code) {
121074
- if (self.parser.lazy[self.now().line]) {
121075
- return nok(code);
121076
- }
121077
- return ok(code);
121078
- }
121079
- }
121080
- // A markup-only line opens with a tag (`<x`/`</x`) and ends (ignoring trailing
121081
- // spaces) at a `>`. That distinguishes a structural continuation like
121082
- // `<span>b</span></div>` from a paragraph like `<b>Note:</b> read *this*`.
121083
- function tokenizeMarkupOnlyContinuation(effects, ok, nok) {
121084
- let lastNonSpace = null;
121085
- return start;
121086
- function start(code) {
121087
- // Caller guarantees we are at `<` at the (already de-indented) line start.
121088
- effects.enter(types_types.data);
121089
- effects.consume(code);
121090
- return afterLessThan;
121091
- }
121092
- function afterLessThan(code) {
121093
- if (code === codes.slash) {
121094
- effects.consume(code);
121095
- return afterSlash;
121096
- }
121097
- return afterSlash(code);
121098
- }
121099
- // The `<` (or `</`) must introduce a real tag, not a stray `<` in prose.
121100
- function afterSlash(code) {
121101
- if (asciiAlpha(code)) {
121102
- lastNonSpace = code;
121103
- effects.consume(code);
121104
- return scanToLineEnd;
121105
- }
121106
- effects.exit(types_types.data);
121107
- return nok(code);
121108
- }
121109
- function scanToLineEnd(code) {
121110
- if (code === null || markdownLineEnding(code)) {
121111
- effects.exit(types_types.data);
121112
- return lastNonSpace === codes.greaterThan ? ok(code) : nok(code);
121113
- }
121114
- if (!markdownSpace(code))
121115
- lastNonSpace = code;
121116
- effects.consume(code);
121117
- return scanToLineEnd;
121118
- }
121119
- }
121120
121222
  /**
121121
121223
  * Micromark extension that tokenizes MDX-like components.
121122
121224
  *
@@ -121170,6 +121272,7 @@ function syntax_mdxComponent() {
121170
121272
 
121171
121273
 
121172
121274
 
121275
+
121173
121276
 
121174
121277
  const buildInlineMdProcessor = (safeMode) => {
121175
121278
  // `jsxTable` must be present so a `<Table>`/`<table>` nested in a component
@@ -121241,7 +121344,7 @@ const toMdxJsxTextElement = (name, attributes, children, position) => ({
121241
121344
  // Raw-body tags (pre/script/style/textarea) must stay byte-exact; table
121242
121345
  // structure (`table` + `tableTags`) is owned by `mdxishTables` and figures by
121243
121346
  // `mdxishJsxToMdast`, both of which run later on raw html nodes.
121244
- const NON_PROMOTABLE_PLAIN_TAGS = new Set([...htmlRawNames, ...tableTags, 'table', 'figure', 'figcaption']);
121347
+ const NON_PROMOTABLE_PLAIN_TAGS = new Set([...htmlRawNames, ...tableTags, 'table', ...NON_REPARSED_BODY_TAGS]);
121245
121348
  const NESTED_TABLE_RE = /<table[\s>]/i;
121246
121349
  const isMarkdownPromotableHtmlTag = (tag) => !NON_PROMOTABLE_PLAIN_TAGS.has(tag);
121247
121350
  // Expression nodes count as plain so `<div>{1+1}</div>` keeps its current
@@ -121600,15 +121703,22 @@ function safeDeindent(text) {
121600
121703
  */
121601
121704
  const parseMdChildren = (value, safeMode) => {
121602
121705
  const parsed = getInlineMdProcessor({ safeMode }).parse(terminateHtmlFlowBlocks(safeDeindent(value).trim()));
121706
+ // Promote nested wrappers bottom-up so an outer wrapper sees markdown buried in a
121707
+ // child claimed whole (e.g. `<li>` in `<ol>`) before its containsMarkdownConstruct check (RM-17560).
121708
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- mutually recursive; hoisted decl, safe at runtime
121709
+ promoteComponentBlocks(parsed, safeMode, null);
121603
121710
  return parsed.children || [];
121604
121711
  };
121605
- // Parses trailing content into sibling nodes and re-queues the parent so any
121606
- // components among them get processed.
121607
- const parseSibling = (stack, parent, index, sibling, safeMode) => {
121712
+ // Splices trailing content in as sibling nodes. parseMdChildren has already
121713
+ // promoted any components nested among them (bottom-up); the main loop's
121714
+ // index-based walk then reaches these spliced siblings and the original children
121715
+ // they shift down, so no parent re-queue is needed. Each spliced subtree is marked
121716
+ // `promoted` so the walk doesn't redundantly re-descend into it (its html is gone).
121717
+ const parseSibling = (parent, index, sibling, safeMode, promoted) => {
121608
121718
  const siblingNodes = parseMdChildren(sibling, safeMode);
121609
121719
  if (siblingNodes.length > 0) {
121610
121720
  parent.children.splice(index + 1, 0, ...siblingNodes);
121611
- stack.push(parent);
121721
+ siblingNodes.forEach(siblingNode => promoted.add(siblingNode));
121612
121722
  }
121613
121723
  };
121614
121724
  // Ends the position at `consumedLength` so the component doesn't claim trailing
@@ -121674,16 +121784,19 @@ const substituteNodeWithMdxNode = (parent, index, mdxNode) => {
121674
121784
  * The opening tag, content, and closing tag are all captured in one HTML node
121675
121785
  * (guaranteed by the mdx-component tokenizer).
121676
121786
  */
121677
- const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
121787
+ function promoteComponentBlocks(tree, safeMode, source) {
121678
121788
  const stack = [tree];
121679
- const safeMode = !!opts.safeMode;
121680
- const source = file?.value ? String(file.value) : null;
121681
121789
  const parseOpts = { preserveExpressionsAsText: safeMode };
121790
+ // Subtrees a nested parseMdChildren already promoted wholesale (spliced siblings):
121791
+ // re-descending them finds no html to promote, so skip them.
121792
+ const promoted = new WeakSet();
121682
121793
  const processChildNode = (parent, index) => {
121683
121794
  const node = parent.children[index];
121684
121795
  if (!node)
121685
121796
  return;
121686
- if ('children' in node && Array.isArray(node.children)) {
121797
+ // Descend into container nodes (lists, blockquotes, …) so their html children
121798
+ // are reached — unless the subtree was already promoted upstream.
121799
+ if ('children' in node && Array.isArray(node.children) && !promoted.has(node)) {
121687
121800
  stack.push(node);
121688
121801
  }
121689
121802
  // Only html nodes can be an unparsed MDX component.
@@ -121738,7 +121851,7 @@ const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
121738
121851
  substituteNodeWithMdxNode(parent, index, componentNode);
121739
121852
  const remainingContent = contentAfterTag.trim();
121740
121853
  if (remainingContent) {
121741
- parseSibling(stack, parent, index, remainingContent, safeMode);
121854
+ parseSibling(parent, index, remainingContent, safeMode, promoted);
121742
121855
  }
121743
121856
  return;
121744
121857
  }
@@ -121765,43 +121878,57 @@ const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
121765
121878
  return;
121766
121879
  // Lowercase tags are usually inline; unwrap a sole paragraph so their
121767
121880
  // phrasing content isn't spuriously block-wrapped.
121881
+ let unwrappedSoleParagraph = false;
121768
121882
  if (!isPascal && parsedChildren.length === 1 && parsedChildren[0].type === 'paragraph') {
121769
121883
  parsedChildren = parsedChildren[0].children;
121884
+ unwrappedSoleParagraph = true;
121885
+ }
121886
+ // Without trailing content the whole node position is correct. With it, end
121887
+ // precisely at the closing tag — preferring source offsets when available (the
121888
+ // node's value strips blockquote/list prefixes), else the consumed span.
121889
+ let endPosition = node.position;
121890
+ if (contentAfterClose) {
121891
+ endPosition = source
121892
+ ? positionEndingAtClosingTagInSource(node.position, closingTagStr, source)
121893
+ : positionEndingAtConsumed(node.position, value, leadingWhitespace + openingTagEnd + closingTagIndex + closingTagStr.length);
121770
121894
  }
121771
121895
  const componentNode = createComponentNode({
121772
121896
  tag,
121773
121897
  attributes,
121774
121898
  children: parsedChildren,
121775
121899
  startPosition: node.position,
121776
- // With trailing content, end precisely at the closing tag. Prefer source
121777
- // offsets when available (the node's value strips blockquote/list
121778
- // prefixes); otherwise fall back to the whole node position.
121779
- endPosition: contentAfterClose
121780
- ? source
121781
- ? positionEndingAtClosingTagInSource(node.position, closingTagStr, source)
121782
- : positionEndingAtConsumed(node.position, value, leadingWhitespace + openingTagEnd + closingTagIndex + closingTagStr.length)
121783
- : node.position,
121900
+ endPosition,
121784
121901
  });
121785
121902
  substituteNodeWithMdxNode(parent, index, componentNode);
121786
- // Re-queue whichever side may hold further components.
121787
- if (contentAfterClose) {
121788
- parseSibling(stack, parent, index, contentAfterClose, safeMode);
121789
- }
121790
- else if (componentNode.children.length > 0) {
121903
+ // The unwrap reparented the children out of their paragraph, so re-walk them
121904
+ // since the children HTML may contain promotable syntax (e.g. `{…}`-attr tags)
121905
+ if (unwrappedSoleParagraph) {
121791
121906
  stack.push(componentNode);
121792
121907
  }
121908
+ // Trailing content after the close becomes siblings; parseMdChildren has
121909
+ // already promoted any components nested inside both sides, so the promoted
121910
+ // subtree itself needs no re-queue.
121911
+ if (contentAfterClose) {
121912
+ parseSibling(parent, index, contentAfterClose, safeMode, promoted);
121913
+ }
121793
121914
  }
121794
121915
  };
121795
- // Depth-first so nodes keep their source order.
121916
+ // Depth-first so nodes keep their source order. Index-based (not forEach) and
121917
+ // re-reading length each step: parseSibling splices siblings in mid-iteration, and
121918
+ // those — plus the original children they shift down — must all stay eligible.
121796
121919
  while (stack.length) {
121797
121920
  const parent = stack.pop();
121798
121921
  if (parent?.children) {
121799
- parent.children.forEach((_child, index) => {
121922
+ for (let index = 0; index < parent.children.length; index += 1) {
121800
121923
  processChildNode(parent, index);
121801
- });
121924
+ }
121802
121925
  }
121803
121926
  }
121804
121927
  return tree;
121928
+ }
121929
+ const mdx_blocks_mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
121930
+ const source = file?.value ? String(file.value) : null;
121931
+ return promoteComponentBlocks(tree, !!opts.safeMode, source);
121805
121932
  };
121806
121933
  /* harmony default export */ const mdx_blocks = (mdx_blocks_mdxishMdxComponentBlocks);
121807
121934
 
@@ -126973,7 +127100,7 @@ function mdxishMdastToMd(mdast) {
126973
127100
  * Processes markdown content with MDX syntax support and returns a HAST.
126974
127101
  * Detects and renders custom component tags from the components hash.
126975
127102
  *
126976
- * @see {@link https://github.com/readmeio/rmdx/blob/main/docs/mdxish-flow.md}
127103
+ * @see .claude/context/MDXish/Processor Overview.md
126977
127104
  */
126978
127105
  function mdxish_mdxish(mdContent, opts = {}) {
126979
127106
  const { components: userComponents = {}, safeMode = false, variables } = opts;
@@ -127257,7 +127384,7 @@ function buildRMDXModule(content, headings, tocHast, opts) {
127257
127384
  * @param opts - The options for the render
127258
127385
  * @returns The React component
127259
127386
  *
127260
- * @see {@link https://github.com/readmeio/rmdx/blob/main/docs/mdxish-flow.md}
127387
+ * @see .claude/context/MDXish/Processor Overview.md
127261
127388
  */
127262
127389
  const renderMdxish = (tree, opts = {}) => {
127263
127390
  const { components: userComponents = {}, variables, ...contextOpts } = opts;