@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.
package/dist/main.node.js CHANGED
@@ -25271,6 +25271,10 @@ const tailwindPrefix = 'readme-tailwind';
25271
25271
 
25272
25272
 
25273
25273
  const TailwindRoot = ({ children, flow }) => {
25274
+ // `styles/gfm.scss` spaces the wrapped block children through this element and
25275
+ // relies on their margins collapsing through it. Keep it a plain block — no
25276
+ // border, padding, or block formatting context (overflow, display: flow-root/
25277
+ // flex/grid) — or the spacing breaks.
25274
25278
  const Tag = flow ? 'div' : 'span';
25275
25279
  return external_react_default().createElement(Tag, { className: tailwindPrefix }, children);
25276
25280
  };
@@ -96525,6 +96529,15 @@ const HTML_TABLE_STRUCTURE_TAGS = new Set([
96525
96529
  'caption',
96526
96530
  'colgroup',
96527
96531
  ]);
96532
+ /**
96533
+ * Tags whose bodies a later mdxish transform keeps raw and never re-parses as
96534
+ * markdown. Both layers depend on this property, not on the tags being figures:
96535
+ * the transformer treats them as non-promotable, and the tokenizer must not claim
96536
+ * markdown islands inside them (a claimed island would never be re-parsed and would
96537
+ * leak as literal text). Currently just the figure tags, whose bodies are owned by
96538
+ * the figure reassembly transform (`mdxishJsxToMdast`).
96539
+ */
96540
+ const NON_REPARSED_BODY_TAGS = new Set(['figure', 'figcaption']);
96528
96541
  /**
96529
96542
  * The two HTML "foreign content" namespaces: SVG and MathML. Their descendants
96530
96543
  * (`<path>`, `<mrow>`, …) are namespaced XML, not HTML tags or components. A closed
@@ -119322,7 +119335,7 @@ function normalizeProperties(node) {
119322
119335
  * Identifies custom MDX components and recursively parses markdown children.
119323
119336
  * Replaces tagName with PascalCase component name for React component resolution.
119324
119337
  *
119325
- * @see {@link https://github.com/readmeio/rmdx/blob/main/docs/mdxish-flow.md}
119338
+ * @see .claude/context/MDXish/Processor Overview.md
119326
119339
  * @param {Options} options - Configuration options
119327
119340
  * @param {CustomComponents} options.components - Available custom components
119328
119341
  * @param {Function} options.processMarkdown - Function to process markdown content
@@ -121722,12 +121735,95 @@ function tokenizeMagicBlockText(effects, ok, nok) {
121722
121735
  */
121723
121736
 
121724
121737
 
121738
+ ;// ./lib/micromark/mdx-component/continuation-checks.ts
121739
+
121740
+
121741
+ /**
121742
+ * Partial constructs the `mdxComponent` tokenizer runs via `effects.check` to
121743
+ * decide whether the next line continues the block. They live here (not in
121744
+ * `syntax.ts`) because they hold none of `createTokenize`'s closure state — each
121745
+ * is a self-contained, single-line lookahead.
121746
+ */
121747
+ function continuation_checks_tokenizeNonLazyContinuationStart(effects, ok, nok) {
121748
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
121749
+ const self = this;
121750
+ return start;
121751
+ function start(code) {
121752
+ if (markdownLineEnding(code)) {
121753
+ effects.enter(types_types.lineEnding);
121754
+ effects.consume(code);
121755
+ effects.exit(types_types.lineEnding);
121756
+ return after;
121757
+ }
121758
+ return nok(code);
121759
+ }
121760
+ function after(code) {
121761
+ if (self.parser.lazy[self.now().line]) {
121762
+ return nok(code);
121763
+ }
121764
+ return ok(code);
121765
+ }
121766
+ }
121767
+ // A markup-only line opens with a tag (`<x`/`</x`) and ends (ignoring trailing
121768
+ // spaces) at a `>`. That distinguishes a structural continuation like
121769
+ // `<span>b</span></div>` from a paragraph like `<b>Note:</b> read *this*`.
121770
+ function tokenizeMarkupOnlyContinuation(effects, ok, nok) {
121771
+ let lastNonSpace = null;
121772
+ return start;
121773
+ function start(code) {
121774
+ // Caller guarantees we are at `<` at the (already de-indented) line start.
121775
+ effects.enter(types_types.data);
121776
+ effects.consume(code);
121777
+ return afterLessThan;
121778
+ }
121779
+ function afterLessThan(code) {
121780
+ if (code === codes.slash) {
121781
+ effects.consume(code);
121782
+ return afterSlash;
121783
+ }
121784
+ return afterSlash(code);
121785
+ }
121786
+ // The `<` (or `</`) must introduce a real tag, not a stray `<` in prose.
121787
+ function afterSlash(code) {
121788
+ if (asciiAlpha(code)) {
121789
+ lastNonSpace = code;
121790
+ effects.consume(code);
121791
+ return scanToLineEnd;
121792
+ }
121793
+ effects.exit(types_types.data);
121794
+ return nok(code);
121795
+ }
121796
+ function scanToLineEnd(code) {
121797
+ if (code === null || markdownLineEnding(code)) {
121798
+ effects.exit(types_types.data);
121799
+ return lastNonSpace === codes.greaterThan ? ok(code) : nok(code);
121800
+ }
121801
+ if (!markdownSpace(code))
121802
+ lastNonSpace = code;
121803
+ effects.consume(code);
121804
+ return scanToLineEnd;
121805
+ }
121806
+ }
121807
+ // A line ending whose next line isn't a lazy paragraph continuation. Checked so a
121808
+ // component body's blank/continuation lines aren't stolen by an interrupted paragraph.
121809
+ const continuation_checks_nonLazyContinuationStart = {
121810
+ tokenize: continuation_checks_tokenizeNonLazyContinuationStart,
121811
+ partial: true,
121812
+ };
121813
+ // Lookahead for `plainClaimLineStart`: is this line markup-only, or a paragraph
121814
+ // that merely starts with a tag? Run via `effects.check` so it never consumes.
121815
+ const markupOnlyContinuation = {
121816
+ tokenize: tokenizeMarkupOnlyContinuation,
121817
+ partial: true,
121818
+ };
121819
+
121725
121820
  ;// ./lib/micromark/mdx-component/syntax.ts
121726
121821
 
121727
121822
 
121728
121823
 
121729
121824
 
121730
121825
 
121826
+
121731
121827
  // Raw tags (type-1: pre/script/style/textarea) and block tags (type-6: div,
121732
121828
  // section, …) always start a block, so they stay flow even with trailing
121733
121829
  // content. Other lowercase tags (i, span, …) follow the type-7 rule and only
@@ -121737,16 +121833,19 @@ const htmlFlowTagNames = new Set([...htmlRawNames, ...htmlBlockNames]);
121737
121833
  // blank lines between nested JSX siblings don't fragment the block. Excludes table
121738
121834
  // tags (mdxishTables owns their blank lines) and voids (never close).
121739
121835
  const plainBlockClaimTagNames = new Set([...htmlBlockNames].filter(tag => !HTML_TABLE_STRUCTURE_TAGS.has(tag) && !HTML_VOID_ELEMENTS.has(tag)));
121740
- const mdx_component_syntax_nonLazyContinuationStart = {
121741
- tokenize: mdx_component_syntax_tokenizeNonLazyContinuationStart,
121742
- partial: true,
121743
- };
121744
- // Lookahead for `plainClaimLineStart`: is this line markup-only, or a paragraph
121745
- // that merely starts with a tag? Run via `effects.check` so it never consumes.
121746
- const markupOnlyContinuation = {
121747
- tokenize: tokenizeMarkupOnlyContinuation,
121748
- partial: true,
121749
- };
121836
+ const foreignContentTags = new Set(FOREIGN_CONTENT_TAGS);
121837
+ // Type-7 lowercase tags (a, span, button, and unknown names): CommonMark ends their
121838
+ // block at a blank line, fragmenting 4+ column children into indented code. Claimable
121839
+ // only in block-wrapper shape (see `blockWrapperOpenerRest`); lookalike openers
121840
+ // (`<https://…>`) never find a closer, so their claim retreats cleanly to CommonMark.
121841
+ // Voids never close and raw/foreign-content bodies have dedicated owners.
121842
+ const isBlockWrapperClaimTagName = (tag) => !htmlFlowTagNames.has(tag) && !HTML_VOID_ELEMENTS.has(tag) && !foreignContentTags.has(tag);
121843
+ // Both are 4 columns per CommonMark, but they mean different things: a tab advances
121844
+ // to the next multiple of TAB_STOP_WIDTH, and INDENTED_CODE_MIN_COLUMNS is the depth
121845
+ // at which a line would fragment into indented code. Named separately so the two
121846
+ // concepts don't read as one incidental literal.
121847
+ const TAB_STOP_WIDTH = 4;
121848
+ const INDENTED_CODE_MIN_COLUMNS = 4;
121750
121849
  function resolveToMdxComponent(events) {
121751
121850
  let index = events.length;
121752
121851
  while (index > 0) {
@@ -121805,6 +121904,23 @@ function createTokenize(mode) {
121805
121904
  // `plainClaimLineStart`: after a blank line it may only continue on a tag line.
121806
121905
  let isPlainBlockClaim = false;
121807
121906
  let pendingBlankLine = false;
121907
+ // Type-7 tag claimed pending the block-wrapper (opener alone on its line) check.
121908
+ let pendingBlockWrapperClaim = false;
121909
+ // True once a block-wrapper claim is confirmed; relaxes the top-of-body island gate
121910
+ // below (type-7 fallback is the fragmentation bug, not intentional indented code).
121911
+ let isBlockWrapperClaim = false;
121912
+ // Leading indent columns of the current plain-claim line, reset per line; ≥4 is
121913
+ // where CommonMark would fragment the island as indented code. Tabs advance to the
121914
+ // next 4-column stop — the same rule `expandIndentToColumns`
121915
+ // (processor/transform/mdxish/indentation.ts) applies, kept in sync by hand since
121916
+ // this side works on a `Code` stream, not a string. NB: do NOT swap this for
121917
+ // `self.now().column`; micromark bumps the point column by 1 per `horizontalTab`
121918
+ // code (the trailing `virtualSpace` codes don't move it), so it measures a tab as
121919
+ // 1 column, reviving the tab-under-measurement bug this math exists to avoid.
121920
+ let plainClaimIndentColumns = 0;
121921
+ // True once a non-blank line follows the opener: a deep island below it is nested
121922
+ // (cosmetic indent), not top-of-body indented code.
121923
+ let sawPlainBlockBodyContent = false;
121808
121924
  // Code span tracking
121809
121925
  let codeSpanOpenSize = 0;
121810
121926
  let codeSpanCloseSize = 0;
@@ -122038,16 +122154,11 @@ function createTokenize(mode) {
122038
122154
  }
122039
122155
  // End of opening tag
122040
122156
  if (code === codes.greaterThan) {
122041
- if (requiresBraceAttr && !sawBraceAttr) {
122042
- // Plain lowercase block tags stay claimable in flow, gated per line by
122043
- // `plainClaimLineStart`; everything else falls through to CommonMark.
122044
- if (!isFlow || !plainBlockClaimTagNames.has(tagName))
122045
- return nok(code);
122046
- isPlainBlockClaim = true;
122047
- }
122157
+ if (requiresBraceAttr && !sawBraceAttr && !claimBraceLessTag())
122158
+ return nok(code);
122048
122159
  effects.consume(code);
122049
122160
  onOpenerLine = isFlow;
122050
- return body;
122161
+ return pendingBlockWrapperClaim ? blockWrapperOpenerRest : body;
122051
122162
  }
122052
122163
  // Quoted attribute value
122053
122164
  if (code === codes.quotationMark || code === codes.apostrophe) {
@@ -122102,9 +122213,38 @@ function createTokenize(mode) {
122102
122213
  // `/ ` without `>` is just part of the attribute area
122103
122214
  return afterOpenTagName(code);
122104
122215
  }
122216
+ // Whether a brace-less lowercase flow tag is claimable: type-6 tags immediately,
122217
+ // type-7 tags pending the block-wrapper check; anything else is CommonMark's.
122218
+ function claimBraceLessTag() {
122219
+ if (!isFlow)
122220
+ return false;
122221
+ if (plainBlockClaimTagNames.has(tagName)) {
122222
+ isPlainBlockClaim = true;
122223
+ return true;
122224
+ }
122225
+ if (isBlockWrapperClaimTagName(tagName)) {
122226
+ pendingBlockWrapperClaim = true;
122227
+ return true;
122228
+ }
122229
+ return false;
122230
+ }
122231
+ // A type-7 tag is claimed only as a block wrapper: opener alone on its line
122232
+ // (trailing spaces ok). Inline content after the opener bails to CommonMark.
122233
+ function blockWrapperOpenerRest(code) {
122234
+ if (markdownSpace(code)) {
122235
+ effects.consume(code);
122236
+ return blockWrapperOpenerRest;
122237
+ }
122238
+ if (!markdownLineEnding(code))
122239
+ return nok(code);
122240
+ pendingBlockWrapperClaim = false;
122241
+ isPlainBlockClaim = true;
122242
+ isBlockWrapperClaim = true;
122243
+ return body(code);
122244
+ }
122105
122245
  // Continuation for multi-line opening tags
122106
122246
  function openTagContinuationStart(code) {
122107
- return effects.check(mdx_component_syntax_nonLazyContinuationStart, openTagContinuationNonLazy, continuationAfter)(code);
122247
+ return effects.check(continuation_checks_nonLazyContinuationStart, openTagContinuationNonLazy, continuationAfter)(code);
122108
122248
  }
122109
122249
  function openTagContinuationNonLazy(code) {
122110
122250
  sawLineEnding = true;
@@ -122149,6 +122289,9 @@ function createTokenize(mode) {
122149
122289
  }
122150
122290
  if (code !== codes.space && code !== codes.horizontalTab) {
122151
122291
  openerLineHasContent = true;
122292
+ // Continuation content marks a later deep island as nested, not indented code.
122293
+ if (!onOpenerLine)
122294
+ sawPlainBlockBodyContent = true;
122152
122295
  }
122153
122296
  if (code === codes.backslash) {
122154
122297
  effects.consume(code);
@@ -122229,7 +122372,7 @@ function createTokenize(mode) {
122229
122372
  return inFencedCode;
122230
122373
  }
122231
122374
  function fencedCodeContinuationStart(code) {
122232
- return effects.check(mdx_component_syntax_nonLazyContinuationStart, fencedCodeContinuationNonLazy, continuationAfter)(code);
122375
+ return effects.check(continuation_checks_nonLazyContinuationStart, fencedCodeContinuationNonLazy, continuationAfter)(code);
122233
122376
  }
122234
122377
  function fencedCodeContinuationNonLazy(code) {
122235
122378
  sawLineEnding = true;
@@ -122437,7 +122580,7 @@ function createTokenize(mode) {
122437
122580
  }
122438
122581
  // ── Body continuation (line endings) ───────────────────────────────────
122439
122582
  function bodyContinuationStart(code) {
122440
- return effects.check(mdx_component_syntax_nonLazyContinuationStart, bodyContinuationNonLazy, continuationAfter)(code);
122583
+ return effects.check(continuation_checks_nonLazyContinuationStart, bodyContinuationNonLazy, continuationAfter)(code);
122441
122584
  }
122442
122585
  function bodyContinuationNonLazy(code) {
122443
122586
  sawLineEnding = true;
@@ -122463,8 +122606,10 @@ function createTokenize(mode) {
122463
122606
  return inBodyBraceString(code);
122464
122607
  return inBodyBraceExpr(code);
122465
122608
  }
122466
- if (isPlainBlockClaim)
122609
+ if (isPlainBlockClaim) {
122610
+ plainClaimIndentColumns = 0;
122467
122611
  return plainClaimLineStart(code);
122612
+ }
122468
122613
  return bodyLineStart(code);
122469
122614
  }
122470
122615
  // Dispatch a non-blank continuation line: fenced code at line start, else body.
@@ -122492,8 +122637,16 @@ function createTokenize(mode) {
122492
122637
  // continue on a tag line (`<…`); any markdown island (`**bold**`, `[block:…]`, a
122493
122638
  // fence) refuses so CommonMark html-flow reparses it exactly as it does today.
122494
122639
  function plainClaimLineStart(code) {
122495
- // Leading whitespace only → treat as a blank line, matching CommonMark.
122496
- if (code === codes.space || code === codes.horizontalTab) {
122640
+ // Leading whitespace only → treat as a blank line, matching CommonMark. A tab
122641
+ // advances to the next stop; its trailing `virtualSpace` fillers add nothing
122642
+ // but must still be consumed or they'd read as line content below.
122643
+ if (markdownSpace(code)) {
122644
+ if (code === codes.horizontalTab) {
122645
+ plainClaimIndentColumns += TAB_STOP_WIDTH - (plainClaimIndentColumns % TAB_STOP_WIDTH);
122646
+ }
122647
+ else if (code === codes.space) {
122648
+ plainClaimIndentColumns += 1;
122649
+ }
122497
122650
  effects.consume(code);
122498
122651
  return plainClaimLineStart;
122499
122652
  }
@@ -122502,10 +122655,19 @@ function createTokenize(mode) {
122502
122655
  effects.exit('mdxComponentData');
122503
122656
  return bodyContinuationStart(code);
122504
122657
  }
122505
- // Across a blank line the block only continues onto a markup-only line; a
122506
- // paragraph that merely starts with a tag must fall back so its markdown
122507
- // parses and rehype-raw re-nests it into the wrapper.
122508
122658
  if (pendingBlankLine) {
122659
+ // A 4+ col island nested under other tags is cosmetic nesting indent, not code:
122660
+ // keep claiming so promotion dedents + re-parses it as markdown (RM-17560).
122661
+ // Block-wrapper claims extend this to top-of-body islands — their CommonMark
122662
+ // fallback is the fragmentation bug, not intentional indented code. Tags whose
122663
+ // bodies stay raw are excluded: a claimed island there would leak as literal text.
122664
+ if (plainClaimIndentColumns >= INDENTED_CODE_MIN_COLUMNS &&
122665
+ (sawPlainBlockBodyContent || isBlockWrapperClaim) &&
122666
+ !NON_REPARSED_BODY_TAGS.has(tagName)) {
122667
+ return plainClaimContinue(code);
122668
+ }
122669
+ // Otherwise only a markup-only tag line continues; markdown/prose falls back to
122670
+ // CommonMark so it parses and rehype-raw re-nests it into the wrapper.
122509
122671
  if (code !== codes.lessThan)
122510
122672
  return nok(code);
122511
122673
  return effects.check(markupOnlyContinuation, plainClaimContinue, nok)(code);
@@ -122526,66 +122688,6 @@ function createTokenize(mode) {
122526
122688
  }
122527
122689
  };
122528
122690
  }
122529
- function mdx_component_syntax_tokenizeNonLazyContinuationStart(effects, ok, nok) {
122530
- // eslint-disable-next-line @typescript-eslint/no-this-alias
122531
- const self = this;
122532
- return start;
122533
- function start(code) {
122534
- if (markdownLineEnding(code)) {
122535
- effects.enter(types_types.lineEnding);
122536
- effects.consume(code);
122537
- effects.exit(types_types.lineEnding);
122538
- return after;
122539
- }
122540
- return nok(code);
122541
- }
122542
- function after(code) {
122543
- if (self.parser.lazy[self.now().line]) {
122544
- return nok(code);
122545
- }
122546
- return ok(code);
122547
- }
122548
- }
122549
- // A markup-only line opens with a tag (`<x`/`</x`) and ends (ignoring trailing
122550
- // spaces) at a `>`. That distinguishes a structural continuation like
122551
- // `<span>b</span></div>` from a paragraph like `<b>Note:</b> read *this*`.
122552
- function tokenizeMarkupOnlyContinuation(effects, ok, nok) {
122553
- let lastNonSpace = null;
122554
- return start;
122555
- function start(code) {
122556
- // Caller guarantees we are at `<` at the (already de-indented) line start.
122557
- effects.enter(types_types.data);
122558
- effects.consume(code);
122559
- return afterLessThan;
122560
- }
122561
- function afterLessThan(code) {
122562
- if (code === codes.slash) {
122563
- effects.consume(code);
122564
- return afterSlash;
122565
- }
122566
- return afterSlash(code);
122567
- }
122568
- // The `<` (or `</`) must introduce a real tag, not a stray `<` in prose.
122569
- function afterSlash(code) {
122570
- if (asciiAlpha(code)) {
122571
- lastNonSpace = code;
122572
- effects.consume(code);
122573
- return scanToLineEnd;
122574
- }
122575
- effects.exit(types_types.data);
122576
- return nok(code);
122577
- }
122578
- function scanToLineEnd(code) {
122579
- if (code === null || markdownLineEnding(code)) {
122580
- effects.exit(types_types.data);
122581
- return lastNonSpace === codes.greaterThan ? ok(code) : nok(code);
122582
- }
122583
- if (!markdownSpace(code))
122584
- lastNonSpace = code;
122585
- effects.consume(code);
122586
- return scanToLineEnd;
122587
- }
122588
- }
122589
122691
  /**
122590
122692
  * Micromark extension that tokenizes MDX-like components.
122591
122693
  *
@@ -122639,6 +122741,7 @@ function mdxComponent() {
122639
122741
 
122640
122742
 
122641
122743
 
122744
+
122642
122745
 
122643
122746
  const buildInlineMdProcessor = (safeMode) => {
122644
122747
  // `jsxTable` must be present so a `<Table>`/`<table>` nested in a component
@@ -122710,7 +122813,7 @@ const toMdxJsxTextElement = (name, attributes, children, position) => ({
122710
122813
  // Raw-body tags (pre/script/style/textarea) must stay byte-exact; table
122711
122814
  // structure (`table` + `tableTags`) is owned by `mdxishTables` and figures by
122712
122815
  // `mdxishJsxToMdast`, both of which run later on raw html nodes.
122713
- const NON_PROMOTABLE_PLAIN_TAGS = new Set([...htmlRawNames, ...tableTags, 'table', 'figure', 'figcaption']);
122816
+ const NON_PROMOTABLE_PLAIN_TAGS = new Set([...htmlRawNames, ...tableTags, 'table', ...NON_REPARSED_BODY_TAGS]);
122714
122817
  const NESTED_TABLE_RE = /<table[\s>]/i;
122715
122818
  const isMarkdownPromotableHtmlTag = (tag) => !NON_PROMOTABLE_PLAIN_TAGS.has(tag);
122716
122819
  // Expression nodes count as plain so `<div>{1+1}</div>` keeps its current
@@ -123069,15 +123172,22 @@ function safeDeindent(text) {
123069
123172
  */
123070
123173
  const parseMdChildren = (value, safeMode) => {
123071
123174
  const parsed = getInlineMdProcessor({ safeMode }).parse(terminateHtmlFlowBlocks(safeDeindent(value).trim()));
123175
+ // Promote nested wrappers bottom-up so an outer wrapper sees markdown buried in a
123176
+ // child claimed whole (e.g. `<li>` in `<ol>`) before its containsMarkdownConstruct check (RM-17560).
123177
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- mutually recursive; hoisted decl, safe at runtime
123178
+ promoteComponentBlocks(parsed, safeMode, null);
123072
123179
  return parsed.children || [];
123073
123180
  };
123074
- // Parses trailing content into sibling nodes and re-queues the parent so any
123075
- // components among them get processed.
123076
- const parseSibling = (stack, parent, index, sibling, safeMode) => {
123181
+ // Splices trailing content in as sibling nodes. parseMdChildren has already
123182
+ // promoted any components nested among them (bottom-up); the main loop's
123183
+ // index-based walk then reaches these spliced siblings and the original children
123184
+ // they shift down, so no parent re-queue is needed. Each spliced subtree is marked
123185
+ // `promoted` so the walk doesn't redundantly re-descend into it (its html is gone).
123186
+ const parseSibling = (parent, index, sibling, safeMode, promoted) => {
123077
123187
  const siblingNodes = parseMdChildren(sibling, safeMode);
123078
123188
  if (siblingNodes.length > 0) {
123079
123189
  parent.children.splice(index + 1, 0, ...siblingNodes);
123080
- stack.push(parent);
123190
+ siblingNodes.forEach(siblingNode => promoted.add(siblingNode));
123081
123191
  }
123082
123192
  };
123083
123193
  // Ends the position at `consumedLength` so the component doesn't claim trailing
@@ -123143,16 +123253,19 @@ const substituteNodeWithMdxNode = (parent, index, mdxNode) => {
123143
123253
  * The opening tag, content, and closing tag are all captured in one HTML node
123144
123254
  * (guaranteed by the mdx-component tokenizer).
123145
123255
  */
123146
- const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
123256
+ function promoteComponentBlocks(tree, safeMode, source) {
123147
123257
  const stack = [tree];
123148
- const safeMode = !!opts.safeMode;
123149
- const source = file?.value ? String(file.value) : null;
123150
123258
  const parseOpts = { preserveExpressionsAsText: safeMode };
123259
+ // Subtrees a nested parseMdChildren already promoted wholesale (spliced siblings):
123260
+ // re-descending them finds no html to promote, so skip them.
123261
+ const promoted = new WeakSet();
123151
123262
  const processChildNode = (parent, index) => {
123152
123263
  const node = parent.children[index];
123153
123264
  if (!node)
123154
123265
  return;
123155
- if ('children' in node && Array.isArray(node.children)) {
123266
+ // Descend into container nodes (lists, blockquotes, …) so their html children
123267
+ // are reached — unless the subtree was already promoted upstream.
123268
+ if ('children' in node && Array.isArray(node.children) && !promoted.has(node)) {
123156
123269
  stack.push(node);
123157
123270
  }
123158
123271
  // Only html nodes can be an unparsed MDX component.
@@ -123207,7 +123320,7 @@ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
123207
123320
  substituteNodeWithMdxNode(parent, index, componentNode);
123208
123321
  const remainingContent = contentAfterTag.trim();
123209
123322
  if (remainingContent) {
123210
- parseSibling(stack, parent, index, remainingContent, safeMode);
123323
+ parseSibling(parent, index, remainingContent, safeMode, promoted);
123211
123324
  }
123212
123325
  return;
123213
123326
  }
@@ -123234,43 +123347,57 @@ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
123234
123347
  return;
123235
123348
  // Lowercase tags are usually inline; unwrap a sole paragraph so their
123236
123349
  // phrasing content isn't spuriously block-wrapped.
123350
+ let unwrappedSoleParagraph = false;
123237
123351
  if (!isPascal && parsedChildren.length === 1 && parsedChildren[0].type === 'paragraph') {
123238
123352
  parsedChildren = parsedChildren[0].children;
123353
+ unwrappedSoleParagraph = true;
123354
+ }
123355
+ // Without trailing content the whole node position is correct. With it, end
123356
+ // precisely at the closing tag — preferring source offsets when available (the
123357
+ // node's value strips blockquote/list prefixes), else the consumed span.
123358
+ let endPosition = node.position;
123359
+ if (contentAfterClose) {
123360
+ endPosition = source
123361
+ ? positionEndingAtClosingTagInSource(node.position, closingTagStr, source)
123362
+ : positionEndingAtConsumed(node.position, value, leadingWhitespace + openingTagEnd + closingTagIndex + closingTagStr.length);
123239
123363
  }
123240
123364
  const componentNode = createComponentNode({
123241
123365
  tag,
123242
123366
  attributes,
123243
123367
  children: parsedChildren,
123244
123368
  startPosition: node.position,
123245
- // With trailing content, end precisely at the closing tag. Prefer source
123246
- // offsets when available (the node's value strips blockquote/list
123247
- // prefixes); otherwise fall back to the whole node position.
123248
- endPosition: contentAfterClose
123249
- ? source
123250
- ? positionEndingAtClosingTagInSource(node.position, closingTagStr, source)
123251
- : positionEndingAtConsumed(node.position, value, leadingWhitespace + openingTagEnd + closingTagIndex + closingTagStr.length)
123252
- : node.position,
123369
+ endPosition,
123253
123370
  });
123254
123371
  substituteNodeWithMdxNode(parent, index, componentNode);
123255
- // Re-queue whichever side may hold further components.
123256
- if (contentAfterClose) {
123257
- parseSibling(stack, parent, index, contentAfterClose, safeMode);
123258
- }
123259
- else if (componentNode.children.length > 0) {
123372
+ // The unwrap reparented the children out of their paragraph, so re-walk them
123373
+ // since the children HTML may contain promotable syntax (e.g. `{…}`-attr tags)
123374
+ if (unwrappedSoleParagraph) {
123260
123375
  stack.push(componentNode);
123261
123376
  }
123377
+ // Trailing content after the close becomes siblings; parseMdChildren has
123378
+ // already promoted any components nested inside both sides, so the promoted
123379
+ // subtree itself needs no re-queue.
123380
+ if (contentAfterClose) {
123381
+ parseSibling(parent, index, contentAfterClose, safeMode, promoted);
123382
+ }
123262
123383
  }
123263
123384
  };
123264
- // Depth-first so nodes keep their source order.
123385
+ // Depth-first so nodes keep their source order. Index-based (not forEach) and
123386
+ // re-reading length each step: parseSibling splices siblings in mid-iteration, and
123387
+ // those — plus the original children they shift down — must all stay eligible.
123265
123388
  while (stack.length) {
123266
123389
  const parent = stack.pop();
123267
123390
  if (parent?.children) {
123268
- parent.children.forEach((_child, index) => {
123391
+ for (let index = 0; index < parent.children.length; index += 1) {
123269
123392
  processChildNode(parent, index);
123270
- });
123393
+ }
123271
123394
  }
123272
123395
  }
123273
123396
  return tree;
123397
+ }
123398
+ const mdxishMdxComponentBlocks = (opts = {}) => (tree, file) => {
123399
+ const source = file?.value ? String(file.value) : null;
123400
+ return promoteComponentBlocks(tree, !!opts.safeMode, source);
123274
123401
  };
123275
123402
  /* harmony default export */ const mdx_blocks = (mdxishMdxComponentBlocks);
123276
123403
 
@@ -127024,7 +127151,7 @@ function mdxishMdastToMd(mdast) {
127024
127151
  * Processes markdown content with MDX syntax support and returns a HAST.
127025
127152
  * Detects and renders custom component tags from the components hash.
127026
127153
  *
127027
- * @see {@link https://github.com/readmeio/rmdx/blob/main/docs/mdxish-flow.md}
127154
+ * @see .claude/context/MDXish/Processor Overview.md
127028
127155
  */
127029
127156
  function mdxish(mdContent, opts = {}) {
127030
127157
  const { components: userComponents = {}, safeMode = false, variables } = opts;
@@ -127364,7 +127491,7 @@ function buildRMDXModule(content, headings, tocHast, opts) {
127364
127491
  * @param opts - The options for the render
127365
127492
  * @returns The React component
127366
127493
  *
127367
- * @see {@link https://github.com/readmeio/rmdx/blob/main/docs/mdxish-flow.md}
127494
+ * @see .claude/context/MDXish/Processor Overview.md
127368
127495
  */
127369
127496
  const renderMdxish = (tree, opts = {}) => {
127370
127497
  const { components: userComponents = {}, variables, ...contextOpts } = opts;